package level import ( "archive/zip" "encoding/json" "fmt" "git.kirsle.net/SketchyMaze/doodle/pkg/balance" "git.kirsle.net/SketchyMaze/doodle/pkg/drawtool" "git.kirsle.net/SketchyMaze/doodle/pkg/enum" "git.kirsle.net/SketchyMaze/doodle/pkg/log" "git.kirsle.net/SketchyMaze/doodle/pkg/native" "git.kirsle.net/go/render" "git.kirsle.net/go/ui" ) // Useful variables. var ( DefaultWallpaper = "notebook.png" ) // Base provides the common struct keys that are shared between Levels and // Doodads. type Base struct { Version int `json:"version"` // File format version spec. GameVersion string `json:"gameVersion"` // Game version that created the level. Title string `json:"title"` Author string `json:"author"` Locked bool `json:"locked"` // v2 level format: zip files with external chunks. // (v0 was json text, v1 was gzip compressed json text). // The game must load levels created using the previous // formats, they will not have a Zipfile and will have // Chunkers in memory from their (gz) json. Zipfile *zip.Reader `json:"-"` // Every drawing type is able to embed other files inside of itself. Files *FileSystem `json:"files,omitempty"` } // Level is the container format for Doodle map drawings. type Level struct { Base Password string `json:"passwd"` UUID string `json:"uuid"` // unique level IDs, especially for the savegame.json GameRule GameRule `json:"rules"` // Chunked pixel data. Chunker *Chunker `json:"chunks"` // The Palette holds the unique "colors" used in this map file, and their // properties (solid, fire, slippery, etc.) Palette *Palette `json:"palette"` // Page boundaries and wallpaper settings. PageType PageType `json:"pageType"` MaxWidth int64 `json:"boundedWidth"` // only if bounded or bordered MaxHeight int64 `json:"boundedHeight"` Wallpaper string `json:"wallpaper"` // The last scrolled position in the editor. ScrollPosition render.Point `json:"scroll"` // Actors keep a list of the doodad instances in this map. Actors ActorMap `json:"actors"` // Publishing: attach any custom doodads the map uses on save. SaveDoodads bool `json:"saveDoodads"` SaveBuiltins bool `json:"saveBuiltins"` // Signature for a level with embedded doodads to still play in free mode. Signature []byte `json:"signature,omitempty"` // Undo history, temporary live data not persisted to the level file. UndoHistory *drawtool.History `json:"-"` // Cache of loaded images (e.g. screenshots). cacheImages map[string]*ui.Image } // GameRule type GameRule struct { Difficulty enum.Difficulty `json:"difficulty"` Survival bool `json:"survival,omitempty"` } // New creates a blank level object with all its members initialized. func New() *Level { return &Level{ Base: Base{ Version: 1, Title: "Untitled", Author: native.DefaultAuthor, Files: NewFileSystem(), }, Chunker: NewChunker(balance.ChunkSize), Palette: &Palette{}, Actors: ActorMap{}, PageType: NoNegativeSpace, Wallpaper: DefaultWallpaper, MaxWidth: 2550, MaxHeight: 3300, UndoHistory: drawtool.NewHistory(balance.UndoHistory), } } // Teardown the level when the game is done with it. This frees up SDL2 cached // texture chunks and reclaims memory in ways the Go garbage collector can not. func (m *Level) Teardown() { var ( chunks int textures int ) // Free any CACHED chunks' memory. for chunk := range m.Chunker.IterCachedChunks() { freed := chunk.Teardown() chunks++ textures += freed } // Free any cached images (screenshots) if m.cacheImages != nil { for _, img := range m.cacheImages { img.Destroy() } } log.Debug("Teardown level (%s): Freed %d textures across %d level chunks", m.Title, textures, chunks) } // Pixel associates a coordinate with a palette index. type Pixel struct { X int `json:"x"` Y int `json:"y"` PaletteIndex int `json:"p"` // Private runtime values. Swatch *Swatch `json:"-"` // pointer to its swatch, for when rendered. } func (p Pixel) String() string { return fmt.Sprintf("Pixel<%s '%s' (%d,%d)>", p.Swatch.Color, p.Swatch.Name, p.X, p.Y) } // Point returns the pixel's point. func (p Pixel) Point() render.Point { return render.Point{ X: p.X, Y: p.Y, } } // MarshalJSON serializes a Pixel compactly as a simple list. func (p Pixel) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf( `[%d, %d, %d]`, p.X, p.Y, p.PaletteIndex, )), nil } // UnmarshalJSON loads a Pixel from JSON again. func (p *Pixel) UnmarshalJSON(text []byte) error { var triplet []int err := json.Unmarshal(text, &triplet) if err != nil { return err } p.X = triplet[0] p.Y = triplet[1] if len(triplet) > 2 { p.PaletteIndex = triplet[2] } return nil }