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.
pull/84/head
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) {
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;

View File

@ -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;
}

View File

@ -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

View File

@ -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]
}

View File

@ -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"`

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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")

View File

@ -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)
}

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

View File

@ -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.

View File

@ -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())

View File

@ -103,7 +103,7 @@ func NewTextToolWindow(cfg TextTool) *ui.Window {
Font: balance.LabelFont,
TextVariable: &currentText,
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 {