From fd649b7ab19f8c98d15d86e9ae3b54768057ae67 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sat, 6 Jul 2019 23:28:11 -0700 Subject: [PATCH] Doodad CLI Tool Features; Write Lock and Hidden * The `doodad` CLI tool got a lot of new commands: * `doodad show` to verbosely print details about Levels and Doodads. * `edit-level` and `edit-doodad` to update details about Levels and Doodads, such as their Title, Author, page type and size, etc. * Doodads gain a `Hidden bool` that hides them from the palette in Editor Mode. The player character (Blue Azulian) is Hidden. * Add some boolProps to the balance/ package and made a dynamic system to easily configure these with the in-game dev console. * Command: `boolProp list` returns available balance.boolProps * `boolProp ` returns the current value. * `boolProp ` sets the value. * The new boolProps are: * showAllDoodads: enable Hidden doodads on the palette UI (NOTE: reload the editor to take effect) * writeLockOverride: edit files that are write locked anyway * prettyJSON: pretty-format the JSON files saved by the game. --- cmd/doodad/commands/edit_doodad.go | 124 ++++++++++++++++ cmd/doodad/commands/edit_level.go | 156 ++++++++++++++++++++ cmd/doodad/commands/show.go | 224 +++++++++++++++++++++++++++++ cmd/doodad/main.go | 3 + lib/render/functions.go | 31 ++++ pkg/balance/boolprops.go | 60 ++++++++ pkg/balance/debug.go | 3 - pkg/commands.go | 21 ++- pkg/doodads/doodad.go | 1 + pkg/doodads/json.go | 6 +- pkg/editor_scene.go | 26 +++- pkg/editor_ui.go | 2 +- pkg/editor_ui_doodad.go | 33 +++-- pkg/level/swatch.go | 27 ++++ pkg/level/types.go | 4 +- pkg/uix/canvas_actors.go | 5 + 16 files changed, 706 insertions(+), 20 deletions(-) create mode 100644 cmd/doodad/commands/edit_doodad.go create mode 100644 cmd/doodad/commands/edit_level.go create mode 100644 cmd/doodad/commands/show.go create mode 100644 pkg/balance/boolprops.go diff --git a/cmd/doodad/commands/edit_doodad.go b/cmd/doodad/commands/edit_doodad.go new file mode 100644 index 0000000..ddb5a1b --- /dev/null +++ b/cmd/doodad/commands/edit_doodad.go @@ -0,0 +1,124 @@ +package commands + +import ( + "fmt" + + "git.kirsle.net/apps/doodle/pkg/doodads" + "git.kirsle.net/apps/doodle/pkg/log" + "github.com/urfave/cli" +) + +// EditDoodad allows writing doodad metadata. +var EditDoodad cli.Command + +func init() { + EditDoodad = cli.Command{ + Name: "edit-doodad", + Usage: "update metadata for a Doodad file", + ArgsUsage: "", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "title", + Usage: "set the doodad title", + }, + cli.StringFlag{ + Name: "author", + Usage: "set the doodad author", + }, + cli.BoolFlag{ + Name: "hide", + Usage: "Hide the doodad from the palette", + }, + cli.BoolFlag{ + Name: "unhide", + Usage: "Unhide the doodad from the palette", + }, + cli.BoolFlag{ + Name: "lock", + Usage: "write-lock the level file", + }, + cli.BoolFlag{ + Name: "unlock", + Usage: "remove the write-lock on the level file", + }, + }, + Action: func(c *cli.Context) error { + if c.NArg() < 1 { + return cli.NewExitError( + "Usage: doodad edit-doodad ", + 1, + ) + } + + var filenames = c.Args() + for _, filename := range filenames { + if err := editDoodad(c, filename); err != nil { + log.Error("%s: %s", filename, err) + } + } + + return nil + }, + } +} + +func editDoodad(c *cli.Context, filename string) error { + var modified bool + + dd, err := doodads.LoadJSON(filename) + if err != nil { + return fmt.Errorf("Failed to load %s: %s", filename, err) + } + + log.Info("File: %s", filename) + + /*************************** + * Update level properties * + ***************************/ + + if c.String("title") != "" { + dd.Title = c.String("title") + log.Info("Set title: %s", dd.Title) + modified = true + } + + if c.String("author") != "" { + dd.Author = c.String("author") + log.Info("Set author: %s", dd.Author) + modified = true + } + + if c.Bool("hide") { + dd.Hidden = true + log.Info("Marked doodad Hidden") + modified = true + } else if c.Bool("unhide") { + dd.Hidden = false + log.Info("Doodad is no longer Hidden") + modified = true + } + + if c.Bool("lock") { + dd.Locked = true + log.Info("Write lock enabled.") + modified = true + } else if c.Bool("unlock") { + dd.Locked = false + log.Info("Write lock disabled.") + modified = true + } + + /****************************** + * Save level changes to disk * + ******************************/ + + if modified { + if err := dd.WriteFile(filename); err != nil { + return cli.NewExitError(fmt.Sprintf("Write error: %s", err), 1) + } + } else { + log.Warn("Note: No changes made to level") + } + + return showDoodad(c, filename) +} diff --git a/cmd/doodad/commands/edit_level.go b/cmd/doodad/commands/edit_level.go new file mode 100644 index 0000000..51da1fe --- /dev/null +++ b/cmd/doodad/commands/edit_level.go @@ -0,0 +1,156 @@ +package commands + +import ( + "fmt" + + "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/apps/doodle/pkg/level" + "git.kirsle.net/apps/doodle/pkg/log" + "github.com/urfave/cli" +) + +// EditLevel allows writing level metadata. +var EditLevel cli.Command + +func init() { + EditLevel = cli.Command{ + Name: "edit-level", + Usage: "update metadata for a Level file", + ArgsUsage: "", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "title", + Usage: "set the level title", + }, + cli.StringFlag{ + Name: "author", + Usage: "set the level author", + }, + cli.StringFlag{ + Name: "password", + Usage: "set the level password", + }, + cli.StringFlag{ + Name: "type", + Usage: "set the page type. One of: Unbounded, Bounded, NoNegativeSpace, Bordered", + }, + cli.StringFlag{ + Name: "max-size", + Usage: "set the page max size (WxH format, like 2550x3300)", + }, + cli.StringFlag{ + Name: "wallpaper", + Usage: "set the wallpaper filename", + }, + cli.BoolFlag{ + Name: "lock", + Usage: "write-lock the level file", + }, + cli.BoolFlag{ + Name: "unlock", + Usage: "remove the write-lock on the level file", + }, + }, + Action: func(c *cli.Context) error { + if c.NArg() < 1 { + return cli.NewExitError( + "Usage: doodad edit-level ", + 1, + ) + } + + var filenames = c.Args() + for _, filename := range filenames { + if err := editLevel(c, filename); err != nil { + log.Error("%s: %s", filename, err) + } + } + + return nil + }, + } +} + +func editLevel(c *cli.Context, filename string) error { + var modified bool + + lvl, err := level.LoadJSON(filename) + if err != nil { + return fmt.Errorf("Failed to load %s: %s", filename, err) + } + + log.Info("File: %s", filename) + + /*************************** + * Update level properties * + ***************************/ + + if c.String("title") != "" { + lvl.Title = c.String("title") + log.Info("Set title: %s", lvl.Title) + modified = true + } + + if c.String("author") != "" { + lvl.Author = c.String("author") + log.Info("Set author: %s", lvl.Author) + modified = true + } + + if c.String("password") != "" { + lvl.Password = c.String("password") + log.Info("Updated level password") + modified = true + } + + if c.String("max-size") != "" { + w, h, err := render.ParseResolution(c.String("max-size")) + if err != nil { + log.Error("-max-size: %s", err) + } else { + lvl.MaxWidth = int64(w) + lvl.MaxHeight = int64(h) + modified = true + } + } + + if c.Bool("lock") { + lvl.Locked = true + log.Info("Write lock enabled.") + modified = true + } else if c.Bool("unlock") { + lvl.Locked = false + log.Info("Write lock disabled.") + modified = true + } + + if c.String("type") != "" { + if pageType, ok := level.PageTypeFromString(c.String("type")); ok { + lvl.PageType = pageType + log.Info("Page Type set to %s", pageType) + modified = true + } else { + log.Error("Invalid -type value. Should be like Unbounded, Bounded, NoNegativeSpace, Bordered") + } + } + + if c.String("wallpaper") != "" { + lvl.Wallpaper = c.String("wallpaper") + log.Info("Set wallpaper: %s", c.String("wallpaper")) + modified = true + } + + /****************************** + * Save level changes to disk * + ******************************/ + + if modified { + if err := lvl.WriteFile(filename); err != nil { + return cli.NewExitError(fmt.Sprintf("Write error: %s", err), 1) + } + } else { + log.Warn("Note: No changes made to level") + } + + return showLevel(c, filename) +} diff --git a/cmd/doodad/commands/show.go b/cmd/doodad/commands/show.go new file mode 100644 index 0000000..62ca9ac --- /dev/null +++ b/cmd/doodad/commands/show.go @@ -0,0 +1,224 @@ +package commands + +import ( + "fmt" + "path/filepath" + "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" + "github.com/urfave/cli" +) + +// Show information about a Level or Doodad file. +var Show cli.Command + +func init() { + Show = cli.Command{ + Name: "show", + Usage: "show information about a level or doodad file", + ArgsUsage: "<.level or .doodad>", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "actors", + Usage: "print verbose actor data in Level files", + }, + cli.BoolFlag{ + Name: "chunks", + Usage: "print verbose data about all the pixel chunks in a file", + }, + cli.BoolFlag{ + Name: "script", + Usage: "print the script from a doodad file and exit", + }, + cli.BoolFlag{ + Name: "verbose, v", + Usage: "print verbose output (all verbose flags enabled)", + }, + }, + Action: func(c *cli.Context) error { + if c.NArg() < 1 { + return cli.NewExitError( + "Usage: doodad show <.level .doodad ...>", + 1, + ) + } + + filenames := c.Args() + for _, filename := range filenames { + switch strings.ToLower(filepath.Ext(filename)) { + case enum.LevelExt: + if err := showLevel(c, filename); err != nil { + log.Error(err.Error()) + return cli.NewExitError("Error", 1) + } + case enum.DoodadExt: + if err := showDoodad(c, filename); err != nil { + log.Error(err.Error()) + return cli.NewExitError("Error", 1) + } + default: + log.Error("File %s: not a level or doodad", filename) + } + } + return nil + }, + } +} + +// showLevel shows data about a level file. +func showLevel(c *cli.Context, filename string) error { + lvl, err := level.LoadJSON(filename) + if err != nil { + return err + } + + fmt.Printf("===== Level: %s =====\n", filename) + + fmt.Println("Headers:") + fmt.Printf(" File version: %d\n", lvl.Version) + fmt.Printf(" Game version: %s\n", lvl.GameVersion) + 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("") + + showPalette(lvl.Palette) + + fmt.Println("Level Settings:") + fmt.Printf(" Page type: %s\n", lvl.PageType.String()) + fmt.Printf(" Max size: %dx%d\n", lvl.MaxWidth, lvl.MaxHeight) + fmt.Printf(" Wallpaper: %s\n", lvl.Wallpaper) + fmt.Println("") + + // Print the actor information. + fmt.Println("Actors:") + fmt.Printf(" Level contains %d actors\n", len(lvl.Actors)) + if c.Bool("actors") || c.Bool("verbose") { + fmt.Println(" List of Actors:") + for id, actor := range lvl.Actors { + fmt.Printf(" - Name: %s\n", actor.Filename) + fmt.Printf(" UUID: %s\n", id) + fmt.Printf(" At: %s\n", actor.Point) + if c.Bool("links") { + for _, link := range actor.Links { + if other, ok := lvl.Actors[link]; ok { + fmt.Printf(" Link: %s (%s)\n", link, other.Filename) + } else { + fmt.Printf(" Link: %s (**UNRESOLVED**)", link) + } + } + } + } + fmt.Println("") + } else { + fmt.Print(" Use -actors or -verbose to serialize Actors\n\n") + } + + // Serialize chunk information. + showChunker(c, lvl.Chunker) + + fmt.Println("") + return nil +} + +func showDoodad(c *cli.Context, filename string) error { + dd, err := doodads.LoadJSON(filename) + if err != nil { + return err + } + + if c.Bool("script") { + fmt.Printf("// %s.js\n", filename) + fmt.Println(strings.TrimSpace(dd.Script)) + return nil + } + + fmt.Printf("===== Doodad: %s =====\n", filename) + + fmt.Println("Headers:") + 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(" Locked: %+v\n", dd.Locked) + fmt.Printf(" Hidden: %+v\n", dd.Hidden) + fmt.Printf(" Script size: %d bytes\n", len(dd.Script)) + fmt.Println("") + + if len(dd.Tags) > 0 { + fmt.Println("Tags:") + for k, v := range dd.Tags { + fmt.Printf(" %s: %s\n", k, v) + } + fmt.Println("") + } + + showPalette(dd.Palette) + + for i, layer := range dd.Layers { + fmt.Printf("Layer %d: %s\n", i, layer.Name) + showChunker(c, layer.Chunker) + } + + fmt.Println("") + return nil +} + +func showPalette(pal *level.Palette) { + fmt.Println("Palette:") + for _, sw := range pal.Swatches { + fmt.Printf(" - Swatch name: %s\n", sw.Name) + fmt.Printf(" Attributes: %s\n", sw.Attributes()) + fmt.Printf(" Color: %s\n", sw.Color.ToHex()) + } + 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 + fmt.Println("Chunks:") + fmt.Printf(" Pixels Per Chunk: %d^2\n", ch.Size) + fmt.Printf(" Number Generated: %d\n", len(ch.Chunks)) + fmt.Printf(" Coordinate Range: (%d,%d) ... (%d,%d)\n", + worldSize.X, + worldSize.Y, + worldSize.W, + worldSize.H, + ) + fmt.Printf(" World Dimensions: %dx%d\n", width, height) + + // Verbose chunk information. + if c.Bool("chunks") || c.Bool("verbose") { + fmt.Println(" Chunk Details:") + for point, chunk := range ch.Chunks { + 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, + ) + } + } else { + fmt.Println(" Use -chunks or -verbose to serialize Chunks") + } + fmt.Println("") +} + +func chunkTypeToName(v int) string { + switch v { + case level.MapType: + return "map" + case level.GridType: + return "grid" + default: + return fmt.Sprintf("type %d", v) + } +} diff --git a/cmd/doodad/main.go b/cmd/doodad/main.go index a028068..2fc793b 100644 --- a/cmd/doodad/main.go +++ b/cmd/doodad/main.go @@ -52,6 +52,9 @@ func main() { app.Commands = []cli.Command{ commands.Convert, + commands.Show, + commands.EditLevel, + commands.EditDoodad, commands.InstallScript, } diff --git a/lib/render/functions.go b/lib/render/functions.go index b78a254..d049f5d 100644 --- a/lib/render/functions.go +++ b/lib/render/functions.go @@ -1,5 +1,36 @@ package render +import ( + "fmt" + "regexp" + "strconv" +) + +var regexpResolution = regexp.MustCompile(`^(\d+)x(\d+)$`) + +// ParseResolution turns a resolution string like "1024x768" and returns the +// width and height values. +func ParseResolution(resi string) (int, int, error) { + m := regexpResolution.FindStringSubmatch(resi) + if m == nil { + return 0, 0, fmt.Errorf("invalid resolution format, should be %s", + regexpResolution.String(), + ) + } + + width, err := strconv.Atoi(m[1]) + if err != nil { + return 0, 0, err + } + + height, err := strconv.Atoi(m[2]) + if err != nil { + return 0, 0, err + } + + return width, height, nil +} + // TrimBox helps with Engine.Copy() to trim a destination box so that it // won't overflow with the parent container. func TrimBox(src, dst *Rect, p Point, S Rect, thickness int32) { diff --git a/pkg/balance/boolprops.go b/pkg/balance/boolprops.go new file mode 100644 index 0000000..0d53665 --- /dev/null +++ b/pkg/balance/boolprops.go @@ -0,0 +1,60 @@ +package balance + +import ( + "errors" + "fmt" + "sort" + "strings" +) + +// Fun bool props to wreak havoc in the game. +var ( + // Force show hidden doodads in the palette in Editor Mode. + ShowHiddenDoodads bool + + // Force ability to edit Locked levels and doodads. + WriteLockOverride bool + + // Pretty-print JSON files when writing. + JSONIndent bool +) + +// Human friendly names for the boolProps. Not necessarily the long descriptive +// variable names above. +var props = map[string]*bool{ + "showAllDoodads": &ShowHiddenDoodads, + "writeLockOverride": &WriteLockOverride, + "prettyJSON": &JSONIndent, + + // WARNING: SLOW! + "disableChunkTextureCache": &DisableChunkTextureCache, +} + +// GetBoolProp reads the current value of a boolProp. +// Special value "list" will error out with a list of available props. +func GetBoolProp(name string) (bool, error) { + if name == "list" { + var keys []string + for k := range props { + keys = append(keys, k) + } + sort.Strings(keys) + return false, fmt.Errorf( + "Boolprops: %s", + strings.Join(keys, ", "), + ) + } + if prop, ok := props[name]; ok { + return *prop, nil + } + return false, errors.New("no such boolProp") +} + +// BoolProp allows easily setting a boolProp by name. +func BoolProp(name string, v bool) error { + if prop, ok := props[name]; ok { + *prop = v + return nil + } + return errors.New("no such boolProp") +} diff --git a/pkg/balance/debug.go b/pkg/balance/debug.go index a33c672..66349c4 100644 --- a/pkg/balance/debug.go +++ b/pkg/balance/debug.go @@ -34,9 +34,6 @@ var ( // Set to a color other than Invisible to force the uix.Canvas to color ALL // Stroke pixels in this color. DebugCanvasStrokeColor = render.Invisible - - // Pretty-print JSON files when writing. - JSONIndent = true ) func init() { diff --git a/pkg/commands.go b/pkg/commands.go index 8ee8f5e..4a134cd 100644 --- a/pkg/commands.go +++ b/pkg/commands.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" + "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/enum" "github.com/robertkrimen/otto" ) @@ -201,8 +202,19 @@ func (c Command) Quit() error { // BoolProp command sets available boolean variables. func (c Command) BoolProp(d *Doodle) error { + if len(c.Args) == 1 { + // Showing the value of a boolProp. Only supported for those defined + // in balance/boolprops.go + value, err := balance.GetBoolProp(c.Args[0]) + if err != nil { + return err + } + d.Flash("%s: %+v", c.Args[0], value) + return nil + } + if len(c.Args) != 2 { - return errors.New("Usage: boolProp ") + return errors.New("Usage: boolProp [true or false]") } var ( @@ -229,7 +241,12 @@ func (c Command) BoolProp(d *Doodle) error { if ok { d.Flash("Set boolProp %s=%s", name, strconv.FormatBool(truthy)) } else { - d.Flash("Unknown boolProp name %s", name) + // Try the global boolProps in balance package. + if err := balance.BoolProp(name, truthy); err != nil { + d.Flash("%s", err) + } else { + d.Flash("%s: %+v", name, truthy) + } } return nil diff --git a/pkg/doodads/doodad.go b/pkg/doodads/doodad.go index 5c1e0e7..e84470e 100644 --- a/pkg/doodads/doodad.go +++ b/pkg/doodads/doodad.go @@ -10,6 +10,7 @@ import ( type Doodad struct { level.Base Filename string `json:"-"` // used internally, not saved in json + Hidden bool `json:"hidden,omitempty"` Palette *level.Palette `json:"palette"` Script string `json:"script"` Layers []Layer `json:"layers"` diff --git a/pkg/doodads/json.go b/pkg/doodads/json.go index e7c7b2a..b828e27 100644 --- a/pkg/doodads/json.go +++ b/pkg/doodads/json.go @@ -7,13 +7,17 @@ import ( "io/ioutil" "os" "path/filepath" + + "git.kirsle.net/apps/doodle/pkg/balance" ) // ToJSON serializes the doodad as JSON. func (d *Doodad) ToJSON() ([]byte, error) { out := bytes.NewBuffer([]byte{}) encoder := json.NewEncoder(out) - encoder.SetIndent("", "\t") + if balance.JSONIndent { + encoder.SetIndent("", "\t") + } err := encoder.Encode(d) return out.Bytes(), err } diff --git a/pkg/editor_scene.go b/pkg/editor_scene.go index c50f8f0..f30278a 100644 --- a/pkg/editor_scene.go +++ b/pkg/editor_scene.go @@ -8,6 +8,7 @@ import ( "git.kirsle.net/apps/doodle/lib/events" "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/doodads" "git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/enum" @@ -86,6 +87,18 @@ func (s *EditorScene) Setup(d *Doodle) error { } } + // Write locked level? + if s.Level != nil && s.Level.Locked { + if balance.WriteLockOverride { + d.Flash("Note: write lock has been overridden") + } else { + d.Flash("That level is write-protected and cannot be viewed in the editor.") + s.Level = nil + s.UI.Canvas.ClearActors() + s.filename = "" + } + } + // No level? if s.Level == nil { log.Debug("EditorScene.Setup: initializing a new Level") @@ -96,7 +109,7 @@ func (s *EditorScene) Setup(d *Doodle) error { s.UI.Canvas.Scrollable = true } case enum.DoodadDrawing: - // No Doodad? + // Getting a doodad from file? if s.filename != "" && s.OpenFile { log.Debug("EditorScene.Setup: Loading doodad from filename at %s", s.filename) if err := s.LoadDoodad(s.filename); err != nil { @@ -104,6 +117,17 @@ func (s *EditorScene) Setup(d *Doodle) error { } } + // Write locked doodad? + if s.Doodad != nil && s.Doodad.Locked { + if balance.WriteLockOverride { + d.Flash("Note: write lock has been overridden") + } else { + d.Flash("That doodad is write-protected and cannot be viewed in the editor.") + s.Doodad = nil + s.filename = "" + } + } + // No Doodad? if s.Doodad == nil { log.Debug("EditorScene.Setup: initializing a new Doodad") diff --git a/pkg/editor_ui.go b/pkg/editor_ui.go index e544374..8ea8232 100644 --- a/pkg/editor_ui.go +++ b/pkg/editor_ui.go @@ -243,7 +243,7 @@ func (u *EditorUI) Loop(ev *events.State) error { ) // Statusbar filename label. - filename := "untitled.map" + filename := "untitled.level" fileType := "Level" if u.Scene.filename != "" { filename = u.Scene.filename diff --git a/pkg/editor_ui_doodad.go b/pkg/editor_ui_doodad.go index c275a31..1c2b852 100644 --- a/pkg/editor_ui_doodad.go +++ b/pkg/editor_ui_doodad.go @@ -126,14 +126,33 @@ func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Fra var buttonSize = (paletteWidth - window.BoxThickness(2)) / int32(perRow) u.doodadButtonSize = buttonSize + // Load all the doodads, skip hidden ones. + var items []*doodads.Doodad + for _, filename := range doodadsAvailable { + doodad, err := doodads.LoadFile(filename) + if err != nil { + log.Error(err.Error()) + doodad = doodads.New(balance.DoodadSize) + } + + // Skip hidden doodads. + if doodad.Hidden && !balance.ShowHiddenDoodads { + log.Info("skip %s: hidden doodad", filename) + continue + } + + doodad.Filename = filename + items = append(items, doodad) + } + // Draw the doodad buttons in a grid `perRow` buttons wide. var ( row *ui.Frame rowCount int // for labeling the ui.Frame for each row btnRows = []*ui.Frame{} // Collect the row frames for the buttons. ) - for i, filename := range doodadsAvailable { - filename := filename + for i, doodad := range items { + doodad := doodad if row == nil || i%perRow == 0 { rowCount++ @@ -146,18 +165,12 @@ func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Fra }) } - doodad, err := doodads.LoadFile(filename) - if err != nil { - log.Error(err.Error()) - doodad = doodads.New(balance.DoodadSize) - } - can := uix.NewCanvas(int(buttonSize), true) - can.Name = filename + can.Name = doodad.Title can.SetBackground(render.White) can.LoadDoodad(doodad) - btn := ui.NewButton(filename, can) + btn := ui.NewButton(doodad.Title, can) btn.Resize(render.NewRect( buttonSize-2, // TODO: without the -2 the button border buttonSize-2, // rests on top of the window border. diff --git a/pkg/level/swatch.go b/pkg/level/swatch.go index a6304e0..96e8863 100644 --- a/pkg/level/swatch.go +++ b/pkg/level/swatch.go @@ -2,6 +2,7 @@ package level import ( "fmt" + "strings" "git.kirsle.net/apps/doodle/lib/render" ) @@ -44,6 +45,32 @@ func (s Swatch) String() string { return s.Name } +// Attributes returns a comma-separated list of attributes as a string on +// this swatch. This is for debugging and the `doodad show` CLI command to +// summarize the swatch and shouldn't be used for game logic. +func (s *Swatch) Attributes() string { + var result string + + if s.Solid { + result += "solid," + } + if s.Fire { + result += "fire," + } + if s.Water { + result += "water," + } + if s.isSparse { + result += "sparse," + } + + if result == "" { + result = "none," + } + + return strings.TrimSuffix(result, ",") +} + // IsSparse returns whether this Swatch is sparse (has only a palette index) and // requires inflation. func (s *Swatch) IsSparse() bool { diff --git a/pkg/level/types.go b/pkg/level/types.go index 6863500..b0a8e77 100644 --- a/pkg/level/types.go +++ b/pkg/level/types.go @@ -21,16 +21,16 @@ type Base struct { GameVersion string `json:"gameVersion" msgpack:"1"` // Game version that created the level. Title string `json:"title" msgpack:"2"` Author string `json:"author" msgpack:"3"` + Locked bool `json:"locked" msgpack:"4"` // Every drawing type is able to embed other files inside of itself. - Files FileSystem `json:"files" msgpack:"4"` + Files FileSystem `json:"files" msgpack:"5"` } // Level is the container format for Doodle map drawings. type Level struct { Base Password string `json:"passwd" msgpack:"10"` - Locked bool `json:"locked" msgpack:"11"` // Chunked pixel data. Chunker *Chunker `json:"chunks" msgpack:"12"` diff --git a/pkg/uix/canvas_actors.go b/pkg/uix/canvas_actors.go index 0ff0114..d475883 100644 --- a/pkg/uix/canvas_actors.go +++ b/pkg/uix/canvas_actors.go @@ -31,6 +31,11 @@ func (w *Canvas) InstallActors(actors level.ActorMap) error { return nil } +// ClearActors removes all the actors from the Canvas. +func (w *Canvas) ClearActors() { + w.actors = []*Actor{} +} + // SetScriptSupervisor assigns the Canvas scripting supervisor to enable // interaction with actor scripts. func (w *Canvas) SetScriptSupervisor(s *scripting.Supervisor) {