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.
This commit is contained in:
Noah 2022-03-06 22:16:09 -08:00
parent 661c5f4365
commit 647124495b
15 changed files with 382 additions and 431 deletions

View File

@ -18,8 +18,8 @@ if (color === 'white') {
} }
function setupAnimations(color) { function setupAnimations(color) {
let left = color === 'blue' ? 'blu-wl' : color+'-wl', let left = color === 'blue' ? 'blu-wl' : color + '-wl',
right = color === 'blue' ? 'blu-wr' : color+'-wr', right = color === 'blue' ? 'blu-wr' : color + '-wr',
leftFrames = [left + '1', left + '2', left + '3', left + '4'], leftFrames = [left + '1', left + '2', left + '3', left + '4'],
rightFrames = [right + '1', right + '2', right + '3', right + '4']; rightFrames = [right + '1', right + '2', right + '3', right + '4'];
@ -71,7 +71,8 @@ function main() {
myPt = Self.Position(); myPt = Self.Position();
// If the player is within aggro range, move towards. // 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"; direction = playerPt.X < myPt.X ? "left" : "right";
followPlayer = true; followPlayer = true;
@ -134,11 +135,16 @@ function playerControls() {
// will be hostile towards the player). Boring players will not be chased after and // will be hostile towards the player). Boring players will not be chased after and
// the Azulian will not harm them if they make contact. // the Azulian will not harm them if they make contact.
function isPlayerFood(actor) { function isPlayerFood(actor) {
// Not a player or is invulnerable. // Not a player or is invulnerable, or Peaceful difficulty.
if (!actor.IsPlayer() || actor.Invulnerable()) { if (!actor.IsPlayer() || actor.Invulnerable() || Level.Difficulty < 0) {
return false; 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. // Azulians are friendly to Thieves and other Azulians.
if (actor.Doodad().Filename === "thief.doodad" || actor.Doodad().Title.indexOf("Azulian") > -1) { if (actor.Doodad().Filename === "thief.doodad" || actor.Doodad().Title.indexOf("Azulian") > -1) {
return false; return false;

View File

@ -1,8 +1,8 @@
// Bird // Bird
let speed = 4, let speed = 4,
Vx = Vy = 0, Vx = Vy = 0,
altitude = Self.Position().Y; // original height in the level altitude = Self.Position().Y; // original height in the level
let direction = "left", let direction = "left",
lastDirection = "left"; lastDirection = "left";
@ -72,7 +72,7 @@ function main() {
// Scan for the player character and dive. // Scan for the player character and dive.
try { try {
AI_ScanForPlayer() AI_ScanForPlayer()
} catch(e) { } catch (e) {
console.error("Error in AI_ScanForPlayer: %s", e); console.error("Error in AI_ScanForPlayer: %s", e);
} }
} }
@ -84,7 +84,7 @@ function main() {
// If diving, exit - don't edit animation. // If diving, exit - don't edit animation.
if (state === states.diving) { if (state === states.diving) {
Self.ShowLayerNamed("dive-"+direction); Self.ShowLayerNamed("dive-" + direction);
lastDirection = direction; lastDirection = direction;
return; return;
} }
@ -109,6 +109,11 @@ function main() {
// It's not hostile towards characters that can fly (having // It's not hostile towards characters that can fly (having
// no gravity). // no gravity).
function AI_ScanForPlayer() { function AI_ScanForPlayer() {
// If Peaceful difficulty, do not attack.
if (Level.Difficulty < 0) {
return
}
let stepY = 12, // number of pixels to skip let stepY = 12, // number of pixels to skip
stepX = stepY, stepX = stepY,
limit = stepX * 20, // furthest we'll scan limit = stepX * 20, // furthest we'll scan
@ -153,14 +158,14 @@ function player() {
// they aren't seen to be moving downwards, cancel the dive. // they aren't seen to be moving downwards, cancel the dive.
let lastPoint = Self.Position(); let lastPoint = Self.Position();
setInterval(() => { setInterval(() => {
let nowAt = Self.Position(); let nowAt = Self.Position();
if (nowAt.Y > lastPoint.Y) { if (nowAt.Y > lastPoint.Y) {
falling = true; falling = true;
} else { } else {
falling = false; falling = false;
} }
lastPoint = nowAt; lastPoint = nowAt;
}, 100); }, 100);
Events.OnKeypress((ev) => { Events.OnKeypress((ev) => {
Vx = 0; Vx = 0;
@ -198,7 +203,7 @@ function player() {
} else { } else {
// Hover in place. // Hover in place.
if (!Self.IsAnimating()) { if (!Self.IsAnimating()) {
Self.PlayAnimation("fly-"+direction); Self.PlayAnimation("fly-" + direction);
} }
diving = false; diving = false;
} }

View File

@ -73,6 +73,7 @@ func New(debug bool, engine render.Engine) *Doodle {
shmem.Flash = d.Flash shmem.Flash = d.Flash
shmem.FlashError = d.FlashError shmem.FlashError = d.FlashError
shmem.Prompt = d.Prompt shmem.Prompt = d.Prompt
shmem.PromptPre = d.PromptPre
if debug { if debug {
log.Logger.Config.Level = golog.DebugLevel log.Logger.Config.Level = golog.DebugLevel

View File

@ -25,3 +25,21 @@ const (
ScreenWidthMedium = 800 ScreenWidthMedium = 800
ScreenWidthLarge = 1000 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]
}

View File

@ -6,6 +6,7 @@ import (
"git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/drawtool"
"git.kirsle.net/apps/doodle/pkg/enum"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
) )
@ -30,7 +31,8 @@ type Base struct {
// 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"` Password string `json:"passwd"`
Difficulty enum.Difficulty `json:"difficulty"`
// Chunked pixel data. // Chunked pixel data.
Chunker *Chunker `json:"chunks"` Chunker *Chunker `json:"chunks"`

View File

@ -5,7 +5,6 @@ package native
import ( import (
"image" "image"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
"git.kirsle.net/go/render/sdl" "git.kirsle.net/go/render/sdl"
sdl2 "github.com/veandco/go-sdl2/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 return nil, err
} }
defer surface.Free() defer surface.Free()
log.Error("surf fmt: %+v", surface.Format)
// Convert the Surface into a pixelformat that supports the .At(x,y) // Convert the Surface into a pixelformat that supports the .At(x,y)
// function properly, as the one we got above is "Not implemented" // function properly, as the one we got above is "Not implemented"

View File

@ -36,6 +36,14 @@ func (d *Doodle) Prompt(question string, callback func(string)) {
d.shell.Open = true 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. // Shell implements the developer console in-game.
type Shell struct { type Shell struct {
parent *Doodle parent *Doodle

View File

@ -27,7 +27,8 @@ var (
FlashError func(string, ...interface{}) FlashError func(string, ...interface{})
// Globally available Prompt() function. // 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. // Ajax file cache for WASM use.
AjaxCache map[string][]byte AjaxCache map[string][]byte

View File

@ -1,6 +1,7 @@
package uix package uix
import ( import (
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/drawtool"
"git.kirsle.net/apps/doodle/pkg/keybind" "git.kirsle.net/apps/doodle/pkg/keybind"
"git.kirsle.net/apps/doodle/pkg/level" "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 { if w.level != nil && addHistory {
w.level.UndoHistory.AddStroke(w.currentStroke) w.level.UndoHistory.AddStroke(w.currentStroke)
} else if w.doodad != nil && addHistory { } 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) w.doodad.UndoHistory.AddStroke(w.currentStroke)
} }

View File

@ -46,6 +46,10 @@ func (w *Canvas) UndoStroke() bool {
if w.level != nil { if w.level != nil {
undoer = w.level.UndoHistory undoer = w.level.UndoHistory
} else if w.doodad != nil { } 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 undoer = w.doodad.UndoHistory
} else { } else {
log.Error("Canvas.UndoStroke: no Level or Doodad currently available to the canvas") log.Error("Canvas.UndoStroke: no Level or Doodad currently available to the canvas")

View File

@ -70,6 +70,10 @@ type Field struct {
SelectValue interface{} // Selectbox default choice SelectValue interface{} // Selectbox default choice
Color *render.Color // Color 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. // Tooltip to add to a form control.
// Checkbox only for now. // Checkbox only for now.
Tooltip ui.Tooltip // config for the tooltip only Tooltip ui.Tooltip // config for the tooltip only
@ -81,8 +85,9 @@ type Field struct {
// Option used in Selectbox or Radiobox fields. // Option used in Selectbox or Radiobox fields.
type Option struct { type Option struct {
Value interface{} Value interface{}
Label string Label string
Separator bool
} }
/* /*
@ -278,6 +283,22 @@ func (form Form) Create(into *ui.Frame, fields []Field) {
// Handlers // Handlers
btn.Handle(ui.Click, func(ed ui.EventData) error { 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 { if row.OnClick != nil {
row.OnClick() row.OnClick()
} }
@ -325,6 +346,10 @@ func (form Form) Create(into *ui.Frame, fields []Field) {
if row.Options != nil { if row.Options != nil {
for _, option := range row.Options { for _, option := range row.Options {
if option.Separator {
btn.AddSeparator()
continue
}
btn.AddItem(option.Label, option.Value, func() {}) btn.AddItem(option.Label, option.Value, func() {})
} }
} }
@ -342,6 +367,12 @@ func (form Form) Create(into *ui.Frame, fields []Field) {
return nil 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) btn.Supervise(form.Supervisor)
form.Supervisor.Add(btn) form.Supervisor.Add(btn)
} }

View File

@ -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 // MakeSelfAPI generates the `Self` object for the scripting API in

View File

@ -4,10 +4,13 @@ import (
"strconv" "strconv"
"git.kirsle.net/apps/doodle/pkg/balance" "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/level"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/modal" "git.kirsle.net/apps/doodle/pkg/modal"
"git.kirsle.net/apps/doodle/pkg/native" "git.kirsle.net/apps/doodle/pkg/native"
"git.kirsle.net/apps/doodle/pkg/shmem" "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/apps/doodle/pkg/wallpaper"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
"git.kirsle.net/go/ui" "git.kirsle.net/go/ui"
@ -90,7 +93,7 @@ func (config AddEditLevel) setupLevelFrame(tf *ui.TabFrame) {
) )
// Given a level to edit? // Given a level to edit?
if config.EditLevel != nil { if !isNewLevel {
newPageType = config.EditLevel.PageType.String() newPageType = config.EditLevel.PageType.String()
newWallpaper = config.EditLevel.Wallpaper newWallpaper = config.EditLevel.Wallpaper
paletteName = textCurrentPalette paletteName = textCurrentPalette
@ -105,244 +108,135 @@ func (config AddEditLevel) setupLevelFrame(tf *ui.TabFrame) {
* Frame for selecting Page Type * Frame for selecting Page Type
******************/ ******************/
typeFrame := ui.NewFrame("Page Type Options Frame") // Selected "Page Type" property.
frame.Pack(typeFrame, ui.Pack{ var pageType = level.Bounded
Side: ui.N, if !isNewLevel {
FillX: true, pageType = config.EditLevel.PageType
})
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},
} }
typeBtn := ui.NewSelectBox("Type Select", ui.Label{ form := magicform.Form{
Font: ui.MenuFont, Supervisor: config.Supervisor,
}) Engine: config.Engine,
typeFrame.Pack(typeBtn, ui.Pack{ Vertical: true,
Side: ui.W, LabelWidth: 120,
Expand: true, PadY: 2,
})
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() {})
} }
fields := []magicform.Field{
// If editing an existing level, pre-select the right page type. {
if config.EditLevel != nil { Label: "Page type:",
typeBtn.SetValue(config.EditLevel.PageType) 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 { if config.EditLevel != nil {
boundsFrame := ui.NewFrame("Bounds Frame") selectedWallpaper = config.EditLevel.Wallpaper
frame.Pack(boundsFrame, ui.Pack{ }
Side: ui.N,
FillX: true,
PadY: 2,
})
label := ui.NewLabel(ui.Label{ fields = append(fields, []magicform.Field{
Text: "Bounded limits:", {
Font: balance.LabelFont, Label: "Wallpaper:",
}) Font: balance.UIFont,
boundsFrame.Pack(label, ui.Pack{ SelectValue: selectedWallpaper,
Side: ui.W, Options: []magicform.Option{
PadY: 2, {
}) Label: "Notebook",
Value: "notebook.png",
var forms = []struct { },
label string {
number *int64 Label: "Legal Pad",
}{ Value: "legal.png",
{ },
label: "Width:", {
number: &config.EditLevel.MaxWidth, 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,
},
}, },
{ OnSelect: func(v interface{}) {
label: "Height:", if filename, ok := v.(string); ok {
number: &config.EditLevel.MaxHeight, // Picking the Custom option?
}, if filename == balance.CustomWallpaperFilename {
} filename, err := native.OpenFile("Choose a custom wallpaper:", "*.png *.jpg *.gif")
for _, form := range forms { if err == nil {
form := form b64data, err := wallpaper.FileToB64(filename)
label := ui.NewLabel(ui.Label{ if err != nil {
Text: form.label, shmem.Flash("Error loading wallpaper: %s", err)
Font: ui.MenuFont, return
}) }
var intvar = int(*form.number) // If editing a level, apply the update straight away.
button := ui.NewButton(form.label, ui.NewLabel(ui.Label{ if config.EditLevel != nil {
IntVariable: &intvar, config.EditLevel.SetFile(balance.CustomWallpaperEmbedPath, []byte(b64data))
Font: ui.MenuFont, newWallpaper = balance.CustomWallpaperFilename
}))
button.Handle(ui.Click, func(ed ui.EventData) error { // Trigger the page type change to the caller.
shmem.Prompt("Enter new "+form.label+" ", func(answer string) { if pageType, ok := level.PageTypeFromString(newPageType); ok {
if answer == "" { config.OnChangePageTypeAndWallpaper(pageType, balance.CustomWallpaperFilename)
}
} else {
// Hold onto the new wallpaper until the level is created.
newWallpaper = balance.CustomWallpaperFilename
newWallpaperB64 = b64data
}
}
return return
} }
if i, err := strconv.Atoi(answer); err == nil { if pageType, ok := level.PageTypeFromString(newPageType); ok {
*form.number = int64(i) config.OnChangePageTypeAndWallpaper(pageType, filename)
intvar = i 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. * Frame for picking a default color palette.
@ -350,206 +244,176 @@ func (config AddEditLevel) setupLevelFrame(tf *ui.TabFrame) {
// For new level or --experimental only. // For new level or --experimental only.
if config.EditLevel == nil || balance.Feature.ChangePalette { if config.EditLevel == nil || balance.Feature.ChangePalette {
palFrame := ui.NewFrame("Palette Frame") var (
frame.Pack(palFrame, ui.Pack{ palettes = []magicform.Option{}
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,
})
if config.EditLevel != nil { if config.EditLevel != nil {
palBtn.AddItem(paletteName, paletteName, func() {}) palettes = append(palettes, []magicform.Option{
palBtn.AddSeparator() {
Label: paletteName, // "Keep current palette"
Value: paletteName,
},
{
Separator: true,
},
}...)
} }
for _, palName := range level.DefaultPaletteNames { for _, palName := range level.DefaultPaletteNames {
palName := palName palettes = append(palettes, magicform.Option{
palBtn.AddItem(palName, palName, func() {}) Label: palName,
Value: palName,
})
} }
palBtn.Handle(ui.Change, func(ed ui.EventData) error { // Add form fields.
if val, ok := palBtn.GetValue(); ok { fields = append(fields, []magicform.Field{
val2, _ := val.Value.(string) {
paletteName = val2 Label: "Palette:",
} Font: balance.UIFont,
return nil Options: palettes,
}) OnSelect: func(v interface{}) {
value, _ := v.(string)
config.Supervisor.Add(palBtn) paletteName = value
palBtn.Supervise(config.Supervisor) },
},
}...)
} }
/****************** /******************
* Frame for giving the level a title. * Extended options for editing existing level (vs. Create New screen)
******************/ ******************/
if config.EditLevel != nil { if config.EditLevel != nil {
label3 := ui.NewLabel(ui.Label{ fields = append(fields, []magicform.Field{
Text: "Metadata", {
Font: balance.LabelFont, Label: "Difficulty:",
}) Font: balance.UIFont,
frame.Pack(label3, ui.Pack{ SelectValue: config.EditLevel.Difficulty,
Side: ui.N, Tooltip: ui.Tooltip{
FillX: true, Text: "Peaceful: enemies may not attack\n" +
}) "Normal: default difficulty\n" +
"Hard: enemies may be more aggressive",
type metadataObj struct { Edge: ui.Top,
Label string },
Binding *string Options: []magicform.Option{
Update func(string) {
} Label: "Peaceful",
var metaRows = []metadataObj{ Value: enum.Peaceful,
{"Title:", &config.EditLevel.Title, func(v string) { config.EditLevel.Title = v }}, },
{"Author:", &config.EditLevel.Author, func(v string) { config.EditLevel.Author = v }}, {
} Label: "Normal (recommended)",
Value: enum.Normal,
for _, mr := range metaRows { },
mr := mr {
mrFrame := ui.NewFrame("Metadata " + mr.Label + "Frame") Label: "Hard",
frame.Pack(mrFrame, ui.Pack{ Value: enum.Hard,
Side: ui.N, },
FillX: true, },
PadY: 2, OnSelect: func(v interface{}) {
}) value, _ := v.(enum.Difficulty)
config.EditLevel.Difficulty = value
// The label. log.Info("Set level difficulty to: %d (%s)", value, value)
mrLabel := ui.NewLabel(ui.Label{ },
Text: mr.Label, },
Font: balance.MenuFont, {
}) Label: "Metadata",
mrLabel.Configure(ui.Config{ Font: balance.LabelFont,
Width: 75, },
}) {
mrFrame.Pack(mrLabel, ui.Pack{ Label: "Title:",
Side: ui.W, Font: balance.UIFont,
}) TextVariable: &config.EditLevel.Title,
PromptUser: func(answer string) {
// The button. config.EditLevel.Title = answer
mrButton := ui.NewButton(mr.Label, ui.NewLabel(ui.Label{ },
TextVariable: mr.Binding, },
Font: balance.MenuFont, {
})) Label: "Author:",
mrButton.Handle(ui.Click, func(ed ui.EventData) error { Font: balance.UIFont,
shmem.Prompt("Enter a new "+mr.Label, func(answer string) { TextVariable: &config.EditLevel.Author,
if answer != "" { PromptUser: func(answer string) {
mr.Update(answer) config.EditLevel.Author = answer
} },
}) },
return nil }...)
})
config.Supervisor.Add(mrButton)
mrFrame.Pack(mrButton, ui.Pack{
Side: ui.W,
Expand: true,
PadX: 2,
})
}
} }
/****************** // The confirm/cancel buttons.
* Confirm/cancel buttons. var okLabel = "Ok"
******************/ if config.EditLevel == nil {
okLabel = "Continue"
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,
})
} }
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. // Creates the "New Doodad" frame.

View File

@ -52,6 +52,10 @@ func NewDoodadDropper(config DoodadDropper) *ui.Window {
// Load all the doodads, skip hidden ones. // Load all the doodads, skip hidden ones.
var items []*doodads.Doodad var items []*doodads.Doodad
for _, filename := range doodadsAvailable { for _, filename := range doodadsAvailable {
if filename == "_autosave.doodad" {
continue
}
doodad, err := doodads.LoadFile(filename) doodad, err := doodads.LoadFile(filename)
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())

View File

@ -103,7 +103,7 @@ func NewTextToolWindow(cfg TextTool) *ui.Window {
Font: balance.LabelFont, Font: balance.LabelFont,
TextVariable: &currentText, TextVariable: &currentText,
OnClick: func() { OnClick: func() {
shmem.Prompt("Enter new message: ", func(answer string) { shmem.PromptPre("Enter new message: ", currentText, func(answer string) {
if answer != "" { if answer != "" {
currentText = answer currentText = answer
if cfg.OnChangeSettings != nil { if cfg.OnChangeSettings != nil {