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!
* Adds walking animations for the Thief.
* Mobile doodads no longer moonwalk: their A.I. used to wait for the
animation to finish before setting the appropriate animation, so when
it changed directions it would "moonwalk" for a time. Their A.I. is
now updated to cancel the animation if they change directions so to
immediately play the correct animation.
The Doodad Properties window brings many features that used to be
available only in the `doodad` CLI tool into the Doodad Editor.
* In the Doodad Editor there is a new menubar item: "Doodad" which
corresponds to the "Level" menu when you're editing a level.
* The "Doodad" menu has two items:
- "Doodad Properties" (NEW)
- "Layers" (moved here from the Tools menu)
* The Doodad Properties window lets you edit the Title and Author values
of the doodad, as well as modify its Tags and manage its Script.
* Its script can be attached (browse for .js file on disk), its existing
script saved back to disk (dev shell prompt) or deleted altogether
from the doodad.
* You can create, modify, and delete Tags on the doodad.
Other changes:
* In the Level Editor, the "Level->Page Settings" menu is renamed to
"Level->Level Properties" to match with "Doodad->Doodad Properties"
and the pop-up window is retitled accordingly.
* The Exit Flag only exits if the Player touches it - not just any
mobile doodad!
* New Doodad: Checkpoint Flag. They update the player's spawn point
whenever the player passes one. The most recently activated
checkpoint is rendered brighter than the others.
* End Level Modal: the fake alert box window drawn by the Play Mode
is replaced with a fancy modal widget (similar to Alert and Confirm).
It handles level victory or failure conditions and can show or hide
all the buttons as needed.
* Gameplay: There is a "Retry from Checkpoint" option added, which
appears in the level failure modal. It will teleport you back to
the Start Flag or the last Checkpoint Flag you had touched, without
resetting the level -- your keys, unlocked doors, etc. will be
preserved so you can retry.
* Set a maximum speed on the "Camera Follows Actor" logic of 64
pixels per tick. This results in a smoother scrolling transition
when the player jumps to a new location on the map, such as by
a Warp Door.
* Update the default color palettes:
* All: Add a "hint" magenta color.
* Colored Pencil: Add a "darkstone" solid color.
Updates to the Doodads JavaScript API:
* SetCheckpoint(Point(x, y)): set the player character's spawn
position. Giving it Self.Position() is an easy way to set the
player spawn to your doodad's location.
* The Thief's ability is now available to the player character in levels
where you'll play as the Thief.
* The Thief is able to steal items from all characters it contacts,
including Azulians and other Thieves.
* A.I. Thieves will not steal items from each other, to prevent loops.
Only a Thief controlled by the player will steal items from a Thief.
New feature: link a Start Flag to another doodad in your level
and you will play as that doodad instead of Boy. All Creatures
are designed to be playable. Playing as "other" doodads leads
to interesting effects, like not being able to activate buttons,
switches, or warp doors and not having an inventory to pick up
keys. The Anvil is fun: it can destroy other mobile doodads by
jumping on them.
If the actor does not specify that it has gravity, the gameplay
starts in antigravity mode. This will be the vast majority of
non-mobile doodads and the Bird.
Other changes:
* The Blue and Red Azulians now share a doodad script.
* The Azulians AI is still to walk back and forth, pickup keys and
press buttons. The Blue Azulian walks slower than the red one.
* The Blue Azulian is no longer hidden from the doodads list.
* Actor UUID values in levels are now V1 UUIDs (time-ordered).
This will help to reliably resolve conflicts in draw order
of overlapping doodads (newest added to level wins).
* Link Tool: clicking on a pair of already-linked doodads will
now unlink them, so you don't have to delete one to delete
the link.
* Actor Tool: deleting an actor immediately calls PruneLinks()
to clean up any links that the deleted doodad might have.
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!
* The Anvil doodad is affected by gravity and becomes dangerous when
falling. If it lands on the player character, you die! If it lands on
any other mobile doodad, it destroys it! It can land on solid doodads
such as the Electric Trapdoor and the Crumbly Floor. It will activate
a Crumbly Floor if it lands on one, and can activate buttons and
switches that it passes.
* JavaScript API: FailLevel(message) can be called from a doodad to kill
the player character. The Anvil does this if it collides with the
player while it's been falling.
* 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.
* Install the new ui.TabFrame widget into the Settings and Doodad
Dropper windows to give them properly tabbed interfaces.
* Doodad Dropper's new tabs divide the list of doodads into categories
to make them easier to find.
* The officially defined categories so far are:
- Objects (Start/End Flags and Box)
- Doors (All locked doors and keys, Warp Doors, and Electric Door)
- Gizmos (All buttons, switches, state blocks/doors, Electric Door)
- Creatures (Blue/Red Azulian, Bird, Boy)
* The "All" tab of the Doodad Dropper will show every doodad regardless
of its category or whether it fit one of the official categories.
* How doodads are assigned categories is by a special "category" tag in
their metadata, e.g. "category=doors,gizmos" - multiple supported.
The Box can be pushed around by the player or other mobile doodads.
It is affected by gravity and can be pushed off ledges.
If the player gets under a box they can bump it up with their head.
* The F4 key to draw collision boxes works reliably again: it draws the
player's hitbox in world-space using the canvas.DrawStrokes()
function, rather than in screen-space so it follows the player
reliably.
* The F4 key also draws hitboxes for ALL other actors in the level:
buttons, enemies, doors, etc.
* The level geometry collision function is updated to respect a doodad's
declared Hitbox from their script, which may result in a smaller box
than their raw Canvas size. The result is tighter collision between
doodads, and Boy's sprite is rather narrow for its square Canvas so
collision on rightward geometry is tighter for the player character.
* Collision checks between actors also respect the actor's declared
hitboxes now, allowing for Boy to get even closer to a locked door
before being blocked.
New doodad interactions:
* Sticky Buttons will emit a "sticky:down" event to linked doodads, with
a boolean value showing the Sticky Button's state.
* Normal Buttons will listen for "sticky:down" -- when a linked Sticky
Button is pressed, the normal Button presses in as well, and stays
pressed while the sticky:down signal is true.
* When the Sticky Button is released (e.g. because it received power
from another doodad), any linked buttons which were sticky:down
release as well.
* Switch doodads emit a new "switch:toggle" event JUST BEFORE sending
the "power" event. Sensitive Doodads can listen for switches in
particular this way.
* The Electric Door listens for switch:toggle; if a Switch is activated,
the Electric Door always flips its current state (open to close, or
vice versa) and ignores the immediately following power event. This
allows doors to toggle on/off regardless of sync with a Switch.
Other changes:
* When the player character dies by fire, instead of the message saying
"Watch out for fire!" it will use the name of the fire swatch that
hurt the player. This way levels could make it say "Watch out for
spikes!" or "lava" or whatever they want. The "Fire" attribute now
just means "instantly kills the player."
* Level Editor: You can now edit the Title and Author name of your level
in the Page Settings window.
* Bugfix: only the player character ends the game by dying in fire.
Other mobile doodads just turn dark but don't end the game.
* Increase the size of Trapdoor doodad sprites by 150% as they were a
bit small for the player character.
* Rename the game from "Project: Doodle" to "Sketchy Maze"
* The crumbly floor doodad was made 50% larger.
* New doodad: Small Key and Small Key Door. These work like the colored
doors and locks except each Small Key is consumed when it unlocks a
door. The door's appearance is of iron bars.
* The inventory HUD displays a small quantity label in the lower-right
corner of items that have a quantity, such as the Small Key. This is
done as a Canvas.CornerLabel string attribute on uix.Canvas.
* The "give all keys" cheat adds 99 Small Keys to your inventory.
* 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.
* The blue and orange ON/OFF state blocks have all been increased in
size to better match the player character (42x42 up from 33x33)
* Added a new mob: the Red Bird. It flies back and forth while
maintaining its altitude, similar to the Red Azulian. Planned AI
behavior is to divebomb the player when it gets close. Dive sprites
are included but not yet hooked up in JavaScript.
* Warp Doors! (WIP). They have a golden "W" on them and come in three
varieties: Brown, Blue and Orange. The blue and orange ones are
sensitive to the State Block and will become dotted outlines when
inactive (and can not be entered in this state). The door opens for
the player character, makes him disappear, then closes again. The plan
is it will then warp you to the location of a linked Warp Door
elsewhere on the level, but for now it will just make the player
re-appear after completing the Close Door animation.
* The colored locked doors and the Electric Door are increased in size
to better match Boy's sprite size.
* Colored doors now have a "locked" and "unlocked" state when closed;
when locked, a gold padlock hangs on the door with a keyhole shaped to
match the corresponding Colored Key.
* Added initial walking sprites for the player character, "Boy."
* Player doodad filename and title screen level are now configurable in
the balance/numbers.go package.
Adds support for sound effects in Doodle and configures some for various
doodads to start out with:
* Buttons and Switches: "Clicked down" and "clicked up" sounds.
* Colored Doors: an "unlocked" sound and a "door opened" sound.
* Electric Door: sci-fi sounds when opening and closing.
* Keys: sound effect for collecting keys.
JavaScript API for Doodads adds a global function `Sound.Play(filename)`
to play sounds. All sounds in the `rtp/sfx/` folder are pre-loaded on
startup for efficient use in the app. Otherwise sounds are lazy-loaded
on first playback.
* Tightens up the surface area of API methods available to the
JavaScript VMs for doodads. Variables and functions are carefully
passed in one-by-one so the doodad script can only access intended
functions and not snoop on undocumented APIs.
* Wrote tons of user documentation for Doodad Scripts: documented the
full surface area of the exposed JavaScript API now that the surface
area is known and limited.
* Early WIP code for the Campaign JSON
* Player character now experiences acceleration and friction when
walking around the map!
* Actor position and movement had to be converted from int's
(render.Point) to float64's to support fine-grained acceleration
steps.
* Added "physics" package and physics.Vector to be a float64 counterpart
for render.Point. Vector is used for uix.Actor.Position() for the sake
of movement math. Vector is flattened back to a render.Point for
collision purposes, since the levels and hitboxes are pixel-bound.
* Refactor the uix.Actor to no longer extend the doodads.Drawing (so it
can have a Position that's a Vector instead of a Point). This broke
some code that expected `.Doodad` to directly reference the
Drawing.Doodad: now you had to refer to it as `a.Drawing.Doodad` which
was ugly. Added convenience method .Doodad() for a shortcut.
* Moved functions like GetBoundingRect() from doodads package to
collision, where it uses its own slimmer Actor interface for just the
relevant methods it needs.
* Added an inventory system for actors as a replacement to the arbitrary
key/value data store. Colored keys now add themselves to the player's
inventory, and colored doors check the inventory.
* Inventory is a map[string]int between doodad filenames
(red-key.doodad) and quantity (0 for key items/unlimited qty).
* API methods to add and remove inventory.
* Items HUD appears in Play Mode in lower-left corner showing doodad
sprites of all the items in the Player's inventory.
* Revamped the sprites for the four colored locked doors. They now have
a side-view profile perspective rather than a front view.
* Doors open facing the left or the right based on what direction the
colliding actor approached it from.
* The `doodad edit-doodad` command now allows setting custom key/value
tags in doodad files, for extra data storage useful to their scripts.
* Colored keys and doors now store a `color` tag with the appropriate
color so that their scripts don't have to parse their Title to find
that information.
* Trapdoors now store a `direction` tag to hold the direction the door
is facing.
* Recent collision update caused a regression where the player would get
"stuck" while standing on top of a solid doodad, unable to walk left
or right.
* When deciding if the actor is on top of a doodad, use the doodad's
Hitbox (if available) instead of the bounding box. This fixes the
upside-down trapdoor acting solid when landed on from the top, since
its Hitbox Y coordinate is not the same as the top of its sprite.
* Cheats: when using the noclip cheat in Play Mode, you can hold down
the Shift key while moving to only move one pixel at a time.
* Two-state Buttons now also subscribe to the state change message, so
other on/off buttons in the same level update to match the state of
the button that was hit.
* Add lock mutexes around the scripting engine to protect from
concurrent event handlers.
Add new doodads:
* Start Flag: place this in a level to set the spawn point of the player
character. If no flag is found, the player spawns at 0,0 in the top
corner of the map. Only use one Start Flag per level, otherwise the
player will randomly spawn at one of them.
* Crumbly Floor: a solid floor that begins to shake and then fall apart
after a moment when a mobile character steps on it. The floor respawns
after 5 seconds.
* State Blocks: blue and orange blocks that toggle between solid and
pass-thru whenever a State Button is activated.
* State Button: a solid "ON/OFF" block that toggles State Blocks back
and forth when touched. Only activates if touched on the side or bottom;
acts as a solid floor when walked on from the top.
New features for doodad scripts:
* Actor scripts: call SetMobile(true) to mark an actor as a mobile mob
(i.e. player character or enemy). Other doodads can check if the actor
colliding with them IsMobile so they don't activate if placed too close
to other (non-mobile) doodads in a level. The Blue and Red Azulians
are the only mobile characters so far.
* Message.Broadcast allows sending a pub/sub message out to ALL doodads
in the level, instead of only to linked doodads as Message.Publish does.
This is used for the State Blocks to globally communicate on/off status
without needing to link them all together manually.
* Discovered a bug where if you hit the Undo key to erase pixels and an
entire chunk became empty by it, the chunk would have rendering errors
and show as a solid black square instead of the level wallpaper
showing through.
* Chunks that have no pixels in them are culled from the chunker
immediately when you call a Delete() operation.
* The level file saver also calls a maintenance function to prune all
empty chunks upon saving the file. So existing levels with broken
chunks need only be re-saved to fix them.
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
* Update the Makefile to choose MacOS friendly `date` formats.
* Build the Windows doodle.exe binary as a GUI application to skip the
console window.
* Added Mac OS build instructions.
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
* Add the other trapdoor directions: Left, Right and Up.
* UI: Show a color square in each Palette Swatch button in Edit Mode.
* Instead of just the label like "solid", "fire", "decoration" it also
shows a square box colored as the swatch color. The label and box
are left-aligned in the button.
* Minor Play Mode physics update:
* The player jump is now limited: they may only continue to move
upwards for 20 ticks, after which they must touch ground before
jumping again.
* Remove the "press Down to move down" button. Only gravity moves you
down.
* Fix a crash in the Editor Mode when you dragged doodads on top of each
other. Source of bug was the loopActorCollision() function, which only
should be useful to Play Mode, and it expected the scripting engine to
be attached to the Canvas. In EditorMode there is no scripting engine.
* Add a Level Exit doodad, which for now is a little blue flag on a pole
that reads "END"
* JavaScript API: global function EndLevel() will end the level. The
exit doodad calls this when touched by the player.
* Add a "Level Completed" alert box UI to PlayScene with dynamic button
layouts.
* The alert box pops up when a doodad calls EndLevel() and contains
action buttons what to do next.
* "Play Again" restarts the current level again.
* "Edit Level" if you came from the EditorScene; otherwise this button
is not visible.
* "Next Level" is a to-be-implemented button to advance in the single
player story mode. Only shows up when PlayScene.HasNext=true.
* "Exit to Menu" is always visible and closes out to the MainScene.
* Refactor texture caching in render.Engine:
* New interface method: NewTexture(filename string, image.Image)
* WASM immediately encodes the image to PNG and generates a JavaScript
`Image()` object to load it with a data URI and keep it in memory.
* SDL2 saves the bitmap to disk as it did before.
* WASM: deprecate the sessionStorage for holding image data. Session
storage methods panic if called. The image data is directly kept in
Go memory as a js.Value holding an Image().
* Shared Memory workaround: the level.Chunk.ToBitmap() function is where
chunk textures get cached, but it had no access to the render.Engine
used in the game. The `pkg/shmem` package holds global pointers to
common structures like the CurrentRenderEngine as a work-around.
* Also shmem.Flash() so Doodle can make its d.Flash() function
globally available, any sub-package can now flash text to the screen
regardless of source code location.
* JavaScript API for Doodads now has a global Flash() function
available.
* WASM: Handle window resize so Doodle can recompute its dimensions
instead of scaling/shrinking the view.
* Implement the pub/sub message passing system that lets the JavaScript
VM of one actor (say, a Button) send messages to other linked actors
in the level (say, an Electric Door)
* Buttons now emit a "power(true)" message while pressed and
"power(false)" when released. Sticky Buttons do not release and so do
not send the power(false) message.
* Electric Doors listen for the "power" event and open or close
themselves based on the boolean value received.
* If a Sticky Button receives power and is currently pressed down, it
will pop back up (reset to "off" position) and notify its linked
actors that they have lost power too. So if a Sticky Button held an
Electric Door open, and another Button powers the Sticky Button, it
would pop back up and also close the Electric Door.
* Implement the handler code for `return false` when actors are
colliding with each other and wish to act like solid walls.
* The locked doors will `return false` when they're closed and the
colliding actor does not have the matching key.
* Add arbitrary key/value storage to Actors. The colored keys will set
an actor value "key:%TITLE%" on the one who touched the key before
destroying itself. The colored doors check that key when touched to
decide whether to open.
* The trapdoor now only opens if you're touching it from the top (your
overlap box Y value is 0), but if you touch it from below and the door
is closed, it acts like a solid object.
* Events.OnCollide now receives a CollideEvent object, which makes
available the .Actor who collided and the .Overlap rect which is
zero-relative to the target actor. Doodad scripts can use the .Overlap
to see WHERE in their own box the other actor has intruded.
* Update the LockedDoor and ElectricDoor doodads to detect when the
player has entered their inner rect (since their doors are narrower
than their doodad size)
* Update the Button doodads to only press in when the player actually
touches them (because their sizes are shorter than their doodad
height)
* Update the Trapdoor to only trigger its animation when the board
along its top has been touched, not when the empty space below was
touched from the bottom.
* Events.OnLeave now implemented and fires when an actor who was
previously intersecting your doodad has left.
* The engine detects when an event JS callback returns false.
Eventually, the OnCollide can return false to signify the collision is
not accepted and the actor should be bumped away as if they hit solid
geometry.
* Add a Red Azulian as a test for mobile enemies.
* Its A.I. has it walk back and forth, changing directions when it
comes up against an obstacle for a few moments.
* It plays walking animations and can trigger collision events with
other Doodads, such as the Electric Door and Trapdoor.
* Move Gravity responsibility to the doodad scripts themselves.
* Call `Self.SetGravity(true)` to opt the Doodad in to gravity.
* The canvas.Loop() adds gravity to any doodad that has it enabled.
* Add animation support for Doodad actors (Play Mode) into the core
engine, so that the Doodad script can register named animations and
play them without managing all the details themselves.
* Doodad API functions on Self: AddAnimation, PlayAnimation,
StopAnimation, IsAnimating
* CLI: the `doodad convert` command will name each layer after the
filename used as the input image.
* CLI: fix the `doodad convert` command creating duplicate Palette
colors when converting a series of input images into a Doodad.
* Add some encoding/decoding functions for binary msgpack format for
levels and doodads. Currently it writes msgpack files that can be
decoded and printed by Python (mp2json.py) but it can't re-read from
the binary format. For now, levels will continue to write in JSON
format.
* Add filesystem abstraction functions to the balance/ package to search
multiple paths to find Levels and Doodads, to make way for
system-level doodads.