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 <name>` returns the current value. * `boolProp <name> <true or false>` 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.
This commit is contained in:
parent
6476a67faf
commit
fd649b7ab1
124
cmd/doodad/commands/edit_doodad.go
Normal file
124
cmd/doodad/commands/edit_doodad.go
Normal file
|
@ -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: "<filename.doodad>",
|
||||||
|
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 <filename.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)
|
||||||
|
}
|
156
cmd/doodad/commands/edit_level.go
Normal file
156
cmd/doodad/commands/edit_level.go
Normal file
|
@ -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: "<filename.level>",
|
||||||
|
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 <filename.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)
|
||||||
|
}
|
224
cmd/doodad/commands/show.go
Normal file
224
cmd/doodad/commands/show.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,9 @@ func main() {
|
||||||
|
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
commands.Convert,
|
commands.Convert,
|
||||||
|
commands.Show,
|
||||||
|
commands.EditLevel,
|
||||||
|
commands.EditDoodad,
|
||||||
commands.InstallScript,
|
commands.InstallScript,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,36 @@
|
||||||
package render
|
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
|
// TrimBox helps with Engine.Copy() to trim a destination box so that it
|
||||||
// won't overflow with the parent container.
|
// won't overflow with the parent container.
|
||||||
func TrimBox(src, dst *Rect, p Point, S Rect, thickness int32) {
|
func TrimBox(src, dst *Rect, p Point, S Rect, thickness int32) {
|
||||||
|
|
60
pkg/balance/boolprops.go
Normal file
60
pkg/balance/boolprops.go
Normal file
|
@ -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")
|
||||||
|
}
|
|
@ -34,9 +34,6 @@ var (
|
||||||
// Set to a color other than Invisible to force the uix.Canvas to color ALL
|
// Set to a color other than Invisible to force the uix.Canvas to color ALL
|
||||||
// Stroke pixels in this color.
|
// Stroke pixels in this color.
|
||||||
DebugCanvasStrokeColor = render.Invisible
|
DebugCanvasStrokeColor = render.Invisible
|
||||||
|
|
||||||
// Pretty-print JSON files when writing.
|
|
||||||
JSONIndent = true
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
)
|
)
|
||||||
|
@ -201,8 +202,19 @@ func (c Command) Quit() error {
|
||||||
|
|
||||||
// BoolProp command sets available boolean variables.
|
// BoolProp command sets available boolean variables.
|
||||||
func (c Command) BoolProp(d *Doodle) error {
|
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 {
|
if len(c.Args) != 2 {
|
||||||
return errors.New("Usage: boolProp <name> <true or false>")
|
return errors.New("Usage: boolProp <name> [true or false]")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -229,7 +241,12 @@ func (c Command) BoolProp(d *Doodle) error {
|
||||||
if ok {
|
if ok {
|
||||||
d.Flash("Set boolProp %s=%s", name, strconv.FormatBool(truthy))
|
d.Flash("Set boolProp %s=%s", name, strconv.FormatBool(truthy))
|
||||||
} else {
|
} 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
|
return nil
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
type Doodad struct {
|
type Doodad struct {
|
||||||
level.Base
|
level.Base
|
||||||
Filename string `json:"-"` // used internally, not saved in json
|
Filename string `json:"-"` // used internally, not saved in json
|
||||||
|
Hidden bool `json:"hidden,omitempty"`
|
||||||
Palette *level.Palette `json:"palette"`
|
Palette *level.Palette `json:"palette"`
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
Layers []Layer `json:"layers"`
|
Layers []Layer `json:"layers"`
|
||||||
|
|
|
@ -7,13 +7,17 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ToJSON serializes the doodad as JSON.
|
// ToJSON serializes the doodad as JSON.
|
||||||
func (d *Doodad) ToJSON() ([]byte, error) {
|
func (d *Doodad) ToJSON() ([]byte, error) {
|
||||||
out := bytes.NewBuffer([]byte{})
|
out := bytes.NewBuffer([]byte{})
|
||||||
encoder := json.NewEncoder(out)
|
encoder := json.NewEncoder(out)
|
||||||
encoder.SetIndent("", "\t")
|
if balance.JSONIndent {
|
||||||
|
encoder.SetIndent("", "\t")
|
||||||
|
}
|
||||||
err := encoder.Encode(d)
|
err := encoder.Encode(d)
|
||||||
return out.Bytes(), err
|
return out.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/lib/events"
|
"git.kirsle.net/apps/doodle/lib/events"
|
||||||
"git.kirsle.net/apps/doodle/lib/render"
|
"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/doodads"
|
||||||
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
||||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
"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?
|
// No level?
|
||||||
if s.Level == nil {
|
if s.Level == nil {
|
||||||
log.Debug("EditorScene.Setup: initializing a new Level")
|
log.Debug("EditorScene.Setup: initializing a new Level")
|
||||||
|
@ -96,7 +109,7 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
s.UI.Canvas.Scrollable = true
|
s.UI.Canvas.Scrollable = true
|
||||||
}
|
}
|
||||||
case enum.DoodadDrawing:
|
case enum.DoodadDrawing:
|
||||||
// No Doodad?
|
// Getting a doodad from file?
|
||||||
if s.filename != "" && s.OpenFile {
|
if s.filename != "" && s.OpenFile {
|
||||||
log.Debug("EditorScene.Setup: Loading doodad from filename at %s", s.filename)
|
log.Debug("EditorScene.Setup: Loading doodad from filename at %s", s.filename)
|
||||||
if err := s.LoadDoodad(s.filename); err != nil {
|
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?
|
// No Doodad?
|
||||||
if s.Doodad == nil {
|
if s.Doodad == nil {
|
||||||
log.Debug("EditorScene.Setup: initializing a new Doodad")
|
log.Debug("EditorScene.Setup: initializing a new Doodad")
|
||||||
|
|
|
@ -243,7 +243,7 @@ func (u *EditorUI) Loop(ev *events.State) error {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Statusbar filename label.
|
// Statusbar filename label.
|
||||||
filename := "untitled.map"
|
filename := "untitled.level"
|
||||||
fileType := "Level"
|
fileType := "Level"
|
||||||
if u.Scene.filename != "" {
|
if u.Scene.filename != "" {
|
||||||
filename = u.Scene.filename
|
filename = u.Scene.filename
|
||||||
|
|
|
@ -126,14 +126,33 @@ func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Fra
|
||||||
var buttonSize = (paletteWidth - window.BoxThickness(2)) / int32(perRow)
|
var buttonSize = (paletteWidth - window.BoxThickness(2)) / int32(perRow)
|
||||||
u.doodadButtonSize = buttonSize
|
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.
|
// Draw the doodad buttons in a grid `perRow` buttons wide.
|
||||||
var (
|
var (
|
||||||
row *ui.Frame
|
row *ui.Frame
|
||||||
rowCount int // for labeling the ui.Frame for each row
|
rowCount int // for labeling the ui.Frame for each row
|
||||||
btnRows = []*ui.Frame{} // Collect the row frames for the buttons.
|
btnRows = []*ui.Frame{} // Collect the row frames for the buttons.
|
||||||
)
|
)
|
||||||
for i, filename := range doodadsAvailable {
|
for i, doodad := range items {
|
||||||
filename := filename
|
doodad := doodad
|
||||||
|
|
||||||
if row == nil || i%perRow == 0 {
|
if row == nil || i%perRow == 0 {
|
||||||
rowCount++
|
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 := uix.NewCanvas(int(buttonSize), true)
|
||||||
can.Name = filename
|
can.Name = doodad.Title
|
||||||
can.SetBackground(render.White)
|
can.SetBackground(render.White)
|
||||||
can.LoadDoodad(doodad)
|
can.LoadDoodad(doodad)
|
||||||
|
|
||||||
btn := ui.NewButton(filename, can)
|
btn := ui.NewButton(doodad.Title, can)
|
||||||
btn.Resize(render.NewRect(
|
btn.Resize(render.NewRect(
|
||||||
buttonSize-2, // TODO: without the -2 the button border
|
buttonSize-2, // TODO: without the -2 the button border
|
||||||
buttonSize-2, // rests on top of the window border.
|
buttonSize-2, // rests on top of the window border.
|
||||||
|
|
|
@ -2,6 +2,7 @@ package level
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/lib/render"
|
"git.kirsle.net/apps/doodle/lib/render"
|
||||||
)
|
)
|
||||||
|
@ -44,6 +45,32 @@ func (s Swatch) String() string {
|
||||||
return s.Name
|
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
|
// IsSparse returns whether this Swatch is sparse (has only a palette index) and
|
||||||
// requires inflation.
|
// requires inflation.
|
||||||
func (s *Swatch) IsSparse() bool {
|
func (s *Swatch) IsSparse() bool {
|
||||||
|
|
|
@ -21,16 +21,16 @@ type Base struct {
|
||||||
GameVersion string `json:"gameVersion" msgpack:"1"` // Game version that created the level.
|
GameVersion string `json:"gameVersion" msgpack:"1"` // Game version that created the level.
|
||||||
Title string `json:"title" msgpack:"2"`
|
Title string `json:"title" msgpack:"2"`
|
||||||
Author string `json:"author" msgpack:"3"`
|
Author string `json:"author" msgpack:"3"`
|
||||||
|
Locked bool `json:"locked" msgpack:"4"`
|
||||||
|
|
||||||
// Every drawing type is able to embed other files inside of itself.
|
// 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.
|
// Level is the container format for Doodle map drawings.
|
||||||
type Level struct {
|
type Level struct {
|
||||||
Base
|
Base
|
||||||
Password string `json:"passwd" msgpack:"10"`
|
Password string `json:"passwd" msgpack:"10"`
|
||||||
Locked bool `json:"locked" msgpack:"11"`
|
|
||||||
|
|
||||||
// Chunked pixel data.
|
// Chunked pixel data.
|
||||||
Chunker *Chunker `json:"chunks" msgpack:"12"`
|
Chunker *Chunker `json:"chunks" msgpack:"12"`
|
||||||
|
|
|
@ -31,6 +31,11 @@ func (w *Canvas) InstallActors(actors level.ActorMap) error {
|
||||||
return nil
|
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
|
// SetScriptSupervisor assigns the Canvas scripting supervisor to enable
|
||||||
// interaction with actor scripts.
|
// interaction with actor scripts.
|
||||||
func (w *Canvas) SetScriptSupervisor(s *scripting.Supervisor) {
|
func (w *Canvas) SetScriptSupervisor(s *scripting.Supervisor) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user