doodle/pkg/level/publishing/publishing.go
Noah Petherbridge 7093b102e3 Embeddable Doodads In Levels
* The Publisher is all hooked up. No native Save File dialogs yet, so
  uses the dev shell Prompt() to ask for output filename.
* Custom-only or builtin doodads too can be stored in the level's file
  data, at "assets/doodads/*.doodad"
* When loading the embedded level in the Editor: it gets its custom
  doodads out of its file, and you can drag and drop them elsehwere,
  link them, Play Mode can use them, etc. but they won't appear in the
  Doodad Dropper if they are not installed in your local doodads
  directory.
* Fleshed out serialization API for the Doodad files:
  - LoadFromEmbeddable() looks to load a doodad from embeddable file
    data in addition to the usual places.
  - Serialize() returns the doodad in bytes, for easy access to embed
    into level data.
  - Deserialize() to parse and return from bytes.
* When loading a level that references doodads not found in its embedded
  data or the filesystem: an Alert modal appears listing the missing
  doodads. The rest of the level loads fine, but the actors referenced
  by these doodads don't load.
2021-06-13 14:59:03 -07:00

95 lines
2.5 KiB
Go

/*
Package publishing contains functionality for "publishing" a Level, which
involves the writing and reading of custom doodads embedded inside
the levels.
Free tiers of the game will not read or write embedded doodads into
levels.
*/
package publishing
import (
"fmt"
"sort"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
)
/*
Level "Publishing" functions, involving the writing and reading of embedded
doodads within level files.
*/
// Publish writes a published level file, with embedded doodads included.
func Publish(lvl *level.Level, filename string, includeBuiltins bool) (*level.Level, error) {
// Get and embed the doodads.
builtins, customs := GetUsedDoodadNames(lvl)
if includeBuiltins {
log.Debug("including builtins: %+v", builtins)
customs = append(customs, builtins...)
}
for _, filename := range customs {
log.Debug("Embed filename: %s", filename)
doodad, err := doodads.LoadFromEmbeddable(filename, lvl)
if err != nil {
return nil, fmt.Errorf("couldn't load doodad %s: %s", filename, err)
}
bin, err := doodad.Serialize()
if err != nil {
return nil, fmt.Errorf("couldn't serialize doodad %s: %s", filename, err)
}
// Embed it.
lvl.SetFile(balance.EmbeddedDoodadsBasePath+filename, bin)
}
log.Info("Publish: write file to %s", filename)
err := lvl.WriteFile(filename)
return lvl, err
}
// GetUsedDoodadNames returns the lists of doodad filenames in use in a level,
// bucketed by built-in or custom user doodads.
func GetUsedDoodadNames(lvl *level.Level) (builtin []string, custom []string) {
// Collect all the doodad names in use in this level.
unique := map[string]interface{}{}
names := []string{}
if lvl != nil {
for _, actor := range lvl.Actors {
if _, ok := unique[actor.Filename]; ok {
continue
}
unique[actor.Filename] = nil
names = append(names, actor.Filename)
}
}
// Identify which of the doodads are built-ins.
// builtin = []string{}
builtinMap := map[string]interface{}{}
// custom := []string{}
if builtins, err := doodads.ListBuiltin(); err == nil {
for _, filename := range builtins {
if _, ok := unique[filename]; ok {
builtin = append(builtin, filename)
builtinMap[filename] = nil
}
}
}
for _, name := range names {
if _, ok := builtinMap[name]; ok {
continue
}
custom = append(custom, name)
}
sort.Strings(builtin)
sort.Strings(custom)
return builtin, custom
}