diff --git a/docs/changes.md b/docs/changes.md index e9809e2..5614002 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,5 +1,87 @@ # Changes +## v0.14.0 (May 4 2024) + +Level screenshots and thumbnails: + +* Level files will begin to take thumbnail screenshots of themselves and + store them as PNG images embedded with the level data, and are displayed + on the Story Mode level select screen. + * A thumbnail is saved the first time your level is saved, and can be + viewed and re-computed in the Level->Level Properties window of the + editor. + * Tip: for bounded levels when you want to screenshot the bottom edge, + try setting the page type to Unbounded to get a good screenshot and + then set it back to Bounded before saving the level. +* In the editor, a new Level->Take Screenshot menu item is available which + will save just your editor viewport as a PNG image to your screenshots + folder. +* In the editor, the Level->Giant Screenshot feature has been improved. + * It now will show a confirmation modal, warning that it may take a + while to screenshot a very large level. + * While the giant screenshot is processing, a progress bar modal will + block interaction with the game so that you don't accidentally modify + the level while the screenshot is generating. + +Updates to the JavaScript API for doodad scripts: + +* `setTimeout()` and `setInterval()` will now be more deterministic and + reliable to time your doodad scripts with, as they are now based on the + game's tick speed. At the target average of 60 FPS, 1000 millisecond + timers will fire in 60 game ticks every time. +* The `Self` API now exposes more functions on the underlying Actor object + that points to the current doodad. See the guidebook for full details. + * Self.Doodad() *Doodad + * Self.GetBoundingRect() Rect + * Self.HasGravity() bool + * Self.IsFrozen() bool + * Self.IsMobile() bool + * Self.LayerCount() int + * Self.ListItems() []string + * Self.SetGrounded(bool) + * Self.SetWet(bool) + * Self.Velocity() Point +* Self.GetVelocity() is now deprecated and will be an alias to Self.Velocity. + +Some minor changes: + +* Tweaked the player movement physics and adjusted doodads accordingly. + * The player is given a lower gravity while they are jumping into the air + than the standard (faster) gravity when they are falling. This results + in a smoother jump animation instead of the player launching quickly from + the ground in order to overcome the constant (fast) gravity. + * Coyote time where the player can still jump a few frames late when they + walk off a cliff and jump late. + * Improved character speed when walking up slopes, they will now travel + horizontally at their regular speed instead of being slowed down by level + collision every step of the way. +* Sound effects are now preferred to be in OGG format over MP3 as it is more + reliable to compile the game cross-platform without the dependency on mpg123. +* Fix a bug where level chunks on the far right and bottom edge of the screen + would flicker out of existence while the level scrolls. +* When JavaScript exceptions are caught in doodad scripts, the error message + will now include the Go and JavaScript stack traces to help with debugging. +* The game window maximizes on startup to fill the screen. +* Fixed a few places where the old "Load Level" menu was being called instead + of the fancy new one with the listbox. +* Fixed a touch screen detection bug that was causing the mouse cursor to hide + on Macbooks when using their touchpad. +* Fixed how touch screen mode is activated. The game's mouse cursor will + disappear on touch and reappear when your last finger leaves the screen, and + then a mouse movement is detected. +* Add a `--touch` command line flag to the game binary, which forces touch screen + mode to always be on (which hides the mouse cursor), in case of touch screen + detection errors or annoyances. + +Some code cleanup and architecture changes: + +* Create a clean build process for FOSS versions of the game, which won't + include any license registration code or proprietary features reserved for + first-party releases of the game. +* Dust off the WebAssembly build of the game: thanks to browser innovations + performance is very good! But many UI elements fail to draw properly and it + is not very usable yet. + ## v0.13.2 (Dec 2 2023) This release brings some new features and optimization for the game's file diff --git a/docs/custom-doodads/api-reference.md b/docs/custom-doodads/api-reference.md index 63e49c2..4a50f28 100644 --- a/docs/custom-doodads/api-reference.md +++ b/docs/custom-doodads/api-reference.md @@ -6,72 +6,86 @@ During gameplay, the doodads are called "actors" which are the _instantiated_ fo Table of Contents: -* [Global Functions](#global-functions) - * [EndLevel()](#endlevel) - * [FailLevel()](#faillevelmessage-string) - * [SetCheckpoint()](#setcheckpointpoint) - * [Flash()](#flashmessage-string-args) - * [GetTick()](#gettick-uint64) - * [time.Now()](#timenow-timetime) - * [time.Add()](#timeaddt-timetime-milliseconds-int64-timetime) - * [time.Since()](#timesincetimetime-timeduration) `v0.10.1` -* [Console Logging](#console-logging) -* [Self](#self) - functions for the current (and other) actors - * [Self.ID()](#selfid-string) - * [Self.IsPlayer()](#selfisplayer-bool) `v0.8.0` - * [Self.GetTag()](#selfgettagstring-name-string) - * [Self.Options()](#selfoptions-string) `v0.13.1` - * [Self.GetOption()](#selfgetoptionstring-name-any) `v0.13.1` - * [Self.Position()](#selfposition-point) - * [Self.MoveTo()](#selfmovetopoint) - * [Self.CameraFollowMe()](#selfcamerafollowme) `v0.13.1` - * [Self.SetHitbox()](#selfsethitboxx-y-w-h-int) - * [Self.Hitbox()](#selfhitbox-rect) `v0.8.0` - * [Self.SetVelocity()](#selfsetvelocityvelocity) - * [Self.GetVelocity()](#selfgetvelocity-velocity) `v0.9.0` - * [Self.SetMobile()](#selfsetmobilebool) - * [Self.SetGravity()](#selfsetgravitybool) - * [Self.SetInvulnerable()](#selfsetinvulnerablebool) `v0.11.0` - * [Self.Invulnerable()](#selfinvulnerable-bool) `v0.11.0` - * [Self.Hide(), Self.Show()](#selfhide-selfshow) `v0.9.0` - * [Self.SetInventory()](#selfsetinventorybool) - * [Self.AddItem()](#selfadditemfilename-string-quantity-int) - * [Self.RemoveItem()](#selfremoveitemfilename-string-quantity-int) - * [Self.HasItem()](#selfhasitemfilename-string-bool) - * [Self.Inventory()](#selfinventory-mapstringint) - * [Self.ShowLayer()](#selfshowlayerindex-int) - * [Self.ShowLayerNamed()](#selfshowlayernamedname-string) - * [Self.AddAnimation()](#selfaddanimationname-string-interval-int-layers-list) - * [Self.PlayAnimation()](#selfplayanimationname-string-callback-func) - * [Self.IsAnimating()](#selfisanimating-bool) - * [Self.StopAnimation()](#selfstopanimation) - * [Self.Destroy()](#selfdestroy) -* [Actors](#actors) - actor management functions - * [Actors.At()](#actorsatpoint-actor) `v0.11.0` - * [Actors.FindPlayer()](#actorsfindplayer-actor) `v0.11.0` - * [Actors.New()](#actorsnewfilename-string-actor) `v0.11.0` - * [Actors.SetPlayerCharacter()](#actorssetplayercharacterfilename-string) `v0.11.0` -* [Level](#level) - functions and variables relating to the level - * [Level.Difficulty](#leveldifficulty-int) `v0.12.0` - * [Level.ResetTimer](#levelresettimer) `v0.12.0` -* [Timers and Intervals](#timers-and-intervals) - * [setTimeout()](#settimeoutfunction-milliseconds-int-int) - * [setInterval()](#setintervalfunction-milliseconds-int-int) - * [clearTimeout()](#cleartimeoutid-int) - * [clearInterval()](#clearintervalid-int) -* [Type Constructors](#type-constructors) - native types - * [RGBA(r,g,b,a)](#rgbared-green-blue-alpha-uint8) - * [Point(x,y)](#pointx-y-int) - * [Vector(x,y)](#vectorx-y-float64) -* [Event Handlers](#event-handlers) - * [Events.OnCollide()](#eventsoncollide-funcevent) - * [Events.OnLeave()](#eventsonleave-funcevent) - * [Events.RunKeypress()](#eventsrunkeypress-funcevent) -* [Pub/Sub Communication](#pubsub-communication) - for linked doodads - * [Official, Standard Pub/Sub Messages](#official-standard-pubsub-messages) - * [Message.Publish()](#messagepublishname-string-data) - * [Message.Subscribe()](#messagesubscribename-string-function) - * [Message.Broadcast()](#messagebroadcastname-string-data) +- [JavaScript API](#javascript-api) + - [Global Functions](#global-functions) + - [EndLevel()](#endlevel) + - [FailLevel(message string)](#faillevelmessage-string) + - [SetCheckpoint(Point)](#setcheckpointpoint) + - [Flash(message string, args...)](#flashmessage-string-args) + - [GetTick() uint64](#gettick-uint64) + - [time.Now() time.Time](#timenow-timetime) + - [time.Add(t time.Time, milliseconds int64) time.Time](#timeaddt-timetime-milliseconds-int64-timetime) + - [time.Since(time.Time) time.Duration](#timesincetimetime-timeduration) + - [Console Logging](#console-logging) + - [Self](#self) + - [Self.ID() string](#selfid-string) + - [Self.IsPlayer() bool](#selfisplayer-bool) + - [Self.Doodad() Doodad](#selfdoodad-doodad) + - [Self.GetTag(string name) string](#selfgettagstring-name-string) + - [Self.Options() \[\]string](#selfoptions-string) + - [Self.GetOption(string name) Any](#selfgetoptionstring-name-any) + - [Self.Position() Point](#selfposition-point) + - [Self.MoveTo(Point)](#selfmovetopoint) + - [Self.MoveBy(Point)](#selfmovebypoint) + - [Self.CameraFollowMe()](#selfcamerafollowme) + - [Self.IsOnScreen() bool](#selfisonscreen-bool) + - [Self.SetHitbox(x, y, w, h int)](#selfsethitboxx-y-w-h-int) + - [Self.Hitbox() Rect](#selfhitbox-rect) + - [Self.SetVelocity(Velocity)](#selfsetvelocityvelocity) + - [Self.Velocity() Velocity](#selfvelocity-velocity) + - [Self.SetMobile(bool)](#selfsetmobilebool) + - [Self.IsMobile() bool](#selfismobile-bool) + - [Self.SetGravity(bool)](#selfsetgravitybool) + - [Self.HasGravity() bool](#selfhasgravity-bool) + - [Self.Grounded() bool](#selfgrounded-bool) + - [Self.SetGrounded(bool)](#selfsetgroundedbool) + - [Self.SetWet(bool)](#selfsetwetbool) + - [Self.IsWet() bool](#selfiswet-bool) + - [Self.SetInvulnerable(bool)](#selfsetinvulnerablebool) + - [Self.Invulnerable() bool](#selfinvulnerable-bool) + - [Self.Hide(), Self.Show()](#selfhide-selfshow) + - [Self.Freeze(), Self.Unfreeze()](#selffreeze-selfunfreeze) + - [Self.IsFrozen() bool](#selfisfrozen-bool) + - [Self.SetInventory(bool)](#selfsetinventorybool) + - [Self.AddItem(filename string, quantity int)](#selfadditemfilename-string-quantity-int) + - [Self.RemoveItem(filename string, quantity int)](#selfremoveitemfilename-string-quantity-int) + - [Self.HasItem(filename string) bool](#selfhasitemfilename-string-bool) + - [Self.ListItems() \[\]string](#selflistitems-string) + - [Self.Inventory() map\[string\]int](#selfinventory-mapstringint) + - [Self.LayerCount() int](#selflayercount-int) + - [Self.ShowLayer(index int)](#selfshowlayerindex-int) + - [Self.ShowLayerNamed(name string)](#selfshowlayernamedname-string) + - [Self.AddAnimation(name string, interval int, layers list)](#selfaddanimationname-string-interval-int-layers-list) + - [Self.PlayAnimation(name string, callback func())](#selfplayanimationname-string-callback-func) + - [Self.IsAnimating() bool](#selfisanimating-bool) + - [Self.StopAnimation()](#selfstopanimation) + - [Self.Destroy()](#selfdestroy) + - [Actors](#actors) + - [Actors.At(Point) \[\]\*Actor](#actorsatpoint-actor) + - [Actors.FindPlayer() \*Actor](#actorsfindplayer-actor) + - [Actors.New(filename string) \*Actor](#actorsnewfilename-string-actor) + - [Actors.SetPlayerCharacter(filename string)](#actorssetplayercharacterfilename-string) + - [Level](#level) + - [Level.Difficulty int](#leveldifficulty-int) + - [Level.ResetTimer()](#levelresettimer) + - [Timers and Intervals](#timers-and-intervals) + - [setTimeout(function, milliseconds int) int](#settimeoutfunction-milliseconds-int-int) + - [setInterval(function, milliseconds int) int](#setintervalfunction-milliseconds-int-int) + - [clearTimeout(id int)](#cleartimeoutid-int) + - [clearInterval(id int)](#clearintervalid-int) + - [Type Constructors](#type-constructors) + - [RGBA(red, green, blue, alpha uint8)](#rgbared-green-blue-alpha-uint8) + - [Point(x, y int)](#pointx-y-int) + - [Vector(x, y float64)](#vectorx-y-float64) + - [Event Handlers](#event-handlers) + - [Events.OnCollide( func(event) )](#eventsoncollide-funcevent-) + - [Events.OnLeave( func(event) )](#eventsonleave-funcevent-) + - [Events.RunKeypress( func(event) )](#eventsrunkeypress-funcevent-) + - [Pub/Sub Communication](#pubsub-communication) + - [Official, Standard Pub/Sub Messages](#official-standard-pubsub-messages) + - [Message.Publish(name string, data...)](#messagepublishname-string-data) + - [Message.Subscribe(name string, function)](#messagesubscribename-string-function) + - [Message.Broadcast(name string, data...)](#messagebroadcastname-string-data) * * * @@ -106,6 +120,8 @@ Flash a message on screen to the user. Flashed messages appear at the bottom of the screen and fade out after a few moments. If multiple messages are flashed at the same time, they stack from the bottom of the window with the newest message on bottom. +The message and args follow the Go language's `fmt.Printf` syntax: %s for strings, %d for integers, %+v to stringify structs and objects verbosely. + Don't abuse this feature as spamming it may annoy the player. ### GetTick() uint64 @@ -116,6 +132,10 @@ Returns the current game tick. This value started at zero when the game was laun This exposes the Go standard library function `time.Now()` that returns the current date and time as a Go time.Time value. +🚨 **Race condition warning:** it is not reliable to use wallclock time when you want precise timing in relation to the game state. If the game is running at a slower or faster than usual frame rate, doodad scripts relying on wallclock time may run at the wrong time. + +It is safer to use `GetTick()` for precise timing purposes, as it will be bound to the game's logic tick. + ### time.Add(t time.Time, milliseconds int64) time.Time Add a number of milliseconds to a Go Time value. @@ -148,7 +168,7 @@ The `time` global exposes the following duration intervals as seen in the above ## Console Logging -Like in node.js and the web browser, `console.log` and friends are available for logging from a doodad script. Logs are emitted to the same place as the game's logs are. +Like in node.js and the web browser, `console.log` and friends are available for logging from a doodad script. Logs are emitted to the same place as the game's logs are (e.g. standard output and to your [profile directory](/profile-directory.html)). ```javascript console.log("Hello world!"); @@ -158,7 +178,6 @@ console.warn("Warning-level messages"); console.error("Error-level messages"); ``` - * * * ## Self @@ -182,6 +201,15 @@ Returns the "actor ID" of the doodad instance loaded inside of a level. This is Check if the doodad is the player character. Some enemy creature doodads check this so as to disable their normal A.I. movement pattern and allow player controls to set its animations. +### Self.Doodad() Doodad + +**New in v0.14.0** + +This returns the Doodad object associated with the current actor. A non-exhaustive list of some of its useful properties include: + +* Filename string +* Size Rect + ### Self.GetTag(string name) string Return a "tag" that was saved with the doodad's file data. @@ -230,7 +258,6 @@ var p = Self.Position() console.log("I am at %d,%d", p.X, p.Y) ``` - ### Self.MoveTo(Point) Teleport the current doodad to an exact point on the level. @@ -240,6 +267,13 @@ Teleport the current doodad to an exact point on the level. Self.MoveTo(Point(0, 0)) ``` +### Self.MoveBy(Point) + +Move the current doodad by a relative X,Y position. + +```javascript +Self.MoveBy(Point(5, 0)) +``` ### Self.CameraFollowMe() @@ -249,6 +283,9 @@ Attract the focus of the game's camera to be centered on your doodad, instead of If the player inputs a directional control, they will reclaim the camera's focus. +### Self.IsOnScreen() bool + +Returns true if the actor is currently within the viewport of the level. Doodads may check this to decide whether to play their sound effects or perhaps even pause their logic. ### Self.SetHitbox(x, y, w, h int) @@ -284,7 +321,6 @@ function main() { } ``` - ### Self.Hitbox() Rect **New in v0.8.0** @@ -304,7 +340,6 @@ function main() { } ``` - ### Self.SetVelocity(Velocity) Set the doodad's velocity. Velocity is a type that can be created with the Velocity() constructor, which takes an X and Y value: @@ -313,10 +348,9 @@ Set the doodad's velocity. Velocity is a type that can be created with the Veloc Self.SetVelocity( Velocity(3.2, 7.0) ); ``` - A positive X velocity propels the doodad to the right. A positive Y velocity propels the doodad downward. -### Self.GetVelocity() Velocity +### Self.Velocity() Velocity **New in v0.9.0** @@ -324,6 +358,8 @@ Returns the current velocity of the doodad. Note: for playable characters, velocity is currently managed by the game engine. +**Updated in v0.14.0:** GetVelocity has been renamed to Velocity. + ### Self.SetMobile(bool) Call `SetMobile(true)` if the doodad will move on its own. @@ -336,6 +372,11 @@ Mobile doodads incur extra work for the game doing collision checking so only se Self.SetMobile(true); ``` +### Self.IsMobile() bool + +**New in v0.14.0** + +Returns true if your actor is marked as mobile. ### Self.SetGravity(bool) @@ -348,6 +389,33 @@ Self.SetGravity(true); console.log(Self.HasGravity()); // true ``` +### Self.HasGravity() bool + +**New in v0.14.0** + +Returns true if the actor is affected by gravity. + +### Self.Grounded() bool + +Returns true if the actor is currently standing on solid ground and resisting the pull of gravity. + +### Self.SetGrounded(bool) + +**New in v0.14.0** + +Update the grounded state of the actor. If `true`, the actor will stop being affected by gravity and is able to jump. + +### Self.SetWet(bool) + +**New in v0.14.0** + +Mark the actor as 'wet' as though they are touching water pixels - turning their sprite blue and giving them the ability to 'swim' (or jump infinitely). + +### Self.IsWet() bool + +**New in v0.14.0** + +Returns true if the actor is currently wet. ### Self.SetInvulnerable(bool) @@ -371,6 +439,18 @@ Returns whether the invulnerable flag is currently set on an actor. Hide the current doodad to make it invisible and Show it again. +### Self.Freeze(), Self.Unfreeze() + +These functions will "freeze" your actor: their script is paused and they won't move until unfrozen. If the player is frozen, they don't respond to controls. + +Generally, this will be called on _other_ actors: for example the Warp Door will freeze and hide the player during the animation. + +### Self.IsFrozen() bool + +**New in v0.14.0** + +Returns true if the actor is currently frozen. Note: if Self is frozen then you aren't able to call this - it's more useful to call it on other actors that your script has frozen. + ### Self.SetInventory(bool) Set whether this doodad has an inventory and can carry items. Doodads without inventories can not pick up keys and other items. @@ -382,7 +462,6 @@ Self.SetInventory(true); Self.GetInventory(); // true ``` - ### Self.AddItem(filename string, quantity int) Add an item to the current doodad's inventory. The filename is the name of the item to add, such as "key-blue.doodad" @@ -397,10 +476,22 @@ Remove items from the current doodad's inventory. Tests if the item is in the inventory. +### Self.ListItems() []string + +**New in v0.14.0** + +This lists the items (doodad filenames, like "red-key.doodad") in the actor's inventory. Items that have quantity are only counted once. + ### Self.Inventory() map\[string\]int Returns the doodad's full inventory data, an object that maps filename strings to quantity integers. +### Self.LayerCount() int + +**New in v0.14.0** + +Returns the count of layers in your doodad's drawing. + ### Self.ShowLayer(index int) Switch the active layer of the doodad to the layer at this index. @@ -412,7 +503,6 @@ Self.ShowLayer(0); // 0 is the first and default layer Self.ShowLayer(1); // show the second layer instead ``` - ### Self.ShowLayerNamed(name string) Switch the active layer by name instead of index. @@ -427,7 +517,6 @@ Doodads created by the command-line `doodad` tool will have their layers named a doodad convert door.png open-1.png open-2.png open-3.png my-door.doodad ``` - ### Self.AddAnimation(name string, interval int, layers list) Register a named animation for your doodad. `interval` is the time in milliseconds before going to the next frame. `layers` is an array of layer names or indexes to be used for the animation. @@ -442,7 +531,6 @@ Self.AddAnimation("open", 100, ["open-1", "open-2", "open-3"]); Self.AddAnimation("close", 100, [3, 2, 1]); ``` - ### Self.PlayAnimation(name string, callback func()) This starts playing the named animation. The callback function will be called when the animation has completed. @@ -456,7 +544,6 @@ Self.PlayAnimation("open", function() { }); ``` - ### Self.IsAnimating() bool Returns true if an animation is currently being played. @@ -566,12 +653,16 @@ setTimeout calls your function after the specified number of milliseconds. Returns an integer "timeout ID" that you'll need if you want to cancel the timeout with clearTimeout. +**Updated in v0.14.0:** the milliseconds are no longer based on the wallclock but are translated into a concrete number of game ticks for more deterministic outcomes. 1000 milliseconds translates to 60 game ticks to match the frame rate of 60 FPS. + ### setInterval(function, milliseconds int) int setInterval calls your function repeatedly after every specified number of milliseconds. Returns an integer "interval ID" that you'll need if you want to cancel the interval with clearInterval. +**Updated in v0.14.0:** the milliseconds are no longer based on the wallclock but are translated into a concrete number of game ticks for more deterministic outcomes. 1000 milliseconds translates to 60 game ticks to match the frame rate of 60 FPS. + ### clearTimeout(id int) Cancels the timeout with the given ID. diff --git a/docs/custom-doodads/scripts.md b/docs/custom-doodads/scripts.md index 5b5b6b2..6cfd724 100644 --- a/docs/custom-doodads/scripts.md +++ b/docs/custom-doodads/scripts.md @@ -19,7 +19,7 @@ function main() { // Handle a collision when another doodad (or player) has entered // the space of our doodad. The `e` has info about the event. - Events.OnCollide(function(e) { + Events.OnCollide((e) => { console.log("Actor %s has entered our hitbox!", e.Actor.ID()); // InHitbox is `true` if we defined a hitbox for ourselves, and @@ -42,7 +42,7 @@ function main() { // Subscribe to "broadcast:ready" and don't publish messages // until the game is ready! - Message.Subscribe("broadcast:ready", function() { + Message.Subscribe("broadcast:ready", () => { // It is now safe to publish messages to linked doodads, something that // could have deadlocked otherwise! Message.Publish("ping", null); @@ -50,7 +50,7 @@ function main() { // OnLeave is called when an actor, who was previously colliding with // us, is no longer doing so. - Events.OnLeave(function(e) { + Events.OnLeave((e) => { console.log("Actor %s has stopped colliding!", e.Actor.ID()); }) } @@ -74,8 +74,6 @@ doodad install-script script.js filename.doodad doodad show --script filename.doodad ``` - - ## Testing Your Script The best way to test your doodad script is to use it in a level! @@ -87,633 +85,4 @@ when the level instance of your doodad is initialized. ## JavaScript API -The following global variables are available to all Doodad scripts. - -### Global Functions - -Some useful globally available functions: - -#### EndLevel() - -This ends the current level, i.e. to be used by the goal flag. - -#### FailLevel(message string) - -Trigger a failure condition in the level. For example, a hazardous doodad -can cause a death message as though the player had touched a "fire" pixel -on the level. - -#### SetCheckpoint(Point) - -Set the respawn point for the player character. Usually, this will be -relative to a checkpoint flag's location on the level. - -```javascript -Events.OnCollide(function(e) { - if (e.Settled && e.Actor.IsPlayer()) { - SetCheckpoint(Self.Position()); - } -}) -``` - -#### Flash(message string, args...) - -Flash a message on screen to the user. - -Flashed messages appear at the bottom of the screen and fade out after a few -moments. If multiple messages are flashed at the same time, they stack from the -bottom of the window with the newest message on bottom. - -Don't abuse this feature as spamming it may annoy the player. - -#### GetTick() uint64 - -Returns the current game tick. This value started at zero when the game was -launched and increments every frame while running. - -#### time.Now() time.Time - -This exposes the Go standard library function `time.Now()` that returns the -current date and time as a Go time.Time value. - -#### time.Add(t time.Time, milliseconds int64) time.Time - -Add a number of milliseconds to a Go Time value. - --------- - -### Self - -Self holds data about the current doodad instance loaded inside of a level. -Many of these are available on other actors that collide with your doodad -in the OnCollide handler, at event.Actor. - -**String attributes:** - -* Self.Title: the doodad title. -* Self.Filename: the doodad filename (useful for inventory items). - -Methods are below. - -#### Self.ID() string - -Returns the "actor ID" of the doodad instance loaded inside of a level. This -is usually a random UUID string that was saved with the level data. - -#### Self.IsPlayer() bool - -**New in v0.8.0** - -Check if the doodad is the player character. Some enemy creature doodads check -this so as to disable their normal A.I. movement pattern and allow player -controls to set its animations. - -#### Self.GetTag(string name) string - -Return a "tag" that was saved with the doodad's file data. - -Tags are an arbitrary key/value data store attached to the doodad file. -You can use the `doodad.exe` tool shipped with the game to view and manage tags -on your own custom doodads: - -```bash -# Command-line doodad tool usage: - -# Show information about a doodad, look for the "Tags:" section. -doodad show filename.doodad - -# Set a tag. "-t" for short. -doodad edit-doodad --tag 'color=blue' filename.doodad - -# Set the tag to empty to remove it. -doodad edit-doodad -t 'color=' filename.doodad -``` - -This is useful for a set of multiple doodads to share the same script but -have different behavior depending on how each is tagged. - -#### Self.Position() Point - -Returns the doodad's current position in the level. - -Point is an object with .X and .Y integer values. - -```javascript -var p = Self.Position() -console.log("I am at %d,%d", p.X, p.Y) -``` - -#### Self.Size() Rect - -Returns the dimensions of your doodad's canvas size. - -#### Self.MoveTo(Point) - -Teleport the current doodad to an exact point on the level. - -```javascript -// Teleport to origin. -Self.MoveTo(Point(0, 0)) -``` - -#### Self.SetHitbox(x, y, w, h int) - -Configure the "solid hitbox" of this doodad. - -The X and Y coordinates are relative to the doodad's sprite: (0,0) is the top -left pixel of the doodad. The W and H are the width and height of the hitbox -starting at those coordinates. - -When another doodad enters the area of your doodad's sprite (for example, the -player character has entered the square shape of your doodad sprite) your script -begins to receive OnCollide events from the approaching actor. - -The OnCollide event tells you if the invading doodad is inside your custom -hitbox which you define here (`InHitbox`) making it easy to make choices based -on that status. - -Here's an example script for a hypothetical "locked door" doodad that acts -solid but only on a thin rectangle in the middle of its sprite: - -```javascript -// Example script for a "locked door" -function main() { - // Suppose the doodad's sprite size is 64x64 pixels square. - // The door is in side profile where the door itself ranges from pixels - // (20, 0) to (24, 64) - Self.SetHitbox(20, 0, 4, 64) - - // OnCollide handlers. - Events.OnCollide(function(e) { - // The convenient e.InHitbox tells you if the colliding actor is - // inside the hitbox we defined. - if (e.InHitbox) { - // Return false to protest the collision (act solid). - return false; - } - }); -} -``` - -#### Self.Hitbox() Rect - -**New in v0.8.0** - -Return the current hitbox of your doodad. If you did not call Self.SetHitbox() -yourself, then this will return the hitbox that was configured on the Doodad's -Properties. - -Check Self.Hitbox().IsZero() to see whether the doodad has a hitbox configured -at all (having a value of 0,0,0,0). For example, the generic doodad scripts -run checks like this: - -```javascript -function main() { - // If the doodad does not have a hitbox set, default it to - // the full square canvas size of this doodad. - if (Self.Hitbox().IsZero()) { - var size = Self.Size(); - Self.SetHitbox(0, 0, size, size); - } -} -``` - -#### Self.SetVelocity(Velocity) - -Set the doodad's velocity. Velocity is a type that can be created with the -Velocity() constructor, which takes an X and Y value: - -```javascript -Self.SetVelocity( Velocity(3.2, 7.0) ); -``` - -A positive X velocity propels the doodad to the right. A positive Y velocity -propels the doodad downward. - -#### Self.GetVelocity() Velocity - -**New in v0.9.0** - -Returns the current velocity of the doodad. - -Note: for playable characters, velocity is currently managed by the -game engine. - -#### Self.SetMobile(bool) - -Call `SetMobile(true)` if the doodad will move on its own. - -This is for mobile doodads such as the player character and enemy mobs. -Stationary doodads like buttons, doors, and trapdoors do not mark themselves -as mobile. - -Mobile doodads incur extra work for the game doing collision checking so only -set this to `true` if your doodad will move (i.e. changes its Velocity or -Position). - -```javascript -Self.SetMobile(true); -``` - -#### Self.SetGravity(bool) - -Set whether gravity applies to this doodad. By default doodads are stationary -and do not fall downwards. The player character and some mobile enemies that -want to be affected by gravity should opt in to this. - -```javascript -Self.SetGravity(true); - -// HasGravity to check. -console.log(Self.HasGravity()); // true -``` - -#### Self.Hide(), Self.Show() - -**New in v0.9.0** - -Hide the current doodad to make it invisible and Show it again. - -#### Self.SetInventory(bool) - -Set whether this doodad has an inventory and can carry items. Doodads without -inventories can not pick up keys and other items. - -```javascript -Self.SetInventory(true); -Self.GetInventory(); // true -``` - -#### Self.AddItem(filename string, quantity int) - -Add an item to the current doodad's inventory. The filename is the name of the -item to add, such as "key-blue.doodad" - -If the quantity is zero, the item goes in as a "key item" which does not show -a quantity in your inventory. The four colored keys are examples of this, as -compared to the Small Key which has a quantity. - -#### Self.RemoveItem(filename string, quantity int) - -Remove items from the current doodad's inventory. - -#### Self.HasItem(filename string) bool - -Tests if the item is in the inventory. - -#### Self.Inventory() map[string]int - -Returns the doodad's full inventory data, an object that maps filename strings -to quantity integers. - -#### Self.ShowLayer(index int) - -Switch the active layer of the doodad to the layer at this index. - -A doodad file can contain multiple layers, or images. The first and default -layer is at index zero, the second layer at index 1, and so on. - -```javascript -Self.ShowLayer(0); // 0 is the first and default layer -Self.ShowLayer(1); // show the second layer instead -``` - -#### Self.ShowLayerNamed(name string) - -Switch the active layer by name instead of index. - -Each layer has an arbitrary name that it can be addressed by instead of needing -to keep track of the layer index. - -Doodads created by the command-line `doodad` tool will have their layers named -automatically by their file name. The layer **indexes** will retain the same -order of file names passed in, with 0 being the first file: - -```bash -# Doodad tool-created doodads have layers named after their file names. -# example "open-1.png" will be named "open-1" -doodad convert door.png open-1.png open-2.png open-3.png my-door.doodad -``` - -#### Self.AddAnimation(name string, interval int, layers list) - -Register a named animation for your doodad. `interval` is the time in -milliseconds before going to the next frame. `layers` is an array of layer -names or indexes to be used for the animation. - -Doodads can animate by having multiple frames (images) in the same file. -Layers are ordered (layer 0 is the first, then increments from there) and -each has a name. This function can take either identifier to specify -which layers are part of the animation. - -```javascript -// Animation named "open" using named layers, 100ms delay between frames. -Self.AddAnimation("open", 100, ["open-1", "open-2", "open-3"]); - -// Animation named "close" using layers by index. -Self.AddAnimation("close", 100, [3, 2, 1]); -``` - -#### Self.PlayAnimation(name string, callback func()) - -This starts playing the named animation. The callback function will be called -when the animation has completed. - -```javascript -Self.PlayAnimation("open", function() { - console.log("I've finished opening!"); - - // The callback is optional; use null if you don't need it. - Self.PlayAnimation("close", null); -}); -``` - -#### Self.IsAnimating() bool - -Returns true if an animation is currently being played. - -#### Self.StopAnimation() - -Stops any currently playing animation. - - -* Self.Doodad(): a pointer to the doodad's file data. - * Self.Doodad().Title: get the title of the doodad file. - * Self.Doodad().Author: the name of the author who wrote the doodad. - * Self.Doodad().Script: the doodad's JavaScript source code. Note that - modifying this won't have any effect in-game, as the script had already - been loaded into the interpreter. - * Self.Doodad().GameVersion: the version of {{ app_name }} that was used - when the doodad was created. - -#### Self.Destroy() - -This destroys the current instance of the doodad as it appears in a level. - -For example, a Key destroys itself when it's picked up so that it disappears -from the level and can't be picked up again. Call this function when the -doodad instance should be destroyed and removed from the active level. - ------ - -### Console Logging - -Like in node.js and the web browser, `console.log` and friends are available -for logging from a doodad script. Logs are emitted to the same place as the -game's logs are. - -```javascript -console.log("Hello world!"); -console.log("Interpolate strings '%s' and numbers '%d'", "string", 123); -console.debug("Debug messages shown when the game is in debug mode"); -console.warn("Warning-level messages"); -console.error("Error-level messages"); -``` - ------ - -### Timers and Intervals - -Like in a web browser, functions such as setTimeout and setInterval are -supported in doodad scripts. - -#### setTimeout(function, milliseconds int) int - -setTimeout calls your function after the specified number of milliseconds. - -1000ms are in one second. - -Returns an integer "timeout ID" that you'll need if you want to cancel the -timeout with clearTimeout. - -#### setInterval(function, milliseconds int) int - -setInterval calls your function repeatedly after every specified number of -milliseconds. - -Returns an integer "interval ID" that you'll need if you want to cancel the -interval with clearInterval. - -#### clearTimeout(id int) - -Cancels the timeout with the given ID. - -#### clearInterval(id int) - -Cancels the interval with the given ID. - ------ - -### Type Constructors - -Some methods may need data of certain native types that aren't available in -JavaScript. These global functions will initialize data of the correct types: - -#### RGBA(red, green, blue, alpha uint8) - -Creates a Color type from red, green, blue and alpha values (integers between -0 and 255). - -#### Point(x, y int) - -Creates a Point object with X and Y coordinates. - -#### Vector(x, y float64) - -Creates a Vector object with X and Y dimensions. - ------ - -### Event Handlers - -Doodad scripts can respond to certain events using functions on the global -`Events` variable. - -#### Events.OnCollide( func(event) ) - -OnCollide is called when another actor is colliding with your doodad's sprite -box. The function is given a CollideEvent object which has the following -attributes: - -* Actor: the doodad which is colliding with your doodad. -* Overlap (Rect): a rectangle of where the two doodads' boxes are overlapping, - relative to your doodad sprite's box. That is, if the Actor was moving in from - the left side of your doodad, the X value would be zero and W would be the - number of pixels of overlap. -* InHitbox (bool): true if the colliding actor's hitbox is intersecting with - the hitbox you defined with SetHitbox(). -* Settled (bool): This is `false` when the game is trying to move the colliding - doodad and is sussing out whether or not your doodad will act solid and - protest its movement. When the game has settled the location of the colliding - doodad it will call OnCollide a final time with Settled=true. If your doodad - has special behavior when touched (i.e. a button that presses in), you should - wait until Settled=true before running your handler for that. - -To contest the collision (behave as a solid object), `return false` from -the OnCollide handler. You can do this while Settled=false to behave as a -solid obstacle and prevent a doodad from intersecting your hitbox. - -#### Events.OnLeave( func(event) ) - -Called when an actor that _was_ colliding with your doodad is no longer -colliding (or has left your doodad's sprite box). - -The event argument is the same as OnCollide, with the Actor available -and Settled=true (others left as default zero values). - -#### Events.OnKeypress( func(event) ) - -Handle a keypress. `event` is an `event.State` from the render engine with -attributes like Up, Down, Left, Right (arrow keys being pressed), Enter, -Escape and some functions like KeyDown("F1"). - -Player character doodads may monitor for keypresses to update their animation -and walk in the correct direction. The game engine **only** delivers Keypress -events to the player character. - -Don't get too creative with this function, in case the way player characters -handle their behaviors is updated in the future. Generally, only the Up, Down, -Left and Right attributes should be relied upon -- touch controls and joysticks -emulate these 'keys' for player movement. **All other keys** are not guaranteed -to function in future releases of the game! - -```javascript -Events.OnKeypress(ev => { - let walking = ev.Right || ev.Left, - jumping = ev.Up; -}); -``` - ------ - -### Pub/Sub Communication - -Doodads in a level are able to send and receive messages to other doodads, -either those that they are **linked** to or those that listen on a more -'broadcast' frequency. - -> **Linking** is when the level author connected two doodads together with -> the Link Tool. The two doodads' scripts can communicate with each other -> in-game over that link. - -For example, if the level author links a Button to an Electric Door, the button -can send a "power" event to the door so that it can open when a player touches -the button. - -Doodads communicate in a "publisher/subscriber" model: one doodad publishes an -event with a name and data, and other doodads subscribe to the named event to -receive that data. - -#### Official, Standard Pub/Sub Messages - -The following message names and data types are used by the game's default -doodads. You're free to use these in your own custom doodads. - -If extending this list with your own custom events, be careful to choose a -unique namespace to prevent collision with other users' custom doodads and -their custom event names. - -| Name | Data Type | Description | -|------|-----------|--------------| -| power | boolean | Communicates a "powered" (true) or "not powered" state, as in a Button to an Electric Door. | -| broadcast:ready | (none) | The level is ready and it is now safe for doodads to publish messages to others. | -| broadcast:state-change | boolean | An "ON/OFF" button was hit and all state blocks should flip. | -| broadcast:checkpoint | string | A checkpoint flag was reached. Value is the actor ID of the checkpoint flag. | -| sticky:down | boolean | A sticky button is pressed Down. If linked to other normal buttons, it tells them to press down as well. Sends a `false` when the Sticky Button itself pops back up. | -| switch:toggle | boolean | A switch has been toggled from on to off. | - -#### Message.Publish(name string, data...) - -Publish a named message to all of your **linked** doodads. - -`data` is a list of arbitrary arguments to send with the message. - -```javascript -// Example button doodad that emits a "power" (bool) state to linked doodads -// that subscribe to this event. -function main() { - // When an actor collides with the button, emit a powered state. - Events.OnCollide(function(e) { - if (e.Settled) { - Message.Publish("power", true); - } - }); - - // When the actor leaves the button, turn off the power. - Events.OnLeave(function(e) { - Message.Publish("power", false); - }) -} -``` - -#### Message.Subscribe(name string, function) - -Subscribe to a named message from any **linked** doodads. - -The function receives the data that was passed with the message. Its data type -is arbitrary and will depend on the type of message. - -```javascript -// Example electronic device doodad that responds to power from linked buttons. -function main() { - // Boolean to store if our electric device has juice. - var powered = false; - - // Do something while powered - setInterval(function() { - if (powered) { - console.log("Brmm...") - } - }, 1000); - - // Subscribe to the `power` event by a linked button or other power source. - Message.Subscribe("power", function(boolValue) { - console.log("Powered %s!", boolValue === true ? "on" : "off"); - powered = boolValue; - }); -} -``` - -#### Message.Broadcast(name string, data...) - -This publishes a named message to **every** doodad in the level, whether it -was linked to the broadcaster or not. - -For example the "ON/OFF" button globally toggles a boolean state in every -state block that subscribes to the `broadcast:state-change` event. - -If you were to broadcast an event like `power` it would activate every single -power-sensitive doodad on the level. - -```javascript -// Example two-state block that globally receives the state-change broadcast. -function main() { - var myState = false; - Message.Subscribe("broadcast:state-change", function(boolValue) { - // Most two-state blocks just flip their own state, ignoring the - // boolValue passed with this message. - myState = !myState; - }); -} - -// Example ON/OFF block that emits the state-change broadcast. It also -// subscribes to the event to keep its own state in sync with all the other -// ON/OFF blocks on the level when they get hit. -function main() { - var myState = false; - - // Listen for other ON/OFF button activations to keep our state in - // sync with theirs. - Message.Subscribe("broadcast:state-change", function(boolValue) { - myState = boolValue; - }); - - // When collided with, broadcast the state toggle to all state blocks. - Events.OnCollide(function(e) { - if (e.Settled) { - myState = !!myState; - Message.Broadcast("broadcast:state-change", myState); - } - }) -} -``` +Please see the [Script API Reference](api-reference.md) for full details. \ No newline at end of file diff --git a/docs/custom-levels/levelpacks.md b/docs/custom-levels/levelpacks.md index 63cbd5d..aeb29ed 100644 --- a/docs/custom-levels/levelpacks.md +++ b/docs/custom-levels/levelpacks.md @@ -19,55 +19,6 @@ Custom doodads: * (In the future) A level pack can hold a folder of custom doodads that will be used by its levels. Instead of each level needing to [publish](publishing.md) (embed) its doodads individually, the level pack holds onto them so multiple levels can use them while saving on disk space! * **Note:** [Free (shareware)](../register.md) versions of the game fully support custom Level Packs, but they don't support embedded custom doodads. Level Packs that use only the built-in doodads work perfectly, and in case you're using custom doodads, the player may manually install the custom doodads in their [profile directory](../profile-directory.md) and play it that way. -## Structure of a Level Pack - -A levelpack is basically a ZIP file that contains levels, doodads, and an index.json that describes the level pack. To create a levelpack by hand, just create a correctly formatted ZIP file and rename the .zip extension to .levelpack. See [Creating a Level Pack](#creating-a-level-pack), below: the doodad tool makes this much easier! - -The contents of a .levelpack ZIP archive are like follows: - - levels/ - First.level - Second.level - Third.level - doodads/ - Example.doodad - index.json - - -The `levels` folder holds your .level files and the `doodads` folder holds custom .doodad files. The doodads folder is optional and doesn't need to be included if there are no custom doodads. - -The index.json file describes the levelpack. At time of writing it is structured like the following: - - { - "Title": "Tutorial", - "description": "Learn how to play the game.", - "author": "Noah P", - "created": "2021-12-27T04:45:29.186345525Z", - "levels": [ - { - "title": "Lesson 1: Controls", - "author": "Noah P", - "filename": "Tutorial 1.level" - }, - { - "title": "Lesson 2: Keys & Doors", - "author": "Noah P", - "filename": "Tutorial 2.level" - }, - { - "title": "Lesson 3: Gizmos and Doodads", - "author": "Noah P", - "filename": "Tutorial 3.level" - } - ], - "freeLevels": 0 - } - - -The `levels` array describes the levels and their sort order. It is possible for .level files to exist in the levelpack which _are not_ described in the index.json; these levels will not be reachable in-game but in the future this may support hiding bonus levels linked to by other levels in the pack. - -**TO DO:** the game does not yet do anything with the `doodads` folder. In the future, this folder will help a group of levels embed a custom doodad _once_ in the levelpack instead of each keeping a separate copy. - ## Creating a Level Pack The easiest way to create a .levelpack file is with the [doodad tool](../doodad-tool.md) that shipped with the game. @@ -124,3 +75,51 @@ When playing a Story Mode level from a levelpack, the "Edit" button may be click You are free to do this and learn from the levels you enjoy, and this won't harm the original levels at all! You can save a copy of the level, make changes to it and playtest your version of the level. +## Structure of a Level Pack + +A levelpack is basically a ZIP file that contains levels, doodads, and an index.json that describes the level pack. To create a levelpack by hand, just create a correctly formatted ZIP file and rename the .zip extension to .levelpack. See [Creating a Level Pack](#creating-a-level-pack), below: the doodad tool makes this much easier! + +The contents of a .levelpack ZIP archive are like follows: + + levels/ + First.level + Second.level + Third.level + doodads/ + Example.doodad + index.json + + +The `levels` folder holds your .level files and the `doodads` folder holds custom .doodad files. The doodads folder is optional and doesn't need to be included if there are no custom doodads. + +The index.json file describes the levelpack. At time of writing it is structured like the following: + + { + "Title": "Tutorial", + "description": "Learn how to play the game.", + "author": "Noah P", + "created": "2021-12-27T04:45:29.186345525Z", + "levels": [ + { + "title": "Lesson 1: Controls", + "author": "Noah P", + "filename": "Tutorial 1.level" + }, + { + "title": "Lesson 2: Keys & Doors", + "author": "Noah P", + "filename": "Tutorial 2.level" + }, + { + "title": "Lesson 3: Gizmos and Doodads", + "author": "Noah P", + "filename": "Tutorial 3.level" + } + ], + "freeLevels": 0 + } + + +The `levels` array describes the levels and their sort order. It is possible for .level files to exist in the levelpack which _are not_ described in the index.json; these levels will not be reachable in-game but in the future this may support hiding bonus levels linked to by other levels in the pack. + +**TO DO:** the game does not yet do anything with the `doodads` folder. In the future, this folder will help a group of levels embed a custom doodad _once_ in the levelpack instead of each keeping a separate copy. \ No newline at end of file diff --git a/docs/custom-levels/publishing.md b/docs/custom-levels/publishing.md index d550d56..1f9ca1c 100644 --- a/docs/custom-levels/publishing.md +++ b/docs/custom-levels/publishing.md @@ -28,8 +28,9 @@ The Publish window will show the list of named doodads which currently exist in your level, designating which doodads are _custom_ and which were built-in with the game (the built-in doodads are shown in blue text plus an asterisk* symbol). -The blue **Export Level** button will prompt for you to give a file name, and -the level will be written there _with_ all of its custom doodads embedded inside. +Make sure the box to "Attach custom doodads when I save the level" is enabled, +and click on the "Save Level Now" button. The custom doodads will be embedded +into the level file on each save while this box is enabled. Optionally, you can choose to "Attach built-in doodads too" -- this will attach copies of the built-in doodads to your level, too, which will override the game's diff --git a/docs/hacking.md b/docs/hacking.md index e257fc7..5d6a852 100644 --- a/docs/hacking.md +++ b/docs/hacking.md @@ -4,6 +4,12 @@ This page discusses some advanced features of the game and targets a more techni I've always loved it when developers keep debugging features in their released games, and playing around with those and figuring out what makes the game tick. I purposely left some debug features in the game that you can play around with. +## Log File + +The game emits logs during its runtime to standard output. Launching the game from within a terminal on Linux or macOS will show the logs there, color coded by error level. The log from the most recent run of the game is also saved to your [profile directory](/profile-directory.html), which may be the only way to see the logs on a Windows machine. + +When custom doodad scripts use `console.log()` and similar, their logs are also written to this file, and it can be useful when debugging your custom doodad scripts. + ## Debug Features Pressing `F3` within the game will draw the **Debug Overlay** on top of the screen, displaying details such as the game's frames per second and some contextual details like: what is the world index of the pixel below your mouse cursor, while you're editing a level? (Details such as this are also seen in the status bar at the bottom of the editor screen). diff --git a/docs/images/custom-wallpaper-ex.png b/docs/images/custom-wallpaper-ex.png index b0d4980..c78eae4 100644 Binary files a/docs/images/custom-wallpaper-ex.png and b/docs/images/custom-wallpaper-ex.png differ diff --git a/docs/images/custom-wallpaper.png b/docs/images/custom-wallpaper.png index 7522ac0..fb5ab12 100644 Binary files a/docs/images/custom-wallpaper.png and b/docs/images/custom-wallpaper.png differ diff --git a/docs/images/levelpack.png b/docs/images/levelpack.png index 208bfe7..eaf1f73 100644 Binary files a/docs/images/levelpack.png and b/docs/images/levelpack.png differ diff --git a/docs/images/main-menu.png b/docs/images/main-menu.png index 1725ec8..53bd4fb 100644 Binary files a/docs/images/main-menu.png and b/docs/images/main-menu.png differ diff --git a/docs/images/newlevel-1.png b/docs/images/newlevel-1.png index e17c71f..31f1f5b 100644 Binary files a/docs/images/newlevel-1.png and b/docs/images/newlevel-1.png differ diff --git a/docs/images/palette-rgba.png b/docs/images/palette-rgba.png index 3f92ff7..c657ec2 100644 Binary files a/docs/images/palette-rgba.png and b/docs/images/palette-rgba.png differ diff --git a/docs/images/patterns.png b/docs/images/patterns.png index 4e37d90..8b51d34 100644 Binary files a/docs/images/patterns.png and b/docs/images/patterns.png differ diff --git a/docs/images/publish.png b/docs/images/publish.png index 4cbc9d2..20061cc 100644 Binary files a/docs/images/publish.png and b/docs/images/publish.png differ diff --git a/docs/images/scoring.png b/docs/images/scoring.png index ce96904..f03681f 100644 Binary files a/docs/images/scoring.png and b/docs/images/scoring.png differ diff --git a/docs/index.md b/docs/index.md index 19c64dc..e6b5abe 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ [Sketchy Maze](about.md) is a drawing-based maze game themed around hand-drawn maps on paper. You can draw a level for a 2D platformer game, drag-and-drop "doodads" such as buttons and doors into your level, play it and share your levels with others. -**Last Updated:** Dec 2, 2023 for Sketchy Maze v0.13.2. +**Last Updated:** May 4, 2024 for Sketchy Maze v0.14.0. ## Table of Contents diff --git a/docs/profile-directory.md b/docs/profile-directory.md index 4a65158..a77ebfa 100644 --- a/docs/profile-directory.md +++ b/docs/profile-directory.md @@ -26,6 +26,7 @@ There are a few interesting folders: And some interesting files: +* **logfile.txt:** holds the log output from your last run of the game. * **settings.json:** holds your game settings and is unique to your installation of the game. * **savegame.json:** holds your progress through Story Mode level packs including high scores on the levels. * **license.key:** if you have [registered the game](register.md) this is a copy of your license key file. diff --git a/docs/story-mode.md b/docs/story-mode.md index 6b5d883..83eda49 100644 --- a/docs/story-mode.md +++ b/docs/story-mode.md @@ -23,6 +23,9 @@ Some Story Mode campaigns have locked levels and only the earliest levels are pl You may complete a level by using cheat codes, e.g. for noclip and going straight for the level goal. This will unlock the next level, but you won't be able to record a High Score for completing a level using cheats. +* The cheat code `master key` will let you play any level, even if it's not unlocked yet. +* During gameplay, the cheat code `warp whistle` will "complete" the current level but not set a high score. + ## Azulian Tag **New in v0.12.0**