diff --git a/cmd/doodle/main.go b/cmd/doodle/main.go index 40bf9ff..d256efd 100644 --- a/cmd/doodle/main.go +++ b/cmd/doodle/main.go @@ -125,7 +125,7 @@ func main() { } // Enable feature flags? - if c.Bool("experimental") { + if c.Bool("experimental") || usercfg.Current.EnableFeatures { balance.FeaturesOn() } diff --git a/pkg/balance/feature_flags.go b/pkg/balance/feature_flags.go index e7f5ad0..e00fc5b 100644 --- a/pkg/balance/feature_flags.go +++ b/pkg/balance/feature_flags.go @@ -2,10 +2,17 @@ package balance // Feature Flags to turn on/off experimental content. var Feature = feature{ - Zoom: false, // enable the zoom in/out feature (very buggy rn) - CustomWallpaper: true, // attach custom wallpaper img to levels + ///////// + // Experimental features that are off by default + Zoom: false, // enable the zoom in/out feature (very buggy rn) ChangePalette: false, // reset your palette after level creation to a diff preset + ///////// + // Fully activated features + + // Attach custom wallpaper img to levels + CustomWallpaper: true, + // Allow embedded doodads in levels. EmbeddableDoodads: true, } @@ -13,13 +20,12 @@ var Feature = feature{ // FeaturesOn turns on all feature flags, from CLI --experimental option. func FeaturesOn() { Feature.Zoom = true - Feature.CustomWallpaper = true Feature.ChangePalette = true } type feature struct { - Zoom bool - CustomWallpaper bool - ChangePalette bool + Zoom bool + CustomWallpaper bool + ChangePalette bool EmbeddableDoodads bool } diff --git a/pkg/doodle.go b/pkg/doodle.go index 9e0e816..3219bd1 100644 --- a/pkg/doodle.go +++ b/pkg/doodle.go @@ -239,6 +239,7 @@ func (d *Doodle) MakeSettingsWindow(supervisor *ui.Supervisor) *ui.Window { DebugOverlay: &DebugOverlay, DebugCollision: &DebugCollision, HorizontalToolbars: &usercfg.Current.HorizontalToolbars, + EnableFeatures: &usercfg.Current.EnableFeatures, } return windows.MakeSettingsWindow(d.width, d.height, cfg) } diff --git a/pkg/editor_scene.go b/pkg/editor_scene.go index 480de4f..2c7407e 100644 --- a/pkg/editor_scene.go +++ b/pkg/editor_scene.go @@ -77,6 +77,23 @@ func (s *EditorScene) Setup(d *Doodle) error { return nil } +// Reset the editor scene from scratch. Good nuclear option when you change the level's +// palette on-the-fly or some other sticky situation and want to reload the editor. +func (s *EditorScene) Reset() { + if s.Level != nil { + s.Level.Chunker.Redraw() + } + if s.Doodad != nil { + s.Doodad.Layers[s.ActiveLayer].Chunker.Redraw() + } + + s.d.Goto(&EditorScene{ + Filename: s.Filename, + Level: s.Level, + Doodad: s.Doodad, + }) +} + // setupAsync initializes trhe editor scene in the background, // underneath a loading screen. func (s *EditorScene) setupAsync(d *Doodle) error { diff --git a/pkg/editor_ui.go b/pkg/editor_ui.go index ca2fcc3..ca5e656 100644 --- a/pkg/editor_ui.go +++ b/pkg/editor_ui.go @@ -347,6 +347,13 @@ func (u *EditorUI) Present(e render.Engine) { if u.Supervisor.IsDragging() { if actor := u.DraggableActor; actor != nil { var size = actor.canvas.Size() + + // Scale the actor up or down by the zoom level of the Editor Canvas. + // TODO: didn't seem to make a difference + // size.W = u.Canvas.ZoomMultiply(size.W) + // size.H = u.Canvas.ZoomMultiply(size.H) + // actor.canvas.Zoom = u.Canvas.Zoom + actor.canvas.Present(u.d.Engine, render.NewPoint( u.cursor.X-(size.W/2), u.cursor.Y-(size.H/2), @@ -415,7 +422,7 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas { // NOTE: The drag event begins at editor_ui_doodad.go when configuring the // Doodad Palette buttons. drawing.Handle(ui.Drop, func(ed ui.EventData) error { - log.Info("Drawing canvas has received a drop!") + // Editor Canvas's position relative to the window. var P = ui.AbsolutePosition(drawing) // Was it an actor from the Doodad Palette? @@ -446,6 +453,10 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas { } ) + // Adjust the level position per the zoom factor. + position.X = drawing.ZoomDivide(position.X) + position.Y = drawing.ZoomDivide(position.Y) + // Was it an already existing actor to re-add to the map? if actor.actor != nil { actor.actor.Point = position diff --git a/pkg/editor_ui_popups.go b/pkg/editor_ui_popups.go index 87972bc..82cf78e 100644 --- a/pkg/editor_ui_popups.go +++ b/pkg/editor_ui_popups.go @@ -133,6 +133,10 @@ func (u *EditorUI) SetupPopups(d *Doodle) { scene.Level.Wallpaper = wallpaper u.Canvas.LoadLevel(scene.Level) }, + OnReload: func() { + log.Warn("RELOAD LEVEL") + scene.Reset() + }, OnCancel: func() { u.levelSettingsWindow.Hide() }, diff --git a/pkg/level/fmt_binary.go b/pkg/level/fmt_binary.go index d4293a2..d226b17 100644 --- a/pkg/level/fmt_binary.go +++ b/pkg/level/fmt_binary.go @@ -62,10 +62,6 @@ func LoadBinary(filename string) (*Level, error) { } // Inflate the chunk metadata to map the pixels to their palette indexes. - m.Chunker.Inflate(m.Palette) - m.Actors.Inflate() - - // Inflate the private instance values. - m.Palette.Inflate() + m.Inflate() return m, err } diff --git a/pkg/level/fmt_json.go b/pkg/level/fmt_json.go index 2474a0b..ca64118 100644 --- a/pkg/level/fmt_json.go +++ b/pkg/level/fmt_json.go @@ -42,11 +42,7 @@ func FromJSON(filename string, data []byte) (*Level, error) { } // Inflate the chunk metadata to map the pixels to their palette indexes. - m.Chunker.Inflate(m.Palette) - m.Actors.Inflate() - - // Inflate the private instance values. - m.Palette.Inflate() + m.Inflate() return m, nil } diff --git a/pkg/level/inflate.go b/pkg/level/inflate.go new file mode 100644 index 0000000..7f60aed --- /dev/null +++ b/pkg/level/inflate.go @@ -0,0 +1,24 @@ +package level + +/* +Inflate the level from its compressed JSON form. + +This is called as part of the LoadJSON function when the level is read +from disk. In the JSON format, pixels in the level refer to the palette +by its index number. + +This function calls the following: + +* Chunker.Inflate(Palette) to update references to the level's pixels to point + to the Swatch entry. +* Actors.Inflate() +* Palette.Inflate() to load private instance values for the palette subsystem. +*/ +func (l *Level) Inflate() { + // Inflate the chunk metadata to map the pixels to their palette indexes. + l.Chunker.Inflate(l.Palette) + l.Actors.Inflate() + + // Inflate the private instance values. + l.Palette.Inflate() +} diff --git a/pkg/level/palette.go b/pkg/level/palette.go index 2dc29c6..6dc8b9a 100644 --- a/pkg/level/palette.go +++ b/pkg/level/palette.go @@ -134,3 +134,28 @@ func (p *Palette) update() { } } } + +// ReplacePalette installs a new palette into your level. +// Your existing level colors, by index, are replaced by the incoming +// palette. If the new palette is smaller, extraneous indices are +// left alone. +func (l *Level) ReplacePalette(pal *Palette) { + for i, swatch := range pal.Swatches { + if i >= len(l.Palette.Swatches) { + l.Palette.Swatches = append(l.Palette.Swatches, swatch) + continue + } + + // Ugly code, but can't just replace the swatch + // wholesale -- the inflated level data means existing + // pixels already have refs to their Swatch and they + // will keep those refs until you fully save and exit + // out of the editor. + l.Palette.Swatches[i].Name = swatch.Name + l.Palette.Swatches[i].Color = swatch.Color + l.Palette.Swatches[i].Pattern = swatch.Pattern + l.Palette.Swatches[i].Solid = swatch.Solid + l.Palette.Swatches[i].Fire = swatch.Fire + l.Palette.Swatches[i].Water = swatch.Water + } +} diff --git a/pkg/uix/canvas_actors.go b/pkg/uix/canvas_actors.go index 8d99216..32ad18a 100644 --- a/pkg/uix/canvas_actors.go +++ b/pkg/uix/canvas_actors.go @@ -116,8 +116,18 @@ func (w *Canvas) drawActors(e render.Engine, p render.Point) { can = a.Canvas // Canvas widget that draws the actor actorPoint = a.Position() actorSize = a.Size() + resizeTo = actorSize ) + // Adjust actor position and size by the zoom level. + actorPoint.X = w.ZoomMultiply(actorPoint.X) + actorPoint.Y = w.ZoomMultiply(actorPoint.Y) + resizeTo.W = w.ZoomMultiply(resizeTo.W) + resizeTo.H = w.ZoomMultiply(resizeTo.H) + + // Tell the actor's canvas to copy our zoom level so it resizes its image too. + can.Zoom = w.Zoom + // Create a box of World Coordinates that this actor occupies. The // Actor X,Y from level data is already a World Coordinate; // accomodate for the size of the Actor. @@ -137,7 +147,6 @@ func (w *Canvas) drawActors(e render.Engine, p render.Point) { X: p.X + w.Scroll.X + actorPoint.X + w.BoxThickness(1), Y: p.Y + w.Scroll.Y + actorPoint.Y + w.BoxThickness(1), } - resizeTo := actorSize // XXX TODO: when an Actor hits the left or top edge and shrinks, // scrolling to offset that shrink is currently hard to solve. diff --git a/pkg/usercfg/usercfg.go b/pkg/usercfg/usercfg.go index cccd3ef..802da9b 100644 --- a/pkg/usercfg/usercfg.go +++ b/pkg/usercfg/usercfg.go @@ -29,6 +29,7 @@ type Settings struct { // Configurable settings (pkg/windows/settings.go) HorizontalToolbars bool `json:",omitempty"` + EnableFeatures bool `json:",omitempty"` // Secret boolprops from balance/boolprops.go ShowHiddenDoodads bool `json:",omitempty"` diff --git a/pkg/windows/add_edit_level.go b/pkg/windows/add_edit_level.go index 67f27a3..fb60cc3 100644 --- a/pkg/windows/add_edit_level.go +++ b/pkg/windows/add_edit_level.go @@ -22,6 +22,7 @@ type AddEditLevel struct { // Callback functions. OnChangePageTypeAndWallpaper func(pageType level.PageType, wallpaper string) OnCreateNewLevel func(*level.Level) + OnReload func() OnCancel func() } @@ -411,7 +412,11 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window { "if the new palette is smaller, some pixels may be\n" + "lost from your level. OK to continue?", ).WithTitle("Change Level Palette").Then(func() { - config.OnCancel() + // Install the new level palette. + config.EditLevel.ReplacePalette(level.DefaultPalettes[paletteName]) + if config.OnReload != nil { + config.OnReload() + } }) return nil } diff --git a/pkg/windows/settings.go b/pkg/windows/settings.go index 7a7d076..91c7768 100644 --- a/pkg/windows/settings.go +++ b/pkg/windows/settings.go @@ -23,6 +23,7 @@ type Settings struct { DebugOverlay *bool DebugCollision *bool HorizontalToolbars *bool + EnableFeatures *bool // Configuration options. SceneName string // name of scene which called this window @@ -71,13 +72,10 @@ func NewSettingsWindow(cfg Settings) *ui.Window { FillX: true, }) - /////////// - // Options (index) Tab + // Make the tabs cfg.makeOptionsTab(tabFrame, Width, Height) - - /////////// - // Controls Tab cfg.makeControlsTab(tabFrame, Width, Height) + cfg.makeExperimentalTab(tabFrame, Width, Height) tabFrame.Supervise(cfg.Supervisor) @@ -455,3 +453,129 @@ func (c Settings) makeControlsTab(tabFrame *ui.TabFrame, Width, Height int) *ui. return frame } + +// Settings Window "Experimental" Tab +func (c Settings) makeExperimentalTab(tabFrame *ui.TabFrame, Width, Height int) *ui.Frame { + tab := tabFrame.AddTab("Experimental", ui.NewLabel(ui.Label{ + Text: "Experimental", + Font: balance.TabFont, + })) + tab.Resize(render.NewRect(Width-4, Height-tab.Size().H-46)) + + // Common click handler for all settings, + // so we can write the updated info to disk. + onClick := func(ed ui.EventData) error { + saveGameSettings() + return nil + } + + rows := []struct { + Header string + Text string + Boolean *bool + TextVariable *string + PadY int + PadX int + name string // for special cases + }{ + { + Header: "Enable Experimental Features", + }, + { + Text: "The setting below can enable experimental features in this\n" + + "game. These are features which are still in development and\n" + + "may have unstable or buggy behavior.", + PadY: 2, + }, + { + Header: "Zoom In/Out", + }, + { + Text: "This adds Zoom options to the level editor. It has a few\n" + + "bugs around scrolling but may be useful already.", + PadY: 2, + }, + { + Header: "Replace Level Palette", + }, + { + Text: "This adds an option to the Level Properties dialog to\n" + + "replace your level palette with one of the defaults,\n" + + "like on the New Level screen. It might not actually work.", + PadY: 2, + }, + { + Boolean: c.EnableFeatures, + Text: "Enable experimental features", + PadX: 4, + }, + { + Text: "Restart the game for changes to take effect.", + PadY: 2, + }, + } + for _, row := range rows { + row := row + frame := ui.NewFrame("Frame") + tab.Pack(frame, ui.Pack{ + Side: ui.N, + FillX: true, + PadY: row.PadY, + }) + + // Headers get their own row to themselves. + if row.Header != "" { + label := ui.NewLabel(ui.Label{ + Text: row.Header, + Font: balance.LabelFont, + }) + frame.Pack(label, ui.Pack{ + Side: ui.W, + PadX: row.PadX, + }) + continue + } + + // Checkboxes get their own row. + if row.Boolean != nil { + cb := ui.NewCheckbox(row.Text, row.Boolean, ui.NewLabel(ui.Label{ + Text: row.Text, + Font: balance.UIFont, + })) + cb.Handle(ui.Click, onClick) + cb.Supervise(c.Supervisor) + + // Add warning to the toolbars option if the EditMode is currently active. + if row.name == "toolbars" && c.SceneName == "Edit" { + ui.NewTooltip(cb, ui.Tooltip{ + Text: "Note: reload your level after changing this option.\n" + + "Playtesting and returning will do.", + Edge: ui.Top, + }) + } + + frame.Pack(cb, ui.Pack{ + Side: ui.W, + PadX: row.PadX, + }) + continue + } + + // Any leftover Text gets packed to the left. + if row.Text != "" { + tf := ui.NewFrame("TextFrame") + label := ui.NewLabel(ui.Label{ + Text: row.Text, + Font: balance.UIFont, + }) + tf.Pack(label, ui.Pack{ + Side: ui.W, + }) + frame.Pack(tf, ui.Pack{ + Side: ui.W, + }) + } + } + + return tab +}