diff --git a/pkg/filesystem/filesystem.go b/pkg/filesystem/filesystem.go index 956f24d..ad37888 100644 --- a/pkg/filesystem/filesystem.go +++ b/pkg/filesystem/filesystem.go @@ -85,6 +85,11 @@ Returns the file path and an error if not found anywhere. func FindFile(filename string) (string, error) { var filetype string + // If the filename has path separators, return it as-is. + if strings.ContainsRune(filename, filepath.Separator) { + return filename, nil + } + // Any hint on what type of file we're looking for? if strings.HasSuffix(filename, enum.LevelExt) { filetype = enum.LevelExt diff --git a/pkg/level/fmt_maintenance.go b/pkg/level/fmt_maintenance.go new file mode 100644 index 0000000..de51671 --- /dev/null +++ b/pkg/level/fmt_maintenance.go @@ -0,0 +1,35 @@ +package level + +import "git.kirsle.net/apps/doodle/pkg/log" + +// Maintenance functions for the file format on disk. + +// PruneLinks cleans up any Actor Links that can not be resolved in the +// level data. For example, if actors were linked in Edit Mode and one +// actor is deleted leaving a broken link. +// +// Returns the number of broken links pruned. +// +// This is called automatically in WriteFile. +func (m *Level) PruneLinks() int { + var count int + for id, actor := range m.Actors { + var newLinks []string + + for _, linkID := range actor.Links { + if _, ok := m.Actors[linkID]; !ok { + log.Warn("Level.PruneLinks: actor %s (%s) was linked to unresolved actor %s", + id, + actor.Filename, + linkID, + ) + count++ + continue + } + newLinks = append(newLinks, linkID) + } + + actor.Links = newLinks + } + return count +} diff --git a/pkg/level/fmt_readwrite.go b/pkg/level/fmt_readwrite.go index ca39bf3..ef74afa 100644 --- a/pkg/level/fmt_readwrite.go +++ b/pkg/level/fmt_readwrite.go @@ -104,6 +104,9 @@ func (m *Level) WriteFile(filename string) error { m.Version = 1 m.GameVersion = branding.Version + // Maintenance functions, clean up cruft before save. + m.PruneLinks() + bin, err := m.ToJSON() if err != nil { return err diff --git a/pkg/main_scene.go b/pkg/main_scene.go index 72819f8..0e25912 100644 --- a/pkg/main_scene.go +++ b/pkg/main_scene.go @@ -6,7 +6,6 @@ import ( "git.kirsle.net/apps/doodle/lib/ui" "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/branding" - "git.kirsle.net/apps/doodle/pkg/filesystem" "git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/scripting" @@ -89,26 +88,25 @@ func (s *MainScene) SetupDemoLevel(d *Doodle) error { H: int32(d.height), }) - // Title screen level to load. - lvlName, _ := filesystem.FindFile("example1.level") - lvl, err := level.LoadJSON(lvlName) - if err != nil { - log.Error("Error loading title-screen.level: %s", err) - } - - s.canvas.LoadLevel(d.Engine, lvl) - s.canvas.InstallActors(lvl.Actors) - - // Load all actor scripts. s.scripting = scripting.NewSupervisor() s.canvas.SetScriptSupervisor(s.scripting) - if err := s.scripting.InstallScripts(lvl); err != nil { - log.Error("Error with title screen level scripts: %s", err) - } - // Run all actors scripts main function to start them off. - if err := s.canvas.InstallScripts(); err != nil { - log.Error("Error running actor main() functions: %s", err) + // Title screen level to load. + if lvl, err := level.LoadFile("example1.level"); err == nil { + s.canvas.LoadLevel(d.Engine, lvl) + s.canvas.InstallActors(lvl.Actors) + + // Load all actor scripts. + if err := s.scripting.InstallScripts(lvl); err != nil { + log.Error("Error with title screen level scripts: %s", err) + } + + // Run all actors scripts main function to start them off. + if err := s.canvas.InstallScripts(); err != nil { + log.Error("Error running actor main() functions: %s", err) + } + } else { + log.Error("Error loading title-screen.level: %s", err) } return nil diff --git a/pkg/play_scene.go b/pkg/play_scene.go index 549c66e..ed3e8e2 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -36,6 +36,7 @@ type PlayScene struct { // The alert box shows up when the level goal is reached and includes // buttons what to do next. alertBox *ui.Window + alertBoxLabel *ui.Label alertReplayButton *ui.Button // Replay level alertEditButton *ui.Button // Edit Level alertNextButton *ui.Button // Next Level @@ -120,6 +121,7 @@ func (s *PlayScene) Setup(d *Doodle) error { s.drawing.OnLevelCollision = func(a *uix.Actor, col *collision.Collide) { if col.InFire { a.Canvas.MaskColor = render.Black + s.DieByFire() } else if col.InWater { a.Canvas.MaskColor = render.DarkBlue } else { @@ -201,11 +203,11 @@ func (s *PlayScene) SetupAlertbox() { * Frame for selecting User Levels ******************/ - label1 := ui.NewLabel(ui.Label{ + s.alertBoxLabel = ui.NewLabel(ui.Label{ Text: "Congratulations on clearing the level!", Font: balance.LabelFont, }) - frame.Pack(label1, ui.Pack{ + frame.Pack(s.alertBoxLabel, ui.Pack{ Anchor: ui.N, FillX: true, PadY: 16, @@ -277,6 +279,24 @@ func (s *PlayScene) RestartLevel() { }) } +// DieByFire ends the level by fire. +func (s *PlayScene) DieByFire() { + log.Info("Watch out for fire!") + s.alertBox.Title = "You've died!" + s.alertBoxLabel.Text = "Watch out for fire!" + + s.alertReplayButton.Show() + if s.CanEdit { + s.alertEditButton.Show() + } + s.alertExitButton.Show() + + s.alertBox.Show() + + // Stop the simulation. + s.running = false +} + // Loop the editor scene. func (s *PlayScene) Loop(d *Doodle, ev *events.State) error { // Update debug overlay values. diff --git a/pkg/uix/canvas_strokes.go b/pkg/uix/canvas_strokes.go index c49b79b..1e29452 100644 --- a/pkg/uix/canvas_strokes.go +++ b/pkg/uix/canvas_strokes.go @@ -188,6 +188,24 @@ func (w *Canvas) drawStrokes(e render.Engine, strokes []*drawtool.Stroke) { ) for _, stroke := range strokes { + // If none of this stroke is in our viewport, don't waste time + // looping through it. + if stroke.Shape == drawtool.Freehand { + if len(stroke.Points) >= 2 { + if !stroke.Points[0].Inside(VP) && !stroke.Points[len(stroke.Points)-1].Inside(VP) { + continue + } + } + } else { + // TODO: a very long line that starts and ends outside the viewport + // but passes thru it would disappear when both ends are out of + // view. + if !stroke.PointA.Inside(VP) && !stroke.PointB.Inside(VP) { + continue + } + } + + // Iter the points and draw what's visible. for point := range stroke.IterPoints() { if !point.Inside(VP) { continue