package commands import ( "errors" "fmt" "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" ) // 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.BoolFlag{ Name: "quiet", 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", }, &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 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", 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", }, &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 { return cli.Exit( "Usage: doodad edit-level ", 1, ) } var filenames = c.Args().Slice() 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) // 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) 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 } 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 * ******************************/ if modified { if err := lvl.WriteFile(filename); err != nil { return cli.Exit(fmt.Sprintf("Write error: %s", err), 1) } } else { log.Warn("Note: No changes made to level") } if c.Bool("quiet") { return nil } 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 }