From 647124495b76b9832c68afdb6db6bc69296735a4 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sun, 6 Mar 2022 22:16:09 -0800 Subject: [PATCH] Level Difficulty + UI Polish Added a new level property: Difficulty * An enum ranging from -1, 0, 1 (Peaceful, Normal, Hard) * Default difficulty is Normal; pre-existing levels are Normal by default per the zero value. Doodad scripts can read the difficulty via the new global variable `Level.Difficulty` and some doodads have been updated: * Azulians: on Peaceful they ignore all player characters, and on Hard they are in "hunt mode": infinite aggro radius and they're aggressive to all characters. * Bird: on Peaceful they will not dive and attack any player character. Other spit and polish: * New Level/Level Properties UI reworked into a magicform. * New "PromptPre(question, answer, func)" function for prompting the user with the developer shell, but pre-filling in an answer for them to either post or edit. * magicform has a PromptUser field option for simple Text/Int fields which present as buttons, so magicform can prompt and update the variable itself. * Don't show the _autosave.doodad in the Doodad Dropper window. --- dev-assets/doodads/azulian/azulian.js | 16 +- dev-assets/doodads/bird/bird.js | 31 +- pkg/doodle.go | 1 + pkg/enum/enum.go | 18 + pkg/level/types.go | 4 +- pkg/native/engine_sdl.go | 2 - pkg/shell.go | 8 + pkg/shmem/globals.go | 3 +- pkg/uix/canvas_editable.go | 5 + pkg/uix/canvas_strokes.go | 4 + pkg/uix/magic-form/magic_form.go | 35 +- pkg/uix/scripting.go | 4 + pkg/windows/add_edit_level.go | 676 ++++++++++---------------- pkg/windows/doodad_dropper.go | 4 + pkg/windows/text_tool.go | 2 +- 15 files changed, 382 insertions(+), 431 deletions(-) diff --git a/dev-assets/doodads/azulian/azulian.js b/dev-assets/doodads/azulian/azulian.js index df3feab..5b580ce 100644 --- a/dev-assets/doodads/azulian/azulian.js +++ b/dev-assets/doodads/azulian/azulian.js @@ -18,8 +18,8 @@ if (color === 'white') { } function setupAnimations(color) { - let left = color === 'blue' ? 'blu-wl' : color+'-wl', - right = color === 'blue' ? 'blu-wr' : color+'-wr', + let left = color === 'blue' ? 'blu-wl' : color + '-wl', + right = color === 'blue' ? 'blu-wr' : color + '-wr', leftFrames = [left + '1', left + '2', left + '3', left + '4'], rightFrames = [right + '1', right + '2', right + '3', right + '4']; @@ -71,7 +71,8 @@ function main() { myPt = Self.Position(); // If the player is within aggro range, move towards. - if (Math.abs(playerPt.X - myPt.X) < aggroX && Math.abs(playerPt.Y - myPt.Y) < aggroY) { + if ((Math.abs(playerPt.X - myPt.X) < aggroX && Math.abs(playerPt.Y - myPt.Y) < aggroY) + || (Level.Difficulty > 0)) { direction = playerPt.X < myPt.X ? "left" : "right"; followPlayer = true; @@ -134,11 +135,16 @@ function playerControls() { // will be hostile towards the player). Boring players will not be chased after and // the Azulian will not harm them if they make contact. function isPlayerFood(actor) { - // Not a player or is invulnerable. - if (!actor.IsPlayer() || actor.Invulnerable()) { + // Not a player or is invulnerable, or Peaceful difficulty. + if (!actor.IsPlayer() || actor.Invulnerable() || Level.Difficulty < 0) { return false; } + // On hard mode they are hostile to any player. + if (Level.Difficulty > 0) { + return true; + } + // Azulians are friendly to Thieves and other Azulians. if (actor.Doodad().Filename === "thief.doodad" || actor.Doodad().Title.indexOf("Azulian") > -1) { return false; diff --git a/dev-assets/doodads/bird/bird.js b/dev-assets/doodads/bird/bird.js index 7fdb035..00de17d 100644 --- a/dev-assets/doodads/bird/bird.js +++ b/dev-assets/doodads/bird/bird.js @@ -1,8 +1,8 @@ // Bird let speed = 4, - Vx = Vy = 0, - altitude = Self.Position().Y; // original height in the level + Vx = Vy = 0, + altitude = Self.Position().Y; // original height in the level let direction = "left", lastDirection = "left"; @@ -72,7 +72,7 @@ function main() { // Scan for the player character and dive. try { AI_ScanForPlayer() - } catch(e) { + } catch (e) { console.error("Error in AI_ScanForPlayer: %s", e); } } @@ -84,7 +84,7 @@ function main() { // If diving, exit - don't edit animation. if (state === states.diving) { - Self.ShowLayerNamed("dive-"+direction); + Self.ShowLayerNamed("dive-" + direction); lastDirection = direction; return; } @@ -109,6 +109,11 @@ function main() { // It's not hostile towards characters that can fly (having // no gravity). function AI_ScanForPlayer() { + // If Peaceful difficulty, do not attack. + if (Level.Difficulty < 0) { + return + } + let stepY = 12, // number of pixels to skip stepX = stepY, limit = stepX * 20, // furthest we'll scan @@ -153,14 +158,14 @@ function player() { // they aren't seen to be moving downwards, cancel the dive. let lastPoint = Self.Position(); setInterval(() => { - let nowAt = Self.Position(); - if (nowAt.Y > lastPoint.Y) { - falling = true; - } else { - falling = false; - } - lastPoint = nowAt; - }, 100); + let nowAt = Self.Position(); + if (nowAt.Y > lastPoint.Y) { + falling = true; + } else { + falling = false; + } + lastPoint = nowAt; + }, 100); Events.OnKeypress((ev) => { Vx = 0; @@ -198,7 +203,7 @@ function player() { } else { // Hover in place. if (!Self.IsAnimating()) { - Self.PlayAnimation("fly-"+direction); + Self.PlayAnimation("fly-" + direction); } diving = false; } diff --git a/pkg/doodle.go b/pkg/doodle.go index 86e0d5f..3b7ec3a 100644 --- a/pkg/doodle.go +++ b/pkg/doodle.go @@ -73,6 +73,7 @@ func New(debug bool, engine render.Engine) *Doodle { shmem.Flash = d.Flash shmem.FlashError = d.FlashError shmem.Prompt = d.Prompt + shmem.PromptPre = d.PromptPre if debug { log.Logger.Config.Level = golog.DebugLevel diff --git a/pkg/enum/enum.go b/pkg/enum/enum.go index 6b304c7..ece7391 100644 --- a/pkg/enum/enum.go +++ b/pkg/enum/enum.go @@ -25,3 +25,21 @@ const ( ScreenWidthMedium = 800 ScreenWidthLarge = 1000 ) + +type Difficulty int + +const ( + // The zero value is the default (Normal) so is b/w compatible with + // level files pre-difficulty setting. + Peaceful Difficulty = iota - 1 + Normal + Hard +) + +func (d Difficulty) String() string { + return []string{ + "Normal", + "Hard", + "Peaceful", + }[d] +} diff --git a/pkg/level/types.go b/pkg/level/types.go index d682c8c..ac95324 100644 --- a/pkg/level/types.go +++ b/pkg/level/types.go @@ -6,6 +6,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/drawtool" + "git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/go/render" ) @@ -30,7 +31,8 @@ type Base struct { // Level is the container format for Doodle map drawings. type Level struct { Base - Password string `json:"passwd"` + Password string `json:"passwd"` + Difficulty enum.Difficulty `json:"difficulty"` // Chunked pixel data. Chunker *Chunker `json:"chunks"` diff --git a/pkg/native/engine_sdl.go b/pkg/native/engine_sdl.go index b3adbbc..6ceb8c0 100644 --- a/pkg/native/engine_sdl.go +++ b/pkg/native/engine_sdl.go @@ -5,7 +5,6 @@ package native import ( "image" - "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/go/render" "git.kirsle.net/go/render/sdl" sdl2 "github.com/veandco/go-sdl2/sdl" @@ -46,7 +45,6 @@ func TextToImage(e render.Engine, text render.Text) (image.Image, error) { return nil, err } defer surface.Free() - log.Error("surf fmt: %+v", surface.Format) // Convert the Surface into a pixelformat that supports the .At(x,y) // function properly, as the one we got above is "Not implemented" diff --git a/pkg/shell.go b/pkg/shell.go index 416bf6f..bbab80b 100644 --- a/pkg/shell.go +++ b/pkg/shell.go @@ -36,6 +36,14 @@ func (d *Doodle) Prompt(question string, callback func(string)) { d.shell.Open = true } +// PromptPre prompts with a pre-filled value. +func (d *Doodle) PromptPre(question string, prefilled string, callback func(string)) { + d.shell.Text = prefilled + d.shell.Prompt = question + d.shell.callback = callback + d.shell.Open = true +} + // Shell implements the developer console in-game. type Shell struct { parent *Doodle diff --git a/pkg/shmem/globals.go b/pkg/shmem/globals.go index e5611b3..324aa2a 100644 --- a/pkg/shmem/globals.go +++ b/pkg/shmem/globals.go @@ -27,7 +27,8 @@ var ( FlashError func(string, ...interface{}) // Globally available Prompt() function. - Prompt func(string, func(string)) + Prompt func(string, func(string)) + PromptPre func(string, string, func(string)) // Ajax file cache for WASM use. AjaxCache map[string][]byte diff --git a/pkg/uix/canvas_editable.go b/pkg/uix/canvas_editable.go index 4004f33..9215894 100644 --- a/pkg/uix/canvas_editable.go +++ b/pkg/uix/canvas_editable.go @@ -1,6 +1,7 @@ package uix import ( + "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/keybind" "git.kirsle.net/apps/doodle/pkg/level" @@ -109,6 +110,10 @@ func (w *Canvas) commitStroke(tool drawtool.Tool, addHistory bool) { if w.level != nil && addHistory { w.level.UndoHistory.AddStroke(w.currentStroke) } else if w.doodad != nil && addHistory { + if w.doodad.UndoHistory == nil { + // HACK: if UndoHistory was not initialized properly. + w.doodad.UndoHistory = drawtool.NewHistory(balance.UndoHistory) + } w.doodad.UndoHistory.AddStroke(w.currentStroke) } diff --git a/pkg/uix/canvas_strokes.go b/pkg/uix/canvas_strokes.go index c6a3a25..e47bedf 100644 --- a/pkg/uix/canvas_strokes.go +++ b/pkg/uix/canvas_strokes.go @@ -46,6 +46,10 @@ func (w *Canvas) UndoStroke() bool { if w.level != nil { undoer = w.level.UndoHistory } else if w.doodad != nil { + if w.doodad.UndoHistory == nil { + // HACK: if UndoHistory was not initialized properly. + w.doodad.UndoHistory = drawtool.NewHistory(balance.UndoHistory) + } undoer = w.doodad.UndoHistory } else { log.Error("Canvas.UndoStroke: no Level or Doodad currently available to the canvas") diff --git a/pkg/uix/magic-form/magic_form.go b/pkg/uix/magic-form/magic_form.go index 2aced85..710699a 100644 --- a/pkg/uix/magic-form/magic_form.go +++ b/pkg/uix/magic-form/magic_form.go @@ -70,6 +70,10 @@ type Field struct { SelectValue interface{} // Selectbox default choice Color *render.Color // Color + // For text-type fields, opt-in to let magicform prompt the + // user using the game's developer shell. + PromptUser func(answer string) + // Tooltip to add to a form control. // Checkbox only for now. Tooltip ui.Tooltip // config for the tooltip only @@ -81,8 +85,9 @@ type Field struct { // Option used in Selectbox or Radiobox fields. type Option struct { - Value interface{} - Label string + Value interface{} + Label string + Separator bool } /* @@ -278,6 +283,22 @@ func (form Form) Create(into *ui.Frame, fields []Field) { // Handlers btn.Handle(ui.Click, func(ed ui.EventData) error { + // Text boxes, we want to prompt the user to enter new value? + if row.PromptUser != nil { + var value string + if row.TextVariable != nil { + value = *row.TextVariable + } else if row.IntVariable != nil { + value = fmt.Sprintf("%d", *row.IntVariable) + } + + shmem.PromptPre("Enter new value: ", value, func(answer string) { + if answer != "" { + row.PromptUser(answer) + } + }) + } + if row.OnClick != nil { row.OnClick() } @@ -325,6 +346,10 @@ func (form Form) Create(into *ui.Frame, fields []Field) { if row.Options != nil { for _, option := range row.Options { + if option.Separator { + btn.AddSeparator() + continue + } btn.AddItem(option.Label, option.Value, func() {}) } } @@ -342,6 +367,12 @@ func (form Form) Create(into *ui.Frame, fields []Field) { return nil }) + // Tooltip? TODO - make nicer. + if row.Tooltip.Text != "" || row.Tooltip.TextVariable != nil { + tt := ui.NewTooltip(btn, row.Tooltip) + tt.Supervise(form.Supervisor) + } + btn.Supervise(form.Supervisor) form.Supervisor.Add(btn) } diff --git a/pkg/uix/scripting.go b/pkg/uix/scripting.go index d9fef1a..3223e43 100644 --- a/pkg/uix/scripting.go +++ b/pkg/uix/scripting.go @@ -64,6 +64,10 @@ func (w *Canvas) MakeScriptAPI(vm *scripting.VM) { } }, }) + + vm.Set("Level", map[string]interface{}{ + "Difficulty": w.level.Difficulty, + }) } // MakeSelfAPI generates the `Self` object for the scripting API in diff --git a/pkg/windows/add_edit_level.go b/pkg/windows/add_edit_level.go index bbb7fbd..9317f78 100644 --- a/pkg/windows/add_edit_level.go +++ b/pkg/windows/add_edit_level.go @@ -4,10 +4,13 @@ import ( "strconv" "git.kirsle.net/apps/doodle/pkg/balance" + "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/modal" "git.kirsle.net/apps/doodle/pkg/native" "git.kirsle.net/apps/doodle/pkg/shmem" + magicform "git.kirsle.net/apps/doodle/pkg/uix/magic-form" "git.kirsle.net/apps/doodle/pkg/wallpaper" "git.kirsle.net/go/render" "git.kirsle.net/go/ui" @@ -90,7 +93,7 @@ func (config AddEditLevel) setupLevelFrame(tf *ui.TabFrame) { ) // Given a level to edit? - if config.EditLevel != nil { + if !isNewLevel { newPageType = config.EditLevel.PageType.String() newWallpaper = config.EditLevel.Wallpaper paletteName = textCurrentPalette @@ -105,244 +108,135 @@ func (config AddEditLevel) setupLevelFrame(tf *ui.TabFrame) { * Frame for selecting Page Type ******************/ - typeFrame := ui.NewFrame("Page Type Options Frame") - frame.Pack(typeFrame, ui.Pack{ - Side: ui.N, - FillX: true, - }) - - label1 := ui.NewLabel(ui.Label{ - Text: "Page Type:", - Font: balance.LabelFont, - }) - typeFrame.Pack(label1, ui.Pack{ - Side: ui.W, - }) - - type typeObj struct { - Name string - Value level.PageType - } - var types = []typeObj{ - {"Bounded", level.Bounded}, - {"Unbounded", level.Unbounded}, - {"No Negative Space", level.NoNegativeSpace}, - // {"Bordered (TODO)", level.Bordered}, + // Selected "Page Type" property. + var pageType = level.Bounded + if !isNewLevel { + pageType = config.EditLevel.PageType } - typeBtn := ui.NewSelectBox("Type Select", ui.Label{ - Font: ui.MenuFont, - }) - typeFrame.Pack(typeBtn, ui.Pack{ - Side: ui.W, - Expand: true, - }) - - for _, t := range types { - // TODO: Hide some options for the free version of the game. - // - At launch only Bounded and Bordered will be available - // in the shareware version. - // - For now, only hide Bordered as it's not yet implemented. - // -------- - // if balance.FreeVersion { - // if t.Value == level.Bordered { - // continue - // } - // } - typeBtn.AddItem(t.Name, t.Value, func() {}) + form := magicform.Form{ + Supervisor: config.Supervisor, + Engine: config.Engine, + Vertical: true, + LabelWidth: 120, + PadY: 2, } - - // If editing an existing level, pre-select the right page type. - if config.EditLevel != nil { - typeBtn.SetValue(config.EditLevel.PageType) + fields := []magicform.Field{ + { + Label: "Page type:", + Font: balance.UIFont, + Options: []magicform.Option{ + { + Label: "Bounded", + Value: level.Bounded, + }, + { + Label: "Bounded", + Value: level.Bounded, + }, + { + Label: "Unbounded", + Value: level.Unbounded, + }, + { + Label: "No Negative Space", + Value: level.NoNegativeSpace, + }, + }, + SelectValue: pageType, + OnSelect: func(v interface{}) { + value, _ := v.(level.PageType) + newPageType = value.String() // for the "New" screen background + config.OnChangePageTypeAndWallpaper(value, newWallpaper) + }, + }, } - typeBtn.Handle(ui.Change, func(ed ui.EventData) error { - if selection, ok := typeBtn.GetValue(); ok { - if pageType, ok := selection.Value.(level.PageType); ok { - newPageType = pageType.String() - config.OnChangePageTypeAndWallpaper(pageType, newWallpaper) - } - } - return nil - }) - - typeBtn.Supervise(config.Supervisor) - config.Supervisor.Add(typeBtn) - /****************** - * Frame for selecting Bounded Level Limits. + * Wallpaper settings ******************/ + var selectedWallpaper = "notebook.png" if config.EditLevel != nil { - boundsFrame := ui.NewFrame("Bounds Frame") - frame.Pack(boundsFrame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 2, - }) + selectedWallpaper = config.EditLevel.Wallpaper + } - label := ui.NewLabel(ui.Label{ - Text: "Bounded limits:", - Font: balance.LabelFont, - }) - boundsFrame.Pack(label, ui.Pack{ - Side: ui.W, - PadY: 2, - }) - - var forms = []struct { - label string - number *int64 - }{ - { - label: "Width:", - number: &config.EditLevel.MaxWidth, + fields = append(fields, []magicform.Field{ + { + Label: "Wallpaper:", + Font: balance.UIFont, + SelectValue: selectedWallpaper, + Options: []magicform.Option{ + { + Label: "Notebook", + Value: "notebook.png", + }, + { + Label: "Legal Pad", + Value: "legal.png", + }, + { + Label: "Graph paper", + Value: "graph.png", + }, + { + Label: "Dotted paper", + Value: "dots.png", + }, + { + Label: "Blueprint", + Value: "blueprint.png", + }, + { + Label: "Pure white", + Value: "white.png", + }, + { + Separator: true, + }, + { + Label: "Custom wallpaper...", + Value: balance.CustomWallpaperFilename, + }, }, - { - label: "Height:", - number: &config.EditLevel.MaxHeight, - }, - } - for _, form := range forms { - form := form - label := ui.NewLabel(ui.Label{ - Text: form.label, - Font: ui.MenuFont, - }) + OnSelect: func(v interface{}) { + if filename, ok := v.(string); ok { + // Picking the Custom option? + if filename == balance.CustomWallpaperFilename { + filename, err := native.OpenFile("Choose a custom wallpaper:", "*.png *.jpg *.gif") + if err == nil { + b64data, err := wallpaper.FileToB64(filename) + if err != nil { + shmem.Flash("Error loading wallpaper: %s", err) + return + } - var intvar = int(*form.number) - button := ui.NewButton(form.label, ui.NewLabel(ui.Label{ - IntVariable: &intvar, - Font: ui.MenuFont, - })) - button.Handle(ui.Click, func(ed ui.EventData) error { - shmem.Prompt("Enter new "+form.label+" ", func(answer string) { - if answer == "" { + // If editing a level, apply the update straight away. + if config.EditLevel != nil { + config.EditLevel.SetFile(balance.CustomWallpaperEmbedPath, []byte(b64data)) + newWallpaper = balance.CustomWallpaperFilename + + // Trigger the page type change to the caller. + if pageType, ok := level.PageTypeFromString(newPageType); ok { + config.OnChangePageTypeAndWallpaper(pageType, balance.CustomWallpaperFilename) + } + } else { + // Hold onto the new wallpaper until the level is created. + newWallpaper = balance.CustomWallpaperFilename + newWallpaperB64 = b64data + } + } return } - if i, err := strconv.Atoi(answer); err == nil { - *form.number = int64(i) - intvar = i + if pageType, ok := level.PageTypeFromString(newPageType); ok { + config.OnChangePageTypeAndWallpaper(pageType, filename) + newWallpaper = filename } - }) - return nil - }) - - config.Supervisor.Add(button) - - boundsFrame.Pack(label, ui.Pack{ - Side: ui.W, - PadX: 1, - }) - boundsFrame.Pack(button, ui.Pack{ - Side: ui.W, - PadX: 1, - }) - } - } - - /****************** - * Frame for selecting Level Wallpaper - ******************/ - - wpFrame := ui.NewFrame("Wallpaper Frame") - frame.Pack(wpFrame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 2, - }) - - label2 := ui.NewLabel(ui.Label{ - Text: "Wallpaper:", - Font: balance.LabelFont, - }) - wpFrame.Pack(label2, ui.Pack{ - Side: ui.W, - PadY: 2, - }) - - type wallpaperObj struct { - Name string - Value string - } - var wallpapers = []wallpaperObj{ - {"Notebook", "notebook.png"}, - {"Legal Pad", "legal.png"}, - {"Graph paper", "graph.png"}, - {"Dotted paper", "dots.png"}, - {"Blueprint", "blueprint.png"}, - {"Pure White", "white.png"}, - // {"Placemat", "placemat.png"}, - } - - wallBtn := ui.NewSelectBox("Wallpaper Select", ui.Label{ - Font: balance.MenuFont, - }) - wallBtn.AlwaysChange = true - wpFrame.Pack(wallBtn, ui.Pack{ - Side: ui.W, - Expand: true, - }) - - for _, t := range wallpapers { - wallBtn.AddItem(t.Name, t.Value, func() {}) - } - - // Add custom wallpaper options. - if balance.Feature.CustomWallpaper { - wallBtn.AddSeparator() - wallBtn.AddItem("Custom wallpaper...", balance.CustomWallpaperFilename, func() {}) - } - - // If editing a level, select the current wallpaper. - if config.EditLevel != nil { - wallBtn.SetValue(config.EditLevel.Wallpaper) - } - - wallBtn.Handle(ui.Change, func(ed ui.EventData) error { - if selection, ok := wallBtn.GetValue(); ok { - if filename, ok := selection.Value.(string); ok { - // Picking the Custom option? - if filename == balance.CustomWallpaperFilename { - filename, err := native.OpenFile("Choose a custom wallpaper:", "*.png *.jpg *.gif") - if err == nil { - b64data, err := wallpaper.FileToB64(filename) - if err != nil { - shmem.Flash("Error loading wallpaper: %s", err) - return nil - } - - // If editing a level, apply the update straight away. - if config.EditLevel != nil { - config.EditLevel.SetFile(balance.CustomWallpaperEmbedPath, []byte(b64data)) - newWallpaper = balance.CustomWallpaperFilename - - // Trigger the page type change to the caller. - if pageType, ok := level.PageTypeFromString(newPageType); ok { - config.OnChangePageTypeAndWallpaper(pageType, balance.CustomWallpaperFilename) - } - } else { - // Hold onto the new wallpaper until the level is created. - newWallpaper = balance.CustomWallpaperFilename - newWallpaperB64 = b64data - } - } - return nil } - - if pageType, ok := level.PageTypeFromString(newPageType); ok { - config.OnChangePageTypeAndWallpaper(pageType, filename) - newWallpaper = filename - } - } - } - return nil - }) - - wallBtn.Supervise(config.Supervisor) - config.Supervisor.Add(wallBtn) + }, + }, + }...) /****************** * Frame for picking a default color palette. @@ -350,206 +244,176 @@ func (config AddEditLevel) setupLevelFrame(tf *ui.TabFrame) { // For new level or --experimental only. if config.EditLevel == nil || balance.Feature.ChangePalette { - palFrame := ui.NewFrame("Palette Frame") - frame.Pack(palFrame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 4, - }) - - label3 := ui.NewLabel(ui.Label{ - Text: "Palette: ", - Font: balance.LabelFont, - }) - palFrame.Pack(label3, ui.Pack{ - Side: ui.W, - }) - - palBtn := ui.NewSelectBox("Palette Select", ui.Label{ - Font: balance.MenuFont, - }) - palBtn.AlwaysChange = true - - palFrame.Pack(palBtn, ui.Pack{ - Side: ui.W, - Expand: true, - }) + var ( + palettes = []magicform.Option{} + ) if config.EditLevel != nil { - palBtn.AddItem(paletteName, paletteName, func() {}) - palBtn.AddSeparator() + palettes = append(palettes, []magicform.Option{ + { + Label: paletteName, // "Keep current palette" + Value: paletteName, + }, + { + Separator: true, + }, + }...) } for _, palName := range level.DefaultPaletteNames { - palName := palName - palBtn.AddItem(palName, palName, func() {}) + palettes = append(palettes, magicform.Option{ + Label: palName, + Value: palName, + }) } - palBtn.Handle(ui.Change, func(ed ui.EventData) error { - if val, ok := palBtn.GetValue(); ok { - val2, _ := val.Value.(string) - paletteName = val2 - } - return nil - }) - - config.Supervisor.Add(palBtn) - palBtn.Supervise(config.Supervisor) + // Add form fields. + fields = append(fields, []magicform.Field{ + { + Label: "Palette:", + Font: balance.UIFont, + Options: palettes, + OnSelect: func(v interface{}) { + value, _ := v.(string) + paletteName = value + }, + }, + }...) } /****************** - * Frame for giving the level a title. + * Extended options for editing existing level (vs. Create New screen) ******************/ if config.EditLevel != nil { - label3 := ui.NewLabel(ui.Label{ - Text: "Metadata", - Font: balance.LabelFont, - }) - frame.Pack(label3, ui.Pack{ - Side: ui.N, - FillX: true, - }) - - type metadataObj struct { - Label string - Binding *string - Update func(string) - } - var metaRows = []metadataObj{ - {"Title:", &config.EditLevel.Title, func(v string) { config.EditLevel.Title = v }}, - {"Author:", &config.EditLevel.Author, func(v string) { config.EditLevel.Author = v }}, - } - - for _, mr := range metaRows { - mr := mr - mrFrame := ui.NewFrame("Metadata " + mr.Label + "Frame") - frame.Pack(mrFrame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 2, - }) - - // The label. - mrLabel := ui.NewLabel(ui.Label{ - Text: mr.Label, - Font: balance.MenuFont, - }) - mrLabel.Configure(ui.Config{ - Width: 75, - }) - mrFrame.Pack(mrLabel, ui.Pack{ - Side: ui.W, - }) - - // The button. - mrButton := ui.NewButton(mr.Label, ui.NewLabel(ui.Label{ - TextVariable: mr.Binding, - Font: balance.MenuFont, - })) - mrButton.Handle(ui.Click, func(ed ui.EventData) error { - shmem.Prompt("Enter a new "+mr.Label, func(answer string) { - if answer != "" { - mr.Update(answer) - } - }) - return nil - }) - config.Supervisor.Add(mrButton) - mrFrame.Pack(mrButton, ui.Pack{ - Side: ui.W, - Expand: true, - PadX: 2, - }) - } + fields = append(fields, []magicform.Field{ + { + Label: "Difficulty:", + Font: balance.UIFont, + SelectValue: config.EditLevel.Difficulty, + Tooltip: ui.Tooltip{ + Text: "Peaceful: enemies may not attack\n" + + "Normal: default difficulty\n" + + "Hard: enemies may be more aggressive", + Edge: ui.Top, + }, + Options: []magicform.Option{ + { + Label: "Peaceful", + Value: enum.Peaceful, + }, + { + Label: "Normal (recommended)", + Value: enum.Normal, + }, + { + Label: "Hard", + Value: enum.Hard, + }, + }, + OnSelect: func(v interface{}) { + value, _ := v.(enum.Difficulty) + config.EditLevel.Difficulty = value + log.Info("Set level difficulty to: %d (%s)", value, value) + }, + }, + { + Label: "Metadata", + Font: balance.LabelFont, + }, + { + Label: "Title:", + Font: balance.UIFont, + TextVariable: &config.EditLevel.Title, + PromptUser: func(answer string) { + config.EditLevel.Title = answer + }, + }, + { + Label: "Author:", + Font: balance.UIFont, + TextVariable: &config.EditLevel.Author, + PromptUser: func(answer string) { + config.EditLevel.Author = answer + }, + }, + }...) } - /****************** - * Confirm/cancel buttons. - ******************/ - - bottomFrame := ui.NewFrame("Button Frame") - frame.Pack(bottomFrame, ui.Pack{ - Side: ui.N, - FillX: true, - PadY: 8, - }) - - var buttons = []struct { - Label string - F func(ui.EventData) error - }{ - {"Continue", func(ed ui.EventData) error { - shmem.Flash("Create new map with %s page type and %s wallpaper", newPageType, newWallpaper) - pageType, ok := level.PageTypeFromString(newPageType) - if !ok { - shmem.Flash("Invalid Page Type '%s'", newPageType) - return nil - } - - lvl := level.New() - lvl.Palette = level.DefaultPalettes[paletteName] - lvl.Wallpaper = newWallpaper - lvl.PageType = pageType - - // Was a custom wallpaper selected for our NEW level? - if lvl.Wallpaper == balance.CustomWallpaperFilename && len(newWallpaperB64) > 0 { - lvl.SetFile(balance.CustomWallpaperEmbedPath, []byte(newWallpaperB64)) - } - - if config.OnCreateNewLevel != nil { - config.OnCreateNewLevel(lvl) - } else { - shmem.FlashError("OnCreateNewLevel not attached") - } - return nil - }}, - - {"Cancel", func(ed ui.EventData) error { - config.OnCancel() - return nil - }}, - - // OK button is for editing an existing level. - {"OK", func(ed ui.EventData) error { - // If we're editing a level, did we select a new palette? - if paletteName != textCurrentPalette { - modal.Confirm( - "Are you sure you want to change the level palette?\n" + - "Existing pixels drawn on your level may change, and\n" + - "if the new palette is smaller, some pixels may be\n" + - "lost from your level. OK to continue?", - ).WithTitle("Change Level Palette").Then(func() { - // Install the new level palette. - config.EditLevel.ReplacePalette(level.DefaultPalettes[paletteName]) - if config.OnReload != nil { - config.OnReload() - } - }) - return nil - } - - config.OnCancel() - return nil - }}, - } - for _, t := range buttons { - // If we're editing settings on an existing level, skip the Continue. - if (isNewLevel && t.Label == "OK") || (!isNewLevel && t.Label != "OK") { - continue - } - btn := ui.NewButton(t.Label, ui.NewLabel(ui.Label{ - Text: t.Label, - Font: balance.MenuFont, - })) - btn.Handle(ui.Click, t.F) - config.Supervisor.Add(btn) - bottomFrame.Pack(btn, ui.Pack{ - Side: ui.W, - PadX: 4, - PadY: 8, - }) + // The confirm/cancel buttons. + var okLabel = "Ok" + if config.EditLevel == nil { + okLabel = "Continue" } + fields = append(fields, []magicform.Field{ + { + Buttons: []magicform.Field{ + { + ButtonStyle: &balance.ButtonPrimary, + Label: okLabel, + Font: balance.UIFont, + OnClick: func() { + // Is it a NEW level? + if config.EditLevel == nil { + shmem.Flash("Create new map with %s page type and %s wallpaper", newPageType, newWallpaper) + pageType, ok := level.PageTypeFromString(newPageType) + if !ok { + shmem.Flash("Invalid Page Type '%s'", newPageType) + return + } + + lvl := level.New() + lvl.Palette = level.DefaultPalettes[paletteName] + lvl.Wallpaper = newWallpaper + lvl.PageType = pageType + + // Was a custom wallpaper selected for our NEW level? + if lvl.Wallpaper == balance.CustomWallpaperFilename && len(newWallpaperB64) > 0 { + lvl.SetFile(balance.CustomWallpaperEmbedPath, []byte(newWallpaperB64)) + } + + if config.OnCreateNewLevel != nil { + config.OnCreateNewLevel(lvl) + } else { + shmem.FlashError("OnCreateNewLevel not attached") + } + } else { + // Editing an existing level. + + // If we're editing a level, did we select a new palette? + // Warn the user about if they want to change palettes. + if paletteName != textCurrentPalette { + modal.Confirm( + "Are you sure you want to change the level palette?\n" + + "Existing pixels drawn on your level may change, and\n" + + "if the new palette is smaller, some pixels may be\n" + + "lost from your level. OK to continue?", + ).WithTitle("Change Level Palette").Then(func() { + // Install the new level palette. + config.EditLevel.ReplacePalette(level.DefaultPalettes[paletteName]) + if config.OnReload != nil { + config.OnReload() + } + }) + return + } + + config.OnCancel() + } + }, + }, + { + Label: "Cancel", + Font: balance.UIFont, + OnClick: func() { + config.OnCancel() + }, + }, + }, + }, + }...) + + form.Create(frame, fields) } // Creates the "New Doodad" frame. diff --git a/pkg/windows/doodad_dropper.go b/pkg/windows/doodad_dropper.go index ac141a6..21b9e05 100644 --- a/pkg/windows/doodad_dropper.go +++ b/pkg/windows/doodad_dropper.go @@ -52,6 +52,10 @@ func NewDoodadDropper(config DoodadDropper) *ui.Window { // Load all the doodads, skip hidden ones. var items []*doodads.Doodad for _, filename := range doodadsAvailable { + if filename == "_autosave.doodad" { + continue + } + doodad, err := doodads.LoadFile(filename) if err != nil { log.Error(err.Error()) diff --git a/pkg/windows/text_tool.go b/pkg/windows/text_tool.go index 6237d87..6ac4683 100644 --- a/pkg/windows/text_tool.go +++ b/pkg/windows/text_tool.go @@ -103,7 +103,7 @@ func NewTextToolWindow(cfg TextTool) *ui.Window { Font: balance.LabelFont, TextVariable: ¤tText, OnClick: func() { - shmem.Prompt("Enter new message: ", func(answer string) { + shmem.PromptPre("Enter new message: ", currentText, func(answer string) { if answer != "" { currentText = answer if cfg.OnChangeSettings != nil {