diff --git a/pkg/level/chunker.go b/pkg/level/chunker.go index 614bfda..fa050eb 100644 --- a/pkg/level/chunker.go +++ b/pkg/level/chunker.go @@ -43,6 +43,7 @@ type Chunker struct { requestsN1 map[render.Point]interface{} // chunks accessed last tick requestsN2 map[render.Point]interface{} // 2 ticks ago (to free soon) chunksToFree map[render.Point]uint64 // chopping block (free after X ticks) + ctfMu sync.Mutex // lock for chunksToFree requestMu sync.Mutex // The palette reference from first call to Inflate() @@ -299,6 +300,11 @@ func (c *Chunker) GetChunk(p render.Point) (*Chunk, bool) { chunk, ok := c.Chunks[p] c.chunkMu.RUnlock() + // Was it on the chopping block for garbage collection? + c.ctfMu.Lock() + delete(c.chunksToFree, p) + c.ctfMu.Unlock() + if ok { // An empty chunk? We hang onto these until save time to commit // the empty chunk to ZIP. @@ -392,6 +398,7 @@ func (c *Chunker) FreeCaches() int { // Chunks requested 2 ticks ago but not this tick, put on the chopping // block to free them later. + c.ctfMu.Lock() for coord := range requestsN2 { // Old point not requested recently? if _, ok := requestsThisTick[coord]; !ok { @@ -411,6 +418,7 @@ func (c *Chunker) FreeCaches() int { delete(c.chunksToFree, coord) c.FreeChunk(coord) } + c.ctfMu.Unlock() // Rotate the cached ticks and clean the slate. c.requestsN2 = c.requestsN1 diff --git a/pkg/modal/end_level.go b/pkg/modal/end_level.go index 3119377..4ed0180 100644 --- a/pkg/modal/end_level.go +++ b/pkg/modal/end_level.go @@ -18,11 +18,13 @@ type ConfigEndLevel struct { Success bool // false = failure condition // Handler functions - what you don't define will not - // show as buttons in the modal. + // show as buttons in the modal. Buttons display priority + // is as follows these docs in the source code. + OnNextLevel func() // Next Level (victory window) + OnRetryCheckpoint func() // Retry from Checkpoint (failed) + OnPityNextLevel func() // Pity "Next Level" (Azulian Tag) OnRestartLevel func() // Restart Level - OnRetryCheckpoint func() // Continue from checkpoint - OnEditLevel func() - OnNextLevel func() // Next Level + OnEditLevel func() // Edit Level (if came from editor) OnExitToMenu func() // Exit to Menu // Set these values to show the "New Record!" part of the modal. @@ -152,6 +154,10 @@ func makeEndLevel(m *Modal, cfg ConfigEndLevel) *ui.Window { Label: "Restart Level", F: cfg.OnRestartLevel, }, + { + Label: "Next Level", + F: cfg.OnPityNextLevel, + }, { Label: "Edit Level", F: cfg.OnEditLevel, diff --git a/pkg/play_scene.go b/pkg/play_scene.go index fbdfd8e..3866f29 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -561,8 +561,8 @@ func (s *PlayScene) FailLevel(message string) { if s.Level.GameRule.Survival { s.ShowEndLevelModal( - true, - "Level Completed", + false, + "You've died!", fmt.Sprintf( "%s\nCongrats on surviving for %s!", message, @@ -639,6 +639,15 @@ func (s *PlayScene) ShowEndLevelModal(success bool, title, message string) { config.OnEditLevel = s.EditLevel } + // Survival Mode failure? The level is considered completed even if you + // die (silver high score) but the default button should be Retry rather + // than Next Level. + var survivalFailure bool + if !success && s.Level.GameRule.Survival { + survivalFailure = true + success = true // level is completed + } + // Beaten the level? if success { config.OnRetryCheckpoint = nil @@ -688,6 +697,14 @@ func (s *PlayScene) ShowEndLevelModal(success bool, title, message string) { } } + // Survival Mode failures: the Retry buttons should be higher + // priority than Next Level but they still get the (pity) + // Next Level button. + if survivalFailure { + config.OnPityNextLevel = config.OnNextLevel + config.OnNextLevel = nil + } + // Show the modal. modal.EndLevel(config, title, message)