Noah Petherbridge
fd649b7ab1
* 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.
323 lines
7.8 KiB
Go
323 lines
7.8 KiB
Go
package doodle
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"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"
|
|
"git.kirsle.net/apps/doodle/pkg/level"
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
|
"git.kirsle.net/apps/doodle/pkg/userdir"
|
|
)
|
|
|
|
// EditorScene manages the "Edit Level" game mode.
|
|
type EditorScene struct {
|
|
// Configuration for the scene initializer.
|
|
DrawingType enum.DrawingType
|
|
OpenFile bool
|
|
Filename string
|
|
DoodadSize int
|
|
|
|
UI *EditorUI
|
|
d *Doodle
|
|
|
|
// The current level or doodad object being edited, based on the
|
|
// DrawingType.
|
|
Level *level.Level
|
|
Doodad *doodads.Doodad
|
|
|
|
// Custom debug overlay values.
|
|
debTool *string
|
|
debSwatch *string
|
|
debWorldIndex *string
|
|
|
|
// Last saved filename by the user.
|
|
filename string
|
|
}
|
|
|
|
// Name of the scene.
|
|
func (s *EditorScene) Name() string {
|
|
return "Edit"
|
|
}
|
|
|
|
// Setup the editor scene.
|
|
func (s *EditorScene) Setup(d *Doodle) error {
|
|
// Debug overlay values.
|
|
s.debTool = new(string)
|
|
s.debSwatch = new(string)
|
|
s.debWorldIndex = new(string)
|
|
customDebugLabels = []debugLabel{
|
|
{"Pixel:", s.debWorldIndex},
|
|
{"Tool:", s.debTool},
|
|
{"Swatch:", s.debSwatch},
|
|
}
|
|
|
|
// Initialize the user interface. It references the palette and such so it
|
|
// must be initialized after those things.
|
|
s.d = d
|
|
s.UI = NewEditorUI(d, s)
|
|
|
|
// Were we given configuration data?
|
|
if s.Filename != "" {
|
|
log.Debug("EditorScene.Setup: Set filename to %s", s.Filename)
|
|
s.filename = s.Filename
|
|
s.Filename = ""
|
|
}
|
|
|
|
// Loading a Level or a Doodad?
|
|
switch s.DrawingType {
|
|
case enum.LevelDrawing:
|
|
if s.Level != nil {
|
|
log.Debug("EditorScene.Setup: received level from scene caller")
|
|
s.UI.Canvas.LoadLevel(d.Engine, s.Level)
|
|
s.UI.Canvas.InstallActors(s.Level.Actors)
|
|
} else if s.filename != "" && s.OpenFile {
|
|
log.Debug("EditorScene.Setup: Loading map from filename at %s", s.filename)
|
|
if err := s.LoadLevel(s.filename); err != nil {
|
|
d.Flash("LoadLevel error: %s", err)
|
|
} else {
|
|
s.UI.Canvas.InstallActors(s.Level.Actors)
|
|
}
|
|
}
|
|
|
|
// 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")
|
|
s.Level = level.New()
|
|
s.Level.Palette = level.DefaultPalette()
|
|
s.UI.Canvas.LoadLevel(d.Engine, s.Level)
|
|
s.UI.Canvas.ScrollTo(render.Origin)
|
|
s.UI.Canvas.Scrollable = true
|
|
}
|
|
case enum.DoodadDrawing:
|
|
// 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 {
|
|
d.Flash("LoadDoodad error: %s", err)
|
|
}
|
|
}
|
|
|
|
// 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")
|
|
s.Doodad = doodads.New(s.DoodadSize)
|
|
s.UI.Canvas.LoadDoodad(s.Doodad)
|
|
}
|
|
|
|
// TODO: move inside the UI. Just an approximate position for now.
|
|
s.UI.Canvas.Resize(render.NewRect(int32(s.DoodadSize), int32(s.DoodadSize)))
|
|
s.UI.Canvas.ScrollTo(render.Origin)
|
|
s.UI.Canvas.Scrollable = false
|
|
s.UI.Workspace.Compute(d.Engine)
|
|
}
|
|
|
|
// Recompute the UI Palette window for the level's palette.
|
|
s.UI.FinishSetup(d)
|
|
|
|
d.Flash("Editor Mode. Press 'P' to play this map.")
|
|
|
|
return nil
|
|
}
|
|
|
|
// Playtest switches the level into Play Mode.
|
|
func (s *EditorScene) Playtest() {
|
|
log.Info("Play Mode, Go!")
|
|
s.d.Goto(&PlayScene{
|
|
Filename: s.filename,
|
|
Level: s.Level,
|
|
CanEdit: true,
|
|
})
|
|
}
|
|
|
|
// Loop the editor scene.
|
|
func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
|
// Update debug overlay values.
|
|
*s.debTool = s.UI.Canvas.Tool.String()
|
|
*s.debSwatch = s.UI.Canvas.Palette.ActiveSwatch.Name
|
|
*s.debWorldIndex = s.UI.Canvas.WorldIndexAt(s.UI.cursor).String()
|
|
|
|
// Has the window been resized?
|
|
if resized := ev.Resized.Read(); resized {
|
|
w, h := d.Engine.WindowSize()
|
|
if w != d.width || h != d.height {
|
|
// Not a false alarm.
|
|
d.width = w
|
|
d.height = h
|
|
s.UI.Resized(d)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Undo/Redo key bindings.
|
|
if ev.ControlActive.Now {
|
|
key := ev.KeyName.Read()
|
|
if key == "z" {
|
|
s.UI.Canvas.UndoStroke()
|
|
} else if key == "y" {
|
|
s.UI.Canvas.RedoStroke()
|
|
}
|
|
}
|
|
|
|
s.UI.Loop(ev)
|
|
|
|
// Switching to Play Mode?
|
|
switch ev.KeyName.Read() {
|
|
case "p":
|
|
s.Playtest()
|
|
case "l":
|
|
d.Flash("Line Tool selected.")
|
|
s.UI.Canvas.Tool = drawtool.LineTool
|
|
s.UI.activeTool = s.UI.Canvas.Tool.String()
|
|
case "f":
|
|
d.Flash("Pencil Tool selected.")
|
|
s.UI.Canvas.Tool = drawtool.PencilTool
|
|
s.UI.activeTool = s.UI.Canvas.Tool.String()
|
|
case "r":
|
|
d.Flash("Rectangle Tool selected.")
|
|
s.UI.Canvas.Tool = drawtool.RectTool
|
|
s.UI.activeTool = s.UI.Canvas.Tool.String()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Draw the current frame.
|
|
func (s *EditorScene) Draw(d *Doodle) error {
|
|
// Clear the canvas and fill it with magenta so it's clear if any spots are missed.
|
|
d.Engine.Clear(render.RGBA(160, 120, 160, 255))
|
|
|
|
s.UI.Present(d.Engine)
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadLevel loads a level from disk.
|
|
func (s *EditorScene) LoadLevel(filename string) error {
|
|
s.filename = filename
|
|
|
|
level, err := level.LoadFile(filename)
|
|
fmt.Printf("%+v\n", level)
|
|
if err != nil {
|
|
return fmt.Errorf("EditorScene.LoadLevel(%s): %s", filename, err)
|
|
}
|
|
|
|
s.DrawingType = enum.LevelDrawing
|
|
s.Level = level
|
|
s.UI.Canvas.LoadLevel(s.d.Engine, s.Level)
|
|
|
|
log.Info("Installing %d actors into the drawing", len(level.Actors))
|
|
if err := s.UI.Canvas.InstallActors(level.Actors); err != nil {
|
|
return fmt.Errorf("EditorScene.LoadLevel: InstallActors: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SaveLevel saves the level to disk.
|
|
// TODO: move this into the Canvas?
|
|
func (s *EditorScene) SaveLevel(filename string) error {
|
|
if s.DrawingType != enum.LevelDrawing {
|
|
return errors.New("SaveLevel: current drawing is not a Level type")
|
|
}
|
|
|
|
if !strings.HasSuffix(filename, enum.LevelExt) {
|
|
filename += enum.LevelExt
|
|
}
|
|
|
|
s.filename = filename
|
|
|
|
m := s.Level
|
|
if m.Title == "" {
|
|
m.Title = "Alpha"
|
|
}
|
|
if m.Author == "" {
|
|
m.Author = os.Getenv("USER")
|
|
}
|
|
|
|
m.Palette = s.UI.Canvas.Palette
|
|
m.Chunker = s.UI.Canvas.Chunker()
|
|
|
|
return m.WriteFile(filename)
|
|
}
|
|
|
|
// LoadDoodad loads a doodad from disk.
|
|
func (s *EditorScene) LoadDoodad(filename string) error {
|
|
s.filename = filename
|
|
|
|
doodad, err := doodads.LoadFile(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("EditorScene.LoadDoodad(%s): %s", filename, err)
|
|
}
|
|
|
|
s.DrawingType = enum.DoodadDrawing
|
|
s.Doodad = doodad
|
|
s.DoodadSize = doodad.Layers[0].Chunker.Size
|
|
s.UI.Canvas.LoadDoodad(s.Doodad)
|
|
return nil
|
|
}
|
|
|
|
// SaveDoodad saves the doodad to disk.
|
|
func (s *EditorScene) SaveDoodad(filename string) error {
|
|
if s.DrawingType != enum.DoodadDrawing {
|
|
return errors.New("SaveDoodad: current drawing is not a Doodad type")
|
|
}
|
|
|
|
if !strings.HasSuffix(filename, enum.DoodadExt) {
|
|
filename += enum.DoodadExt
|
|
}
|
|
|
|
s.filename = filename
|
|
d := s.Doodad
|
|
if d.Title == "" {
|
|
d.Title = "Untitled Doodad"
|
|
}
|
|
if d.Author == "" {
|
|
d.Author = os.Getenv("USER")
|
|
}
|
|
|
|
// TODO: is this copying necessary?
|
|
d.Palette = s.UI.Canvas.Palette
|
|
d.Layers[0].Chunker = s.UI.Canvas.Chunker()
|
|
|
|
// Save it to their profile directory.
|
|
filename = userdir.DoodadPath(filename)
|
|
log.Info("Write Doodad: %s", filename)
|
|
return d.WriteFile(filename)
|
|
}
|
|
|
|
// Destroy the scene.
|
|
func (s *EditorScene) Destroy() error {
|
|
return nil
|
|
}
|