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 <level> 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.
This commit is contained in:
parent
672ee9641a
commit
9a51ac39f9
59
Changes.md
59
Changes.md
|
@ -1,5 +1,64 @@
|
||||||
# Changes
|
# 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 <level name>` 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)
|
## v0.10.0 (Dec 30 2021)
|
||||||
|
|
||||||
New features and changes:
|
New features and changes:
|
||||||
|
|
|
@ -23,6 +23,11 @@ build:
|
||||||
doodad convert -t "Power Source" power-64.png power-source.doodad
|
doodad convert -t "Power Source" power-64.png power-source.doodad
|
||||||
doodad install-script power.js 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\
|
for i in *.doodad; do\
|
||||||
doodad edit-doodad --tag "category=technical" $${i};\
|
doodad edit-doodad --tag "category=technical" $${i};\
|
||||||
done
|
done
|
||||||
|
|
BIN
dev-assets/doodads/regions/warp-door-64.png
Normal file
BIN
dev-assets/doodads/regions/warp-door-64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 821 B |
|
@ -17,7 +17,7 @@ build:
|
||||||
doodad install-script warp-door.js warp-door-orange.doodad
|
doodad install-script warp-door.js warp-door-orange.doodad
|
||||||
|
|
||||||
for i in *.doodad; do\
|
for i in *.doodad; do\
|
||||||
doodad edit-doodad --tag "category=doors" $${i};\
|
doodad edit-doodad --tag "category=doors" --hitbox=34,76 $${i};\
|
||||||
done
|
done
|
||||||
for i in warp-door-*.doodad; do\
|
for i in warp-door-*.doodad; do\
|
||||||
doodad edit-doodad --tag "category=doors,gizmos" $${i};\
|
doodad edit-doodad --tag "category=doors,gizmos" $${i};\
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
// Warp Doors
|
// Warp Doors
|
||||||
function main() {
|
function main() {
|
||||||
Self.SetHitbox(0, 0, 34, 76);
|
|
||||||
|
|
||||||
// Are we a blue or orange door? Regular warp door will be 'none'
|
// Are we a blue or orange door? Regular warp door will be 'none'
|
||||||
var color = Self.GetTag("color");
|
var color = Self.GetTag("color");
|
||||||
var isStateDoor = color === 'blue' || color === 'orange';
|
var isStateDoor = color === 'blue' || color === 'orange';
|
||||||
|
@ -23,6 +21,11 @@ function main() {
|
||||||
Self.AddAnimation("close", animSpeed, ["orange-4", "orange-3", "orange-2", "orange-1"]);
|
Self.AddAnimation("close", animSpeed, ["orange-4", "orange-3", "orange-2", "orange-1"]);
|
||||||
spriteDefault = "orange-1";
|
spriteDefault = "orange-1";
|
||||||
spriteDisabled = "orange-off";
|
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 {
|
} else {
|
||||||
Self.AddAnimation("open", animSpeed, ["door-2", "door-3", "door-4"]);
|
Self.AddAnimation("open", animSpeed, ["door-2", "door-3", "door-4"]);
|
||||||
Self.AddAnimation("close", animSpeed, ["door-4", "door-3", "door-2", "door-1"]);
|
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.
|
// The player Uses the door.
|
||||||
var flashedCooldown = false; // "Locked Door" flashed message.
|
var flashedCooldown = false; // "Locked Door" flashed message.
|
||||||
Events.OnUse(function(e) {
|
Events.OnUse(function(e) {
|
||||||
|
@ -74,6 +81,40 @@ function main() {
|
||||||
return;
|
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.
|
// Freeze the player.
|
||||||
e.Actor.Freeze()
|
e.Actor.Freeze()
|
||||||
|
|
||||||
|
|
|
@ -59,11 +59,18 @@ var (
|
||||||
DefaultEraserBrushSize = 8
|
DefaultEraserBrushSize = 8
|
||||||
MaxEraserBrushSize = 32 // the bigger, the slower
|
MaxEraserBrushSize = 32 // the bigger, the slower
|
||||||
|
|
||||||
|
// Interval for auto-save in the editor
|
||||||
|
AutoSaveInterval = 5 * time.Minute
|
||||||
|
|
||||||
// Default player character doodad in Play Mode.
|
// Default player character doodad in Play Mode.
|
||||||
PlayerCharacterDoodad = "boy.doodad"
|
PlayerCharacterDoodad = "boy.doodad"
|
||||||
|
|
||||||
// Level name for the title screen.
|
// Level names for the title screen.
|
||||||
DemoLevelName = "Tutorial 3.level"
|
DemoLevelName = []string{
|
||||||
|
"Tutorial 1.level",
|
||||||
|
"Tutorial 2.level",
|
||||||
|
"Tutorial 3.level",
|
||||||
|
}
|
||||||
|
|
||||||
// Level attachment filename for the custom wallpaper.
|
// Level attachment filename for the custom wallpaper.
|
||||||
// NOTE: due to hard-coded "assets/wallpapers/" prefix in uix/canvas.go#LoadLevel.
|
// NOTE: due to hard-coded "assets/wallpapers/" prefix in uix/canvas.go#LoadLevel.
|
||||||
|
|
|
@ -60,6 +60,8 @@ func (c Command) Run(d *Doodle) error {
|
||||||
return c.Play(d)
|
return c.Play(d)
|
||||||
case "close":
|
case "close":
|
||||||
return c.Close(d)
|
return c.Close(d)
|
||||||
|
case "titlescreen":
|
||||||
|
return c.TitleScreen(d)
|
||||||
case "exit":
|
case "exit":
|
||||||
case "quit":
|
case "quit":
|
||||||
return c.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")
|
d.Flash("Enter a JavaScript shell on the in-game interpreter")
|
||||||
case "boolprop":
|
case "boolprop":
|
||||||
d.Flash("Toggle boolean values. `boolProp list` lists available")
|
d.Flash("Toggle boolean values. `boolProp list` lists available")
|
||||||
|
case "titlescreen":
|
||||||
|
d.Flash("Usage: titlescreen <filename.level>")
|
||||||
|
d.Flash("Open the title screen with a level")
|
||||||
case "help":
|
case "help":
|
||||||
d.Flash("Usage: help <command>")
|
d.Flash("Usage: help <command>")
|
||||||
d.Flash("Gets further help on a command")
|
d.Flash("Gets further help on a command")
|
||||||
|
@ -252,6 +257,20 @@ func (c Command) Play(d *Doodle) error {
|
||||||
return nil
|
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 <level name.level>")
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := c.Args[0]
|
||||||
|
d.shell.Write("Playing level: " + filename)
|
||||||
|
d.Goto(&MainScene{
|
||||||
|
LevelFilename: filename,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Quit the command line shell.
|
// Quit the command line shell.
|
||||||
func (c Command) Quit() error {
|
func (c Command) Quit() error {
|
||||||
return nil
|
return nil
|
||||||
|
|
44
pkg/common_menubar.go
Normal file
44
pkg/common_menubar.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -245,6 +245,7 @@ func (d *Doodle) MakeSettingsWindow(supervisor *ui.Supervisor) *ui.Window {
|
||||||
CrosshairSize: &usercfg.Current.CrosshairSize,
|
CrosshairSize: &usercfg.Current.CrosshairSize,
|
||||||
CrosshairColor: &usercfg.Current.CrosshairColor,
|
CrosshairColor: &usercfg.Current.CrosshairColor,
|
||||||
HideTouchHints: &usercfg.Current.HideTouchHints,
|
HideTouchHints: &usercfg.Current.HideTouchHints,
|
||||||
|
DisableAutosave: &usercfg.Current.DisableAutosave,
|
||||||
}
|
}
|
||||||
return windows.MakeSettingsWindow(d.width, d.height, cfg)
|
return windows.MakeSettingsWindow(d.width, d.height, cfg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||||
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
||||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||||
|
@ -47,6 +49,8 @@ type EditorScene struct {
|
||||||
|
|
||||||
// Last saved filename by the user.
|
// Last saved filename by the user.
|
||||||
filename string
|
filename string
|
||||||
|
|
||||||
|
lastAutosaveAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name of the scene.
|
// Name of the scene.
|
||||||
|
@ -66,6 +70,9 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
{"Swatch:", s.debSwatch},
|
{"Swatch:", s.debSwatch},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize autosave time.
|
||||||
|
s.lastAutosaveAt = time.Now()
|
||||||
|
|
||||||
// Show the loading screen.
|
// Show the loading screen.
|
||||||
loadscreen.ShowWithProgress()
|
loadscreen.ShowWithProgress()
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -424,6 +431,16 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
|
||||||
|
|
||||||
s.UI.Loop(ev)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,7 +488,6 @@ func (s *EditorScene) LoadLevel(filename string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveLevel saves the level to disk.
|
// SaveLevel saves the level to disk.
|
||||||
// TODO: move this into the Canvas?
|
|
||||||
func (s *EditorScene) SaveLevel(filename string) error {
|
func (s *EditorScene) SaveLevel(filename string) error {
|
||||||
if s.DrawingType != enum.LevelDrawing {
|
if s.DrawingType != enum.LevelDrawing {
|
||||||
return errors.New("SaveLevel: current drawing is not a Level type")
|
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)
|
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.
|
// LoadDoodad loads a doodad from disk.
|
||||||
func (s *EditorScene) LoadDoodad(filename string) error {
|
func (s *EditorScene) LoadDoodad(filename string) error {
|
||||||
s.filename = filename
|
s.filename = filename
|
||||||
|
|
|
@ -6,12 +6,13 @@ package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
"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/drawtool"
|
||||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||||
"git.kirsle.net/apps/doodle/pkg/level/giant_screenshot"
|
"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/log"
|
||||||
"git.kirsle.net/apps/doodle/pkg/native"
|
"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/userdir"
|
||||||
"git.kirsle.net/apps/doodle/pkg/windows"
|
"git.kirsle.net/apps/doodle/pkg/windows"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
|
@ -143,20 +144,22 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
||||||
native.OpenLocalURL(userdir.ScreenshotDirectory)
|
native.OpenLocalURL(userdir.ScreenshotDirectory)
|
||||||
})
|
})
|
||||||
|
|
||||||
levelMenu.AddSeparator()
|
if usercfg.Current.EnableFeatures {
|
||||||
levelMenu.AddItemAccel("New viewport", "v", func() {
|
levelMenu.AddSeparator()
|
||||||
pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{
|
levelMenu.AddItemAccel("New viewport", "v", func() {
|
||||||
Supervisor: u.Supervisor,
|
pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{
|
||||||
Engine: u.d.Engine,
|
Supervisor: u.Supervisor,
|
||||||
Level: u.Scene.Level,
|
Engine: u.d.Engine,
|
||||||
Event: u.d.event,
|
Level: u.Scene.Level,
|
||||||
|
Event: u.d.event,
|
||||||
|
|
||||||
Tool: &u.Scene.UI.Canvas.Tool,
|
Tool: &u.Scene.UI.Canvas.Tool,
|
||||||
BrushSize: &u.Scene.UI.Canvas.BrushSize,
|
BrushSize: &u.Scene.UI.Canvas.BrushSize,
|
||||||
|
})
|
||||||
|
|
||||||
|
pip.Show()
|
||||||
})
|
})
|
||||||
|
}
|
||||||
pip.Show()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////
|
////////
|
||||||
|
@ -261,36 +264,16 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
||||||
|
|
||||||
////////
|
////////
|
||||||
// Help menu
|
// Help menu
|
||||||
helpMenu := menu.AddMenu("Help")
|
var (
|
||||||
helpMenu.AddItemAccel("User Manual", "F1", func() {
|
helpMenu = u.d.MakeHelpMenu(menu, u.Supervisor)
|
||||||
native.OpenLocalURL(balance.GuidebookPath)
|
registerText = "Register"
|
||||||
})
|
)
|
||||||
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()
|
|
||||||
})
|
|
||||||
helpMenu.AddSeparator()
|
helpMenu.AddSeparator()
|
||||||
helpMenu.AddItem("Go to Website", func() {
|
if license.IsRegistered() {
|
||||||
native.OpenURL(branding.Website)
|
registerText = "Registration"
|
||||||
})
|
}
|
||||||
helpMenu.AddItem("Guidebook Online", func() {
|
helpMenu.AddItem(registerText, func() {
|
||||||
native.OpenURL(branding.GuidebookURL)
|
u.licenseWindow.Show()
|
||||||
})
|
})
|
||||||
|
|
||||||
menu.Supervise(u.Supervisor)
|
menu.Supervise(u.Supervisor)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||||
|
@ -24,7 +25,8 @@ import (
|
||||||
|
|
||||||
// MainScene implements the main menu of Doodle.
|
// MainScene implements the main menu of Doodle.
|
||||||
type MainScene struct {
|
type MainScene struct {
|
||||||
Supervisor *ui.Supervisor
|
Supervisor *ui.Supervisor
|
||||||
|
LevelFilename string // custom level filename to load in background
|
||||||
|
|
||||||
// Background wallpaper canvas.
|
// Background wallpaper canvas.
|
||||||
scripting *scripting.Supervisor
|
scripting *scripting.Supervisor
|
||||||
|
@ -296,8 +298,16 @@ func (s *MainScene) SetupDemoLevel(d *Doodle) error {
|
||||||
s.scripting = scripting.NewSupervisor()
|
s.scripting = scripting.NewSupervisor()
|
||||||
s.canvas.SetScriptSupervisor(s.scripting)
|
s.canvas.SetScriptSupervisor(s.scripting)
|
||||||
|
|
||||||
// Title screen level to load.
|
// Title screen level to load. Pick a random level.
|
||||||
if lvl, err := level.LoadFile(balance.DemoLevelName); err == nil {
|
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.LoadLevel(lvl)
|
||||||
s.canvas.InstallActors(lvl.Actors)
|
s.canvas.InstallActors(lvl.Actors)
|
||||||
|
|
||||||
|
@ -312,6 +322,16 @@ func (s *MainScene) SetupDemoLevel(d *Doodle) error {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Error("Error loading demo level %s: %s", balance.DemoLevelName, err)
|
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
|
return nil
|
||||||
|
|
|
@ -50,9 +50,11 @@ type PlayScene struct {
|
||||||
cheated bool // user has entered a cheat code while playing
|
cheated bool // user has entered a cheat code while playing
|
||||||
|
|
||||||
// UI widgets.
|
// UI widgets.
|
||||||
supervisor *ui.Supervisor
|
supervisor *ui.Supervisor
|
||||||
screen *ui.Frame // A window sized invisible frame to position UI elements.
|
screen *ui.Frame // A window sized invisible frame to position UI elements.
|
||||||
editButton *ui.Button
|
menubar *ui.MenuBar
|
||||||
|
editButton *ui.Button
|
||||||
|
winLevelPacks *ui.Window
|
||||||
|
|
||||||
// Custom debug labels.
|
// Custom debug labels.
|
||||||
debPosition *string
|
debPosition *string
|
||||||
|
@ -119,6 +121,13 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
|
||||||
s.screen = ui.NewFrame("Screen")
|
s.screen = ui.NewFrame("Screen")
|
||||||
s.screen.Resize(render.NewRect(d.width, d.height))
|
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.
|
// Level Exit handler.
|
||||||
s.scripting.OnLevelExit(s.BeatLevel)
|
s.scripting.OnLevelExit(s.BeatLevel)
|
||||||
s.scripting.OnLevelFail(s.FailLevel)
|
s.scripting.OnLevelFail(s.FailLevel)
|
||||||
|
@ -620,6 +629,9 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
||||||
// Visualize the touch regions?
|
// Visualize the touch regions?
|
||||||
s.DrawTouchable()
|
s.DrawTouchable()
|
||||||
|
|
||||||
|
// Let Supervisor draw menus
|
||||||
|
s.supervisor.Present(d.Engine)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
79
pkg/play_scene_menubar.go
Normal file
79
pkg/play_scene_menubar.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -28,6 +28,13 @@ func (s *PlayScene) LoopTouchable(ev *event.State) {
|
||||||
cursor = render.NewPoint(ev.CursorX, ev.CursorY)
|
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.
|
// Detect if the player is idle.
|
||||||
// Idle means that they are not holding any directional or otherwise input key.
|
// 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.
|
// Keyboard inputs and touch events from this function will set these keys.
|
||||||
|
|
|
@ -42,7 +42,8 @@ func NewJSProxy(vm *VM) JSProxy {
|
||||||
return shmem.Tick
|
return shmem.Tick
|
||||||
},
|
},
|
||||||
"time": map[string]interface{}{
|
"time": map[string]interface{}{
|
||||||
"Now": time.Now,
|
"Now": time.Now,
|
||||||
|
"Since": time.Since,
|
||||||
"Add": func(t time.Time, ms int64) time.Time {
|
"Add": func(t time.Time, ms int64) time.Time {
|
||||||
return t.Add(time.Duration(ms) * time.Millisecond)
|
return t.Add(time.Duration(ms) * time.Millisecond)
|
||||||
},
|
},
|
||||||
|
|
|
@ -36,6 +36,7 @@ type Settings struct {
|
||||||
CrosshairSize int `json:",omitempty"`
|
CrosshairSize int `json:",omitempty"`
|
||||||
CrosshairColor render.Color
|
CrosshairColor render.Color
|
||||||
HideTouchHints bool `json:",omitempty"`
|
HideTouchHints bool `json:",omitempty"`
|
||||||
|
DisableAutosave bool `json:",omitempty"`
|
||||||
|
|
||||||
// Secret boolprops from balance/boolprops.go
|
// Secret boolprops from balance/boolprops.go
|
||||||
ShowHiddenDoodads bool `json:",omitempty"`
|
ShowHiddenDoodads bool `json:",omitempty"`
|
||||||
|
|
|
@ -39,7 +39,7 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
|
||||||
|
|
||||||
// size of the popup window
|
// size of the popup window
|
||||||
width = 320
|
width = 320
|
||||||
height = 340
|
height = 360
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get the available .levelpack files.
|
// Get the available .levelpack files.
|
||||||
|
@ -275,7 +275,7 @@ func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp
|
||||||
buttonWidth = width - 40
|
buttonWidth = width - 40
|
||||||
|
|
||||||
page = 1
|
page = 1
|
||||||
perPage = 3
|
perPage = 4
|
||||||
pages = int(
|
pages = int(
|
||||||
math.Ceil(
|
math.Ceil(
|
||||||
float64(len(lp.Levels)) / float64(perPage),
|
float64(len(lp.Levels)) / float64(perPage),
|
||||||
|
|
|
@ -77,25 +77,37 @@ func NewPiPWindow(cfg PiP) *ui.Window {
|
||||||
canvas.Editable = true
|
canvas.Editable = true
|
||||||
canvas.Resize(render.NewRect(canvasWidth, canvasHeight))
|
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"
|
// NOTE: my UI toolkit calls this every tick, if this is "fixed"
|
||||||
// in the future make one that does.
|
// 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 {
|
window.Handle(ui.MouseMove, func(ed ui.EventData) error {
|
||||||
canvas.Loop(cfg.Event)
|
canvas.Loop(cfg.Event)
|
||||||
|
|
||||||
// Check if bound values have modified.
|
// Did we have tool bindings for an editable PiP?
|
||||||
if *cfg.Tool != curTool {
|
if editable {
|
||||||
curTool = *cfg.Tool
|
// Check if bound values have modified.
|
||||||
canvas.Tool = curTool
|
if *cfg.Tool != curTool {
|
||||||
}
|
curTool = *cfg.Tool
|
||||||
if *cfg.BrushSize != curThicc {
|
canvas.Tool = curTool
|
||||||
curThicc = *cfg.BrushSize
|
}
|
||||||
canvas.BrushSize = curThicc
|
if *cfg.BrushSize != curThicc {
|
||||||
|
curThicc = *cfg.BrushSize
|
||||||
|
canvas.BrushSize = curThicc
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ type Settings struct {
|
||||||
CrosshairSize *int
|
CrosshairSize *int
|
||||||
CrosshairColor *render.Color
|
CrosshairColor *render.Color
|
||||||
HideTouchHints *bool
|
HideTouchHints *bool
|
||||||
|
DisableAutosave *bool
|
||||||
|
|
||||||
// Configuration options.
|
// Configuration options.
|
||||||
SceneName string // name of scene which called this window
|
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,
|
PadX: 4,
|
||||||
name: "toolbars",
|
name: "toolbars",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Boolean: c.DisableAutosave,
|
||||||
|
Text: "Disable auto-save in the Editor",
|
||||||
|
PadX: 4,
|
||||||
|
name: "autosave",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Integer: c.CrosshairSize,
|
Integer: c.CrosshairSize,
|
||||||
Text: "Editor: Crosshair size (0 to disable):",
|
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" +
|
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" +
|
"Profile Directory, which you can access below:",
|
||||||
"by others to use them in your game. Click on the button below\n" +
|
|
||||||
"to (hopefully) be taken to your Profile Directory:",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
|
@ -634,20 +639,11 @@ func (c Settings) makeExperimentalTab(tabFrame *ui.TabFrame, Width, Height int)
|
||||||
PadY: 2,
|
PadY: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: "Zoom In/Out",
|
Header: "Viewport window",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Text: "This adds Zoom options to the level editor. It has a few\n" +
|
Text: "This option in the Level menu opens another view into\n" +
|
||||||
"bugs around scrolling but may be useful already.",
|
"the level. Has glitchy wallpaper problems.",
|
||||||
PadY: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: "Replace Level Palette",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "This adds an option to the Level Properties dialog to\n" +
|
|
||||||
"replace your level palette with one of the defaults,\n" +
|
|
||||||
"like on the New Level screen. It might not actually work.",
|
|
||||||
PadY: 2,
|
PadY: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue
Block a user