diff --git a/dev-assets/doodads/build.sh b/dev-assets/doodads/build.sh index 33dd6ea..5b91a4d 100755 --- a/dev-assets/doodads/build.sh +++ b/dev-assets/doodads/build.sh @@ -86,7 +86,19 @@ azulians() { cd .. } +objects() { + cd objects/ + + doodad convert -t "Exit Flag" exit-flag.png exit-flag.doodad + doodad install-script exit-flag.js exit-flag.doodad + + cp *.doodad ../../../assets/doodads/ + + cd .. +} + buttons doors trapdoors azulians +objects diff --git a/dev-assets/doodads/objects/exit-flag.js b/dev-assets/doodads/objects/exit-flag.js new file mode 100644 index 0000000..056890c --- /dev/null +++ b/dev-assets/doodads/objects/exit-flag.js @@ -0,0 +1,11 @@ +// Exit Flag. +function main() { + console.log("%s initialized!", Self.Doodad.Title); + Self.SetHitbox(22+16, 16, 75-16, 86); + + Events.OnCollide(function(e) { + if (e.InHitbox) { + EndLevel(); + } + }); +} diff --git a/dev-assets/doodads/objects/exit-flag.png b/dev-assets/doodads/objects/exit-flag.png new file mode 100644 index 0000000..585152d Binary files /dev/null and b/dev-assets/doodads/objects/exit-flag.png differ diff --git a/pkg/doodle.go b/pkg/doodle.go index d158638..12425cd 100644 --- a/pkg/doodle.go +++ b/pkg/doodle.go @@ -2,6 +2,7 @@ package doodle import ( "fmt" + "path/filepath" "strings" "time" @@ -223,11 +224,7 @@ func (d *Doodle) NewDoodad(size int) { // EditDrawing loads a drawing (Level or Doodad) in Edit Mode. func (d *Doodle) EditDrawing(filename string) error { log.Info("Loading drawing from file: %s", filename) - parts := strings.Split(filename, ".") - if len(parts) < 2 { - return fmt.Errorf("filename `%s` has no file extension", filename) - } - ext := strings.ToLower(parts[len(parts)-1]) + ext := strings.ToLower(filepath.Ext(filename)) scene := &EditorScene{ Filename: filename, @@ -235,11 +232,11 @@ func (d *Doodle) EditDrawing(filename string) error { } switch ext { - case "level": - case "map": + case ".level": + case ".map": log.Info("is a Level type") scene.DrawingType = enum.LevelDrawing - case "doodad": + case ".doodad": if balance.FreeVersion { return fmt.Errorf("Doodad editor not supported in your version of the game") } diff --git a/pkg/editor_scene.go b/pkg/editor_scene.go index a758103..88b86a4 100644 --- a/pkg/editor_scene.go +++ b/pkg/editor_scene.go @@ -131,6 +131,7 @@ func (s *EditorScene) Playtest() { s.d.Goto(&PlayScene{ Filename: s.filename, Level: s.Level, + CanEdit: true, }) } @@ -167,7 +168,7 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error { // Draw the current frame. func (s *EditorScene) Draw(d *Doodle) error { // Clear the canvas and fill it with magenta so it's clear if any spots are missed. - d.Engine.Clear(render.Magenta) + d.Engine.Clear(render.RGBA(160, 120, 160, 255)) s.UI.Present(d.Engine) diff --git a/pkg/editor_ui.go b/pkg/editor_ui.go index d36bf70..67a05cc 100644 --- a/pkg/editor_ui.go +++ b/pkg/editor_ui.go @@ -148,6 +148,7 @@ func (u *EditorUI) Resized(d *Doodle) { // Position the workspace around with the other widgets. { + frame := u.Workspace frame.MoveTo(render.NewPoint( 0, diff --git a/pkg/play_scene.go b/pkg/play_scene.go index 79161db..36dce4b 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -19,16 +19,27 @@ type PlayScene struct { // Configuration attributes. Filename string Level *level.Level + CanEdit bool // i.e. you came from the Editor Mode + HasNext bool // has a next level to load next // Private variables. d *Doodle drawing *uix.Canvas scripting *scripting.Supervisor + running bool // UI widgets. supervisor *ui.Supervisor editButton *ui.Button + // The alert box shows up when the level goal is reached and includes + // buttons what to do next. + alertBox *ui.Window + alertReplayButton *ui.Button // Replay level + alertEditButton *ui.Button // Edit Level + alertNextButton *ui.Button // Next Level + alertExitButton *ui.Button // Exit to menu + // Custom debug labels. debPosition *string debViewport *string @@ -50,6 +61,30 @@ func (s *PlayScene) Setup(d *Doodle) error { s.scripting = scripting.NewSupervisor() s.supervisor = ui.NewSupervisor() + // Level Exit handler. + s.SetupAlertbox() + s.scripting.OnLevelExit(func() { + d.Flash("Hurray!") + + // Pause the simulation. + s.running = false + + // Toggle the relevant buttons on. + if s.CanEdit { + s.alertEditButton.Show() + } + if s.HasNext { + s.alertNextButton.Show() + } + + // Always-visible buttons. + s.alertReplayButton.Show() + s.alertExitButton.Show() + + // Show the alert box. + s.alertBox.Show() + }) + // Initialize debug overlay values. s.debPosition = new(string) s.debViewport = new(string) @@ -126,10 +161,90 @@ func (s *PlayScene) Setup(d *Doodle) error { } d.Flash("Entered Play Mode. Press 'E' to edit this map.") + s.running = true return nil } +// SetupAlertbox configures the alert box UI. +func (s *PlayScene) SetupAlertbox() { + window := ui.NewWindow("Level Completed") + window.Configure(ui.Config{ + Width: 320, + Height: 160, + Background: render.Grey, + }) + window.Compute(s.d.Engine) + + { + frame := ui.NewFrame("Open Drawing Frame") + window.Pack(frame, ui.Pack{ + Anchor: ui.N, + Fill: true, + Expand: true, + }) + + /****************** + * Frame for selecting User Levels + ******************/ + + label1 := ui.NewLabel(ui.Label{ + Text: "Congratulations on clearing the level!", + Font: balance.LabelFont, + }) + frame.Pack(label1, ui.Pack{ + Anchor: ui.N, + FillX: true, + PadY: 16, + }) + + /****************** + * Confirm/cancel buttons. + ******************/ + + bottomFrame := ui.NewFrame("Button Frame") + frame.Pack(bottomFrame, ui.Pack{ + Anchor: ui.N, + FillX: true, + PadY: 8, + }) + + // Button factory for the various options. + makeButton := func(text string, handler func()) *ui.Button { + btn := ui.NewButton(text, ui.NewLabel(ui.Label{ + Font: balance.LabelFont, + Text: text, + })) + btn.Handle(ui.Click, func(p render.Point) { + handler() + }) + bottomFrame.Pack(btn, ui.Pack{ + Anchor: ui.W, + PadX: 2, + }) + s.supervisor.Add(btn) + btn.Hide() // all buttons hidden by default + return btn + } + + s.alertReplayButton = makeButton("Play Again", func() { + s.RestartLevel() + }) + s.alertEditButton = makeButton("Edit Level", func() { + s.EditLevel() + }) + s.alertNextButton = makeButton("Next Level", func() { + s.d.Flash("Not Implemented") + }) + s.alertExitButton = makeButton("Exit to Menu", func() { + s.d.Goto(&MainScene{}) + }) + } + + s.alertBox = window + s.alertBox.Hide() +} + // EditLevel toggles out of Play Mode to edit the level. func (s *PlayScene) EditLevel() { log.Info("Edit Mode, Go!") @@ -139,6 +254,16 @@ func (s *PlayScene) EditLevel() { }) } +// RestartLevel starts the level over again. +func (s *PlayScene) RestartLevel() { + log.Info("Restart Level") + s.d.Goto(&PlayScene{ + Filename: s.Filename, + Level: s.Level, + CanEdit: s.CanEdit, + }) +} + // Loop the editor scene. func (s *PlayScene) Loop(d *Doodle, ev *events.State) error { // Update debug overlay values. @@ -166,14 +291,17 @@ func (s *PlayScene) Loop(d *Doodle, ev *events.State) error { return nil } - // Loop the script supervisor so timeouts/intervals can fire in scripts. - if err := s.scripting.Loop(); err != nil { - log.Error("PlayScene.Loop: scripting.Loop: %s", err) - } + // Is the simulation still running? + if s.running { + // Loop the script supervisor so timeouts/intervals can fire in scripts. + if err := s.scripting.Loop(); err != nil { + log.Error("PlayScene.Loop: scripting.Loop: %s", err) + } - s.movePlayer(ev) - if err := s.drawing.Loop(ev); err != nil { - log.Error("Drawing loop error: %s", err.Error()) + s.movePlayer(ev) + if err := s.drawing.Loop(ev); err != nil { + log.Error("Drawing loop error: %s", err.Error()) + } } return nil @@ -201,6 +329,16 @@ func (s *PlayScene) Draw(d *Doodle) error { Y: canSize.H - size.H - padding, }) + // Draw the alert box window. + if !s.alertBox.Hidden() { + s.alertBox.Compute(d.Engine) + s.alertBox.MoveTo(render.Point{ + X: int32(d.width/2) - (s.alertBox.Size().W / 2), + Y: int32(d.height/2) - (s.alertBox.Size().H / 2), + }) + s.alertBox.Present(d.Engine, s.alertBox.Point()) + } + return nil } @@ -248,14 +386,14 @@ func (s *PlayScene) Drawing() *uix.Canvas { func (s *PlayScene) LoadLevel(filename string) error { s.Filename = filename - level, err := level.LoadJSON(filename) + level, err := level.LoadFile(filename) if err != nil { return fmt.Errorf("PlayScene.LoadLevel(%s): %s", filename, err) } s.Level = level s.drawing.LoadLevel(s.d.Engine, s.Level) - // s.drawing.InstallActors(s.Level.Actors) + s.drawing.InstallActors(s.Level.Actors) return nil } diff --git a/pkg/scripting/scripting.go b/pkg/scripting/scripting.go index 001fcc3..910a21b 100644 --- a/pkg/scripting/scripting.go +++ b/pkg/scripting/scripting.go @@ -15,6 +15,9 @@ import ( // unique ID. type Supervisor struct { scripts map[string]*VM + + // Global event handlers. + onLevelExit func() } // NewSupervisor creates a new JavaScript Supervior. @@ -67,6 +70,7 @@ func (s *Supervisor) AddLevelScript(id string) error { s.scripts[id] = NewVM(id) RegisterPublishHooks(s.scripts[id]) + RegisterEventHooks(s, s.scripts[id]) if err := s.scripts[id].RegisterLevelHooks(); err != nil { return err } diff --git a/pkg/scripting/supervisor_events.go b/pkg/scripting/supervisor_events.go new file mode 100644 index 0000000..ca2a80a --- /dev/null +++ b/pkg/scripting/supervisor_events.go @@ -0,0 +1,23 @@ +package scripting + +/* +RegisterEventHooks attaches the supervisor level event hooks into a JS VM. + +Names registered: + +- EndLevel(): for a doodad to exit the level. Panics if the OnLevelExit + handler isn't defined. +*/ +func RegisterEventHooks(s *Supervisor, vm *VM) { + vm.Set("EndLevel", func() { + if s.onLevelExit == nil { + panic("JS EndLevel(): no OnLevelExit handler attached to script supervisor") + } + s.onLevelExit() + }) +} + +// OnLevelExit registers an event hook for when a Level Exit doodad is reached. +func (s *Supervisor) OnLevelExit(handler func()) { + s.onLevelExit = handler +}