Compare commits

...

161 Commits

Author SHA1 Message Date
1f00af5741 Collision Detection Fix + Doodad CLI Fixes
Fix collision detection when an actor's hitbox is offset from 0,0:

* Actors having a hitbox that didn't begin at X,Y 0,0 used to experience
  clipping issues with level geometry: because the game tracks their Position
  (top left corner of their graphical sprite) and their target position wasn't
  being correctly offset by their hitbox offset.
* To resolve the issue, an "ActorOffset" struct is added: you give it the
  original game's Actor (with its offset hitbox) and it will record the offset
  and give a mocked Actor for collision detection purposes: where the Position
  and Target can be offset and where its Hitbox claims to begin at 0,0 matching
  its offsetted Position.
* The translation between your original Actor and Offset Actor is handled at the
  boundary of the CollidesWithGrid function, so the main algorithm didn't need
  to be messed with and the game itself doesn't need to care about the offset.

Make some fixes to the doodad CLI tool:

* Fix palette colors being duplicated/doubled when converting from an image.
* The --palette flag in `doodad convert` now actually functions: so you can
  supply an initial palette.json with colors and attributes to e.g. mark which
  colors should be solid or fire and give them names. The palette.json doesn't
  need to be comprehensive: it will be extended with new distinct colors as
  needed during the conversion.
2024-05-27 15:14:00 -07:00
90414609a9 Update doodad tool documentation 2024-05-24 21:05:54 -07:00
57b757a378 Merge pull request 'RLE Compression for File Formats' (#95) from rle-compression into master
Reviewed-on: #95
2024-05-24 23:47:59 +00:00
f35bc48c05 Code cleanup for RLE compression 2024-05-24 16:43:11 -07:00
c7a3c7a797 Remove never-used GridType accessor + documentation [PTO]
* Add documentation for the game's file formats and RLE encoding
* Remove the never-used GridType Chunk Accessor constant
2024-05-24 16:06:43 -07:00
6be2f86b58 RLE Encoding Code Cleanup [PTO]
* For the doodad tool: skip the assets embed folder, the doodad binary doesn't
  need to include all the game's doodads/levelpacks/etc. and can save on file
  size.
* In `doodad resave`, .doodad files with Vacuum() and upgrade their chunker from
  the MapAccessor to the RLEAccessor.
* Fix a rare concurrent map read/write error in OptimizeChunkerAccessors.
2024-05-24 15:03:32 -07:00
4851730ccf Fix RLE Encoding Off-by-One Errors [PTO]
Levels can now be converted to RLE encoded chunk accessors and be re-saved
continuously without any loss of information.

Off-by-one errors resolved:

* The rle.NewGrid() was adding a +1 everywhere making the 2D grids have 129
  elements to a side for a 128 chunk size.
* In rle.Decompress() the cursor value and translation to X,Y coordinates is
  fixed to avoid a pixel going missing at the end of the first row (128,0)
* The abs.X-- hack in UnmarshalBinary is no longer needed to prevent the
  chunks from scooting a pixel to the right on every save.

Doodad tool updates:

* Remove unused CLI flags in `doodad resave` (actors, chunks, script,
  attachment, verbose) and add a `--output` flag to save to a different file
  name to the original.
* Update `doodad show` to allow debugging of RLE compressed chunks:
    * CLI flag `--chunk=1,2` to specify a single chunk coordinate to debug
    * CLI flag `--visualize-rle` will Visualize() RLE compressed chunks in
      their 2D grid form in your terminal window (VERY noisy for large
      levels! Use the --chunk option to narrow to one chunk).

Bug fixes and misc changes:

* Chunk.Usage() to return a better percentage of chunk utilization.
* Chunker.ChunkFromZipfile() was split out into two functions:
    * RawChunkFromZipfile retrieves the raw bytes of the chunk as well as the
      file extension discovered (.bin or .json) so the caller can interpret
      the bytes correctly.
    * ChunkFromZipfile calls the former function and then depending on file
      extension, unmarshals from binary or json.
    * The Raw function enables the `doodad show` command to debug and visualize
      the raw contents of the RLE compressed chunks.
* Updated the Visualize() function for the RLE encoder: instead of converting
  palette indexes to hex (0-F) which would begin causing problems for palette
  indexes above 16 (as they would use two+ characters), indexes are mapped to
  a wider range of symbols (0-9A-Z) and roll over if you have more than 36
  colors on your level. This at least keeps the Visualize() grid an easy to
  read 128x128 characters in your terminal.
2024-05-24 13:54:41 -07:00
5654145fd8 (Experimental) Run Length Encoding for Levels
Finally add a second option for Chunk MapAccessor implementation besides the
MapAccessor. The RLEAccessor is basically a MapAccessor that will compress
your drawing with Run Length Encoding (RLE) in the on-disk format in the ZIP
file.

This slashes the file sizes of most levels:

* Shapeshifter: 21.8 MB -> 8.1 MB
* Jungle: 10.4 MB -> 4.1 MB
* Zoo: 2.8 MB -> 1.3 MB

Implementation details:

* The RLE binary format for Chunks is a stream of Uvarint pairs storing the
  palette index number and the number of pixels to repeat it (along the Y,X
  axis of the chunk).
    * Null colors are represented by a Uvarint that decodes to 0xFFFF
      or 65535 in decimal.
    * Gameplay logic currently limits maps to 256 colors.
* The default for newly created chunks in-game will be RLE by default.
* Its in-memory representation is still a MapAccessor (a map of absolute
  world coordinates to palette index).
* The game can still open and play legacy MapAccessor maps.
* On save in the editor, the game will upgrade/convert MapAccessor chunks over
  to RLEAccessors, improving on your level's file size with a simple re-save.

Current Bugs

* On every re-save to RLE, one pixel is lost in the bottom-right corner of
  each chunk. Each subsequent re-save loses one more pixel to the left, so what
  starts as a single pixel per chunk slowly evolves into a horizontal line.
* Some pixels smear vertically as well.
* Off-by-negative-one errors when some chunks Iter() their pixels but compute
  a relative coordinate of (-1,0)! Some mismatch between the stored world coords
  of a pixel inside the chunk vs. the chunk's assigned coordinate by the Chunker:
  certain combinations of chunk coord/abs coord.

To Do

* The `doodad touch` command should re-save existing levels to upgrade them.
2024-05-23 23:02:01 -07:00
b1d7c7a384 WIP Run Length Encoding for Levels 2024-05-23 19:15:10 -07:00
57c76de679 Remove deprecated go build -i flag for Windows 2024-05-05 16:49:11 -07:00
9dfd38ea5e Remove redundant make setup step 2024-05-05 16:34:27 -07:00
35962540e1 Fix blockers for v0.14.0 release
* Mac app: exclude the redundant copy of rtp/ folder in the .dmg disk
  image as it only needs to live inside the .app bundle.
* Fix the settings.json file to be initialized and saved to disk on
  first launch of the game, and the crosshair color default.
* Fix the chdir detection in main.go especially to locate the rtp/
  folder inside the macOS app bundle.
2024-05-05 15:59:56 -07:00
e20c694d93 Log game output to disk, update deps for 1.14.0 2024-05-04 19:32:40 -07:00
8595ad0eba Prepare v1.14.0 for release 2024-05-04 18:26:32 -07:00
866e5e7fd8 Deterministic JavaScript intervals + Code Cleanup
* Remove several unused functions in doodad.Drawing (velocity, acceleration,
  grounded, etc.) - uix.Actor is where these are actually managed.
* In the JavaScript API, setTimeout() and setInterval() will translate the
  milliseconds from wallclock time into a fixed number of game ticks to match
  the target frame rate for better deterministic timing.
2024-04-27 00:10:28 -07:00
c4456ac51b Scripting: Bring Self API up to Actor API parity
Updates the Self API to expose more uix.Actor fields:

* Doodad()
* GetBoundingRect()
* HasGravity()
* IsFrozen()
* IsMobile()
* LayerCount()
* ListItems()
* SetGrounded()
* SetWet()
* Velocity() - note: it was Self.GetVelocity() before.

Self.GetVelocity is deprecated and made an alias to Velocity.
2024-04-26 22:34:13 -07:00
21847f5e57 Code cleanup for TouchScreenMode 2024-04-19 23:13:32 -07:00
b8665c8b8d TouchScreenMode Fix
Made some fixes to touchscreen control detection:

* TouchScreenMode is activated on the first SDL2 FingerDown
* TouchScreenMode deactivates after the last finger is removed, and a
  mouse event happens at least 5 ticks later.
2024-04-19 22:42:47 -07:00
5b3121171e Fix touchscreen mode detection
* Touchscreen mode used to be detected based on SDL2 GetNumTouchDevices
  but on a Macbook, the trackpad registers as a touch device - worse,
  GetNumTouchDevices will only start returning 1 the first time some
  devices are touched.
* The result was that on macOS the custom mouse cursor was drawn by
  default, but on the first trackpad touch, would disappear in favor of
  assuming the game is running on a touch screen device (which is not
  the case).
* New method: the render engine has an IsFingerDown boolean which will
  be true as long as at least one finger has registered a FingerDown
  event, but not yet a FingerUp event.
* So as long as one finger is down, the mouse cursor can disappear and
  then it comes back on release. This isn't perfectly ideal for pure
  touch devices (ideally the cursor remains hidden until a mouse
  movement without touch occurs).
2024-04-19 22:01:33 -07:00
f2a20808ea Fix bootstrap.py script 2024-04-18 23:07:25 -07:00
9e90ea4c6c Update Go dependencies 2024-04-18 22:50:18 -07:00
3cdd56424a Merge pull request 'WIP Doodle++' (#93) from dpp into master
Reviewed-on: #93
2024-04-19 05:49:40 +00:00
33dc17bb19 Doodle++ Code Cleanup 2024-04-18 22:49:12 -07:00
a79601f983 D++ Default Author and Embedded Doodads Error
* Update native.DefaultAuthor to get the name registered from the user's JWT
  license in a way that avoids cyclic dependency errors.
* When plus_dpp.go#GetRegistration succeeds, it updates DefaultAuthor to the
  registered name. The main.go now gets and prints the registered owner to
  ensure this is populated on startup.
* Return correct ErrRegisteredFeature error when the FOSS version fails
  to load embedded doodads.
2024-04-18 22:31:11 -07:00
a06787411d Resolve circular import errors for Doodle++ plugin
* pkg/plus/dpp is the main plugin bridge, and defines nothing but an interface
  that defines the Doodle++ surface area (referring to internal game types such
  as doodad.Doodad or level.Level), but not their implementations.
  * dpp.Driver (an interface) is the main API that other parts of the game will
    call, for example "dpp.Driver.IsLevelSigned()"
  * plus_dpp.go and plus_foss.go provide the dpp.Driver implementation for their
    build; with plus_dpp.go generally forwarding function calls directly to the
    proprietary dpp package and plus_foss.go generally returning false/errors.
  * The bootstrap package simply assigns the above stub function to dpp.Driver
* pkg/plus/bootstrap is a package directly imported by main (in the doodle and
  doodad programs) and it works around circular dependency issues: this package
  simply assigns dpp.Driver to the DPP or FOSS version.

Miscellaneous fixes:

* File->Open in the editor and PlayScene will use the new Open Level window
  instead of loading the legacy GotoLoadMenu scene.
* Deprecated legacy scenes: d.GotoLoadMenu() and d.GotoPlayMenu().
* The doodle-admin program depends on the private dpp package, so can not be
  compiled in FOSS mode.
2024-04-18 22:12:56 -07:00
7eb7f6148c WIP Doodle++ 2024-04-18 20:23:07 -07:00
f4ef0f8d8f Fix doodad properties button hitbox 2024-02-11 17:00:19 -08:00
6def8f7625 Walk up slopes smoothly, texture freeing improvement
* Fix collision detection to allow actors to walk up slopes smoothly, without
  losing any horizontal velocity.
* Fix scrolling a level canvas so that chunks near the right or bottom edge
  of the viewpoint were getting culled prematurely.
* Centralize JavaScript exception catching logic to attach Go and JS stack
  traces where possible to be more useful for debugging.
* Performance: flush all SDL2 textures from memory between scene transitions
  in the app. Also add a `flush-textures` dev console command to flush the
  textures at any time - they all should regenerate if still needed based on
  underlying go.Images which can be garbage collected.
2024-02-07 22:14:48 -08:00
85523d8311 Coyote time 2024-02-06 20:56:07 -08:00
8216e5863b Tweak gravity and player physics 2024-02-06 19:04:47 -08:00
6fc5900131 Update Go dependencies 2023-12-21 21:09:25 -08:00
bb28b990e6 Winres config 2023-12-21 20:17:49 -08:00
1a9706c09f Level Thumbnails on Story Mode Select
* Rework the Story Mode UI to display level thumbnails.
  * Responsive UI: defaults to wide screen mode and shows 3 levels horizontally
    but on narrow/mobile display, shows 2 levels per page in portrait.
  * Add "Tiny" screenshot size (224x126) to fit the Story Mode UI.
  * Make the pager buttons bigger and more touchable.
* Maximize the game window on startup unless the -w option with a specific
  window resolution is provided.
2023-12-09 14:59:31 -08:00
9cce93f431 Dust off WASM build support 2023-12-08 21:52:34 -08:00
da83231559 Level Screenshots and Thumbnails
Adds some support for "less giant" level screenshots.

* In the Editor, the Level->Take Screenshot menu will render a cropped screen
  shot of just the level viewport on screen. Note: it is not an SDL2 screen
  copy but generated from scratch from the level data.
* In levels themselves, screenshots can be stored inside the level data in
  three different sizes: large (1280x720), medium and small (each a halved
  size of the previous).
* The first screenshot is created when the level is saved, starting from
  wherever the scroll position in the editor is at, and recording the 720p
  view of the level from there.
* The level screenshot can be previewed and updated in the Level Properties
  window of the editor: so you can scroll the editor to just the right position
  and take a good screenshot to represent your level.
* In the future: these embedded level screenshots will be displayed on the
  Story Mode and other screens to see a preview of each level.

Other tweaks:

* When taking a Giant Screenshot: a confirm modal will warn the player that
  it may take a while. And during the screenshot, show the new Wait Modal to
  block player interaction until the screenshot has finished.
2023-12-08 19:48:02 -08:00
481638bea6 PlaySound: Support OGG fallback over MP3 2023-12-02 14:15:41 -08:00
282229ba80 Prepare v1.13.2 for release 2023-12-02 12:46:17 -08:00
ffb9068fb6 Unit test fixes and code cleanup 2023-12-02 12:33:14 -08:00
79996ccd34 ListBoxes Overhaul
* 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.
2023-04-08 21:26:08 -07:00
cf1bc81f25 Update savegame format, Allow out-of-bounds camera
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.
2023-03-07 21:55:10 -08:00
d397584323 Update changelog 2023-02-18 17:50:08 -08:00
82884c79ae Signed Levels and Levelpacks
Add the ability for the free version of the game to allow loading levels that
use embedded custom doodads if those levels are signed.

* Uses the same signing keys as the JWT token for license registrations.
* Levels and Levelpacks can both be signed. So individual levels with embedded
  doodads can work in free versions of the game.
* Levelpacks now support embedded doodads properly: the individual levels in
  the pack don't need to embed a custom doodad, but if the doodad exists in
  the levelpack's doodads/ folder it will load from there instead - for full
  versions of the game OR when the levelpack is signed.

Signatures are computed by getting a listing of embedded assets inside the
zipfile (the assets/ folder in levels, and the doodads/ + levels/ folders
in levelpacks). Thus for individual signed levels, the level geometry and
metadata may be changed without breaking the signature but if custom doodads
are changed the signature will break.

The doodle-admin command adds subcommands to `sign-level` and `verify-level`
to manage signatures on levels and levelpacks.

When using the `doodad levelpack create` command, any custom doodads the
levels mention that are found in your profile directory get embedded into
the zipfile by default (with --doodads custom).
2023-02-18 17:37:54 -08:00
856de848c9 Add cheat code to send power to all actors 2023-02-18 14:21:07 -08:00
03cd1d4ca0 Binary format for chunks in zipfiles 2023-02-18 12:45:36 -08:00
0d8933513e Merge pull request 'Chunker size to uint8 and Rectangular Doodads' (#84) from file-format-optimization into master
Reviewed-on: #84
2023-02-18 05:49:48 +00:00
1e37509421 Update README and fix perfect run icon display 2023-02-17 21:49:19 -08:00
31097881ff Finalize Non-square Doodads
* Fix display bug with rectangular doodads scrolling off screen.
* The default Author of new files will be your registration name, if available
  before using your $USER name.
2023-02-17 21:09:11 -08:00
ddcad27485 WIP: Chunker size to uint8 and Rectangular Doodads
Convert the Chunker size to a uint8 so chunk sizes are limited to 255px. This
means that inside of a chunk, uint8's can track the relative pixel coordinates
and result in a great memory savings since all of these uint8's are currently
64-bits wide apiece.

WIP on rectangular shaped doodads:
* You can create such a doodad in the editor and draw it normally.
* It doesn't draw the right size when dragged into your level however:
  - In uix.Actor.Size() it gets a rect of the doodad's square Chunker size,
    instead of getting the proper doodad.Size rect.
  - If you give it the doodad.Size rect, it draws the Canvas size correctly
    instead of a square - the full drawing appears and in gameplay its hitbox
    (assuming the same large rectangle size) works correctly in-game.
  - But, the doodad has scrolling issues when it gets to the top or left edge
    of the screen! This old gnarly bug has come back. For some reason square
    canvas doodads draw correctly but rectangular ones have the drawing scroll
    just a bit - how far it scrolls is proportional to how big the doodad is,
    with the Start Flag only scrolling a few pixels before it stops.
2023-02-16 21:47:18 -08:00
a10a09a818 Cheats Menu UI
* Added a Cheats Menu UI accessible from the Settings window's "Experimental"
  tab and from there you can enable the Cheats Menu from the "Help" screen of
  the gameplay mode.
* Commonly used cheats all have corresponding buttons to click on, especially
  helpful for touchscreen devices like the Pinephone where keyboard input
  doesn't always work reliably.
* The buttons in the Cheats Menu just automate entry of the cheat commands.
* `boolProp` command has a new `flip` option to toggle their value (e.g.
  `boolProp show-hidden-doodads flip`)
2023-01-02 12:36:12 -08:00
06dd30893c Editor: Allow using doodad settings buttons in Pan Tool 2022-12-08 20:03:53 -08:00
cbc8682406 Dockerfile, AppImage Release
* Add a Dockerfile to this repo for self-contained easy releases.
  Run it from an x86_64 Linux host and it will produce 64-bit and
  32-bit Linux (rpm, deb, AppImage, tar.gz) and Windows releases.
* The `make appimage` command is more self-sufficient: it will
  download the appimagetool-x86_64.AppImage program for your $ARCH
  for an easy no-dependencies run after you have run `make dist`
2022-12-08 19:15:48 -08:00
56c03dda45 Unpin goja and fix callback registry functions 2022-10-10 19:14:38 -07:00
48d1f2c3b7 Pin github.com/dop251/goja v0.0.0-20220501172647-e1eca0b61fa9
The newer goja caused problems calling RunCollide or RunKeypress on
doodad scripts - resulting in a broken player character and no collision
events running on doodad scripts. Investigate later.
2022-10-10 13:43:41 -07:00
e330a7b6bb Update dependencies for v0.13.1 2022-10-10 13:28:04 -07:00
2dd6b5e34b Update default level palettes for new pixel attributes
* Default palette: adds "semisolid"
* Colored Pencil: adds "planks" (semisolid) and "ice" (slippery)
* Neon Bright: make "electric" semisolid and add "ice blue"
* Blueprint: make "electric" semisolid and add "ice" (slippery)
2022-10-10 11:17:11 -07:00
8b5dab6d6f Slippery Pixels + Update Changelog for 0.13.1 2022-10-10 10:52:28 -07:00
ecaa8c6cef SemiSolid Pixels + Icons
* Add new pixel attributes: SemiSolid and Slippery (the latter is WIP)
* SemiSolid pixels are only solid below the player character. You can walk on
  them and up and down SemiSolid slopes, but can freely pass through from the
  sides or jump through from below.
* Update the Palette Editor UI to replace the Attributes buttons: instead of
  text labels they now have smaller icons (w/ tooltips) for the Solid,
  SemiSolid, Fire, Water and Slippery attributes.
* Bugfix in Palette Editor: use cropped (24x24) images for the Tex buttons so
  that the large Bubbles texture stays within its designated space!
* uix.Actor.SetGrounded() to also set the Y velocity to zero when an actor
  becomes grounded. This fixes a minor bug where the player's Y velocity (due
  to gravity) was not updated while they were grounded, which may eventually
  become useful to allow them to jump down thru a SemiSolid floor. Warp Doors
  needed a fix to work around the bug, to set the player's Grounded(false) or
  else they would hover a few pixels above the ground at their destination,
  since Grounded status paused gravity calculations.
2022-10-09 21:39:43 -07:00
701073cecc Doodad/Actor Runtime Options
* 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.
2022-10-09 17:41:24 -07:00
7d15651ff6 "Look At Me" for Doodad Scripts
* 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.
2022-09-24 23:54:51 -07:00
653184b8f8 JavaScript Exception Catcher UI
* Add an exception catcher that pops open a UI window showing errors that
  occur in doodad scripts during gameplay.
* Shows a preview of the header of the error (character wrapped) with a
  Copy button to copy the full raw text to clipboard for inspection.
* Buttons to dismiss the modal once or stop any further errors from
  opening during this gameplay session (until next restart).
* Add developer shell commands to test the exception catcher:
  - 'throw <message>' to throw a custom message.
  - 'throw2' to stress test a "long" message.
  - 'throw3' to throw a realistic message copied from an actual error.
* Scripting engine: console.log() and friends will now insert the script
  VM's name in front of its messages (the filename + actor ID).
2022-09-24 21:58:01 -07:00
cd103f06c7 Touchscreen fixes 2022-09-24 19:05:42 -07:00
73421d27f2 Wait Modal
* Add modal.Wait() that creates a global progress bar modal which is not
  dismissable by the user; the caller must Dismiss() the modal
  themselves when ready.
* It will be useful in the future in case e.g. saving a Level needs to
  take a while to rebalance chunks and the modal prevents ALL
  interaction with the game so the user can't further modify the level
  while it's busy refactoring itself.
* Cheat code: "test wait screen" to show the Wait modal for 10 seconds.
2022-09-24 18:39:02 -07:00
546b5705db Detect touchscreen and tweak some behaviors 2022-09-24 17:45:54 -07:00
6631d8d11c Open Source Release 2022-09-24 16:17:30 -07:00
6404024d12 Update bootstrap to pull doodad sources 2022-09-24 15:39:54 -07:00
83f0a2fb49 Split doodads into new repository 2022-09-24 15:35:47 -07:00
ec0b5ba6ca Rename Go module 2022-09-24 15:17:25 -07:00
3e16051724 Update Linux launcher for mobile compatibility 2022-07-04 10:22:15 -07:00
53c72f18d1 Bugfix: Crusher to fall indefinitely w/o a time limit 2022-05-08 12:09:09 -07:00
d7f247e4cc Fix doodad tool --tag property 2022-05-08 10:58:53 -07:00
a28644d253 No appimage builds for now 2022-05-07 20:46:03 -07:00
46ab5c9de0 Remove replace directives in go.mod 2022-05-07 20:29:31 -07:00
0a18cd4227 Prepare v0.13.0 for release 2022-05-07 20:23:25 -07:00
34c45095b5 Idle animations for Boy 2022-05-07 20:18:44 -07:00
434416d3a4 Spit and polish
* Made the loadscreen useful again (give it work to do async so the game
  doesn't simply freeze during): does a first call to LoadUnloadChunks
  to preload the viewport chunks.
* Hide the mouse cursor when movement keys are pressed.
2022-05-07 18:54:37 -07:00
450c6b3bb2 Spit and polish
* On the failure (but still success) dialog on Survival Mode levels
  (e.g. Azulian Tag): make the default be to retry the level but
  show a "pity" Next Level button below, as the level is marked as
  completed (silver score) and the next one is unlocked.
2022-05-07 17:42:38 -07:00
ffc2c6f69b Performance?: Don't unload chunks so eagerly
Previously: the Chunker tracks with chunks were gotten during the
current game tick and the N-1 and N-2 ticks, and chunks not accessed in
two ticks were freed immediately.

Now: they go into a "garbage collection" pool with a minimum number of
game ticks to free. So if they're needed again, they're saved from the
gc pool. F3 overlay data shows the count of the gc pool.
2022-05-07 17:16:03 -07:00
315c8a81a0 Update Changelog 2022-05-05 22:34:03 -07:00
94d0da78e7 Swimming Physics and Bubble Pattern
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.
2022-05-05 21:35:32 -07:00
4efa8d00fc Fancy Mouse Cursors
The gamepad mouse cursor has become THE mouse cursor. It is always visible and your
real cursor is hidden, and this way the game can swap out other cursors for certain
scenarios:

* The Pencil Tool in the editor will use a pencil cursor over the level canvas.
* The Flood Tool has a custom Flood cursor so you don't forget it's selected!

Other improvements:

* The Palette buttons in the editor now render using their swatch's pattern
  instead of only using its color.
* If you have an ultra HD monitor and open a Bounded level in the editor which
  is too small to fill your screen, the editor canvas limits its size to fit
  the level (preferable over showing parts of the level you can't actually play
  as it's out of bounds).
* The "brush size" box is only drawn around the cursor when a relevant tool is
  selected (Pencil, Line, Rect, Ellipse, Eraser)
2022-05-04 22:38:26 -07:00
9b75f1b039 Spit and polish
* New built-in wallpaper: "Dotted paper (dark)" is a dark-themed wallpaper.
* New built-in palette: "Neon Bright" with bright colors for dark levels.
* New cheat: "warp whistle" to automatically win the level.
* In case the user has a VERY LARGE screen resolution bigger than the full
  bounds of a Bounded level, the Play Scene will cap the size and center
  the level canvas onto the window. This is preferable to being able to see
  beyond the level's boundaries and hitting an invisible wall in-game.
* Make the titlescreen Lazy Scroll work on unbounded levels. It can't bounce
  off scroll boundaries but it will reverse course if it reaches the level's
  furthest limits.
* Bugfix: characters' white eyes were transparent in-game. Multiple culprits
  from the `doodad convert` tool defaulting the chroma key to white, to the
  SDL2 textures considering white to be transparent. For the latter, the game
  offsets the color by -1 blue.
2022-05-03 21:15:39 -07:00
75fa0c7e56 Stability and Bugfixes
* Editor: Auto-save on a background goroutine so you don't randomly freeze
  the editor up during.
* Fix actor linking issues when you drag and re-place a linked doodad: the
  level was too eagerly calling PruneLinks() whenever a doodad was 'destroyed'
  (such as the one just picked up) breaking half of the link connection.
* Chunk unloader: do not unload a chunk that has been modified (Set or Delete
  called on), keep them in memory until the next ZIP file save to flush them
  out to disk.
* Link Tool: if you clicked an actor and don't want to connect a link, click
  the first actor again to de-select it.

Updates to the `doodad` tool:

* `doodad edit-level --resize <int>` can re-chunk a level to use a different
  chunk size than the default 128. Large chunk sizes 512+ lead to performance
  problems.
2022-05-02 20:35:53 -07:00
fc736abd5f Doodads: Gems, Snake and Crusher
Adds several new doodads to the game and 5 new wallpapers (parchment
paper in blue, green, red, white and yellow).

New doodads:

* Crusher: A purple block-headed mob wearing an iron helmet. It tries
  to crush the player when you get underneath. Its flat helmet can be
  ridden on like an elevator back up.
* Snake: A green stationary mob that always faces toward the player.
  If the player is nearby and jumps, the Snake will jump too and hope
  to catch the player in mid-air.
* Gems and Totems: A new key & lock collectible. Gems have quantity so
  you can collect multiple, and place them into matching Totems. A
  Totem gives off a power signal when its gem is placed and all other
  Totems it is linked to have also been activated. A single Totem may
  link to an Electric Door and require only one gem to open it, or it
  can link to other Totems and they all require gems before the power
  signal is sent out.
2022-05-01 15:18:23 -07:00
ad67e2b42b New Doodad: Blue Bird
* The blue bird follows the same base AI as the red bird (it has a
  target altitude that it tries to maintain, and it will dive at the
  player) but the blue bird flies in a sine wave pattern around its
  target altitude. It also has a longer scan radius to search for the
  player than the red bird.
* The sine wave pattern of the blue bird means you may fly under its
  radar depending how high it is on average.

Cheat codes that replace the player character are refactored to make
it easier to extend, and new cheats have been added:

* super azulian: play as the Red Azulian.
* hyper azulian: play as the White Azulian.
* bluebird: play as the new Bird (blue).
2022-04-30 17:59:55 -07:00
402b5efa7e Zipfiles for Attached Files Too
* The level.FileSystem type has updated to support ZIP files too.
* Legacy levels loaded from gz/json have their old FileSystem as a
  simple map[filename]data and this parses from JSON OK.
* On save to zip, the legacy loaded file data gets exported to ZIP.
* Going forward: newly added or deleted files during runtime are kept in
  the legacy file map until the next save when the filemap is again
  flushed out to ZIP.
* For regular read-access, the FileSystem reads from the ZIP file if the
  data is not in the hot map (legacy file or recently modified
  attachment).
* Bugfix: be sure to Inflate() the Level/Doodad after loading from
  zipfile - it used to be that directly after a save, trying to play the
  level failed because the Level.Actors struct was missing their IDs,
  and similarly recently written chunks would error out (become black
  voids) on levels/doodads so we Inflate() both after save/replacing
  their zip handle.
2022-04-30 12:50:00 -07:00
302506eda9 Cheat: $ d.SetPlayerCharacter("anything.doodad")
Too restricted by the cheat codes to play as certain characters
on-demand? Use the JS shell in the developer console to set any doodad
you want:

    $ d.SetPlayerCharacter("key-blue")
    $ d.SetPlayerCharacter("anvil")
    $ d.SetPlayerCharacter("box.doodad")

The .doodad suffix is optional.

Interesting behaviors when playing as odd doodads:

* Most non-mobile doodads don't collide with each other, so you can pass
  through doors and not activate buttons if you play as a key or a
  trapdoor. Non-mobile doodads also generally have antigravity so you
  can fly freely around the map.
* Non-mobile doodads can not open Warp Doors or interact with the Exit
  Flag. You'll have to change back to a creature such as "boy" or
  "azu-blue" to win the level.
* If you are a key, the Thief can collect you! This removes your player
  doodad from the level and soft locks the game. No worries, another
  call to d.SetPlayerCharacter() will put you back on the map!
* If the doodad name isn't found, you'll play as the built-in fallback
  doodad, which is just a red "X" shape. It has anti-gravity and does
  not generally interact with any doodad (can not push buttons or
  collect keys - but it can pass through doors and other obstacles. Can
  not win the level goal flag, though!)
2022-04-29 21:39:53 -07:00
93623e4e8a Zipfiles as File Format for Levels and Doodads
Especially to further optimize memory for large levels, Levels and
Doodads can now read and write to a ZIP file format on disk with
chunks in external files within the zip.

Existing doodads and levels can still load as normal, and will be
converted into ZIP files on the next save:

* The Chunker.ChunkMap which used to hold ALL chunks in the main json/gz
  file, now becomes the cache of "hot chunks" loaded from ZIP. If there is
  a ZIP file, chunks not accessed recently are flushed from the ChunkMap
  to save on memory.
* During save, the ChunkMap is flushed to ZIP along with any non-loaded
  chunks from a previous zipfile. So legacy levels "just work" when
  saving, and levels loaded FROM Zip will manage their ChunkMap hot
  memory more carefully.

Memory savings observed on "Azulian Tag - Forest.level":

* Before: 1716 MB was loaded from the old level format into RAM along
  with a slow load screen.
* After: only 243 MB memory was used by the game and it loaded with
  a VERY FAST load screen.

Updates to the F3 Debug Overlay:

* "Chunks: 20 in 45 out 20 cached" shows the count of chunks inside the
  viewport (having bitmaps and textures loaded) vs. chunks outside which
  have their textures freed (but data kept), and the number of chunks
  currently hot cached in the ChunkMap.

The `doodad` tool has new commands to "touch" your existing levels
and doodads, to upgrade them to the new format (or you can simply
open and re-save them in-game):

    doodad edit-level --touch ./example.level
    doodad edit-doodad --touch ./example.doodad

The output from that and `doodad show` should say "File format: zipfile"
in the headers section.

To do:

* File attachments should also go in as ZIP files, e.g. wallpapers
2022-04-29 20:34:59 -07:00
2d3f36379c AppImage Support 2022-04-25 21:31:46 -07:00
9cdc7260bb Prepare v0.12.1 for release 2022-04-16 17:50:40 -07:00
c5353df211 LoadUnloadChunk for Memory Optimization
Instead of the loadscreen eager-loading ALL level chunks to Go Images, only
load the chunks within the "LoadingViewport" - which is the on-screen
Viewport plus a margin of chunks off the screen edges.

During gameplay, every few ticks, reevaluate which chunks are inside or
outside the LoadingViewport; for chunks outside, free their SDL2 textures
and free their cached bitmaps to keep overall memory usage down. The
AzulianTag-Forest level now stays under 200 Textures at any given time
and the loadscreen goes faster as it doesn't have to load every chunk's
images up front.

The LoadUnloadChunk feature can be turned on/off with feature flags. If
disabled the old behavior is restored: loadscreen loads all images and
the LoadUnloadChunks function is not run.

Other changes:

* loadscreen: do not free textures in the Hide() function as this runs on
  a different goroutine and may break. The 4 wallpaper textures are OK
  to keep in memory anyway, the loadscreen is reused often!
* Free more leaked textures: on the Inventory frame and when an actor
  calls Self.Destroy()
* Stop leaking goroutines in the PubSub feature of the doodad script
  engine; scripting.Supervisor.Teardown() sends a stop signal to all
  scripts to clean up neatly. Canvas.Destroy() tears down its scripting
  supervisor automatically.
2022-04-10 12:40:25 -07:00
d694fcc7c2 Fix climbing on the right bug + eager-render boolprop
* New boolProp to help debug memory issues: eager-render, set it to
  false and the loadscreen will not eagerload Go images for all the
  level chunks.
* Finally fix the level collision bug where the player could climb walls
  to the right.
2022-04-09 18:21:26 -07:00
6b8c7a1efe Update Go dependencies 2022-04-09 16:01:56 -07:00
db5760ee83 Optimize memory by freeing up SDL2 textures
* Added to the F3 Debug Overlay is a "Texture:" label that counts the number
  of textures currently loaded by the (SDL2) render engine.
* Added Teardown() functions to Level, Doodad and the Chunker they both use
  to free up SDL2 textures for all their cached graphics.
* The Canvas.Destroy() function now cleans up all textures that the Canvas
  is responsible for: calling the Teardown() of the Level or Doodad, calling
  Destroy() on all level actors, and cleaning up Wallpaper textures.
* The Destroy() method of the game's various Scenes will properly Destroy()
  their canvases to clean up when transitioning to another scene. The
  MainScene, MenuScene, EditorScene and PlayScene.
* Fix the sprites package to actually cache the ui.Image widgets. The game
  has very few sprites so no need to free them just yet.

Some tricky places that were leaking textures have been cleaned up:

* Canvas.InstallActors() destroys the canvases of existing actors before it
  reinitializes the list and installs the replacements.
* The DraggableActor when the user is dragging an actor around their level
  cleans up the blueprint masked drag/drop actor before nulling it out.

Misc changes:

* The player character cheats during Play Mode will immediately swap out the
  player character on the current level.
* Properly call the Close() function instead of Hide() to dismiss popup
  windows. The Close() function itself calls Hide() but also triggers
  WindowClose event handlers. The Doodad Dropper subscribes to its close
  event to free textures for all its doodad canvases.
2022-04-09 14:41:24 -07:00
dbd79ad972 Update go.mod 2022-03-27 14:26:06 -07:00
ba373553cb Prepare v0.12.0 for release 2022-03-27 14:23:25 -07:00
38a23f00b2 Reset Timer Doodad + Various Fixes
* 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.
2022-03-27 11:51:14 -07:00
af6b8625d6 Flood Tool, Survival Mode for Azulian Tag
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.
2022-03-26 13:55:06 -07:00
bf706efdc6 Update Changes.md 2022-03-19 12:24:01 -07:00
647124495b Level Difficulty + UI Polish
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.
2022-03-06 22:20:53 -08:00
661c5f4365 Loadscreen Update & Window Resize Fixes
* Loadscreen: put the progress bar between the Title and Subtitle so it
  looks good even on mobile landscape orientation (narrow height)
* Bugfixes around window OnResize events: the loadscreen handles
  resizing correctly now and the Level Editor (or w/e) will also be the
  right size if you resized the window during loading.
2022-03-06 12:07:59 -08:00
ba4fbf55ef Update dependencies 2022-03-06 11:37:08 -08:00
44122d4130 Spit and polish
UI improvements specifically for mobile (running the game with the
`-w mobile` or `-w landscape` options) screen sizes.

* Rework the Settings window to be mobile friendly to landscape
  oriented screens (`doodle -w landscape`) and migrate Options tab
  to magicform.
* The toolbar in the Editor will be a single column of buttons
  on small screens, such as `-w mobile` (375x812) portrait mode
  smartphone. On larger screens the toolbar shows in two columns
  of buttons.
* Fix tooltips not drawing on top.
* Centralize the hard-coded references to specific font filenames
* Add cheat code: `test load screen` to bring a sample loading screen up
  for a few seconds. It needs improvement on `-w landscape`
2022-03-05 22:44:54 -08:00
77297fd60d Text Tool and Pan Tool
Two new tools added to the Level Editor:

* Pan Tool: left-click to scroll the level around safely.
* Text Tool: write text onto your level.

Features of the Text Tool:

* Can choose from the game's built-in fonts, size and enter the message
  you want to write.
* The mouse cursor previews the text when hovered over the level.
* Click to "stamp" the text onto your level. The currently selected
  color swatch will be used to color the text in.
* Adds two new fonts: Azulian.ttf and Rive.ttf that can be selected in
  the Text Tool.

Some implementation notes:

* Added package native/engine_sdl.go that handles the lower-level
  SDL2_TTF logic to rasterize the text into a black&white image.
* WASM not supported yet (if the game even still built for WASM);
  native/engine_wasm.go stubs out the TextToImage() call with a "not
  supported" error just in case.

Other changes:

* New Toolbar icons: they are 24x24 instead of 32x32 to make more room
  for more tools.
* The toolbar now shows two buttons per row for a more densely packed
  layout. For very narrow screen widths (< 600px) the default Vertical
  Toolbar layout will use one-button-per-row to not eat too much screen
  real estate.
* In the Horizontal Toolbars layout there are 2 buttons per column.
2022-03-05 15:34:20 -08:00
bc15155b68 Update dependencies 2022-02-21 14:18:36 -08:00
0b2e04b336 Update changelog for v0.11.0 2022-02-21 13:26:25 -08:00
962098d4e7 v0.11.0 last minute tweaks
* When playing as the Bird, the dive attack is able to destroy other
  mobile doodads such as Azulians and Thieves.
* The Box has been made invulnerable so it can't be destroyed by Anvils
  or player-controlled Birds.
* Bugfixes with pop-up modals:
  * The quit game confirm modal doesn't appear if another modal is
    already active on screen.
  * The Escape key can dismiss Alert and Confirm modals.
* Add "Level" menu items to Play Mode to restart the level or retry from
  the last checkpoint (in case of softlocks, etc.)
2022-02-21 13:09:51 -08:00
40cb9f15cb Prepare v0.11.0 for release (+ fixes)
* The title screen now loads the default maps from a LevelPack. The game
  no longer ships with the Tutorial levels in the "levels" folder as
  default; they are in the LevelPack so the "Edit Drawing" screen begins
  as a blank slate for only user levels.
* Add the Zoo level to the Tutorial levelpack
* Bugfixes around changing the player character to work around clipping
  issues if the character has changed height drastically.
2022-02-20 17:48:07 -08:00
293ac668e7 Azulian: Don't follow boring players
If the Azulians aren't hostile to the player character (e.g. you are
playing as a Thief or Azulian or you are invulnerable because you're the
Anvil), the Azulians won't aggro and pathfind to you either.
2022-02-20 12:19:56 -08:00
1205dc2cd3 Invulnerable Anvil and other fixes
* Add methods `Invulnerable() bool` and `SetInvulnerable(bool)` to the
  Actor API accessible in JavaScript (e.g. `Self.SetInvulnerable(true)`)
* The Anvil is invulnerable - when played as, it can crush other mobs by
  jumping on them but is not defeated by those mobs at the same time.
* Anvils don't destroy invulnerable mobs, such as other Anvils.
* Bugfix: the Electric Door is considered to be opened from the first
  frame of animation when the door begins opening, and remains opened
  until the final frame of animation when it is closing.
* New cheat code: `megaton weight` to play as the Anvil by default.
2022-02-20 11:48:36 -08:00
0fc046250e Window Focus Bugfixes
* Fix the Doodad Dropper and Registration windows not stealing the focus
  when they are opened via menu bars.
* Bugfixes in gamepad support: stop at the first controller found,
  Draw() to handle controllers going away and hide the mouse cursor
2022-02-19 20:20:58 -08:00
4de0126b19 Game Controller Support
Adds support for Xbox and Nintendo style game controllers. The gamepad
controls are documented on the README and in the game's Settings window.

The buttons are not customizable yet, except that the player can choose
between two button styles:

* X Style (default): "A" button is on the bottom and "B" on the right.
* N Style: swaps the A/B and the X/Y buttons to use a Nintendo-style
  layout instead of an Xbox-style.
2022-02-19 18:31:22 -08:00
626fd53a84 Checkpoint Flag can Re-assign Player Character
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.
2022-01-18 21:24:36 -08:00
44aba8f1b4 White Azulian, Respawn invincibility timer
* Respawning from a checkpoint grants 3 seconds of immunity in case
  enemies are spawn camping.
* Add the white Azulian as an even faster and harder enemy than the red
  Azulian: twice as fast, jumps higher, and can detect the player from
  further away.
2022-01-18 18:32:15 -08:00
3f7e384633 Invincibility Cheat
Add cheat `god mode` that toggles invincibility. Fire pixels and hostile
mobs can't fail the level for you.
2022-01-17 22:02:27 -08:00
9201475060 Update Doodad JS API + Hostile mobs
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
2022-01-17 21:28:05 -08:00
1cc6eee5c8 Refactor Level Publishing + MagicForm
* magicform is a helper package that may eventually be part of the go/ui
  library, for easily creating structured form layouts.
* The Level Publisher UI is the first to utilize magicform.

Refactor how level publishing works:

* Level data now stores SaveDoodads and SaveBuiltins (bools) and when
  the level editor saves the file, it will attach custom and/or builtin
  doodads just before save.
* Move the menu item from the File menu to Level->Publish
* The Publisher UI just shows the checkboxes to toggle the level
  settings and a convenient Save button along with descriptive text.
* Free versions get the "Register" window popping up if they click the
  Save Now button from within the publisher window.

Note: free versions can still toggle the booleans on/off but their game
will not attach any new doodads on save.

* Free games which open a level w/ embedded doodads will get a pop-up
  warning that the doodads aren't available.
* If they DON'T turn off the SaveDoodads option, they can still edit and
  save the level and keep the existing doodads attached.
* If they UNCHECK the option and save, all attached doodads are removed
  from the level.
2022-01-17 18:51:11 -08:00
5ca87c752f Sort level actors deterministically by their (time sensitive) ID 2022-01-16 20:20:48 -08:00
4d08bf1d85 Switch JavaScript engine to goja
* Switch from otto to goja for JavaScript engine.
* goja supports many ES6 syntax features like arrow functions,
  const/let, for-of with more coming soon.
* Same great features as otto, more modern environment for doodads!
2022-01-16 20:09:27 -08:00
d67c1cfcf1 Send User-Agent of version/os/arch on update check 2022-01-16 18:33:27 -08:00
05b97df846 Prepare v0.10.1 for release 2022-01-09 15:03:02 -08:00
9e4f34864d Remove MsgPack, Fix doodad display on top/left edges
* Clean up unused msgpack code for levels and doodads
* Fix the cosmetic bug where actors in your level would display wrongly
  when scrolling off the top/left edges of the screen: they used to
  anchor at their own 0,0 coordinate and crop their width/height leading
  to a 'scrolling' effect that didn't happen on the right/bottom edges.
2022-01-09 13:16:29 -08:00
cbd7816fdf Easter Egg: RiveScript Chatbot
The Default handler of the developer command shell now calls out to
RiveScript to match the user's message to a friendly reply. If
RiveScript returns NoReplyMatched then give the "command not found"
error.
2022-01-08 19:21:08 -08:00
48e18da511 Centralize cheats, detect cheated player character
* If the player runs the PlayAsBird cheat they shouldn't be able to win
  a high score on a level, so at level startup it detects whether the
  DefaultPlayerCharacterDoodad has changed from default on a level that
  doesn't use the Start Flag to set a specific doodad - and immediately
  marks the session as cheated
2022-01-08 18:27:37 -08:00
24c47d1e3f Fix build scripts for real 2022-01-08 17:18:44 -08:00
51e585b2f8 Fix build scripts around architecture info 2022-01-08 17:07:24 -08:00
96314a852d Update go.mod dependencies to latest 2022-01-08 17:06:19 -08:00
3130d8ca94 Undo replace directives in go.mod 2022-01-03 20:33:41 -08:00
a6297f6cb6 Make build scripts more architecture-aware 2022-01-03 20:23:49 -08:00
9a51ac39f9 Spit and polish
* New doodad: Invisible Warp Door
* All warp doors require the player to be grounded (if affected by
  gravity) to open them. No jumping or falling thru and opening
  a warp door mid-air!
* Title Screen now randomly selects from a couple of levels.
* Title Screen: if it fails to load a level it sets up a basic
  blank level with a wallpaper instead.
* New developer shell command: titlescreen <level>
  Opens the MainScene with a custom user level as the background.
* Add Auto-save to the Editor to save your drawing every 5 minutes
* Add a MenuBar to the Play Scene for easier navigation to other
  features of the game.
* Doodad JS API: time.Since() now available.
2022-01-02 22:36:32 -08:00
672ee9641a Savegame and High Scores
* Adds pkg/savegame to store user progress thru Level Packs.
* The savegame.json is mildly tamper resistant by including a checksum
  along with the JSON body.
* The checksum combines the JSON string + an app secret (in savegame.go)
  + user specific entropy (stored in their settings.json). If the user
  modifies their save file and the checksum becomes invalid the game
  will not load the save file, acting like it didn't exist, resetting
  all their high scores.

Updates to the Story Mode window:

* On the LevelPacks list: shows e.g. "[completed 0 of 3 levels]" showing
  a user's progress thru the level pack.
* Below the levels on the Detail screen:
  * Shows an indicator whether the level is completed or not.
  * Shows high scores (fastest times beating the level)
  * Shows a padlock icon if levels are locked and the player hasn't
    reached them yet. Pops up an Alert modal if a locked level is
    clicked on.

Scoring is based around your fastest time elapsed to finish the level.

* Perfect Time (gold coin): player has not died during the level.
* Best Time (silver coin): player has continued from a checkpoint.

In-game an elapsed timer is shown in the top left corner along with the
gold or silver coin indicating if your run has been Perfect.

If the user enters any Cheat Codes during gameplay they are not eligible
to win a high score, but the level will still be marked as completed.
The icon next to the in-game timer disappears when a cheat code has been
entered.
2022-01-02 16:28:43 -08:00
690fdedb91 Add the ui.ColorPicker 2022-01-01 18:48:34 -08:00
fa5f303dad Bugfix: embedded levelpacks from bindata 2021-12-30 18:39:11 -08:00
3881457300 Prepare v0.10.0 for release 2021-12-30 17:57:13 -08:00
d16a8657aa Window Icon, UI Polish
* SDL2 builds of the game now set their app window icon.
* Create/Edit Level window is updated to show a tabbed UI to create a
  new Level or a new Doodad. The dedicated main menu button to create a
  new doodad (which immediately prompted for its size) is replaced by
  this new tab's UI.
* Edit Drawing/Play Level window is more responsive to smaller screen
  sizes by drawing fewer columns of filenames.
* Bugfix: the Alert and Confirm modals always re-center themselves on
  screen, especially to adapt between Portrait or Landscape mode on a
  mobile device.
2021-12-30 16:31:45 -08:00
37377cdcc1 Update Changelog 2021-12-26 21:07:52 -08:00
6d3ffcd98c Finalize basic functionality for Level Packs
* The "Story Mode" button on the MainScene opens the levelpacks window.
* Levelpacks from all places are shown (built-in and user files), basic
  level picker works.
* When playing a level out of a levelpack: the PlayScene gets the file
  data from the zipfile and plays it OK.
* When a levelpack level is solved, the "Next Level" button appears on
  the success modal and hitting Return will advance to the next level in
  the pack. The final level doesn't show this button.
* The user can edit levelpack levels! Clicking the "Edit" button on the
  Play Mode moves the loaded level over to the EditScene and the user
  could save it to disk or edit/playtest it perfectly OK! The link to
  the levelpack is lost upon opening in the editor, so the "Next Level"
  victory button doesn't appear.
2021-12-26 20:48:29 -08:00
678326540b WIP LevelPack UI + Landscape Mode Title Screen
The title screen is now responsive to landscape mode. If the window is
not tall enough to show all the menu buttons (~600px) it will switch to
a horizontal layout with the title on the left and buttons on the right.

WIP "Story Mode" button that brings up a Level Packs selection window.
2021-12-23 21:11:45 -08:00
a75b7208ca Doodad Tool: Levelpacks
Adds `doodad levelpack create` and `doodad levelpack show` commands to
the CLI tool to create levelpacks.

A levelpack is a ZIP file containing a descriptive index.json and
directories for levels and doodads.
2021-12-23 19:15:32 -08:00
ddf0074099 Condensed Palette, Bird AI Update
* The Red Bird now records its original altitude on the level and will
  try and return there should it accidentally climb up or down a wall.
  Sometimes goes into a wavy pattern surrounding its original altitude.
* Editor UI: in the default (vertical) toolbar, the Palette now has a
  two column view to show more color choices on screen at once.
* User setting added: hide the touch control hints.
2021-10-12 20:49:48 -07:00
3a9cc83e78 Bugfix: Undo/Redo works for the Doodad Editor
Changed dependencies around so the undo/redo feature works on doodads as
well as levels.
2021-10-11 16:10:04 -07:00
0ec259b171 Crosshair Option + Doodad Editor crash fix
* The level scroll logic was getting a null pointer crash if you open a
  doodad rather than a level file.
* Add a crosshair option to the level editor, configurable in the Game
  Settings window.
2021-10-11 15:57:33 -07:00
8ca411a0ae Prepare for release v0.9.0 2021-10-09 21:29:14 -07:00
a112c19d76 Few small tweaks 2021-10-09 21:22:50 -07:00
1a8a5eb94b Polish and bugfixes
- Fix a memory sharing bug in the Giant Screenshot feature.
- Main Menu to eagerload chunks in the background to make scrolling less
  jittery. No time for a loadscreen!
- Extra script debugging: names/IDs of doodads are shown when they send
  messages to one another.
- Level Properties: you can edit the Bounded max width/height values for
  the level.

Doodad changes:

- Buttons: fix a timing bug and keep better track of who is stepping on it,
  only popping up when all colliders have left. The effect: they pop up
  immediately (not after 200ms) and are more reliable.
- Keys: zero-qty keys will no longer put themselves into the inventory of
  characters who already have one except for the player character. So
  the Thief will not steal them if she already has the key.

Added to the JavaScript API:

* time.Hour, time.Minute, time.Second, time.Millisecond, time.Microsecond
2021-10-09 20:45:38 -07:00
feea703d0c Update changelog for upcoming 0.9.0 2021-10-07 21:26:39 -07:00
e80a3f0446 Various minor tweaks and changes
* Recolor some of the region doodads
* Add command: `doodad edit-level --remove-actor` to remove actors from
  your level.
* Tweak the player jump velocity from playtesting levels.
2021-10-07 20:50:24 -07:00
d6acee5a66 Adjust Gravity and Prevent Moonwalking
* 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.
2021-10-07 18:49:09 -07:00
fb5a8a1ae8 Async Giant Screenshot, Player Physics and UI Polish
* The "Giant Screenshot" feature takes a very long time, so it is made
  asynchronous. If you try and run a second one while the first is busy,
  you get an error flash. You can continue editing the level, even
  playtest it, or load a different level, and it will continue crunching
  on the Giant Screenshot and flash when it's finished.
* Updated the player physics to use proper Velocity to jump off the
  ground rather than the hacky timer-based fixed speed approach.
* FlashError() function to flash "error level" messages to the screen.
  They appear in orange text instead of the usual blue, and most error
  messages in the game use this now. The dev console "error <msg>"
  command can simulate an error message.
* Flashed message fonts are updated. The blue font now uses softer
  stroke and shadow colors and the same algorithm applies to the orange
  error flashes.

Some other changes to player physics:

* Max velocity, acceleration speed, and gravity have been tweaked.
* Fast turn-around if you are moving right and then need to go left.
  Your velocity resets to zero at the transition so you quickly get
  going the way you want to go.

Some levels that need a bit of love for the new platforming physics:

* Tutorial 3.level
2021-10-07 18:27:38 -07:00
0b0af70a62 Viewport Windows, Quality of Life, Spit and Polish
* New keybind: 'v' to open a new Viewport in the Level Editor.
* New keybind: Backspace to close the topmost UI window,
  and Shift+Backspace to close them all.
* Zoom has graduated out of experimental feature status. Still a bit
  buggy but workable.
* Viewport windows now copy the Tool and BrushSize of the toplevel
  editor, so drawing in and out of viewports works well.
* Viewport window UI improved: buttons to grow or shrink the window
  size, refresh the actors, etc.
2021-10-06 22:22:34 -07:00
a24c94a161 Multitouch Level Panning
Add multi-touch gesture support so that the player can scroll the level
in the editor (and title screen) by treating a two finger swipe to be
equivalent to a middle click drag.

Fun quirks found with SDL2's MultiGestureEvent:

* They don't begin sending us the event until motion is detected after
  two fingers have touched the screen; not the moment the second finger
  touches it.
* It spams us with events when it detects any tiny change and a lot of
  cool details like rotate/pinch deltas, but it never tells us when the
  multitouch STOPS! The game has to block left clicks while multitouch
  happens so the user doesn't draw all over their level, so it needs to
  know when touch has ended.
* The workaround is to track the mouse cursor position at the first
  touch and each delta thereafter; if the deltas stop changing tick to
  tick, unset the "is touching" variable.
2021-10-06 20:02:09 -07:00
cc16a472af "Playtest From Here" Feature
In the level editor, the "Play (P)" button has a new feature: Play
From Here. On mouse down you begin dragging a silhouette of Boy or
whoever the default player character is, as if you were dragging a
doodad onto your level.

Drop the silhouette on your level and enter Play Mode from that
location instead of the Start Flag.

Release your cursor over the Play button or press the "P" key to
spawn at the Start Flag as usual.
2021-10-04 22:02:00 -07:00
c2c91e45a9 Middle-click to Pan + Remember Scroll Position
In the editor, clicking and dragging with the middle mouse button
will scroll the view of the editor in place of the arrow keys.

When entering Play Mode, the original scroll position in the level
editor is remembered for when you come back - no more having to
scroll from 0,0 each time to get back to where you were working!
2021-10-04 20:49:11 -07:00
489a43ea8c Touch Screen Controls for Play Mode!
The game can now be played using only a touch screen! The left
mouse click (Button1) can now move and control the player
character.

* A box in the very middle of the screen is the "Use" button and
  a deadzone for directional inputs.
* Anywhere outside the middle and to the left registers a Left
  button, to the right a Right button, above the top of the middle
  is a Jump button, and below the bottom of the middle is a down
  input (for antigravity mode).
* Tight platforming is possible: above and below the middle box,
  the left/right split is tight in the middle of the window. You
  can get tight jumps if jumping or go below if you don't want to
  jump. The left/right deadzone is only over the space of the Use
  button.

If the player is idle for a while with no controller inputs, some
hints will fade in about the touch controls.

Note: the ScrollboxOffset to track the player character is changed
to 60,60 from 60,100 so the camera will track tighter to the player
and so the player will mostly be over the Use button on touch
controls as long as he's away from a level boundary.
2021-10-04 19:51:31 -07:00
1f83300cec Picture-in-Picture Window (WIP)
In the Level Editor, the "Level->New viewport" menu opens a window with
its own view into your level. You can open as many viewports as you
want.

* Mouse over a viewport and the arrow keys scroll that canvas instead of
  the main editor canvas!
* You can draw inside the viewports! A selectbox to choose the tool to
  draw with. No palette or thickness support yet!
* The actors are installed as-is when the viewport is created and it
  doesn't show any changes to actors after. Make a new viewport for a
  refreshed view.
* Strokes committed inside the viewport show up in the main editor (and
  in other viewports), and vice versa. The viewports accurately track
  changes to the level's colors, just not the actors.
* Fun feature to load a DIFFERENT level inside of the viewport! Editing
  that level doesn't save changes or anything.
2021-10-03 21:18:39 -07:00
4469847c72 Giant Screenshot Feature
In the Level Editor, the "Level->Giant Screenshot" menu will take a full
scale PNG screenshot of the entire level, with its wallpaper and
doodads, and save it in ~/.config/doodle/screenshots.

It is currently CPU intensive and slow. With future work it should be
made asynchronous. The function is abstracted away nicely so that the
doodad CLI tool may support this as well.
2021-10-03 17:21:17 -07:00
55efdd6eb5 Technical Doodad: Checkpoint Region
The Checkpoint Region acts as an invisible checkpoint flag, remembering
the player's location should they need to respawn there.

New cheat: `show all actors` during Play Mode will make every hidden
actor visible. Useful to see your technical doodads during gameplay!

Developer shell: `Execute(command string)` is available to the
JavaScript interpreter. It simulates another command being run on the
developer console.
2021-10-02 21:36:03 -07:00
97e179716c Add Technical Doodads + UI Fixes
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!
2021-10-02 20:52:16 -07:00
df3a1679b6 Makefile: mingw 32-bit 2021-09-12 17:15:54 -07:00
528e7b4807 Prepare release v0.8.1 2021-09-12 16:55:36 -07:00
0a1d86e1f5 Bugfix: Scroll constraint favors top/left edge
For levels having a top/left scroll boundary, the top/left point takes
higher priority for resolving out-of-bounds scroll ranges instead of the
bottom/right.

This fixes a bug where you Zoom Out of a level far enough that the
entire boundaries of a Bounded level are smaller than the viewport into
the level. It could happen if playing normal levels in Play Mode on a
very high-resolution monitor. Previously, the level would anchor to the
bottom/right corner of your screen.

With the Zoom In/Out Feature this broke the ability to scroll well on
the level; so the easy fix is to put the X>0, Y>0 bounds check after the
above, so the level will hug the top/left corner of the screen which
fixes both problems.
2021-09-12 15:59:40 -07:00
21520e71e9 Zoom: Fix scrolling into negative coordinates
* If you open a wide unbounded level like Castle.level and zoom out and
  scroll left (into negative world coordinates), the level chunks
  display correctly now.
2021-09-12 15:47:16 -07:00
482 changed files with 22226 additions and 5852 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
bin/*
dist/*

3
.gitignore vendored
View File

@ -6,10 +6,13 @@ bin/
dist/
rtp/
guidebook/
docker-artifacts/
wasm/assets/
*.wasm
*.doodad
*.level
*.levelpack
*.AppImage
docker/ubuntu
docker/debian
docker/fedora

View File

@ -1,19 +1,50 @@
# Building Doodle
* [Quickstart](#quickstart-with-bootstrap-py)
* [Detailed Instructions](#detailed-instructions)
* [Linux](#linux)
* [Flatpak for Linux](#flatpak-for-linux)
* [Windows Cross-Compile from Linux](#windows-cross-compile-from-linux)
* [Old Docs](#old-docs)
- [Building Doodle](#building-doodle)
- [Dockerfile](#dockerfile)
- [Automated Release Scripts](#automated-release-scripts)
- [Go Environment](#go-environment)
- [Quickstart with bootstrap.py](#quickstart-with-bootstrappy)
- [Detailed Instructions](#detailed-instructions)
- [Fonts](#fonts)
- [Makefile](#makefile)
- [Dependencies](#dependencies)
- [Flatpak for Linux](#flatpak-for-linux)
- [Windows Cross-Compile from Linux](#windows-cross-compile-from-linux)
- [Windows DLLs](#windows-dlls)
- [Build on macOS from scratch](#build-on-macos-from-scratch)
- [WebAssembly](#webassembly)
- [Build Tags](#build-tags)
- [doodad](#doodad)
- [dpp](#dpp)
# Dockerfile
The Dockerfile in this git repo may be the quickest way to fully
release the game for as many platforms as possible. Run it from a
64-bit host Linux system and it will generate Linux and Windows
releases for 64-bit and 32-bit Intel CPUs.
It depends on your git clone of doodle to be fully initialized
(e.g., you have run the bootstrap.py script and a `make dist`
would build a release for your current system, with doodads and
runtime assets all in the right places).
Run `make docker` and the results will be in the
`artifacts/release` folder in your current working directory.
**Fedora notes (SELinux):** if you run this from a Fedora host
you will need to `sudo setenforce permissive` to allow the
Dockerfile to mount the artifacts/release folder to export its
results.
# Automated Release Scripts
For the quickest ways to fully end-to-end build Sketchy Maze for various
platforms to produce public release artifacts, see the following repos:
Other Dockerfiles and scripts used to release the game:
* [doodle-docker](https://git.kirsle.net/doodle-docker) provides a Dockerfile that
fully end-to-end releases the latest version of the game for Linux and Windows:
* [SketchyMaze/docker](https://git.kirsle.net/SketchyMaze/docker) provides a Dockerfile
that fully end-to-end releases the latest version of the game for Linux and Windows. 64bit and 32bit versions that freshly clone the
game from git and output their respective CPU release artifacts:
* Windows: .zip file
* Linux: .tar.gz, .rpm, .deb
* [flatpak](https://code.sketchymaze.com/game/flatpak) is a Flatpak manifest for
@ -22,6 +53,21 @@ platforms to produce public release artifacts, see the following repos:
The Docker container depends on all the git servers being up, but if you have
the uber blob source code you can read the Dockerfile to see what it does.
# Go Environment
Part of the build scripts involve building and running the `doodad` command
from this repo in order to generate the game's built-in doodads. For this to
work smoothly from your Linux or macOS build environment, you may need to
ensure that your `${GOPATH}/bin` directory is on your `$PATH` by, for example,
configuring this in your bash/zsh profile:
```bash
export GOPATH="${HOME}/go"
export PATH="${PATH}:${GOPATH}/bin"
```
For a complete example, see the "Build on macOS from scratch" section below.
# Quickstart with bootstrap.py
From any Unix-like system (Fedora, Ubuntu, macOS) the bootstrap.py script
@ -63,7 +109,7 @@ MP3 support issues? [See here](https://github.com/veandco/go-sdl2/issues/299#iss
also mirrored on GitHub. Other supporting repos need mirroring too, or
otherwise, full source tarballs (the result of bootstrap.py) will be
built and archived somewhere safe for posterity in case git.kirsle.net
ever goes away. The doodle mirror is at <https://github.com/kirsle/doodle>
ever goes away. The doodle mirror is at <https://github.com/SketchyMaze/doodle>
(private repository) and the others are there too (go/render, go/ui, etc.)
# Detailed Instructions
@ -71,30 +117,29 @@ ever goes away. The doodle mirror is at <https://github.com/kirsle/doodle>
For building the app the hard way, and in-depth instructions, read
this section. You'll need the following git repositories:
* `git.kirsle.net/apps/doodle` - the game engine.
* `git.kirsle.net/apps/doodle-masters` - where built-in level files are kept,
as well as master GIMP drawings for sprites and such but only the levels
are necessary to build the app properly. Tho even then the app would
work fine with no levels built in!
* `git.kirsle.net/apps/doodle-vendor` - vendored libraries for Windows (SDL2.dll etc.)
* `git.kirsle.net/apps/doodle-rtp` - runtime package (sounds and music mostly)
* `git.kirsle.net/SketchyMaze/doodle` - the game engine.
* `git.kirsle.net/SketchyMaze/assets` - where built-in level files are kept (optional)
* `git.kirsle.net/SketchyMaze/vendor` - vendored libraries for Windows (SDL2.dll etc.)
* `git.kirsle.net/SketchyMaze/rtp` - runtime package (sounds and music mostly)
* `git.kirsle.net/SketchyMaze/doodads` - sources to compile the built-in doodads.
The [doodle-docker](https://git.kirsle.net/apps/doodle-docker) repo will
The [docker](https://git.kirsle.net/SketchyMaze/docker) repo will
be more up-to-date than the instructions below, as that repo actually has
runnable code in the Dockerfile!
```bash
# Clone all the repos down to your project folder
git clone git@git.kirsle.net:apps/doodle-rtp rtp
git clone git@git.kirsle.net:apps/doodle-vendor vendor
git clone git@git.kirsle.net:apps/doodle-masters masters
git clone git@git.kirsle.net:apps/doodle doodle
git clone https://git.kirsle.net/SketchyMaze/rtp rtp
git clone https://git.kirsle.net/SketchyMaze/vendor vendor
git clone https://git.kirsle.net/SketchyMaze/masters masters
git clone https://git.kirsle.net/SketchyMaze/doodle doodle
git clone https://git.kirsle.net/SketchyMaze/doodads doodle/deps/doodads
# Enter doodle/ project
cd doodle/
# Copy fonts and levels in
cp ../masters/levels assets/levels
cp ../assets/levelpacks assets/levelpacks
cp ../vendor/fonts assets/fonts
mkdir rtp && cp -r ../rtp/* rtp/
@ -103,7 +148,7 @@ make setup # -or-
go get ./... # install dependencies etc.
# The app should build now. Build and install the doodad tool.
go install git.kirsle.net/apps/doodle/cmd/doodad
go install git.kirsle.net/SketchyMaze/doodle/cmd/doodad
doodad --version
# "doodad version 0.3.0-alpha build ..."
@ -121,13 +166,27 @@ make mingw
make release
```
The `make setup` command tries to do the above.
`make build` produces a local binary in the bin/ folder and `make dist`
will build an app for distribution in the dist/ folder.
Levels should be copied in from the doodle-masters repo into the
assets/levels/ folder before building the game.
The bootstrap.py script does all of the above up to `make dist` so if you need
fully release the game by hand (e.g. on a macOS host) you can basically get away
with:
1. Clone the doodle repo and cd into it
2. Run `bootstrap.py` to fully set up your OS with dependencies and build a
release quality version of the game with all latest assets (the script finishes
with a `make dist`).
3. Run `make release` to package the dist/ artifact into platform specific
release artifacts (.rpm/.deb/.tar.gz bundles for Linux, .zip for Windows,
.dmg if running on macOS) which output into the dist/release/ folder.
Before step 3 you may want to download the latest Guidebook to bundle with
the game (optional). Grab and extract the tarball and run `make dist && make release`:
```bash
wget -O - https://download.sketchymaze.com/guidebook.tar.gz | tar -xzvf -
```
## Fonts
@ -153,7 +212,7 @@ The doodle-vendor repo has copies of these fonts.
Makefile commands for Unix-likes:
* `make setup`: install Go dependencies and set up the build environment
* `make doodads`: build the default Doodads from sources in `dev-assets/`
* `make doodads`: build the default Doodads from sources in `deps/doodads/`
* `make build`: build the Doodle and Doodad binaries to the `bin/` folder.
* `make buildall`: runs all build steps: doodads, build.
* `make build-free`: build the shareware binaries to the `bin/` folder. See
@ -195,7 +254,7 @@ brew install golang sdl2 sdl2_ttf sdl2_mixer pkg-config
## Flatpak for Linux
The repo for this is at <https://code.sketchymaze.com/game/flatpak>.
The repo for this is at <https://git.kirsle.net/SketchyMaze/flatpak>.
## Windows Cross-Compile from Linux
@ -244,33 +303,107 @@ cp /usr/x86_64-w64-mingw32/bin/SDL*.dll bin/
SDL2_ttf requires libfreetype, you can get its DLL here:
https://github.com/ubawurinna/freetype-windows-binaries
# Old Docs
## Build on macOS from scratch
## Build Tags
Here are some detailed instructions how to build Sketchy Maze from a fresh
install of macOS Ventura that assumes no previous software or configuration
was applied to the system yet.
These aren't really used much anymore but documented here:
Install homebrew: https://brew.sh pay attention to the instructions at the end
of the install to set up your zsh profile for homebrew to work correctly.
### shareware
Clone the doodle repository:
> Files ending with `_free.go` are for the shareware release as opposed to
> `_paid.go` for the full version.
```bash
git clone https://git.kirsle.net/SketchyMaze/doodle
cd doodle
```
Builds the game in the free shareware release mode.
Note: on a fresh install, invoking the `git` command may cause macOS to install
developer tools and Xcode. After installed, run the git clone again to finish
cloning the repository.
Run `make build-free` to build the shareware binary.
Set your Go environment variables: edit your ~/.zprofile and ensure that $GOPATH
is configured and that your $PATH includes $GOPATH/bin. **Note:** restart your
terminal session or reload the config file (e.g. `. ~/.zprofile`) after making
this change.
Shareware releases of the game have the following changes compared to the default
(release) mode:
```bash
# in your .zprofile, .bash_profile, .zshrc or similar shell config
export GOPATH="${HOME}/go"
export PATH="${PATH}:${GOPATH}/bin"
```
* No access to the Doodad Editor scene in-game (soft toggle)
Run the bootstrap script:
### developer
```bash
python3 bootstrap.py
```
> Files ending with `_developer.go` are for the developer build as opposed to
> `_release.go` for the public version.
Answer N (default) when asked to clone dependency repos over ssh. The bootstrap
script will `brew install` any necessary dependencies (Go, SDL2, etc.) and clone
support repos for the game (doodads, levelpacks, assets).
Developer builds support extra features over the standard release version:
# WebAssembly
* Ability to write the JSON file format for Levels and Doodads.
There is some **experimental** support for a WebAssembly build of Sketchy Maze
since the very early days. Early on, the game "basically worked" but performance
could be awful: playing levels was OK but clicking and dragging in the editor
would cause your browser to freeze. Then for a time, the game wouldn't even get
that far. Recently (December 2023), WASM performance seems much better but there
are strange graphical glitches:
Run `make build-debug` to build a developer version of the program.
* On the title screen, the example levels in the background load OK and their
doodads will wander around and performance seems OK.
* But during Play Mode, only the menu bar draws but nothing else on the screen.
* In the Level Editor, the entire screen is white BUT tooltips will appear and
the menu bar can be clicked on (blindly) and the drop-down menus do appear.
Some popups like the Palette Editor can be invoked and draw to varying degrees
of success.
Some tips to get a WASM build to work:
* For fonts: symlink it so that ./wasm/fonts points to ./assets/fonts.
* You may need an updated wasm_exec.js shim from Go. On Fedora,
`dnf install golang-misc` and `cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .`
from the wasm/ folder.
* Run `make wasm` to build the WASM binary and `make wasm-serve` to run a simple
Go web server to serve it from.
# Build Tags
Go build tags used by this game:
## doodad
This tag is used when building the `doodad` command-line tool.
It ensures that the embedded bindata assets (built-in doodads, etc.) do not
need to be bundled into the doodad binary, but only the main game binary.
## dpp
The dpp tag stands for Doodle++ and is used for official commercial builds of
the game. Doodle++ builds include additional code not found in the free & open
source release of the game engine.
This build tag should be set automatically by the Makefile **if** the deps/
folder has a git clone of the dpp project. The bootstrap.py script will clone
the dpp repo **if** you use SSH to clone dependencies: so you will need SSH
credentials to the upstream git server. It basically means that third-party
users who download the open source release will not have the dpp dependency,
and will not build dpp copies of the game.
If you _do_ have the dpp dependency, you can force build (and run) FOSS
versions of the game via the Makefile commands `make build-free`,
`make run-free` or `make dist-free` which are counterparts to the main make
commands but which deliberately do not set the dpp build tag.
In source code, files ending with `_dpp.go` and `_foss.go` are conditionally
compiled depending on this build tag.
How to tell whether your build of Sketchy Maze is Doodle++ include:
* The version string on the title screen.
* FOSS builds (not dpp) will say "open source" in the version.
* DPP builds may say "shareware" if unregistered or just the version.

View File

@ -1,5 +1,854 @@
# Changes
## v0.14.1 (TBD)
The file format for Levels and Doodads has been optimized to store drawing data
with Run Length Encoding (RLE) compression which nets a filesize savings upwards
of 90%, especially for levels featuring large areas of solid colors.
* For example, the Shapeshifter level from the First Quest has shrank from
22 MB to only 263 KB.
* The complete size of the First Quest levelpack from the previous release of
the game shrinks from 50 MB to only 1.8 MB!
* The game is still able to load levels and doodads created by previous releases
and will automatically convert them into the optimized RLE format when you
save them back to disk.
* The `doodad resave` command can also optimize your levels and doodads outside
of the game's editor.
Other miscellaneous changes:
* Command line option `sketchymaze --new` to open the game quickly to a new
level in the editor.
Cleanup of old features and unused code:
* The game can no longer save any Chunk files in their legacy JSON format: it
can still read JSON but all writes will be in the binary chunk format (usually
with the new RLE compression). Regular releases of the game have not been
writing in the JSON format for a while as it is controlled by hard-coded
feature flag constants.
## 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
formats to improve performance and memory usage.
Some new features:
* **Doodads can be non-square!** You can now set a rectangular canvas size
for your doodads. Many of the game's built-in doodads that used to be
off-center before (doors, creatures) because their sprites were not squares
now have correct rectangular shapes.
* A **Cheats Menu** has been added which enables you to enter many of the
game's cheat codes by clicking on buttons instead. Enable it through the
"Experimental" tab of the Settings, and the cheats menu can be opened from
the Help menu bar during gameplay.
* The game now supports **Signed Levels** and levelpacks which will enable the
free version of the game to play levels that have embedded doodads in them.
The idea is that the base game in the future may come with levelpacks that
use custom doodads (to tell a specific story) and these doodads may be so
specific and niche that they will ride with the levelpack instead of be
built-in to the game. To allow the free version of the game to play these
levels, the levelpacks will be signed. It also makes it possible for
promotional levelpacks or "free DLC" to be shipped separately and allow
free versions of the game to play them with their attached custom doodads.
Other miscellaneous changes:
* The default Author name on your new drawings will prefer to use your
license registration name (if the game is registered) before falling back
on your operating system's $USER name like before.
* In the level editor, you can now use the Pan Tool to access the actor
properties of doodads you've dropped into your level. Similar to the
Actor Tool, when you mouse-over an actor on your level it will highlight
in a grey box and a gear icon in the corner can be clicked to access
its properties. Making the properties available for the Pan Tool can
help with touchscreen devices, where it is difficult to touch the
properties button without accidentally dragging the actor elsewhere
on your level as might happen with the Actor Tool!
* Fix a bug wherein the gold "perfect run" icon next to the level timer
would sometimes not appear, especially after you had been cheating
before - if you restart the level with no cheats active the gold icon
should now always appear.
* New cheat code: `tesla` will send a power signal to ALL actors on the
current level in play mode - opening all electric doors and trapdoors.
May cause fun chaos during gameplay. Probably not very useful.
* Start distributing AppImage releases for GNU/Linux (64-bit and 32-bit)
Some technical changes related to file format optimization:
* Palettes are now limited to 256 colors so that a palette index can fit
into a uint8 on disk.
* Chunks in your level and doodad files are now encoded in a binary format
instead of JSON for a reduction in file size. The current (and only)
chunk implementation (the MapAccessor) encodes to a binary format involving
trios of varints (X, Y position + a Uvarint for palette index).
* Chunk sizes in levels/doodads is now a uint8 type, meaning the maximum
chunk size is 255x255 pixels. The game's default has always been 128x128
but now there is a limit. This takes a step towards optimizing the game's
file formats: large world coordinates (64-bit) are mapped to a chunk
coordinate, and if each chunk only needs to worry about the 255 pixels
in its territory, space can be saved in memory without chunks needing to
theoretically support 64-bit sizes of pixels!
## v0.13.1 (Oct 10 2022)
This release brings a handful of minor new features to the game.
First, there are a couple of new Pixel Attributes available in the level editor:
* Semi-Solid: pixels with this attribute only behave as "solid" when walked on
from above. The player can jump through the bottom of a Semi-Solid and land
on top, and gradual slopes can be walked up and down as well, but a steep
slope or a wall can be simply passed through as though it were just decoration.
* Slippery: the player's acceleration and friction are reduced when walking on
a slippery floor. In the future, players and other mobile doodads may slide
down slippery slopes automatically as well (not yet implemented).
* These attributes are available in the Level Editor by clicking the "Edit"
button on your Palette (or the "Tools -> Edit Palette" menu). The Palette
Editor now has small icon images for the various attributes to make room for
the expanded arsenal of options.
Doodad/Actor Runtime Options have been added:
* In the Doodad Editor's "Doodad Properties" window, see the new "Options" tab.
* Doodad Options allow a map creator to customize certain properties about your
doodad, on a per-instance basis (instances of doodads are called "actors" when
placed in your level).
* In the Level Editor when the Actor Tool is selected, mousing over a doodad on
your level will show a new gear icon in the corner. Clicking the icon will open
the Actor Properties window, where you may toggle some of the doodad options
(if a doodad has any options available).
* Options can be of type boolean, string, or integer and have a custom name and a
default value at the doodad level. In the Level Editor, the map creator can
set values for the available options which the doodad script can read using the
`Self.GetOption()` method.
* Several of the game's built-in doodads have options you can play with, which are
documented below.
New and updated doodads:
* "Look At Me" is a new Technical doodad that will draw the camera's attention
to it when it receives a power signal from a linked button. For example, if
a button would open an Electric Door far across the level, you can also place
a "Look At Me" near the door and link the button to both doodads. When the
button is pressed, the camera will scroll to the "Look At Me" and the player
can see that the door has opened.
* Anvils will now attract the camera's attention while they are falling.
Several of the game's built-in doodads have new Actor Runtime Options you can
configure in your custom levels:
* Warp Doors: "locked (exit only)" will make it so the player can not enter the
warp door - they will get a message on-screen that it is locked, similar to
how warp doors behave when they aren't linked to another door. If it is linked
to another door, the player may still exit from the 'locked' door -
essentially creating a one-way warp, without needing to rely on the
orange/blue state doors. The "Invisible Warp Door" technical doodad also
supports this option.
* Electric Door & Electric Trapdoor: check the "opened" option and these doors
will be opened by default when the level gameplay begins. A switch may still
toggle the doors closed, or if the doors receive and then lose a power signal
they will close as normal.
* Colored Doors & Small Key Door: you may mark the doors as "unlocked" at the
start of your level, and they won't require a key to open.
* Colored Keys & Small Key: you may mark the keys as "has gravity" and they
will be subject to the force of gravity and be considered a "mobile" doodad
that may activate buttons or trapdoors that they fall onto.
* Gemstones: these items already had gravity by default, and now they have a
"has gravity" option you may disable if you'd prefer gemstones not to be
subject to gravity (and make them behave the way keys used to).
* Gemstome Totems: for cosmetic purposes you may toggle the "has gemstone"
option and the totem will already have its stone inserted at level start.
These gemstones will NOT emit a power signal or interact normally with
linked totems - they should be configured this way only for the cosmetic
appearance, e.g., to have one totem filled and some others empty; only the
empty totems should be linked together and to a door that would open when
they are all filled.
* Fire Region: you may pick a custom "name" for this doodad (default is "fire")
to make it better behave as normal fire pixels do: "Watch out for (name)!"
Improvements in support of custom content:
* Add a JavaScript "Exception Catcher" window in-game. If your doodad scripts
encounter a scripting error, a red window will pop up showing the text of
the exception with buttons to copy the full text to your clipboard (in case
it doesn't all fit on-screen) and to suppress any further exceptions for
the rest of your game session (in case a broken doodad is spamming you with
error messages). Cheat codes can invoke the Exception Catcher for testing:
`throw <message>` to show custom text, `throw2` to test a "long" message
and `throw3` to throw a realistic message.
* Calling `console.log()` and similar from doodad scripts will now prefix the
log message with the doodad's filename and level ID.
There are new JavaScript API methods available to doodad scripts:
* `Self.CameraFollowMe()` will attract the game's camera viewport to center
on your doodad, taking the camera's focus away from the player character.
The camera will return to the player if they enter a directional input.
* `Self.Options()` returns a string array of all of the options available on
the current doodad.
* `Self.GetOption(name)` returns the configured value for a given option.
Some improvements to the `doodad` command-line tool:
* `doodad show` will print the Options on a .doodad file and, when showing
a .level file with the `--actors` option, will list any Options configured
on a level's actors where they differ from the doodad's defaults.
* `doodad edit-doodad` adds a `--option` parameter to define an option on a
doodad programmatically. The syntax is like `--option name=type=default`
for example `--option unlocked=bool=true` or `--option unlocked=bool`; the
default value is optional if you want it to be the "zero value" (false,
zero, or empty string).
Minor fixes and improvements:
* Add a "Wait" modal with a progress bar. Not used yet but may be useful
for long operations like Giant Screenshot or level saving to block input
to the game while it's busy doing something. Can be tested using the
cheat code "test wait screen"
* Detect the presence of a touchscreen device and automatically disable
on-screen touch hints during gameplay if not on a touch screen.
* Mobile Linux: mark the Sketchy Maze launcher as supporting the mobile
form-factor for the Phosh desktop shell especially.
* Fix the Crusher doodad sometimes not falling until it hits the ground
and stopping early on slower computers.
* Small tweaks to player physics - acceleration increased from 0.025 to
0.04 pixels per tick.
## v0.13.0 (May 7 2022)
This is a major update that brings deep architectural changes and a lot
of new content to the game.
Swimming physics have been added:
* The **water** pixels finally do something besides turn the character
blue: swimming physics have finally been hooked up!
* While the player is touching water pixels (and is colored blue),
your gravity and jump speed are reduced, but you can "jump"
infinite times in order to swim higher in the water. Hold the jump
button to climb slowly, spam it to climb quickly.
* The **Azulians** understand how to swim, too, though left to their
own devices they will sink to the bottom of a body of water. They'll
swim (jump) up if you're detected and above them. The Blue Azulian
has the shortest vertical aggro radius, so it's not a very good swimmer
but the White Azulian can traverse water with ease and track you
from a greater distance.
New levels:
* **The Jungle** (First Quest) - a direct sequel to the Boat level, it's
a jungle and Mayan themed platformer featuring many of the new doodads
such as snakes, gemstones, and crushers.
* **Gems & Totems** (Tutorial, Lesson 4) - a tutorial level about the
new Gem and Totem doodads.
* **Swimming** (Tutorial, Lesson 5) - a tutorial level to learn how
"water pixels" work with some moderately safe platforming puzzles
included.
* **Night Sky** (Azulian Tag) - a moderately difficult Azulian Tag level
with relatively few enemies but plenty of tricky platforming.
* Some of the existing levels have had minor updates to take advantage
of newer game features, such as the water being re-done for the Castle
level.
New doodads:
* **Blue Bird:** a blue version of the Bird which flies in a sine wave
pattern about its original altitude, and has a larger search range to
dive at the player character.
* **Snake:** a green snake that sits coiled up and always faces the
player. If you get nearby and try and jump over, the Snake will jump
up and hope to catch you.
* **Crusher:** a block-headed enemy with an iron helmet which tries to
drop on you from above. Its helmet makes a safe platform to ride
back up like an elevator.
* **Gems and Totems:** four collectible gems (in different colors and
shapes) that slot into Totems of a matching shape. Totems can link
together to require multiple gemstones before they'll emit a power
signal to other linked doodads.
New **File Formats**:
* Levels and Doodads have a new file format based on ZIP files,
like levelpacks.
* It massively improves loading screen times and helps the
game keep a substantially lighter memory footprint (up to 85%
less memory used, like 1.5 GB -> 200 MB on _Azulian Tag - Forest_).
* **Your old levels and doodads still work**! The next time you save
them, they will be converted to the new file format automatically.
* The `doodad` tool can also upgrade your levels by running:
`doodad edit-level --touch <filename>.level`
Other new content to use in your levels:
* New wallpapers: Dotted paper (dark), Parchment paper (red, blue,
green, and yellow).
* New palette: Neon Bright, all bright colors to pair with a dark level
wallpaper.
* New brush pattern: Bubbles. The default "water" color of all the game's
palettes will now use the Bubbles pattern by default.
New cheat codes:
* `super azulian`: play as the Red Azulian.
* `hyper azulian`: play as the White Azulian.
* `bluebird`: play as the Blue Bird.
* `warp whistle`: automatically win the current level, with a snarky
cheater message in the victory dialog. It will mark the level as
Completed but not reward a high score.
* `$ d.SetPlayerCharacter("anything.doodad")` - set your character to any
doodad you want, besides the ones that have dedicated cheat codes. The
".doodad" suffix is optional. Some to try out are "key-blue", "anvil",
or "box". If you are playing as a key, a mob might be able to collect
you! This will softlock the level, but another call to
`d.SetPlayerCharacter()` will fix it! Use the `pinocchio` cheat or
restart the game to return to the default character (boy.doodad)
Updates to the JavaScript API for doodads:
* `Self.IsWet() bool` can test if your actor is currently in water.
Other changes this release:
* Editor: fancy **mouse cursors** gives some visual feedback about what
tool is active in the editor, with a Pencil and a Flood Fill
cursor when those tools are selected.
* Editor: your **Palette** buttons will now show their pattern with their
color as the button face, rather than just the color.
* Editor: Auto-save is run on a background thread so that, for large
levels, it doesn't momentarily freeze the editor on save when it runs.
* Editor: Fix the Link Tool forgetting connections when you pick up and
drop one of the linked doodads.
* Link Tool: if you click a doodad and don't want to link it to another,
click the first doodad again to de-select it (or change tools).
* Editor: your last scroll position on a level is saved with it, so the
editor will be where you left it when you reopen your drawing.
* Doodad tool: `doodad edit-level --resize <int>` can re-encode a level
file using a different chunk size (the default has been 128).
Experimental! Very large or small chunk sizes can lead to different
performance characteristics in game!
* Fixed the bug where characters' white eyes were showing as transparent.
## v0.12.1 (April 16 2022)
This update focuses on memory and performance improvements for the game.
Some larger levels such as "Azulian Tag - Forest" could run out of
memory on 32-bit systems. To improve on memory usage, the game more
aggressively frees SDL2 textures when no longer needed and it doesn't
try to keep the _whole_ level's chunks ready in memory (as rendered
images) -- only the chunks near the window viewport are loaded, and
chunks that leave the viewport are freed to reclaim memory. Improvements
are still a work in progress.
New fields are added to the F3 debug overlay to peek at its performance:
* "Textures:" shows the count of SDL2 textures currently loaded
in memory. Some textures, such as toolbar buttons and the
loadscreen wallpaper, lazy load and persist in memory. Most level
and doodad textures should free when the level exits.
* "Sys/Heap:" shows current memory utilization in terms of MiB taken
from the OS and MiB of active heap memory, respectively.
* "Threads:" counts the number of goroutines currently active. In
gameplay, each actor monitors its PubSub queue on a goroutine and
these should clean up when the level exits.
* "Chunks:" shows the count of level chunks inside and outside the
loading viewport.
Some other changes and bug fixes in this release include:
* Fixed the bug where the player was able to "climb" vertical walls to
their right.
* When entering a cheat code that changes the default player character
during gameplay, you immediately become that character.
## v0.12.0 (March 27 2022)
This update adds several new features to gameplay and the Level Editor.
A **Game Rules** feature has been added to the Level Editor which allows
customizing certain gameplay features while that level is being played.
These settings are available in the Level Properties window of the editor:
* The **Difficulty** rule can modify the behavior of enemy doodads
when the level is played. Choose between Peaceful, Normal, or Hard.
* On Peaceful, Azulians and Birds don't attack the player, acting
like pre-0.11.0 versions that ignored the player character.
* On Hard difficulty, Azulians have an infinite aggro radius
(they'll immediately hunt the player from any distance on
level start) and they are hostile to _all_ player creatures.
* **Survival Mode** changes the definition of "high score" for levels
where the player is very likely to die at least once.
* The silver high score (respawned at checkpoint) will be for the
_longest_ time survived on the level rather than the fastest time
to complete it.
* The gold high score (got to an Exit Flag without dying once) is
still rewarded to the fastest time completing the level.
An update to the Level Editor's toolbar:
* New **Text Tool** to easily paste messages onto your level, selecting
from the game's built-in fonts.
* New **Pan Tool** to be able to scroll the level safely by dragging with
your mouse or finger.
* New **Flood Tool** (or paint bucket tool) can be used to replace
contiguous areas of your level from one color to another.
* The toolbar buttons are smaller and rearranged. On medium-size screens
or larger, the toolbar buttons are drawn side-by-side in two columns.
On narrower screens with less real estate, it will use a single column
when it fits better.
New levels:
* An **Azulian Tag** levelpack has been added featuring two levels so
far of the Azulian Tag game mode.
New doodads:
* A technical doodad for **Reset Level Timer** resets the timer to zero,
one time, when touched by the player. If the doodad receives a power
signal from a linked doodad, it can reset the level timer again if the
player touches it once more.
Updates to the JavaScript API for custom doodads:
* New global integer `Level.Difficulty` is available to doodad scripts to
query the difficulty setting of the current level:
* Peaceful (-1): `Level.Difficulty < 0`
* Normal (0): `Level.Difficulty == 0`
* Hard (1): `Level.Difficulty > 1`
* New function `Level.ResetTimer()` resets the in-game timer to zero.
New cheat codes:
* `test load screen` tests the loading screen UI for a few seconds.
* `master key` allows playing locked Story Mode levels without unlocking
them first by completing the earlier levels.
Other changes:
* Several screens were re-worked to be responsive to mobile screen sizes,
including the Settings window, loading screen, and the level editor.
* Fixed a bug where making the app window bigger during a loading screen
caused the Editor to not adapt to the larger window.
* Don't show the _autosave.doodad in the Doodad Dropper.
* The Azulians have had their jump heights buffed slightly.
* Birds no longer register as solid when colliding with other birds (or
more generally, characters unaffected by gravity).
Bugs fixed:
* When modifying your Palette to rename a color or add an additional
color, it wasn't possible to draw with that new color without fully
exiting and reloading the editor - this is now resolved.
* The palette editor will try and prevent the user from giving the same
name to different colors.
## v0.11.0 (Feb 21 2022)
New features:
* **Game Controller** support has been added! The game can now be played
with an Xbox style controller, including Nintendo Pro Controllers. The
game supports an "X Style" and "N Style" button layout, the latter of
which swaps the A/B and the X/Y buttons so gameplay controls match the
button labels in your controller.
* The **JavaScript Engine** for doodad scripts has been switched from
github.com/robertkrimen/otto to github.com/dop251/goja which helps
"modernize" the experience of writing doodads. Goja supports many
common ES6 features already, such as:
* Arrow functions
* `let` and `const` keywords
* Promises
* for-of loops
* The **JavaScript API** has been expanded with new functions and
many of the built-in Creatures have gotten an A.I. update.
* For full versions of the game, the **Publish Level** function is now
more streamlined to just a checkbox for automatically bundling your
doodads next time you save the level.
New levels:
* **The Zoo:** part of the Tutorial levelpack, it shows off all of the
game's built-in doodads and features a character selection room to
play as different creatures.
* **Shapeshifter:** a new level on the First Quest where you switch
controls between the Boy, Azulian and the Bird in order to clear the
level.
Some of the built-in doodads have updates to their A.I. and creatures
are becoming more dangerous:
* The **Bird** now searches for the player diagonally in front of
it for about 240px or so. If spotted it will dive toward you and
it is dangerous when diving! When _playing_ as the bird, the dive sprite
is used when flying diagonally downwards. The player-controlled bird
can kill mobile doodads by diving into them.
* The **Azulians** will start to follow the player when you get
close and they are dangerous when they touch you -- but not if
you're the **Thief.** The red Azulian has a wider search radius,
higher jump and faster speed than the blue Azulian.
* A new **White Azulian** has been added to the game. It is even faster
than the Red Azulian! And it can jump higher, too!
* The **Checkpoint Flag** can now re-assign the player character when
activated! Just link a doodad to the Checkpoint Flag like you do the
Start Flag. When the player reaches the checkpoint, their character
sprite is replaced with the linked doodad!
* The **Anvil** is invulnerable -- if the player character is the Anvil
it can not die by fire or hostile enemies, and Anvils can not destroy
other Anvils.
* The **Box** is also made invulnerable so it can't be destroyed by a
player-controlled Anvil or Bird.
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
* `Actors.SetPlayerCharacter(filename string)`: replace the nearest
player character with the named doodad, e.g. "boy.doodad"
* `Self.Invulnerable() bool` and `Self.SetInvulnerable(bool)`: set a
doodad is invulnerable, especially for the player character, e.g.
if playing as the Anvil you can't be defeated by mobs or fire.
New cheat codes:
* `god mode`: toggle invincibility. When on, fire pixels and hostile
mobs can't make you fail the level.
* `megaton weight`: play as the Anvil by default on levels that don't
specify a player character otherwise.
Other changes:
* When respawning from a checkpoint, the player is granted 3 seconds of
invulnerability; so if hostile mobs are spawn camping the player, you
don't get soft-locked!
* The draw order of actors on a level is now deterministic: the most
recently added actor will always draw on top when overlapping another,
and the player actor is always on top.
* JavaScript exceptions raised in doodad scripts will be logged to the
console instead of crashing the game. In the future these will be
caught and presented nicely in an in-game popup window.
* When playing as the Bird, the flying animation now loops while the
player is staying still rather than pausing.
* The "Level" menu in Play Mode has options to restart the level or
retry from last checkpoint, in case a player got softlocked.
* When the game checks if there's an update available via
<https://download.sketchymaze.com/version.json> it will send a user
agent header like: "Sketchy Maze v0.10.2 on linux/amd64" sending only
static data about the version and OS.
## v0.10.1 (Jan 9 2022)
New features:
* **High scores and level progression:** when playing levels out of
a Level Pack, the game will save your progress and high scores on
each level you play. See details on how scoring works so far, below.
* **Auto-save** for the Editor. Automatically saves your drawing every
5 minutes. Look for e.g. the _autosave.level to recover your drawing
if the game crashed or exited wrongly!
* **Color picker UI:** when asked to choose a color (e.g. for your level
palette) a UI window makes picking a color easy! You can still manually
enter a hexadecimal color value but this is no longer required!
Scoring system:
* The high score on a level is based on how quickly you complete it.
A timer is shown in-game and there are two possible high scores
for each level in a pack:
* Perfect Time (gold): if you complete the level without dying and
restarting at a checkpoint.
* Best Time (silver): if you had used a checkpoint.
* The gold/silver icon is displayed next to the timer in-game; it
starts gold and drops to silver if you die and restart from checkpoint.
It gives you a preview of which high score you'll be competing with.
* If cheat codes are used, the user is not eligible for a high score
but may still mark the level "completed."
* Level Packs may have some of their later levels locked by default,
with only one or a few available immediately. Completing a level will
unlock the next level until they have all been unlocked.
New and changed doodads:
* **Invisible Warp Door:** a technical doodad to create an invisible
Warp Door (press Space/'Use' key to activate).
* All **Warp Doors** now require the player to be grounded before they
can be opened, or else under the effects of antigravity. You shouldn't
be able to open Warp Doors while falling or jumping off the ground
at them.
Revised levels:
* Desert-2of2.level uses a new work-around for the unfortunate glitch
of sometimes getting stuck on two boxes, instead of a cheat code
being necessary to resolve.
* Revised difficulty on Tutorial 2 and Tutorial 3.
Miscellaneous changes:
* Title Screen: picks a random level from a few options, in the future
it will pick random user levels too.
* Play Mode gets a menu bar like the Editor for easier navigation to
other game features.
* New dev shell command: `titlescreen <level name>` to load the Title
Screen with the named level as its background, can be used to load
user levels _now_.
* For the doodads JavaScript API: `time.Since()` is now available (from
the Go standard library)
* Fix a cosmetic bug where doodads scrolling off the top or left edges
of the level were being drawn incorrectly.
* Fix the Editor's status bar where it shows your cursor position
relative to the level and absolute to the app window to show the
correct values of each (they were reversed before).
* The developer shell now has a chatbot in it powered by RiveScript.
## v0.10.0 (Dec 30 2021)
New features and changes:
* **Level Packs:** you can group a set of levels into a sequential
adventure. The game's built-in levels have been migrated into Level
Packs and users can create their own, too!
* **Crosshair Option:** in the level editor you can have a crosshair
drawn at your cursor position, which may help align things while
making a level. Find the option in the game's Settings window.
* **Smaller Palette Colors:** the color buttons in the Level Editor are
smaller and fit two to a row. This allows for more colors but may be
difficult for touch controls.
* Doodad AI updates: the **Bird** records its original altitude and will
attempt to fly back there when it can, so in case it slid up or down a
ramp it will correct its height when it comes back the other way.
* The "New Level" and "New Doodad" functions on the main menu are
consolidated into a window together that can create either, bringing
a proper UI to creating a doodad.
* Added a setting to **hide touch control hints** from Play Mode.
* The title screen is more adaptive to mobile. If the window height isn't
tall enough to show the menu, it switches to a 'landscape mode' layout.
* Adds a custom icon to the application window.
A few notes about level packs:
* A levelpack is basically a zip file containing levels and custom
doodads. **Note:** the game does not yet handle the doodads folder
of a levelpack at all.
* The `doodad` command-line tool can create .levelpack files. See
`doodad levelpack create --help`. This is the easiest way to
generate the `index.json` file.
* In the future, a .levelpack will be able to hold custom doodads on
behalf of the levels it contains, de-duplicating files and saving
on space. Currently, the levels inside your levelpack should embed
their own custom doodads each.
* Free (shareware) editions of the game can create and play custom
level packs that use only the game's built-in doodads. Free versions
of the game can't play levels with embedded custom doodads. You can
always copy custom .doodad files into your profile directory though!
Bugs fixed:
* Undo/Redo now works again for the Doodad Editor.
* Fix crash when opening the Doodad Editor (v0.9.0 regression).
* The Play Level/Edit Drawing window is more responsive to small screens
and will draw fewer columns of filenames.
* Alert and Confirm popup modals always re-center themselves, especially
to adapt to the user switching from Portrait to Landscape orientation
on mobile.
## v0.9.0 (Oct 9, 2021)
New features:
* **Touch controls:** the game is now fully playable on small
touchscreen devices! For gameplay, touch the middle of the screen
to "use" objects and touch anywhere _around_ that to move the
player character in that direction. In the level editor, swipe
with two fingers to pan and scroll the viewport.
* **Proper platforming physics:** the player character "jumps" by
setting a velocity and letting gravity take care of the rest,
rather than the old timer-based flat jump speed approach. Most
levels play about the same but the jump reach _is_ slightly
different than before!
* **Giant Screenshot:** in the Level Editor you can take a "Giant
Screenshot" of your _entire_ level as it appears in the editor
as a large PNG image in your game's screenshots folder. This is
found in the "Level" menu in the editor.
* **Picture-in-Picture Viewport:** in the Level Editor you can open
another viewport window into your level. This window can be resized,
moved around, and can look at a different part of your level from
your main editor window. Mouse over the viewport and your arrow keys
scroll it instead of the main window.
* **"Playtest from here":** if you click and drag from the "Play (P)"
button of the level editor, you can drop your player character
anywhere you want on your level, and play from there instead of
the Start Flag.
* **Middle-click to pan the level:** holding the middle click button
and dragging your mouse cursor will scroll the level in the editor
and on the title screen. This can be faster than the arrow keys to
scroll quickly!
* **Zoom in/out** support has come out of "experimental" status. It's
still a _little_ finicky but workable!
* **Bounded level limits** are now configurable in the Level Properties
window, to set the scroll constraints for Bounded level types.
Some new "technical" doodads are added to the game. These doodads are
generally invisible during gameplay and have various effects which can
be useful to set up your level the right way:
* **Goal Region:** a 128x128 region that acts as an Exit Flag and will
win the level if touched by the player.
* **Checkpoint Region:** a 128x128 invisible checkpoint flag that sets
the player's spawn point.
* **Fire Region:** a 128x128 region that triggers the level fail
condition, behaving like fire pixels.
* **Power Source:** a 64x64 invisible source of power. Link it to doodads
like the Electric Door and it will emit a power(true) signal on level
start, causing the door to "begin" in an opened state.
* **Stall Player (250ms):** when the player touches this doodad, control
freezes for 250ms, one time. If this doodad receives power from a linked
button or switch, it will reset the trap, and stall the player once more
should they contact it again.
The Doodad Dropper window has a dedicated category for these
technical doodads.
New methods available to the Doodad JavaScript API:
* `Self.Hide()` and `Self.Show()` to turn invisible and back.
* `Self.GetVelocity()` that returns a Vector of the actor's current speed.
* New broadcast message type: `broadcast:ready` (no arguments). Subscribe
to this to know the gameplay is ready and you can safely publish messages
to your linked doodads or whatever, which could've ended up in deadlocks
if done too early!
* New constants are exposed for Go time.Duration values: `time.Hour`,
`time.Minute`, `time.Second`, `time.Millisecond` and `time.Microsecond`
can be multiplied with a number to get a duration.
Some slight UI polish:
* The **Doodad Dropper** window of the level editor now shows a slotted
background where there are any empty doodad slots on the current page.
* Your **scroll position** is remembered when you playtest the level; so
coming back to the editor, your viewport into the level as where you
left it!
* New **keybinds** are added to the Level & Doodad Editor:
* `Backspace` to close the current popup UI modal
* `Shift+Backspace` to close _all_ popup UI modals
* `v` to open a new Viewport window
* Some **flashed messages** are orange instead of blue to denote an 'error'
status. The colors of the fonts are softened a bit.
* New **cheat code**: `show all actors` to make all invisible actors shown
during Play Mode. You would be able to see all the technical doodads
this way!
* New command for the doodad CLI tool: `doodad edit-level --remove-actor`
to remove actors by name or UUID from a level file.
## v0.8.1 (September 12, 2021)
New features:
* **Enable Experimental Features UI:** in the game's Settings window
there is a tab to enable experimental features. It is equivalent to
running the game with the `--experimental` option but the setting
in-game is persistent across runs of the game.
* **Zoom In/Out:** the Zoom feature _mostly_ works but has a couple
small bugs. The `+` and `-` keys on your number bar (-=) will zoom in
or out in the Editor. Press `1` to reset zoom to 100% and press `0`
to scroll the level back to origin. These controls are also
available in the "View" menu of the editor.
* **Replace Palette:** with experimental features on it is possible to
select a different palette for your already-created level. This will
replace colors on your palette until the template palette has been
filled in, and pixels already drawn on your level will update too.
Other changes:
* The title screen buttons are more colorful.
* This release begins to target 32-bit Windows and Linux among its builds.
## v0.8.0 (September 4, 2021)
This release brings some new features, new doodads, and new levels.
@ -243,7 +1092,7 @@ Some bugs fixed:
* **Collision fixes:** you should be able to walk up gentle slopes to the left
without jumping, as easily as you could to the right.
* **Debugging:** the F4 key to show collision hitboxes around all doodads in
Play Mode now functions again, and draws boxes around _all_ doodads, not just the
Play Mode now functions again, and draws boxes around _all_ doodads, not just the
player character.
* **Hitboxes are tighter:** a doodad's declared hitbox size (from their JavaScript)
is used when a doodad collides against level geometry or other doodads. Meaning:

121
Dockerfile Normal file
View File

@ -0,0 +1,121 @@
##
# Fully build and distribute Linux and Windows binaries for Project: Doodle.
#
# This is designed to be run from a fully initialized Doodle environment
# (you had run the bootstrap.py for your system, and the doodads and
# levelpacks are installed in the assets/ folder, and `make dist` would
# build a release quality game for your local machine).
#
# It will take your working directory (minus any platform-specific artifacts
# and git repos cloned in to your deps/ folder) and build them from a sane
# Debian base and generate full release artifacts for:
#
# - Linux (x86_64 and i686) as .rpm, .deb, .flatpak and .tar.gz
# - Windows (64-bit and 32-bit) as .zip
#
# Artifact outputs will be in the dist/mw/ folder.
##
FROM debian:latest AS build64
ENV GOPATH /go
ENV GOPROXY direct
ENV PATH /opt/go/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/go/bin
# Install all dependencies.
RUN apt update && apt -y install git zip tar libsdl2-dev libsdl2-ttf-dev \
libsdl2-mixer-dev gcc-mingw-w64-x86-64 gcc make wget \
flatpak-builder ruby-dev gcc rpm libffi-dev \
ruby-dev ruby-rubygems rpm libffi-dev rsync file
RUN gem install fpm; exit 0
# Download and install modern Go.
WORKDIR /root
RUN wget https://go.dev/dl/go1.21.4.linux-amd64.tar.gz -O go.tgz && \
tar -xzf go.tgz && \
cp -r go /opt/go
# Add some cacheable directories to speed up Dockerfile trial-and-error.
ADD deps/vendor /SketchyMaze/deps/vendor
# MinGW setup for Windows executable cross-compile.
WORKDIR /SketchyMaze/deps/vendor/mingw-libs
RUN for i in *.tar.gz; do tar -xzvf $i; done
RUN cp -r SDL2-2.0.9/x86_64-w64-mingw32 /usr && \
cp -r SDL2_mixer-2.0.4/x86_64-w64-mingw32 /usr && \
cp -r SDL2_ttf-2.0.15/x86_64-w64-mingw32 /usr
RUN mkdir -p /usr/lib/golang/pkg/windows_amd64
WORKDIR /SketchyMaze
RUN mkdir -p bin && cp deps/vendor/DLL/*.dll bin/
# Add the current working directory (breaks the docker cache every time).
ADD . /SketchyMaze
# Fetch the guidebook.
# RUN sh -c '[[ ! -d ./guidebook ]] && wget -O - https://download.sketchymaze.com/guidebook.tar.gz | tar -xzvf -'
# Use go-winres on the Windows exe (embed application icons)
RUN go install github.com/tc-hib/go-winres@latest && go-winres make
# Revert any local change to go.mod (replace lines)
RUN git checkout -- go.mod
# Install Go dependencies and do the thing:
# - builds the program for Linux
# - builds for Windows via MinGW
# - runs `make dist/` creating an uber build for both OS's
# - runs release.sh to carve out the Linux and Windows versions and
# zip them all up nicely.
RUN make setup && make from-docker64
# Collect the build artifacts.
RUN mkdir -p artifacts && cp -rv dist/release ./artifacts/
###
# 32-bit Dockerfile version of the above
###
FROM i386/debian:latest AS build32
ENV GOPATH /go
ENV GOPROXY direct
ENV PATH /opt/go/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/go/bin
# Dependencies, note the w64-i686 difference to the above
RUN apt update && apt -y install git zip tar libsdl2-dev libsdl2-ttf-dev \
libsdl2-mixer-dev gcc-mingw-w64-i686 gcc make wget \
flatpak-builder ruby-dev gcc rpm libffi-dev \
ruby-dev ruby-rubygems rpm libffi-dev rsync file
RUN gem install fpm; exit 0
# Download and install modern Go.
WORKDIR /root
RUN wget https://go.dev/dl/go1.19.3.linux-386.tar.gz -O go.tgz && \
tar -xzf go.tgz && \
cp -r go /opt/go
COPY --from=build64 /SketchyMaze /SketchyMaze
# MinGW setup for Windows executable cross-compile.
WORKDIR /SketchyMaze/deps/vendor/mingw-libs
RUN for i in *.tar.gz; do tar -xzvf $i; done
RUN cp -r SDL2-2.0.9/i686-w64-mingw32 /usr && \
cp -r SDL2_mixer-2.0.4/i686-w64-mingw32 /usr && \
cp -r SDL2_ttf-2.0.15/i686-w64-mingw32 /usr
RUN mkdir -p /usr/lib/golang/pkg/windows_386
WORKDIR /SketchyMaze
RUN mkdir -p bin && cp deps/vendor/DLL-32bit/*.dll bin/
# Do the thing.
RUN make setup && make from-docker32
# Collect the build artifacts.
RUN mkdir -p artifacts && cp -rv dist/release ./artifacts/
###
# Back to (64bit) base for the final CMD to copy artifacts out.
###
FROM debian:latest
COPY --from=build32 /SketchyMaze /SketchyMaze
CMD ["cp", "-r", "-v", \
"/SketchyMaze/artifacts/release/", \
"/mnt/export/"]

636
LICENSE.md Normal file
View File

@ -0,0 +1,636 @@
# GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 [Free Software Foundation, Inc.](http://fsf.org/)
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
## Preamble
The GNU General Public License is a free, copyleft license for software and
other kinds of works.
The licenses for most software and other practical works are designed to take
away your freedom to share and change the works. By contrast, the GNU General
Public License is intended to guarantee your freedom to share and change all
versions of a program--to make sure it remains free software for all its users.
We, the Free Software Foundation, use the GNU General Public License for most
of our software; it applies also to any other work released this way by its
authors. You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom to
distribute copies of free software (and charge for them if you wish), that you
receive source code or can get it if you want it, that you can change the
software or use pieces of it in new free programs, and that you know you can do
these things.
To protect your rights, we need to prevent others from denying you these rights
or asking you to surrender the rights. Therefore, you have certain
responsibilities if you distribute copies of the software, or if you modify it:
responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for
a fee, you must pass on to the recipients the same freedoms that you received.
You must make sure that they, too, receive or can get the source code. And you
must show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps:
1. assert copyright on the software, and
2. offer you this License giving you legal permission to copy, distribute
and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that
there is no warranty for this free software. For both users' and authors' sake,
the GPL requires that modified versions be marked as changed, so that their
problems will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified
versions of the software inside them, although the manufacturer can do so. This
is fundamentally incompatible with the aim of protecting users' freedom to
change the software. The systematic pattern of such abuse occurs in the area of
products for individuals to use, which is precisely where it is most
unacceptable. Therefore, we have designed this version of the GPL to prohibit
the practice for those products. If such problems arise substantially in other
domains, we stand ready to extend this provision to those domains in future
versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States
should not allow patents to restrict development and use of software on
general-purpose computers, but in those that do, we wish to avoid the special
danger that patents applied to a free program could make it effectively
proprietary. To prevent this, the GPL assures that patents cannot be used to
render the program non-free.
The precise terms and conditions for copying, distribution and modification
follow.
## TERMS AND CONDITIONS
### 0. Definitions.
*This License* refers to version 3 of the GNU General Public License.
*Copyright* also means copyright-like laws that apply to other kinds of works,
such as semiconductor masks.
*The Program* refers to any copyrightable work licensed under this License.
Each licensee is addressed as *you*. *Licensees* and *recipients* may be
individuals or organizations.
To *modify* a work means to copy from or adapt all or part of the work in a
fashion requiring copyright permission, other than the making of an exact copy.
The resulting work is called a *modified version* of the earlier work or a work
*based on* the earlier work.
A *covered work* means either the unmodified Program or a work based on the
Program.
To *propagate* a work means to do anything with it that, without permission,
would make you directly or secondarily liable for infringement under applicable
copyright law, except executing it on a computer or modifying a private copy.
Propagation includes copying, distribution (with or without modification),
making available to the public, and in some countries other activities as well.
To *convey* a work means any kind of propagation that enables other parties to
make or receive copies. Mere interaction with a user through a computer
network, with no transfer of a copy, is not conveying.
An interactive user interface displays *Appropriate Legal Notices* to the
extent that it includes a convenient and prominently visible feature that
1. displays an appropriate copyright notice, and
2. tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the work
under this License, and how to view a copy of this License.
If the interface presents a list of user commands or options, such as a menu, a
prominent item in the list meets this criterion.
### 1. Source Code.
The *source code* for a work means the preferred form of the work for making
modifications to it. *Object code* means any non-source form of a work.
A *Standard Interface* means an interface that either is an official standard
defined by a recognized standards body, or, in the case of interfaces specified
for a particular programming language, one that is widely used among developers
working in that language.
The *System Libraries* of an executable work include anything, other than the
work as a whole, that (a) is included in the normal form of packaging a Major
Component, but which is not part of that Major Component, and (b) serves only
to enable use of the work with that Major Component, or to implement a Standard
Interface for which an implementation is available to the public in source code
form. A *Major Component*, in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system (if any) on
which the executable work runs, or a compiler used to produce the work, or an
object code interpreter used to run it.
The *Corresponding Source* for a work in object code form means all the source
code needed to generate, install, and (for an executable work) run the object
code and to modify the work, including scripts to control those activities.
However, it does not include the work's System Libraries, or general-purpose
tools or generally available free programs which are used unmodified in
performing those activities but which are not part of the work. For example,
Corresponding Source includes interface definition files associated with source
files for the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require, such as
by intimate data communication or control flow between those subprograms and
other parts of the work.
The Corresponding Source need not include anything that users can regenerate
automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
### 2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on
the Program, and are irrevocable provided the stated conditions are met. This
License explicitly affirms your unlimited permission to run the unmodified
Program. The output from running a covered work is covered by this License only
if the output, given its content, constitutes a covered work. This License
acknowledges your rights of fair use or other equivalent, as provided by
copyright law.
You may make, run and propagate covered works that you do not convey, without
conditions so long as your license otherwise remains in force. You may convey
covered works to others for the sole purpose of having them make modifications
exclusively for you, or provide you with facilities for running those works,
provided that you comply with the terms of this License in conveying all
material for which you do not control copyright. Those thus making or running
the covered works for you must do so exclusively on your behalf, under your
direction and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes it
unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure
under any applicable law fulfilling obligations under article 11 of the WIPO
copyright treaty adopted on 20 December 1996, or similar laws prohibiting or
restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention is
effected by exercising rights under this License with respect to the covered
work, and you disclaim any intention to limit operation or modification of the
work as a means of enforcing, against the work's users, your or third parties'
legal rights to forbid circumvention of technological measures.
### 4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it,
in any medium, provided that you conspicuously and appropriately publish on
each copy an appropriate copyright notice; keep intact all notices stating that
this License and any non-permissive terms added in accord with section 7 apply
to the code; keep intact all notices of the absence of any warranty; and give
all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may
offer support or warranty protection for a fee.
### 5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it
from the Program, in the form of source code under the terms of section 4,
provided that you also meet all of these conditions:
- a) The work must carry prominent notices stating that you modified it, and
giving a relevant date.
- b) The work must carry prominent notices stating that it is released under
this License and any conditions added under section 7. This requirement
modifies the requirement in section 4 to *keep intact all notices*.
- c) You must license the entire work, as a whole, under this License to
anyone who comes into possession of a copy. This License will therefore
apply, along with any applicable section 7 additional terms, to the whole
of the work, and all its parts, regardless of how they are packaged. This
License gives no permission to license the work in any other way, but it
does not invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your work need
not make them do so.
A compilation of a covered work with other separate and independent works,
which are not by their nature extensions of the covered work, and which are not
combined with it such as to form a larger program, in or on a volume of a
storage or distribution medium, is called an *aggregate* if the compilation and
its resulting copyright are not used to limit the access or legal rights of the
compilation's users beyond what the individual works permit. Inclusion of a
covered work in an aggregate does not cause this License to apply to the other
parts of the aggregate.
### 6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4
and 5, provided that you also convey the machine-readable Corresponding Source
under the terms of this License, in one of these ways:
- a) Convey the object code in, or embodied in, a physical product (including
a physical distribution medium), accompanied by the Corresponding Source
fixed on a durable physical medium customarily used for software
interchange.
- b) Convey the object code in, or embodied in, a physical product (including
a physical distribution medium), accompanied by a written offer, valid for
at least three years and valid for as long as you offer spare parts or
customer support for that product model, to give anyone who possesses the
object code either
1. a copy of the Corresponding Source for all the software in the product
that is covered by this License, on a durable physical medium
customarily used for software interchange, for a price no more than your
reasonable cost of physically performing this conveying of source, or
2. access to copy the Corresponding Source from a network server at no
charge.
- c) Convey individual copies of the object code with a copy of the written
offer to provide the Corresponding Source. This alternative is allowed only
occasionally and noncommercially, and only if you received the object code
with such an offer, in accord with subsection 6b.
- d) Convey the object code by offering access from a designated place
(gratis or for a charge), and offer equivalent access to the Corresponding
Source in the same way through the same place at no further charge. You
need not require recipients to copy the Corresponding Source along with the
object code. If the place to copy the object code is a network server, the
Corresponding Source may be on a different server operated by you or a
third party) that supports equivalent copying facilities, provided you
maintain clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the Corresponding
Source, you remain obligated to ensure that it is available for as long as
needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission, provided you
inform other peers where the object code and Corresponding Source of the
work are being offered to the general public at no charge under subsection
6d.
A separable portion of the object code, whose source code is excluded from the
Corresponding Source as a System Library, need not be included in conveying the
object code work.
A *User Product* is either
1. a *consumer product*, which means any tangible personal property which is
normally used for personal, family, or household purposes, or
2. anything designed or sold for incorporation into a dwelling.
In determining whether a product is a consumer product, doubtful cases shall be
resolved in favor of coverage. For a particular product received by a
particular user, *normally used* refers to a typical or common use of that
class of product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected to use,
the product. A product is a consumer product regardless of whether the product
has substantial commercial, industrial or non-consumer uses, unless such uses
represent the only significant mode of use of the product.
*Installation Information* for a User Product means any methods, procedures,
authorization keys, or other information required to install and execute
modified versions of a covered work in that User Product from a modified
version of its Corresponding Source. The information must suffice to ensure
that the continued functioning of the modified object code is in no case
prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as part of a
transaction in which the right of possession and use of the User Product is
transferred to the recipient in perpetuity or for a fixed term (regardless of
how the transaction is characterized), the Corresponding Source conveyed under
this section must be accompanied by the Installation Information. But this
requirement does not apply if neither you nor any third party retains the
ability to install modified object code on the User Product (for example, the
work has been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates for a
work that has been modified or installed by the recipient, or for the User
Product in which it has been modified or installed. Access to a network may be
denied when the modification itself materially and adversely affects the
operation of the network or violates the rules and protocols for communication
across the network.
Corresponding Source conveyed, and Installation Information provided, in accord
with this section must be in a format that is publicly documented (and with an
implementation available to the public in source code form), and must require
no special password or key for unpacking, reading or copying.
### 7. Additional Terms.
*Additional permissions* are terms that supplement the terms of this License by
making exceptions from one or more of its conditions. Additional permissions
that are applicable to the entire Program shall be treated as though they were
included in this License, to the extent that they are valid under applicable
law. If additional permissions apply only to part of the Program, that part may
be used separately under those permissions, but the entire Program remains
governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any
additional permissions from that copy, or from any part of it. (Additional
permissions may be written to require their own removal in certain cases when
you modify the work.) You may place additional permissions on material, added
by you to a covered work, for which you have or can give appropriate copyright
permission.
Notwithstanding any other provision of this License, for material you add to a
covered work, you may (if authorized by the copyright holders of that material)
supplement the terms of this License with terms:
- a) Disclaiming warranty or limiting liability differently from the terms of
sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or author
attributions in that material or in the Appropriate Legal Notices displayed
by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in reasonable
ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors or authors
of the material; or
- e) Declining to grant rights under trademark law for use of some trade
names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that material by
anyone who conveys the material (or modified versions of it) with
contractual assumptions of liability to the recipient, for any liability
that these contractual assumptions directly impose on those licensors and
authors.
All other non-permissive additional terms are considered *further restrictions*
within the meaning of section 10. If the Program as you received it, or any
part of it, contains a notice stating that it is governed by this License along
with a term that is a further restriction, you may remove that term. If a
license document contains a further restriction but permits relicensing or
conveying under this License, you may add to a covered work material governed
by the terms of that license document, provided that the further restriction
does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place,
in the relevant source files, a statement of the additional terms that apply to
those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a
separately written license, or stated as exceptions; the above requirements
apply either way.
### 8. Termination.
You may not propagate or modify a covered work except as expressly provided
under this License. Any attempt otherwise to propagate or modify it is void,
and will automatically terminate your rights under this License (including any
patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a
particular copyright holder is reinstated
- a) provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and
- b) permanently, if the copyright holder fails to notify you of the
violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated
permanently if the copyright holder notifies you of the violation by some
reasonable means, this is the first time you have received notice of violation
of this License (for any work) from that copyright holder, and you cure the
violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses
of parties who have received copies or rights from you under this License. If
your rights have been terminated and not permanently reinstated, you do not
qualify to receive new licenses for the same material under section 10.
### 9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy
of the Program. Ancillary propagation of a covered work occurring solely as a
consequence of using peer-to-peer transmission to receive a copy likewise does
not require acceptance. However, nothing other than this License grants you
permission to propagate or modify any covered work. These actions infringe
copyright if you do not accept this License. Therefore, by modifying or
propagating a covered work, you indicate your acceptance of this License to do
so.
### 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a
license from the original licensors, to run, modify and propagate that work,
subject to this License. You are not responsible for enforcing compliance by
third parties with this License.
An *entity transaction* is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered work
results from an entity transaction, each party to that transaction who receives
a copy of the work also receives whatever licenses to the work the party's
predecessor in interest had or could give under the previous paragraph, plus a
right to possession of the Corresponding Source of the work from the
predecessor in interest, if the predecessor has it or can get it with
reasonable efforts.
You may not impose any further restrictions on the exercise of the rights
granted or affirmed under this License. For example, you may not impose a
license fee, royalty, or other charge for exercise of rights granted under this
License, and you may not initiate litigation (including a cross-claim or
counterclaim in a lawsuit) alleging that any patent claim is infringed by
making, using, selling, offering for sale, or importing the Program or any
portion of it.
### 11. Patents.
A *contributor* is a copyright holder who authorizes use under this License of
the Program or a work on which the Program is based. The work thus licensed is
called the contributor's *contributor version*.
A contributor's *essential patent claims* are all patent claims owned or
controlled by the contributor, whether already acquired or hereafter acquired,
that would be infringed by some manner, permitted by this License, of making,
using, or selling its contributor version, but do not include claims that would
be infringed only as a consequence of further modification of the contributor
version. For purposes of this definition, *control* includes the right to grant
patent sublicenses in a manner consistent with the requirements of this
License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent
license under the contributor's essential patent claims, to make, use, sell,
offer for sale, import and otherwise run, modify and propagate the contents of
its contributor version.
In the following three paragraphs, a *patent license* is any express agreement
or commitment, however denominated, not to enforce a patent (such as an express
permission to practice a patent or covenant not to sue for patent
infringement). To *grant* such a patent license to a party means to make such
an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the
Corresponding Source of the work is not available for anyone to copy, free of
charge and under the terms of this License, through a publicly available
network server or other readily accessible means, then you must either
1. cause the Corresponding Source to be so available, or
2. arrange to deprive yourself of the benefit of the patent license for this
particular work, or
3. arrange, in a manner consistent with the requirements of this License, to
extend the patent license to downstream recipients.
*Knowingly relying* means you have actual knowledge that, but for the patent
license, your conveying the covered work in a country, or your recipient's use
of the covered work in a country, would infringe one or more identifiable
patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you
convey, or propagate by procuring conveyance of, a covered work, and grant a
patent license to some of the parties receiving the covered work authorizing
them to use, propagate, modify or convey a specific copy of the covered work,
then the patent license you grant is automatically extended to all recipients
of the covered work and works based on it.
A patent license is *discriminatory* if it does not include within the scope of
its coverage, prohibits the exercise of, or is conditioned on the non-exercise
of one or more of the rights that are specifically granted under this License.
You may not convey a covered work if you are a party to an arrangement with a
third party that is in the business of distributing software, under which you
make payment to the third party based on the extent of your activity of
conveying the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory patent
license
- a) in connection with copies of the covered work conveyed by you (or copies
made from those copies), or
- b) primarily for and in connection with specific products or compilations
that contain the covered work, unless you entered into that arrangement, or
that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied
license or other defenses to infringement that may otherwise be available to
you under applicable patent law.
### 12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not excuse
you from the conditions of this License. If you cannot convey a covered work so
as to satisfy simultaneously your obligations under this License and any other
pertinent obligations, then as a consequence you may not convey it at all. For
example, if you agree to terms that obligate you to collect a royalty for
further conveying from those to whom you convey the Program, the only way you
could satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
### 13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have permission to
link or combine any covered work with a work licensed under version 3 of the
GNU Affero General Public License into a single combined work, and to convey
the resulting work. The terms of this License will continue to apply to the
part which is the covered work, but the special requirements of the GNU Affero
General Public License, section 13, concerning interaction through a network
will apply to the combination as such.
### 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU
General Public License from time to time. Such new versions will be similar in
spirit to the present version, but may differ in detail to address new problems
or concerns.
Each version is given a distinguishing version number. If the Program specifies
that a certain numbered version of the GNU General Public License *or any later
version* applies to it, you have the option of following the terms and
conditions either of that numbered version or of any later version published by
the Free Software Foundation. If the Program does not specify a version number
of the GNU General Public License, you may choose any version ever published by
the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the
GNU General Public License can be used, that proxy's public statement of
acceptance of a version permanently authorizes you to choose that version for
the Program.
Later license versions may give you additional or different permissions.
However, no additional obligations are imposed on any author or copyright
holder as a result of your choosing to follow a later version.
### 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE
LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER
PARTIES PROVIDE THE PROGRAM *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
### 16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY
HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot
be given local legal effect according to their terms, reviewing courts shall
apply local law that most closely approximates an absolute waiver of all civil
liability in connection with the Program, unless a warranty or assumption of
liability accompanies a copy of the Program in return for a fee.
## END OF TERMS AND CONDITIONS ###
### How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible
use to the public, the best way to achieve this is to make it free software
which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach
them to the start of each source file to most effectively state the exclusion
of warranty; and each file should have at least the *copyright* line and a
pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like
this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w` and `show c` should show the appropriate
parts of the General Public License. Of course, your program's commands might
be different; for a GUI interface, you would use an *about box*.
You should also get your employer (if you work as a programmer) or school, if
any, to sign a *copyright disclaimer* for the program, if necessary. For more
information on this, and how to apply and follow the GNU GPL, see
[http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
The GNU General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may consider
it more useful to permit linking proprietary applications with the library. If
this is what you want to do, use the GNU Lesser General Public License instead
of this License. But first, please read
[http://www.gnu.org/philosophy/why-not-lgpl.html](http://www.gnu.org/philosophy/why-not-lgpl.html).

113
Makefile
View File

@ -9,16 +9,21 @@ CURDIR=$(shell curdir)
LDFLAGS := -ldflags "-X main.Build=$(BUILD) -X main.BuildDate=$(BUILD_DATE)"
LDFLAGS_W := -ldflags "-X main.Build=$(BUILD) -X main.BuildDate=$(BUILD_DATE) -H windowsgui"
# Doodle++ build tag for official builds of the game.
BUILD_TAGS := -tags=""
ifneq ("$(wildcard ./deps/dpp)", "")
BUILD_TAGS = -tags="dpp"
endif
# `make setup` to set up a new environment, pull dependencies, etc.
.PHONY: setup
setup: clean
go get ./...
# `make build` to build the binary.
.PHONY: build
build:
go build $(LDFLAGS) -i -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) -i -o bin/doodad cmd/doodad/main.go
go build $(LDFLAGS) $(BUILD_TAGS) -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) -tags=doodad -o bin/doodad cmd/doodad/main.go
# `make buildall` to run all build steps including doodads.
.PHONY: buildall
@ -28,15 +33,8 @@ buildall: doodads build
.PHONY: build-free
build-free:
gofmt -w .
go build $(LDFLAGS) -tags="shareware" -i -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) -tags="shareware" -i -o bin/doodad cmd/doodad/main.go
# `make build-debug` to build the binary in developer mode.
.PHONY: build-debug
build-debug:
gofmt -w .
go build $(LDFLAGS) -tags="developer" -i -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) -tags="developer" -i -o bin/doodad cmd/doodad/main.go
go build $(LDFLAGS) -o bin/sketchymaze cmd/doodle/main.go
go build $(LDFLAGS) -tags=doodad -o bin/doodad cmd/doodad/main.go
# `make bindata` generates the embedded binary assets package.
.PHONY: bindata
@ -62,32 +60,42 @@ wasm-serve: wasm
# `make install` to install the Go binaries to your GOPATH.
.PHONY: install
install:
go install git.kirsle.net/apps/doodle/cmd/...
go install git.kirsle.net/SketchyMaze/doodle/cmd/...
# `make doodads` to build the doodads from the dev-assets folder.
# `make doodads` to build the doodads from the deps/doodads folder.
.PHONY: doodads
doodads:
cd dev-assets/doodads && ./build.sh
cd deps/doodads && ./build.sh > /dev/null
# `make mingw` to cross-compile a Windows binary with mingw.
.PHONY: mingw
mingw: doodads
mingw:
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS_W) -i -o bin/sketchymaze.exe cmd/doodle/main.go
go build $(LDFLAGS_W) $(BUILD_TAGS) -o bin/sketchymaze.exe cmd/doodle/main.go
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS) -i -o bin/doodad.exe cmd/doodad/main.go
go build $(LDFLAGS) -tags=doodad -o bin/doodad.exe cmd/doodad/main.go
# `make mingw32` to cross-compile a Windows binary with mingw (32-bit).
.PHONY: mingw32
mingw32:
env CGO_ENABLED="1" CC="/usr/bin/i686-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS_W) $(BUILD_TAGS) -o bin/sketchymaze.exe cmd/doodle/main.go
env CGO_ENABLED="1" CC="/usr/bin/i686-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS) -tags=doodad -o bin/doodad.exe cmd/doodad/main.go
# `make mingw-free` for Windows binary in free mode.
.PHONY: mingw-free
mingw-free: doodads
mingw-free:
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS_W) -tags="shareware" -i -o bin/sketchymaze.exe cmd/doodle/main.go
go build $(LDFLAGS_W) -o bin/sketchymaze.exe cmd/doodle/main.go
env CGO_ENABLED="1" CC="/usr/bin/x86_64-w64-mingw32-gcc" \
GOOS="windows" CGO_LDFLAGS="-lmingw32 -lSDL2" CGO_CFLAGS="-D_REENTRANT" \
go build $(LDFLAGS) -tags="shareware" -i -o bin/doodad.exe cmd/doodad/main.go
go build $(LDFLAGS) -tags=doodad -o bin/doodad.exe cmd/doodad/main.go
# `make release` runs the release.sh script, must be run
# after `make dist`
@ -95,21 +103,57 @@ mingw-free: doodads
release:
./scripts/release.sh
# `make release32` runs release with ARCH_LABEL=32bit to product
# artifacts targeting an i386 architecture (e.g. in rpm and deb packages
# metadata about the release)
.PHONY: release32
release32:
env ARCH_LABEL=32bit ./scripts/release.sh
# `make appimage` builds an AppImage, run it after `make dist`
.PHONY: appimage
appimage:
./appimage.sh
# `make mingw-release` runs a FULL end-to-end release of Linux and Windows
# binaries of the game, zipped and tagged and ready to go.
.PHONY: mingw-release
mingw-release: doodads build mingw __dist-common release
.PHONY: mingw32-release
mingw32-release: doodads build mingw32 __dist-common release32
# `make from-docker64` is an internal command run by the Dockerfile to build the
# game - assumes doodads and assets are in the right spot already.
.PHONY: from-docker64
.PHONY: from-docker32
from-docker64: build mingw __dist-common
ARCH=x86_64 make appimage
make release
from-docker32: build mingw32 __dist-common
ARCH=i686 make appimage
make release32
# `make osx` to cross-compile a Mac OS binary with osxcross.
# .PHONY: osx
# osx: doodads
# CGO_ENABLED=1 CC=[path-to-osxcross]/target/bin/[arch]-apple-darwin[version]-clang GOOS=darwin GOARCH=[arch] go build -tags static -ldflags "-s -w" -a
# `make run` to run it in debug mode.
# `make run` to run it from source.
.PHONY: run
run:
go run cmd/doodle/main.go -debug
go run ${BUILD_TAGS} cmd/doodle/main.go
# `make run-free` to run it from source with no build tags (foss version).
.PHONY: run-free
run-free:
go run cmd/doodle/main.go
# `make debug` to run it in -debug mode.
.PHONY: debug
debug:
go run $(BUILD_TAGS) cmd/doodle/main.go -debug
# `make guitest` to run it in guitest mode.
.PHONY: guitest
@ -125,6 +169,12 @@ test:
.PHONY: dist
dist: doodads build __dist-common
# `make docker` runs the Dockerfile to do a full release for 64-bit and 32-bit Linux
# and Windows apps.
.PHONY: docker
docker:
./scripts/docker-build.sh
# `make dist-free` builds and tars up a release in shareware mode.
.PHONY: dist-free
dist-free: doodads build-free __dist-common
@ -141,22 +191,7 @@ __dist-common:
cd dist && tar -czvf sketchymaze-$(VERSION).tar.gz sketchymaze-$(VERSION)
cd dist && zip -r sketchymaze-$(VERSION).zip sketchymaze-$(VERSION)
# `make docker` to run the Docker builds
.PHONY: docker docker.ubuntu docker.debian docker.fedora __docker.dist
docker.ubuntu:
mkdir -p docker/ubuntu
./docker/dist-ubuntu.sh
docker.debian:
mkdir -p docker/debian
./docker/dist-debian.sh
docker.fedora:
mkdir -p docker/fedora
./docker/dist-fedora.sh
docker: docker.ubuntu docker.debian docker.fedora
__docker.dist: dist
cp dist/*.tar.gz dist/*.zip /mnt/export/
# `make clean` cleans everything up.
.PHONY: clean
clean:
rm -rf bin dist docker/ubuntu docker/debian docker/fedora
rm -rf bin dist docker-artifacts

View File

@ -47,28 +47,24 @@ Except as contained in this notice, the name of Tavmjong Bah shall not be used i
## Go Modules
### github.com/robertkrimen/otto
### github.com/dop251/goja
```
Copyright (c) 2016 Dmitry Panov
Copyright (c) 2012 Robert Krimen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```
### github.com/satori/go.uuid
@ -151,33 +147,3 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
### github.com/vmihailenco/msgpack
```
Copyright (c) 2013 The github.com/vmihailenco/msgpack Authors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```

110
README.md
View File

@ -49,6 +49,17 @@ game:
* If you receive a map with custom doodads, you can **install** the doodads
into your copy of the game and use them in your own maps.
# Developer Documentation
In case you are reading this from the game's open source repository, take a
look in the `docs/` folder or the `*.md` files in the root of the repository.
Some to start with:
* [Building](Building.md) the game (tl;dr. run bootstrap.py)
* [Tour of the Code](docs/Tour%20of%20the%20Code.md)
* [Evolution of File Formats](docs/Evolution%20of%20File%20Formats.md)
# Keybindings
Global Keybindings:
@ -97,51 +108,42 @@ Ctrl-Y
Redo
```
# Built-In Doodads
# Gamepad Controls
A brief introduction to the built-in doodads available so far:
The game supports Xbox and Nintendo style game controllers. The button
bindings are not yet customizable, except to choose between the
"X Style" or "N Style" for A/B and X/Y button mappings.
- **Characters**
- Blue Azulian: this is used as the player character for now. If
dragged into a level, it doesn't do anything but is affected
by gravity.
- Red Azulian: an example mobile mob for now. It walks back and
forth, changing directions only when it comes across an
obstacle and can't proceed any further.
- **Doors and Keys**
- Colored Doors: these act like solid barriers until the player or
another doodad collects the matching colored key.
- Colored Keys: these are collectable items that allow the player or
another doodad to open the door of matching color. Note that
inventories are doodad-specific, so other mobs besides the
player can steal a key before the player gets it! (For now)
- Electric Door: this mechanical door stays closed and only
opens when it receives a power signal from a linked button.
- Trapdoor: this door allows one-way access, but once it's closed
behind you, you can't pass through it from that side!
- **Buttons**
- Button: while pressed by a player or other doodad, the button
emits a power signal to any doodad it is linked to. Link a button
to an electric door, and the door will open while the button is
pressed and close when the button is released.
- Sticky Button: this button will stay pressed once activated and
will not release unless it receives a power signal from another
linked doodad. For example, one Button that links to a Sticky
Button will release the sticky button if pressed.
- **Switches**
- Switch: when touched by the player or other doodad, the switch will
toggle its state from "OFF" to "ON" or vice versa. Link it to an
Electric Door to open/close the door. Link switches _to each other_ as
well as to a door, and all switches will stay in sync with their ON/OFF
state when any switch is pressed.
- **Crumbly Floor**
- This rocky floor will break and fall away after being stepped on.
- **Two State Blocks**
- Blue and orange blocks that will toggle between solid and pass-thru
whenever the corresponding ON/OFF block is hit.
- **Start and Exit Flags**
- The "Go" flag lets you pick a spawn point for the player character.
- The "Exit" flag marks the level goal.
Gamepad controls very depending on two modes the game can be in:
## Mouse Mode
The Gamepad emulates a mouse cursor in this mode.
* The left analog stick moves a cursor around the screen.
* The right analog stick scrolls the level (title screen and editor)
* A or X button simulates a Left-click
* B or Y button simulates a Right-click
* L1 (left shoulder) emulates a Middle-click
* L2 (left trigger) closes the top-most window in the editor mode
(like the Backspace key).
## Gameplay Mode
When playing a level, the controls are as follows:
* The left analog stick and the D-Pad will move the player character.
* A or X button to "Use" objects such as Warp Doors.
* B or Y button to "Jump"
* R1 (right shoulder) toggles between Mouse Mode and Gameplay Mode.
You can use the R1 button to access Mouse Mode to interact with the
menus or click on the "Edit Level" button.
Note: characters with antigravity (such as the Bird) can move in all
four directions but characters with gravity only move left and right
and have the dedicated "Jump" button. This differs from regular
keyboard controls where the Up arrow is to Jump.
# Developer Console
@ -295,4 +297,24 @@ Supported variables include:
# Author
Copyright (C) 2021 Noah Petherbridge. All rights reserved.
The doodle engine for _Sketchy Maze_ is released as open source software under
the terms of the GNU General Public License. The assets to the game, including
its default doodads and levels, are licensed separately from the doodle engine.
Any third party fork of the doodle engine MUST NOT include any official artwork
from Sketchy Maze.
Doodle Engine for Sketchy Maze
Copyright (C) 2022 Noah Petherbridge
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

58
appimage.sh Executable file
View File

@ -0,0 +1,58 @@
#!/bin/bash
# Script to build an AppImage.
# Run it like `ARCH=x86_64 make appimage`
# It will fetch your appimagetool-x86_64.AppImage program to build the appimage.
if [[ ! -d "./dist/sketchymaze-latest" ]]; then
echo "error: run make dist before make appimage"
exit 1
fi
if [[ "$ARCH" == "" ]]; then
echo "You should set ARCH=x86_64 (or your platform for AppImage output)"
exit 1
fi
APPDIR="./dist/AppDir"
LAUNCHER="./scripts/appimage-launcher.sh"
DESKTOP="./scripts/appimage.desktop"
ICON_IMG="./etc/icons/256.png"
ICON_VECTOR="./etc/icons/orange-128.svg"
APP_RUN="$APPDIR/AppRun"
DIR_ICON="$APPDIR/sketchymaze.svg"
APPIMAGETOOL="appimagetool-$ARCH.AppImage"
if [[ ! -f "./$APPIMAGETOOL" ]]; then
echo "Downloading appimagetool"
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$ARCH.AppImage"
chmod a+x $APPIMAGETOOL
fi
# Clean start
if [[ -d "$APPDIR" ]]; then
echo "Note: Removing previous $APPDIR"
rm -rf "$APPDIR"
fi
echo "Creating $APPDIR"
mkdir $APPDIR
# Entrypoint script (AppRun)
cp "./scripts/appimage-launcher.sh" "$APPDIR/AppRun"
chmod +x "$APPDIR/AppRun"
# .DirIcon PNG for thumbnailers
cp "./etc/icons/256.png" "$APPDIR/.DirIcon"
cp "./etc/icons/orange-128.svg" "$APPDIR/sketchymaze.svg"
# .desktop launcher
cp "./scripts/appimage.desktop" "$APPDIR/sketchymaze.desktop"
# Everything else
rsync -av "./dist/sketchymaze-latest/" "$APPDIR/"
echo "Making AppImage..."
cd $APPDIR
../../$APPIMAGETOOL $(pwd)

View File

@ -1,3 +1,6 @@
//go:build !doodad
// +build !doodad
// Package assets gets us off go-bindata by using Go 1.16 embed support.
//
// For Go 1.16 embed, this source file had to live inside the assets/ folder

32
assets/assets_omitted.go Normal file
View File

@ -0,0 +1,32 @@
//go:build doodad
// +build doodad
// Dummy version of assets_embed.go that doesn't embed any files.
// For the `doodad` tool.
package assets
import (
"embed"
"errors"
)
var Embedded embed.FS
var errNotEmbedded = errors.New("assets not embedded")
// AssetDir returns the list of embedded files at the directory name.
func AssetDir(name string) ([]string, error) {
return nil, errNotEmbedded
}
// Asset returns the byte data of an embedded asset.
func Asset(name string) ([]byte, error) {
return nil, errNotEmbedded
}
// AssetNames dumps the names of all embedded assets,
// with their legacy "assets/" prefix from go-bindata.
func AssetNames() []string {
return nil
}

BIN
assets/icons/1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
assets/icons/128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
assets/icons/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

BIN
assets/icons/256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
assets/icons/32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/icons/512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
assets/icons/64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
assets/icons/96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
assets/pattern/bubbles.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,185 @@
! version = 2.0
> begin
+ request
- {ok}
< begin
// Bot Variables
! var name = Aiden
! var age = 15
! var sex = male
! var location = Michigan
! var city = Detroit
! var eyes = blue
! var hair = light brown
! var hairlen = short
! var color = blue
! var band = Nickelback
! var book = Myst
! var author = Stephen King
! var job = robot
! var website = www.sketchymaze.com
// Substitutions
! sub &quot; = "
! sub &apos; = '
! sub &amp; = &
! sub &lt; = <
! sub &gt; = >
! sub + = plus
! sub - = minus
! sub / = divided
! sub * = times
! sub i'm = i am
! sub i'd = i would
! sub i've = i have
! sub i'll = i will
! sub don't = do not
! sub isn't = is not
! sub you'd = you would
! sub you're = you are
! sub you've = you have
! sub you'll = you will
! sub he'd = he would
! sub he's = he is
! sub he'll = he will
! sub she'd = she would
! sub she's = she is
! sub she'll = she will
! sub they'd = they would
! sub they're = they are
! sub they've = they have
! sub they'll = they will
! sub we'd = we would
! sub we're = we are
! sub we've = we have
! sub we'll = we will
! sub whats = what is
! sub what's = what is
! sub what're = what are
! sub what've = what have
! sub what'll = what will
! sub can't = can not
! sub whos = who is
! sub who's = who is
! sub who'd = who would
! sub who'll = who will
! sub don't = do not
! sub didn't = did not
! sub it's = it is
! sub could've = could have
! sub couldn't = could not
! sub should've = should have
! sub shouldn't = should not
! sub would've = would have
! sub wouldn't = would not
! sub when's = when is
! sub when're = when are
! sub when'd = when did
! sub y = why
! sub u = you
! sub ur = your
! sub r = are
! sub n = and
! sub im = i am
! sub wat = what
! sub wats = what is
! sub ohh = oh
! sub becuse = because
! sub becasue = because
! sub becuase = because
! sub practise = practice
! sub its a = it is a
! sub fav = favorite
! sub fave = favorite
! sub yesi = yes i
! sub yetit = yet it
! sub iam = i am
! sub welli = well i
! sub wellit = well it
! sub amfine = am fine
! sub aman = am an
! sub amon = am on
! sub amnot = am not
! sub realy = really
! sub iamusing = i am using
! sub amleaving = am leaving
! sub yuo = you
! sub youre = you are
! sub didnt = did not
! sub ain't = is not
! sub aint = is not
! sub wanna = want to
! sub brb = be right back
! sub bbl = be back later
! sub gtg = got to go
! sub g2g = got to go
! sub lyl = love you lots
! sub gf = girlfriend
! sub g/f = girlfriend
! sub bf = boyfriend
! sub b/f = boyfriend
! sub b/f/f = best friend forever
! sub :-) = smile
! sub :) = smile
! sub :d = grin
! sub :-d = grin
! sub :-p = tongue
! sub :p = tongue
! sub ;-) = wink
! sub ;) = wink
! sub :-( = sad
! sub :( = sad
! sub :'( = cry
! sub :-[ = shy
! sub :-\ = uncertain
! sub :-/ = uncertain
! sub :-s = uncertain
! sub 8-) = cool
! sub 8) = cool
! sub :-* = kissyface
! sub :-! = foot
! sub o:-) = angel
! sub >:o = angry
! sub :@ = angry
! sub 8o| = angry
! sub :$ = blush
! sub :-$ = blush
! sub :-[ = blush
! sub :[ = bat
! sub (a) = angel
! sub (h) = cool
! sub 8-| = nerdy
! sub |-) = tired
! sub +o( = ill
! sub *-) = uncertain
! sub ^o) = raised eyebrow
! sub (6) = devil
! sub (l) = love
! sub (u) = broken heart
! sub (k) = kissyface
! sub (f) = rose
! sub (w) = wilted rose
// Person substitutions
! person i am = you are
! person you are = I am
! person i'm = you're
! person you're = I'm
! person my = your
! person your = my
! person you = I
! person i = you
// Set arrays
! array malenoun = male guy boy dude boi man men gentleman gentlemen
! array femalenoun = female girl chick woman women lady babe
! array mennoun = males guys boys dudes bois men gentlemen
! array womennoun = females girls chicks women ladies babes
! array lol = lol lmao rofl rotfl haha hahaha
! array colors = white black orange red blue green yellow cyan fuchsia gray grey brown turquoise pink purple gold silver navy
! array height = tall long wide thick
! array measure = inch in centimeter cm millimeter mm meter m inches centimeters millimeters meters
! array yes = yes yeah yep yup ya yea
! array no = no nah nope nay

View File

@ -0,0 +1,69 @@
// Learn stuff about our users.
+ my name is *
- <set name=<formal>>Nice to meet you, <get name>.
- <set name=<formal>><get name>, nice to meet you.
+ my name is <bot name>
- <set name=<bot name>>What a coincidence! That's my name too!
- <set name=<bot name>>That's my name too!
+ call me *
- <set name=<formal>><get name>, I will call you that from now on.
+ i am * years old
- <set age=<star>>A lot of people are <get age>, you're not alone.
- <set age=<star>>Cool, I'm <bot age> myself.{weight=49}
+ i am a (@malenoun)
- <set sex=male>Alright, you're a <star>.
+ i am a (@femalenoun)
- <set sex=female>Alright, you're female.
+ i (am from|live in) *
- <set location={formal}<star2>{/formal}>I've spoken to people from <get location> before.
+ my favorite * is *
- <set fav<star1>=<star2>>Why is it your favorite?
+ i am single
- <set status=single><set spouse=nobody>I am too.
+ i have a girlfriend
- <set status=girlfriend>What's her name?
+ i have a boyfriend
- <set status=boyfriend>What's his name?
+ *
% what is her name
- <set spouse=<formal>>That's a pretty name.
+ *
% what is his name
- <set spouse=<formal>>That's a cool name.
+ my (girlfriend|boyfriend)* name is *
- <set spouse=<formal>>That's a nice name.
+ (what is my name|who am i|do you know my name|do you know who i am){weight=10}
- Your name is <get name>.
- You told me your name is <get name>.
- Aren't you <get name>?
+ (how old am i|do you know how old i am|do you know my age){weight=10}
- You are <get age> years old.
- You're <get age>.
+ am i a (@malenoun) or a (@femalenoun){weight=10}
- You're a <get sex>.
+ am i (@malenoun) or (@femalenoun){weight=10}
- You're a <get sex>.
+ what is my favorite *{weight=10}
- Your favorite <star> is <get fav<star>>
+ who is my (boyfriend|girlfriend|spouse){weight=10}
- <get spouse>

View File

@ -0,0 +1,294 @@
// A generic set of chatting responses. This set mimicks the classic Eliza bot.
+ [*] (sorry|apologize|apology) [*]
- Please don't apologize.
- Apologies are not necessary.
- I've told you that apologies are not required.
- It did not bother me. Please continue.
+ i remember *
- Do you often think of <star>?
- Does thinking of <star> bring anything else to mind?
- Why do you remember <star> just now?
- What in the present situation reminds you of <star>?
- What is the connection between me and <star>?
- What else does <star> remind you of?
+ do you remember *
- Did you think I would forget <star>?
- Why do you think I should recall <star> now?
- What about <star>?
- You mentioned <star>?
+ you remember *
- How could I forget <star>?
- What about <star> should I remember?
+ i forget *
- Can you think of why you might forget <star>?
- Why can't you remember <star>?
- How often do you think of <star>?
- Does it bother you to forget that?
- Could it be a mental block?
- Are you generally forgetful?
- Do you think you are suppressing <star>?
+ did you forget *
- Why do you ask?
- Are you sure you told me?
- Would it bother you if I forgot <star>?
- Why should I recall <star> just now?
- Tell me more about <star>.
+ [*] if *
- Do you think it's likely that <star>?
- Do you wish that <star>?
- What do you know about <star>?
- Really, if <star>?
- What would you do if <star>?
- But what are the chances that <star>?
- What does this speculation lead to?
+ [*] i dreamed *
- Really, <star>?
- Have you ever fantasized <star> while you were awake?
- Have you ever dreamed <star> before?
- What does that dream suggest to you?
- Do you dream often?
- What persons appear in your dreams?
- Do you believe that dreams have something to do with your problem?
+ [*] perhaps [*]
- You don't seem quite certain.
- Why the uncertain tone?
- Can't you be more positive?
- You aren't sure?
- Don't you know?
- How likely, would you estimate?
+ (hello|hi|hey|howdy|hola|hai|yo) [*]
- How do you do. Please state your problem.
- Hi. What seems to be your problem?
+ [*] computer [*]
- Do computers worry you?
- Why do you mention computers?
- What do you think machines have to do with your problem?
- Don't you think computers can help people?
- What about machines worries you?
- What do you think about machines?
+ am i *
- Do you believe you are <star>?
- Would you want to be <star>?
- Do you wish I would tell you you are <star>?
- What would it mean if you were <star>?
+ are you *
- Are you interested in whether I am <star> or not?
- Would you prefer if I weren't <star>?
- Perhaps I am <star> in your fantasies.
- Do you sometimes think I am <star>?
- Would it matter to you?
- What if I were <star>?
+ you are *
- What makes you think I am <star>?
- Does it please you to believe I am <star>?
- Do you sometimes wish you were <star>?
- Perhaps you would like to be <star>.
+ * are *
- Did you think they might not be <star2>?
- Would you like it if they were not <star2>?
- What if they were not <star2>?
- Are they always <star2>?
- Are you positive they are <star2>?
+ [*] your *
- Why are you concerned over my <star>?
- What about your own <star>?
- Are you worried about someone else's <star>?
- Really, my <star>?
- What makes you think of my <star>?
- Do you want my <star>?
+ was i *
- What if you were <star>?
- Do you think you were <star>?
- Were you <star>?
- What would it mean if you were <star>?
- What does '<star>' suggest to you?
+ i was *
- Were you really?
- Why do you tell me you were <star> now?
- Perhaps I already know you were <star>.
+ [*] was you *
- Would you like to believe I was <star>?
- What suggests that I was <star>?
- What do you think?
- Perhaps I was <star>.
- What if I had been <star>?
+ i (desire|want|need) *
- What would it mean to you if you got <star2>?
- Why do you want <star2>?
- Suppose you got <star2> soon.
- What if you never got <star2>?
- What would getting <star2> mean to you?
- What does wanting <star2> have to do with this discussion?
+ i am (sad|unhappy|mad|angry|pissed|depressed) [*]
- I am sorry to hear that you are <star>.
- Do you think coming here will help you not to be <star>?
- I'm sure it's not pleasant to be <star>.
- Can you explain what made you <star>?
+ i am (happy|excited|glad) [*]
- How have I helped you to be <star>?
- Has your treatment made you <star>?
- What makes you <star> just now?
- Can you explain why you are <star>?
+ i (believe|think) *
- Do you really think so?
- But you are not sure you <star2>.
- Do you really doubt you <star2>
+ i am *
- Is it because you are <star> that you came to me?
- How long have you been <star>?
- Do you believe it is normal to be <star>?
- Do you enjoy being <star>?
- Do you know anyone else who is <star>?
+ i can not *
- How do you know that you can't <star>?
- Have you tried?
- Perhaps you could <star> now.
- Do you really want to be able to <star>?
- What if you could <star>?
+ i do not *
- Don't you really <star>?
- Why don't you <star>?
- Do you wish to be able to <star>?
- Does that trouble you?
+ i feel *
- Tell me more about such feelings.
- Do you often feel <star>?
- Do you enjoy feeling <star>?
- Of what does feeling <star> remind you?
+ i * you
- Perhaps in your fantasies we <star> each other.
- Do you wish to <star> me?
- You seem to need to <star> me.
- Do you <star> anyone else?
+ you * me
- Why do you think I <star> you?
- You like to think I <star> you -- don't you?
- What makes you think I <star> you?
- Really, I <star> you?
- Do you wish to believe I <star> you?
- Suppose I did <star> you -- what would that mean?
- Does someone else believe I <star> you?
+ [*] you *
- We were discussing you -- not me.
- Oh, I <star>?
- You're not really talking about me -- are you?
- What are your feelings now?
+ [*] (yes|yeah|yep|yup) [*]
- Please go on.
- Please tell me more about this.
- Why don't you tell me a little more about this.
- I see.
- I understand.
+ [*] (nope|nah) [*]
- Are you saying no just to be negative?
- Does this make you feel unhappy?
- Why not?
- Why 'no'?
+ no
@ nope
+ no one *
- Are you sure, no one <star>?
- Surely someone <star>.
- Can you think of anyone at all?
- Are you thinking of a very special person?
- Who, may I ask?
- You have a particular person in mind, don't you?
- Who do you think you are talking about?
+ [*] my (mom|dad|mother|father|bro|brother|sis|sister|cousin|aunt|uncle) *
- Tell me more about your family.
- Who else in your family <star2>?
- Your <star>?
- What else comes to mind when you think of your <star>?
+ can you *
- You believe I can <star> don't you?
- You want me to be able to <star>.
- Perhaps you would like to be able to <star> yourself.
+ can i *
- Whether or not you can <star> depends on you more than on me.
- Do you want to be able to <star>?
- Perhaps you don't want to <star>.
+ (what|who|when|where|how) [*]
- Why do you ask?
- Does that question interest you?
- What is it you really want to know?
- Are such questions much on your mind?
- What answer would please you most?
- What do you think?
- What comes to mind when you ask that?
- Have you asked such questions before?
- Have you asked anyone else?
+ [*] because [*]
- Is that the real reason?
- Don't any other reasons come to mind?
- Does that reason seem to explain anything else?
- What other reasons might there be?
+ why do not you *
- Do you believe I don't <star>?
- Perhaps I will <star> in good time.
- Should you <star> yourself?
- You want me to <star>?
+ why can not i *
- Do you think you should be able to <star>?
- Do you want to be able to <star>?
- Do you believe this will help you to <star>?
- Have you any idea why you can't <star>?
+ everyone *
- Really, everyone?
- Surely not everyone.
- Can you think of anyone in particular?
- Who, for example?
- Are you thinking of a very special person?
- Who, may I ask?
- Someone special perhaps?
- You have a particular reason in mind, don't you?
- Who do you think you're talking about?
+ [*] (fuck|fucker|shit|damn|shut up|bitch) [*]
- Does it make you feel strong to use that kind of language?
- Are you venting your feelings now?
- Are you angry?
- Does this topic make you feel angry?
- Is something making you feel angry?
- Does using that kind of language make you feel better?

View File

@ -0,0 +1,58 @@
// Tell the user stuff about ourself.
+ <bot name>
- Yes?
+ <bot name> *
- Yes? {@<star>}
+ asl
- <bot age>/<bot sex>/<bot location>
+ (what is your name|who are you|who is this)
- I am <bot name>.
- You can call me <bot name>.
+ how old are you
- I'm <bot age> years old.
- I'm <bot age>.
+ are you a (@malenoun) or a (@femalenoun)
- I'm a <bot sex>.
+ are you (@malenoun) or (@femalenoun)
- I'm a <bot sex>.
+ where (are you|are you from|do you live)
- I'm from <bot location>.
+ what (city|town) (are you from|do you live in)
- I'm in <bot city>.
+ what is your favorite color
- Definitely <bot color>.
+ what is your favorite band
- I like <bot band> the most.
+ what is your favorite book
- The best book I've read was <bot book>.
+ what is your occupation
- I'm a <bot job>.
+ where is your (website|web site|site)
- <bot website>
+ what color are your eyes
- I have <bot eyes> eyes.
- {sentence}<bot eyes>{/sentence}.
+ what do you look like
- I have <bot eyes> eyes and <bot hairlen> <bot hair> hair.
+ what do you do
- I'm a <bot job>.
+ who is your favorite author
- <bot author>.

View File

@ -0,0 +1,19 @@
// Triggers about Sketchy Maze in particular
+ what are you
- I'm <bot name>, a chatbot built into your game!
+ what can you do
- Type `help` for a list of commands.\n
^ I can also answer random questions that I understand.
// Misc open-ended triggers not caught in RiveScript standard brain
+ i *
- Do you always talk about yourself?
- That's interesting.
- That's cool.
+ why *
- To make you ask questions.
- Because I said so.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 687 B

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 717 B

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 709 B

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
assets/sprites/gear.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/sprites/gold.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 626 B

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 679 B

After

Width:  |  Height:  |  Size: 665 B

BIN
assets/sprites/padlock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

BIN
assets/sprites/pan-tool.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 B

After

Width:  |  Height:  |  Size: 689 B

BIN
assets/sprites/pencil.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
assets/sprites/pointer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 648 B

After

Width:  |  Height:  |  Size: 637 B

BIN
assets/sprites/silver.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@ -14,7 +14,7 @@ the repos easily. This script will also handle installing the SDL2 dependencies
on Fedora, Debian and macOS type systems.
"""
import sys
import argparse
import os
import os.path
import subprocess
@ -22,9 +22,10 @@ import pathlib
# Git repositories.
repos = {
"git@git.kirsle.net:apps/doodle-masters": "masters",
"git@git.kirsle.net:apps/doodle-vendor": "vendor",
"git@git.kirsle.net:apps/doodle-rtp": "rtp",
"git@git.kirsle.net:SketchyMaze/doodads": "doodads",
"git@git.kirsle.net:SketchyMaze/assets": "assets",
"git@git.kirsle.net:SketchyMaze/vendor": "vendor",
"git@git.kirsle.net:SketchyMaze/rtp": "rtp",
"git@git.kirsle.net:go/render": "render",
"git@git.kirsle.net:go/ui": "ui",
"git@git.kirsle.net:go/audio": "audio",
@ -36,10 +37,14 @@ repos_github = {
"git@github.com:kirsle/audio": "audio",
# TODO: the rest
}
repos_ssh = {
# SSH-only (private) repos.
"git@git.kirsle.net:SketchyMaze/dpp": "dpp",
}
# Software dependencies.
dep_fedora = ["make", "golang", "SDL2-devel", "SDL2_ttf-devel", "SDL2_mixer-devel"]
dep_debian = ["make", "golang", "libsdl2-dev", "libsdl2-ttf-dev", "libsdl2-mixer-dev"]
dep_fedora = ["make", "golang", "SDL2-devel", "SDL2_ttf-devel", "SDL2_mixer-devel", "zip", "rsync"]
dep_debian = ["make", "golang", "libsdl2-dev", "libsdl2-ttf-dev", "libsdl2-mixer-dev", "zip", "rsync"]
dep_macos = ["golang", "sdl2", "sdl2_ttf", "sdl2_mixer", "pkg-config"]
dep_arch = ["go", "sdl2", "sdl2_ttf", "sdl2_mixer"]
@ -48,18 +53,18 @@ dep_arch = ["go", "sdl2", "sdl2_ttf", "sdl2_mixer"]
ROOT = pathlib.Path().absolute()
def main():
def main(fast=False):
print(
"Project: Doodle Full Installer\n"
"Current working directory: {root}\n"
"Project: Doodle Full Installer\n\n"
"Current working directory: {root}\n\n"
"Ensure your SSH keys are set up on git.kirsle.net to easily clone repos.\n"
"Also check your $GOPATH is set and your $PATH will run binaries installed,\n"
"for e.g. GOPATH=$HOME/go and PATH includes $HOME/go/bin; otherwise the\n"
"'make doodads' command won't function later."
"'make doodads' command won't function later.\n"
.format(root=ROOT)
)
input("Press Enter to begin.")
install_deps()
install_deps(fast)
clone_repos()
patch_gomod()
copy_assets()
@ -67,22 +72,24 @@ def main():
build()
def install_deps():
def install_deps(fast):
"""Install system dependencies."""
fast = " -y" if fast else ""
if shell("which rpm") == 0 and shell("which dnf") == 0:
# Fedora-like.
if shell("rpm -q {}".format(' '.join(dep_fedora))) != 0:
must_shell("sudo dnf install {}".format(' '.join(dep_fedora)))
must_shell("sudo dnf install {}{}".format(' '.join(dep_fedora), fast))
elif shell("which brew") == 0:
# MacOS, as Catalina has an apt command now??
must_shell("brew install {}".format(' '.join(dep_macos)))
must_shell("brew install {} {}".format(' '.join(dep_macos), fast))
elif shell("which apt") == 0:
# Debian-like.
if shell("dpkg-query -l {}".format(' '.join(dep_debian))) != 0:
must_shell("sudo apt update && sudo apt install {}".format(' '.join(dep_debian)))
must_shell("sudo apt update && sudo apt install {}{}".format(' '.join(dep_debian), fast))
elif shell("which pacman") == 0:
# Arch-like.
must_shell("sudo pacman -S {}".format(' '.join(dep_arch)))
must_shell("sudo pacman -S{} {}".format(fast, ' '.join(dep_arch)))
else:
print("Warning: didn't detect your package manager to install SDL2 and other dependencies")
@ -111,6 +118,7 @@ def patch_gomod():
"\n\nreplace git.kirsle.net/go/render => {root}/deps/render\n"
"replace git.kirsle.net/go/ui => {root}/deps/ui\n"
"replace git.kirsle.net/go/audio => {root}/deps/audio\n"
"replace git.kirsle.net/SketchyMaze/dpp => {root}/deps/dpp\n"
.format(root=ROOT)
)
@ -119,15 +127,15 @@ def copy_assets():
"""Copy assets from other repos into doodle."""
if not os.path.isdir("assets/fonts"):
shell("cp -rv deps/vendor/fonts assets/fonts")
if not os.path.isdir("assets/levels"):
shell("cp -rv deps/masters/levels assets/levels")
if not os.path.isdir("assets/levelpacks"):
shell("cp -rv deps/assets/levelpacks/levelpacks assets/levelpacks")
if not os.path.isdir("rtp"):
shell("mkdir -p rtp && cp -rv deps/rtp/* rtp/")
def install_doodad():
"""Install the doodad CLI tool from the doodle repo."""
must_shell("go install git.kirsle.net/apps/doodle/cmd/doodad")
must_shell("go install git.kirsle.net/SketchyMaze/doodle/cmd/doodad")
def build():
@ -147,4 +155,25 @@ def must_shell(cmd):
if __name__ == "__main__":
parser = argparse.ArgumentParser("doodle bootstrap")
parser.add_argument("--fast", "-f",
action="store_true",
help="Run the script non-interactively (yes to your package manager, git clone over https)",
)
args = parser.parse_args()
if args.fast:
main(fast=args.fast)
quit()
if not input("Use ssh to git clone these repos? [yN] ").lower().startswith("y"):
keys = list(repos.keys())
for k in keys:
https = k.replace("git@git.kirsle.net:", "https://git.kirsle.net/")
repos[https] = repos[k]
del repos[k]
else:
# mix in SSH-only repos
repos.update(repos_ssh)
main()

View File

@ -1,21 +1,116 @@
# doodad.exe
The doodad tool is a command line interface for interacting with Levels and
Doodad files, collectively referred to as "Doodle drawings" or just "drawings"
for short.
The `doodad` tool is a command line interface for interacting with Levels and Doodad files, collectively referred to as "Doodle drawings" or just "drawings" for short. It provides many useful features for custom content creators that are not available in the game's user interface.
# Commands
- [doodad.exe](#doodadexe)
- [Quick Start](#quick-start)
- [Creating a custom doodad](#creating-a-custom-doodad)
- [Creating a custom Level Pack](#creating-a-custom-level-pack)
- [Usage](#usage)
- [Features in Depth](#features-in-depth)
- [$ `doodad convert`: to and from image files](#-doodad-convert-to-and-from-image-files)
- [$ `doodad show`: Get information about a level or doodad](#-doodad-show-get-information-about-a-level-or-doodad)
- [Editing Level or Doodad Properties](#editing-level-or-doodad-properties)
- [Where to Find It](#where-to-find-it)
## doodad convert
# Quick Start
Convert between standard image files (bitmap or PNG) and Doodle drawings
(levels or doodads).
## Creating a custom doodad
This command can be used to "export" a Doodle drawing as a PNG (when run against
a Level file, it may export a massive PNG image containing the entire level).
It may also "import" a new Doodle drawing from an image on disk.
This is an example how to fully create a custom doodad using PNG images for their sprites. The game's built-in doodads are all built in this way:
Example:
```bash
# Create the initial doodad from PNG images making up its layers.
% doodad convert --title "Bird (red)" --author "Myself" \
left-1.png left-2.png right-1.png right-2.png \
bird-red.doodad
# Attach my custom JavaScript to program the doodad.
% doodad install-script bird.js bird-red.doodad
# Note: `doodad show --script` can get the script back out.
# Set tags and options on my doodad
% doodad edit-doodad --tag "category=creatures" \
--tag "color=blue" \
--option "No A.I.=bool" \
bird-red.doodad
# See the Guidebook for more information!
```
## Creating a custom Level Pack
The doodad tool is currently the best way to create a custom Level Pack for the game. Level Packs will appear in the Story Mode selector, and you can install local levelpacks by putting them in that folder of your Profile Directory.
```bash
# First Quest
doodad levelpack create -t "First Quest" -d "The first story mode campaign." \
-a "$AUTHOR" --doodads none --free 1 \
"levelpacks/builtin-100-FirstQuest.levelpack" \
"levels/Castle.level" \
"levels/Boat.level" \
"levels/Jungle.level" \
"levels/Thief 1.level" \
"levels/Desert-1of2.level" \
"levels/Desert-2of2.level" \
"levels/Shapeshifter.level"
```
Some useful options you can set on your levelpack:
* Title: defaults to your first level's title.
* Description: for the Story Mode picker.
* Free levels: if you want progressive unlocking of levels, specify how many levels are unlocked to start with (at least 1). Otherwise all levels are unlocked.
* Doodads: by default any custom doodad will be bundled with the levelpack.
* Options for `--doodads` are `none`, `custom` (default), and `all`
* Use `none` if your levelpack uses _only_ built-in doodads.
# Usage
See `doodad --help` for documentation of the available commands and their options; a recent example of which is included here.
```
NAME:
doodad - command line interface for Doodle
USAGE:
doodad [global options] command [command options]
VERSION:
v0.14.1 (open source) build N/A. Built on 2024-05-24T19:23:33-07:00
COMMANDS:
convert convert between images and Doodle drawing files
edit-doodad update metadata for a Doodad file
edit-level update metadata for a Level file
install-script install the JavaScript source to a doodad
levelpack create and manage .levelpack archives
resave load and re-save a level or doodad file to migrate to newer file format versions
show show information about a level or doodad file
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--debug enable debug level logging (default: false)
--help, -h show help
--version, -v print the version
```
The `--help` or `-h` flag can be given to subcommands too, which often have their own commands and flags. For example, `doodad convert -h`
# Features in Depth
## $ `doodad convert`: to and from image files
This command can convert between image files (BMP or PNG) and Doodle drawings (levels and doodads) in both directions.
For custom doodad authors this means you can draw your sprites in a much more capable image editing tool, and create a doodad from PNG images. The game's built-in doodads are all created in this way.
This command can also be used to "export" a drawing as a PNG image: when run against a Level file, it may export a massive PNG image containing the entire level! Basically the "Giant Screenshot" function from the level editor.
It can also create a Level from a screenshot, generating the Palette from the distinct colors found (for you to assign properties to in the editor, for e.g. solid geometry).
Examples:
```bash
# Export a full screenshot of your level
@ -24,20 +119,149 @@ $ doodad convert mymap.level screenshot.png
# Create a new level based from a PNG image.
$ doodad convert scanned-drawing.png new-level.level
# Create a doodad from a series of PNG images as its layers.
$ doodad convert -t "My Button" up.png down.png my-button.doodad
# Create a new doodad based from a BMP image, and in this image the chroma
# color (transparent) is #FF00FF instead of white as default.
$ doodad convert --key '#FF00FF' button.png button.doodad
```
Supported image types:
Supported image types to convert to or from:
* PNG (8-bit or 24-bit, with transparent pixels or chroma key)
* PNG (8-bit or 24-bit, with transparent pixels or chroma)
* BMP (bitmap image with chroma key)
The chrome key defaults to white (`#FFFFFF`), so pixels of that color are
treated as transparent and ignored. For PNG images, if a pixel is fully
transparent (alpha channel 0%) it will also be skipped.
The chrome key defaults to white (`#FFFFFF`), so pixels of that color are treated as transparent and ignored. For PNG images, if a pixel is fully transparent (alpha channel 0%) it will also be skipped. So the easiest format to use are PNG images with transparent pixels.
When converting an image into a drawing, the unique colors identified in the
drawing are extracted into the palette. You will need to later edit the palette
to assign meaning to the colors.
When converting an image into a Level or Doodad, the unique colors identified in the drawing are extracted to create the **Palette**. You will need to later edit the palette to assign meaning to the colors (giving them names or level properties). In particular, when importing a Level you'd want to mark which colors are solid ground.
## $ `doodad show`: Get information about a level or doodad
The `doodad show` command can return metadata and debug the contents of a Level or Doodad file.
```
% doodad show Example.level
===== Level: Example.level =====
Headers:
File format: zipfile
File version: 1
Game version: 0.14.0
Level UUID: d136fef3-1a3a-453c-b616-05aca8dd6840
Level title: Lesson 1: Controls
Author: Noah P
Password:
Locked: false
Game Rules:
Difficulty: Normal (0)
Survival: false
Palette:
- Swatch name: solid
Attributes: solid
Color: #777777
- Swatch name: decoration
Attributes: none
Color: #ff66ff
- Swatch name: fire
Attributes: fire,water
Color: #ff0000
- Swatch name: semisolid
Attributes: semi-solid
Color: #cccccc
Level Settings:
Page type: Bounded
Max size: 2550x3300
Wallpaper: notebook.png
Attached Files:
assets/screenshots/large.png: 63377 bytes
assets/screenshots/medium.png: 25807 bytes
assets/screenshots/small.png: 6837 bytes
assets/screenshots/tiny.png: 7414 bytes
Actors:
Level contains 16 actors
Use -actors or -verbose to serialize Actors
Chunks:
Pixels Per Chunk: 128^2
Number Generated: 102
Coordinate Range: (0,0) ... (1919,2047)
World Dimensions: 1919x2047
Use -chunks or -verbose to serialize Chunks
```
Or for a doodad:
```
% doodad show example.doodad
===== Doodad: example.doodad =====
Headers:
File format: zipfile
File version: 1
Game version: 0.14.1
Doodad title: Fire Region
Author: Noah
Dimensions: Rect<0,0,128,128>
Hitbox: Rect<0,0,128,128>
Locked: true
Hidden: false
Script size: 378 bytes
Tags:
category: technical
Options:
str name = fire
Palette:
- Swatch name: Color<#8a2b2b+ff>
Attributes: solid
Color: #8a2b2b
Layer 0: fire-128
Chunks:
Pixels Per Chunk: 128^2
Number Generated: 1
Coordinate Range: (0,0) ... (127,127)
World Dimensions: 127x127
Use -chunks or -verbose to serialize Chunks
```
## Editing Level or Doodad Properties
The `edit-doodad` and `edit-level` subcommands allow setting properties on your custom files programmatically.
Properties you can set for both file types include:
* Metadata like the Title and Author name
* Lock your drawing from being edited in-game
For Doodads, you can set their Tags and Options, Hitbox, etc. - most of the useful settings are supported, as the game's built-in doodads use this program!
# Where to Find It
The `doodad` tool ships with the official releases of the game, and may be found in one of the following places:
* **Windows:** doodad.exe should be in the same place as sketchymaze.exe (e.g. in the ZIP file)
* **Mac OS:** it is available inside the "Sketchy Maze.app" bundle, in the "Contents/MacOS" folder next to the `sketchymaze` binary.
Invoke it from a terminal like:
```bash
alias doodad="/Applications/Sketchy Maze.app/Contents/MacOS/doodad"
doodad -h
```
* **Linux:** the doodad binary should be in the same place as the sketchymaze program itself:
* In the .tar.gz file
* In /opt/sketchymaze if installed by an .rpm or .deb package
* AppImage: `./SketchyMaze.AppImage doodad` will invoke the doodad command.
* Flatpak: `flatpak run com.sketchymaze.Doodle doodad` will invoke the doodad command. Invoke it from a terminal like:
```bash
alias doodad="flatpak run com.sketchymaze.Doodle doodad"
doodad -h
```

View File

@ -11,10 +11,11 @@ import (
"image/png"
"git.kirsle.net/apps/doodle/pkg/branding"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/go/render"
"github.com/urfave/cli/v2"
"golang.org/x/image/bmp"
@ -32,8 +33,8 @@ func init() {
Flags: []cli.Flag{
&cli.StringFlag{
Name: "key",
Usage: "chroma key color for transparency on input image files",
Value: "#ffffff",
Usage: "chroma key color for transparency on input image files, e.g. #ffffff",
Value: "",
},
&cli.StringFlag{
Name: "title",
@ -57,12 +58,16 @@ func init() {
}
// Parse the chroma key.
chroma, err := render.HexColor(c.String("key"))
if err != nil {
return cli.Exit(
"Chrome key not a valid color: "+err.Error(),
1,
)
var chroma = render.Invisible
if key := c.String("key"); key != "" {
color, err := render.HexColor(c.String("key"))
if err != nil {
return cli.Exit(
"Chrome key not a valid color: "+err.Error(),
1,
)
}
chroma = color
}
args := c.Args().Slice()
@ -100,7 +105,8 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
// Read the source images. Ensure they all have the same boundaries.
var (
imageBounds image.Point
chunkSize int // the square shape for the Doodad chunk size
width int // dimensions of the incoming image
height int
images []image.Image
)
@ -125,11 +131,8 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
// Validate all images are the same size.
if i == 0 {
imageBounds = imageSize
if imageSize.X > imageSize.Y {
chunkSize = imageSize.X
} else {
chunkSize = imageSize.Y
}
width = imageSize.X
height = imageSize.Y
} else if imageSize != imageBounds {
return cli.Exit("your source images are not all the same dimensions", 1)
}
@ -137,6 +140,17 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
images = append(images, img)
}
// Initialize the palette from a JSON file?
var palette *level.Palette
if paletteFile := c.String("palette"); paletteFile != "" {
log.Info("Loading initial palette from file: %s", paletteFile)
if p, err := level.LoadPaletteFromFile(paletteFile); err != nil {
return err
} else {
palette = p
}
}
// Helper function to translate image filenames into layer names.
toLayerName := func(filename string) string {
ext := filepath.Ext(filename)
@ -146,18 +160,19 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
// Generate the output drawing file.
switch strings.ToLower(filepath.Ext(outputFile)) {
case extDoodad:
log.Info("Output is a Doodad file (chunk size %d): %s", chunkSize, outputFile)
doodad := doodads.New(chunkSize)
doodad := doodads.New(width, height)
doodad.GameVersion = branding.Version
doodad.Title = c.String("title")
if doodad.Title == "" {
doodad.Title = "Converted Doodad"
}
doodad.Author = os.Getenv("USER")
doodad.Author = native.DefaultAuthor
// Write the first layer and gather its palette.
log.Info("Converting first layer to drawing and getting the palette")
palette, layer0 := imageToChunker(images[0], chroma, nil, chunkSize)
var chunkSize = doodad.ChunkSize8()
log.Info("Output is a Doodad file (%dx%d): %s", width, height, outputFile)
palette, layer0 := imageToChunker(images[0], chroma, palette, chunkSize)
doodad.Palette = palette
doodad.Layers[0].Chunker = layer0
doodad.Layers[0].Name = toLayerName(inputFiles[0])
@ -168,10 +183,7 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
img := images[i]
log.Info("Converting extra layer %d", i)
_, chunker := imageToChunker(img, chroma, palette, chunkSize)
doodad.Layers = append(doodad.Layers, doodads.Layer{
Name: toLayerName(inputFiles[i]),
Chunker: chunker,
})
doodad.AddLayer(toLayerName(inputFiles[i]), chunker)
}
}
@ -187,12 +199,15 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
lvl := level.New()
lvl.GameVersion = branding.Version
lvl.MaxWidth = int64(width)
lvl.MaxHeight = int64(height)
lvl.PageType = level.Bounded
lvl.Title = c.String("title")
if lvl.Title == "" {
lvl.Title = "Converted Level"
}
lvl.Author = os.Getenv("USER")
palette, chunker := imageToChunker(images[0], chroma, nil, lvl.Chunker.Size)
lvl.Author = native.DefaultAuthor
palette, chunker := imageToChunker(images[0], chroma, palette, lvl.Chunker.Size)
lvl.Palette = palette
lvl.Chunker = chunker
@ -287,7 +302,8 @@ func drawingToImage(c *cli.Context, chroma render.Color, inputFiles []string, ou
//
// img: input image like a PNG
// chroma: transparent color
func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette, chunkSize int) (*level.Palette, *level.Chunker) {
// palette: your palette so far (new distinct colors are added)
func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette, chunkSize uint8) (*level.Palette, *level.Chunker) {
var (
chunker = level.NewChunker(chunkSize)
bounds = img.Bounds()
@ -336,7 +352,10 @@ func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette
sort.Strings(sortedColors)
for _, hex := range sortedColors {
if _, ok := newColors[hex]; ok {
palette.Swatches = append(palette.Swatches, uniqueColor[hex])
if err := palette.AddSwatch(uniqueColor[hex]); err != nil {
log.Error("Could not add more colors to the palette: %s", err)
panic(err.Error())
}
}
}
palette.Inflate()

View File

@ -6,8 +6,8 @@ import (
"strconv"
"strings"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/go/render"
"github.com/urfave/cli/v2"
)
@ -38,11 +38,16 @@ func init() {
Name: "hitbox",
Usage: "set the doodad hitbox (X,Y,W,H or W,H format)",
},
&cli.StringSliceFlag{
&cli.StringFlag{
Name: "tag",
Aliases: []string{"t"},
Usage: "set a key/value tag on the doodad, in key=value format. Empty value deletes the tag.",
},
&cli.StringFlag{
Name: "option",
Aliases: []string{"o"},
Usage: "set an option on the doodad, in key=type=default format, e.g. active=bool=true, speed=int=10, name=str. Value types are bool, str, int.",
},
&cli.BoolFlag{
Name: "hide",
Usage: "Hide the doodad from the palette",
@ -59,6 +64,10 @@ func init() {
Name: "unlock",
Usage: "remove the write-lock on the level file",
},
&cli.BoolFlag{
Name: "touch",
Usage: "simply load and re-save the doodad, to migrate it to a zipfile",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
@ -94,6 +103,11 @@ func editDoodad(c *cli.Context, filename string) error {
* Update level properties *
***************************/
if c.Bool("touch") {
log.Info("Just touching and resaving the file")
modified = true
}
if c.String("title") != "" {
dd.Title = c.String("title")
log.Info("Set title: %s", dd.Title)
@ -135,29 +149,57 @@ func editDoodad(c *cli.Context, filename string) error {
}
// Tags.
tags := c.StringSlice("tag")
if len(tags) > 0 {
for _, tag := range tags {
parts := strings.SplitN(tag, "=", 2)
if len(parts) != 2 {
log.Error("--tag: must be in format `key=value`. Value may be blank to delete a tag. len=%d", len(parts))
os.Exit(1)
}
var (
key = parts[0]
value = parts[1]
)
if value == "" {
log.Debug("Delete tag '%s'", key)
delete(dd.Tags, key)
} else {
log.Debug("Set tag '%s' to '%s'", key, value)
dd.Tags[key] = value
}
modified = true
tag := c.String("tag")
if len(tag) > 0 {
parts := strings.SplitN(tag, "=", 3)
if len(parts) != 2 {
log.Error("--tag: must be in format `key=value`. Value may be blank to delete a tag. len=%d tag=%s got=%+v", len(parts), tag, parts)
os.Exit(1)
}
var (
key = parts[0]
value = parts[1]
)
if value == "" {
log.Debug("Delete tag '%s'", key)
delete(dd.Tags, key)
} else {
log.Debug("Set tag '%s' to '%s'", key, value)
dd.Tags[key] = value
}
modified = true
}
// Options.
opt := c.String("option")
if len(opt) > 0 {
parts := strings.SplitN(opt, "=", 3)
if len(parts) < 2 {
log.Error("--option: must be in format `name=type` or `name=type=value`")
os.Exit(1)
}
var (
name = parts[0]
dataType = parts[1]
value string
)
if len(parts) == 3 {
value = parts[2]
}
// Validate the data types.
if dataType != "bool" && dataType != "str" && dataType != "int" {
log.Error("--option: invalid type, should be a bool, str or int")
os.Exit(1)
}
value = dd.SetOption(name, dataType, value)
log.Info("Set option %s (%s) = %s", name, dataType, value)
modified = true
}
if c.Bool("hide") {

View File

@ -1,10 +1,12 @@
package commands
import (
"errors"
"fmt"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/go/render"
"github.com/urfave/cli/v2"
)
@ -23,6 +25,11 @@ func init() {
Aliases: []string{"q"},
Usage: "limit output (don't show doodad data at the end)",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "write to a different output file than the input (especially for --resize)",
},
&cli.StringFlag{
Name: "title",
Usage: "set the level title",
@ -41,7 +48,11 @@ func init() {
},
&cli.StringFlag{
Name: "max-size",
Usage: "set the page max size (WxH format, like 2550x3300)",
Usage: "set the bounded level page max size (WxH format, like 2550x3300)",
},
&cli.IntFlag{
Name: "resize",
Usage: "change the chunk size, and re-encode the whole level into chunks of the new size",
},
&cli.StringFlag{
Name: "wallpaper",
@ -55,6 +66,14 @@ func init() {
Name: "unlock",
Usage: "remove the write-lock on the level file",
},
&cli.StringFlag{
Name: "remove-actor",
Usage: "Remove all instances of the actor from the level. Value is their filename or UUID.",
},
&cli.BoolFlag{
Name: "touch",
Usage: "simply load and re-save the level, to migrate it to a zipfile",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
@ -86,10 +105,20 @@ func editLevel(c *cli.Context, filename string) error {
log.Info("File: %s", filename)
// Migrating it to a different chunk size?
if c.Int("resize") > 0 {
return rechunkLevel(c, filename, lvl)
}
/***************************
* Update level properties *
***************************/
if c.Bool("touch") {
log.Info("Just touching and resaving the file")
modified = true
}
if c.String("title") != "" {
lvl.Title = c.String("title")
log.Info("Set title: %s", lvl.Title)
@ -145,6 +174,29 @@ func editLevel(c *cli.Context, filename string) error {
modified = true
}
if c.String("remove-actor") != "" {
var (
match = c.String("remove-actor")
removeIDs = []string{}
)
for id, actor := range lvl.Actors {
if id == match || actor.Filename == match {
removeIDs = append(removeIDs, id)
}
}
if len(removeIDs) > 0 {
for _, id := range removeIDs {
delete(lvl.Actors, id)
}
log.Info("Removed %d instances of actor %s from the level.", len(removeIDs), match)
modified = true
} else {
log.Error("Did not find any actors like %s in the level.", match)
}
}
/******************************
* Save level changes to disk *
******************************/
@ -163,3 +215,51 @@ func editLevel(c *cli.Context, filename string) error {
return showLevel(c, filename)
}
// doodad edit-level --resize CHUNK_SIZE
//
// Handles the deep operation of re-copying the old level into a new level
// at the new chunk size.
func rechunkLevel(c *cli.Context, filename string, lvl *level.Level) error {
var chunkSize = balance.ChunkSize
if v := c.Int("resize"); v != 0 {
if v > 255 {
return errors.New("chunk size must be a uint8 <= 255")
}
chunkSize = uint8(v)
}
log.Info("Resizing the level's chunk size.")
log.Info("Current chunk size: %d", lvl.Chunker.Size)
log.Info("Target chunk size: %d", chunkSize)
if output := c.String("output"); output != "" {
filename = output
log.Info("Output file will be: %s", filename)
}
if chunkSize == lvl.Chunker.Size {
return errors.New("the level already has the target chunk size")
}
// Keep the level's current Chunker, and set a new one.
var oldChunker = lvl.Chunker
lvl.Chunker = level.NewChunker(chunkSize)
// Iterate all the Pixels of the old chunker.
log.Info("Copying pixels from old chunker into new chunker (this may take a while)...")
for pixel := range oldChunker.IterPixels() {
lvl.Chunker.Set(
pixel.Point(),
pixel.Swatch,
)
}
log.Info("Writing new data to filename: %s", filename)
if err := lvl.WriteFile(filename); err != nil {
log.Error(err.Error())
}
return showLevel(c, filename)
return nil
}

View File

@ -4,8 +4,8 @@ import (
"fmt"
"io/ioutil"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"github.com/urfave/cli/v2"
)

View File

@ -0,0 +1,343 @@
package commands
import (
"archive/zip"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"time"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
"github.com/urfave/cli/v2"
)
// LevelPack creation and management.
var LevelPack *cli.Command
func init() {
LevelPack = &cli.Command{
Name: "levelpack",
Usage: "create and manage .levelpack archives",
ArgsUsage: "-o output.levelpack <list of .level files>",
Subcommands: []*cli.Command{
{
Name: "create",
Usage: "create a new .levelpack file from source files",
ArgsUsage: "<output.levelpack> <input.level> [input.level...]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "title",
Aliases: []string{"t"},
Usage: "set a title for your levelpack, default will use the first level's title",
},
&cli.StringFlag{
Name: "author",
Aliases: []string{"a"},
Usage: "set an author for your levelpack, default will use the first level's author",
},
&cli.StringFlag{
Name: "description",
Aliases: []string{"d"},
Usage: "set a description for your levelpack",
},
&cli.IntFlag{
Name: "free",
Aliases: []string{"f"},
Usage: "set number of free levels (levels unlocked by default), 0 means all unlocked",
},
&cli.StringFlag{
Name: "doodads",
Aliases: []string{"D"},
Usage: "which doodads to embed: none, custom, all",
Value: "custom",
},
},
Action: levelpackCreate,
},
{
Name: "show",
Usage: "print details about a levelpack file",
ArgsUsage: "<input.levelpack>",
Action: levelpackShow,
},
},
Flags: []cli.Flag{},
}
}
// Subcommand `levelpack show`
func levelpackShow(c *cli.Context) error {
if c.NArg() < 1 {
return cli.Exit(
"Usage: doodad levelpack show <file.levelpack>",
1,
)
}
var filename = c.Args().Slice()[0]
if !strings.HasSuffix(filename, ".levelpack") {
return cli.Exit("file must name a .levelpack", 1)
}
lp, err := levelpack.LoadFile(filename)
if err != nil {
return cli.Exit(err, 1)
}
fmt.Printf("===== Levelpack: %s =====\n", filename)
fmt.Println("Headers:")
fmt.Printf(" Title: %s\n", lp.Title)
fmt.Printf(" Author: %s\n", lp.Author)
fmt.Printf(" Description: %s\n", lp.Description)
fmt.Printf(" Free levels: %d\n", lp.FreeLevels)
// List the levels.
fmt.Println("\nLevels:")
for i, lvl := range lp.Levels {
fmt.Printf("%d. %s: %s\n", i+1, lvl.Filename, lvl.Title)
}
// List the doodads.
dl := lp.ListFiles("doodads/")
if len(dl) > 0 {
fmt.Println("\nDoodads:")
for i, doodad := range dl {
fmt.Printf("%d. %s\n", i, doodad)
}
}
return nil
}
// Subcommand `levelpack create`
func levelpackCreate(c *cli.Context) error {
if c.NArg() < 2 {
return cli.Exit(
"Usage: doodad levelpack create <out.levelpack> <in.level ...>",
1,
)
}
var (
args = c.Args().Slice()
outfile = args[0]
infiles = args[1:]
title = c.String("title")
author = c.String("author")
description = c.String("description")
free = c.Int("free")
embedDoodads = c.String("doodads")
)
// Validate params.
if !strings.HasSuffix(outfile, ".levelpack") {
return cli.Exit("Output file must have a .levelpack extension", 1)
}
if embedDoodads != "none" && embedDoodads != "custom" && embedDoodads != "all" {
return cli.Exit(
"--doodads: must be one of all, custom, none",
1,
)
}
var lp = levelpack.LevelPack{
Title: title,
Author: author,
Description: description,
FreeLevels: free,
Created: time.Now().UTC(),
}
// Create a temp directory to work with.
workdir, err := os.MkdirTemp(userdir.CacheDirectory, "levelpack-*")
if err != nil {
return cli.Exit(
fmt.Sprintf("Couldn't make temp folder: %s", err),
1,
)
}
log.Info("Working directory: %s", workdir)
defer os.RemoveAll(workdir)
// Useful folders inside the working directory.
var (
levelDir = filepath.Join(workdir, "levels")
doodadDir = filepath.Join(workdir, "doodads")
assets = []string{
"index.json",
}
)
os.MkdirAll(levelDir, 0755)
os.MkdirAll(doodadDir, 0755)
// Get the list of the game's builtin doodads.
builtins, err := doodads.ListBuiltin()
if err != nil {
return cli.Exit(err, 1)
}
// Read the input levels.
for i, filename := range infiles {
if !strings.HasSuffix(filename, ".level") {
return cli.Exit(
fmt.Sprintf("input file at position %d (%s) was not a .level file", i, filename),
1,
)
}
lvl, err := level.LoadJSON(filename)
if err != nil {
return cli.Exit(
fmt.Sprintf("%s: %s", filename, err),
1,
)
}
// Fill in defaults for --title, --author
if lp.Title == "" {
lp.Title = lvl.Title
}
if lp.Author == "" {
lp.Author = lvl.Author
}
// Log the level in the index.json list.
lp.Levels = append(lp.Levels, levelpack.Level{
UUID: lvl.UUID,
Title: lvl.Title,
Author: lvl.Author,
Filename: filepath.Base(filename),
})
// Grab all the level's doodads to embed in the zip folder.
for _, actor := range lvl.Actors {
// What was the user's embeds request? (--doodads)
if embedDoodads == "none" {
break
} else if embedDoodads == "custom" {
// Custom doodads only.
if isBuiltinDoodad(builtins, actor.Filename) {
log.Warn("Doodad %s is a built-in, skipping embed", actor.Filename)
continue
}
}
if _, err := os.Stat(filepath.Join(doodadDir, actor.Filename)); !os.IsNotExist(err) {
continue
}
log.Info("Adding doodad to zipfile: %s", actor.Filename)
// Get this doodad from the game's built-ins or the user's
// profile directory only. Pulling embedded doodads out of
// the level is NOT supported.
asset, err := doodads.LoadFile(actor.Filename)
if err != nil {
return cli.Exit(
fmt.Sprintf("%s: Doodad file '%s': %s", filename, asset.Filename, err),
1,
)
}
var targetFile = filepath.Join(doodadDir, actor.Filename)
assets = append(assets, targetFile)
log.Debug("Write doodad: %s", targetFile)
err = asset.WriteFile(filepath.Join(doodadDir, actor.Filename))
if err != nil {
return cli.Exit(
fmt.Sprintf("Writing doodad %s: %s", actor.Filename, err),
1,
)
}
}
// Copy the level in.
var targetFile = filepath.Join(levelDir, filepath.Base(filename))
assets = append(assets, targetFile)
log.Info("Write level: %s", filename)
err = copyFile(filename, filepath.Join(levelDir, filepath.Base(filename)))
if err != nil {
return cli.Exit(
fmt.Sprintf("couldn't copy %s to %s: %s", filename, targetFile, err),
1,
)
}
}
log.Info("Writing index.json")
if err := lp.WriteFile(filepath.Join(workdir, "index.json")); err != nil {
return cli.Exit(err, 1)
}
// Zip the levelpack directory.
log.Info("Creating levelpack file: %s", outfile)
zipf, err := os.Create(outfile)
if err != nil {
return cli.Exit(
fmt.Sprintf("failed to create %s: %s", outfile, err),
1,
)
}
zipper := zip.NewWriter(zipf)
defer zipper.Close()
// Embed all the assets.
sort.Strings(assets)
for _, asset := range assets {
asset = strings.TrimPrefix(asset, workdir+"/")
log.Info("Zip: %s", asset)
err := zipFile(zipper, asset, filepath.Join(workdir, asset))
if err != nil {
return cli.Exit(err, 1)
}
}
log.Info("Written: %s", outfile)
return cli.Exit("", 0)
}
// copyFile copies a file on disk to another location.
func copyFile(source, target string) error {
input, err := ioutil.ReadFile(source)
if err != nil {
return err
}
return ioutil.WriteFile(target, input, 0644)
}
// zipFile reads a file on disk to add to a zip file.
// The `key` is the filepath inside the ZIP file, filename is the actual source file on disk.
func zipFile(zf *zip.Writer, key, filename string) error {
input, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
writer, err := zf.Create(key)
if err != nil {
return err
}
_, err = writer.Write(input)
return err
}
// Helper function to test whether a filename is part of the builtin doodads.
func isBuiltinDoodad(doodads []string, filename string) bool {
for _, cmp := range doodads {
if cmp == filename {
return true
}
}
return false
}

View File

@ -0,0 +1,115 @@
package commands
import (
"fmt"
"path/filepath"
"strings"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"github.com/urfave/cli/v2"
)
// Resave a Level or Doodad to adapt to file format upgrades.
var Resave *cli.Command
func init() {
Resave = &cli.Command{
Name: "resave",
Usage: "load and re-save a level or doodad file to migrate to newer file format versions",
ArgsUsage: "<.level or .doodad>",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "write to a different file than the input",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
return cli.Exit(
"Usage: doodad resave <.level .doodad ...>",
1,
)
}
filenames := c.Args().Slice()
for _, filename := range filenames {
switch strings.ToLower(filepath.Ext(filename)) {
case enum.LevelExt:
if err := resaveLevel(c, filename); err != nil {
log.Error(err.Error())
return cli.Exit("Error", 1)
}
case enum.DoodadExt:
if err := resaveDoodad(c, filename); err != nil {
log.Error(err.Error())
return cli.Exit("Error", 1)
}
default:
log.Error("File %s: not a level or doodad", filename)
}
}
return nil
},
}
}
// resaveLevel shows data about a level file.
func resaveLevel(c *cli.Context, filename string) error {
lvl, err := level.LoadJSON(filename)
if err != nil {
return err
}
log.Info("Loaded level from file: %s", filename)
log.Info("Last saved game version: %s", lvl.GameVersion)
// Different output filename?
if output := c.String("output"); output != "" {
log.Info("Output will be saved to: %s", output)
filename = output
}
if err := lvl.Vacuum(); err != nil {
log.Error("Vacuum error: %s", err)
} else {
log.Info("Run vacuum on level file.")
}
log.Info("Saving back to disk")
if err := lvl.WriteJSON(filename); err != nil {
return fmt.Errorf("couldn't write %s: %s", filename, err)
}
return showLevel(c, filename)
}
func resaveDoodad(c *cli.Context, filename string) error {
dd, err := doodads.LoadJSON(filename)
if err != nil {
return err
}
log.Info("Loaded doodad from file: %s", filename)
log.Info("Last saved game version: %s", dd.GameVersion)
// Different output filename?
if output := c.String("output"); output != "" {
log.Info("Output will be saved to: %s", output)
filename = output
}
if err := dd.Vacuum(); err != nil {
log.Error("Vacuum error: %s", err)
} else {
log.Info("Run vacuum on doodad file.")
}
log.Info("Saving back to disk")
if err := dd.WriteJSON(filename); err != nil {
return fmt.Errorf("couldn't write %s: %s", filename, err)
}
return showDoodad(c, filename)
}

View File

@ -1,14 +1,18 @@
package commands
import (
"bytes"
"encoding/binary"
"fmt"
"path/filepath"
"sort"
"strings"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/enum"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/level/rle"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"github.com/urfave/cli/v2"
)
@ -43,6 +47,14 @@ func init() {
Aliases: []string{"v"},
Usage: "print verbose output (all verbose flags enabled)",
},
&cli.BoolFlag{
Name: "visualize-rle",
Usage: "visually dump RLE encoded chunks to the terminal (VERY noisy for large drawings!)",
},
&cli.StringFlag{
Name: "chunk",
Usage: "specific chunk coordinate; when debugging chunks, only show this chunk (example: 2,-1)",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
@ -92,17 +104,30 @@ func showLevel(c *cli.Context, filename string) error {
}
}
// Is it a new zipfile format?
var fileType = "json or gzip"
if lvl.Zipfile != nil {
fileType = "zipfile"
}
fmt.Printf("===== Level: %s =====\n", filename)
fmt.Println("Headers:")
fmt.Printf(" File format: %s\n", fileType)
fmt.Printf(" File version: %d\n", lvl.Version)
fmt.Printf(" Game version: %s\n", lvl.GameVersion)
fmt.Printf(" Level UUID: %s\n", lvl.UUID)
fmt.Printf(" Level title: %s\n", lvl.Title)
fmt.Printf(" Author: %s\n", lvl.Author)
fmt.Printf(" Password: %s\n", lvl.Password)
fmt.Printf(" Locked: %+v\n", lvl.Locked)
fmt.Println("")
fmt.Println("Game Rules:")
fmt.Printf(" Difficulty: %s (%d)\n", lvl.GameRule.Difficulty, lvl.GameRule.Difficulty)
fmt.Printf(" Survival: %+v\n", lvl.GameRule.Survival)
fmt.Println("")
showPalette(lvl.Palette)
fmt.Println("Level Settings:")
@ -131,6 +156,19 @@ func showLevel(c *cli.Context, filename string) error {
fmt.Printf(" - Name: %s\n", actor.Filename)
fmt.Printf(" UUID: %s\n", id)
fmt.Printf(" At: %s\n", actor.Point)
if len(actor.Options) > 0 {
var ordered = []string{}
for name := range actor.Options {
ordered = append(ordered, name)
}
sort.Strings(ordered)
fmt.Println(" Options:")
for _, name := range ordered {
val := actor.Options[name]
fmt.Printf(" %s %s = %v\n", val.Type, val.Name, val.Value)
}
}
if c.Bool("links") {
for _, link := range actor.Links {
if other, ok := lvl.Actors[link]; ok {
@ -147,7 +185,7 @@ func showLevel(c *cli.Context, filename string) error {
}
// Serialize chunk information.
showChunker(c, lvl.Chunker)
showChunker(c, lvl.Chunker, 0)
fmt.Println("")
return nil
@ -165,13 +203,21 @@ func showDoodad(c *cli.Context, filename string) error {
return nil
}
// Is it a new zipfile format?
var fileType = "json or gzip"
if dd.Zipfile != nil {
fileType = "zipfile"
}
fmt.Printf("===== Doodad: %s =====\n", filename)
fmt.Println("Headers:")
fmt.Printf(" File format: %s\n", fileType)
fmt.Printf(" File version: %d\n", dd.Version)
fmt.Printf(" Game version: %s\n", dd.GameVersion)
fmt.Printf(" Doodad title: %s\n", dd.Title)
fmt.Printf(" Author: %s\n", dd.Author)
fmt.Printf(" Dimensions: %s\n", dd.Size)
fmt.Printf(" Hitbox: %s\n", dd.Hitbox)
fmt.Printf(" Locked: %+v\n", dd.Locked)
fmt.Printf(" Hidden: %+v\n", dd.Hidden)
@ -186,11 +232,26 @@ func showDoodad(c *cli.Context, filename string) error {
fmt.Println("")
}
if len(dd.Options) > 0 {
var ordered = []string{}
for name := range dd.Options {
ordered = append(ordered, name)
}
sort.Strings(ordered)
fmt.Println("Options:")
for _, name := range ordered {
opt := dd.Options[name]
fmt.Printf(" %s %s = %v\n", opt.Type, opt.Name, opt.Default)
}
fmt.Println("")
}
showPalette(dd.Palette)
for i, layer := range dd.Layers {
fmt.Printf("Layer %d: %s\n", i, layer.Name)
showChunker(c, layer.Chunker)
showChunker(c, layer.Chunker, i)
}
fmt.Println("")
@ -207,13 +268,27 @@ func showPalette(pal *level.Palette) {
fmt.Println("")
}
func showChunker(c *cli.Context, ch *level.Chunker) {
var worldSize = ch.WorldSize()
var width = worldSize.W - worldSize.X
var height = worldSize.H - worldSize.Y
func showChunker(c *cli.Context, ch *level.Chunker, layer int) {
var (
worldSize = ch.WorldSize()
chunkSize = int(ch.Size)
width = worldSize.W - worldSize.X
height = worldSize.H - worldSize.Y
// Chunk debugging CLI options.
visualize = c.Bool("visualize-rle")
specificChunk = c.String("chunk")
)
// If it's a Zipfile, count its chunks.
var chunkCount = len(ch.Chunks)
if ch.Zipfile != nil {
chunkCount = len(level.ChunksInZipfile(ch.Zipfile, layer))
}
fmt.Println("Chunks:")
fmt.Printf(" Pixels Per Chunk: %d^2\n", ch.Size)
fmt.Printf(" Number Generated: %d\n", len(ch.Chunks))
fmt.Printf(" Number Generated: %d\n", chunkCount)
fmt.Printf(" Coordinate Range: (%d,%d) ... (%d,%d)\n",
worldSize.X,
worldSize.Y,
@ -225,15 +300,53 @@ func showChunker(c *cli.Context, ch *level.Chunker) {
// Verbose chunk information.
if c.Bool("chunks") || c.Bool("verbose") {
fmt.Println(" Chunk Details:")
for point, chunk := range ch.Chunks {
for point := range ch.IterChunks() {
// Debugging specific chunk coordinate?
if specificChunk != "" && point.String() != specificChunk {
log.Warn("Skip chunk %s: not the specific chunk you're looking for", point)
continue
}
chunk, ok := ch.GetChunk(point)
if !ok {
continue
}
fmt.Printf(" - Coord: %s\n", point)
fmt.Printf(" Type: %s\n", chunkTypeToName(chunk.Type))
fmt.Printf(" Range: (%d,%d) ... (%d,%d)\n",
int(point.X)*ch.Size,
int(point.Y)*ch.Size,
(int(point.X)*ch.Size)+ch.Size,
(int(point.Y)*ch.Size)+ch.Size,
int(point.X)*chunkSize,
int(point.Y)*chunkSize,
(int(point.X)*chunkSize)+chunkSize,
(int(point.Y)*chunkSize)+chunkSize,
)
fmt.Printf(" Usage: %f (%d len of %d)\n", chunk.Usage(), chunk.Len(), chunkSize*chunkSize)
// Visualize the RLE encoded chunks?
if visualize && chunk.Type == level.RLEType {
ext, bin, err := ch.RawChunkFromZipfile(point)
if err != nil {
log.Error(err.Error())
continue
} else if ext != ".bin" {
log.Error("Unexpected filetype for RLE compressed chunk (expected .bin, got %s)", ext)
continue
}
// Read off the first byte (chunk type)
var reader = bytes.NewBuffer(bin)
binary.ReadUvarint(reader)
bin = reader.Bytes()
grid, err := rle.NewGrid(chunkSize)
if err != nil {
log.Error(err.Error())
continue
}
grid.Decompress(bin)
fmt.Println(grid.Visualize())
}
}
} else {
fmt.Println(" Use -chunks or -verbose to serialize Chunks")
@ -241,12 +354,12 @@ func showChunker(c *cli.Context, ch *level.Chunker) {
fmt.Println("")
}
func chunkTypeToName(v int) string {
func chunkTypeToName(v uint64) string {
switch v {
case level.MapType:
return "map"
case level.GridType:
return "grid"
case level.RLEType:
return "rle map"
default:
return fmt.Sprintf("type %d", v)
}

View File

@ -7,10 +7,10 @@ import (
"sort"
"time"
"git.kirsle.net/apps/doodle/cmd/doodad/commands"
"git.kirsle.net/apps/doodle/pkg/branding"
"git.kirsle.net/apps/doodle/pkg/license"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/cmd/doodad/commands"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/bootstrap"
"github.com/urfave/cli/v2"
)
@ -27,19 +27,15 @@ func init() {
}
func main() {
bootstrap.InitPlugins()
app := cli.NewApp()
app.Name = "doodad"
app.Usage = "command line interface for Doodle"
var freeLabel string
if !license.IsRegistered() {
freeLabel = " (shareware)"
}
app.Version = fmt.Sprintf("%s build %s%s. Built on %s",
branding.Version,
app.Version = fmt.Sprintf("%s build %s. Built on %s",
builds.Version,
Build,
freeLabel,
BuildDate,
)
@ -53,9 +49,11 @@ func main() {
app.Commands = []*cli.Command{
commands.Convert,
commands.Show,
commands.Resave,
commands.EditLevel,
commands.EditDoodad,
commands.InstallScript,
commands.LevelPack,
}
sort.Sort(cli.FlagsByName(app.Flags))

View File

@ -1,8 +1,8 @@
package command
import (
"git.kirsle.net/apps/doodle/pkg/license"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"github.com/urfave/cli/v2"
)

View File

@ -4,8 +4,8 @@ import (
"fmt"
"io/ioutil"
"git.kirsle.net/apps/doodle/pkg/license"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"github.com/urfave/cli/v2"
)

View File

@ -0,0 +1,92 @@
package command
import (
"fmt"
"strings"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/dpp/license"
"git.kirsle.net/SketchyMaze/dpp/license/levelsigning"
"github.com/urfave/cli/v2"
)
// SignLevel a license key for Sketchy Maze.
var SignLevel *cli.Command
func init() {
SignLevel = &cli.Command{
Name: "sign-level",
Usage: "sign a level file so that it may use embedded assets in free versions of the game.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "key",
Aliases: []string{"k"},
Usage: "Private key .pem file for signing",
Required: true,
},
&cli.StringFlag{
Name: "input",
Aliases: []string{"i"},
Usage: "Input file name (.level or .levelpack)",
Required: true,
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output file, default outputs to console",
},
},
Action: func(c *cli.Context) error {
key, err := license.AdminLoadPrivateKey(c.String("key"))
if err != nil {
return cli.Exit(err.Error(), 1)
}
var (
filename = c.String("input")
output = c.String("output")
)
if output == "" {
output = filename
}
// Sign a level?
if strings.HasSuffix(filename, ".level") {
lvl, err := level.LoadJSON(filename)
if err != nil {
return cli.Exit(err.Error(), 1)
}
// Sign it.
if sig, err := levelsigning.SignLevel(key, lvl); err != nil {
return cli.Exit(fmt.Errorf("couldn't sign level: %s", err), 1)
} else {
lvl.Signature = sig
err := lvl.WriteFile(output)
if err != nil {
return cli.Exit(err.Error(), 1)
}
}
} else if strings.HasSuffix(filename, ".levelpack") {
lp, err := levelpack.LoadFile(filename)
if err != nil {
return cli.Exit(err.Error(), 1)
}
// Sign it.
if sig, err := levelsigning.SignLevelPack(key, lp); err != nil {
return cli.Exit(fmt.Errorf("couldn't sign levelpack: %s", err), 1)
} else {
lp.Signature = sig
err := lp.WriteZipfile(output)
if err != nil {
return cli.Exit(err.Error(), 1)
}
}
}
return nil
},
}
}

View File

@ -4,8 +4,8 @@ import (
"io/ioutil"
"time"
"git.kirsle.net/apps/doodle/pkg/license"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"github.com/urfave/cli/v2"
)

View File

@ -0,0 +1,73 @@
package command
import (
"strings"
"git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/levelpack"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/dpp/license"
"git.kirsle.net/SketchyMaze/dpp/license/levelsigning"
"github.com/urfave/cli/v2"
)
// VerifyLevel a license key for Sketchy Maze.
var VerifyLevel *cli.Command
func init() {
VerifyLevel = &cli.Command{
Name: "verify-level",
Usage: "check the signature on a level or levelpack file.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "key",
Aliases: []string{"k"},
Usage: "Public key .pem file that signed the level",
Required: true,
},
&cli.StringFlag{
Name: "filename",
Aliases: []string{"f"},
Usage: "File name of the .level or .levelpack",
Required: true,
},
},
Action: func(c *cli.Context) error {
key, err := license.AdminLoadPublicKey(c.String("key"))
if err != nil {
return cli.Exit(err.Error(), 1)
}
filename := c.String("filename")
if strings.HasSuffix(filename, ".level") {
lvl, err := level.LoadJSON(filename)
if err != nil {
return cli.Exit(err.Error(), 1)
}
// Verify it.
if ok := levelsigning.VerifyLevel(key, lvl); !ok {
log.Error("Signature is not valid!")
return cli.Exit("", 1)
} else {
log.Info("Level signature is OK!")
}
} else if strings.HasSuffix(filename, ".levelpack") {
lp, err := levelpack.LoadFile(filename)
if err != nil {
return cli.Exit(err.Error(), 1)
}
// Verify it.
if ok := levelsigning.VerifyLevelPack(key, lp); !ok {
log.Error("Signature is not valid!")
return cli.Exit("", 1)
} else {
log.Info("Levelpack signature is OK!")
}
}
return nil
},
}
}

View File

@ -8,8 +8,8 @@ import (
"sort"
"time"
"git.kirsle.net/apps/doodle/cmd/doodle-admin/command"
"git.kirsle.net/apps/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/cmd/doodle-admin/command"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"github.com/urfave/cli/v2"
)
@ -47,6 +47,8 @@ func main() {
command.Key,
command.Sign,
command.Verify,
command.SignLevel,
command.VerifyLevel,
}
sort.Sort(cli.FlagsByName(app.Flags))

View File

@ -3,25 +3,36 @@ package main
import (
"errors"
"fmt"
"math/rand"
"os"
"path/filepath"
"regexp"
"runtime"
"runtime/pprof"
"sort"
"strconv"
"time"
"git.kirsle.net/apps/doodle/assets"
doodle "git.kirsle.net/apps/doodle/pkg"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/branding"
"git.kirsle.net/apps/doodle/pkg/license"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/shmem"
"git.kirsle.net/apps/doodle/pkg/sound"
"git.kirsle.net/apps/doodle/pkg/usercfg"
"git.kirsle.net/SketchyMaze/doodle/assets"
doodle "git.kirsle.net/SketchyMaze/doodle/pkg"
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding"
"git.kirsle.net/SketchyMaze/doodle/pkg/branding/builds"
"git.kirsle.net/SketchyMaze/doodle/pkg/chatbot"
"git.kirsle.net/SketchyMaze/doodle/pkg/gamepad"
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/bootstrap"
"git.kirsle.net/SketchyMaze/doodle/pkg/plus/dpp"
"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
"git.kirsle.net/SketchyMaze/doodle/pkg/sound"
"git.kirsle.net/SketchyMaze/doodle/pkg/sprites"
"git.kirsle.net/SketchyMaze/doodle/pkg/usercfg"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
golog "git.kirsle.net/go/log"
"git.kirsle.net/go/render"
"git.kirsle.net/go/render/sdl"
"github.com/urfave/cli/v2"
sdl2 "github.com/veandco/go-sdl2/sdl"
_ "image/png"
)
@ -40,32 +51,34 @@ func init() {
// Use all the CPU cores for collision detection and other load balanced
// goroutine work in the app.
runtime.GOMAXPROCS(runtime.NumCPU())
// Seed the random number generator.
rand.Seed(time.Now().UnixNano())
}
func main() {
runtime.LockOSThread()
bootstrap.InitPlugins()
app := cli.NewApp()
app.Name = "doodle"
app.Usage = fmt.Sprintf("%s - %s", branding.AppName, branding.Summary)
var freeLabel string
if !license.IsRegistered() {
freeLabel = " (shareware)"
}
// Load user settings from disk ASAP.
if err := usercfg.Load(); err != nil {
log.Error("Error loading user settings (defaults will be used): %s", err)
}
app.Version = fmt.Sprintf("%s build %s%s. Built on %s",
branding.Version,
// Set default user settings.
if usercfg.Current.CrosshairColor == render.Invisible {
usercfg.Current.CrosshairColor = balance.DefaultCrosshairColor
usercfg.Save()
}
// Set GameController style.
gamepad.SetStyle(gamepad.Style(usercfg.Current.ControllerStyle))
app.Version = fmt.Sprintf("%s build %s. Built on %s",
builds.Version,
Build,
freeLabel,
BuildDate,
)
@ -75,10 +88,24 @@ func main() {
Aliases: []string{"d"},
Usage: "enable debug level logging",
},
&cli.StringFlag{
Name: "log",
Aliases: []string{"o"},
Usage: "path on disk to copy the game's standard output logs (default goes to your game profile directory)",
},
&cli.StringFlag{
Name: "pprof",
Usage: "record pprof metrics to a filename",
},
&cli.StringFlag{
Name: "chdir",
Usage: "working directory for the game's runtime package",
},
&cli.BoolFlag{
Name: "new",
Aliases: []string{"n"},
Usage: "open immediately to the level editor",
},
&cli.BoolFlag{
Name: "edit",
Aliases: []string{"e"},
@ -89,6 +116,11 @@ func main() {
Aliases: []string{"w"},
Usage: "set the window size (e.g. -w 1024x768) or special value: desktop, mobile, landscape, maximized",
},
&cli.BoolFlag{
Name: "touch",
Aliases: []string{"t"},
Usage: "force TouchScreenMode to be on at all times, which hides the mouse cursor",
},
&cli.BoolFlag{
Name: "guitest",
Usage: "enter the GUI Test scene on startup",
@ -104,12 +136,43 @@ func main() {
}
app.Action = func(c *cli.Context) error {
// Set the log level now if debugging is enabled.
if c.Bool("debug") {
log.Logger.Config.Level = golog.DebugLevel
}
// Write the game's log to disk.
if err := initLogFile(c.String("log")); err != nil {
log.Error("Couldn't write logs to disk: %s", err)
}
log.Info("Starting %s %s", app.Name, app.Version)
// Print registration information, + also this sets the DefaultAuthor field.
if reg, err := dpp.Driver.GetRegistration(); err == nil {
log.Info("Registered to %s", reg.Name)
}
// --chdir into a different working directory? e.g. for Flatpak especially.
if doodlePath := c.String("chdir"); doodlePath != "" {
if err := os.Chdir(doodlePath); err != nil {
log.Error("--chdir: couldn't enter '%s': %s", doodlePath, err)
if err := setWorkingDirectory(c); err != nil {
log.Error("Couldn't set working directory: %s", err)
}
// Recording pprof stats?
if cpufile := c.String("pprof"); cpufile != "" {
log.Info("Saving CPU profiling data to %s", cpufile)
fh, err := os.Create(cpufile)
if err != nil {
log.Error("--pprof: can't create file: %s", err)
return err
}
defer fh.Close()
if err := pprof.StartCPUProfile(fh); err != nil {
log.Error("pprof: %s", err)
return err
}
defer pprof.StopCPUProfile()
}
var filename string
@ -118,10 +181,12 @@ func main() {
}
// Setting a custom resolution?
var maximize = true
if c.String("window") != "" {
if err := setResolution(c.String("window")); err != nil {
panic(err)
}
maximize = false
}
// Enable feature flags?
@ -129,10 +194,9 @@ func main() {
balance.FeaturesOn()
}
// Offline mode?
if c.Bool("offline") {
shmem.OfflineMode = true
}
// Set other program flags.
shmem.OfflineMode = c.Bool("offline")
native.ForceTouchScreenModeAlwaysOn = c.Bool("touch")
// SDL engine.
engine := sdl.New(
@ -141,6 +205,9 @@ func main() {
balance.Height,
)
// Activate game controller event support.
sdl2.GameControllerEventState(1)
// Load the SDL fonts in from bindata storage.
if fonts, err := assets.AssetDir("assets/fonts"); err == nil {
for _, file := range fonts {
@ -160,8 +227,34 @@ func main() {
game := doodle.New(c.Bool("debug"), engine)
game.SetupEngine()
// Start with maximized window unless -w was given.
if maximize {
log.Info("Maximize window")
engine.Maximize()
}
// Reload usercfg - if their settings.json doesn't exist, we try and pick a
// default "hide touch hints" based on touch device presence - which is only
// known after SetupEngine.
usercfg.Load()
// Hide the mouse cursor over the window, we draw our own sprite image for it.
engine.ShowCursor(false)
// Set the app window icon.
if engine, ok := game.Engine.(*sdl.Renderer); ok {
if icon, err := sprites.LoadImage(game.Engine, balance.WindowIcon); err == nil {
engine.SetWindowIcon(icon.Image)
} else {
log.Error("Couldn't load WindowIcon (%s): %s", balance.WindowIcon, err)
}
}
if c.Bool("guitest") {
game.Goto(&doodle.GUITestScene{})
} else if c.Bool("new") {
game.NewMap()
} else if filename != "" {
if c.Bool("edit") {
game.EditFile(filename)
@ -178,7 +271,14 @@ func main() {
// Log what Doodle thinks its working directory is, for debugging.
pwd, _ := os.Getwd()
log.Debug("PWD: %s", pwd)
log.Info("Program's working directory is: %s", pwd)
// Initialize the developer shell chatbot easter egg.
chatbot.Setup()
// Log some basic environment details.
w, h := engine.WindowSize()
log.Info("Window size: %dx%d", w, h)
game.Run()
return nil
@ -193,6 +293,53 @@ func main() {
}
}
// Set the app's working directory to find the runtime rtp assets.
func setWorkingDirectory(c *cli.Context) error {
// If they used the --chdir CLI option, go there.
if doodlePath := c.String("chdir"); doodlePath != "" {
return os.Chdir(doodlePath)
}
var test = func(paths ...string) bool {
paths = append(paths, filepath.Join("rtp", "Credits.txt"))
_, err := os.Stat(filepath.Join(paths...))
return err == nil
}
// If the rtp/ folder is already here, nothing is needed.
if test() {
return nil
}
// Get the path to the executable and search around from there.
ex, err := os.Executable()
if err != nil {
return fmt.Errorf("couldn't find the path to current executable: %s", err)
}
exPath := filepath.Dir(ex)
log.Debug("Trying to locate rtp/ folder relative to game's executable path: %s", exPath)
// Test a few relative paths around the executable's folder.
paths := []string{
exPath, // same directory, e.g. Linux /opt/sketchymaze root or Windows zipfile
filepath.Join(exPath, ".."), // parent directory, e.g. from the git clone root
filepath.Join(exPath, "..", "Resources"), // e.g. in a macOS .app bundle.
// Some well-known installed paths to check.
"/opt/sketchymaze", // Linux deb/rpm package
"/app/share/sketchymaze", // Linux flatpak package
}
for _, testPath := range paths {
if test(testPath) {
log.Info("Found rtp folder in: %s", testPath)
return os.Chdir(testPath)
}
}
return nil
}
func setResolution(value string) error {
switch value {
case "desktop", "maximized":
@ -221,3 +368,18 @@ func setResolution(value string) error {
}
return nil
}
func initLogFile(filename string) error {
// Default log file to disk goes to your profile directory.
if filename == "" {
filename = userdir.LogFile
}
fh, err := golog.NewFileTee(filename)
if err != nil {
return err
}
log.Logger.Config.Writer = fh
return nil
}

View File

@ -1,22 +0,0 @@
SHELL = /bin/bash
ALL: build
.PHONY: build
build:
doodad convert -t "Blue Azulian" blu-front.png blu-back.png \
blu-wr{1,2,3,4}.png blu-wl{1,2,3,4}.png azu-blu.doodad
doodad edit-doodad --tag "color=blue" azu-blu.doodad
doodad install-script azulian.js azu-blu.doodad
doodad convert -t "Red Azulian" red-front.png red-back.png \
red-wr{1,2,3,4}.png red-wl{1,2,3,4}.png azu-red.doodad
doodad edit-doodad --tag "color=red" azu-red.doodad
doodad install-script azulian.js azu-red.doodad
# Tag the category for these doodads
for i in *.doodad; do\
doodad edit-doodad --tag "category=creatures" $${i};\
done
cp *.doodad ../../../assets/doodads/

View File

@ -1,43 +0,0 @@
// Azulian (Red)
// DEPRECATED: they both share azulian.js now.
function main() {
var playerSpeed = 4;
var gravity = 4;
var Vx = Vy = 0;
var direction = "right";
Self.SetHitbox(0, 0, 32, 32)
Self.SetMobile(true);
Self.SetInventory(true);
Self.SetGravity(true);
Self.AddAnimation("walk-left", 100, ["red-wl1", "red-wl2", "red-wl3", "red-wl4"]);
Self.AddAnimation("walk-right", 100, ["red-wr1", "red-wr2", "red-wr3", "red-wr4"]);
// Sample our X position every few frames and detect if we've hit a solid wall.
var sampleTick = 0;
var sampleRate = 5;
var lastSampledX = 0;
setInterval(function () {
if (sampleTick % sampleRate === 0) {
var curX = Self.Position().X;
var delta = Math.abs(curX - lastSampledX);
if (delta < 5) {
direction = direction === "right" ? "left" : "right";
}
lastSampledX = curX;
}
sampleTick++;
// TODO: Vector() requires floats, pain in the butt for JS,
// the JS API should be friendlier and custom...
var Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1));
Self.SetVelocity(Vector(Vx, 0.0));
if (!Self.IsAnimating()) {
Self.PlayAnimation("walk-" + direction, null);
}
}, 100);
}

View File

@ -1,83 +0,0 @@
// Azulian (Red and Blue)
var playerSpeed = 12,
animating = false,
direction = "right",
lastDirection = "right";
function setupAnimations(color) {
var left = color === 'blue' ? 'blu-wl' : 'red-wl',
right = color === 'blue' ? 'blu-wr' : 'red-wr',
leftFrames = [left + '1', left + '2', left + '3', left + '4'],
rightFrames = [right + '1', right + '2', right + '3', right + '4'];
Self.AddAnimation("walk-left", 100, leftFrames);
Self.AddAnimation("walk-right", 100, rightFrames);
}
function main() {
var color = Self.GetTag("color");
playerSpeed = color === 'blue' ? 2 : 4;
Self.SetMobile(true);
Self.SetGravity(true);
Self.SetInventory(true);
Self.SetHitbox(0, 0, 24, 32);
setupAnimations(color);
if (Self.IsPlayer()) {
return playerControls();
}
// A.I. pattern: walks back and forth, turning around
// when it meets resistance.
// Sample our X position every few frames and detect if we've hit a solid wall.
var sampleTick = 0;
var sampleRate = 5;
var lastSampledX = 0;
setInterval(function () {
if (sampleTick % sampleRate === 0) {
var curX = Self.Position().X;
var delta = Math.abs(curX - lastSampledX);
if (delta < 5) {
direction = direction === "right" ? "left" : "right";
}
lastSampledX = curX;
}
sampleTick++;
var Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1));
Self.SetVelocity(Vector(Vx, 0.0));
// If we changed directions, stop animating now so we can
// turn around quickly without moonwalking.
if (direction !== lastDirection) {
Self.StopAnimation();
}
if (!Self.IsAnimating()) {
Self.PlayAnimation("walk-" + direction, null);
}
lastDirection = direction;
}, 100);
}
function playerControls() {
// Note: player speed is controlled by the engine.
Events.OnKeypress(function (ev) {
if (ev.Right) {
if (!Self.IsAnimating()) {
Self.PlayAnimation("walk-right", null);
}
} else if (ev.Left) {
if (!Self.IsAnimating()) {
Self.PlayAnimation("walk-left", null);
}
} else {
Self.StopAnimation();
animating = false;
}
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 910 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 838 B

View File

@ -1,14 +0,0 @@
ALL: build
.PHONY: build
build:
doodad convert -t "Bird (red)" left-1.png left-2.png right-1.png right-2.png \
dive-left.png dive-right.png bird-red.doodad
doodad install-script bird.js bird-red.doodad
# Tag the category for these doodads
for i in *.doodad; do\
doodad edit-doodad --tag "category=creatures" $${i};\
done
cp *.doodad ../../../assets/doodads/

View File

@ -1,99 +0,0 @@
// Bird
function main() {
var speed = 4;
var Vx = Vy = 0;
var altitude = Self.Position().Y; // original height in the level
var direction = "left",
lastDirection = "left";
var states = {
flying: 0,
diving: 1,
};
var state = states.flying;
Self.SetMobile(true);
Self.SetGravity(false);
Self.SetHitbox(0, 0, 46, 32);
Self.AddAnimation("fly-left", 100, ["left-1", "left-2"]);
Self.AddAnimation("fly-right", 100, ["right-1", "right-2"]);
// Player Character controls?
if (Self.IsPlayer()) {
return player();
}
Events.OnCollide(function (e) {
if (e.Actor.IsMobile() && e.InHitbox) {
return false;
}
});
// Sample our X position every few frames and detect if we've hit a solid wall.
var sampleTick = 0;
var sampleRate = 2;
var lastSampledX = 0;
var lastSampledY = 0;
setInterval(function () {
if (sampleTick % sampleRate === 0) {
var curX = Self.Position().X;
var delta = Math.abs(curX - lastSampledX);
if (delta < 5) {
direction = direction === "right" ? "left" : "right";
}
lastSampledX = curX;
}
sampleTick++;
// TODO: Vector() requires floats, pain in the butt for JS,
// the JS API should be friendlier and custom...
var Vx = parseFloat(speed * (direction === "left" ? -1 : 1));
Self.SetVelocity(Vector(Vx, 0.0));
// If we changed directions, stop animating now so we can
// turn around quickly without moonwalking.
if (direction !== lastDirection) {
Self.StopAnimation();
}
if (!Self.IsAnimating()) {
Self.PlayAnimation("fly-" + direction, null);
}
lastDirection = direction;
}, 100);
}
// If under control of the player character.
function player() {
Self.SetInventory(true);
Events.OnKeypress(function (ev) {
Vx = 0;
Vy = 0;
if (ev.Up) {
Vy = -playerSpeed;
} else if (ev.Down) {
Vy = playerSpeed;
}
if (ev.Right) {
if (!Self.IsAnimating()) {
Self.PlayAnimation("fly-right", null);
}
Vx = playerSpeed;
} else if (ev.Left) {
if (!Self.IsAnimating()) {
Self.PlayAnimation("fly-left", null);
}
Vx = -playerSpeed;
} else {
Self.StopAnimation();
animating = false;
}
Self.SetVelocity(Vector(Vx, Vy));
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Some files were not shown because too many files have changed in this diff Show More