WIP: MsgPack stubs, Level Filesystem Module
* Add some encoding/decoding functions for binary msgpack format for levels and doodads. Currently it writes msgpack files that can be decoded and printed by Python (mp2json.py) but it can't re-read from the binary format. For now, levels will continue to write in JSON format. * Add filesystem abstraction functions to the balance/ package to search multiple paths to find Levels and Doodads, to make way for system-level doodads.
This commit is contained in:
parent
d042457365
commit
f76ba6fbb7
31
Building.md
31
Building.md
|
@ -4,6 +4,10 @@ Makefile commands for Linux:
|
|||
|
||||
* `make setup`: install Go dependencies and set up the build environment
|
||||
* `make build`: build the Doodle and Doodad binaries to the `bin/` folder.
|
||||
* `make build-free`: build the shareware binaries to the `bin/` folder. See
|
||||
Build Tags below.
|
||||
* `make build-debug`: build a debug binary (not release-mode) to the `bin/`
|
||||
folder. See Build Tags below.
|
||||
* `make run`: run a local dev build of Doodle in debug mode
|
||||
* `make guitest`: run a local dev build in the GUITest scene
|
||||
* `make test`: run the test suite
|
||||
|
@ -17,6 +21,33 @@ Makefile commands for Linux:
|
|||
* `make docker.fedora`
|
||||
* `make clean`: clean all build artifacts
|
||||
|
||||
## Build Tags
|
||||
|
||||
### shareware
|
||||
|
||||
> Files ending with `_free.go` are for the shareware release as opposed to
|
||||
> `_paid.go` for the full version.
|
||||
|
||||
Builds the game in the free shareware release mode.
|
||||
|
||||
Run `make build-free` to build the shareware binary.
|
||||
|
||||
Shareware releases of the game have the following changes compared to the default
|
||||
(release) mode:
|
||||
|
||||
* No access to the Doodad Editor scene in-game (soft toggle)
|
||||
|
||||
### developer
|
||||
|
||||
> Files ending with `_developer.go` are for the developer build as opposed to
|
||||
> `_release.go` for the public version.
|
||||
|
||||
Developer builds support extra features over the standard release version:
|
||||
|
||||
* Ability to write the JSON file format for Levels and Doodads.
|
||||
|
||||
Run `make build-debug` to build a developer version of the program.
|
||||
|
||||
## Linux
|
||||
|
||||
Dependencies are Go, SDL2 and SDL2_ttf:
|
||||
|
|
7
Makefile
7
Makefile
|
@ -27,6 +27,13 @@ build-free:
|
|||
go build $(LDFLAGS) -tags="shareware" -i -o bin/doodle cmd/doodle/main.go
|
||||
go build $(LDFLAGS) -tags="shareware" -i -o bin/doodad cmd/doodad/main.go
|
||||
|
||||
# `make build-debug` to build the binary in developer mode.
|
||||
.PHONY: build-debug
|
||||
build-debug:
|
||||
gofmt -w .
|
||||
go build $(LDFLAGS) -tags="developer" -i -o bin/doodle cmd/doodle/main.go
|
||||
go build $(LDFLAGS) -tags="developer" -i -o bin/doodad cmd/doodad/main.go
|
||||
|
||||
# `make doodads` to build the doodads from the dev-assets folder.
|
||||
.PHONY: doodads
|
||||
doodads:
|
||||
|
|
|
@ -1,4 +1,52 @@
|
|||
function main() {
|
||||
log.Info("Azulian '%s' initialized!", Self.Doodad.Title);
|
||||
Self.ShowLayer(2);
|
||||
|
||||
var playerSpeed = 12;
|
||||
var gravity = 4;
|
||||
var Vx = Vy = 0;
|
||||
|
||||
var animating = false;
|
||||
var animStart = animEnd = 0;
|
||||
var animFrame = animStart;
|
||||
|
||||
setInterval(function() {
|
||||
if (animating) {
|
||||
if (animFrame < animStart || animFrame > animEnd) {
|
||||
animFrame = animStart;
|
||||
}
|
||||
|
||||
animFrame++;
|
||||
if (animFrame === animEnd) {
|
||||
animFrame = animStart;
|
||||
}
|
||||
Self.ShowLayer(animFrame);
|
||||
} else {
|
||||
Self.ShowLayer(animStart);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
Events.OnKeypress(function(ev) {
|
||||
Vx = 0;
|
||||
Vy = 0;
|
||||
|
||||
if (ev.Right.Now) {
|
||||
animStart = 2;
|
||||
animEnd = animStart+4;
|
||||
animating = true;
|
||||
Vx = playerSpeed;
|
||||
} else if (ev.Left.Now) {
|
||||
animStart = 6;
|
||||
animEnd = animStart+4;
|
||||
animating = true;
|
||||
Vx = -playerSpeed;
|
||||
} else {
|
||||
animating = false;
|
||||
}
|
||||
|
||||
if (!Self.Grounded()) {
|
||||
Vy += gravity;
|
||||
}
|
||||
|
||||
// Self.SetVelocity(Point(Vx, Vy));
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
"image/color"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/vmihailenco/msgpack"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -155,6 +157,52 @@ func (c *Color) UnmarshalJSON(b []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c Color) EncodeMsgpack(enc *msgpack.Encoder) error {
|
||||
return enc.EncodeString(fmt.Sprintf(
|
||||
`"#%02x%02x%02x"`,
|
||||
c.Red, c.Green, c.Blue,
|
||||
))
|
||||
}
|
||||
|
||||
func (c Color) DecodeMsgpack(dec *msgpack.Decoder) error {
|
||||
hex, err := dec.DecodeString()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Color.DecodeMsgpack: %s", err)
|
||||
}
|
||||
|
||||
parsed, err := HexColor(hex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Color.DecodeMsgpack: HexColor: %s", err)
|
||||
}
|
||||
|
||||
c.Red = parsed.Red
|
||||
c.Blue = parsed.Blue
|
||||
c.Green = parsed.Green
|
||||
c.Alpha = parsed.Alpha
|
||||
return nil
|
||||
}
|
||||
|
||||
// // MarshalMsgpack serializes the Color for msgpack.
|
||||
// func (c Color) MarshalMsgpack() ([]byte, error) {
|
||||
// data := []uint8{
|
||||
// c.Red, c.Green, c.Blue, c.Alpha,
|
||||
// }
|
||||
// return msgpack.Marshal(data)
|
||||
// }
|
||||
//
|
||||
// // UnmarshalMsgpack decodes a Color from msgpack format.
|
||||
// func (c *Color) UnmarshalMsgpack(b []byte) error {
|
||||
// var data []uint8
|
||||
// if err := msgpack.Unmarshal(data, b); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// c.Red = 255
|
||||
// c.Green = data[1]
|
||||
// c.Blue = data[2]
|
||||
// c.Alpha = data[3]
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// Add a relative color value to the color.
|
||||
func (c Color) Add(r, g, b, a int) Color {
|
||||
var (
|
||||
|
|
22
mp2json.py
Normal file
22
mp2json.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""mp2json: convert a msgpack binary file into JSON for debugging."""
|
||||
|
||||
import msgpack
|
||||
import json
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: mp2json <filename.level>")
|
||||
|
||||
with open(sys.argv[1], 'rb') as fh:
|
||||
header = fh.read(8)
|
||||
magic = header[:6].decode("utf-8")
|
||||
if magic != "DOODLE":
|
||||
print("input file doesn't appear to be a doodle drawing binary")
|
||||
sys.exit(1)
|
||||
|
||||
reader = msgpack.Unpacker(fh, raw=False, max_buffer_size=10*1024*1024)
|
||||
for o in reader:
|
||||
print(o)
|
||||
print(json.dumps(o, indent=2))
|
|
@ -30,6 +30,9 @@ var (
|
|||
// Put a border around all Canvas widgets.
|
||||
DebugCanvasBorder = render.Invisible
|
||||
DebugCanvasLabel = false // Tag the canvas with a label.
|
||||
|
||||
// Pretty-print JSON files when writing.
|
||||
JSONIndent = false
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
118
pkg/balance/filesystem.go
Normal file
118
pkg/balance/filesystem.go
Normal file
|
@ -0,0 +1,118 @@
|
|||
package balance
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||
)
|
||||
|
||||
// Binary file format headers for Levels and Doodads.
|
||||
//
|
||||
// The header is 8 bytes long: "DOODLE" + file format version + file type number.
|
||||
const (
|
||||
BinMagic = "DOODLE"
|
||||
BinVersion uint8 = 1 // version of the file format we support
|
||||
BinLevelType uint8 = 1
|
||||
BinDoodadType uint8 = 2
|
||||
)
|
||||
|
||||
// MakeHeader creates the binary file header.
|
||||
func MakeHeader(filetype uint8) []byte {
|
||||
header := make([]byte, len(BinMagic)+2)
|
||||
for i := 0; i < len(BinMagic); i++ {
|
||||
header[i] = BinMagic[i]
|
||||
}
|
||||
|
||||
header[len(header)-2] = BinVersion
|
||||
header[len(header)-1] = filetype
|
||||
|
||||
return header
|
||||
}
|
||||
|
||||
// ReadHeader reads and verifies a header from a filehandle.
|
||||
func ReadHeader(filetype uint8, fh io.Reader) error {
|
||||
header := make([]byte, len(BinMagic)+2)
|
||||
_, err := fh.Read(header)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReadHeader: %s", err)
|
||||
}
|
||||
|
||||
if string(header[:len(BinMagic)]) != BinMagic {
|
||||
return errors.New("not a doodle drawing (no magic number in header)")
|
||||
}
|
||||
|
||||
// Verify the file format version and type.
|
||||
var (
|
||||
fileVersion = header[len(header)-2]
|
||||
fileType = header[len(header)-1]
|
||||
)
|
||||
|
||||
if fileVersion == 0 || fileVersion > BinVersion {
|
||||
return errors.New("binary format was created using a newer version of the game")
|
||||
} else if fileType != filetype {
|
||||
return errors.New("drawing type is not the type we expected")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
FindFile looks for a file (level or doodad) in a few places.
|
||||
|
||||
The filename should already have a ".level" or ".doodad" file extension. If
|
||||
neither is given, the exact filename will be searched in all places.
|
||||
|
||||
1. Check in the files built into the program binary.
|
||||
2. Check for system files in the binary's assets/ folder.
|
||||
3. Check the user folders.
|
||||
|
||||
Returns the file path and an error if not found anywhere.
|
||||
*/
|
||||
func FindFile(filename string) (string, error) {
|
||||
var filetype string
|
||||
|
||||
// Any hint on what type of file we're looking for?
|
||||
if strings.HasSuffix(filename, enum.LevelExt) {
|
||||
filetype = enum.LevelExt
|
||||
} else if strings.HasSuffix(filename, enum.DoodadExt) {
|
||||
filetype = enum.DoodadExt
|
||||
}
|
||||
|
||||
// Search level directories.
|
||||
if filetype == enum.LevelExt || filetype == "" {
|
||||
// system levels
|
||||
candidate := filepath.Join(".", "assets", "levels", filename)
|
||||
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
||||
return candidate, nil
|
||||
}
|
||||
|
||||
// user levels
|
||||
candidate = userdir.LevelPath(filename)
|
||||
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
||||
return candidate, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Search doodad directories.
|
||||
if filetype == enum.DoodadExt || filetype == "" {
|
||||
// system doodads
|
||||
candidate := filepath.Join(".", "assets", "doodads", filename)
|
||||
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
||||
return candidate, nil
|
||||
}
|
||||
|
||||
// user doodads
|
||||
candidate = userdir.DoodadPath(filename)
|
||||
if _, err := os.Stat(candidate); !os.IsNotExist(err) {
|
||||
return candidate, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("file not found")
|
||||
}
|
|
@ -3,7 +3,6 @@ package doodle
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
|
@ -171,7 +170,7 @@ func (s *EditorScene) Draw(d *Doodle) error {
|
|||
func (s *EditorScene) LoadLevel(filename string) error {
|
||||
s.filename = filename
|
||||
|
||||
level, err := level.LoadJSON(filename)
|
||||
level, err := level.LoadFile(filename)
|
||||
fmt.Printf("%+v\n", level)
|
||||
if err != nil {
|
||||
return fmt.Errorf("EditorScene.LoadLevel(%s): %s", filename, err)
|
||||
|
@ -213,20 +212,7 @@ func (s *EditorScene) SaveLevel(filename string) error {
|
|||
m.Palette = s.UI.Canvas.Palette
|
||||
m.Chunker = s.UI.Canvas.Chunker()
|
||||
|
||||
json, err := m.ToJSON()
|
||||
if err != nil {
|
||||
return fmt.Errorf("SaveLevel error: %s", err)
|
||||
}
|
||||
|
||||
// Save it to their profile directory.
|
||||
filename = userdir.LevelPath(filename)
|
||||
log.Info("Write Level: %s", filename)
|
||||
err = ioutil.WriteFile(filename, json, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Create map file error: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return m.WriteFile(filename)
|
||||
}
|
||||
|
||||
// LoadDoodad loads a doodad from disk.
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||
"github.com/satori/go.uuid"
|
||||
"github.com/vmihailenco/msgpack"
|
||||
"golang.org/x/image/bmp"
|
||||
)
|
||||
|
||||
|
@ -41,8 +42,9 @@ type Chunk struct {
|
|||
// JSONChunk holds a lightweight (interface-free) copy of the Chunk for
|
||||
// unmarshalling JSON files from disk.
|
||||
type JSONChunk struct {
|
||||
Type int `json:"type"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
Type int `json:"type" msgpack:"0"`
|
||||
Data json.RawMessage `json:"data" msgpack:"-"`
|
||||
BinData interface{} `json:"-" msgpack:"1"`
|
||||
}
|
||||
|
||||
// Accessor provides a high-level API to interact with absolute pixel coordinates
|
||||
|
@ -57,6 +59,9 @@ type Accessor interface {
|
|||
Len() int
|
||||
MarshalJSON() ([]byte, error)
|
||||
UnmarshalJSON([]byte) error
|
||||
// MarshalMsgpack() ([]byte, error)
|
||||
// UnmarshalMsgpack([]byte) error
|
||||
// Serialize() interface{}
|
||||
}
|
||||
|
||||
// NewChunk creates a new chunk.
|
||||
|
@ -277,3 +282,66 @@ func (c *Chunk) UnmarshalJSON(b []byte) error {
|
|||
return fmt.Errorf("Chunk.UnmarshalJSON: unsupported chunk type '%d'", c.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chunk) EncodeMsgpack(enc *msgpack.Encoder) error {
|
||||
data := c.Accessor
|
||||
|
||||
generic := &JSONChunk{
|
||||
Type: c.Type,
|
||||
BinData: data,
|
||||
}
|
||||
|
||||
return enc.Encode(generic)
|
||||
}
|
||||
|
||||
func (c *Chunk) DecodeMsgpack(dec *msgpack.Decoder) error {
|
||||
generic := &JSONChunk{}
|
||||
err := dec.Decode(generic)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Chunk.DecodeMsgpack: %s", err)
|
||||
}
|
||||
|
||||
switch c.Type {
|
||||
case MapType:
|
||||
c.Accessor = generic.BinData.(MapAccessor)
|
||||
default:
|
||||
return fmt.Errorf("Chunk.DecodeMsgpack: unsupported chunk type '%d'", c.Type)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// // MarshalMsgpack writes the chunk to msgpack format.
|
||||
// func (c *Chunk) MarshalMsgpack() ([]byte, error) {
|
||||
// // data, err := c.Accessor.MarshalMsgpack()
|
||||
// // if err != nil {
|
||||
// // return []byte{}, err
|
||||
// // }
|
||||
// data := c.Accessor
|
||||
//
|
||||
// generic := &JSONChunk{
|
||||
// Type: c.Type,
|
||||
// BinData: data,
|
||||
// }
|
||||
// b, err := msgpack.Marshal(generic)
|
||||
// return b, err
|
||||
// }
|
||||
//
|
||||
// // UnmarshalMsgpack loads the chunk from msgpack format.
|
||||
// func (c *Chunk) UnmarshalMsgpack(b []byte) error {
|
||||
// // Parse it generically so we can hand off the inner "data" object to the
|
||||
// // right accessor for unmarshalling.
|
||||
// generic := &JSONChunk{}
|
||||
// err := msgpack.Unmarshal(b, generic)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("Chunk.UnmarshalMsgpack: failed to unmarshal into generic JSONChunk type: %s", err)
|
||||
// }
|
||||
//
|
||||
// switch c.Type {
|
||||
// case MapType:
|
||||
// c.Accessor = NewMapAccessor()
|
||||
// return c.Accessor.UnmarshalMsgpack(generic.Data)
|
||||
// default:
|
||||
// return fmt.Errorf("Chunk.UnmarshalMsgpack: unsupported chunk type '%d'", c.Type)
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"github.com/vmihailenco/msgpack"
|
||||
)
|
||||
|
||||
// MapAccessor implements a chunk accessor by using a map of points to their
|
||||
|
@ -127,3 +128,65 @@ func (a MapAccessor) UnmarshalJSON(b []byte) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// // MarshalMsgpack serializes for msgpack.
|
||||
// func (a MapAccessor) MarshalMsgpack() ([]byte, error) {
|
||||
// dict := map[string]int{}
|
||||
// for point, sw := range a {
|
||||
// dict[point.String()] = sw.Index()
|
||||
// }
|
||||
// return msgpack.Marshal(dict)
|
||||
// }
|
||||
//
|
||||
// // Serialize converts the chunk accessor to a map for serialization.
|
||||
// func (a MapAccessor) Serialize() interface{} {
|
||||
// dict := map[string]int{}
|
||||
// for point, sw := range a {
|
||||
// dict[point.String()] = sw.Index()
|
||||
// }
|
||||
// return dict
|
||||
// }
|
||||
//
|
||||
// // UnmarshalMsgpack decodes from msgpack format.
|
||||
// func (a MapAccessor) UnmarshalMsgpack(b []byte) error {
|
||||
// var dict map[string]int
|
||||
// err := msgpack.Unmarshal(b, &dict)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// for coord, index := range dict {
|
||||
// point, err := render.ParsePoint(coord)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("MapAccessor.UnmarshalJSON: %s", err)
|
||||
// }
|
||||
// a[point] = NewSparseSwatch(index)
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (a MapAccessor) EncodeMsgpack(enc *msgpack.Encoder) error {
|
||||
dict := map[string]int{}
|
||||
for point, sw := range a {
|
||||
dict[point.String()] = sw.Index()
|
||||
}
|
||||
return enc.Encode(dict)
|
||||
}
|
||||
|
||||
func (a MapAccessor) DecodeMsgpack(dec *msgpack.Decoder) error {
|
||||
v, err := dec.DecodeMap()
|
||||
if err != nil {
|
||||
return fmt.Errorf("MapAccessor.DecodeMsgpack: %s", err)
|
||||
}
|
||||
dict := v.(map[string]int)
|
||||
|
||||
for coord, index := range dict {
|
||||
point, err := render.ParsePoint(coord)
|
||||
if err != nil {
|
||||
return fmt.Errorf("MapAccessor.UnmarshalJSON: %s", err)
|
||||
}
|
||||
a[point] = NewSparseSwatch(index)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"math"
|
||||
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"github.com/vmihailenco/msgpack"
|
||||
)
|
||||
|
||||
// Chunker is the data structure that manages the chunks of a level, and
|
||||
|
@ -240,3 +241,14 @@ func (c ChunkMap) MarshalJSON() ([]byte, error) {
|
|||
out, err := json.Marshal(dict)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// MarshalMsgpack to convert the chunk map to binary.
|
||||
func (c ChunkMap) MarshalMsgpack() ([]byte, error) {
|
||||
dict := map[string]*Chunk{}
|
||||
for point, chunk := range c {
|
||||
dict[point.String()] = chunk
|
||||
}
|
||||
|
||||
out, err := msgpack.Marshal(dict)
|
||||
return out, err
|
||||
}
|
||||
|
|
71
pkg/level/fmt_binary.go
Normal file
71
pkg/level/fmt_binary.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package level
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"github.com/vmihailenco/msgpack"
|
||||
)
|
||||
|
||||
// ToBinary serializes the level to binary format.
|
||||
func (m *Level) ToBinary() ([]byte, error) {
|
||||
header := balance.MakeHeader(balance.BinLevelType)
|
||||
out := bytes.NewBuffer(header)
|
||||
encoder := msgpack.NewEncoder(out)
|
||||
err := encoder.Encode(m)
|
||||
return out.Bytes(), err
|
||||
}
|
||||
|
||||
// WriteBinary writes a level to binary format on disk.
|
||||
func (m *Level) WriteBinary(filename string) error {
|
||||
bin, err := m.ToBinary()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Level.WriteBinary: encode error: %s", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filename, bin, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Level.WriteBinary: WriteFile error: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadBinary loads a map from binary file on disk.
|
||||
func LoadBinary(filename string) (*Level, error) {
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
// Read and verify the file header from the binary format.
|
||||
err = balance.ReadHeader(balance.BinLevelType, fh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode the file from disk.
|
||||
m := New()
|
||||
decoder := msgpack.NewDecoder(fh)
|
||||
err = decoder.Decode(&m)
|
||||
if err != nil {
|
||||
return m, fmt.Errorf("level.LoadBinary: decode error: %s", err)
|
||||
}
|
||||
|
||||
// Fill in defaults.
|
||||
if m.Wallpaper == "" {
|
||||
m.Wallpaper = DefaultWallpaper
|
||||
}
|
||||
|
||||
// Inflate the chunk metadata to map the pixels to their palette indexes.
|
||||
m.Chunker.Inflate(m.Palette)
|
||||
m.Actors.Inflate()
|
||||
|
||||
// Inflate the private instance values.
|
||||
m.Palette.Inflate()
|
||||
return m, err
|
||||
}
|
|
@ -6,32 +6,21 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
)
|
||||
|
||||
// ToJSON serializes the level as JSON.
|
||||
func (m *Level) ToJSON() ([]byte, error) {
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
encoder := json.NewEncoder(out)
|
||||
encoder.SetIndent("", "\t")
|
||||
if balance.JSONIndent {
|
||||
encoder.SetIndent("", "\t")
|
||||
}
|
||||
err := encoder.Encode(m)
|
||||
return out.Bytes(), err
|
||||
}
|
||||
|
||||
// WriteJSON writes a level to JSON on disk.
|
||||
func (m *Level) WriteJSON(filename string) error {
|
||||
json, err := m.ToJSON()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Level.WriteJSON: JSON encode error: %s", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filename, json, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Level.WriteJSON: WriteFile error: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadJSON loads a map from JSON file.
|
||||
func LoadJSON(filename string) (*Level, error) {
|
||||
fh, err := os.Open(filename)
|
||||
|
@ -61,3 +50,18 @@ func LoadJSON(filename string) (*Level, error) {
|
|||
m.Palette.Inflate()
|
||||
return m, err
|
||||
}
|
||||
|
||||
// WriteJSON writes a level to JSON on disk.
|
||||
func (m *Level) WriteJSON(filename string) error {
|
||||
json, err := m.ToJSON()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Level.WriteJSON: JSON encode error: %s", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filename, json, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Level.WriteJSON: WriteFile error: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
65
pkg/level/fmt_readwrite.go
Normal file
65
pkg/level/fmt_readwrite.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package level
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||
)
|
||||
|
||||
// LoadFile reads a level file from disk, checking a few locations.
|
||||
func LoadFile(filename string) (*Level, error) {
|
||||
if !strings.HasSuffix(filename, enum.LevelExt) {
|
||||
filename += enum.LevelExt
|
||||
}
|
||||
|
||||
// Search the system and user paths for this level.
|
||||
filename, err := balance.FindFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Try the binary format.
|
||||
if level, err := LoadBinary(filename); err == nil {
|
||||
return level, nil
|
||||
} else {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
|
||||
// Then the JSON format.
|
||||
if level, err := LoadJSON(filename); err == nil {
|
||||
return level, nil
|
||||
} else {
|
||||
log.Warn(err.Error())
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid file type")
|
||||
}
|
||||
|
||||
// WriteFile saves a level to disk in the user's config directory.
|
||||
func (m *Level) WriteFile(filename string) error {
|
||||
if !strings.HasSuffix(filename, enum.LevelExt) {
|
||||
filename += enum.LevelExt
|
||||
}
|
||||
|
||||
// bin, err := m.ToBinary()
|
||||
bin, err := m.ToJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save it to their profile directory.
|
||||
filename = userdir.LevelPath(filename)
|
||||
log.Info("Write Level: %s", filename)
|
||||
err = ioutil.WriteFile(filename, bin, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("level.WriteFile: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -16,36 +16,36 @@ var (
|
|||
// 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"`
|
||||
Version int `json:"version" msgpack:"0"` // File format version spec.
|
||||
GameVersion string `json:"gameVersion" msgpack:"1"` // Game version that created the level.
|
||||
Title string `json:"title" msgpack:"2"`
|
||||
Author string `json:"author" msgpack:"3"`
|
||||
|
||||
// Every drawing type is able to embed other files inside of itself.
|
||||
Files FileSystem `json:"files"`
|
||||
Files FileSystem `json:"files" msgpack:"4"`
|
||||
}
|
||||
|
||||
// Level is the container format for Doodle map drawings.
|
||||
type Level struct {
|
||||
Base
|
||||
Password string `json:"passwd"`
|
||||
Locked bool `json:"locked"`
|
||||
Password string `json:"passwd" msgpack:"10"`
|
||||
Locked bool `json:"locked" msgpack:"11"`
|
||||
|
||||
// Chunked pixel data.
|
||||
Chunker *Chunker `json:"chunks"`
|
||||
Chunker *Chunker `json:"chunks" msgpack:"12"`
|
||||
|
||||
// The Palette holds the unique "colors" used in this map file, and their
|
||||
// properties (solid, fire, slippery, etc.)
|
||||
Palette *Palette `json:"palette"`
|
||||
Palette *Palette `json:"palette" msgpack:"13"`
|
||||
|
||||
// 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"`
|
||||
PageType PageType `json:"pageType" msgpack:"14"`
|
||||
MaxWidth int64 `json:"boundedWidth" msgpack:"15"` // only if bounded or bordered
|
||||
MaxHeight int64 `json:"boundedHeight" msgpack:"16"`
|
||||
Wallpaper string `json:"wallpaper" msgpack:"17"`
|
||||
|
||||
// Actors keep a list of the doodad instances in this map.
|
||||
Actors ActorMap `json:"actors"`
|
||||
Actors ActorMap `json:"actors" msgpack:"18"`
|
||||
}
|
||||
|
||||
// New creates a blank level object with all its members initialized.
|
||||
|
|
|
@ -198,6 +198,11 @@ func (s *PlayScene) movePlayer(ev *events.State) {
|
|||
}
|
||||
|
||||
s.Player.SetVelocity(velocity)
|
||||
|
||||
// TODO: invoke the player OnKeypress for animation testing
|
||||
// if velocity != render.Origin {
|
||||
s.scripting.To(s.Player.ID()).Events.RunKeypress(ev)
|
||||
// }
|
||||
}
|
||||
|
||||
// Drawing returns the private world drawing, for debugging with the console.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package scripting
|
||||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/lib/events"
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
|
@ -36,6 +37,16 @@ func (e *Events) RunCollide() error {
|
|||
return e.run(CollideEvent)
|
||||
}
|
||||
|
||||
// OnKeypress fires when another actor collides with yours.
|
||||
func (e *Events) OnKeypress(call otto.FunctionCall) otto.Value {
|
||||
return e.register(KeypressEvent, call.Argument(0))
|
||||
}
|
||||
|
||||
// RunKeypress invokes the OnCollide handler function.
|
||||
func (e *Events) RunKeypress(ev *events.State) error {
|
||||
return e.run(KeypressEvent, ev)
|
||||
}
|
||||
|
||||
// register a named event.
|
||||
func (e *Events) register(name string, callback otto.Value) otto.Value {
|
||||
if !callback.IsFunction() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user