From 9a51ac39f9e65ca65a14f5b8565b3ebd6a66b6e3 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sun, 2 Jan 2022 22:36:32 -0800 Subject: [PATCH] Spit and polish * New doodad: Invisible Warp Door * All warp doors require the player to be grounded (if affected by gravity) to open them. No jumping or falling thru and opening a warp door mid-air! * Title Screen now randomly selects from a couple of levels. * Title Screen: if it fails to load a level it sets up a basic blank level with a wallpaper instead. * New developer shell command: titlescreen Opens the MainScene with a custom user level as the background. * Add Auto-save to the Editor to save your drawing every 5 minutes * Add a MenuBar to the Play Scene for easier navigation to other features of the game. * Doodad JS API: time.Since() now available. --- Changes.md | 59 +++++++++++++++ dev-assets/doodads/regions/Makefile | 7 +- dev-assets/doodads/regions/warp-door-64.png | Bin 0 -> 821 bytes dev-assets/doodads/warp-door/Makefile | 4 +- dev-assets/doodads/warp-door/warp-door.js | 45 ++++++++++- pkg/balance/numbers.go | 11 ++- pkg/commands.go | 19 +++++ pkg/common_menubar.go | 44 +++++++++++ pkg/doodle.go | 1 + pkg/editor_scene.go | 36 ++++++++- pkg/editor_ui_menubar.go | 67 +++++++---------- pkg/main_scene.go | 26 ++++++- pkg/play_scene.go | 18 ++++- pkg/play_scene_menubar.go | 79 ++++++++++++++++++++ pkg/play_scene_touch.go | 7 ++ pkg/scripting/js_api.go | 3 +- pkg/usercfg/usercfg.go | 1 + pkg/windows/levelpack_open.go | 4 +- pkg/windows/pip_canvas.go | 38 ++++++---- pkg/windows/settings.go | 26 +++---- 20 files changed, 408 insertions(+), 87 deletions(-) create mode 100644 dev-assets/doodads/regions/warp-door-64.png create mode 100644 pkg/common_menubar.go create mode 100644 pkg/play_scene_menubar.go diff --git a/Changes.md b/Changes.md index 0bd7914..5e11578 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,64 @@ # Changes +## v0.11.0 (TBD) + +New features: + +* **High scores and level progression:** when playing levels out of + a Level Pack, the game will save your progress and high scores on + each level you play. See details on how scoring works so far, below. +* **Auto-save** for the Editor. Automatically saves your drawing every + 5 minutes. Look for e.g. the _autosave.level to recover your drawing + if the game crashed or exited wrongly! +* **Color picker UI:** when asked to choose a color (e.g. for your level + palette) a UI window makes picking a color easy! You can still manually + enter a hexadecimal color value but this is no longer required! + +Scoring system: + +* The high score on a level is based on how quickly you complete it. + A timer is shown in-game and there are two possible high scores + for each level in a pack: + * Perfect Time (gold): if you complete the level without dying and + restarting at a checkpoint. + * Best Time (silver): if you had used a checkpoint. +* The gold/silver icon is displayed next to the timer in-game; it + starts gold and drops to silver if you die and restart from checkpoint. + It gives you a preview of which high score you'll be competing with. +* If cheat codes are used, the user is not eligible for a high score + but may still mark the level "completed." +* Level Packs may have some of their later levels locked by default, + with only one or a few available immediately. Completing a level will + unlock the next level until they have all been unlocked. + +New and changed doodads: + +* **Invisible Warp Door:** a technical doodad to create an invisible + Warp Door (press Space/'Use' key to activate). +* All **Warp Doors** now require the player to be grounded before they + can be opened, or else under the effects of antigravity. You shouldn't + be able to open Warp Doors while falling or jumping off the ground + at them. + +Revised levels: + +* Desert-2of2.level uses a new work-around for the unfortunate glitch + of sometimes getting stuck on two boxes, instead of a cheat code + being necessary to resolve. +* Revised difficulty on Tutorial 2 and Tutorial 3. + +Miscellaneous changes: + +* Title Screen: picks a random level from a few options, in the future + it will pick random user levels too. +* Play Mode gets a menu bar like the Editor for easier navigation to + other game features. +* New dev shell command: `titlescreen ` to load the Title + Screen with the named level as its background, can be used to load + user levels _now_. +* For the doodads JavaScript API: `time.Since()` is now available (from + the Go standard library) + ## v0.10.0 (Dec 30 2021) New features and changes: diff --git a/dev-assets/doodads/regions/Makefile b/dev-assets/doodads/regions/Makefile index 7d8c6b2..6ea450f 100644 --- a/dev-assets/doodads/regions/Makefile +++ b/dev-assets/doodads/regions/Makefile @@ -23,7 +23,12 @@ build: doodad convert -t "Power Source" power-64.png power-source.doodad doodad install-script power.js power-source.doodad + # Warp Door + doodad convert -t "Invisible Warp Door" warp-door-64.png reg-warp-door.doodad + doodad edit-doodad --tag "color=invisible" reg-warp-door.doodad + doodad install-script ../warp-door/warp-door.js reg-warp-door.doodad + for i in *.doodad; do\ doodad edit-doodad --tag "category=technical" $${i};\ done - cp *.doodad ../../../assets/doodads/ \ No newline at end of file + cp *.doodad ../../../assets/doodads/ diff --git a/dev-assets/doodads/regions/warp-door-64.png b/dev-assets/doodads/regions/warp-door-64.png new file mode 100644 index 0000000000000000000000000000000000000000..f2b31d43b9e052d0dc7ed90a7a068015bd500e37 GIT binary patch literal 821 zcmV-51Iqk~P)EX>4Tx04R}tkv&MmP!xqvQ$>-AgGEFHGgLvaAS&XhRVYG*P%E_RVDi#GXws0R zxHt-~1qXi?s}3&Cx;nTDg5VE`qmz@OiTyrGnJet4ik&{7FJrA6-y{D4^000SaNLh0L z01FcU01FcV0GgZ_00007bV*G`2j&3-0umG4(lG)600A*cL_t(|+U=WB4uc>JhQW9j z@8dytA5Y_+%m~5NVvu6LFWJL{ZS=R4w%g*mggsR7*+SIJ*M3gx_dl3|bKDCMQ`gbD z-KeBCvjq?U0emw{fg%r#rR!&b^z3Xt=1AkO@qo`3;_)aME{(GVdJC{FVVCa8s5!)R z*PvXOC*8Mc5NuVblwMnk36G`1H5HZ=>GNQA^|{#?kVfb`UeP1UH$4UNJ802BbI0I_ zA^`vZycBgREamc=aSIKRzisg=xqEs|J1)&{Wps60JYTq!3XhN~(-HD6z-(a=J-?na z;Q_rm!sfPs`L_7osBfVKRwY2%7S9(i_4l;>zo)%`-v^Kf00000fKOc30;IwS@MHF9 zH03Z`009sH0T2KI5C8!XAgcy7@aJi^z&rQ?$wSF;A2tV&00000NkvXXu0mjfuGeS^ literal 0 HcmV?d00001 diff --git a/dev-assets/doodads/warp-door/Makefile b/dev-assets/doodads/warp-door/Makefile index a4842e1..90d80b2 100644 --- a/dev-assets/doodads/warp-door/Makefile +++ b/dev-assets/doodads/warp-door/Makefile @@ -17,10 +17,10 @@ build: doodad install-script warp-door.js warp-door-orange.doodad for i in *.doodad; do\ - doodad edit-doodad --tag "category=doors" $${i};\ + doodad edit-doodad --tag "category=doors" --hitbox=34,76 $${i};\ done for i in warp-door-*.doodad; do\ doodad edit-doodad --tag "category=doors,gizmos" $${i};\ done - cp *.doodad ../../../assets/doodads/ \ No newline at end of file + cp *.doodad ../../../assets/doodads/ diff --git a/dev-assets/doodads/warp-door/warp-door.js b/dev-assets/doodads/warp-door/warp-door.js index 0cb6e1a..10b587f 100644 --- a/dev-assets/doodads/warp-door/warp-door.js +++ b/dev-assets/doodads/warp-door/warp-door.js @@ -1,7 +1,5 @@ // Warp Doors function main() { - Self.SetHitbox(0, 0, 34, 76); - // Are we a blue or orange door? Regular warp door will be 'none' var color = Self.GetTag("color"); var isStateDoor = color === 'blue' || color === 'orange'; @@ -23,6 +21,11 @@ function main() { Self.AddAnimation("close", animSpeed, ["orange-4", "orange-3", "orange-2", "orange-1"]); spriteDefault = "orange-1"; spriteDisabled = "orange-off"; + } else if (color === 'invisible') { + // Invisible Warp Door region. + Self.Hide(); + Self.AddAnimation("open", animSpeed, [0, 0]); + Self.AddAnimation("close", animSpeed, [0, 0]); } else { Self.AddAnimation("open", animSpeed, ["door-2", "door-3", "door-4"]); Self.AddAnimation("close", animSpeed, ["door-4", "door-3", "door-2", "door-1"]); @@ -48,6 +51,10 @@ function main() { }); } + // For player groundedness work-around + var playerLastY = []; // last sampling of Y values + var lastUsed = time.Now(); + // The player Uses the door. var flashedCooldown = false; // "Locked Door" flashed message. Events.OnUse(function(e) { @@ -74,6 +81,40 @@ function main() { return; } + // The player must be grounded or have no gravity to open the door. + if (!e.Actor.Grounded() && e.Actor.HasGravity()) { + // Work-around: if two Boxes are stacked atop each other the player can + // get stuck if he jumps on top. He may not be Grounded but isn't changing + // effective Y position and a warp door may work as a good way out. + var yValue = e.Actor.Position().Y; + + // Collect a sampling of last few Y values. If the player Y position + // is constant the last handful of frames, treat them as if they're + // grounded (or else they can't activate the warp door). + playerLastY.unshift(yValue); + if (playerLastY.length < 6) { + return; + } + + // We have enough history. + playerLastY.pop(); + + // Hasn't moved? + var isGrounded = true; + for (var i = 0; i < playerLastY.length; i++) { + if (yValue !== playerLastY[i]) { + isGrounded = false; + break; + } + } + + if (!isGrounded) { + return; + } + + // Player was effectively grounded! No change in Y position. + } + // Freeze the player. e.Actor.Freeze() diff --git a/pkg/balance/numbers.go b/pkg/balance/numbers.go index 3c32bb4..0304726 100644 --- a/pkg/balance/numbers.go +++ b/pkg/balance/numbers.go @@ -59,11 +59,18 @@ var ( DefaultEraserBrushSize = 8 MaxEraserBrushSize = 32 // the bigger, the slower + // Interval for auto-save in the editor + AutoSaveInterval = 5 * time.Minute + // Default player character doodad in Play Mode. PlayerCharacterDoodad = "boy.doodad" - // Level name for the title screen. - DemoLevelName = "Tutorial 3.level" + // Level names for the title screen. + DemoLevelName = []string{ + "Tutorial 1.level", + "Tutorial 2.level", + "Tutorial 3.level", + } // Level attachment filename for the custom wallpaper. // NOTE: due to hard-coded "assets/wallpapers/" prefix in uix/canvas.go#LoadLevel. diff --git a/pkg/commands.go b/pkg/commands.go index 5b02de9..0bde7cd 100644 --- a/pkg/commands.go +++ b/pkg/commands.go @@ -60,6 +60,8 @@ func (c Command) Run(d *Doodle) error { return c.Play(d) case "close": return c.Close(d) + case "titlescreen": + return c.TitleScreen(d) case "exit": case "quit": return c.Quit() @@ -192,6 +194,9 @@ func (c Command) Help(d *Doodle) error { d.Flash("Enter a JavaScript shell on the in-game interpreter") case "boolprop": d.Flash("Toggle boolean values. `boolProp list` lists available") + case "titlescreen": + d.Flash("Usage: titlescreen ") + d.Flash("Open the title screen with a level") case "help": d.Flash("Usage: help ") d.Flash("Gets further help on a command") @@ -252,6 +257,20 @@ func (c Command) Play(d *Doodle) error { return nil } +// TitleScreen loads the title with a custom user level. +func (c Command) TitleScreen(d *Doodle) error { + if len(c.Args) == 0 { + return errors.New("Usage: titlescreen ") + } + + filename := c.Args[0] + d.shell.Write("Playing level: " + filename) + d.Goto(&MainScene{ + LevelFilename: filename, + }) + return nil +} + // Quit the command line shell. func (c Command) Quit() error { return nil diff --git a/pkg/common_menubar.go b/pkg/common_menubar.go new file mode 100644 index 0000000..d6d6fda --- /dev/null +++ b/pkg/common_menubar.go @@ -0,0 +1,44 @@ +package doodle + +import ( + "git.kirsle.net/apps/doodle/pkg/balance" + "git.kirsle.net/apps/doodle/pkg/branding" + "git.kirsle.net/apps/doodle/pkg/native" + "git.kirsle.net/apps/doodle/pkg/windows" + "git.kirsle.net/go/render" + "git.kirsle.net/go/ui" +) + +// Common menubars between Play and Edit. + +// MakeHelpMenu creates the "Help" menu with its common items +// across any scene. +func (d *Doodle) MakeHelpMenu(menu *ui.MenuBar, supervisor *ui.Supervisor) *ui.MenuButton { + helpMenu := menu.AddMenu("Help") + helpMenu.AddItemAccel("User Manual", "F1", func() { + native.OpenLocalURL(balance.GuidebookPath) + }) + helpMenu.AddItem("About", func() { + aboutWindow := windows.NewAboutWindow(windows.About{ + Supervisor: supervisor, + Engine: d.Engine, + }) + aboutWindow.Compute(d.Engine) + aboutWindow.Supervise(supervisor) + + // Center the window. + aboutWindow.MoveTo(render.Point{ + X: (d.width / 2) - (aboutWindow.Size().W / 2), + Y: 60, + }) + aboutWindow.Show() + }) + helpMenu.AddSeparator() + helpMenu.AddItem("Go to Website", func() { + native.OpenURL(branding.Website) + }) + helpMenu.AddItem("Guidebook Online", func() { + native.OpenURL(branding.GuidebookURL) + }) + return helpMenu +} diff --git a/pkg/doodle.go b/pkg/doodle.go index 4ae0c15..82ceeeb 100644 --- a/pkg/doodle.go +++ b/pkg/doodle.go @@ -245,6 +245,7 @@ func (d *Doodle) MakeSettingsWindow(supervisor *ui.Supervisor) *ui.Window { CrosshairSize: &usercfg.Current.CrosshairSize, CrosshairColor: &usercfg.Current.CrosshairColor, HideTouchHints: &usercfg.Current.HideTouchHints, + DisableAutosave: &usercfg.Current.DisableAutosave, } return windows.MakeSettingsWindow(d.width, d.height, cfg) } diff --git a/pkg/editor_scene.go b/pkg/editor_scene.go index b6b1235..3a510cf 100644 --- a/pkg/editor_scene.go +++ b/pkg/editor_scene.go @@ -5,7 +5,9 @@ import ( "fmt" "os" "strings" + "time" + "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/doodads" "git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/enum" @@ -47,6 +49,8 @@ type EditorScene struct { // Last saved filename by the user. filename string + + lastAutosaveAt time.Time } // Name of the scene. @@ -66,6 +70,9 @@ func (s *EditorScene) Setup(d *Doodle) error { {"Swatch:", s.debSwatch}, } + // Initialize autosave time. + s.lastAutosaveAt = time.Now() + // Show the loading screen. loadscreen.ShowWithProgress() go func() { @@ -424,6 +431,16 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error { s.UI.Loop(ev) + // Trigger auto-save of the level in case of crash or accidental closure. + if time.Since(s.lastAutosaveAt) > balance.AutoSaveInterval { + s.lastAutosaveAt = time.Now() + if !usercfg.Current.DisableAutosave { + if err := s.AutoSave(); err != nil { + d.FlashError("Autosave error: %s", err) + } + } + } + return nil } @@ -471,7 +488,6 @@ func (s *EditorScene) LoadLevel(filename string) error { } // SaveLevel saves the level to disk. -// TODO: move this into the Canvas? func (s *EditorScene) SaveLevel(filename string) error { if s.DrawingType != enum.LevelDrawing { return errors.New("SaveLevel: current drawing is not a Level type") @@ -500,6 +516,24 @@ func (s *EditorScene) SaveLevel(filename string) error { return m.WriteFile(filename) } +// AutoSave takes an autosave snapshot of the level or drawing. +func (s *EditorScene) AutoSave() error { + var filename = "_autosave.level" + + switch s.DrawingType { + case enum.LevelDrawing: + s.d.Flash("Automatically saved level to %s", filename) + return s.Level.WriteFile(filename) + case enum.DoodadDrawing: + filename = "_autosave.doodad" + + s.d.Flash("Automatically saved doodad to %s", filename) + return s.Doodad.WriteFile(filename) + } + + return nil +} + // LoadDoodad loads a doodad from disk. func (s *EditorScene) LoadDoodad(filename string) error { s.filename = filename diff --git a/pkg/editor_ui_menubar.go b/pkg/editor_ui_menubar.go index a38a8f7..b4c063a 100644 --- a/pkg/editor_ui_menubar.go +++ b/pkg/editor_ui_menubar.go @@ -6,12 +6,13 @@ package doodle import ( "git.kirsle.net/apps/doodle/pkg/balance" - "git.kirsle.net/apps/doodle/pkg/branding" "git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/level/giant_screenshot" + "git.kirsle.net/apps/doodle/pkg/license" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/native" + "git.kirsle.net/apps/doodle/pkg/usercfg" "git.kirsle.net/apps/doodle/pkg/userdir" "git.kirsle.net/apps/doodle/pkg/windows" "git.kirsle.net/go/render" @@ -143,20 +144,22 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar { native.OpenLocalURL(userdir.ScreenshotDirectory) }) - levelMenu.AddSeparator() - levelMenu.AddItemAccel("New viewport", "v", func() { - pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{ - Supervisor: u.Supervisor, - Engine: u.d.Engine, - Level: u.Scene.Level, - Event: u.d.event, + if usercfg.Current.EnableFeatures { + levelMenu.AddSeparator() + levelMenu.AddItemAccel("New viewport", "v", func() { + pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{ + Supervisor: u.Supervisor, + Engine: u.d.Engine, + Level: u.Scene.Level, + Event: u.d.event, - Tool: &u.Scene.UI.Canvas.Tool, - BrushSize: &u.Scene.UI.Canvas.BrushSize, + Tool: &u.Scene.UI.Canvas.Tool, + BrushSize: &u.Scene.UI.Canvas.BrushSize, + }) + + pip.Show() }) - - pip.Show() - }) + } } //////// @@ -261,36 +264,16 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar { //////// // Help menu - helpMenu := menu.AddMenu("Help") - helpMenu.AddItemAccel("User Manual", "F1", func() { - native.OpenLocalURL(balance.GuidebookPath) - }) - helpMenu.AddItem("Register", func() { - u.licenseWindow.Show() - }) - helpMenu.AddItem("About", func() { - if u.aboutWindow == nil { - u.aboutWindow = windows.NewAboutWindow(windows.About{ - Supervisor: u.Supervisor, - Engine: d.Engine, - }) - u.aboutWindow.Compute(d.Engine) - u.aboutWindow.Supervise(u.Supervisor) - - // Center the window. - u.aboutWindow.MoveTo(render.Point{ - X: (d.width / 2) - (u.aboutWindow.Size().W / 2), - Y: 60, - }) - } - u.aboutWindow.Show() - }) + var ( + helpMenu = u.d.MakeHelpMenu(menu, u.Supervisor) + registerText = "Register" + ) helpMenu.AddSeparator() - helpMenu.AddItem("Go to Website", func() { - native.OpenURL(branding.Website) - }) - helpMenu.AddItem("Guidebook Online", func() { - native.OpenURL(branding.GuidebookURL) + if license.IsRegistered() { + registerText = "Registration" + } + helpMenu.AddItem(registerText, func() { + u.licenseWindow.Show() }) menu.Supervise(u.Supervisor) diff --git a/pkg/main_scene.go b/pkg/main_scene.go index 1b6bf02..8eab47a 100644 --- a/pkg/main_scene.go +++ b/pkg/main_scene.go @@ -2,6 +2,7 @@ package doodle import ( "fmt" + "math/rand" "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/branding" @@ -24,7 +25,8 @@ import ( // MainScene implements the main menu of Doodle. type MainScene struct { - Supervisor *ui.Supervisor + Supervisor *ui.Supervisor + LevelFilename string // custom level filename to load in background // Background wallpaper canvas. scripting *scripting.Supervisor @@ -296,8 +298,16 @@ func (s *MainScene) SetupDemoLevel(d *Doodle) error { s.scripting = scripting.NewSupervisor() s.canvas.SetScriptSupervisor(s.scripting) - // Title screen level to load. - if lvl, err := level.LoadFile(balance.DemoLevelName); err == nil { + // Title screen level to load. Pick a random level. + levelName := balance.DemoLevelName[0] + if s.LevelFilename != "" { + levelName = s.LevelFilename + } else if len(balance.DemoLevelName) > 1 { + randIndex := rand.Intn(len(balance.DemoLevelName)) + levelName = balance.DemoLevelName[randIndex] + } + + if lvl, err := level.LoadFile(levelName); err == nil { s.canvas.LoadLevel(lvl) s.canvas.InstallActors(lvl.Actors) @@ -312,6 +322,16 @@ func (s *MainScene) SetupDemoLevel(d *Doodle) error { } } else { log.Error("Error loading demo level %s: %s", balance.DemoLevelName, err) + + // Create a basic notebook level. + s.canvas.LoadLevel(&level.Level{ + Chunker: level.NewChunker(100), + Palette: level.NewPalette(), + PageType: level.Bounded, + MaxWidth: 42, + MaxHeight: 42, + Wallpaper: "notebook.png", + }) } return nil diff --git a/pkg/play_scene.go b/pkg/play_scene.go index b6e1f21..939a793 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -50,9 +50,11 @@ type PlayScene struct { cheated bool // user has entered a cheat code while playing // UI widgets. - supervisor *ui.Supervisor - screen *ui.Frame // A window sized invisible frame to position UI elements. - editButton *ui.Button + supervisor *ui.Supervisor + screen *ui.Frame // A window sized invisible frame to position UI elements. + menubar *ui.MenuBar + editButton *ui.Button + winLevelPacks *ui.Window // Custom debug labels. debPosition *string @@ -119,6 +121,13 @@ func (s *PlayScene) setupAsync(d *Doodle) error { s.screen = ui.NewFrame("Screen") s.screen.Resize(render.NewRect(d.width, d.height)) + // Menu Bar + s.menubar = s.setupMenuBar(d) + s.screen.Pack(s.menubar, ui.Pack{ + Side: ui.N, + FillX: true, + }) + // Level Exit handler. s.scripting.OnLevelExit(s.BeatLevel) s.scripting.OnLevelFail(s.FailLevel) @@ -620,6 +629,9 @@ func (s *PlayScene) Draw(d *Doodle) error { // Visualize the touch regions? s.DrawTouchable() + // Let Supervisor draw menus + s.supervisor.Present(d.Engine) + return nil } diff --git a/pkg/play_scene_menubar.go b/pkg/play_scene_menubar.go new file mode 100644 index 0000000..84624e3 --- /dev/null +++ b/pkg/play_scene_menubar.go @@ -0,0 +1,79 @@ +package doodle + +import ( + "git.kirsle.net/apps/doodle/pkg/levelpack" + "git.kirsle.net/apps/doodle/pkg/shmem" + "git.kirsle.net/apps/doodle/pkg/usercfg" + "git.kirsle.net/apps/doodle/pkg/windows" + "git.kirsle.net/go/render" + "git.kirsle.net/go/ui" +) + +// Set up the menu bar for Play Scene. +func (u *PlayScene) setupMenuBar(d *Doodle) *ui.MenuBar { + menu := ui.NewMenuBar("Main Menu") + + //////// + // Game menu + gameMenu := menu.AddMenu("Game") + gameMenu.AddItem("Story Mode", func() { + // TODO: de-duplicate code from MainScene + if u.winLevelPacks == nil { + u.winLevelPacks = windows.NewLevelPackWindow(windows.LevelPack{ + Supervisor: u.supervisor, + Engine: d.Engine, + + OnPlayLevel: func(lp levelpack.LevelPack, which levelpack.Level) { + if err := d.PlayFromLevelpack(lp, which); err != nil { + shmem.FlashError(err.Error()) + } + }, + OnCloseWindow: func() { + u.winLevelPacks.Hide() + }, + }) + } + u.winLevelPacks.MoveTo(render.Point{ + X: (d.width / 2) - (u.winLevelPacks.Size().W / 2), + Y: (d.height / 2) - (u.winLevelPacks.Size().H / 2), + }) + u.winLevelPacks.Show() + }) + gameMenu.AddItemAccel("New drawing", "Ctrl-N", d.GotoNewMenu) + gameMenu.AddItemAccel("Open drawing", "Ctrl-O", d.GotoLoadMenu) + + gameMenu.AddSeparator() + gameMenu.AddItem("Quit to menu", func() { + d.Goto(&MainScene{}) + }) + gameMenu.AddItemAccel("Quit", "Escape", func() { + d.ConfirmExit() + }) + + //////// + // Level menu + levelMenu := menu.AddMenu("Level") + levelMenu.AddItemAccel("Edit level", "E", u.EditLevel) + + // Hilariously broken, someday! + if usercfg.Current.EnableFeatures { + levelMenu.AddSeparator() + levelMenu.AddItemAccel("New viewport", "v", func() { + pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{ + Supervisor: u.supervisor, + Engine: u.d.Engine, + Level: u.Level, + Event: u.d.event, + }) + + pip.Show() + }) + } + + d.MakeHelpMenu(menu, u.supervisor) + + menu.Supervise(u.supervisor) + menu.Compute(d.Engine) + + return menu +} diff --git a/pkg/play_scene_touch.go b/pkg/play_scene_touch.go index c3c4cad..202b937 100644 --- a/pkg/play_scene_touch.go +++ b/pkg/play_scene_touch.go @@ -28,6 +28,13 @@ func (s *PlayScene) LoopTouchable(ev *event.State) { cursor = render.NewPoint(ev.CursorX, ev.CursorY) ) + // Don't do any of this if the mouse is over the menu bar, so + // clicking on the menus doesn't make the character move or jump. + if cursor.Inside(s.menubar.Rect()) || s.supervisor.GetModal() != nil || + s.supervisor.IsPointInWindow(cursor) { + return + } + // Detect if the player is idle. // Idle means that they are not holding any directional or otherwise input key. // Keyboard inputs and touch events from this function will set these keys. diff --git a/pkg/scripting/js_api.go b/pkg/scripting/js_api.go index 3d72a31..f40c22e 100644 --- a/pkg/scripting/js_api.go +++ b/pkg/scripting/js_api.go @@ -42,7 +42,8 @@ func NewJSProxy(vm *VM) JSProxy { return shmem.Tick }, "time": map[string]interface{}{ - "Now": time.Now, + "Now": time.Now, + "Since": time.Since, "Add": func(t time.Time, ms int64) time.Time { return t.Add(time.Duration(ms) * time.Millisecond) }, diff --git a/pkg/usercfg/usercfg.go b/pkg/usercfg/usercfg.go index df16647..3139be6 100644 --- a/pkg/usercfg/usercfg.go +++ b/pkg/usercfg/usercfg.go @@ -36,6 +36,7 @@ type Settings struct { CrosshairSize int `json:",omitempty"` CrosshairColor render.Color HideTouchHints bool `json:",omitempty"` + DisableAutosave bool `json:",omitempty"` // Secret boolprops from balance/boolprops.go ShowHiddenDoodads bool `json:",omitempty"` diff --git a/pkg/windows/levelpack_open.go b/pkg/windows/levelpack_open.go index c0c4457..b63295a 100644 --- a/pkg/windows/levelpack_open.go +++ b/pkg/windows/levelpack_open.go @@ -39,7 +39,7 @@ func NewLevelPackWindow(config LevelPack) *ui.Window { // size of the popup window width = 320 - height = 340 + height = 360 ) // Get the available .levelpack files. @@ -275,7 +275,7 @@ func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp buttonWidth = width - 40 page = 1 - perPage = 3 + perPage = 4 pages = int( math.Ceil( float64(len(lp.Levels)) / float64(perPage), diff --git a/pkg/windows/pip_canvas.go b/pkg/windows/pip_canvas.go index 3f7f11f..5ca2767 100644 --- a/pkg/windows/pip_canvas.go +++ b/pkg/windows/pip_canvas.go @@ -77,25 +77,37 @@ func NewPiPWindow(cfg PiP) *ui.Window { canvas.Editable = true canvas.Resize(render.NewRect(canvasWidth, canvasHeight)) + // If we have tool bindings to edit in PiP window + var ( + editable bool + curTool drawtool.Tool + curThicc int + ) + if cfg.Tool != nil && cfg.BrushSize != nil { + editable = true + curTool = *cfg.Tool + curThicc = *cfg.BrushSize + canvas.Tool = curTool + } + // NOTE: my UI toolkit calls this every tick, if this is "fixed" // in the future make one that does. - var ( - curTool = *cfg.Tool - curThicc = *cfg.BrushSize - ) - canvas.Tool = curTool window.Handle(ui.MouseMove, func(ed ui.EventData) error { canvas.Loop(cfg.Event) - // Check if bound values have modified. - if *cfg.Tool != curTool { - curTool = *cfg.Tool - canvas.Tool = curTool - } - if *cfg.BrushSize != curThicc { - curThicc = *cfg.BrushSize - canvas.BrushSize = curThicc + // Did we have tool bindings for an editable PiP? + if editable { + // Check if bound values have modified. + if *cfg.Tool != curTool { + curTool = *cfg.Tool + canvas.Tool = curTool + } + if *cfg.BrushSize != curThicc { + curThicc = *cfg.BrushSize + canvas.BrushSize = curThicc + } } + return nil }) diff --git a/pkg/windows/settings.go b/pkg/windows/settings.go index c186512..0a70c20 100644 --- a/pkg/windows/settings.go +++ b/pkg/windows/settings.go @@ -29,6 +29,7 @@ type Settings struct { CrosshairSize *int CrosshairColor *render.Color HideTouchHints *bool + DisableAutosave *bool // Configuration options. SceneName string // name of scene which called this window @@ -142,6 +143,12 @@ func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.F PadX: 4, name: "toolbars", }, + { + Boolean: c.DisableAutosave, + Text: "Disable auto-save in the Editor", + PadX: 4, + name: "autosave", + }, { Integer: c.CrosshairSize, Text: "Editor: Crosshair size (0 to disable):", @@ -170,9 +177,7 @@ func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.F }, { Text: "Levels and doodads you create in-game are placed in your\n" + - "Profile Directory. This is also where you can place content made\n" + - "by others to use them in your game. Click on the button below\n" + - "to (hopefully) be taken to your Profile Directory:", + "Profile Directory, which you can access below:", }, } for _, row := range rows { @@ -634,20 +639,11 @@ func (c Settings) makeExperimentalTab(tabFrame *ui.TabFrame, Width, Height int) PadY: 2, }, { - Header: "Zoom In/Out", + Header: "Viewport window", }, { - 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.", + Text: "This option in the Level menu opens another view into\n" + + "the level. Has glitchy wallpaper problems.", PadY: 2, }, {