diff --git a/Changes.md b/Changes.md index e5fbc47..905b546 100644 --- a/Changes.md +++ b/Changes.md @@ -1,15 +1,31 @@ # Changes -## v0.8.0 (TBD) - -To Do: -* Thief needs animations -* New levels +## v0.8.0 (September 3, 2021) This release brings some new features, new doodads, and new levels. New features: +* **Checkpoints** for gameplay will ease the pain of dying to fire + pixels or Anvils by teleporting you back to the checkpoint instead + of resetting the whole level. +* The **Doodad Properties** window while editing a doodad grants access + to many features which were previously only available via the + `doodad` tool, such as: + * Edit metadata like the Title and Author of your doodad + * Set the default hitbox of your doodad. + * Attach, open, and delete the JavaScript for your doodad + * Manage tags (key/value store) on your doodads: how you can + communicate settings to the JavaScript which can receive the + tags via `Self.GetTag("name")` +* Some **Generic Doodad Scripts** are built in. Using only the in-game + tools, it is possible to create custom doodads which have some basic + in-game logic and you don't need to write any code. The generic + scripts include: + * Generic Solid: the hitbox is solid + * Generic Fire: its hitbox harms the player + * Generic Anvil: harmless, deadly when falling + * Generic Collectible Item: it goes in your inventory * **All Characters are Playable!** Use the Link Tool to connect your Start Flag with another doodad on your level, and you will play **as** that doodad when the level starts. The Creature doodads are @@ -31,6 +47,18 @@ New doodads have been added: * The **Blue Azulian** is now selectable from the Doodads menu. It behaves like the Red Azulian but moves at half the speed. The Azulians can pick up items and open doors. +* The **Checkpoint Flag** will remember the player's spot in the level. + Dying to fire pixels or Anvils no longer forces a restart of the + level - you can resume from your last checkpoint, or the Start Flag + by default. + +New levels have been added: + +* **Castle.level:** introduces the new Thief character. Castle-themed + level showing off various new doodads. +* **Thief 1.level:** a level where you play as the Thief! You need to + steal Small Keys from dozens of Azulians and even steal items back + from another Thief who has already stolen some of the keys. Some doodads have changed behavior: @@ -52,12 +80,11 @@ The user interface has been improved: 5. All: a classic view paging over all doodads (and doodads not fitting any of the above categories). - doodad edit-doodad --tag "categories=doors,gizmos" filename.doodad - New functions are available in the JavaScript API for custom doodads: * FailLevel(message string): global function that kills the player with a custom death message. +* SetCheckpoint(Point): set the player respawn location * Self.MoveTo(Point(x, y int)) * Self.IsPlayer() bool * Self.SetInventory(bool): turn on or off inventory. Keys and other @@ -69,6 +96,7 @@ New functions are available in the JavaScript API for custom doodads: * Self.RemoveItem(filename string, quantity int) * Self.HasItem(filename string) * Self.Inventory() map[string]int +* Self.Hitbox() - also see Self.Hitbox.IsEmpty() The Events.OnLeave() callback now receives a CollideEvent argument, like OnCollide, instead of the useless actor ID string. Notable @@ -87,6 +115,12 @@ Other miscellaneous changes: * A **death barrier** will prevent Boy from falling forever on unbounded maps should he somehow fall off the level. The death barrier is a Y value 1,000 pixels below the lowest pixel on your map. +* Mobile doodads no longer "moonwalk" when they change directions. +* A new color is added to all default palettes: "hint" (pink) for + writing hint notes. +* A maximum scroll speed on the "follow the player character" logic + makes for cooler animations when the character teleports around. +* Levels and Doodads are now sorted on the Open menu. ## v0.7.2 (July 19 2021) diff --git a/cmd/doodad/commands/convert.go b/cmd/doodad/commands/convert.go index cec3c7f..a0c0cb4 100644 --- a/cmd/doodad/commands/convert.go +++ b/cmd/doodad/commands/convert.go @@ -48,7 +48,7 @@ func init() { }, Action: func(c *cli.Context) error { if c.NArg() < 2 { - return cli.NewExitError( + return cli.Exit( "Usage: doodad convert \n"+ " Image file types: png, bmp\n"+ " Drawing file types: level, doodad", @@ -59,7 +59,7 @@ func init() { // Parse the chroma key. chroma, err := render.HexColor(c.String("key")) if err != nil { - return cli.NewExitError( + return cli.Exit( "Chrome key not a valid color: "+err.Error(), 1, ) @@ -76,22 +76,22 @@ func init() { if inputType == extPNG || inputType == extBMP { if outputType == extLevel || outputType == extDoodad { if err := imageToDrawing(c, chroma, inputFiles, outputFile); err != nil { - return cli.NewExitError(err.Error(), 1) + return cli.Exit(err.Error(), 1) } return nil } - return cli.NewExitError("Image inputs can only output to Doodle drawings", 1) + return cli.Exit("Image inputs can only output to Doodle drawings", 1) } else if inputType == extLevel || inputType == extDoodad { if outputType == extPNG || outputType == extBMP { if err := drawingToImage(c, chroma, inputFiles, outputFile); err != nil { - return cli.NewExitError(err.Error(), 1) + return cli.Exit(err.Error(), 1) } return nil } - return cli.NewExitError("Doodle drawing inputs can only output to image files", 1) + return cli.Exit("Doodle drawing inputs can only output to image files", 1) } - return cli.NewExitError("File types must be: png, bmp, level, doodad", 1) + return cli.Exit("File types must be: png, bmp, level, doodad", 1) }, } } @@ -107,13 +107,13 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou for i, filename := range inputFiles { reader, err := os.Open(filename) if err != nil { - return cli.NewExitError(err.Error(), 1) + return cli.Exit(err.Error(), 1) } img, format, err := image.Decode(reader) log.Info("Parsed image %d of %d. Format: %s", i+1, len(inputFiles), format) if err != nil { - return cli.NewExitError(err.Error(), 1) + return cli.Exit(err.Error(), 1) } // Get the bounding box information of the source image. @@ -131,7 +131,7 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou chunkSize = imageSize.Y } } else if imageSize != imageBounds { - return cli.NewExitError("your source images are not all the same dimensions", 1) + return cli.Exit("your source images are not all the same dimensions", 1) } images = append(images, img) @@ -177,7 +177,7 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou err := doodad.WriteJSON(outputFile) if err != nil { - return cli.NewExitError(err.Error(), 1) + return cli.Exit(err.Error(), 1) } case extLevel: log.Info("Output is a Level file: %s", outputFile) @@ -198,10 +198,10 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou err := lvl.WriteJSON(outputFile) if err != nil { - return cli.NewExitError(err.Error(), 1) + return cli.Exit(err.Error(), 1) } default: - return cli.NewExitError("invalid output file: not a Doodle drawing", 1) + return cli.Exit("invalid output file: not a Doodle drawing", 1) } return nil diff --git a/cmd/doodad/commands/edit_doodad.go b/cmd/doodad/commands/edit_doodad.go index d810678..18157ed 100644 --- a/cmd/doodad/commands/edit_doodad.go +++ b/cmd/doodad/commands/edit_doodad.go @@ -3,10 +3,12 @@ package commands import ( "fmt" "os" + "strconv" "strings" "git.kirsle.net/apps/doodle/pkg/doodads" "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/go/render" "github.com/urfave/cli/v2" ) @@ -32,6 +34,10 @@ func init() { Name: "author", Usage: "set the doodad author", }, + &cli.StringFlag{ + Name: "hitbox", + Usage: "set the doodad hitbox (X,Y,W,H or W,H format)", + }, &cli.StringSliceFlag{ Name: "tag", Aliases: []string{"t"}, @@ -56,7 +62,7 @@ func init() { }, Action: func(c *cli.Context) error { if c.NArg() < 1 { - return cli.NewExitError( + return cli.Exit( "Usage: doodad edit-doodad ", 1, ) @@ -100,6 +106,34 @@ func editDoodad(c *cli.Context, filename string) error { modified = true } + if c.String("hitbox") != "" { + // Setting a hitbox, parse it out. + parts := strings.Split(c.String("hitbox"), ",") + var ints []int + for _, part := range parts { + a, err := strconv.Atoi(strings.TrimSpace(part)) + if err != nil { + return err + } + ints = append(ints, a) + } + + if len(ints) == 2 { + dd.Hitbox = render.NewRect(ints[0], ints[1]) + modified = true + } else if len(ints) == 4 { + dd.Hitbox = render.Rect{ + X: ints[0], + Y: ints[1], + W: ints[2], + H: ints[3], + } + modified = true + } else { + return cli.Exit("Hitbox should be in X,Y,W,H or just W,H format, 2 or 4 numbers.", 1) + } + } + // Tags. tags := c.StringSlice("tag") if len(tags) > 0 { @@ -152,7 +186,7 @@ func editDoodad(c *cli.Context, filename string) error { if modified { if err := dd.WriteJSON(filename); err != nil { - return cli.NewExitError(fmt.Sprintf("Write error: %s", err), 1) + return cli.Exit(fmt.Sprintf("Write error: %s", err), 1) } } else { log.Warn("Note: No changes made to level") diff --git a/cmd/doodad/commands/edit_level.go b/cmd/doodad/commands/edit_level.go index be9affd..4d620f1 100644 --- a/cmd/doodad/commands/edit_level.go +++ b/cmd/doodad/commands/edit_level.go @@ -58,7 +58,7 @@ func init() { }, Action: func(c *cli.Context) error { if c.NArg() < 1 { - return cli.NewExitError( + return cli.Exit( "Usage: doodad edit-level ", 1, ) @@ -151,7 +151,7 @@ func editLevel(c *cli.Context, filename string) error { if modified { if err := lvl.WriteFile(filename); err != nil { - return cli.NewExitError(fmt.Sprintf("Write error: %s", err), 1) + return cli.Exit(fmt.Sprintf("Write error: %s", err), 1) } } else { log.Warn("Note: No changes made to level") diff --git a/cmd/doodad/commands/install_script.go b/cmd/doodad/commands/install_script.go index c954e6e..f151c6a 100644 --- a/cmd/doodad/commands/install_script.go +++ b/cmd/doodad/commands/install_script.go @@ -26,7 +26,7 @@ func init() { }, Action: func(c *cli.Context) error { if c.NArg() != 2 { - return cli.NewExitError( + return cli.Exit( "Usage: doodad install-script ", 1, ) @@ -41,12 +41,12 @@ func init() { // Read the JavaScript source. javascript, err := ioutil.ReadFile(scriptFile) if err != nil { - return cli.NewExitError(err.Error(), 1) + return cli.Exit(err.Error(), 1) } doodad, err := doodads.LoadJSON(doodadFile) if err != nil { - return cli.NewExitError( + return cli.Exit( fmt.Sprintf("Failed to read doodad file: %s", err), 1, ) diff --git a/cmd/doodad/commands/show.go b/cmd/doodad/commands/show.go index 68ac24a..4c02ace 100644 --- a/cmd/doodad/commands/show.go +++ b/cmd/doodad/commands/show.go @@ -34,9 +34,9 @@ func init() { Usage: "print the script from a doodad file and exit", }, &cli.StringFlag{ - Name: "attachment", + Name: "attachment", Aliases: []string{"a"}, - Usage: "print the contents of the attached filename to terminal", + Usage: "print the contents of the attached filename to terminal", }, &cli.BoolFlag{ Name: "verbose", @@ -46,7 +46,7 @@ func init() { }, Action: func(c *cli.Context) error { if c.NArg() < 1 { - return cli.NewExitError( + return cli.Exit( "Usage: doodad show <.level .doodad ...>", 1, ) @@ -58,12 +58,12 @@ func init() { case enum.LevelExt: if err := showLevel(c, filename); err != nil { log.Error(err.Error()) - return cli.NewExitError("Error", 1) + return cli.Exit("Error", 1) } case enum.DoodadExt: if err := showDoodad(c, filename); err != nil { log.Error(err.Error()) - return cli.NewExitError("Error", 1) + return cli.Exit("Error", 1) } default: log.Error("File %s: not a level or doodad", filename) @@ -172,6 +172,7 @@ func showDoodad(c *cli.Context, filename string) error { 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(" Hitbox: %s\n", dd.Hitbox) fmt.Printf(" Locked: %+v\n", dd.Locked) fmt.Printf(" Hidden: %+v\n", dd.Hidden) fmt.Printf(" Script size: %d bytes\n", len(dd.Script)) diff --git a/pkg/branding/branding.go b/pkg/branding/branding.go index dae318e..7bfb6b9 100644 --- a/pkg/branding/branding.go +++ b/pkg/branding/branding.go @@ -4,7 +4,7 @@ package branding const ( AppName = "Sketchy Maze" Summary = "A drawing-based maze game" - Version = "0.7.2" + Version = "0.8.0" Website = "https://www.sketchymaze.com" Copyright = "2021 Noah Petherbridge" Byline = "a game by Noah Petherbridge." diff --git a/pkg/main_scene.go b/pkg/main_scene.go index b536042..eb60876 100644 --- a/pkg/main_scene.go +++ b/pkg/main_scene.go @@ -385,8 +385,8 @@ func (s *MainScene) Draw(d *Doodle) error { // Version label s.labelVersion.MoveTo(render.Point{ - X: (d.width / 2) - (s.labelVersion.Size().W / 2), - Y: s.labelSubtitle.Point().Y + s.labelSubtitle.Size().H + 8, + X: (d.width) - (s.labelVersion.Size().W) - 20, + Y: 20, }) s.labelVersion.Present(d.Engine, s.labelVersion.Point())