Make Fire Deadly

* Touching "fire" pixels in a level will pop up the End Level alert box
  saying you've died by fire and can restart the level.
* Update level.WriteFile() to prune broken links between actors before
  save. So when a linked actor is deleted, the leftover link data is
  cleaned up.
* Slight optimization in Canvas.drawStrokes: if either end of the stroke
  is not within view of the screen, don't show the stroke.
physics
Noah 2019-07-06 20:31:50 -07:00
parent cb02feff1d
commit 6476a67faf
6 changed files with 99 additions and 20 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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