diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66e3370 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/cmd/**/*.exe +/cmd/gmlgo/gmlgo diff --git a/.travis.yml b/.travis.yml index 1025ae5..3cb56b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,13 @@ language: go go: - - "1.10" + - "1.11" + +# https://restic.net/blog/2018-09-02/travis-build-cache +cache: + directories: + - $HOME/.cache/go-build + - $HOME/gopath/pkg/mod addons: apt: @@ -19,7 +25,14 @@ addons: - libxxf86vm-dev install: + # Underlying framework used - go get -t -v github.com/hajimehoshi/ebiten/... + # Used by some structures + - go get -t -v github.com/golang/protobuf/... + # Debug Mode: Live file reloading (watch files for changes) + - go get -t -v github.com/fsnotify/fsnotify/... + # Debug Mode: Used for generating UUID's for room objects, etc. + - go get -t -v github.com/rs/xid/... before_script: - export DISPLAY=:99.0 @@ -27,10 +40,15 @@ before_script: - sleep 3 script: - - go build -v ./... + - go install -tags debug -v ./gml/... + - go install -tags headless -v ./gml/... + - go install -v ./gml/... + - go install -v ./cmd/gmlgo + - gmlgo ./examples/spaceship + - go build -tags debug -v ./examples/... + - go build -tags headless -v ./examples/... + - go build -v ./examples/... + - go test -v ./cmd/... # NOTE(Jake): 2018-06-20 - # # No tests exist yet - # - #- go test -v ./... - - go vet -v ./... + #- go test -v ./gml/... diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3f638e6 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Jake Bentvelzen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 9c757ae..a3ae91f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Game Maker Language Go [![Build Status](https://travis-ci.org/silbinarywolf/gml-go.svg?branch=master)](https://travis-ci.org/silbinarywolf/gml-go) +[![Documentation](https://godoc.org/github.com/silbinarywolf/gml-go?status.svg)](https://github.com/silbinarywolf/gml-go) +[![Report Card](https://goreportcard.com/badge/github.com/silbinarywolf/gml-go)](https://godoc.org/github.com/silbinarywolf/gml-go) -**NOTE: This is currently a hobby project and not meant for any use other than my own. If you are interested in this or use this, please let me know so I can improve documentation, tagging, etc** +**NOTE: This project is currently undergoing a large refactoring effort to help ease workflow and serialization. I'm also aiming to improve the documentation, add examples and improve test coverage. This is still just a hobby project for now!** This is a library / framework that aims to create workflow like Game Maker, but utilizing the Go programming language. @@ -14,11 +16,11 @@ go get github.com/silbinarywolf/gml-go ## Requirements -* Golang 1.10+ +* Golang 1.11+ ## Documentation -* TODO when the library has progressed +* TODO when this library has been refactored * [License](LICENSE.md) # Rough Roadmap @@ -33,10 +35,10 @@ This project is mostly for fun and I have no intentions to get anything done unl - This should cover installing "gofmt" / "goimports" - Transitioning from GML to Golang, major / minor differences * Add build tools to help with: + - [x] Auto generating entity IDs and the like. - Packing assets into texture atlases - - Converting Tiled maps into an internal engine format - - (maybe) Auto generating entity IDs and the like. ## Credits -* [Hajime Hoshi](https://github.com/hajimehoshi/ebiten) for his fantastically simple 2D game library, [https://github.com/hajimehoshi/ebiten](Ebiten). +* [Hajime Hoshi](https://github.com/hajimehoshi/ebiten) for their fantastically simple 2D game library, [https://github.com/hajimehoshi/ebiten](Ebiten). +* [Yann Le Coroller ](www.yannlecoroller.com) for their free to use Helvetica style font. \ No newline at end of file diff --git a/cmd/gmlgo/gmlgo.go b/cmd/gmlgo/gmlgo.go new file mode 100644 index 0000000..855c76a --- /dev/null +++ b/cmd/gmlgo/gmlgo.go @@ -0,0 +1,484 @@ +// I test this by running the following from the root dir: +// - go install -v ./cmd/gmlgo && gmlgo ./examples/spaceship +package main // import "github.com/silbinarywolf/gml-go/cmd/gmlgo" + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/build" + "go/format" + "go/importer" + "go/parser" + "go/token" + "go/types" + "io/ioutil" + "log" + "os" + "path/filepath" + "sort" + "strconv" + "strings" +) + +const ( + defaultImportName = "gml" + importString = "\"github.com/silbinarywolf/gml-go/gml\"" + version = "0.1.0" +) + +var ( +//buildTags = flag.String("tags", "", "comma-separated list of build tags to apply") +) + +// Usage is a replacement usage function for the flags package. +func Usage() { + fmt.Fprintf(os.Stderr, "Usage of gmlgo:\n") + fmt.Fprintf(os.Stderr, "\tgmlgo [directory]\n") + fmt.Fprintf(os.Stderr, "\tgmlgo files... # Must be a single package\n") + fmt.Fprintf(os.Stderr, "For more information, see:\n") + fmt.Fprintf(os.Stderr, "\thttp://godoc.org/github.com/silbinarywolf/gml-go/cmd/gmlgo\n") + //fmt.Fprintf(os.Stderr, "Flags:\n") + //flag.PrintDefaults() +} + +func main() { + log.SetFlags(0) + log.SetPrefix("gmlgo: ") + flag.Usage = Usage + flag.Parse() + + // We accept either one directory or a list of files. Which do we have? + args := flag.Args() + if len(args) == 0 { + // Default: process whole package in current directory. + args = []string{"."} + } + + for _, dir := range args { + // todo(Jake): 2018-12-03 - #33 + // Replace "game" with scanning each sub-package, throw an error if multiple packages + // have multiple objects. Constraint for now will be all object types need to be in the same package + gameDir := filepath.Join(dir, "game") + + // get filename + baseName := fmt.Sprintf("gmlgo_gen.go") + outputName := filepath.Join(gameDir, strings.ToLower(baseName)) + + // check existing file + var input []byte + if _, err := os.Stat(outputName); !os.IsNotExist(err) { + input, err = ioutil.ReadFile(outputName) + if err != nil { + log.Fatalf("reading file: %s", err) + } + if len(input) == 0 { + log.Printf("cannot generate %s as it's empty. rename or delete your %s file.", outputName, outputName) + return + } + if !strings.Contains(string(input), "// Code generated by \"gmlgo") { + log.Printf("cannot generate %s file as it's not using gmlgo generated code. rename your %s file.\n", outputName, outputName) + return + } + } + + // Run generate + g := Generator{} + g.parsePackageDir(gameDir, []string{}) + g.generate() + g.generateAssets(dir) + + // If no generated output, don't write anything + if g.buf.Len() == 0 { + log.Printf("no gml.Object structs found, no output for %s\n", outputName) + return + } + + // Format the output. + src := g.format() + + // If no generated output, don't write anything + if len(src) == 0 { + log.Printf("no gml.Object structs found, no output for %s\n", outputName) + return + } + + // Check if any changes + if bytes.Equal(input, src) { + log.Printf("no changes to %s\n", outputName) + return + } + + // Write to file. + err := ioutil.WriteFile(outputName, src, 0644) + if err != nil { + log.Fatalf("error writing output: %s", err) + } + log.Printf("updated %s\n", outputName) + } +} + +// isDirectory reports whether the named file is a directory. +func isDirectory(name string) bool { + info, err := os.Stat(name) + if err != nil { + log.Fatal(err) + } + return info.IsDir() +} + +// Generator holds the state of the analysis. Primarily used to buffer +// the output for format.Source. +type Generator struct { + buf bytes.Buffer // Accumulated output. + pkg *Package // Package we are scanning. +} + +func (g *Generator) Printf(format string, args ...interface{}) { + fmt.Fprintf(&g.buf, format, args...) +} + +// File holds a single parsed file and associated data. +type File struct { + pkg *Package // Package to which this file belongs. + file *ast.File // Parsed AST. +} + +type Package struct { + dir string + name string + defs map[*ast.Ident]types.Object + files []*File + typesPkg *types.Package +} + +func buildContext(tags []string) *build.Context { + ctx := build.Default + ctx.BuildTags = tags + return &ctx +} + +// parsePackageDir parses the package residing in the directory. +func (g *Generator) parsePackageDir(directory string, tags []string) { + pkg, err := buildContext(tags).ImportDir(directory, 0) + if err != nil { + log.Fatalf("parsePackageDir: cannot parse %s: %s", directory, err) + } + var names []string + names = append(names, pkg.GoFiles...) + names = prefixDirectory(directory, names) + g.parsePackage(directory, names, nil) +} + +// prefixDirectory places the directory name on the beginning of each name in the list. +func prefixDirectory(directory string, names []string) []string { + if directory == "." { + return names + } + ret := make([]string, len(names)) + for i, name := range names { + ret[i] = filepath.Join(directory, name) + } + return ret +} + +// parsePackage analyzes the single package constructed from the named files. +// If text is non-nil, it is a string to be used instead of the content of the file, +// to be used for testing. parsePackage exits if there is an error. +func (g *Generator) parsePackage(directory string, names []string, text interface{}) { + var files []*File + var astFiles []*ast.File + g.pkg = new(Package) + fs := token.NewFileSet() + for _, name := range names { + if !strings.HasSuffix(name, ".go") { + continue + } + parsedFile, err := parser.ParseFile(fs, name, text, parser.ParseComments) + if err != nil { + log.Fatalf("parsing package: %s: %s", name, err) + } + astFiles = append(astFiles, parsedFile) + files = append(files, &File{ + file: parsedFile, + pkg: g.pkg, + }) + } + if len(astFiles) == 0 { + log.Fatalf("%s: no buildable Go files", directory) + } + g.pkg.name = astFiles[0].Name.Name + g.pkg.files = files + g.pkg.dir = directory + g.pkg.typeCheck(fs, astFiles) +} + +// check type-checks the package so we can evaluate contants whose values we are printing. +func (pkg *Package) typeCheck(fs *token.FileSet, astFiles []*ast.File) { + pkg.defs = make(map[*ast.Ident]types.Object) + config := types.Config{ + IgnoreFuncBodies: true, // We only need to evaluate constants. + Importer: importer.Default(), // func defaultImporter() types.Importer + FakeImportC: true, + DisableUnusedImportCheck: true, + } + info := &types.Info{ + Defs: pkg.defs, + } + typesPkg, err := config.Check(pkg.dir, fs, astFiles, info) + if err != nil { + log.Fatalf("checking package: %s", err) + } + pkg.typesPkg = typesPkg +} + +type Struct struct { + Name string +} + +type AssetKind struct { + Name string + Assets []string +} + +// generate produces the code for object indexes +func (g *Generator) generate() { + var structsUsingGMLObject []Struct + for _, file := range g.pkg.files { + gmlPackageName := "" + if file.file == nil { + continue + } + //fmt.Printf("file: %s\n---------------\n\n", file.file.Name.String()) + ast.Inspect(file.file, func(n ast.Node) bool { + switch n := n.(type) { + // import "github.com/silbinarywolf/gml-go/gml" + case *ast.ImportSpec: + if n.Path.Value == importString { + gmlPackageName = defaultImportName + if n.Name != nil { + // import gml "github.com/silbinarywolf/gml-go/gml" + gmlPackageName = n.Name.Name + } + } + return false + // type XXXX struct + case *ast.TypeSpec: + structName := n.Name.Name + switch n := n.Type.(type) { + // type XXXX struct + case *ast.StructType: + if n.Incomplete || + gmlPackageName == "" { + return false + } + for _, field := range n.Fields.List { + switch fieldType := field.Type.(type) { + case *ast.SelectorExpr: + switch kind := fieldType.X.(type) { + case *ast.Ident: + // Find embedded "gml.Object" + if kind.Name == gmlPackageName && + fieldType.Sel.Name == "Object" { + structsUsingGMLObject = append(structsUsingGMLObject, Struct{ + Name: structName, + }) + } + } + } + } + return false + } + return false + } + return true + }) + } + + if len(structsUsingGMLObject) == 0 { + return + } + + // Sort alphabetically + sort.Slice(structsUsingGMLObject[:], func(i, j int) bool { + return structsUsingGMLObject[i].Name < structsUsingGMLObject[j].Name + }) + + // Print the header and package clause. + g.Printf("// Code generated by \"gmlgo\"\n") + g.Printf("// %s\n", version) + g.Printf("// DO NOT EDIT. DO NOT COMMIT TO YOUR VCS REPOSITORY.\n") + g.Printf("\n") + g.Printf(`package ` + g.pkg.name + ` +`) + g.Printf(` +import ( + "github.com/silbinarywolf/gml-go/gml" +) +`) + g.generateObjectIndexes(structsUsingGMLObject) + g.generateObjectMetaAndMethods(structsUsingGMLObject) +} + +func (g *Generator) generateAssets(dir string) { + { + // Read asset names + assetDir := filepath.Join(dir, "asset") + files, err := ioutil.ReadDir(assetDir) + if err != nil { + log.Fatal(err) + } + var assetKinds []AssetKind + for _, f := range files { + switch name := f.Name(); name { + case "font": + case "sprite": + files, err := ioutil.ReadDir(filepath.Join(assetDir, name)) + if err != nil { + log.Fatal(err) + } + var assetNames []string + for _, f := range files { + if f.IsDir() { + //assetName := f.Name() + assetNames = append(assetNames, f.Name()) + //g.Printf(" Spr%s gml.SpriteIndex = %d\n", assetName, assetCount) + //assetCount++ + } + } + if len(assetNames) > 0 { + assetKinds = append(assetKinds, AssetKind{ + Name: name, + Assets: assetNames, + }) + } + default: + if !f.IsDir() { + // Ignore files + continue + } + log.Fatal(fmt.Errorf("Unexpected asset kind directory: %s", name)) + } + } + // Generate asset indexes + for _, assetKind := range assetKinds { + if len(assetKind.Assets) == 0 { + continue + } + var prefix, gotype string + switch assetKind.Name { + case "font": + prefix = "Fnt" + gotype = "todo" // todo: Implement gml.FontIndex + case "sprite": + prefix = "Spr" + gotype = "gml.SpriteIndex" + default: + panic("Unimplemented asset kind: " + assetKind.Name) + } + + { + g.Printf("const (\n") + for i, assetName := range assetKind.Assets { + // ie. SprPlayer gml.SpriteIndex = 1 + g.Printf(" %s%s %s = %d\n", prefix, assetName, gotype, i+1) + } + g.Printf("\n)\n\n") + } + { + g.Printf("var _gen_%s_index_to_name = []string{\n", prefix) + for _, assetName := range assetKind.Assets { + // ie. SprPlayer: "Player" + g.Printf(" %s%s: \"%s\",\n", prefix, assetName, assetName) + } + g.Printf("\n}\n\n") + } + { + g.Printf("var _gen_%s_name_to_index = map[string]gml.SpriteIndex{\n", prefix) + for _, assetName := range assetKind.Assets { + // ie. "Player": SprPlayer + g.Printf(" \"%s\": %s%s,\n", assetName, prefix, assetName) + } + g.Printf("\n}\n") + } + switch assetKind.Name { + case "font": + prefix = "Fnt" + gotype = "todo" // todo: Implement gml.FontIndex + case "sprite": + g.Printf(` +func init() { + gml.SpriteInitializeIndexToName(_gen_Spr_index_to_name, _gen_Spr_name_to_index) +} + +`) + default: + panic("Unimplemented asset kind: " + assetKind.Name) + } + } + } +} + +func (g *Generator) generateObjectIndexes(structsUsingGMLObject []Struct) { + g.Printf(` +const ( +`) + for i, record := range structsUsingGMLObject { + g.Printf(" Obj" + record.Name + " gml.ObjectIndex = " + strconv.Itoa(i+1) + "\n") + } + g.Printf(`) + +`) +} + +func (g *Generator) generateObjectMetaAndMethods(structsUsingGMLObject []Struct) { + { + // Write object index list + g.Printf("var _gen_Obj_index_list = []gml.ObjectIndex{\n") + for _, record := range structsUsingGMLObject { + // ie. ObjPlayer, + g.Printf(" Obj%s,\n", record.Name) + } + g.Printf("\n}\n\n") + } + + { + // Write object index to data list + g.Printf("var _gen_Obj_index_to_data = []gml.ObjectType{\n") + for _, record := range structsUsingGMLObject { + // ie. ObjPlayer: new(Player), + g.Printf(" Obj%s: new(%s),\n", record.Name, record.Name) + } + g.Printf("\n}\n\n") + } + + { + // Write Object types + for _, record := range structsUsingGMLObject { + //g.Printf("func (inst *" + record.Name + ") ObjectIndex() gml.ObjectIndex { return Obj" + record.Name + " }\n") + g.Printf("func (inst *" + record.Name + ") ObjectName() string { return \"" + record.Name + "\" }\n") + g.Printf("\n") + } + g.Printf("\n") + g.Printf(` + +func init() { + gml.ObjectInitTypes(_gen_Obj_index_to_data, _gen_Obj_index_list) +} +`) + } +} + +// format returns the gofmt-ed contents of the Generator's buffer. +func (g *Generator) format() []byte { + src, err := format.Source(g.buf.Bytes()) + if err != nil { + // Should never happen, but can arise when developing this code. + // The user can compile the output to see the error. + log.Printf("warning: internal error: invalid Go generated: %s", err) + log.Printf("warning: compile the package to analyze the error") + return g.buf.Bytes() + } + return src +} diff --git a/cmd/gmlgo/golden_test.go b/cmd/gmlgo/golden_test.go new file mode 100644 index 0000000..850d610 --- /dev/null +++ b/cmd/gmlgo/golden_test.go @@ -0,0 +1,72 @@ +package main + +import ( + "testing" +) + +// Golden represents a test case. +type Golden struct { + name string + input string // input; the package clause is provided when running the test. + output string // exected output. +} + +var golden = []Golden{ + {"simple", simple_in, simple_out}, +} + +// Each example starts with "type XXX [u]int", with a single space separating them. + +// Simple test: enumeration of type int starting at 0. +const simple_in = ` +import ( + "github.com/silbinarywolf/gml-go/gml" +) + +type GameObjectA struct { + gml.Object +} +` + +const simple_out = `// Code generated by "gmlgo" +// 0.1.0 +// DO NOT EDIT. DO NOT COMMIT TO YOUR VCS REPOSITORY. + +package test + +import ( + "github.com/silbinarywolf/gml-go/gml" +) + +const ( + ObjGameObjectA gml.ObjectIndex = 1 +) + +var _gen_Obj_index_list = []gml.ObjectIndex{ + ObjGameObjectA, +} + +var _gen_Obj_index_to_data = []gml.ObjectType{ + ObjGameObjectA: new(GameObjectA), +} + +func (inst *GameObjectA) ObjectName() string { return "GameObjectA" } + +func init() { + gml.ObjectInitTypes(_gen_Obj_index_to_data, _gen_Obj_index_list) +} +` + +func TestGolden(t *testing.T) { + for _, test := range golden { + g := Generator{} + input := "package test\n" + test.input + file := test.name + ".go" + g.parsePackage(".", []string{file}, input) + g.generate() + got := string(g.format()) + if got != test.output { + t.Errorf("%s: got\n====\n%s====\nexpected\n====%s", test.name, got, test.output) + } + } +} diff --git a/examples/spaceship/.gitignore b/examples/spaceship/.gitignore new file mode 100644 index 0000000..05516f4 --- /dev/null +++ b/examples/spaceship/.gitignore @@ -0,0 +1,10 @@ +# Ignore compiled asset data +asset/**/*.data +asset/font/*.rtf# +# Ignore code generated files +gmlgo_gen.go +**/gmlgo_gen.go +# Ignore binary for Windows +*.exe +# Ignore binary for Linux / Mac +./spaceship diff --git a/examples/spaceship/asset/font/Alte Haas Grotesk licence.rtf b/examples/spaceship/asset/font/Alte Haas Grotesk licence.rtf new file mode 100644 index 0000000..2b3359b --- /dev/null +++ b/examples/spaceship/asset/font/Alte Haas Grotesk licence.rtf @@ -0,0 +1,16 @@ +{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf270 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\paperw11900\paperh16840\margl1440\margr1440\vieww9000\viewh8400\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural + +\f0\fs24 \cf0 Alte Haas Grotesk is a typeface that look like an helvetica printed in an old Muller-Brockmann Book.\ +\ +These fonts are freeware and can be distributed as long as they are \ +together with this text file. \ +\ +I would appreciate very much to see what you have done with it anyway.\ +\ +yann le coroller \ +www.yannlecoroller.com\ +yann@lecoroller.com} \ No newline at end of file diff --git a/examples/spaceship/asset/font/AlteHaasGroteskRegular.ttf b/examples/spaceship/asset/font/AlteHaasGroteskRegular.ttf new file mode 100644 index 0000000..766c4d5 Binary files /dev/null and b/examples/spaceship/asset/font/AlteHaasGroteskRegular.ttf differ diff --git a/examples/spaceship/asset/sprite/Bullet/0.png b/examples/spaceship/asset/sprite/Bullet/0.png new file mode 100644 index 0000000..43066ef Binary files /dev/null and b/examples/spaceship/asset/sprite/Bullet/0.png differ diff --git a/examples/spaceship/asset/sprite/Spaceship/0.png b/examples/spaceship/asset/sprite/Spaceship/0.png new file mode 100644 index 0000000..58cc8f6 Binary files /dev/null and b/examples/spaceship/asset/sprite/Spaceship/0.png differ diff --git a/examples/spaceship/game/game.go b/examples/spaceship/game/game.go new file mode 100644 index 0000000..7e1ba35 --- /dev/null +++ b/examples/spaceship/game/game.go @@ -0,0 +1,42 @@ +package game + +import "github.com/silbinarywolf/gml-go/gml" + +const ( + WindowTitle = "Spaceship" + WindowWidth = 640 + WindowHeight = 480 + WindowScale = 1 +) + +var ( + gameWorld GameWorld +) + +type GameWorld struct { + // todo(Jake): 2018-11-24 - #6 + // Change int to gml.RoomIndex + CurrentRoomIndex int +} + +func GameStart() { + // todo(Jake): 2018-11-24 - #15 + // - Simplify this so that you can just pass "asset.AlteHaasGroteskRegular"? + // - Change LoadFont to return FontIndex + gml.DrawSetFont(gml.LoadFont("AlteHaasGroteskRegular", gml.FontSettings{ + Size: 16, // 12pt == 16px + DPI: 96, + })) + + // Setup camera + // todo(Jake): 2018-11-24 - #3 + // Change CameraCreate to use geom.Size for w/h + gml.CameraCreate(0, 0, 0, float64(gml.WindowWidth()), float64(gml.WindowHeight())) + gameWorld.CurrentRoomIndex = gml.RoomInstanceNew() + gml.InstanceCreateRoom(gml.Vec{float64(gml.WindowWidth()) / 2, float64(gml.WindowHeight()) / 2}, gameWorld.CurrentRoomIndex, ObjPlayer) +} + +func GameUpdate() { + gml.Update(true) + gml.Draw() +} diff --git a/examples/spaceship/game/obj_bullet.go b/examples/spaceship/game/obj_bullet.go new file mode 100644 index 0000000..84348a5 --- /dev/null +++ b/examples/spaceship/game/obj_bullet.go @@ -0,0 +1,39 @@ +package game + +import ( + "github.com/silbinarywolf/gml-go/gml" +) + +type Bullet struct { + gml.Object + // todo(Jake): 2018-12-02 - #24 + // Swap this to gml.InstanceIndex when ready + // Maybe also add "vet" functionality - #25 + Owner gml.ObjectType +} + +func (inst *Bullet) Create() { + inst.SetSprite(SprBullet) +} + +func (inst *Bullet) Destroy() { + +} + +func (inst *Bullet) Update() { + inst.Y -= 8 + + for _, other := range gml.CollisionRectList(inst, inst.Pos()) { + other, ok := other.(*EnemyShip) + if !ok { + continue + } + owner := inst.Owner.(*Player) + owner.Score += 1 + gml.InstanceDestroy(other) + } +} + +func (inst *Bullet) Draw() { + gml.DrawSelf(&inst.SpriteState, inst.Pos()) +} diff --git a/examples/spaceship/game/obj_enemy_ship.go b/examples/spaceship/game/obj_enemy_ship.go new file mode 100644 index 0000000..db39b2a --- /dev/null +++ b/examples/spaceship/game/obj_enemy_ship.go @@ -0,0 +1,26 @@ +package game + +import ( + "github.com/silbinarywolf/gml-go/gml" +) + +type EnemyShip struct { + gml.Object +} + +func (inst *EnemyShip) Create() { + inst.SetSprite(SprSpaceship) + inst.ImageScale.Y = -1 +} + +func (inst *EnemyShip) Destroy() { + +} + +func (inst *EnemyShip) Update() { + inst.Y += 8 +} + +func (inst *EnemyShip) Draw() { + gml.DrawSelf(&inst.SpriteState, inst.Pos()) +} diff --git a/examples/spaceship/game/obj_player.go b/examples/spaceship/game/obj_player.go new file mode 100644 index 0000000..2acfa13 --- /dev/null +++ b/examples/spaceship/game/obj_player.go @@ -0,0 +1,50 @@ +package game + +import ( + "math/rand" + + "github.com/silbinarywolf/gml-go/gml" +) + +type Player struct { + gml.Object + enemyCreateAlarm gml.Alarm + Score int +} + +func (inst *Player) Create() { + inst.SetSprite(SprSpaceship) +} + +func (inst *Player) Destroy() { +} + +func (inst *Player) Update() { + if inst.enemyCreateAlarm.Update(60) { + // Spawn enemies at the top of the frame, every 60 frames + gml.InstanceCreateRoom(gml.Vec{float64(rand.Intn(gml.WindowWidth())), 0}, gameWorld.CurrentRoomIndex, ObjEnemyShip) + } + + if gml.KeyboardCheck(gml.VkLeft) { + inst.X -= 8 + } + if gml.KeyboardCheck(gml.VkRight) { + inst.X += 8 + } + if gml.KeyboardCheck(gml.VkUp) { + inst.Y -= 8 + } + if gml.KeyboardCheck(gml.VkDown) { + inst.Y += 8 + } + if gml.KeyboardCheckPressed(gml.VkSpace) { + bullet := gml.InstanceCreateRoom(inst.Pos(), gameWorld.CurrentRoomIndex, ObjBullet).(*Bullet) + bullet.Owner = inst + } +} + +func (inst *Player) Draw() { + gml.DrawSelf(&inst.SpriteState, inst.Pos()) + + gml.DrawTextF(gml.Vec{0, 32}, "Score: %d", inst.Score) +} diff --git a/examples/spaceship/main.go b/examples/spaceship/main.go new file mode 100644 index 0000000..d575a54 --- /dev/null +++ b/examples/spaceship/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/silbinarywolf/gml-go/examples/spaceship/game" + "github.com/silbinarywolf/gml-go/gml" +) + +func main() { + gml.Run(game.GameStart, game.GameUpdate, game.WindowWidth, game.WindowHeight, game.WindowScale, game.WindowTitle) +} diff --git a/gml/alarm.go b/gml/alarm.go new file mode 100644 index 0000000..4d2ba28 --- /dev/null +++ b/gml/alarm.go @@ -0,0 +1,23 @@ +package gml + +type Alarm struct { + timeSet int + timeLeft int +} + +// todo(Jake): 2018-12-02: #23 +// I'd like to test this alarm system against Game Maker and +// see if I can make it feel the same. +// (ie. you give the same values as Game Maker, you can get the same results) +func (alarm *Alarm) Update(frames int) bool { + if alarm.timeSet == 0 { + alarm.timeSet = frames + alarm.timeLeft = frames + } + alarm.timeLeft -= 1 + if alarm.timeLeft <= 0 { + alarm.timeSet = 0 + return true + } + return false +} diff --git a/gml/animation_editor_debug.go b/gml/animation_editor_debug.go new file mode 100644 index 0000000..5dfa5cf --- /dev/null +++ b/gml/animation_editor_debug.go @@ -0,0 +1,445 @@ +// +build debug + +package gml + +import ( + "encoding/json" + "image/color" + "io/ioutil" + "math" + "strconv" + + "github.com/silbinarywolf/gml-go/gml/internal/file" + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" +) + +type animMenu int + +const ( + animMenuNone animMenu = 0 + iota + animMenuSprite + animMenuSpriteBboxLeft + animMenuSpriteBboxTop + animMenuSpriteBboxRight + animMenuSpriteBboxBottom +) + +const ( + handleDragNone int = 0 + iota + handleDragLeftTop + handleDragRightTop + handleDragRightBottom + handleDragLeftBottom +) + +var ( + animationEditor *debugAnimationEditor +) + +type debugAnimationEditor struct { + debugSpriteViewer + menuOpened animMenu + handleDragging int + handleDragBeginPos geom.Vec + spriteViewing SpriteState + isInPlayback bool +} + +type animationEditorConfig struct { + SpriteSelected string `json:"SpriteSelected,omitempty"` +} + +func animationEditorLazyLoad() { + if animationEditor != nil { + return + } + animationEditor = new(debugAnimationEditor) + animationEditor.animationConfigLoad() +} + +func (editor *debugAnimationEditor) animationConfigLoad() { + configPath := debugConfigPath("animation_editor") + fileData, err := file.OpenFile(configPath) + if err == nil { + bytes, err := ioutil.ReadAll(fileData) + if err != nil { + panic("Error loading " + configPath + "\n" + "Error: " + err.Error()) + } + editorConfig := animationEditorConfig{} + if err := json.Unmarshal(bytes, &editorConfig); err != nil { + panic("Error unmarshalling " + configPath + "\n" + "Error: " + err.Error()) + } + name := editorConfig.SpriteSelected + editor.spriteViewing = SpriteState{} + // todo(Jake): 2018-10-28 + // Add function to load a sprite if it exists, we don't want to crash + // if we remove a sprite that we previously had loaded. + spr := sprite.SpriteLoadByName(name) + editor.spriteViewing.SetSprite(spr) + } +} + +func (editor *debugAnimationEditor) animationConfigSave() { + editorConfig := animationEditorConfig{} + editorConfig.SpriteSelected = editor.spriteViewing.SpriteIndex().Name() + json, _ := json.MarshalIndent(editorConfig, "", "\t") + configPath := debugConfigPath("animation_editor") + err := ioutil.WriteFile(configPath, json, 0644) + if err != nil { + println("Failed to write animation editor config: " + configPath + "\n" + "Error: " + err.Error()) + } +} + +func (editor *debugAnimationEditor) animationEditorToggleMenu(menu animMenu) { + if editor.menuOpened == menu { + menu = animMenuNone + } + spriteIndex := editor.spriteViewing.SpriteIndex() + imageIndex := int(math.Floor(editor.spriteViewing.ImageIndex())) + collisionMask := sprite.GetCollisionMask(spriteIndex, imageIndex, 0) + value, err := strconv.ParseFloat(KeyboardString(), 64) + if err == nil { + switch editor.menuOpened { + case animMenuSpriteBboxLeft: + diff := value - collisionMask.Rect.X + collisionMask.Rect.X = value + collisionMask.Rect.Size.X -= int32(diff) + case animMenuSpriteBboxTop: + diff := value - collisionMask.Rect.Y + collisionMask.Rect.Y = value + collisionMask.Rect.Size.Y -= int32(diff) + case animMenuSpriteBboxRight: + collisionMask.Rect.Size.X = int32(value - collisionMask.Rect.X) + case animMenuSpriteBboxBottom: + collisionMask.Rect.Size.Y = int32(value - collisionMask.Rect.Y) + } + } + editor.menuOpened = menu +} + +func animationEditorUpdate() { + animationEditorLazyLoad() + editor := animationEditor + DrawSetGUI(true) + + // + { + pos := geom.Vec{16, 16} + DrawTextColor(pos, "Animation Editor", color.White) + pos.Y += 24 + DrawTextColor(pos, "Space = Play/Pause Animation", color.White) + pos.Y += 24 + DrawTextColor(pos, "CTRL + P = Open Sprite List", color.White) + + if spriteIndex := editor.spriteViewing.SpriteIndex(); spriteIndex != sprite.SprUndefined { + pos.Y += 24 + DrawTextColor(pos, "CTRL + S = Save", color.White) + + if KeyboardCheck(VkControl) && KeyboardCheckPressed(VkS) { + err := sprite.DebugWriteSpriteConfig(spriteIndex) + if err != nil { + panic(err) + } + } + } + } + + // Shortcut keys + if KeyboardCheck(VkControl) { + if KeyboardCheckPressed(VkP) { + editor.animationEditorToggleMenu(animMenuSprite) + } + } + if KeyboardCheckPressed(VkSpace) { + editor.isInPlayback = !editor.isInPlayback + } + + // Change frame viewing + if spr := editor.spriteViewing.SpriteIndex(); spr.IsValid() { + imageIndex := math.Floor(editor.spriteViewing.ImageIndex()) + if KeyboardCheckPressed(VkLeft) { + imageIndex -= 1 + if imageIndex < 0 { + imageIndex = editor.spriteViewing.ImageNumber() - 1 + } + editor.spriteViewing.SetImageIndex(imageIndex) + } + if KeyboardCheckPressed(VkRight) { + imageIndex += 1 + if imageIndex > editor.spriteViewing.ImageNumber() { + imageIndex = 0 + } + editor.spriteViewing.SetImageIndex(imageIndex) + } + } + + // + var collisionMask *sprite.CollisionMask + var inheritCollisionMask *sprite.CollisionMask + if spriteIndex := editor.spriteViewing.SpriteIndex(); spriteIndex.IsValid() { + imageIndex := int(math.Floor(editor.spriteViewing.ImageIndex())) + collisionMask = sprite.GetCollisionMask(spriteIndex, imageIndex, 0) + switch collisionMask.Kind { + case sprite.CollisionMaskInherit: + for ; imageIndex > 0; imageIndex-- { + collisionMask = sprite.GetCollisionMask(spriteIndex, imageIndex, 0) + if collisionMask.Kind != sprite.CollisionMaskInherit { + break + } + } + if imageIndex == 0 { + collisionMask = sprite.GetCollisionMask(spriteIndex, imageIndex, 0) + if collisionMask.Kind == sprite.CollisionMaskInherit { + collisionMask = &sprite.CollisionMask{ + Kind: sprite.CollisionMaskManual, + Rect: geom.Rect{ + Size: spriteIndex.Size(), + }, + } + } + } + inheritCollisionMask = collisionMask + case sprite.CollisionMaskManual: + // + } + } + + if spriteIndex := editor.spriteViewing.SpriteIndex(); spriteIndex.IsValid() { + size := spriteIndex.Size() + pos := geom.Vec{float64(windowWidth()/2) - (float64(size.X) / 2), float64(windowHeight()/2) - (float64(size.Y) / 2)} + + { + // Draw backdrop + pos := pos + DrawRectangle(pos, size.Vec(), color.RGBA{195, 195, 195, 255}) + } + + // Sprite + if editor.isInPlayback { + editor.spriteViewing.ImageUpdate() + } + DrawSprite(spriteIndex, editor.spriteViewing.ImageIndex(), pos) + + if collisionMask != nil { + // Draw collision box + var rect geom.Rect = collisionMask.Rect + rect.X += pos.X + rect.Y += pos.Y + DrawRectangle(rect.Vec, rect.Size.Vec(), color.RGBA{255, 0, 0, 128}) + } + + if collisionMask != nil && + inheritCollisionMask == nil { + // Draw resize handles + offset := pos + + // Get distance mouse moved + var diffX, diffY float64 + { + mousePos := mouseScreenPosition() + handleBeginPos := editor.handleDragBeginPos + diffX = mousePos.X - handleBeginPos.X + diffY = mousePos.Y - handleBeginPos.Y + if KeyboardCheck(VkControl) { + diffX = math.Round(diffX / 4) + diffY = math.Round(diffY / 4) + } + } + + { + // Top-Left + rect := geom.Rect{} + rect.Size = geom.Size{12, 12} + rect.X = offset.X + collisionMask.Rect.Left() - float64(rect.Size.X/2) + rect.Y = offset.Y + collisionMask.Rect.Top() - float64(rect.Size.Y/2) + + // Handle hitbox handles + if editor.handleDragging == handleDragLeftTop { + collisionMask.Rect.X += diffX + collisionMask.Rect.Size.X -= int32(diffX) + collisionMask.Rect.Y += diffY + collisionMask.Rect.Size.Y -= int32(diffY) + } + col := color.RGBA{255, 255, 255, 255} + if debugDrawIsMouseOver(rect.Pos(), rect.Size.Vec()) { + if MouseCheckPressed(MbLeft) { + editor.handleDragging = handleDragLeftTop + } + col = color.RGBA{200, 200, 200, 255} + } + DrawRectangle(rect.Pos(), rect.Size.Vec(), col) + } + { + // Top-Right + rect := geom.Rect{} + rect.Size = geom.Size{12, 12} + rect.X = offset.X + collisionMask.Rect.Right() - float64(rect.Size.X/2) + rect.Y = offset.Y + collisionMask.Rect.Top() - float64(rect.Size.Y/2) + + // Handle hitbox handles + if editor.handleDragging == handleDragRightTop { + collisionMask.Rect.Size.X += int32(diffX) + collisionMask.Rect.Y += diffY + collisionMask.Rect.Size.Y -= int32(diffY) + } + col := color.RGBA{255, 255, 255, 255} + if debugDrawIsMouseOver(rect.Pos(), rect.Size.Vec()) { + if MouseCheckPressed(MbLeft) { + editor.handleDragging = handleDragRightTop + } + col = color.RGBA{200, 200, 200, 255} + } + DrawRectangle(rect.Pos(), rect.Size.Vec(), col) + } + { + // Bottom-Left + rect := geom.Rect{} + rect.Size = geom.Size{12, 12} + rect.X = offset.X + collisionMask.Rect.Left() - float64(rect.Size.X/2) + rect.Y = offset.Y + collisionMask.Rect.Bottom() - float64(rect.Size.Y/2) + + // Handle hitbox handles + if editor.handleDragging == handleDragLeftBottom { + collisionMask.Rect.X += diffX + collisionMask.Rect.Size.X -= int32(diffX) + //collisionMask.Rect.Y = diffY + collisionMask.Rect.Size.Y += int32(diffY) + } + col := color.RGBA{255, 255, 255, 255} + if debugDrawIsMouseOver(rect.Pos(), rect.Size.Vec()) { + if MouseCheckPressed(MbLeft) { + editor.handleDragging = handleDragLeftBottom + } + col = color.RGBA{200, 200, 200, 255} + } + DrawRectangle(rect.Pos(), rect.Size.Vec(), col) + } + { + // Bottom-Right + rect := geom.Rect{} + rect.Size = geom.Size{12, 12} + rect.X = offset.X + collisionMask.Rect.Right() - float64(rect.Size.X/2) + rect.Y = offset.Y + collisionMask.Rect.Bottom() - float64(rect.Size.Y/2) + + // Handle hitbox handles + if editor.handleDragging == handleDragRightBottom { + collisionMask.Rect.Size.X += int32(diffX) + collisionMask.Rect.Size.Y += int32(diffY) + } + col := color.RGBA{255, 255, 255, 255} + if debugDrawIsMouseOver(rect.Pos(), rect.Size.Vec()) { + if MouseCheckPressed(MbLeft) { + editor.handleDragging = handleDragRightBottom + } + col = color.RGBA{200, 200, 200, 255} + } + DrawRectangle(rect.Pos(), rect.Size.Vec(), col) + } + { + // Update State + editor.handleDragBeginPos = mouseScreenPosition() + if !MouseCheckButton(MbLeft) { + editor.handleDragging = handleDragNone + } + } + } + } + + if editor.menuOpened != animMenuNone { + switch editor.menuOpened { + case animMenuSprite: + if selectedSpr, ok := animationEditor.debugSpriteViewer.update(); ok { + editor.spriteViewing = SpriteState{} + editor.spriteViewing.SetSprite(selectedSpr) + editor.menuOpened = animMenuNone + editor.animationConfigSave() + } + } + } + + if spriteIndex := editor.spriteViewing.SpriteIndex(); spriteIndex.IsValid() { + basePos := geom.Vec{(float64(windowWidth()) / 2) - 140, float64(windowHeight())} + basePos.Y -= 210 + + imageIndex := int(math.Floor(editor.spriteViewing.ImageIndex())) + DrawTextF(basePos, "Frame: %d", imageIndex) + basePos.Y += 24 + if drawButton(basePos, "Kind: Inherit") { + collisionMask = sprite.GetCollisionMask(spriteIndex, imageIndex, 0) + collisionMask.Kind = sprite.CollisionMaskInherit + } + basePos.Y += 30 + if drawButton(basePos, "Kind: Manual") { + collisionMask = sprite.GetCollisionMask(spriteIndex, imageIndex, 0) + if collisionMask.Kind != sprite.CollisionMaskManual { + collisionMask.Rect = inheritCollisionMask.Rect + collisionMask.Kind = sprite.CollisionMaskManual + } + } + basePos.Y += 40 + + pos := basePos + + // + drawMask := inheritCollisionMask + if drawMask == nil { + drawMask = collisionMask + } + + { + text := strconv.FormatFloat(drawMask.Rect.Left(), 'f', -1, 64) + if KeyboardCheck(VkControl) && KeyboardCheckPressed(Vk1) { + editor.animationEditorToggleMenu(animMenuSpriteBboxLeft) + if editor.menuOpened == animMenuSpriteBboxLeft { + SetKeyboardString(text) + } + } + if drawInputText(&pos, "Left (CTRL + 1)", text, editor.menuOpened == animMenuSpriteBboxLeft) { + editor.animationEditorToggleMenu(animMenuSpriteBboxLeft) + } + } + { + pos.Y += 24 + + text := strconv.FormatFloat(drawMask.Rect.Bottom(), 'f', -1, 64) + if KeyboardCheck(VkControl) && KeyboardCheckPressed(Vk3) { + editor.animationEditorToggleMenu(animMenuSpriteBboxBottom) + if editor.menuOpened == animMenuSpriteBboxBottom { + SetKeyboardString(text) + } + } + if drawInputText(&pos, "Bottom (CTRL + 3)", text, editor.menuOpened == animMenuSpriteBboxBottom) { + editor.animationEditorToggleMenu(animMenuSpriteBboxBottom) + } + } + pos = basePos + pos.X += 160 + { + text := strconv.FormatFloat(drawMask.Rect.Top(), 'f', -1, 64) + if KeyboardCheck(VkControl) && KeyboardCheckPressed(Vk2) { + editor.animationEditorToggleMenu(animMenuSpriteBboxTop) + if editor.menuOpened == animMenuSpriteBboxTop { + SetKeyboardString(text) + } + } + if drawInputText(&pos, "Top (CTRL + 2)", text, editor.menuOpened == animMenuSpriteBboxTop) { + editor.animationEditorToggleMenu(animMenuSpriteBboxTop) + } + } + { + pos.Y += 24 + + text := strconv.FormatFloat(drawMask.Rect.Right(), 'f', -1, 64) + if KeyboardCheck(VkControl) && KeyboardCheckPressed(Vk4) { + editor.animationEditorToggleMenu(animMenuSpriteBboxRight) + if editor.menuOpened == animMenuSpriteBboxRight { + SetKeyboardString(text) + } + } + if drawInputText(&pos, "Right (CTRL + 4)", text, editor.menuOpened == animMenuSpriteBboxRight) { + editor.animationEditorToggleMenu(animMenuSpriteBboxRight) + } + } + } +} diff --git a/gml/animation_editor_nodebug.go b/gml/animation_editor_nodebug.go new file mode 100644 index 0000000..482d4fd --- /dev/null +++ b/gml/animation_editor_nodebug.go @@ -0,0 +1,7 @@ +// +build !debug + +package gml + +func animationEditorUpdate() { + +} diff --git a/gml/camera.go b/gml/camera.go index 7775182..e6ef96b 100644 --- a/gml/camera.go +++ b/gml/camera.go @@ -1,106 +1,7 @@ -package gml +// +build headless -import ( - "github.com/silbinarywolf/gml-go/gml/internal/object" -) +package gml var ( - __currentCamera *camera - cameraList [8]camera + gCameraManager *cameraManager = newCameraState() ) - -type camera struct { - enabled bool - follow object.ObjectType - Vec - size Vec -} - -func CameraSetEnabled(index int) { - view := &cameraList[index] - view.enabled = true -} - -func cameraGetActive() *camera { - return __currentCamera -} - -func cameraSetActive(index int) { - __currentCamera = &cameraList[index] -} - -func cameraClearActive() { - __currentCamera = nil -} - -func CameraSetViewPos(index int, pos Vec) { - view := &cameraList[index] - view.Vec = pos - - if inst := view.follow; inst != nil { - roomInst := RoomGetInstance(inst.BaseObject().RoomInstanceIndex()) - if roomInst != nil { - room := roomInst.room - left := float64(room.Left) - right := float64(room.Right) - top := float64(room.Top) - bottom := float64(room.Bottom) - - view.X = pos.X - (view.size.X / 2) - view.Y = pos.Y - (view.size.Y / 2) - if view.X < left { - view.X = left - } - if view.X+view.size.X > right { - view.X = right - view.size.X - } - if view.Y < top { - view.Y = top - } - if view.Y+view.size.Y > bottom { - view.Y = bottom - view.size.Y - } - } - } -} - -func CameraSetViewSize(index int, size Vec) { - view := &cameraList[index] - view.size = size -} - -func CameraSetViewTarget(index int, inst object.ObjectType) { - view := &cameraList[index] - view.follow = inst -} - -func (view *camera) update() { - if view.follow != nil { - //cam := cameraGetActive() - inst := view.follow.BaseObject() - - roomInst := RoomGetInstance(inst.RoomInstanceIndex()) - if roomInst != nil { - room := roomInst.room - left := float64(room.Left) - right := float64(room.Right) - top := float64(room.Top) - bottom := float64(room.Bottom) - - view.X = inst.X - (view.size.X / 2) - view.Y = inst.Y - (view.size.Y / 2) - if view.X < left { - view.X = left - } - if view.X+view.size.X > right { - view.X = right - view.size.X - } - if view.Y < top { - view.Y = top - } - if view.Y+view.size.Y > bottom { - view.Y = bottom - view.size.Y - } - } - } -} diff --git a/gml/camera_headless.go b/gml/camera_headless.go new file mode 100644 index 0000000..0a5487b --- /dev/null +++ b/gml/camera_headless.go @@ -0,0 +1,64 @@ +// +build headless + +package gml + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/object" +) + +type cameraManager struct { +} + +type camera struct { +} + +func newCameraState() *cameraManager { + return nil +} + +func (view *camera) Reset() { +} + +func CameraCreate(index int, windowX, windowY, windowWidth, windowHeight float64) { +} + +func CameraSetSize(index int, windowWidth, windowHeight float64) { +} + +// cameraGetActive gets the current camera we're drawing objects onto +func cameraGetActive() *camera { + return nil +} + +// cameraSetActive gets the current camera we want to draw objects onto +func cameraSetActive(index int) { +} + +func cameraClearActive() { +} + +func CameraGetViewPos(index int) geom.Vec { + return geom.Vec{0, 0} +} + +func CameraSetViewPos(index int, pos geom.Vec) { +} + +func CameraSetViewSize(index int, size geom.Vec) { +} + +func CameraSetViewTarget(index int, inst object.ObjectType) { +} + +func cameraClear(index int) { +} + +func cameraDraw(index int) { +} + +func cameraInstanceDestroy(inst object.ObjectType) { +} + +func (view *camera) update() { +} diff --git a/gml/camera_nonheadless.go b/gml/camera_nonheadless.go new file mode 100644 index 0000000..afeb1e5 --- /dev/null +++ b/gml/camera_nonheadless.go @@ -0,0 +1,222 @@ +// +build !headless + +package gml + +import ( + "math" + "strconv" + + "github.com/hajimehoshi/ebiten" + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/object" +) + +var ( + gCameraManager *cameraManager = newCameraState() +) + +type cameraManager struct { + cameras [8]camera + current *camera +} + +type camera struct { + enabled bool + follow object.ObjectType + geom.Vec + windowPos geom.Vec + size geom.Vec + scale geom.Vec + screen *ebiten.Image +} + +func newCameraState() *cameraManager { + manager := new(cameraManager) + for i := 0; i < len(manager.cameras); i++ { + view := &manager.cameras[i] + view.Reset() + } + return manager +} + +func (view *camera) Reset() { + view.size.X = float64(windowWidth()) + view.size.Y = float64(windowHeight()) + view.scale.X = 1 + view.scale.Y = 1 +} + +//func (view *camera) Size() geom.Vec { +// return view.size +//} + +func (view *camera) Scale() geom.Vec { + return view.scale +} + +func CameraCreate(index int, windowX, windowY, windowWidth, windowHeight float64) { + view := &gCameraManager.cameras[index] + if view.enabled { + panic("Camera " + strconv.Itoa(index) + " is already enabled.") + return + } + if windowWidth == 0 || + windowHeight == 0 { + panic("Cannot have camera window width or height of 0") + } + view.windowPos.X = windowX + view.windowPos.Y = windowY + view.size.X = windowWidth + view.size.Y = windowHeight + view.enabled = true +} + +func CameraSetSize(index int, windowWidth, windowHeight float64) { + view := &gCameraManager.cameras[index] + if !view.enabled { + panic("Camera " + strconv.Itoa(index) + " is not enabled.") + } + view.size.X = windowWidth + view.size.Y = windowHeight +} + +// cameraGetActive gets the current camera we're drawing objects onto +func cameraGetActive() *camera { + return gCameraManager.current +} + +// cameraSetActive gets the current camera we want to draw objects onto +func cameraSetActive(index int) { + gCameraManager.current = &gCameraManager.cameras[index] +} + +func cameraClearActive() { + gCameraManager.current = nil +} + +func CameraGetViewPos(index int) geom.Vec { + view := &gCameraManager.cameras[index] + return view.Vec +} + +func CameraSetViewPos(index int, pos geom.Vec) { + view := &gCameraManager.cameras[index] + view.Vec = pos + + if inst := view.follow; inst != nil { + roomInst := roomGetInstance(inst.BaseObject().RoomInstanceIndex()) + if roomInst != nil { + room := roomInst.room + left := float64(room.Left) + right := float64(room.Right) + top := float64(room.Top) + bottom := float64(room.Bottom) + + view.X = pos.X - (view.size.X / 2) + view.Y = pos.Y - (view.size.Y / 2) + if view.X < left { + view.X = left + } + if view.X+view.size.X > right { + view.X = right - view.size.X + } + if view.Y < top { + view.Y = top + } + if view.Y+view.size.Y > bottom { + view.Y = bottom - view.size.Y + } + view.X = math.Floor(view.X) + view.Y = math.Floor(view.Y) + } + } +} + +func CameraSetViewSize(index int, size geom.Vec) { + view := &gCameraManager.cameras[index] + view.size = size +} + +func CameraSetViewTarget(index int, inst object.ObjectType) { + view := &gCameraManager.cameras[index] + view.follow = inst +} + +func cameraClear(index int) { + view := &gCameraManager.cameras[index] + view.screen.Clear() +} + +func cameraDraw(index int) { + view := &gCameraManager.cameras[index] + op := ebiten.DrawImageOptions{} + op.GeoM.Scale(view.scale.X, view.scale.Y) + op.GeoM.Translate(view.windowPos.X, view.windowPos.Y) + gScreen.DrawImage(view.screen, &op) +} + +func cameraInstanceDestroy(inst object.ObjectType) { + manager := gCameraManager + for i := 0; i < len(manager.cameras); i++ { + view := &manager.cameras[i] + if view.follow == inst { + view.follow = nil + } + } +} + +func (view *camera) update() { + // Update screen render target + { + mustCreateNewRenderTarget := false + if view.screen == nil { + // Create new camera + mustCreateNewRenderTarget = true + } else { + // Resize camera + if int(view.size.X) != view.screen.Bounds().Max.X || + int(view.size.Y) != view.screen.Bounds().Max.Y { + mustCreateNewRenderTarget = true + } + } + if mustCreateNewRenderTarget { + image, err := ebiten.NewImage(int(view.size.X), int(view.size.Y), ebiten.FilterDefault) + if err != nil { + panic(err) + } + view.screen = image + } + } + + // Update player follow + if view.follow != nil { + inst := view.follow.BaseObject() + if inst != nil { + roomInst := roomGetInstance(inst.BaseObject().RoomInstanceIndex()) + if roomInst != nil { + room := roomInst.room + left := float64(room.Left) + right := float64(room.Right) + top := float64(room.Top) + bottom := float64(room.Bottom) + + view.X = inst.X - (view.size.X / 2) + view.Y = inst.Y - (view.size.Y / 2) + if view.X < left { + view.X = left + } + if view.X+view.size.X > right { + view.X = right - view.size.X + } + if view.Y < top { + view.Y = top + } + if view.Y+view.size.Y > bottom { + view.Y = bottom - view.size.Y + } + view.X = math.Floor(view.X) + view.Y = math.Floor(view.Y) + } + } + } +} diff --git a/gml/collision.go b/gml/collision.go index 2da5102..0df3189 100644 --- a/gml/collision.go +++ b/gml/collision.go @@ -1,5 +1,10 @@ package gml +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/object" +) + const ( DEBUG_COLLISION = false ) @@ -8,54 +13,112 @@ type collisionObject interface { BaseObject() *Object } -func PlaceFree(instType collisionObject, position Vec) bool { - baseObj := instType.BaseObject() +func CollisionRectList(instType collisionObject, position geom.Vec) []object.ObjectType { + inst := instType.BaseObject() + room := roomGetInstance(inst.BaseObject().RoomInstanceIndex()) + if room == nil { + panic("RoomInstance this object belongs to has been destroyed") + } - var instanceManager *instanceManager - { - inst := baseObj + // Create collision rect at position provided in function + r1 := inst.Rect + r1.Vec = position + r1.Size = inst.Size - if room := RoomGetInstance(inst.RoomInstanceIndex()); room == nil { - instanceManager = gState.globalInstances - } else { - instanceManager = &room.instanceManager + // todo(Jake): 2018-12-01 - #18 + // Consider pooling reusable object.ObjectType slices to + // improve performance. + var list []object.ObjectType + for i := 0; i < len(room.instanceLayers); i++ { + for _, otherT := range room.instanceLayers[i].manager.instances { + other := otherT.BaseObject() + if !object.IsDestroyed(other) && + r1.CollisionRectangle(other.Rect) && + inst != other { + list = append(list, otherT) + } } } + if len(list) == 0 { + return nil + } + return list +} + +func PlaceFree(instType collisionObject, position geom.Vec) bool { + inst := instType.BaseObject() + room := roomGetInstance(inst.BaseObject().RoomInstanceIndex()) + if room == nil { + panic("RoomInstance this object belongs to has been destroyed") + } - inst := baseObj.Space - r1Left := position.X - r1Right := r1Left + float64(inst.Size.X) - r1Top := position.Y - r1Bottom := r1Top + float64(inst.Size.Y) + // Create collision rect at position provided in function + r1 := inst.Rect + r1.Vec = position + r1.Size = inst.Size //var debugString string hasCollision := false - for _, bucket := range instanceManager.spaces.Buckets() { - for i := 0; i < bucket.Len(); i++ { - other := bucket.Get(i) - r2Left := other.X - r2Right := r2Left + float64(other.Size.X) - r2Top := other.Y - r2Bottom := r2Top + float64(other.Size.Y) - - // NOTE(Jake): 2018-07-08 - // - // For JavaScript performance, we get a 1.2x speedup if we - // handle as much logic in one if-statement as possible. - // - // For native binaries, it doesn't seem to change performance noticeably - // at all if I add "if inst == other || !instanceManager.spaces.IsUsed(i) { continue; }" - // - // ("gjbt" and Chrome 67 Windows were for benchmarking) - // - if r1Left < r2Right && r1Right > r2Left && - r1Top < r2Bottom && r1Bottom > r2Top && - inst != other && - bucket.IsUsed(i) { + for i := 0; i < len(room.instanceLayers); i++ { + for _, other := range room.instanceLayers[i].manager.instances { + other := other.BaseObject() + if other.Solid() && + r1.CollisionRectangle(other.Rect) && + inst != other { hasCollision = true } } + /*spaces := &room.instanceLayers[i].manager.spaces + for _, bucket := range spaces.Buckets() { + for i := 0; i < bucket.Len(); i++ { + other := bucket.Get(i) + // NOTE(Jake): 2018-07-08 + // + // For JavaScript performance, we get a 1.2x speedup if we + // handle as much logic in one if-statement as possible. + // + // For native binaries, it doesn't seem to change performance noticeably + // at all if I add "if inst == other || !instanceManager.spaces.IsUsed(i) { continue; }" + // + // ("gjbt" and Chrome 67 Windows were for benchmarking) + // + // NOTE(Jake): 2018-08-11 + // + // Heavily refactored this since the above benchmark. But who cares really. I'll probably + // need to re-do this collision engine so it supports spatial hashing. + // + if other.Solid() && + r1.CollisionRectangle(other.Rect) && + inst != other && + bucket.IsUsed(i) { + hasCollision = true + } + } + }*/ } + for i := 0; i < len(room.spriteLayers); i++ { + layer := &room.spriteLayers[i] + if !layer.hasCollision { + continue + } + for _, other := range layer.sprites { + if r1.CollisionRectangle(other.Rect()) { + hasCollision = true + } + } + /*spaces := layer.sprites + for _, bucket := range spaces.Buckets() { + for i := 0; i < bucket.Len(); i++ { + other := bucket.Get(i) + if r1.CollisionRectangle(other.Rect) && + inst != other && + bucket.IsUsed(i) { + hasCollision = true + } + } + }*/ + } + /*if DEBUG_COLLISION && len(debugString) > 0 { // Get calling function name / line diff --git a/gml/collision_test.go b/gml/collision_test.go deleted file mode 100644 index 7a09d0d..0000000 --- a/gml/collision_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package gml - -import ( - "testing" -) - -// NOTE(Jake): 2018-07-08 -// -// Native: ("go test --bench=.") -// ------- -// BenchmarkPlaceFree250-4 3000000 424 ns/op -// BenchmarkPlaceFree500-4 2000000 877 ns/op -// BenchmarkPlaceFreeMMOCase_250SolidWalls_1024MovingEntities-4 100 23510338 ns/op -// -// JS: ("GOOS=linux gjbt --bench=."") -// --- -// BenchmarkPlaceFree250 500000 2326 ns/op -// BenchmarkPlaceFree500 300000 3910 ns/op -// BenchmarkPlaceFreeMMOCase_250SolidWalls_1024MovingEntities 10 103200000 ns/op -// - -// NOTE(Jake): 2018-07-07 -// -// Entities: -// - 250 "wall" solid entities -// - 1 player entity -// - Player entity calling "PlaceFree" -// -func BenchmarkPlaceFree250(b *testing.B) { - roomInstance := RoomInstanceEmptyCreate() - // Create solid instances to test against - // NOTE(Jake): 2018-07-07 - // - // Haven't written collision types for objects yet, so - // everything is considered solid. - // - for i := 0; i < 250; i++ { - roomInstance.InstanceCreate(V(0, 0), ObjDummyPlayer) - } - playerInstance := roomInstance.InstanceCreate(V(0, 0), ObjDummyPlayer).(*DummyPlayer) - - b.ResetTimer() - for n := 0; n < b.N; n++ { - PlaceFree(playerInstance, V(32, 32)) - } -} - -// NOTE(Jake): 2018-07-07 -// -// Entities: -// - 500 "wall" solid entities -// - 1 player entity -// - Player entity calling "PlaceFree" -// -func BenchmarkPlaceFree500(b *testing.B) { - roomInstance := RoomInstanceEmptyCreate() - // Create solid instances to test against - // NOTE(Jake): 2018-07-07 - // - // Haven't written collision types for objects yet, so - // everything is considered solid. - // - for i := 0; i < 500; i++ { - roomInstance.InstanceCreate(V(0, 0), ObjDummyPlayer) - } - playerInstance := roomInstance.InstanceCreate(V(0, 0), ObjDummyPlayer).(*DummyPlayer) - - b.ResetTimer() - for n := 0; n < b.N; n++ { - PlaceFree(playerInstance, V(32, 32)) - } -} - -// NOTE(Jake): 2018-07-07 -// -// Entities: -// - 250 "wall" solid entities -// - 1024 moving/non-trivial entities -// - All 1024 moving entities calling "PlaceFree" 10 times. -// -func BenchmarkPlaceFreeMMOCase_250SolidWalls_1024MovingEntities(b *testing.B) { - roomInstance := RoomInstanceEmptyCreate() - // Create solid instances to test against - // NOTE(Jake): 2018-07-07 - // - // Haven't written collision types for objects yet, so - // everything is considered solid. - // - for i := 0; i < 250; i++ { - roomInstance.InstanceCreate(V(float64(i*32), 0), ObjDummyPlayer) - } - movingEntityInstances := make([]*DummyPlayer, 1024) - for i := 0; i < len(movingEntityInstances); i++ { - movingEntityInstances[i] = roomInstance.InstanceCreate(V(0, 0), ObjDummyPlayer).(*DummyPlayer) - } - - b.ResetTimer() - for n := 0; n < b.N; n++ { - for _, movingEntityInstance := range movingEntityInstances { - // NOTE(Jake): 2018-07-07 - // - // Assume each entity would call PlaceFree() at least 10 times each. - // - for i := 0; i < 10; i++ { - PlaceFree(movingEntityInstance, V(32, 32)) - } - } - } -} diff --git a/gml/debug.go b/gml/debug.go index 6905805..14dd06a 100644 --- a/gml/debug.go +++ b/gml/debug.go @@ -1,7 +1,9 @@ -// +build !debug - package gml +type debugMenu int + const ( - debugMode = false + debugMenuNone debugMenu = 0 + iota + debugMenuRoomEditor + debugMenuAnimationEditor ) diff --git a/gml/debug_debug.go b/gml/debug_debug.go index ed2dd3e..e968a74 100644 --- a/gml/debug_debug.go +++ b/gml/debug_debug.go @@ -2,6 +2,80 @@ package gml +import ( + "os" + + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" + "github.com/silbinarywolf/gml-go/gml/internal/user" +) + const ( debugMode = true ) + +var ( + debugMenuID = debugMenuNone +) + +func debugConfigPath(name string) string { + configPath := user.HomeDir() + "/.gmlgo" + if _, err := os.Stat(configPath); os.IsNotExist(err) { + os.Mkdir(configPath, 0700) + } + configPath = configPath + "/" + name + ".json" + return configPath +} + +func debugMenuOpenOrToggleClosed(id debugMenu) { + if debugMenuID != id { + debugMenuID = id + } else { + debugMenuID = debugMenuNone + + // Reset camera + CameraSetViewSize(0, geom.Vec{float64(windowWidth()), float64(windowHeight())}) + CameraSetViewTarget(0, nil) + } +} + +func debugUpdate() { + sprite.DebugWatch() + + if KeyboardCheck(VkControl) { + if KeyboardCheckPressed(VkA) { + debugMenuOpenOrToggleClosed(debugMenuAnimationEditor) + } + if KeyboardCheckPressed(VkR) { + debugMenuOpenOrToggleClosed(debugMenuRoomEditor) + } + } +} + +func debugDrawIsMouseOver(pos geom.Vec, size geom.Vec) bool { + if DrawGetGUI() { + return isMouseScreenOver(pos, size) + } else { + return isMouseOver(pos, size) + } +} + +func isMouseOver(pos geom.Vec, size geom.Vec) bool { + mousePos := MousePosition() + left := pos.X + right := left + float64(size.X) + top := pos.Y + bottom := top + float64(size.Y) + return mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom +} + +func isMouseScreenOver(pos geom.Vec, size geom.Vec) bool { + mousePos := mouseScreenPosition() + left := pos.X + right := left + float64(size.X) + top := pos.Y + bottom := top + float64(size.Y) + return mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom +} diff --git a/gml/debug_nodebug.go b/gml/debug_nodebug.go new file mode 100644 index 0000000..9a9476b --- /dev/null +++ b/gml/debug_nodebug.go @@ -0,0 +1,14 @@ +// +build !debug + +package gml + +const ( + debugMode = false +) + +const ( + debugMenuID = debugMenuNone +) + +func debugUpdate() { +} diff --git a/gml/draw.go b/gml/draw.go index 08d68ce..9622d10 100644 --- a/gml/draw.go +++ b/gml/draw.go @@ -1,9 +1,10 @@ package gml import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) -func DrawSelf(state *sprite.SpriteState, position Vec) { - DrawSpriteExt(state.Sprite(), state.ImageIndex(), position, state.ImageScale) +func DrawSelf(state *sprite.SpriteState, position geom.Vec) { + DrawSpriteScaled(state.SpriteIndex(), state.ImageIndex(), position, state.ImageScale) } diff --git a/gml/draw_debug.go b/gml/draw_debug.go new file mode 100644 index 0000000..85e1178 --- /dev/null +++ b/gml/draw_debug.go @@ -0,0 +1,70 @@ +// +build debug + +package gml + +import ( + "image/color" + + "github.com/silbinarywolf/gml-go/gml/internal/geom" +) + +func drawInputText(pos *geom.Vec, label string, text string, isFocused bool) bool { + size := geom.Vec{100, 20} + DrawTextColor(geom.Vec{pos.X, pos.Y}, label, color.White) + pos.Y += 12 + borderCol := color.RGBA{255, 255, 255, 255} + isMouseOver := debugDrawIsMouseOver(*pos, size) + if isMouseOver { + borderCol = color.RGBA{255, 255, 0, 255} + } + if isFocused { + text = KeyboardString() + "|" + borderCol = color.RGBA{255, 0, 0, 255} + } + DrawRectangleBorder(*pos, size, color.Black, 2, borderCol) + DrawTextColor(geom.Vec{pos.X + 8, pos.Y + 16}, text, color.White) + pos.Y += size.Y + if MouseCheckPressed(MbLeft) && isMouseOver { + if !isFocused { + SetKeyboardString(text) + } + return true + } + if isFocused && + (KeyboardCheckPressed(VkEnter) || KeyboardCheckPressed(VkNumpadEnter)) { + return true + } + return false +} + +func drawButton(pos geom.Vec, text string) bool { + // Config + paddingH := 32.0 + borderWidth := 2.0 + size := geom.Vec{StringWidth(text) + paddingH, 24} + + // Handle mouse over + isMouseOver := debugDrawIsMouseOver(pos, size) + var innerRectColor color.RGBA + if isMouseOver { + innerRectColor = color.RGBA{180, 180, 180, 255} + } else { + innerRectColor = color.RGBA{255, 255, 255, 255} + } + + // Draw Border (outer rect) + DrawRectangleBorder(pos, size, innerRectColor, borderWidth, color.RGBA{0, 162, 232, 255}) + /* pos.X += borderWidth + pos.Y += borderWidth + size.X -= borderWidth * 2 + size.Y -= borderWidth * 2 + + // Draw Rect (inner rect) + DrawRectangle(pos, size, innerRectColor)*/ + + // Draw Text + pos.X += paddingH * 0.5 + pos.Y += 16 + DrawTextColor(pos, text, color.Black) + return MouseCheckPressed(MbLeft) && isMouseOver +} diff --git a/gml/draw_headless.go b/gml/draw_headless.go index 7171fe4..cfca667 100644 --- a/gml/draw_headless.go +++ b/gml/draw_headless.go @@ -8,14 +8,30 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) -func DrawSprite(spr *sprite.Sprite, subimage float64, position Vec) { +func DrawGetGUI() bool { + return false } -func DrawSpriteExt(spr *sprite.Sprite, subimage float64, position Vec, scale Vec) { +func DrawSetGUI(guiMode bool) { } -func DrawRectangle(pos Vec, size Vec, col color.RGBA) { +func DrawSprite(spr sprite.SpriteIndex, subimage float64, position Vec) { +} + +func DrawSpriteScaled(spr sprite.SpriteIndex, subimage float64, position Vec, scale Vec) { +} + +func DrawSpriteExt(spr sprite.SpriteIndex, subimage float64, position Vec, scale Vec, alpha float64) { +} + +func DrawRectangle(pos Vec, size Vec, col color.Color) { +} + +func DrawRectangleBorder(position Vec, size Vec, color color.Color, borderSize float64, borderColor color.Color) { } func DrawText(position Vec, message string) { } + +func DrawTextF(position Vec, message string, args ...interface{}) { +} diff --git a/gml/draw_nonheadless.go b/gml/draw_nonheadless.go index 7a6ebe8..619aa3f 100644 --- a/gml/draw_nonheadless.go +++ b/gml/draw_nonheadless.go @@ -3,54 +3,115 @@ package gml import ( + "fmt" "image/color" + "math" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/text" + + "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) -func DrawSprite(spr *sprite.Sprite, subimage float64, position Vec) { - screen := gScreen - { - camPos := cameraGetActive().Vec - position.X -= camPos.X - position.Y -= camPos.Y - } +var ( + isDrawGuiMode = false +) - frame := sprite.GetRawFrame(spr, subimage) - op := ebiten.DrawImageOptions{} - op.GeoM.Translate(position.X, position.Y) - screen.DrawImage(frame, &op) +// DrawGetGUI returns whether Draw functions will draw relative to the screen or not +func DrawGetGUI() bool { + return isDrawGuiMode +} + +// DrawSetGUI allows you to set whether you want to draw relative to the screen (true) or to the world (false) +func DrawSetGUI(guiMode bool) { + isDrawGuiMode = guiMode +} + +func DrawSprite(spriteIndex sprite.SpriteIndex, subimage float64, position geom.Vec) { + DrawSpriteExt(spriteIndex, subimage, position, geom.Vec{1, 1}, 1.0) +} + +func DrawSpriteScaled(spriteIndex sprite.SpriteIndex, subimage float64, position geom.Vec, scale geom.Vec) { + DrawSpriteExt(spriteIndex, subimage, position, scale, 1.0) } // draw_sprite_ext( sprite, subimg, x, y, xscale, yscale, rot, colour, alpha ); -func DrawSpriteExt(spr *sprite.Sprite, subimage float64, position Vec, scale Vec) { - screen := gScreen - { - camPos := cameraGetActive().Vec - position.X -= camPos.X - position.Y -= camPos.Y - } +func DrawSpriteExt(spriteIndex sprite.SpriteIndex, subimage float64, position geom.Vec, scale geom.Vec, alpha float64) { + position = maybeApplyOffsetByCamera(position) + // NOTE(Jake): 2018-07-09 + // + // This doesn't work. A cleaner solution might be to + // render everything to a seperate image if possible then + // scale that and render. + // + // Since this is only really needed for the map editor, I dont + // have a problem with it. + // + //scale.X *= view.Scale().X + //scale.Y *= view.Scale().Y - frame := sprite.GetRawFrame(spr, subimage) + frame := sprite.GetRawFrame(spriteIndex, int(math.Floor(subimage))) op := ebiten.DrawImageOptions{} - // op.GeoM.Scale(width/float64(ew), height/float64(eh)) op.GeoM.Scale(scale.X, scale.Y) op.GeoM.Translate(position.X, position.Y) - screen.DrawImage(frame, &op) + + op.ColorM.Scale(1.0, 1.0, 1.0, alpha) + //op.Colorgeom.RotateHue(float64(360)) + + drawGetTarget().DrawImage(frame, &op) +} + +func DrawRectangle(position geom.Vec, size geom.Vec, col color.Color) { + position = maybeApplyOffsetByCamera(position) + + ebitenutil.DrawRect(drawGetTarget(), position.X, position.Y, size.X, size.Y, col) +} + +func DrawRectangleBorder(position geom.Vec, size geom.Vec, color color.Color, borderSize float64, borderColor color.Color) { + position = maybeApplyOffsetByCamera(position) + ebitenutil.DrawRect(drawGetTarget(), position.X, position.Y, size.X, size.Y, borderColor) + position.X += borderSize + position.Y += borderSize + size.X -= borderSize * 2 + size.Y -= borderSize * 2 + ebitenutil.DrawRect(drawGetTarget(), position.X, position.Y, size.X, size.Y, color) } -func DrawRectangle(pos Vec, size Vec, col color.RGBA) { - screen := gScreen - ebitenutil.DrawRect(screen, pos.X, pos.Y, size.X, size.Y, col) +func DrawText(position geom.Vec, message string) { + if !g_fontManager.hasFontSet() { + panic("Must call DrawSetFont() before calling DrawText.") + } + position = maybeApplyOffsetByCamera(position) + text.Draw(drawGetTarget(), message, g_fontManager.currentFont.font, int(position.X), int(position.Y), color.White) } -func DrawText(position Vec, message string) { - screen := gScreen +func DrawTextColor(position geom.Vec, message string, col color.Color) { if !g_fontManager.hasFontSet() { panic("Must call DrawSetFont() before calling DrawText.") } - text.Draw(screen, message, g_fontManager.currentFont.font, int(position.X), int(position.Y), color.White) + position = maybeApplyOffsetByCamera(position) + text.Draw(drawGetTarget(), message, g_fontManager.currentFont.font, int(position.X), int(position.Y), col) +} + +func DrawTextF(position Vec, format string, args ...interface{}) { + DrawText(position, fmt.Sprintf(format, args...)) +} + +func drawGetTarget() *ebiten.Image { + if camera := cameraGetActive(); camera != nil { + return camera.screen + } + return gScreen +} + +func maybeApplyOffsetByCamera(position geom.Vec) geom.Vec { + if !isDrawGuiMode { + if view := cameraGetActive(); view != nil { + position.X -= view.X + position.Y -= view.Y + } + } + return position } diff --git a/gml/file.go b/gml/file.go index 35b9d11..1050c76 100644 --- a/gml/file.go +++ b/gml/file.go @@ -1,22 +1,22 @@ package gml import ( - "io/ioutil" - "strings" - "github.com/silbinarywolf/gml-go/gml/internal/file" ) -func AssetsDirectory() string { - return file.AssetsDirectory +func AssetDirectory() string { + return file.AssetDirectory } func ProgramDirectory() string { return file.ProgramDirectory } -func ReadFileAsString(path string) (string, error) { - path = AssetsDirectory() + "/" + path +// todo(Jake): 2018-12-02: #21 +// Deprecated. Only used in private project, can be removed after we support "data" binary files +// FileStringReadAll will read a file from the "asset" directory, used to be ReadFileAsString +/*func FileStringReadAll(path string) (string, error) { + path = AssetDirectory() + "/" + path fileData, err := file.OpenFile(path) if err != nil { return "", err @@ -28,3 +28,4 @@ func ReadFileAsString(path string) (string, error) { } return strings.TrimSpace(string(bytesData)), nil } +*/ diff --git a/gml/font_manager.go b/gml/font_manager.go index dcb91b0..e72f3bb 100644 --- a/gml/font_manager.go +++ b/gml/font_manager.go @@ -1,5 +1,7 @@ package gml +// todo(Jake): 2018-12-02 - #27 +// Consider simplifying this or removing it type FontSettings struct { DPI float64 Size float64 diff --git a/gml/font_manager_nonheadless.go b/gml/font_manager_nonheadless.go index 3cecfe3..f687d03 100644 --- a/gml/font_manager_nonheadless.go +++ b/gml/font_manager_nonheadless.go @@ -7,12 +7,18 @@ import ( "io/ioutil" "github.com/golang/freetype/truetype" - "github.com/hajimehoshi/ebiten/ebitenutil" + "github.com/silbinarywolf/gml-go/gml/internal/file" "golang.org/x/image/font" ) var g_fontManager = newFontManager() +const ( + fontDirectoryBase = "font" +) + +// todo(Jake): 2018-12-02 - #26 +// Stop exposing FontManager struct type FontManager struct { currentFont *Font assetMap map[string]*Font @@ -36,8 +42,8 @@ func LoadFont(name string, settings FontSettings) *Font { return result } - path := AssetsDirectory() + "/fonts/" + name + ".ttf" - fileData, err := ebitenutil.OpenFile(path) + path := AssetDirectory() + "/" + fontDirectoryBase + "/" + name + ".ttf" + fileData, err := file.OpenFile(path) if err != nil { panic(errors.New("Unable to find font: " + path + ". Error: " + err.Error())) } @@ -46,6 +52,7 @@ func LoadFont(name string, settings FontSettings) *Font { if err != nil { panic(errors.New("Unable to read font file into bytes: " + path)) } + //fmt.Printf("%v\n", b) tt, err := truetype.Parse(b) if err != nil { panic(errors.New("Unable to parse true type font file: " + path + ", err: " + err.Error())) diff --git a/gml/game.go b/gml/game.go deleted file mode 100644 index 6859079..0000000 --- a/gml/game.go +++ /dev/null @@ -1,11 +0,0 @@ -package gml - -type gameState struct { - hasGameRestarted bool -} - -var g_game gameState - -func GameRestart() { - g_game.hasGameRestarted = true -} diff --git a/gml/geom.go b/gml/geom.go new file mode 100644 index 0000000..d6135f2 --- /dev/null +++ b/gml/geom.go @@ -0,0 +1,9 @@ +package gml + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" +) + +type Vec = geom.Vec + +type Size = geom.Size diff --git a/gml/gopherjs_test.go b/gml/gopherjs_test.go new file mode 100644 index 0000000..8504f17 --- /dev/null +++ b/gml/gopherjs_test.go @@ -0,0 +1,18 @@ +// +build js + +package gml + +import ( + "fmt" + "testing" + + "github.com/gopherjs/gopherjs/js" +) + +// NOTE(Jake): 2018-09-09 +// Can't find the documentation but this was needed for GopherJS +func TestMain(m *testing.M) { + i := m.Run() + + js.Global.Call("eval", fmt.Sprintf("window.$GopherJSTestResult = %v", i)) +} diff --git a/gml/instance.go b/gml/instance.go index c26d82a..ad8b20f 100644 --- a/gml/instance.go +++ b/gml/instance.go @@ -1,18 +1,12 @@ package gml -import "github.com/silbinarywolf/gml-go/gml/internal/object" - -type instanceManagerResettableData struct { - instances []object.ObjectType - spaces object.SpaceBucketArray -} - -func (manager *instanceManager) reset() { - manager.instanceManagerResettableData = instanceManagerResettableData{} -} +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/object" +) type instanceManager struct { - instanceManagerResettableData + instances []object.ObjectType } func newInstanceManager() *instanceManager { @@ -21,56 +15,152 @@ func newInstanceManager() *instanceManager { return manager } -func (manager *instanceManager) InstanceCreate(position Vec, objectIndex object.ObjectIndex, roomInstanceIndex int) object.ObjectType { - // Create and add to entity list - index := len(manager.instances) +func (manager *instanceManager) reset() { + *manager = instanceManager{} +} - // - var inst object.ObjectType - { - spaceIndex := manager.spaces.GetNew() - space := manager.spaces.Get(spaceIndex) - inst = object.NewRawInstance(objectIndex, index, roomInstanceIndex, space, spaceIndex) - manager.instances = append(manager.instances, inst) +func instanceCreateLayer(position geom.Vec, layer *roomInstanceLayerInstance, roomInst *RoomInstance, objectIndex object.ObjectIndex) object.ObjectType { + return layer.manager.InstanceCreate(position, objectIndex, roomInst.Index(), layer.index) +} + +func InstanceGet(index object.ObjectIndex) object.ObjectType { + panic("todo: Implement InstanceGet()") + return nil +} + +func InstanceChangeRoom(inst object.ObjectType, roomInstanceIndex int) { + roomInst := &gState.roomInstances[roomInstanceIndex] + if !roomInst.used { + return } + // NOTE(Jake): 2018-07-22 + // For now instances default to the last instance layer + layerIndex := len(roomInst.instanceLayers) - 1 + layer := &roomInst.instanceLayers[layerIndex] + + instanceRemove(inst) + layer.manager.instanceAdd(inst, roomInst.Index(), layer.index) +} + +func InstanceCreateRoom(position geom.Vec, roomInstanceIndex int, objectIndex object.ObjectIndex) object.ObjectType { + roomInst := &gState.roomInstances[roomInstanceIndex] + // NOTE(Jake): 2018-07-22 + // For now instances default to the last instance layer + layerIndex := len(roomInst.instanceLayers) - 1 + layer := &roomInst.instanceLayers[layerIndex] + //fmt.Printf("InstanceCreateRoom: Create on layer %d\n", layerIndex) + return layer.manager.InstanceCreate(position, objectIndex, roomInst.Index(), layer.index) +} - // Attach +func InstanceExists(inst object.ObjectType) bool { baseObj := inst.BaseObject() + if baseObj == nil { + return false + } + roomInst := roomGetInstance(baseObj.RoomInstanceIndex()) + // todo(Jake): 2018-08-20 + // + // Check to see if current entity is destroyed + // + return roomInst != nil +} + +func (manager *instanceManager) instanceAdd(inst object.ObjectType, roomInstanceIndex, layerIndex int) { + // Move entity to new list + index := len(manager.instances) + object.MoveInstance(inst, index, roomInstanceIndex, layerIndex) + manager.instances = append(manager.instances, inst) +} + +func (manager *instanceManager) InstanceCreate(position geom.Vec, objectIndex object.ObjectIndex, roomInstanceIndex, layerIndex int) object.ObjectType { + // Create and add to entity list + index := len(manager.instances) + + // Get instance + inst := object.NewRawInstance(objectIndex, index, roomInstanceIndex, layerIndex) + manager.instances = append(manager.instances, inst) // Init and Set position inst.Create() - baseObj.Vec = position + inst.BaseObject().Vec = position return inst } -func (manager *instanceManager) InstanceDestroy(inst object.ObjectType) { - be := inst.BaseObject() +func instanceRemove(inst object.ObjectType) { + baseObj := inst.BaseObject() + + // Get slots + roomInstanceIndex := baseObj.RoomInstanceIndex() + layerIndex := object.LayerInstanceIndex(baseObj) + index := object.InstanceIndex(baseObj) - // Free up space slot - be.Space = nil - if be.SpaceIndex() > -1 { - manager.spaces.Remove(be.SpaceIndex()) + // Get manager + roomInst := &gState.roomInstances[roomInstanceIndex] + layerInst := &roomInst.instanceLayers[layerIndex] + manager := &layerInst.manager + + if manager.instances[index] != inst { + panic("instanceRemove failed as instance provided has already been removed") + } + // Get index + /*index := -1 + for i, otherInst := range manager.instances { + if inst == otherInst { + index = i + } } + if index == -1 { + panic("instanceRemove failed as instance provided has already been removed") + }*/ // Unordered delete - i := be.Index() + // NOTE(Jake): 2018-09-15 + // Im aware this sometimes causes the server to crash... + // but I also don't want to fix this yet as I might store each type of an + // entity in its own bucket array soon... + // + // At the very least I should maybe make this a "mark as deleted" + // system where it cleans up the entity list at the end of the frame. + // lastEntry := manager.instances[len(manager.instances)-1] - manager.instances[i] = lastEntry + manager.instances[index] = lastEntry + object.SetInstanceIndex(lastEntry.BaseObject(), index) manager.instances = manager.instances[:len(manager.instances)-1] } +func InstanceDestroy(inst object.ObjectType) { + baseObj := inst.BaseObject() + if object.IsDestroyed(baseObj) { + // NOTE(Jake): 2018-10-07 + // Maybe making this just silently returning will be better / less error + // prone? For now lets be strict. + panic("Cannot call InstanceDestroy on an object more than once.") + return + } + + // Run user-destroy code + inst.Destroy() + + // Mark as destroyed + object.MarkAsDestroyed(baseObj) + + // NOTE(Jake): 2018-10-07 + // Remove at the end of the frame (gState.update) + gState.instancesMarkedForDelete = append(gState.instancesMarkedForDelete, inst) +} + func (manager *instanceManager) update(animationUpdate bool) { { instances := manager.instances for _, inst := range instances { + if inst == nil { + continue + } inst.Update() } if animationUpdate { for _, inst := range instances { - if inst == nil { - continue - } baseObj := inst.BaseObject() baseObj.SpriteState.ImageUpdate() } @@ -79,19 +169,10 @@ func (manager *instanceManager) update(animationUpdate bool) { } func (manager *instanceManager) draw() { - for i := 0; i < len(cameraList); i++ { - cam := &cameraList[i] - if !cam.enabled { + for _, inst := range manager.instances { + if inst == nil { continue } - cam.update() - cameraSetActive(i) - for _, inst := range manager.instances { - if inst == nil { - continue - } - inst.Draw() - } + inst.Draw() } - cameraClearActive() } diff --git a/gml/instance_iterator_object.go b/gml/instance_iterator_object.go new file mode 100644 index 0000000..4d42b67 --- /dev/null +++ b/gml/instance_iterator_object.go @@ -0,0 +1,58 @@ +package gml + +import ( + "reflect" + + "github.com/silbinarywolf/gml-go/gml/internal/object" +) + +type instanceIteratorObjectState struct { + roomInstanceIndex int + layerIndex int + instanceIndex int +} + +// Iterate over instances that belong to the same room as provided argument +func InstancesIteratorObject(inst object.ObjectType) instanceIteratorObjectState { + if inst == nil || + reflect.ValueOf(inst).IsNil() { + return instanceIteratorObjectState{ + roomInstanceIndex: -1, + } + } + baseObj := inst.BaseObject() + roomInstanceIndex := baseObj.RoomInstanceIndex() + return instanceIteratorObjectState{ + instanceIndex: -1, + roomInstanceIndex: roomInstanceIndex, + } +} + +func (iterator *instanceIteratorObjectState) Next() bool { + if iterator.roomInstanceIndex == -1 { + return false + } + roomInst := &gState.roomInstances[iterator.roomInstanceIndex] + if iterator.layerIndex >= len(roomInst.instanceLayers) { + return false + } +loop: + iterator.instanceIndex++ + layer := &roomInst.instanceLayers[iterator.layerIndex] + for iterator.instanceIndex < len(layer.manager.instances) { + if !object.IsDestroyed(layer.manager.instances[iterator.instanceIndex].BaseObject()) { + return true + } + iterator.instanceIndex++ + } + iterator.instanceIndex = 0 + iterator.layerIndex++ + if iterator.layerIndex < len(roomInst.instanceLayers) { + goto loop + } + return false +} + +func (iterator *instanceIteratorObjectState) Value() object.ObjectType { + return gState.roomInstances[iterator.roomInstanceIndex].instanceLayers[iterator.layerIndex].manager.instances[iterator.instanceIndex] +} diff --git a/gml/instance_iterator_room.go b/gml/instance_iterator_room.go new file mode 100644 index 0000000..46b9c9e --- /dev/null +++ b/gml/instance_iterator_room.go @@ -0,0 +1,9 @@ +package gml + +// Iterate over instances in the provided room +func InstancesIteratorRoom(roomInstanceIndex int) instanceIteratorObjectState { + return instanceIteratorObjectState{ + instanceIndex: -1, + roomInstanceIndex: roomInstanceIndex, + } +} diff --git a/gml/instance_test.go b/gml/instance_test.go index d7dcba6..12506c1 100644 --- a/gml/instance_test.go +++ b/gml/instance_test.go @@ -1,12 +1,12 @@ package gml -import ( - "testing" - - "github.com/silbinarywolf/gml-go/gml/internal/object" -) - -func TestInstanceDestroyStability(t *testing.T) { +// NOTE(Jake): 2018-09-09 +// +// This test sucks and is out of date. +// the server still crashes, probably during a for-loop +// of every instance +// +/*func TestInstanceDestroyStability(t *testing.T) { roomInstance := RoomInstanceEmptyCreate() roomInstances := make([]object.ObjectType, 1024) for i := 0; i < len(roomInstances); i++ { @@ -48,3 +48,4 @@ func TestInstanceDestroyStability(t *testing.T) { } } } +*/ diff --git a/gml/internal/file/file.go b/gml/internal/file/file.go index 6f98c17..f6fd792 100644 --- a/gml/internal/file/file.go +++ b/gml/internal/file/file.go @@ -6,7 +6,16 @@ import ( ) var ( - AssetsDirectory string = "â–²not-setâ–²" + AssetDirectory string = "â–²not-setâ–²" + + // todo(Jake): 2018-11-24 + // Think of a better name? ProgramPath? + // The name should work as both a full URL (web output) and full directory path. + ProgramDirectory string = computeProgramDirectory() +) + +const ( + assetDirectoryBase = "asset" ) // ReadSeekCloser is io.ReadSeeker and io.Closer. @@ -17,13 +26,11 @@ type readSeekCloser interface { func init() { // NOTE(Jake): 2018-06-03 - // // Allow setting asset dir via environment variable for `go test` support - // - AssetsDirectory = os.Getenv("GML_ASSET_DIR") - if AssetsDirectory != "" { - AssetsDirectory = AssetsDirectory + "/assets" + AssetDirectory = os.Getenv("GML_ASSET_DIR") + if AssetDirectory != "" { + AssetDirectory = AssetDirectory + "/" + assetDirectoryBase } else { - AssetsDirectory = ProgramDirectory + "/assets" + AssetDirectory = ProgramDirectory + "/" + assetDirectoryBase } } diff --git a/gml/internal/file/file_js.go b/gml/internal/file/file_js.go index 4d38b5b..4c862a7 100644 --- a/gml/internal/file/file_js.go +++ b/gml/internal/file/file_js.go @@ -6,31 +6,27 @@ import ( "path/filepath" "strings" - "github.com/gopherjs/gopherjs/js" + "github.com/gopherjs/gopherwasm/js" "github.com/hajimehoshi/ebiten/ebitenutil" ) -var ( - ProgramDirectory string = calculateProgramDir() -) - func OpenFile(path string) (readSeekCloser, error) { return ebitenutil.OpenFile(path) } -func calculateProgramDir() string { - // Setup program dir - location := js.Global.Get("location") - result := location.Get("href").String() - result = filepath.Dir(result) - result = strings.TrimPrefix(result, "file:/") - if strings.HasPrefix(result, "http:/") { - result = strings.TrimPrefix(result, "http:/") - result = "http://" + result +// computeProgramDirectory returns the directory or url that the executable is running from +func computeProgramDirectory() string { + location := js.Global().Get("location") + url := location.Get("href").String() + url = filepath.Dir(url) + url = strings.TrimPrefix(url, "file:/") + if strings.HasPrefix(url, "http:/") { + url = strings.TrimPrefix(url, "http:/") + url = "http://" + url } - if strings.HasPrefix(result, "https:/") { - result = strings.TrimPrefix(result, "https:/") - result = "https://" + result + if strings.HasPrefix(url, "https:/") { + url = strings.TrimPrefix(url, "https:/") + url = "https://" + url } - return result + return url } diff --git a/gml/internal/file/file_nonjs.go b/gml/internal/file/file_nonjs.go index 4ef8a93..ab71732 100644 --- a/gml/internal/file/file_nonjs.go +++ b/gml/internal/file/file_nonjs.go @@ -10,15 +10,12 @@ import ( "path/filepath" ) -var ( - ProgramDirectory string = calculateProgramDir() -) - func OpenFile(path string) (readSeekCloser, error) { return os.Open(filepath.FromSlash(path)) } -func calculateProgramDir() string { +// computeProgramDirectory returns the directory or url that the executable is running from +func computeProgramDirectory() string { exePath, err := os.Executable() if err != nil { panic(err) diff --git a/gml/internal/file/user_debug.go b/gml/internal/file/user_debug.go deleted file mode 100644 index e112f8c..0000000 --- a/gml/internal/file/user_debug.go +++ /dev/null @@ -1,28 +0,0 @@ -// +build debug - -package file - -import ( - "os/user" - "path" - "strings" -) - -var ( - debugUsername string = "â–²not-setâ–²" -) - -func DebugUsernameFileSafe() string { - return debugUsername -} - -func init() { - // Setup file-safe escaped username - user, _ := user.Current() - username := user.Username - username = path.Clean(username) - username = strings.Replace(username, "/", "-", -1) - username = strings.Replace(username, "\\", "-", -1) - username = strings.Replace(username, "_", "-", -1) - debugUsername = username -} diff --git a/gml/internal/file/user_nondebug.go b/gml/internal/file/user_nondebug.go deleted file mode 100644 index 13dd67b..0000000 --- a/gml/internal/file/user_nondebug.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build !debug - -package file - -func DebugUsernameFileSafe() string { - return "" -} diff --git a/gml/internal/geom/deprecated.go b/gml/internal/geom/deprecated.go new file mode 100644 index 0000000..ab5bea0 --- /dev/null +++ b/gml/internal/geom/deprecated.go @@ -0,0 +1,37 @@ +package geom + +// +// NOTE(Jake): 2018-08-11 +// +// This code is only used by the room_editor_debug.go. +// Need to look at removing it in favour of making rect.go nicer. +// + +type RoomEditorDebugRect struct { + LeftTop Vec + RightBottom Vec +} + +func (rect *RoomEditorDebugRect) Left() float64 { return rect.LeftTop.X } +func (rect *RoomEditorDebugRect) Right() float64 { return rect.RightBottom.X } +func (rect *RoomEditorDebugRect) Top() float64 { return rect.LeftTop.Y } +func (rect *RoomEditorDebugRect) Bottom() float64 { return rect.RightBottom.Y } + +func R(a Vec, b Vec) RoomEditorDebugRect { + rect := RoomEditorDebugRect{} + if a.X < b.X { + rect.LeftTop.X = a.X + rect.RightBottom.X = b.X + } else { + rect.LeftTop.X = b.X + rect.RightBottom.X = a.X + } + if a.Y < b.Y { + rect.LeftTop.Y = a.Y + rect.RightBottom.Y = b.Y + } else { + rect.LeftTop.Y = b.Y + rect.RightBottom.Y = a.Y + } + return rect +} diff --git a/gml/internal/geom/rect.go b/gml/internal/geom/rect.go new file mode 100644 index 0000000..3335d0e --- /dev/null +++ b/gml/internal/geom/rect.go @@ -0,0 +1,75 @@ +package geom + +type Rect struct { + Vec // Position (contains X,Y) + Size Size // Size (X,Y) +} + +func (rect *Rect) Pos() Vec { + return rect.Vec +} + +func (rect *Rect) Left() float64 { return rect.Vec.X } +func (rect *Rect) Right() float64 { return rect.Vec.X + float64(rect.Size.X) } +func (rect *Rect) Top() float64 { return rect.Vec.Y } +func (rect *Rect) Bottom() float64 { return rect.Vec.Y + float64(rect.Size.Y) } + +func (rect *Rect) DistancePoint(point Vec) float64 { + yDist := 0.0 + if point.Y < rect.Top() { + yDist = rect.Top() - point.Y + } else if point.Y > rect.Bottom() { + yDist = point.Y - rect.Bottom() + } + xDist := 0.0 + if point.X < rect.Left() { + xDist = rect.Left() - point.X + } else if point.X > rect.Right() { + xDist = point.X - rect.Right() + } + return Vec{xDist, yDist}.Norm() +} + +// https://stackoverflow.com/questions/4978323/how-to-calculate-distance-between-two-rectangles-context-a-game-in-lua +func (rect *Rect) DistanceRect(otherRect Rect) float64 { + left := otherRect.Right() < rect.Left() + right := rect.Right() < otherRect.Left() + bottom := otherRect.Bottom() < rect.Top() + top := rect.Bottom() < otherRect.Top() + if top && left { + // dist((x1, y1b), (x2b, y2)) + return Vec{rect.Left(), rect.Bottom()}.DistancePoint(Vec{otherRect.Right(), otherRect.Top()}) + } else if left && bottom { + // dist((x1, y1), (x2b, y2b)) + return Vec{rect.Left(), rect.Top()}.DistancePoint(Vec{otherRect.Right(), otherRect.Bottom()}) + } else if bottom && right { + // dist((x1b, y1), (x2, y2b)) + return Vec{rect.Right(), rect.Top()}.DistancePoint(Vec{otherRect.Left(), otherRect.Bottom()}) + } else if right && top { + // dist((x1b, y1b), (x2, y2)) + return Vec{rect.Right(), rect.Bottom()}.DistancePoint(Vec{otherRect.Left(), otherRect.Bottom()}) + } else if left { + // x1 - x2b + return rect.Left() - otherRect.Right() + } else if right { + // x2 - x1b + return otherRect.Left() - rect.Right() + } else if bottom { + // y1 - y2b + return rect.Top() - otherRect.Bottom() + } else if top { + // y2 - y1b + return otherRect.Top() - rect.Bottom() + } + return 0 +} + +func (rect *Rect) CollisionPoint(pos Vec) bool { + return pos.X > rect.Left() && pos.X < rect.Right() && + pos.Y > rect.Top() && pos.Y < rect.Bottom() +} + +func (r1 Rect) CollisionRectangle(r2 Rect) bool { + return r1.Right() > r2.Left() && r1.Bottom() > r2.Top() && + r1.Left() < r2.Right() && r1.Top() < r2.Bottom() +} diff --git a/gml/internal/geom/size.go b/gml/internal/geom/size.go new file mode 100644 index 0000000..e25b95d --- /dev/null +++ b/gml/internal/geom/size.go @@ -0,0 +1,17 @@ +package geom + +type Size struct { + // NOTE(Jake): 2018-07-08 + // + // When profiling the collision.go tests, I tried adjusting these types to: + // - uint32, uint16, int16, int, float32 + // + // When benchmarking with "go test --bench=." and "gjbt --bench=.", it seemed + // that int32 gave the best performance + // + X, Y int32 +} + +func (s *Size) Vec() Vec { + return Vec{float64(s.X), float64(s.Y)} +} diff --git a/gml/internal/geom/vec.go b/gml/internal/geom/vec.go new file mode 100644 index 0000000..fff2ec8 --- /dev/null +++ b/gml/internal/geom/vec.go @@ -0,0 +1,16 @@ +package geom + +import "math" + +type Vec struct { + X, Y float64 +} + +func (v Vec) Sub(ov Vec) Vec { return Vec{v.X - ov.X, v.Y - ov.Y} } + +// Norm returns the vector's norm. +func (v Vec) Norm() float64 { return math.Sqrt(v.Dot(v)) } + +func (v Vec) Dot(ov Vec) float64 { return v.X*ov.X + v.Y*ov.Y } + +func (v Vec) DistancePoint(ov Vec) float64 { return v.Sub(ov).Norm() } diff --git a/gml/internal/math/math.go b/gml/internal/math/math.go deleted file mode 100644 index 4c425ce..0000000 --- a/gml/internal/math/math.go +++ /dev/null @@ -1,50 +0,0 @@ -package math - -type Vec struct { - X, Y float64 -} - -func V(x float64, y float64) Vec { - return Vec{X: x, Y: y} -} - -type Size struct { - // NOTE(Jake): 2018-07-08 - // - // When profiling the collision.go tests, I tried adjusting these types to: - // - uint32, uint16, int16, int, float32 - // - // When benchmarking with "go test --bench=." and "gjbt --bench=.", it seemed - // that int32 gave the best performance - // - X, Y int32 -} - -type Rect struct { - LeftTop Vec - RightBottom Vec -} - -func (rect *Rect) Left() float64 { return rect.LeftTop.X } -func (rect *Rect) Right() float64 { return rect.RightBottom.X } -func (rect *Rect) Top() float64 { return rect.LeftTop.Y } -func (rect *Rect) Bottom() float64 { return rect.RightBottom.Y } - -func R(a Vec, b Vec) Rect { - rect := Rect{} - if a.X < b.X { - rect.LeftTop.X = a.X - rect.RightBottom.X = b.X - } else { - rect.LeftTop.X = b.X - rect.RightBottom.X = a.X - } - if a.Y < b.Y { - rect.LeftTop.Y = a.Y - rect.RightBottom.Y = b.Y - } else { - rect.LeftTop.Y = b.Y - rect.RightBottom.Y = a.Y - } - return rect -} diff --git a/gml/internal/object/instance.go b/gml/internal/object/instance.go new file mode 100644 index 0000000..7a5f11f --- /dev/null +++ b/gml/internal/object/instance.go @@ -0,0 +1,32 @@ +package object + +type instanceObject struct { + isDestroyed bool + index int // index in the 'entities' array + roomInstanceIndex int // Room Instance Index belongs to + layerInstanceIndex int // Layer belongs to +} + +func (inst *Object) RoomInstanceIndex() int { + return inst.roomInstanceIndex +} + +func IsDestroyed(inst *Object) bool { + return inst.isDestroyed +} + +func MarkAsDestroyed(inst *Object) { + inst.isDestroyed = true +} + +func SetInstanceIndex(inst *Object, index int) { + inst.index = index +} + +func InstanceIndex(inst *Object) int { + return inst.index +} + +func LayerInstanceIndex(inst *Object) int { + return inst.layerInstanceIndex +} diff --git a/gml/internal/object/object.go b/gml/internal/object/object.go index d5eae1a..1e2a9a0 100644 --- a/gml/internal/object/object.go +++ b/gml/internal/object/object.go @@ -3,7 +3,7 @@ package object import ( "math" - m "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) @@ -14,15 +14,17 @@ type ObjectType interface { ObjectIndex() ObjectIndex ObjectName() string Create() + Destroy() Update() Draw() } type Object struct { sprite.SpriteState // Sprite (contains SetSprite) - SpaceObject - index int // index in the 'entities' array - roomInstanceIndex int // index of the room in the 'room' array + geom.Rect + instanceObject + objectIndex ObjectIndex + solid bool imageAngleRadians float64 // Image Angle } @@ -31,25 +33,29 @@ func (inst *Object) create() { inst.ImageScale.Y = 1.0 } +func (inst *Object) SetSolid(isSolid bool) { + inst.solid = isSolid +} + +func (inst *Object) Solid() bool { return inst.solid } func (inst *Object) BaseObject() *Object { return inst } -func (inst *Object) Index() int { return inst.index } -func (inst *Object) RoomInstanceIndex() int { return inst.roomInstanceIndex } -func (inst *Object) Pos() m.Vec { return inst.Vec } +func (inst *Object) ObjectIndex() ObjectIndex { return inst.objectIndex } func (inst *Object) ImageAngleRadians() float64 { return inst.imageAngleRadians } func (inst *Object) ImageAngle() float64 { return inst.imageAngleRadians * (180 / math.Pi) } -//func (inst *Object) ImageScale() m.Vec { return inst.imageScale } +//func (inst *Object) ImageScale() geom.Vec { return inst.imageScale } -func (inst *Object) SetSprite(sprite *sprite.Sprite) { - inst.SpriteState.SetSprite(sprite) +func (inst *Object) SetSprite(spriteIndex sprite.SpriteIndex) { + inst.SpriteState.SetSprite(spriteIndex) // Infer width and height if they aren't manually set // (This might be a bad idea, too magic! But feels like Game Maker, so...) + size := spriteIndex.Size() if inst.Size.X == 0 { - inst.Size.X = sprite.Size().X + inst.Size.X = size.X } if inst.Size.Y == 0 { - inst.Size.Y = sprite.Size().Y + inst.Size.Y = size.Y } } @@ -60,3 +66,7 @@ func (inst *Object) SetImageAngle(angleInDegrees float64) { func (inst *Object) SetImageAngleRadians(angleInRadians float64) { inst.imageAngleRadians = angleInRadians } + +func (inst *Object) CollisionInstance(otherInst ObjectType) bool { + return inst.Rect.CollisionRectangle(otherInst.BaseObject().Rect) +} diff --git a/gml/internal/object/object_manager.go b/gml/internal/object/object_manager.go index ad7ab4e..f396a9a 100644 --- a/gml/internal/object/object_manager.go +++ b/gml/internal/object/object_manager.go @@ -1,14 +1,18 @@ package object -import "reflect" +import ( + "fmt" + "reflect" +) var ( gObjectManager *objectManager = newObjectManager() ) type objectManager struct { - idToEntityData []ObjectType - nameToID map[string]ObjectIndex + idToEntityData []ObjectType + objectIndexList []ObjectIndex + nameToID map[string]ObjectIndex } func newObjectManager() *objectManager { @@ -18,11 +22,12 @@ func newObjectManager() *objectManager { } } -func InitTypes(objTypes []ObjectType) { +func InitTypes(objTypes []ObjectType, objectIndexList []ObjectIndex) { manager := gObjectManager if manager.idToEntityData != nil { panic("Cannot call init type function more than once.") } + manager.objectIndexList = objectIndexList manager.idToEntityData = objTypes for _, objType := range objTypes { if objType == nil { @@ -30,10 +35,19 @@ func InitTypes(objTypes []ObjectType) { } name := objType.ObjectName() objectIndex := objType.ObjectIndex() + otherID, used := manager.nameToID[name] + if used { + otherType := manager.idToEntityData[otherID] + panic(fmt.Sprintf("You cannot have two objects with the same object name.\n- %T::ObjectName() == %s\n- %T::ObjectName() == %s", objType, objType.ObjectName(), otherType, otherType.ObjectName())) + } manager.nameToID[name] = objectIndex } } +func ObjectIndexList() []ObjectIndex { + return gObjectManager.objectIndexList +} + func IDToEntityData() []ObjectType { return gObjectManager.idToEntityData } @@ -45,8 +59,26 @@ func NameToID() map[string]ObjectIndex { return gObjectManager.nameToID } -func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, space *Space, spaceIndex int) ObjectType { - // Create +func MoveInstance(inst ObjectType, index int, roomInstanceIndex int, layerIndex int) { + // Initialize object + baseObj := inst.BaseObject() + baseObj.index = index + baseObj.roomInstanceIndex = roomInstanceIndex + baseObj.layerInstanceIndex = layerIndex +} + +func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, layerIndex int) ObjectType { + valToCopy := gObjectManager.idToEntityData[objectIndex] + if valToCopy == nil { + panic("Invalid objectIndex given") + } + inst := reflect.New(reflect.ValueOf(valToCopy).Elem().Type()).Interface().(ObjectType) + MoveInstance(inst, index, roomInstanceIndex, layerIndex) + baseObj := inst.BaseObject() + baseObj.objectIndex = objectIndex + baseObj.create() + return inst + /*// Create valToCopy := gObjectManager.idToEntityData[objectIndex] inst := reflect.New(reflect.ValueOf(valToCopy).Elem().Type()).Interface().(ObjectType) @@ -54,6 +86,7 @@ func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, s baseObj := inst.BaseObject() baseObj.index = index baseObj.roomInstanceIndex = roomInstanceIndex + baseObj.layerInstanceIndex = layerIndex // todo(Jake): 2018-07-08 // // Figure out a cleaner way to handle this functionality across @@ -61,11 +94,8 @@ func NewRawInstance(objectIndex ObjectIndex, index int, roomInstanceIndex int, s // // Perhaps force objects to have to be created via an instance manager. // - baseObj.Space = space - baseObj.spaceIndex = spaceIndex - baseObj.create() - - return inst + baseObj.SpaceObject.Init(space, spaceIndex) + baseObj.create()*/ } func ObjectGetIndex(name string) (ObjectIndex, bool) { diff --git a/gml/internal/object/space.go b/gml/internal/object/space.go deleted file mode 100644 index fbabbed..0000000 --- a/gml/internal/object/space.go +++ /dev/null @@ -1,124 +0,0 @@ -package object - -import ( - m "github.com/silbinarywolf/gml-go/gml/internal/math" -) - -const ( - spaceBucketSize = 256 -) - -type SpaceObject struct { - *Space - spaceIndex int -} - -func (space *SpaceObject) SpaceIndex() int { - return space.spaceIndex -} - -type Space struct { - m.Vec // Position (contains X,Y) - Size m.Size // Size (X,Y) -} - -type SpaceBucketArray struct { - length int - buckets []*SpaceBucket -} - -type SpaceBucket struct { - usedCount int - spaces [spaceBucketSize]Space - used [spaceBucketSize]bool -} - -func NewSpaceBucketArray() *SpaceBucketArray { - result := new(SpaceBucketArray) - return result -} - -func (array *SpaceBucketArray) GetNew() int { - for b, bucket := range array.buckets { - index := bucket.getNew() - if index == -1 { - continue - } - array.length++ - return index + (b * spaceBucketSize) - } - // Create new bucket, all other buckets are full! - bucket := new(SpaceBucket) - b := len(array.buckets) - array.buckets = append(array.buckets, bucket) - array.length++ - return bucket.getNew() + (b * spaceBucketSize) -} - -func (array *SpaceBucketArray) Get(index int) *Space { - bucket := array.buckets[index/spaceBucketSize] - return &bucket.spaces[index%spaceBucketSize] -} - -func (array *SpaceBucketArray) Remove(index int) { - bucket := array.buckets[index/spaceBucketSize] - bucketIndex := index % spaceBucketSize - if !bucket.used[bucketIndex] { - panic("Invalid operation. Cannot remove unused Space{} object.") - } - bucket.remove(bucketIndex) - array.length-- -} - -func (array *SpaceBucketArray) Buckets() []*SpaceBucket { - return array.buckets -} - -// NOTE(Jake): 2018-07-08 -// -// Experimented with returning this as a second value in `Get`, however -// the thing that yielded the best performance on both JS and native outputs -// was checking "IsUsed()" seperately. -// -// In fact, checking "IsUsed" seems to be an almost-free operation when added -// to the end of the if-statement checking collisions -// -func (array *SpaceBucketArray) IsUsed(index int) bool { - bucket := array.buckets[index/spaceBucketSize] - return bucket.used[index%spaceBucketSize] -} - -func (array *SpaceBucketArray) Len() int { - return array.length -} - -func (bucket *SpaceBucket) Get(index int) *Space { - return &bucket.spaces[index] -} - -func (bucket *SpaceBucket) IsUsed(index int) bool { - return bucket.used[index] -} - -func (_ *SpaceBucket) Len() int { - return spaceBucketSize -} - -func (bucket *SpaceBucket) remove(index int) { - bucket.used[index] = false - bucket.usedCount-- -} - -func (bucket *SpaceBucket) getNew() int { - if bucket.usedCount == spaceBucketSize { - return -1 - } - for i, _ := range bucket.used { - if !bucket.used[i] { - bucket.used[i] = true - bucket.usedCount++ - return i - } - } - return -1 -} diff --git a/gml/internal/reditor/reditor.go b/gml/internal/reditor/reditor.go new file mode 100644 index 0000000..9a893dc --- /dev/null +++ b/gml/internal/reditor/reditor.go @@ -0,0 +1,14 @@ +package reditor + +type Menu int + +const ( + MenuNone Menu = 0 + iota + MenuEntity + MenuSprite + MenuBackground + MenuNewRoom + MenuLoadRoom + MenuNewLayer + MenuSetOrder +) diff --git a/gml/internal/reditor/reditor_debug.go b/gml/internal/reditor/reditor_debug.go new file mode 100644 index 0000000..f02f477 --- /dev/null +++ b/gml/internal/reditor/reditor_debug.go @@ -0,0 +1,12 @@ +// +build debug + +package reditor + +import ( + "github.com/rs/xid" +) + +func UUID() string { + guid := xid.New() + return guid.String() +} diff --git a/gml/internal/reditor/reditor_nondebug.go b/gml/internal/reditor/reditor_nondebug.go new file mode 100644 index 0000000..0880ac3 --- /dev/null +++ b/gml/internal/reditor/reditor_nondebug.go @@ -0,0 +1,7 @@ +// +build !debug + +package reditor + +func UUID() string { + panic("Not implemented for non-debug builds.") +} diff --git a/gml/internal/room/room.go b/gml/internal/room/room.go new file mode 100644 index 0000000..203deb1 --- /dev/null +++ b/gml/internal/room/room.go @@ -0,0 +1,13 @@ +package room + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/file" +) + +func (room *Room) Filepath() string { + return file.AssetDirectory + "/" + RoomDirectoryBase + "/" + room.Config.UUID +} + +//func (room *Room) LayerCount() int { +// return len(room.InstanceLayers) +//} diff --git a/gml/internal/room/room.pb.go b/gml/internal/room/room.pb.go index 9e4f37d..4b5b3de 100644 --- a/gml/internal/room/room.pb.go +++ b/gml/internal/room/room.pb.go @@ -2,15 +2,28 @@ // source: room.proto /* - Package gml is a generated protocol buffer package. + Package room is a generated protocol buffer package. It is generated from these files: room.proto + room_config.proto + room_layer_background.proto + room_layer_config.proto + room_layer_instance.proto + room_layer_kind.proto + room_layer_sprite.proto room_object.proto + room_sprite_object.proto It has these top-level messages: Room + RoomConfig + RoomLayerBackground + RoomLayerConfig + RoomLayerInstance + RoomLayerSprite RoomObject + RoomSpriteObject */ package room @@ -32,15 +45,16 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type Room struct { - Filepath string `protobuf:"bytes,1,opt,name=Filepath,proto3" json:"Filepath,omitempty"` - Left int32 `protobuf:"varint,2,opt,name=Left,proto3" json:"Left,omitempty"` - Right int32 `protobuf:"varint,3,opt,name=Right,proto3" json:"Right,omitempty"` - Top int32 `protobuf:"varint,4,opt,name=Top,proto3" json:"Top,omitempty"` - Bottom int32 `protobuf:"varint,5,opt,name=Bottom,proto3" json:"Bottom,omitempty"` - Instances []*RoomObject `protobuf:"bytes,6,rep,name=Instances" json:"Instances,omitempty"` - // Room Editor - UserEntityCount int64 `protobuf:"varint,7,opt,name=UserEntityCount,proto3" json:"UserEntityCount,omitempty"` - DeletedInstances []*RoomObject `protobuf:"bytes,8,rep,name=DeletedInstances" json:"DeletedInstances,omitempty"` + Config *RoomConfig `protobuf:"bytes,1,opt,name=Config" json:"Config,omitempty"` + Left int32 `protobuf:"varint,2,opt,name=Left,proto3" json:"Left,omitempty"` + Right int32 `protobuf:"varint,3,opt,name=Right,proto3" json:"Right,omitempty"` + Top int32 `protobuf:"varint,4,opt,name=Top,proto3" json:"Top,omitempty"` + Bottom int32 `protobuf:"varint,5,opt,name=Bottom,proto3" json:"Bottom,omitempty"` + // Layers + InstanceLayers []*RoomLayerInstance `protobuf:"bytes,6,rep,name=InstanceLayers" json:"InstanceLayers,omitempty"` + BackgroundLayers []*RoomLayerBackground `protobuf:"bytes,7,rep,name=BackgroundLayers" json:"BackgroundLayers,omitempty"` + SpriteLayers []*RoomLayerSprite `protobuf:"bytes,8,rep,name=SpriteLayers" json:"SpriteLayers,omitempty"` + DeletedLayers []string `protobuf:"bytes,9,rep,name=DeletedLayers" json:"DeletedLayers,omitempty"` } func (m *Room) Reset() { *m = Room{} } @@ -48,11 +62,11 @@ func (m *Room) String() string { return proto.CompactTextString(m) } func (*Room) ProtoMessage() {} func (*Room) Descriptor() ([]byte, []int) { return fileDescriptorRoom, []int{0} } -func (m *Room) GetFilepath() string { +func (m *Room) GetConfig() *RoomConfig { if m != nil { - return m.Filepath + return m.Config } - return "" + return nil } func (m *Room) GetLeft() int32 { @@ -83,29 +97,36 @@ func (m *Room) GetBottom() int32 { return 0 } -func (m *Room) GetInstances() []*RoomObject { +func (m *Room) GetInstanceLayers() []*RoomLayerInstance { if m != nil { - return m.Instances + return m.InstanceLayers } return nil } -func (m *Room) GetUserEntityCount() int64 { +func (m *Room) GetBackgroundLayers() []*RoomLayerBackground { if m != nil { - return m.UserEntityCount + return m.BackgroundLayers } - return 0 + return nil } -func (m *Room) GetDeletedInstances() []*RoomObject { +func (m *Room) GetSpriteLayers() []*RoomLayerSprite { if m != nil { - return m.DeletedInstances + return m.SpriteLayers + } + return nil +} + +func (m *Room) GetDeletedLayers() []string { + if m != nil { + return m.DeletedLayers } return nil } func init() { - proto.RegisterType((*Room)(nil), "gml.Room") + proto.RegisterType((*Room)(nil), "room.Room") } func (m *Room) Marshal() (dAtA []byte, err error) { size := m.Size() @@ -122,11 +143,15 @@ func (m *Room) MarshalTo(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Filepath) > 0 { + if m.Config != nil { dAtA[i] = 0xa i++ - i = encodeVarintRoom(dAtA, i, uint64(len(m.Filepath))) - i += copy(dAtA[i:], m.Filepath) + i = encodeVarintRoom(dAtA, i, uint64(m.Config.Size())) + n1, err := m.Config.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 } if m.Left != 0 { dAtA[i] = 0x10 @@ -148,8 +173,8 @@ func (m *Room) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintRoom(dAtA, i, uint64(m.Bottom)) } - if len(m.Instances) > 0 { - for _, msg := range m.Instances { + if len(m.InstanceLayers) > 0 { + for _, msg := range m.InstanceLayers { dAtA[i] = 0x32 i++ i = encodeVarintRoom(dAtA, i, uint64(msg.Size())) @@ -160,13 +185,20 @@ func (m *Room) MarshalTo(dAtA []byte) (int, error) { i += n } } - if m.UserEntityCount != 0 { - dAtA[i] = 0x38 - i++ - i = encodeVarintRoom(dAtA, i, uint64(m.UserEntityCount)) + if len(m.BackgroundLayers) > 0 { + for _, msg := range m.BackgroundLayers { + dAtA[i] = 0x3a + i++ + i = encodeVarintRoom(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } } - if len(m.DeletedInstances) > 0 { - for _, msg := range m.DeletedInstances { + if len(m.SpriteLayers) > 0 { + for _, msg := range m.SpriteLayers { dAtA[i] = 0x42 i++ i = encodeVarintRoom(dAtA, i, uint64(msg.Size())) @@ -177,6 +209,21 @@ func (m *Room) MarshalTo(dAtA []byte) (int, error) { i += n } } + if len(m.DeletedLayers) > 0 { + for _, s := range m.DeletedLayers { + dAtA[i] = 0x4a + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } return i, nil } @@ -192,8 +239,8 @@ func encodeVarintRoom(dAtA []byte, offset int, v uint64) int { func (m *Room) Size() (n int) { var l int _ = l - l = len(m.Filepath) - if l > 0 { + if m.Config != nil { + l = m.Config.Size() n += 1 + l + sovRoom(uint64(l)) } if m.Left != 0 { @@ -208,21 +255,30 @@ func (m *Room) Size() (n int) { if m.Bottom != 0 { n += 1 + sovRoom(uint64(m.Bottom)) } - if len(m.Instances) > 0 { - for _, e := range m.Instances { + if len(m.InstanceLayers) > 0 { + for _, e := range m.InstanceLayers { l = e.Size() n += 1 + l + sovRoom(uint64(l)) } } - if m.UserEntityCount != 0 { - n += 1 + sovRoom(uint64(m.UserEntityCount)) + if len(m.BackgroundLayers) > 0 { + for _, e := range m.BackgroundLayers { + l = e.Size() + n += 1 + l + sovRoom(uint64(l)) + } } - if len(m.DeletedInstances) > 0 { - for _, e := range m.DeletedInstances { + if len(m.SpriteLayers) > 0 { + for _, e := range m.SpriteLayers { l = e.Size() n += 1 + l + sovRoom(uint64(l)) } } + if len(m.DeletedLayers) > 0 { + for _, s := range m.DeletedLayers { + l = len(s) + n += 1 + l + sovRoom(uint64(l)) + } + } return n } @@ -270,9 +326,9 @@ func (m *Room) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Filepath", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRoom @@ -282,20 +338,24 @@ func (m *Room) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthRoom } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex > l { return io.ErrUnexpectedEOF } - m.Filepath = string(dAtA[iNdEx:postIndex]) + if m.Config == nil { + m.Config = &RoomConfig{} + } + if err := m.Config.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 2: if wireType != 0 { @@ -375,7 +435,7 @@ func (m *Room) Unmarshal(dAtA []byte) error { } case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Instances", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field InstanceLayers", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -399,16 +459,16 @@ func (m *Room) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Instances = append(m.Instances, &RoomObject{}) - if err := m.Instances[len(m.Instances)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.InstanceLayers = append(m.InstanceLayers, &RoomLayerInstance{}) + if err := m.InstanceLayers[len(m.InstanceLayers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 7: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field UserEntityCount", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BackgroundLayers", wireType) } - m.UserEntityCount = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRoom @@ -418,14 +478,26 @@ func (m *Room) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.UserEntityCount |= (int64(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } + if msglen < 0 { + return ErrInvalidLengthRoom + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BackgroundLayers = append(m.BackgroundLayers, &RoomLayerBackground{}) + if err := m.BackgroundLayers[len(m.BackgroundLayers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 8: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DeletedInstances", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SpriteLayers", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -449,11 +521,40 @@ func (m *Room) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DeletedInstances = append(m.DeletedInstances, &RoomObject{}) - if err := m.DeletedInstances[len(m.DeletedInstances)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.SpriteLayers = append(m.SpriteLayers, &RoomLayerSprite{}) + if err := m.SpriteLayers[len(m.SpriteLayers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeletedLayers", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoom + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoom + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DeletedLayers = append(m.DeletedLayers, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRoom(dAtA[iNdEx:]) @@ -583,21 +684,25 @@ var ( func init() { proto.RegisterFile("room.proto", fileDescriptorRoom) } var fileDescriptorRoom = []byte{ - // 249 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0xca, 0xcf, 0xcf, - 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4e, 0xcf, 0xcd, 0x91, 0x12, 0x04, 0x09, 0xc4, - 0xe7, 0x27, 0x65, 0xa5, 0x26, 0x97, 0x40, 0xc4, 0x95, 0x7a, 0x98, 0xb8, 0x58, 0x82, 0xf2, 0xf3, - 0x73, 0x85, 0xa4, 0xb8, 0x38, 0xdc, 0x32, 0x73, 0x52, 0x0b, 0x12, 0x4b, 0x32, 0x24, 0x18, 0x15, - 0x18, 0x35, 0x38, 0x83, 0xe0, 0x7c, 0x21, 0x21, 0x2e, 0x16, 0x9f, 0xd4, 0xb4, 0x12, 0x09, 0x26, - 0x05, 0x46, 0x0d, 0xd6, 0x20, 0x30, 0x5b, 0x48, 0x84, 0x8b, 0x35, 0x28, 0x33, 0x3d, 0xa3, 0x44, - 0x82, 0x19, 0x2c, 0x08, 0xe1, 0x08, 0x09, 0x70, 0x31, 0x87, 0xe4, 0x17, 0x48, 0xb0, 0x80, 0xc5, - 0x40, 0x4c, 0x21, 0x31, 0x2e, 0x36, 0xa7, 0xfc, 0x92, 0x92, 0xfc, 0x5c, 0x09, 0x56, 0xb0, 0x20, - 0x94, 0x27, 0xa4, 0xcb, 0xc5, 0xe9, 0x99, 0x57, 0x5c, 0x92, 0x98, 0x97, 0x9c, 0x5a, 0x2c, 0xc1, - 0xa6, 0xc0, 0xac, 0xc1, 0x6d, 0xc4, 0xaf, 0x97, 0x9e, 0x9b, 0xa3, 0x07, 0x72, 0x8d, 0x3f, 0xd8, - 0x89, 0x41, 0x08, 0x15, 0x42, 0x1a, 0x5c, 0xfc, 0xa1, 0xc5, 0xa9, 0x45, 0xae, 0x79, 0x25, 0x99, - 0x25, 0x95, 0xce, 0xf9, 0xa5, 0x79, 0x25, 0x12, 0xec, 0x0a, 0x8c, 0x1a, 0xcc, 0x41, 0xe8, 0xc2, - 0x42, 0xd6, 0x5c, 0x02, 0x2e, 0xa9, 0x39, 0xa9, 0x25, 0xa9, 0x29, 0x08, 0xf3, 0x39, 0xb0, 0x9b, - 0x8f, 0xa1, 0xd0, 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, - 0x63, 0x9c, 0xf1, 0x58, 0x8e, 0x21, 0x89, 0x0d, 0x1c, 0x4e, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x1e, 0xba, 0x15, 0x9d, 0x4d, 0x01, 0x00, 0x00, + // 305 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x91, 0xb1, 0x4e, 0xc3, 0x30, + 0x10, 0x86, 0x71, 0x93, 0x06, 0x7a, 0x05, 0x14, 0x2c, 0xa0, 0x6e, 0x91, 0xa2, 0x08, 0x31, 0x64, + 0xea, 0x50, 0x26, 0x26, 0xa4, 0x02, 0x03, 0x52, 0x27, 0xc3, 0x5e, 0xb5, 0xc1, 0x0d, 0x11, 0x4d, + 0x2e, 0x4a, 0xcc, 0xc0, 0x9b, 0xf0, 0x48, 0x8c, 0x3c, 0x02, 0x0a, 0x33, 0xef, 0x80, 0x72, 0x71, + 0x69, 0x1b, 0x96, 0xe8, 0xfc, 0x7f, 0xbf, 0xff, 0xbb, 0x8b, 0x01, 0x72, 0xc4, 0x64, 0x98, 0xe5, + 0xa8, 0x91, 0xdb, 0x55, 0x3d, 0x38, 0xaa, 0xbe, 0xd3, 0x10, 0xd3, 0x45, 0x1c, 0xd5, 0x60, 0xd0, + 0x27, 0x69, 0x39, 0x7b, 0x53, 0xf9, 0x34, 0x4e, 0x0b, 0x3d, 0x4b, 0x43, 0x65, 0xd0, 0xd9, 0x06, + 0x9a, 0xcf, 0xc2, 0x97, 0x28, 0xc7, 0xd7, 0xf4, 0xc9, 0xc0, 0xde, 0x06, 0x2c, 0xb2, 0x3c, 0xd6, + 0xe6, 0xd6, 0xf9, 0x4f, 0x0b, 0x6c, 0x89, 0x98, 0xf0, 0x00, 0x9c, 0x1b, 0xea, 0x24, 0x98, 0xcf, + 0x82, 0xee, 0xc8, 0x1d, 0xd2, 0x3c, 0x15, 0xab, 0x75, 0x69, 0x38, 0xe7, 0x60, 0x4f, 0xd4, 0x42, + 0x8b, 0x96, 0xcf, 0x82, 0xb6, 0xa4, 0x9a, 0x1f, 0x43, 0x5b, 0xc6, 0xd1, 0xb3, 0x16, 0x16, 0x89, + 0xf5, 0x81, 0xbb, 0x60, 0x3d, 0x62, 0x26, 0x6c, 0xd2, 0xaa, 0x92, 0x9f, 0x82, 0x33, 0x46, 0xad, + 0x31, 0x11, 0x6d, 0x12, 0xcd, 0x89, 0x5f, 0xc3, 0xe1, 0xbd, 0x59, 0x67, 0x52, 0x0d, 0x59, 0x08, + 0xc7, 0xb7, 0x82, 0xee, 0xa8, 0xb7, 0x9e, 0x82, 0xf4, 0x95, 0x49, 0x36, 0xec, 0xfc, 0x0e, 0xdc, + 0xf1, 0xdf, 0xd2, 0x26, 0x62, 0x97, 0x22, 0xfa, 0x8d, 0x88, 0xb5, 0x4d, 0xfe, 0xbb, 0xc2, 0xaf, + 0x60, 0xff, 0x81, 0x7e, 0x8f, 0x89, 0xd8, 0xa3, 0x88, 0x93, 0x46, 0x44, 0x6d, 0x91, 0x5b, 0x56, + 0x7e, 0x01, 0x07, 0xb7, 0x6a, 0xa9, 0xb4, 0x5a, 0xb5, 0xef, 0xf8, 0x56, 0xd0, 0x91, 0xdb, 0xe2, + 0xd8, 0xfd, 0x28, 0x3d, 0xf6, 0x59, 0x7a, 0xec, 0xab, 0xf4, 0xd8, 0xfb, 0xb7, 0xb7, 0x33, 0x77, + 0xe8, 0x21, 0x2e, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x15, 0x8d, 0x3d, 0x6a, 0x00, 0x02, 0x00, + 0x00, } diff --git a/gml/internal/room/room.proto b/gml/internal/room/room.proto index ea62ab9..8e5116b 100644 --- a/gml/internal/room/room.proto +++ b/gml/internal/room/room.proto @@ -1,17 +1,21 @@ syntax = "proto3"; package room; -import "room_object.proto"; +import "room_config.proto"; +import "room_layer_instance.proto"; +import "room_layer_background.proto"; +import "room_layer_sprite.proto"; message Room { - string Filepath = 1; + RoomConfig Config = 1; int32 Left = 2; int32 Right = 3; int32 Top = 4; int32 Bottom = 5; - repeated RoomObject Instances = 6; // todo(Jake): 2018-06-10, Rename 'Instances' to 'Objects' - // Room Editor - int64 UserEntityCount = 7; - repeated RoomObject DeletedObjects = 8; + // Layers + repeated RoomLayerInstance InstanceLayers = 6; + repeated RoomLayerBackground BackgroundLayers = 7; + repeated RoomLayerSprite SpriteLayers = 8; + repeated string DeletedLayers = 9; } diff --git a/gml/internal/room/room_config.pb.go b/gml/internal/room/room_config.pb.go new file mode 100644 index 0000000..2c36717 --- /dev/null +++ b/gml/internal/room/room_config.pb.go @@ -0,0 +1,334 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_config.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomConfig struct { + UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"` + Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` +} + +func (m *RoomConfig) Reset() { *m = RoomConfig{} } +func (m *RoomConfig) String() string { return proto.CompactTextString(m) } +func (*RoomConfig) ProtoMessage() {} +func (*RoomConfig) Descriptor() ([]byte, []int) { return fileDescriptorRoomConfig, []int{0} } + +func (m *RoomConfig) GetUUID() string { + if m != nil { + return m.UUID + } + return "" +} + +func (m *RoomConfig) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func init() { + proto.RegisterType((*RoomConfig)(nil), "room.RoomConfig") +} +func (m *RoomConfig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomConfig) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.UUID) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintRoomConfig(dAtA, i, uint64(len(m.UUID))) + i += copy(dAtA[i:], m.UUID) + } + if len(m.Name) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintRoomConfig(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + } + return i, nil +} + +func encodeVarintRoomConfig(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomConfig) Size() (n int) { + var l int + _ = l + l = len(m.UUID) + if l > 0 { + n += 1 + l + sovRoomConfig(uint64(l)) + } + l = len(m.Name) + if l > 0 { + n += 1 + l + sovRoomConfig(uint64(l)) + } + return n +} + +func sovRoomConfig(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomConfig(x uint64) (n int) { + return sovRoomConfig(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomConfig) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomConfig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomConfig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomConfig + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UUID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomConfig + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRoomConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomConfig + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomConfig(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomConfig + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomConfig(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomConfig = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomConfig = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_config.proto", fileDescriptorRoomConfig) } + +var fileDescriptorRoomConfig = []byte{ + // 110 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2c, 0xca, 0xcf, 0xcf, + 0x8d, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, + 0x09, 0x29, 0x99, 0x70, 0x71, 0x05, 0xe5, 0xe7, 0xe7, 0x3a, 0x83, 0x65, 0x84, 0x84, 0xb8, 0x58, + 0x42, 0x43, 0x3d, 0x5d, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x90, 0x98, 0x5f, + 0x62, 0x6e, 0xaa, 0x04, 0x13, 0x44, 0x0c, 0xc4, 0x76, 0x12, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, + 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x67, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0x1b, 0x6a, + 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x72, 0xf8, 0x5c, 0xa2, 0x69, 0x00, 0x00, 0x00, +} diff --git a/gml/internal/room/room_config.proto b/gml/internal/room/room_config.proto new file mode 100644 index 0000000..1103c7c --- /dev/null +++ b/gml/internal/room/room_config.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; +package room; + +message RoomConfig { + string UUID = 1; + string Name = 2; +} diff --git a/gml/internal/room/room_layer.go b/gml/internal/room/room_layer.go new file mode 100644 index 0000000..2319d60 --- /dev/null +++ b/gml/internal/room/room_layer.go @@ -0,0 +1,5 @@ +package room + +type RoomLayer interface { + GetConfig() *RoomLayerConfig +} diff --git a/gml/internal/room/room_layer_background.pb.go b/gml/internal/room/room_layer_background.pb.go new file mode 100644 index 0000000..174e14c --- /dev/null +++ b/gml/internal/room/room_layer_background.pb.go @@ -0,0 +1,419 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_layer_background.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomLayerBackground struct { + Config *RoomLayerConfig `protobuf:"bytes,1,opt,name=Config" json:"Config,omitempty"` + SpriteName string `protobuf:"bytes,2,opt,name=SpriteName,proto3" json:"SpriteName,omitempty"` + X int32 `protobuf:"varint,3,opt,name=X,proto3" json:"X,omitempty"` + Y int32 `protobuf:"varint,4,opt,name=Y,proto3" json:"Y,omitempty"` +} + +func (m *RoomLayerBackground) Reset() { *m = RoomLayerBackground{} } +func (m *RoomLayerBackground) String() string { return proto.CompactTextString(m) } +func (*RoomLayerBackground) ProtoMessage() {} +func (*RoomLayerBackground) Descriptor() ([]byte, []int) { + return fileDescriptorRoomLayerBackground, []int{0} +} + +func (m *RoomLayerBackground) GetConfig() *RoomLayerConfig { + if m != nil { + return m.Config + } + return nil +} + +func (m *RoomLayerBackground) GetSpriteName() string { + if m != nil { + return m.SpriteName + } + return "" +} + +func (m *RoomLayerBackground) GetX() int32 { + if m != nil { + return m.X + } + return 0 +} + +func (m *RoomLayerBackground) GetY() int32 { + if m != nil { + return m.Y + } + return 0 +} + +func init() { + proto.RegisterType((*RoomLayerBackground)(nil), "room.RoomLayerBackground") +} +func (m *RoomLayerBackground) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomLayerBackground) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Config != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintRoomLayerBackground(dAtA, i, uint64(m.Config.Size())) + n1, err := m.Config.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if len(m.SpriteName) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintRoomLayerBackground(dAtA, i, uint64(len(m.SpriteName))) + i += copy(dAtA[i:], m.SpriteName) + } + if m.X != 0 { + dAtA[i] = 0x18 + i++ + i = encodeVarintRoomLayerBackground(dAtA, i, uint64(m.X)) + } + if m.Y != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintRoomLayerBackground(dAtA, i, uint64(m.Y)) + } + return i, nil +} + +func encodeVarintRoomLayerBackground(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomLayerBackground) Size() (n int) { + var l int + _ = l + if m.Config != nil { + l = m.Config.Size() + n += 1 + l + sovRoomLayerBackground(uint64(l)) + } + l = len(m.SpriteName) + if l > 0 { + n += 1 + l + sovRoomLayerBackground(uint64(l)) + } + if m.X != 0 { + n += 1 + sovRoomLayerBackground(uint64(m.X)) + } + if m.Y != 0 { + n += 1 + sovRoomLayerBackground(uint64(m.Y)) + } + return n +} + +func sovRoomLayerBackground(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomLayerBackground(x uint64) (n int) { + return sovRoomLayerBackground(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomLayerBackground) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomLayerBackground: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomLayerBackground: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerBackground + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Config == nil { + m.Config = &RoomLayerConfig{} + } + if err := m.Config.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpriteName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomLayerBackground + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SpriteName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field X", wireType) + } + m.X = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.X |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Y", wireType) + } + m.Y = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Y |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipRoomLayerBackground(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomLayerBackground + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomLayerBackground(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomLayerBackground + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerBackground + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomLayerBackground(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomLayerBackground = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomLayerBackground = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_layer_background.proto", fileDescriptorRoomLayerBackground) } + +var fileDescriptorRoomLayerBackground = []byte{ + // 179 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2e, 0xca, 0xcf, 0xcf, + 0x8d, 0xcf, 0x49, 0xac, 0x4c, 0x2d, 0x8a, 0x4f, 0x4a, 0x4c, 0xce, 0x4e, 0x2f, 0xca, 0x2f, 0xcd, + 0x4b, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0x49, 0x4a, 0x89, 0x23, 0x29, 0x49, + 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0x87, 0x48, 0x2b, 0x35, 0x30, 0x72, 0x09, 0x07, 0xe5, 0xe7, 0xe7, + 0xfa, 0x80, 0xa4, 0x9c, 0xe0, 0x9a, 0x85, 0x74, 0xb9, 0xd8, 0x9c, 0xc1, 0xea, 0x24, 0x18, 0x15, + 0x18, 0x35, 0xb8, 0x8d, 0x44, 0xf5, 0x40, 0x26, 0xe8, 0xc1, 0x95, 0x42, 0x24, 0x83, 0xa0, 0x8a, + 0x84, 0xe4, 0xb8, 0xb8, 0x82, 0x0b, 0x8a, 0x32, 0x4b, 0x52, 0xfd, 0x12, 0x73, 0x53, 0x25, 0x98, + 0x14, 0x18, 0x35, 0x38, 0x83, 0x90, 0x44, 0x84, 0x78, 0xb8, 0x18, 0x23, 0x24, 0x98, 0x15, 0x18, + 0x35, 0x58, 0x83, 0x18, 0x23, 0x40, 0xbc, 0x48, 0x09, 0x16, 0x08, 0x2f, 0xd2, 0x49, 0xe0, 0xc4, + 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf1, 0x58, 0x8e, 0x21, + 0x89, 0x0d, 0xec, 0x36, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfe, 0x87, 0xbe, 0x87, 0xd9, + 0x00, 0x00, 0x00, +} diff --git a/gml/internal/room/room_layer_background.proto b/gml/internal/room/room_layer_background.proto new file mode 100644 index 0000000..23758f2 --- /dev/null +++ b/gml/internal/room/room_layer_background.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package room; + +import "room_layer_config.proto"; + +message RoomLayerBackground { + RoomLayerConfig Config = 1; + string SpriteName = 2; + int32 X = 3; + int32 Y = 4; +} diff --git a/gml/internal/room/room_layer_config.pb.go b/gml/internal/room/room_layer_config.pb.go new file mode 100644 index 0000000..a39b7f0 --- /dev/null +++ b/gml/internal/room/room_layer_config.pb.go @@ -0,0 +1,452 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_layer_config.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomLayerConfig struct { + Kind RoomLayerKind `protobuf:"varint,1,opt,name=Kind,proto3,enum=room.RoomLayerKind" json:"Kind,omitempty"` + UUID string `protobuf:"bytes,2,opt,name=UUID,proto3" json:"UUID,omitempty"` + Name string `protobuf:"bytes,3,opt,name=Name,proto3" json:"Name,omitempty"` + Order int32 `protobuf:"varint,4,opt,name=Order,proto3" json:"Order,omitempty"` + // RoomLayerSprite Only + HasCollision bool `protobuf:"varint,5,opt,name=HasCollision,proto3" json:"HasCollision,omitempty"` +} + +func (m *RoomLayerConfig) Reset() { *m = RoomLayerConfig{} } +func (m *RoomLayerConfig) String() string { return proto.CompactTextString(m) } +func (*RoomLayerConfig) ProtoMessage() {} +func (*RoomLayerConfig) Descriptor() ([]byte, []int) { return fileDescriptorRoomLayerConfig, []int{0} } + +func (m *RoomLayerConfig) GetKind() RoomLayerKind { + if m != nil { + return m.Kind + } + return RoomLayerKind_None +} + +func (m *RoomLayerConfig) GetUUID() string { + if m != nil { + return m.UUID + } + return "" +} + +func (m *RoomLayerConfig) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *RoomLayerConfig) GetOrder() int32 { + if m != nil { + return m.Order + } + return 0 +} + +func (m *RoomLayerConfig) GetHasCollision() bool { + if m != nil { + return m.HasCollision + } + return false +} + +func init() { + proto.RegisterType((*RoomLayerConfig)(nil), "room.RoomLayerConfig") +} +func (m *RoomLayerConfig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomLayerConfig) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Kind != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintRoomLayerConfig(dAtA, i, uint64(m.Kind)) + } + if len(m.UUID) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintRoomLayerConfig(dAtA, i, uint64(len(m.UUID))) + i += copy(dAtA[i:], m.UUID) + } + if len(m.Name) > 0 { + dAtA[i] = 0x1a + i++ + i = encodeVarintRoomLayerConfig(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + } + if m.Order != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintRoomLayerConfig(dAtA, i, uint64(m.Order)) + } + if m.HasCollision { + dAtA[i] = 0x28 + i++ + if m.HasCollision { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + return i, nil +} + +func encodeVarintRoomLayerConfig(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomLayerConfig) Size() (n int) { + var l int + _ = l + if m.Kind != 0 { + n += 1 + sovRoomLayerConfig(uint64(m.Kind)) + } + l = len(m.UUID) + if l > 0 { + n += 1 + l + sovRoomLayerConfig(uint64(l)) + } + l = len(m.Name) + if l > 0 { + n += 1 + l + sovRoomLayerConfig(uint64(l)) + } + if m.Order != 0 { + n += 1 + sovRoomLayerConfig(uint64(m.Order)) + } + if m.HasCollision { + n += 2 + } + return n +} + +func sovRoomLayerConfig(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomLayerConfig(x uint64) (n int) { + return sovRoomLayerConfig(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomLayerConfig) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomLayerConfig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomLayerConfig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + m.Kind = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Kind |= (RoomLayerKind(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomLayerConfig + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UUID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomLayerConfig + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Order", wireType) + } + m.Order = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Order |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field HasCollision", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.HasCollision = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipRoomLayerConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomLayerConfig + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomLayerConfig(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomLayerConfig + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomLayerConfig(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomLayerConfig = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomLayerConfig = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_layer_config.proto", fileDescriptorRoomLayerConfig) } + +var fileDescriptorRoomLayerConfig = []byte{ + // 200 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2f, 0xca, 0xcf, 0xcf, + 0x8d, 0xcf, 0x49, 0xac, 0x4c, 0x2d, 0x8a, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, + 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0x49, 0x48, 0x89, 0x22, 0x49, 0x67, 0x67, 0xe6, 0xa5, 0x40, + 0x24, 0x95, 0xe6, 0x30, 0x72, 0xf1, 0x07, 0xe5, 0xe7, 0xe7, 0xfa, 0x80, 0x24, 0x9c, 0xc1, 0xda, + 0x84, 0xd4, 0xb9, 0x58, 0xbc, 0x33, 0xf3, 0x52, 0x24, 0x18, 0x15, 0x18, 0x35, 0xf8, 0x8c, 0x84, + 0xf5, 0x40, 0x3a, 0xf5, 0xe0, 0x8a, 0x40, 0x52, 0x41, 0x60, 0x05, 0x42, 0x42, 0x5c, 0x2c, 0xa1, + 0xa1, 0x9e, 0x2e, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0x36, 0x48, 0xcc, 0x2f, 0x31, + 0x37, 0x55, 0x82, 0x19, 0x22, 0x06, 0x62, 0x0b, 0x89, 0x70, 0xb1, 0xfa, 0x17, 0xa5, 0xa4, 0x16, + 0x49, 0xb0, 0x28, 0x30, 0x6a, 0xb0, 0x06, 0x41, 0x38, 0x42, 0x4a, 0x5c, 0x3c, 0x1e, 0x89, 0xc5, + 0xce, 0xf9, 0x39, 0x39, 0x99, 0xc5, 0x99, 0xf9, 0x79, 0x12, 0xac, 0x0a, 0x8c, 0x1a, 0x1c, 0x41, + 0x28, 0x62, 0x4e, 0x02, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, + 0xe3, 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0x77, 0x1b, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, + 0xe6, 0x27, 0xdb, 0xac, 0xef, 0x00, 0x00, 0x00, +} diff --git a/gml/internal/room/room_layer_config.proto b/gml/internal/room/room_layer_config.proto new file mode 100644 index 0000000..c3863b3 --- /dev/null +++ b/gml/internal/room/room_layer_config.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package room; + +import "room_layer_kind.proto"; + +message RoomLayerConfig { + RoomLayerKind Kind = 1; + string UUID = 2; + string Name = 3; + int32 Order = 4; + + // RoomLayerSprite Only + bool HasCollision = 5; +} diff --git a/gml/internal/room/room_layer_instance.pb.go b/gml/internal/room/room_layer_instance.pb.go new file mode 100644 index 0000000..3060043 --- /dev/null +++ b/gml/internal/room/room_layer_instance.pb.go @@ -0,0 +1,417 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_layer_instance.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomLayerInstance struct { + Config *RoomLayerConfig `protobuf:"bytes,1,opt,name=Config" json:"Config,omitempty"` + Instances []*RoomObject `protobuf:"bytes,2,rep,name=Instances" json:"Instances,omitempty"` + // Room Editor only + DeletedInstances []*RoomObject `protobuf:"bytes,3,rep,name=DeletedInstances" json:"DeletedInstances,omitempty"` +} + +func (m *RoomLayerInstance) Reset() { *m = RoomLayerInstance{} } +func (m *RoomLayerInstance) String() string { return proto.CompactTextString(m) } +func (*RoomLayerInstance) ProtoMessage() {} +func (*RoomLayerInstance) Descriptor() ([]byte, []int) { + return fileDescriptorRoomLayerInstance, []int{0} +} + +func (m *RoomLayerInstance) GetConfig() *RoomLayerConfig { + if m != nil { + return m.Config + } + return nil +} + +func (m *RoomLayerInstance) GetInstances() []*RoomObject { + if m != nil { + return m.Instances + } + return nil +} + +func (m *RoomLayerInstance) GetDeletedInstances() []*RoomObject { + if m != nil { + return m.DeletedInstances + } + return nil +} + +func init() { + proto.RegisterType((*RoomLayerInstance)(nil), "room.RoomLayerInstance") +} +func (m *RoomLayerInstance) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomLayerInstance) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Config != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintRoomLayerInstance(dAtA, i, uint64(m.Config.Size())) + n1, err := m.Config.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if len(m.Instances) > 0 { + for _, msg := range m.Instances { + dAtA[i] = 0x12 + i++ + i = encodeVarintRoomLayerInstance(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if len(m.DeletedInstances) > 0 { + for _, msg := range m.DeletedInstances { + dAtA[i] = 0x1a + i++ + i = encodeVarintRoomLayerInstance(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func encodeVarintRoomLayerInstance(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomLayerInstance) Size() (n int) { + var l int + _ = l + if m.Config != nil { + l = m.Config.Size() + n += 1 + l + sovRoomLayerInstance(uint64(l)) + } + if len(m.Instances) > 0 { + for _, e := range m.Instances { + l = e.Size() + n += 1 + l + sovRoomLayerInstance(uint64(l)) + } + } + if len(m.DeletedInstances) > 0 { + for _, e := range m.DeletedInstances { + l = e.Size() + n += 1 + l + sovRoomLayerInstance(uint64(l)) + } + } + return n +} + +func sovRoomLayerInstance(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomLayerInstance(x uint64) (n int) { + return sovRoomLayerInstance(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomLayerInstance) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomLayerInstance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomLayerInstance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerInstance + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Config == nil { + m.Config = &RoomLayerConfig{} + } + if err := m.Config.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Instances", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerInstance + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Instances = append(m.Instances, &RoomObject{}) + if err := m.Instances[len(m.Instances)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeletedInstances", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerInstance + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DeletedInstances = append(m.DeletedInstances, &RoomObject{}) + if err := m.DeletedInstances[len(m.DeletedInstances)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRoomLayerInstance(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomLayerInstance + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomLayerInstance(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomLayerInstance + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerInstance + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomLayerInstance(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomLayerInstance = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomLayerInstance = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_layer_instance.proto", fileDescriptorRoomLayerInstance) } + +var fileDescriptorRoomLayerInstance = []byte{ + // 186 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2c, 0xca, 0xcf, 0xcf, + 0x8d, 0xcf, 0x49, 0xac, 0x4c, 0x2d, 0x8a, 0xcf, 0xcc, 0x2b, 0x2e, 0x49, 0xcc, 0x4b, 0x4e, 0xd5, + 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0x49, 0x49, 0x09, 0x82, 0x15, 0xe4, 0x27, 0x65, + 0xa5, 0x26, 0x97, 0x40, 0x24, 0xa4, 0xc4, 0x91, 0xf4, 0x24, 0xe7, 0xe7, 0xa5, 0x65, 0xa6, 0x43, + 0x24, 0x94, 0x36, 0x30, 0x72, 0x09, 0x06, 0xe5, 0xe7, 0xe7, 0xfa, 0x80, 0xa4, 0x3c, 0xa1, 0xa6, + 0x09, 0xe9, 0x72, 0xb1, 0x39, 0x83, 0x55, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x89, 0xea, + 0x81, 0xf4, 0xeb, 0xc1, 0x15, 0x42, 0x24, 0x83, 0xa0, 0x8a, 0x84, 0xf4, 0xb8, 0x38, 0x61, 0x5a, + 0x8b, 0x25, 0x98, 0x14, 0x98, 0x35, 0xb8, 0x8d, 0x04, 0x10, 0x3a, 0xfc, 0xc1, 0x0e, 0x09, 0x42, + 0x28, 0x11, 0xb2, 0xe1, 0x12, 0x70, 0x49, 0xcd, 0x49, 0x2d, 0x49, 0x4d, 0x41, 0x68, 0x63, 0xc6, + 0xa1, 0x0d, 0x43, 0xa5, 0x93, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, + 0x24, 0xc7, 0x38, 0xe3, 0xb1, 0x1c, 0x43, 0x12, 0x1b, 0xd8, 0x2f, 0xc6, 0x80, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x9a, 0x11, 0x61, 0x2c, 0x1a, 0x01, 0x00, 0x00, +} diff --git a/gml/internal/room/room_layer_instance.proto b/gml/internal/room/room_layer_instance.proto new file mode 100644 index 0000000..140ac7a --- /dev/null +++ b/gml/internal/room/room_layer_instance.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; +package room; + +import "room_object.proto"; +import "room_layer_config.proto"; + +message RoomLayerInstance { + RoomLayerConfig Config = 1; + repeated RoomObject Instances = 2; + + // Room Editor only + repeated RoomObject DeletedInstances = 3; +} diff --git a/gml/internal/room/room_layer_kind.pb.go b/gml/internal/room/room_layer_kind.pb.go new file mode 100644 index 0000000..ed348d9 --- /dev/null +++ b/gml/internal/room/room_layer_kind.pb.go @@ -0,0 +1,59 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_layer_kind.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomLayerKind int32 + +const ( + RoomLayerKind_None RoomLayerKind = 0 + RoomLayerKind_Instance RoomLayerKind = 1 + RoomLayerKind_Background RoomLayerKind = 2 + RoomLayerKind_Sprite RoomLayerKind = 3 +) + +var RoomLayerKind_name = map[int32]string{ + 0: "None", + 1: "Instance", + 2: "Background", + 3: "Sprite", +} +var RoomLayerKind_value = map[string]int32{ + "None": 0, + "Instance": 1, + "Background": 2, + "Sprite": 3, +} + +func (x RoomLayerKind) String() string { + return proto.EnumName(RoomLayerKind_name, int32(x)) +} +func (RoomLayerKind) EnumDescriptor() ([]byte, []int) { return fileDescriptorRoomLayerKind, []int{0} } + +func init() { + proto.RegisterEnum("room.RoomLayerKind", RoomLayerKind_name, RoomLayerKind_value) +} + +func init() { proto.RegisterFile("room_layer_kind.proto", fileDescriptorRoomLayerKind) } + +var fileDescriptorRoomLayerKind = []byte{ + // 142 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2d, 0xca, 0xcf, 0xcf, + 0x8d, 0xcf, 0x49, 0xac, 0x4c, 0x2d, 0x8a, 0xcf, 0xce, 0xcc, 0x4b, 0xd1, 0x2b, 0x28, 0xca, 0x2f, + 0xc9, 0x17, 0x62, 0x01, 0x09, 0x6b, 0x39, 0x73, 0xf1, 0x06, 0xe5, 0xe7, 0xe7, 0xfa, 0x80, 0x64, + 0xbd, 0x33, 0xf3, 0x52, 0x84, 0x38, 0xb8, 0x58, 0xfc, 0xf2, 0xf3, 0x52, 0x05, 0x18, 0x84, 0x78, + 0xb8, 0x38, 0x3c, 0xf3, 0x8a, 0x4b, 0x12, 0xf3, 0x92, 0x53, 0x05, 0x18, 0x85, 0xf8, 0xb8, 0xb8, + 0x9c, 0x12, 0x93, 0xb3, 0xd3, 0x8b, 0xf2, 0x4b, 0xf3, 0x52, 0x04, 0x98, 0x84, 0xb8, 0xb8, 0xd8, + 0x82, 0x0b, 0x8a, 0x32, 0x4b, 0x52, 0x05, 0x98, 0x9d, 0x04, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, + 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x19, 0x8f, 0xe5, 0x18, 0x92, 0xd8, 0xc0, 0x76, 0x18, + 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x5e, 0xdf, 0x54, 0x6c, 0x7c, 0x00, 0x00, 0x00, +} diff --git a/gml/internal/room/room_layer_kind.proto b/gml/internal/room/room_layer_kind.proto new file mode 100644 index 0000000..4238e62 --- /dev/null +++ b/gml/internal/room/room_layer_kind.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; +package room; + +enum RoomLayerKind { + None = 0; + Instance = 1; + Background = 2; + Sprite = 3; +} diff --git a/gml/internal/room/room_layer_sprite.pb.go b/gml/internal/room/room_layer_sprite.pb.go new file mode 100644 index 0000000..2a0e299 --- /dev/null +++ b/gml/internal/room/room_layer_sprite.pb.go @@ -0,0 +1,415 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_layer_sprite.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomLayerSprite struct { + Config *RoomLayerConfig `protobuf:"bytes,1,opt,name=Config" json:"Config,omitempty"` + Sprites []*RoomSpriteObject `protobuf:"bytes,2,rep,name=Sprites" json:"Sprites,omitempty"` + // Room Editor only + DeletedSprites []*RoomSpriteObject `protobuf:"bytes,3,rep,name=DeletedSprites" json:"DeletedSprites,omitempty"` +} + +func (m *RoomLayerSprite) Reset() { *m = RoomLayerSprite{} } +func (m *RoomLayerSprite) String() string { return proto.CompactTextString(m) } +func (*RoomLayerSprite) ProtoMessage() {} +func (*RoomLayerSprite) Descriptor() ([]byte, []int) { return fileDescriptorRoomLayerSprite, []int{0} } + +func (m *RoomLayerSprite) GetConfig() *RoomLayerConfig { + if m != nil { + return m.Config + } + return nil +} + +func (m *RoomLayerSprite) GetSprites() []*RoomSpriteObject { + if m != nil { + return m.Sprites + } + return nil +} + +func (m *RoomLayerSprite) GetDeletedSprites() []*RoomSpriteObject { + if m != nil { + return m.DeletedSprites + } + return nil +} + +func init() { + proto.RegisterType((*RoomLayerSprite)(nil), "room.RoomLayerSprite") +} +func (m *RoomLayerSprite) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomLayerSprite) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Config != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintRoomLayerSprite(dAtA, i, uint64(m.Config.Size())) + n1, err := m.Config.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if len(m.Sprites) > 0 { + for _, msg := range m.Sprites { + dAtA[i] = 0x12 + i++ + i = encodeVarintRoomLayerSprite(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if len(m.DeletedSprites) > 0 { + for _, msg := range m.DeletedSprites { + dAtA[i] = 0x1a + i++ + i = encodeVarintRoomLayerSprite(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func encodeVarintRoomLayerSprite(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomLayerSprite) Size() (n int) { + var l int + _ = l + if m.Config != nil { + l = m.Config.Size() + n += 1 + l + sovRoomLayerSprite(uint64(l)) + } + if len(m.Sprites) > 0 { + for _, e := range m.Sprites { + l = e.Size() + n += 1 + l + sovRoomLayerSprite(uint64(l)) + } + } + if len(m.DeletedSprites) > 0 { + for _, e := range m.DeletedSprites { + l = e.Size() + n += 1 + l + sovRoomLayerSprite(uint64(l)) + } + } + return n +} + +func sovRoomLayerSprite(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomLayerSprite(x uint64) (n int) { + return sovRoomLayerSprite(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomLayerSprite) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomLayerSprite: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomLayerSprite: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerSprite + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Config == nil { + m.Config = &RoomLayerConfig{} + } + if err := m.Config.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sprites", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerSprite + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sprites = append(m.Sprites, &RoomSpriteObject{}) + if err := m.Sprites[len(m.Sprites)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeletedSprites", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRoomLayerSprite + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DeletedSprites = append(m.DeletedSprites, &RoomSpriteObject{}) + if err := m.DeletedSprites[len(m.DeletedSprites)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRoomLayerSprite(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomLayerSprite + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomLayerSprite(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomLayerSprite + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomLayerSprite + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomLayerSprite(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomLayerSprite = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomLayerSprite = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_layer_sprite.proto", fileDescriptorRoomLayerSprite) } + +var fileDescriptorRoomLayerSprite = []byte{ + // 187 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2f, 0xca, 0xcf, 0xcf, + 0x8d, 0xcf, 0x49, 0xac, 0x4c, 0x2d, 0x8a, 0x2f, 0x2e, 0x28, 0xca, 0x2c, 0x49, 0xd5, 0x2b, 0x28, + 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0x49, 0x48, 0x49, 0x80, 0xa5, 0x21, 0x12, 0xf1, 0xf9, 0x49, + 0x59, 0xa9, 0xc9, 0x25, 0x10, 0x79, 0x29, 0x64, 0x8d, 0xc9, 0xf9, 0x79, 0x69, 0x99, 0xe9, 0x10, + 0x09, 0xa5, 0x4d, 0x8c, 0x5c, 0xfc, 0x41, 0xf9, 0xf9, 0xb9, 0x3e, 0x20, 0xa9, 0x60, 0xb0, 0x4e, + 0x21, 0x5d, 0x2e, 0x36, 0x67, 0xb0, 0x1a, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x51, 0x3d, + 0x90, 0x6e, 0x3d, 0xb8, 0x32, 0x88, 0x64, 0x10, 0x54, 0x91, 0x90, 0x01, 0x17, 0x3b, 0x44, 0x63, + 0xb1, 0x04, 0x93, 0x02, 0xb3, 0x06, 0xb7, 0x91, 0x18, 0x42, 0x3d, 0x44, 0xc2, 0x1f, 0xec, 0x94, + 0x20, 0x98, 0x32, 0x21, 0x3b, 0x2e, 0x3e, 0x97, 0xd4, 0x9c, 0xd4, 0x92, 0xd4, 0x14, 0x98, 0x46, + 0x66, 0xbc, 0x1a, 0xd1, 0x54, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, + 0x83, 0x47, 0x72, 0x8c, 0x33, 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x7d, 0x63, 0x0c, 0x08, 0x00, + 0x00, 0xff, 0xff, 0x6b, 0x42, 0x8b, 0x1a, 0x21, 0x01, 0x00, 0x00, +} diff --git a/gml/internal/room/room_layer_sprite.proto b/gml/internal/room/room_layer_sprite.proto new file mode 100644 index 0000000..591c6fc --- /dev/null +++ b/gml/internal/room/room_layer_sprite.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; +package room; + +import "room_sprite_object.proto"; +import "room_layer_config.proto"; + +message RoomLayerSprite { + RoomLayerConfig Config = 1; + repeated RoomSpriteObject Sprites = 2; + + // Room Editor only + repeated RoomSpriteObject DeletedSprites = 3; +} diff --git a/gml/internal/room/room_manager.go b/gml/internal/room/room_manager.go index b15ce84..4014970 100644 --- a/gml/internal/room/room_manager.go +++ b/gml/internal/room/room_manager.go @@ -8,6 +8,10 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/file" ) +const ( + RoomDirectoryBase = "room" +) + var ( gRoomManager = newRoomManager() ) @@ -25,7 +29,7 @@ func newRoomManager() *RoomManager { func loadRoomFromDataFile(name string) (*Room, error) { start := time.Now() - roomDataPath := file.AssetsDirectory + "/room/" + name + ".data" + roomDataPath := file.AssetDirectory + "/" + RoomDirectoryBase + "/" + name + ".data" dataFile, err := file.OpenFile(roomDataPath) if err != nil { return nil, err diff --git a/gml/internal/room/room_manager_nonjs.go b/gml/internal/room/room_manager_nonjs.go index 5fe518a..ec35bc5 100644 --- a/gml/internal/room/room_manager_nonjs.go +++ b/gml/internal/room/room_manager_nonjs.go @@ -8,15 +8,19 @@ package room import ( "bufio" "bytes" + "encoding/json" "io/ioutil" "os" + "path" "path/filepath" + "sort" "strconv" "strings" "time" "github.com/silbinarywolf/gml-go/gml/internal/file" "github.com/silbinarywolf/gml-go/gml/internal/object" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) func (room *Room) writeDataFile(roomPath string) error { @@ -31,114 +35,9 @@ func (room *Room) writeDataFile(roomPath string) error { return nil } -func (room *Room) readInstance(instancePath string) { - //println("Loading ", instancePath, "...") - instanceFileData, err := file.OpenFile(instancePath) - if err != nil { - panic("Unable to find map entity file: " + err.Error()) - } - bytesData, err := ioutil.ReadAll(instanceFileData) - instanceFileData.Close() - if err != nil { - panic("Unable to find map entity file: Read all: " + err.Error()) - } - bytesReader := bytes.NewReader(bytesData) - scanner := bufio.NewScanner(bytesReader) - - // Entity name - scanner.Scan() - entityName := strings.TrimSpace(scanner.Text()) - - // X - scanner.Scan() - x64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) - x := int32(x64) - if err != nil { - println("Error parsing Y of entity", entityName) - return - } - - // Y - scanner.Scan() - y64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) - y := int32(y64) - if err != nil { - println("Error parsing X of entity", entityName) - return - } - if err := scanner.Err(); err != nil { - println("Error parsing entity, error: ", err.Error()) - return - } - objectIndex, ok := object.ObjectGetIndex(entityName) - if !ok { - println("Missing mapping of name \"" + entityName + "\" to entity ID. Is this name defined in your gml.Init()?") - return - } - - // Set room dimensions - { - // NOTE(Jake): 2018-06-02 - // - // Probably a slow hack to get the entity size - // for building map data on-fly, but whatever! - // - inst := object.NewRawInstance(objectIndex, 0, 0, new(object.Space), -1) - baseObj := inst.BaseObject() - inst.Create() - - x := int32(x) - y := int32(y) - width := int32(baseObj.Size.X) - height := int32(baseObj.Size.Y) - - if x < room.Left { - room.Left = x - } - right := x + width - if right > room.Right { - room.Right = right - } - if y < room.Top { - room.Top = y - } - bottom := y + height - if bottom > room.Bottom { - room.Bottom = bottom + height - } - } - - basename := filepath.Base(instancePath) - filename := strings.TrimSuffix(basename, filepath.Ext(basename)) - - // Increase entity counter - { - filenameParts := strings.Split(filename, "_") - if len(filenameParts) == 3 { - id := filenameParts[len(filenameParts)-1] - count, err := strconv.ParseInt(id, 10, 64) - if err == nil { - if count > room.UserEntityCount { - username := filenameParts[len(filenameParts)-2] - if username == file.DebugUsernameFileSafe() { - room.UserEntityCount = count - } - } - } else { - println(filename, ": Skipping, Error parsing the last part (entity ID) after splitting by _") - } - } else { - println(filename, ": Expected to split into 3 parts, not", len(filenameParts)) - } - } - - room.Instances = append(room.Instances, &RoomObject{ - Filename: filename, - ObjectIndex: int32(objectIndex), - X: x, - Y: y, - }) -} +//func loadInstanceLayer(layer *RoomLayerInstance) { +// +//} func LoadRoom(name string) *Room { manager := gRoomManager @@ -148,41 +47,18 @@ func LoadRoom(name string) *Room { return res } - // Load from *.data file if it exists - //if mapDataFile, _ := loadMapFromData(name); mapDataFile != nil { - // manager.assetMap[name] = mapDataFile - // return mapDataFile - //} - - roomPath := file.AssetsDirectory + "/room/" + name + // Load room + start := time.Now() - // Read entities - instancePathList := make([]string, 0, 1000) - err := filepath.Walk(roomPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - println("prevent panic by handling failure accessing a path " + roomPath + ": " + err.Error()) - return err - } - if info.IsDir() { - return nil - } - instancePathList = append(instancePathList, path) - return nil - }) + // Load from *.data file if it exists + /*room, err := loadRoomFromDataFile(name) if err != nil { panic(err) - } - - // - room := new(Room) - room.Filepath = roomPath - room.Instances = make([]*RoomObject, 0, len(instancePathList)) - start := time.Now() - for _, instance := range instancePathList { - room.readInstance(instance) - } + }*/ + room := loadRoomFromDirectoryFiles(name) elapsed := time.Since(start) println("Room \"" + name + "\" took " + elapsed.String() + " to load.") + manager.assetMap[name] = room // NOTE(Jake): 2018-05-29 @@ -192,7 +68,357 @@ func LoadRoom(name string) *Room { // // Write out *.data file (for browsers / fast client loading) // - room.DebugWriteDataFile(roomPath) + room.DebugWriteDataFile(room.Filepath()) + + return room +} + +func loadRoomFromDirectoryFiles(name string) *Room { + objectTypeToInitState := make(map[object.ObjectIndex]object.ObjectType) + defer func() { + for _, inst := range objectTypeToInitState { + // NOTE(Jake): 2018-09-15 + // Cleanup entities or else they might stay alive on the server + // ie. networked entities + // I had to fix a bug where 26 enemies were "there" because this + // Destroy() wasn't here. + inst.Destroy() + } + }() + + // + room := new(Room) + room.Config = new(RoomConfig) + // NOTE(Jake): 2018-07-21 + // + // I might want UUID to be an actual UUID in the future + // however can't do this yet as the folder name needs to be the room + // name currently. + // + room.Config.UUID = name + room.Config.Name = name + //room.Instances = make([]*RoomObject, 0, len(instancePathList)) + roomPath := room.Filepath() + { + // Read config + configPath := roomPath + "/config.json" + fileData, err := file.OpenFile(configPath) + if err != nil { + panic("Failed to load config.json for room: " + configPath + "\n" + "Error: " + err.Error()) + //continue + } + bytes, err := ioutil.ReadAll(fileData) + if err != nil { + panic("Error loading load config.json for room: " + configPath + "\n" + "Error: " + err.Error()) + } + if err := json.Unmarshal(bytes, room.Config); err != nil { + panic("Error unmarshalling load config.json for room: " + configPath + "\n" + "Error: " + err.Error()) + } + } + { + // Read layer directories + var layerPathList []string + err := filepath.Walk(roomPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + println("prevent panic by handling failure accessing a path " + roomPath + ": " + err.Error()) + return err + } + if !info.IsDir() { + // Skip files + return nil + } + if path == roomPath { + // Skip self + return nil + } + layerPathList = append(layerPathList, path) + return nil + }) + if err != nil { + panic(err) + } + // Load each layer config + for _, pathStr := range layerPathList { + configPath := pathStr + "/config.json" + fileData, err := file.OpenFile(configPath) + if err != nil { + panic("Failed to load config.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + //continue + } + bytesData, err := ioutil.ReadAll(fileData) + if err != nil { + panic("Error loading load config.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + //continue + } + layerConfig := new(RoomLayerConfig) + if err := json.Unmarshal(bytesData, layerConfig); err != nil { + panic("Error unmarshalling load config.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + //continue + } + //fmt.Printf("%v", layerConfig.String()) + switch layerConfig.Kind { + case RoomLayerKind_Instance: + layer := new(RoomLayerInstance) + layer.Config = layerConfig + layerPath := roomPath + "/" + layer.Config.UUID + + // Read instance files + var pathList []string + err := filepath.Walk(layerPath, func(pathStr string, info os.FileInfo, err error) error { + if err != nil { + println("prevent panic by handling failure accessing a path " + roomPath + ": " + err.Error()) + return err + } + // Skip directories and only get *.txt files + if info.IsDir() || + path.Ext(pathStr) != ".txt" { + return nil + } + pathList = append(pathList, pathStr) + return nil + }) + if err != nil { + panic(err) + } + // Read each individual file + for _, path := range pathList { + //println("Loading ", instancePath, "...") + instanceFileData, err := file.OpenFile(path) + if err != nil { + panic("Unable to find map entity file: " + err.Error()) + } + bytesData, err := ioutil.ReadAll(instanceFileData) + instanceFileData.Close() + if err != nil { + panic("Unable to find map entity file: Read all: " + err.Error()) + } + bytesReader := bytes.NewReader(bytesData) + scanner := bufio.NewScanner(bytesReader) + + // Entity name + scanner.Scan() + entityName := strings.TrimSpace(scanner.Text()) + + // X + scanner.Scan() + x64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) + x := int32(x64) + if err != nil { + println("Error parsing Y of entity", entityName) + continue + } + + // Y + scanner.Scan() + y64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) + y := int32(y64) + if err != nil { + println("Error parsing X of entity", entityName) + continue + } + if err := scanner.Err(); err != nil { + println("Error parsing entity, error: ", err.Error()) + continue + } + objectIndex, ok := object.ObjectGetIndex(entityName) + if !ok { + println("Missing mapping of name \"" + entityName + "\" to entity ID. Is this name defined in your gml.Init()?") + continue + } + + // Set room dimensions + { + // NOTE(Jake): 2018-06-02 + // + // Probably a slow hack to get the entity size + // for building map data on-fly, but whatever! + // + // NOTE(Jake): 2018-09-15 + // Should definitely look into an entity having default + // hitbox etc as Create() can cause bugs, like multiple + // networked entities because I didn't call gml.Destroy() + // + inst, ok := objectTypeToInitState[objectIndex] + if !ok { + inst = object.NewRawInstance(objectIndex, 0, 0, 0) + inst.Create() + objectTypeToInitState[objectIndex] = inst + } + baseObj := inst.BaseObject() + + x := int32(x) + y := int32(y) + width := int32(baseObj.Size.X) + height := int32(baseObj.Size.Y) + + if x < room.Left { + room.Left = x + } + if right := x + width; right > room.Right { + room.Right = right + } + if y < room.Top { + room.Top = y + } + if bottom := y + height; bottom > room.Bottom { + room.Bottom = bottom + height + } + } + + basename := filepath.Base(path) + uuid := strings.TrimSuffix(basename, filepath.Ext(basename)) + + layer.Instances = append(layer.Instances, &RoomObject{ + UUID: uuid, + ObjectIndex: int32(objectIndex), + X: x, + Y: y, + }) + } + + room.InstanceLayers = append(room.InstanceLayers, layer) + case RoomLayerKind_Background: + layer := new(RoomLayerBackground) + layerPath := roomPath + "/" + layerConfig.UUID + + configPath := layerPath + "/background.json" + fileData, err := file.OpenFile(configPath) + if err != nil { + panic("Failed to load background.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + } + bytesData, err := ioutil.ReadAll(fileData) + if err != nil { + panic("Error loading load background.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + } + if err := json.Unmarshal(bytesData, layer); err != nil { + panic("Error unmarshalling load background.json for layer: " + configPath + "\n" + "Error: " + err.Error()) + } + // NOTE(Jake): 2018-07-29 + // + // "Config" data should ideally not be written into the above struct + // so we're just ensuring here that config.json is the "source of truth" + // for this data. + // + layer.Config = layerConfig + room.BackgroundLayers = append(room.BackgroundLayers, layer) + case RoomLayerKind_Sprite: + layer := new(RoomLayerSprite) + layer.Config = layerConfig + layerPath := roomPath + "/" + layer.Config.UUID + + // Read instance files + var pathList []string + err := filepath.Walk(layerPath, func(pathStr string, info os.FileInfo, err error) error { + if err != nil { + println("prevent panic by handling failure accessing a path " + roomPath + ": " + err.Error()) + return err + } + // Skip directories and only get *.txt files + if info.IsDir() || + path.Ext(pathStr) != ".txt" { + return nil + } + pathList = append(pathList, pathStr) + return nil + }) + if err != nil { + panic(err) + } + + // Read each individual file + for _, path := range pathList { + //println("Loading ", instancePath, "...") + instanceFileData, err := file.OpenFile(path) + if err != nil { + panic("Unable to find sprite object file: " + err.Error()) + } + bytesData, err := ioutil.ReadAll(instanceFileData) + instanceFileData.Close() + if err != nil { + panic("Unable to find sprite object file: Read all: " + err.Error()) + } + bytesReader := bytes.NewReader(bytesData) + scanner := bufio.NewScanner(bytesReader) + + // Entity name + scanner.Scan() + spriteName := strings.TrimSpace(scanner.Text()) + + // X + scanner.Scan() + x64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) + x := int32(x64) + if err != nil { + println("Error parsing Y of sprite object", spriteName) + continue + } + + // Y + scanner.Scan() + y64, err := strconv.ParseInt(strings.TrimSpace(scanner.Text()), 10, 32) + y := int32(y64) + if err != nil { + println("Error parsing X of sprite object", spriteName) + continue + } + if err := scanner.Err(); err != nil { + println("Error parsing sprite object, error: ", err.Error()) + continue + } + + // Set room dimensions + { + spriteIndex := sprite.SpriteLoadByName(spriteName) + if spriteIndex == sprite.SprUndefined { + println("Error loading sprite sprite \"", spriteName, "\" error: ", err.Error()) + continue + } + x := int32(x) + y := int32(y) + size := spriteIndex.Size() + width := int32(size.X) + height := int32(size.Y) + + if x < room.Left { + room.Left = x + } + if right := x + width; right > room.Right { + room.Right = right + } + if y < room.Top { + room.Top = y + } + if bottom := y + height; bottom > room.Bottom { + room.Bottom = bottom + height + } + } + + basename := filepath.Base(path) + uuid := strings.TrimSuffix(basename, filepath.Ext(basename)) + + layer.Sprites = append(layer.Sprites, &RoomSpriteObject{ + UUID: uuid, + SpriteName: spriteName, + X: x, + Y: y, + }) + } + + room.SpriteLayers = append(room.SpriteLayers, layer) + default: + panic("Unknown or unhandled layer kind: " + layerConfig.Kind.String() + "(" + strconv.Itoa(int(layerConfig.Kind)) + ")") + } + } + } + { + // Sort each layer + sort.Slice(room.InstanceLayers, func(i, j int) bool { + return room.InstanceLayers[i].Config.Order < room.InstanceLayers[j].Config.Order + }) + sort.Slice(room.BackgroundLayers, func(i, j int) bool { + return room.BackgroundLayers[i].Config.Order < room.BackgroundLayers[j].Config.Order + }) + } return room } diff --git a/gml/internal/room/room_object.pb.go b/gml/internal/room/room_object.pb.go index 55bfcfe..6946c04 100644 --- a/gml/internal/room/room_object.pb.go +++ b/gml/internal/room/room_object.pb.go @@ -15,7 +15,7 @@ var _ = fmt.Errorf var _ = math.Inf type RoomObject struct { - Filename string `protobuf:"bytes,1,opt,name=Filename,proto3" json:"Filename,omitempty"` + UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"` ObjectIndex int32 `protobuf:"varint,2,opt,name=ObjectIndex,proto3" json:"ObjectIndex,omitempty"` X int32 `protobuf:"varint,3,opt,name=X,proto3" json:"X,omitempty"` Y int32 `protobuf:"varint,4,opt,name=Y,proto3" json:"Y,omitempty"` @@ -26,9 +26,9 @@ func (m *RoomObject) String() string { return proto.CompactTextString func (*RoomObject) ProtoMessage() {} func (*RoomObject) Descriptor() ([]byte, []int) { return fileDescriptorRoomObject, []int{0} } -func (m *RoomObject) GetFilename() string { +func (m *RoomObject) GetUUID() string { if m != nil { - return m.Filename + return m.UUID } return "" } @@ -72,11 +72,11 @@ func (m *RoomObject) MarshalTo(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Filename) > 0 { + if len(m.UUID) > 0 { dAtA[i] = 0xa i++ - i = encodeVarintRoomObject(dAtA, i, uint64(len(m.Filename))) - i += copy(dAtA[i:], m.Filename) + i = encodeVarintRoomObject(dAtA, i, uint64(len(m.UUID))) + i += copy(dAtA[i:], m.UUID) } if m.ObjectIndex != 0 { dAtA[i] = 0x10 @@ -108,7 +108,7 @@ func encodeVarintRoomObject(dAtA []byte, offset int, v uint64) int { func (m *RoomObject) Size() (n int) { var l int _ = l - l = len(m.Filename) + l = len(m.UUID) if l > 0 { n += 1 + l + sovRoomObject(uint64(l)) } @@ -168,7 +168,7 @@ func (m *RoomObject) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Filename", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -193,7 +193,7 @@ func (m *RoomObject) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Filename = string(dAtA[iNdEx:postIndex]) + m.UUID = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 0 { @@ -381,14 +381,14 @@ var ( func init() { proto.RegisterFile("room_object.proto", fileDescriptorRoomObject) } var fileDescriptorRoomObject = []byte{ - // 141 bytes of a gzipped FileDescriptorProto + // 137 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2c, 0xca, 0xcf, 0xcf, 0x8d, 0xcf, 0x4f, 0xca, 0x4a, 0x4d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, - 0x09, 0x29, 0xa5, 0x71, 0x71, 0x05, 0xe5, 0xe7, 0xe7, 0xfa, 0x83, 0x65, 0x84, 0xa4, 0xb8, 0x38, - 0xdc, 0x32, 0x73, 0x52, 0xf3, 0x12, 0x73, 0x53, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xe0, - 0x7c, 0x21, 0x05, 0x2e, 0x6e, 0x88, 0x2a, 0xcf, 0xbc, 0x94, 0xd4, 0x0a, 0x09, 0x26, 0x05, 0x46, - 0x0d, 0xd6, 0x20, 0x64, 0x21, 0x21, 0x1e, 0x2e, 0xc6, 0x08, 0x09, 0x66, 0xb0, 0x38, 0x63, 0x04, - 0x88, 0x17, 0x29, 0xc1, 0x02, 0xe1, 0x45, 0x3a, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, - 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x33, 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x9d, 0x61, 0x0c, - 0x08, 0x00, 0x00, 0xff, 0xff, 0x11, 0x27, 0x75, 0xff, 0x9b, 0x00, 0x00, 0x00, + 0x09, 0x29, 0xc5, 0x71, 0x71, 0x05, 0xe5, 0xe7, 0xe7, 0xfa, 0x83, 0x65, 0x84, 0x84, 0xb8, 0x58, + 0x42, 0x43, 0x3d, 0x5d, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x21, 0x05, 0x2e, + 0x6e, 0x88, 0xac, 0x67, 0x5e, 0x4a, 0x6a, 0x85, 0x04, 0x93, 0x02, 0xa3, 0x06, 0x6b, 0x10, 0xb2, + 0x90, 0x10, 0x0f, 0x17, 0x63, 0x84, 0x04, 0x33, 0x58, 0x9c, 0x31, 0x02, 0xc4, 0x8b, 0x94, 0x60, + 0x81, 0xf0, 0x22, 0x9d, 0x04, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, + 0x39, 0xc6, 0x19, 0x8f, 0xe5, 0x18, 0x92, 0xd8, 0xc0, 0xd6, 0x1b, 0x03, 0x02, 0x00, 0x00, 0xff, + 0xff, 0x53, 0x4d, 0x2c, 0x53, 0x93, 0x00, 0x00, 0x00, } diff --git a/gml/internal/room/room_object.proto b/gml/internal/room/room_object.proto index 8cf5545..f68b8ef 100644 --- a/gml/internal/room/room_object.proto +++ b/gml/internal/room/room_object.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package room; message RoomObject { - string Filename = 1; + string UUID = 1; int32 ObjectIndex = 2; int32 X = 3; int32 Y = 4; diff --git a/gml/internal/room/room_sprite_object.pb.go b/gml/internal/room/room_sprite_object.pb.go new file mode 100644 index 0000000..8e25e1d --- /dev/null +++ b/gml/internal/room/room_sprite_object.pb.go @@ -0,0 +1,479 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: room_sprite_object.proto + +package room + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type RoomSpriteObject struct { + UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"` + X int32 `protobuf:"varint,2,opt,name=X,proto3" json:"X,omitempty"` + Y int32 `protobuf:"varint,3,opt,name=Y,proto3" json:"Y,omitempty"` + Width int32 `protobuf:"varint,4,opt,name=Width,proto3" json:"Width,omitempty"` + Height int32 `protobuf:"varint,5,opt,name=Height,proto3" json:"Height,omitempty"` + SpriteName string `protobuf:"bytes,6,opt,name=SpriteName,proto3" json:"SpriteName,omitempty"` +} + +func (m *RoomSpriteObject) Reset() { *m = RoomSpriteObject{} } +func (m *RoomSpriteObject) String() string { return proto.CompactTextString(m) } +func (*RoomSpriteObject) ProtoMessage() {} +func (*RoomSpriteObject) Descriptor() ([]byte, []int) { return fileDescriptorRoomSpriteObject, []int{0} } + +func (m *RoomSpriteObject) GetUUID() string { + if m != nil { + return m.UUID + } + return "" +} + +func (m *RoomSpriteObject) GetX() int32 { + if m != nil { + return m.X + } + return 0 +} + +func (m *RoomSpriteObject) GetY() int32 { + if m != nil { + return m.Y + } + return 0 +} + +func (m *RoomSpriteObject) GetWidth() int32 { + if m != nil { + return m.Width + } + return 0 +} + +func (m *RoomSpriteObject) GetHeight() int32 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *RoomSpriteObject) GetSpriteName() string { + if m != nil { + return m.SpriteName + } + return "" +} + +func init() { + proto.RegisterType((*RoomSpriteObject)(nil), "room.RoomSpriteObject") +} +func (m *RoomSpriteObject) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoomSpriteObject) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.UUID) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(len(m.UUID))) + i += copy(dAtA[i:], m.UUID) + } + if m.X != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(m.X)) + } + if m.Y != 0 { + dAtA[i] = 0x18 + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(m.Y)) + } + if m.Width != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(m.Width)) + } + if m.Height != 0 { + dAtA[i] = 0x28 + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(m.Height)) + } + if len(m.SpriteName) > 0 { + dAtA[i] = 0x32 + i++ + i = encodeVarintRoomSpriteObject(dAtA, i, uint64(len(m.SpriteName))) + i += copy(dAtA[i:], m.SpriteName) + } + return i, nil +} + +func encodeVarintRoomSpriteObject(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RoomSpriteObject) Size() (n int) { + var l int + _ = l + l = len(m.UUID) + if l > 0 { + n += 1 + l + sovRoomSpriteObject(uint64(l)) + } + if m.X != 0 { + n += 1 + sovRoomSpriteObject(uint64(m.X)) + } + if m.Y != 0 { + n += 1 + sovRoomSpriteObject(uint64(m.Y)) + } + if m.Width != 0 { + n += 1 + sovRoomSpriteObject(uint64(m.Width)) + } + if m.Height != 0 { + n += 1 + sovRoomSpriteObject(uint64(m.Height)) + } + l = len(m.SpriteName) + if l > 0 { + n += 1 + l + sovRoomSpriteObject(uint64(l)) + } + return n +} + +func sovRoomSpriteObject(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRoomSpriteObject(x uint64) (n int) { + return sovRoomSpriteObject(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoomSpriteObject) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoomSpriteObject: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoomSpriteObject: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomSpriteObject + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UUID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field X", wireType) + } + m.X = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.X |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Y", wireType) + } + m.Y = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Y |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Width", wireType) + } + m.Width = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Width |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpriteName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRoomSpriteObject + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SpriteName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRoomSpriteObject(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRoomSpriteObject + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRoomSpriteObject(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRoomSpriteObject + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRoomSpriteObject + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRoomSpriteObject(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRoomSpriteObject = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRoomSpriteObject = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("room_sprite_object.proto", fileDescriptorRoomSpriteObject) } + +var fileDescriptorRoomSpriteObject = []byte{ + // 178 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x28, 0xca, 0xcf, 0xcf, + 0x8d, 0x2f, 0x2e, 0x28, 0xca, 0x2c, 0x49, 0x8d, 0xcf, 0x4f, 0xca, 0x4a, 0x4d, 0x2e, 0xd1, 0x2b, + 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xc9, 0x28, 0x4d, 0x60, 0xe4, 0x12, 0x08, 0xca, 0xcf, + 0xcf, 0x0d, 0x06, 0xab, 0xf0, 0x07, 0x2b, 0x10, 0x12, 0xe2, 0x62, 0x09, 0x0d, 0xf5, 0x74, 0x91, + 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x85, 0x78, 0xb8, 0x18, 0x23, 0x24, 0x98, 0x14, + 0x18, 0x35, 0x58, 0x83, 0x18, 0x23, 0x40, 0xbc, 0x48, 0x09, 0x66, 0x08, 0x2f, 0x52, 0x48, 0x84, + 0x8b, 0x35, 0x3c, 0x33, 0xa5, 0x24, 0x43, 0x82, 0x05, 0x2c, 0x02, 0xe1, 0x08, 0x89, 0x71, 0xb1, + 0x79, 0xa4, 0x66, 0xa6, 0x67, 0x94, 0x48, 0xb0, 0x82, 0x85, 0xa1, 0x3c, 0x21, 0x39, 0x2e, 0x2e, + 0x88, 0x6d, 0x7e, 0x89, 0xb9, 0xa9, 0x12, 0x6c, 0x60, 0x3b, 0x90, 0x44, 0x9c, 0x04, 0x4e, 0x3c, + 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x19, 0x8f, 0xe5, 0x18, 0x92, + 0xd8, 0xc0, 0x2e, 0x36, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xca, 0x0e, 0xa9, 0x1b, 0xcd, 0x00, + 0x00, 0x00, +} diff --git a/gml/internal/room/room_sprite_object.proto b/gml/internal/room/room_sprite_object.proto new file mode 100644 index 0000000..af7d4d1 --- /dev/null +++ b/gml/internal/room/room_sprite_object.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package room; + +message RoomSpriteObject { + string UUID = 1; + int32 X = 2; + int32 Y = 3; + int32 Width = 4; + int32 Height = 5; + string SpriteName = 6; +} diff --git a/gml/internal/sprite/collision_mask.go b/gml/internal/sprite/collision_mask.go new file mode 100644 index 0000000..8fab00e --- /dev/null +++ b/gml/internal/sprite/collision_mask.go @@ -0,0 +1,17 @@ +package sprite + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" +) + +const ( + CollisionMaskInherit CollisionMaskKind = 0 + iota + CollisionMaskManual +) + +type CollisionMaskKind int + +type CollisionMask struct { + Kind CollisionMaskKind + Rect geom.Rect +} diff --git a/gml/internal/sprite/sprite.go b/gml/internal/sprite/sprite.go index fd7b71d..a26ec43 100644 --- a/gml/internal/sprite/sprite.go +++ b/gml/internal/sprite/sprite.go @@ -1,18 +1,48 @@ package sprite import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) +const ( + maxCollisionMasks = 3 +) + +const SprUndefined SpriteIndex = 0 + type Sprite struct { name string frames []SpriteFrame - size math.Size + size geom.Size imageSpeed float64 } -func (spr *Sprite) Name() string { return spr.name } -func (spr *Sprite) Size() math.Size { return spr.size } +func (spr *Sprite) Name() string { return spr.name } +func (spr *Sprite) isLoaded() bool { return len(spr.frames) > 0 } +func (spr *Sprite) rect() geom.Rect { + return geom.Rect{ + Vec: geom.Vec{}, + Size: spr.size, + } +} + +type SpriteIndex int32 + +func (spriteIndex SpriteIndex) Name() string { return g_spriteManager.assetList[spriteIndex].name } +func (spriteIndex SpriteIndex) Size() geom.Size { return g_spriteManager.assetList[spriteIndex].size } +func (spriteIndex SpriteIndex) ImageSpeed() float64 { + return g_spriteManager.assetList[spriteIndex].imageSpeed +} +func (spriteIndex SpriteIndex) IsValid() bool { + return spriteIndex > 0 +} +func (spriteIndex SpriteIndex) IsLoaded() bool { + return len(g_spriteManager.assetList[spriteIndex].frames) > 0 +} + +func Frames(spriteIndex SpriteIndex) []SpriteFrame { + return g_spriteManager.assetList[spriteIndex].frames +} /*func (spr *Sprite) GetFrame(index int) *SpriteFrame { return &spr.frames[index] @@ -24,20 +54,23 @@ func newSprite(name string, frames []SpriteFrame, config spriteConfig) *Sprite { spr.frames = frames spr.imageSpeed = config.ImageSpeed - width := 0 - height := 0 - for _, frame := range frames { - frameWidth, frameHeight := frame.Size() - if width < frameWidth { - width = frameWidth + if len(frames) > 0 { + width := 0 + height := 0 + for _, frame := range frames { + frameWidth, frameHeight := frame.Size() + if width < frameWidth { + width = frameWidth + } + if height < frameHeight { + height = frameHeight + } + //frame.collisionMasks = make([]SpriteCollisionMask, maxCollisionMasks) } - if height < frameHeight { - height = frameHeight + spr.size = geom.Size{ + X: int32(width), + Y: int32(height), } } - spr.size = math.Size{ - X: int32(width), - Y: int32(height), - } return spr } diff --git a/gml/internal/sprite/sprite_asset.go b/gml/internal/sprite/sprite_asset.go index 9c7c531..7efbd9f 100644 --- a/gml/internal/sprite/sprite_asset.go +++ b/gml/internal/sprite/sprite_asset.go @@ -1,17 +1,18 @@ package sprite import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) type spriteAssetFrame struct { - Size math.Vec - Data []byte + Size geom.Vec + CollisionMasks [maxCollisionMasks]CollisionMask + Data []byte } type spriteAsset struct { Name string - Size math.Vec + Size geom.Vec ImageSpeed float64 Frames []spriteAssetFrame } @@ -32,9 +33,21 @@ func newSpriteAsset(name string, frames []spriteAssetFrame, config spriteConfig) height = frameHeight } } - spr.Size = math.Vec{ + spr.Size = geom.Vec{ X: width, Y: height, } + + // Load collision masks + for maskID, mask := range config.CollisionMasks { + for frameIndex, _ := range spr.Frames { + if frameMask, ok := mask[frameIndex]; ok { + mask := &spr.Frames[frameIndex].CollisionMasks[maskID] + *mask = frameMask + // fmt.Printf("%v, mask id: %d, frame id: %d\n", frameMask, maskID, frameIndex) + } + } + } + return spr } diff --git a/gml/internal/sprite/sprite_config.go b/gml/internal/sprite/sprite_config.go index 7fc6cf3..31ba161 100644 --- a/gml/internal/sprite/sprite_config.go +++ b/gml/internal/sprite/sprite_config.go @@ -13,11 +13,13 @@ type spriteConfig struct { // Remember! JSON unmarshal won't work on // unexported fields! // - ImageSpeed float64 `json:"ImageSpeed"` + ImageSpeed float64 `json:"ImageSpeed"` + CollisionMasks map[int]map[int]CollisionMask `json:"CollisionMasks"` } -func loadConfig(path string) spriteConfig { - fileData, err := file.OpenFile(path) +func loadConfig(name string) spriteConfig { + configPath := file.AssetDirectory + "/" + SpriteDirectoryBase + "/" + name + "/config.json" + fileData, err := file.OpenFile(configPath) if err != nil { return spriteConfig{} } diff --git a/gml/internal/sprite/sprite_frame.go b/gml/internal/sprite/sprite_frame.go new file mode 100644 index 0000000..cb98aae --- /dev/null +++ b/gml/internal/sprite/sprite_frame.go @@ -0,0 +1,9 @@ +package sprite + +type spriteFrameShared struct { + collisionMasks [maxCollisionMasks]CollisionMask +} + +func (spr *spriteFrameShared) init(frameData spriteAssetFrame) { + spr.collisionMasks = frameData.CollisionMasks +} diff --git a/gml/internal/sprite/sprite_frame_headless.go b/gml/internal/sprite/sprite_frame_headless.go index b0602b9..ddbf4c3 100644 --- a/gml/internal/sprite/sprite_frame_headless.go +++ b/gml/internal/sprite/sprite_frame_headless.go @@ -7,16 +7,19 @@ import ( ) type SpriteFrame struct { + spriteFrameShared width, height int } func (frame *SpriteFrame) Size() (width int, height int) { return frame.width, frame.height } func createFrame(frameData spriteAssetFrame) (SpriteFrame, error) { - return SpriteFrame{ + r := SpriteFrame{ width: int(frameData.Size.X), height: int(frameData.Size.Y), - }, nil + } + r.init(frameData) + return r, nil } // NOTE(Jake): 2018-06-17 diff --git a/gml/internal/sprite/sprite_frame_nonheadless.go b/gml/internal/sprite/sprite_frame_nonheadless.go index 7687281..d76da85 100644 --- a/gml/internal/sprite/sprite_frame_nonheadless.go +++ b/gml/internal/sprite/sprite_frame_nonheadless.go @@ -5,12 +5,12 @@ package sprite import ( "bytes" "image" - "math" "github.com/hajimehoshi/ebiten" ) type SpriteFrame struct { + spriteFrameShared image *ebiten.Image } @@ -26,9 +26,11 @@ func createFrame(frameData spriteAssetFrame) (SpriteFrame, error) { if err != nil { return SpriteFrame{}, err } - return SpriteFrame{ + r := SpriteFrame{ image: sheet, - }, nil + } + r.init(frameData) + return r, nil } // NOTE(Jake): 2018-06-17 @@ -36,7 +38,7 @@ func createFrame(frameData spriteAssetFrame) (SpriteFrame, error) { // This is called by draw_nonheadless.go in the parent package // so that it can draw the image. // -func GetRawFrame(spr *Sprite, index float64) *ebiten.Image { +func GetRawFrame(spriteIndex SpriteIndex, index int) *ebiten.Image { // NOTE(Jake): 2018-06-17 // // Golang does not "cast", it uses type conversion, which means @@ -45,6 +47,5 @@ func GetRawFrame(spr *Sprite, index float64) *ebiten.Image { // // https://stackoverflow.com/questions/35115868/how-to-round-to-nearest-int-when-casting-float-to-int-in-go // - i := int(math.Floor(index)) - return spr.frames[i].image + return Frames(spriteIndex)[index].image } diff --git a/gml/internal/sprite/sprite_manager.go b/gml/internal/sprite/sprite_manager.go index 9132a7d..ecd9231 100644 --- a/gml/internal/sprite/sprite_manager.go +++ b/gml/internal/sprite/sprite_manager.go @@ -9,22 +9,75 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/file" ) +const ( + SpriteDirectoryBase = "sprite" +) + var ( g_spriteManager = newSpriteManager() ) -func newSpriteManager() SpriteManager { - manager := SpriteManager{} - manager.assetMap = make(map[string]*Sprite) +type spriteManager struct { + assetList []Sprite + spriteNameToIndex map[string]SpriteIndex + spriteIndexToName []string +} + +func newSpriteManager() *spriteManager { + manager := &spriteManager{} return manager } -type SpriteManager struct { - assetMap map[string]*Sprite +// SpriteInitializeIndexToName is used by code generated by gmlgo so you can query a sprite by index or name +func SpriteInitializeIndexToName(indexToName []string, nameToIndex map[string]SpriteIndex) { + g_spriteManager.spriteIndexToName = indexToName + g_spriteManager.spriteNameToIndex = nameToIndex + g_spriteManager.assetList = make([]Sprite, len(g_spriteManager.spriteIndexToName)) +} + +func SpriteNames() []string { + return g_spriteManager.spriteIndexToName +} + +func sprite(index SpriteIndex) *Sprite { + sprite := &g_spriteManager.assetList[index] + if sprite.isLoaded() { + return sprite + } + return nil +} + +func SpriteLoadByName(name string) SpriteIndex { + index, ok := g_spriteManager.spriteNameToIndex[name] + if !ok { + return SprUndefined + } + return index +} + +/*func SpriteSize(index SpriteIndex) geom.Size { + manager := g_spriteManager + sprite := &manager.assetList[index] + if !sprite.isUsed() { + panic("sprite: Invalid sprite.") + } + return sprite.Size() +}*/ + +func SpriteLoad(index SpriteIndex) { + manager := g_spriteManager + sprite := &manager.assetList[index] + if sprite.isLoaded() { + return + } + name := manager.spriteIndexToName[index] + // todo(Jake): change loadSprite() to return Sprite, not *Sprite + result := loadSprite(name) + *sprite = *result } func loadSpriteFromData(name string) *spriteAsset { - path := file.AssetsDirectory + "/sprites/" + name + ".data" + path := file.AssetDirectory + "/" + SpriteDirectoryBase + "/" + name + ".data" fileData, err := file.OpenFile(path) if err != nil { //panic(errors.New("Unable to find image: " + path)) @@ -66,16 +119,3 @@ func loadSprite(name string) *Sprite { }) return result } - -func LoadSprite(name string) *Sprite { - manager := g_spriteManager - - // Use already loaded asset - if res, ok := manager.assetMap[name]; ok { - return res - } - result := loadSprite(name) - manager.assetMap[name] = result - - return result -} diff --git a/gml/internal/sprite/sprite_manager_debug.go b/gml/internal/sprite/sprite_manager_debug.go index b33438d..13a3c66 100644 --- a/gml/internal/sprite/sprite_manager_debug.go +++ b/gml/internal/sprite/sprite_manager_debug.go @@ -5,6 +5,7 @@ package sprite import ( "bytes" "encoding/gob" + "encoding/json" "errors" "image" "image/png" @@ -15,7 +16,7 @@ import ( "github.com/fsnotify/fsnotify" "github.com/silbinarywolf/gml-go/gml/internal/file" - "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) var ( @@ -31,6 +32,10 @@ func init() { //watcher.Close() } +func debugSpriteByName() { + +} + func DebugWatch() { // Get list of sprites updated this frame var watcherSpritesToUpdate []string @@ -59,18 +64,54 @@ FileWatchLoop: } // If those sprites are loaded, reload them - manager := g_spriteManager for _, spriteName := range watcherSpritesToUpdate { - spr := manager.assetMap[spriteName] - if spr != nil { - newSprData := loadSprite(spriteName) - *spr = *newSprData + spriteIndex := SpriteLoadByName(spriteName) + if spriteIndex == SprUndefined { + continue + } + spr := sprite(spriteIndex) + newSprData := loadSprite(spriteName) + *spr = *newSprData + } +} + +// DebugWriteSpriteConfig is called by the animation editor +func DebugWriteSpriteConfig(spriteIndex SpriteIndex) error { + spr := sprite(spriteIndex) + name := spr.Name() + config := loadConfig(name) + + // Write collision masks + { + collisionMasks := make(map[int]map[int]CollisionMask) + masks := make(map[int]CollisionMask) + for i, _ := range spr.frames { + mask := *GetCollisionMask(spriteIndex, i, 0) + if mask.Kind == CollisionMaskInherit { + delete(masks, i) + } else { + masks[i] = mask + } } + collisionMasks[0] = masks + config.CollisionMasks = collisionMasks + } + + configPath := file.AssetDirectory + "/" + SpriteDirectoryBase + "/" + name + "/config.json" + + json, err := json.MarshalIndent(config, "", "\t") + if err != nil { + return err + } + err = ioutil.WriteFile(configPath, json, 0644) + if err != nil { + return errors.New("Unable to write sprite config out to file: " + configPath + ", error:" + err.Error()) } + return nil } func debugWriteSprite(name string) { - folderPath := file.AssetsDirectory + "/sprites/" + name + "/" + folderPath := file.AssetDirectory + "/" + SpriteDirectoryBase + "/" + name + "/" // NOTE(Jake): 2018-06-18 // @@ -79,6 +120,10 @@ func debugWriteSprite(name string) { watcher.Remove(folderPath) watcher.Add(folderPath) + // Read config information (if it exists) + var config spriteConfig + config = loadConfig(name) + // Load frames // // NOTE(Jake): 2018-03-12 @@ -108,23 +153,20 @@ func debugWriteSprite(name string) { } imageSize := image.Bounds().Size() frame := spriteAssetFrame{ - Size: math.V(float64(imageSize.X), float64(imageSize.Y)), + Size: geom.Vec{float64(imageSize.X), float64(imageSize.Y)}, Data: buf.Bytes(), } frames = append(frames, frame) } - // Read config information (if it exists) - var config spriteConfig - configPath := folderPath + "config.json" - config = loadConfig(configPath) - // Create sprite asset := newSpriteAsset(name, frames, config) + // + // Write to file { - spritePath := file.AssetsDirectory + "/sprites/" + name + spritePath := file.AssetDirectory + "/" + SpriteDirectoryBase + "/" + name var data bytes.Buffer gob.NewEncoder(&data).Encode(asset) err := ioutil.WriteFile(spritePath+".data", data.Bytes(), 0644) diff --git a/gml/internal/sprite/sprite_manager_nondebug.go b/gml/internal/sprite/sprite_manager_nondebug.go index 7282b10..a5624cf 100644 --- a/gml/internal/sprite/sprite_manager_nondebug.go +++ b/gml/internal/sprite/sprite_manager_nondebug.go @@ -5,5 +5,8 @@ package sprite func DebugWatch() { } +func DebugWriteSpriteConfig(spr *Sprite) { +} + func debugWriteSprite(name string) { } diff --git a/gml/internal/sprite/sprite_state.go b/gml/internal/sprite/sprite_state.go index bbacc6f..eff96e7 100644 --- a/gml/internal/sprite/sprite_state.go +++ b/gml/internal/sprite/sprite_state.go @@ -1,23 +1,49 @@ package sprite import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) type SpriteState struct { - sprite *Sprite - ImageScale math.Vec - imageIndex float64 + spriteIndex SpriteIndex + ImageScale geom.Vec + imageIndex float64 } -func (state *SpriteState) Sprite() *Sprite { return state.sprite } -func (state *SpriteState) ImageIndex() float64 { return state.imageIndex } -func (state *SpriteState) ImageNumber() float64 { return float64(len(state.sprite.frames)) } -func (state *SpriteState) ImageSpeed() float64 { return state.sprite.imageSpeed } +func GetCollisionMask(spriteIndex SpriteIndex, imageIndex int, kind int) *CollisionMask { + spr := sprite(spriteIndex) + if spr == nil { + return nil + } + return &spr.frames[imageIndex].collisionMasks[kind] +} -func (state *SpriteState) SetSprite(sprite *Sprite) { - state.sprite = sprite - state.imageIndex = 0 +func (state *SpriteState) SpriteIndex() SpriteIndex { return state.spriteIndex } +func (state *SpriteState) sprite() SpriteIndex { return state.spriteIndex } +func (state *SpriteState) ImageIndex() float64 { return state.imageIndex } +func (state *SpriteState) ImageSpeed() float64 { + if state.spriteIndex == SprUndefined { + return 0 + } + spr := sprite(state.spriteIndex) + return spr.imageSpeed +} +func (state *SpriteState) ImageNumber() float64 { + if state.spriteIndex == SprUndefined { + return 0 + } + spr := sprite(state.spriteIndex) + return float64(len(spr.frames)) +} + +func (state *SpriteState) SetSprite(spriteIndex SpriteIndex) { + if state.spriteIndex != spriteIndex { + if !spriteIndex.IsLoaded() { + SpriteLoad(spriteIndex) + } + state.spriteIndex = spriteIndex + state.imageIndex = 0 + } } func (state *SpriteState) SetImageIndex(imageIndex float64) { diff --git a/gml/internal/timegml/now_js.go b/gml/internal/timegml/now_js.go new file mode 100644 index 0000000..0d83d53 --- /dev/null +++ b/gml/internal/timegml/now_js.go @@ -0,0 +1,14 @@ +// +build js + +package timegml + +import ( + "time" + + "github.com/gopherjs/gopherwasm/js" +) + +func Now() int64 { + // time.Now() is not reliable until GopherJS supports performance.now(). + return int64(js.Global().Get("performance").Call("now").Float() * float64(time.Millisecond)) +} diff --git a/gml/internal/timegml/now_notjs.go b/gml/internal/timegml/now_notjs.go new file mode 100644 index 0000000..1af04a4 --- /dev/null +++ b/gml/internal/timegml/now_notjs.go @@ -0,0 +1,11 @@ +// +build !js + +package timegml + +import ( + "time" +) + +func Now() int64 { + return time.Now().UnixNano() +} diff --git a/gml/internal/user/user.go b/gml/internal/user/user.go new file mode 100644 index 0000000..e19d1fc --- /dev/null +++ b/gml/internal/user/user.go @@ -0,0 +1,17 @@ +package user + +import ( + "os" + "runtime" +) + +func HomeDir() string { + if runtime.GOOS == "windows" { + home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + return home + } + return os.Getenv("HOME") +} diff --git a/gml/keyboard_vk.go b/gml/keyboard_vk.go index df33902..89caff4 100644 --- a/gml/keyboard_vk.go +++ b/gml/keyboard_vk.go @@ -35,6 +35,16 @@ const ( VkF10 VkF11 VkF12 + Vk0 + Vk1 + Vk2 + Vk3 + Vk4 + Vk5 + Vk6 + Vk7 + Vk8 + Vk9 VkNumpad0 VkNumpad1 VkNumpad2 @@ -45,6 +55,7 @@ const ( VkNumpad7 VkNumpad8 VkNumpad9 + VkNumpadEnter VkMultiply // multiply key on the numeric keypad VkDivide // divide key on the numeric keypad VkAdd // key on the numeric keypad diff --git a/gml/keyboard_vk_nonheadless.go b/gml/keyboard_vk_nonheadless.go index d2bdb1f..49988fa 100644 --- a/gml/keyboard_vk_nonheadless.go +++ b/gml/keyboard_vk_nonheadless.go @@ -41,16 +41,27 @@ var keyboardVkToEbiten = []ebiten.Key{ VkF10: ebiten.KeyF10, VkF11: ebiten.KeyF11, VkF12: ebiten.KeyF11, - VkNumpad0: ebiten.Key0, - VkNumpad1: ebiten.Key1, - VkNumpad2: ebiten.Key2, - VkNumpad3: ebiten.Key3, - VkNumpad4: ebiten.Key4, - VkNumpad5: ebiten.Key5, - VkNumpad6: ebiten.Key6, - VkNumpad7: ebiten.Key7, - VkNumpad8: ebiten.Key8, - VkNumpad9: ebiten.Key9, + Vk0: ebiten.Key0, + Vk1: ebiten.Key1, + Vk2: ebiten.Key2, + Vk3: ebiten.Key3, + Vk4: ebiten.Key4, + Vk5: ebiten.Key5, + Vk6: ebiten.Key6, + Vk7: ebiten.Key7, + Vk8: ebiten.Key8, + Vk9: ebiten.Key9, + VkNumpad0: ebiten.KeyKP0, + VkNumpad1: ebiten.KeyKP1, + VkNumpad2: ebiten.KeyKP2, + VkNumpad3: ebiten.KeyKP3, + VkNumpad4: ebiten.KeyKP4, + VkNumpad5: ebiten.KeyKP5, + VkNumpad6: ebiten.KeyKP6, + VkNumpad7: ebiten.KeyKP7, + VkNumpad8: ebiten.KeyKP8, + VkNumpad9: ebiten.KeyKP9, + VkNumpadEnter: ebiten.KeyKPEnter, VkMultiply: 0, // multiply key on the numeric keypad VkDivide: 0, // divide key on the numeric keypad VkAdd: 0, // key on the numeric keypad diff --git a/gml/main.go b/gml/main.go index 166424b..3cb5b56 100644 --- a/gml/main.go +++ b/gml/main.go @@ -1,7 +1,7 @@ package gml import ( - "github.com/silbinarywolf/gml-go/gml/internal/sprite" + "github.com/silbinarywolf/gml-go/gml/internal/timegml" ) type mainFunctions struct { @@ -12,35 +12,84 @@ type mainFunctions struct { var gMainFunctions *mainFunctions = new(mainFunctions) var ( - gWidth int - gHeight int + gWindowWidth int + gWindowHeight int + gWindowScale float64 // Window scale ) func update() error { - sprite.DebugWatch() + frameStartTime := timegml.Now() keyboardUpdate() keyboardStringUpdate() mouseUpdate() - if EditorIsActive() { - EditorUpdate() - EditorDraw() - } else { + + debugUpdate() + + switch debugMenuID { + case debugMenuNone: gMainFunctions.update() + case debugMenuRoomEditor: + cameraSetActive(0) + cameraClear(0) + + editorLazyInit() + editorUpdate() + + cameraDraw(0) + cameraClearActive() + case debugMenuAnimationEditor: + cameraSetActive(0) + cameraClear(0) + + animationEditorUpdate() + + cameraDraw(0) + cameraClearActive() + default: + panic("Invalid debug mode.") } if g_game.hasGameRestarted { + panic("todo: Fix / test this. I assume its broken") gState.globalInstances.reset() gMainFunctions.gameStart() g_game.hasGameRestarted = false } + + // NOTE(Jake): 2018-09-29 + // Ignoring when 0 is reported. This happens on Windows + // and just makes the frame usage timer annoying. + frameBudgetUsed := timegml.Now() - frameStartTime + if frameBudgetUsed > 0 { + gState.frameBudgetNanosecondsUsed = frameBudgetUsed + } return nil } +func WindowWidth() int { + return gWindowWidth +} + +func WindowHeight() int { + return gWindowHeight +} + +func WindowScale() float64 { + return gWindowScale +} + +// todo: replace windowWidth() with WindowWidth() func windowWidth() int { - return gWidth + return gWindowWidth } +// todo: replace windowHeight() with WindowHeight() func windowHeight() int { - return gHeight + return gWindowHeight +} + +// todo: replace windowScale() with WindowScale() +func windowScale() float64 { + return gWindowScale } func Update(animationUpdate bool) { diff --git a/gml/main_headless.go b/gml/main_headless.go index 81cb583..f03e600 100644 --- a/gml/main_headless.go +++ b/gml/main_headless.go @@ -8,9 +8,10 @@ func Draw() { // no-op } -func Run(gameStartFunc func(), updateFunc func(), width int, height int, title string) { - gWidth = width - gHeight = height +func Run(gameStartFunc func(), updateFunc func(), width int, height int, scale float64, title string) { + gWindowWidth = width + gWindowHeight = height + gWindowScale = scale gMainFunctions.gameStart = gameStartFunc gMainFunctions.update = updateFunc @@ -22,6 +23,16 @@ func Run(gameStartFunc func(), updateFunc func(), width int, height int, title s select { case <-tick: updateFunc() + // todo(Jake): 2018-07-10 + // + // Should improve this to be more robust! + // - https://trello.com/c/1RUkMGOx/55-improve-clock-for-headless-mode + // + // However, I'm deferring this effort as the way Ebiten works might change + // in the future: + // - https://github.com/hajimehoshi/ebiten/issues/605 + // + // time.Sleep(time.Second / 60) } } } diff --git a/gml/main_nonheadless.go b/gml/main_nonheadless.go index 6c3944a..afdc303 100644 --- a/gml/main_nonheadless.go +++ b/gml/main_nonheadless.go @@ -19,14 +19,15 @@ func Draw() { gState.draw() } -func Run(gameStartFunc func(), updateFunc func(), width int, height int, title string) { - gWidth = width - gHeight = height +func Run(gameStartFunc func(), updateFunc func(), width int, height int, scale float64, title string) { + gWindowWidth = width + gWindowHeight = height + gWindowScale = scale gMainFunctions.gameStart = gameStartFunc gMainFunctions.update = updateFunc gMainFunctions.gameStart() ebiten.SetRunnableInBackground(true) - ebiten.Run(updateEbiten, width, height, 2, title) + ebiten.Run(updateEbiten, width, height, scale, title) } diff --git a/gml/math.go b/gml/math.go deleted file mode 100644 index 88e39d5..0000000 --- a/gml/math.go +++ /dev/null @@ -1,13 +0,0 @@ -package gml - -import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" -) - -type Vec = math.Vec - -type Size = math.Size - -func V(x float64, y float64) math.Vec { - return math.Vec{X: x, Y: y} -} diff --git a/gml/mouse_buttons_headless.go b/gml/mouse_buttons_headless.go index 777a89c..b30a4dc 100644 --- a/gml/mouse_buttons_headless.go +++ b/gml/mouse_buttons_headless.go @@ -6,5 +6,5 @@ const ( MbLeft int = iota + 0 MbRight MbMiddle - mbSize + MbSize ) diff --git a/gml/mouse_buttons_nonheadless.go b/gml/mouse_buttons_nonheadless.go index 36b1b9a..bc967ff 100644 --- a/gml/mouse_buttons_nonheadless.go +++ b/gml/mouse_buttons_nonheadless.go @@ -8,5 +8,5 @@ const ( MbLeft int = iota + int(ebiten.MouseButtonLeft) MbRight = int(ebiten.MouseButtonRight) MbMiddle = int(ebiten.MouseButtonMiddle) - mbSize + MbSize = int(ebiten.MouseButtonMiddle) + 1 ) diff --git a/gml/mouse_headless.go b/gml/mouse_headless.go index 04df0f4..8f3e2a6 100644 --- a/gml/mouse_headless.go +++ b/gml/mouse_headless.go @@ -3,7 +3,7 @@ package gml import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) func MouseCheckButton(button int) bool { @@ -15,11 +15,15 @@ func MouseCheckPressed(button int) bool { } func MousePosition() Vec { - return math.V(0, 0) + return geom.Vec{} } -func mousePosition() math.Vec { - return math.V(0, 0) +func mousePosition() geom.Vec { + return geom.Vec{} +} + +func mouseScreenPosition() Vec { + return geom.Vec{} } func mouseUpdate() { diff --git a/gml/mouse_nonheadless.go b/gml/mouse_nonheadless.go index e02a4ca..187d044 100644 --- a/gml/mouse_nonheadless.go +++ b/gml/mouse_nonheadless.go @@ -3,14 +3,14 @@ package gml import ( - "github.com/silbinarywolf/gml-go/gml/internal/math" - "github.com/hajimehoshi/ebiten" + "github.com/silbinarywolf/gml-go/gml/internal/geom" ) var ( - pressingMouseButtonLastFrame [mbSize]bool - _mousePos math.Vec + mouseButtonPress [MbSize]int // this array is reset every frame + pressingMouseButtonLastFrame [MbSize]bool + _mousePos geom.Vec ) func MouseCheckButton(button int) bool { @@ -18,7 +18,8 @@ func MouseCheckButton(button int) bool { } func MouseCheckPressed(button int) bool { - isHeld := MouseCheckButton(button) + return mouseButtonPress[button] == 1 + /*isHeld := MouseCheckButton(button) if !isHeld { pressingMouseButtonLastFrame[button] = false } @@ -28,30 +29,36 @@ func MouseCheckPressed(button int) bool { if isHeld { pressingMouseButtonLastFrame[button] = true } - return isHeld + return isHeld*/ } -func MousePosition() Vec { - return mousePosition() +func MousePosition() geom.Vec { + return _mousePos } -/*func MouseX() float64 { - x, _ := ebiten.CursorPosition() - return float64(x) +// Get the mouse position relative to the window +func mouseScreenPosition() geom.Vec { + x, y := ebiten.CursorPosition() + return geom.Vec{float64(x), float64(y)} } -func MouseY() float64 { - _, y := ebiten.CursorPosition() - return float64(y) +// +// NOTE(Jake): 2018-07-10 +// +// Ebiten doesn't have mouseWheel() support on a stable version yet and +// it doesn't support browser mouse wheel. +// - https://github.com/hajimehoshi/ebiten/issues/630 +// +// I'll look into this later! +// +/*func mouseWheel() geom.Vec { + xoff, yoff := ebiten.MouseWheel() + return geom.V(xoff, yoff) }*/ -func mousePosition() math.Vec { - return _mousePos -} - func mouseUpdate() { x, y := ebiten.CursorPosition() - newPos := math.V(float64(x), float64(y)) + newPos := geom.Vec{float64(x), float64(y)} // NOTE(Jake): 2018-06-09 // @@ -64,11 +71,20 @@ func mouseUpdate() { // // This is future-me's problem though! // - cam := &cameraList[0] - newPos.X += cam.X - newPos.Y += cam.Y + viewPos := CameraGetViewPos(0) + newPos.X += viewPos.X + newPos.Y += viewPos.Y _mousePos = newPos + + // Add code to check mouse inputs + for btn := MbLeft; btn < MbSize; btn++ { + if MouseCheckButton(btn) { + mouseButtonPress[btn]++ + } else { + mouseButtonPress[btn] = 0 + } + } } //mouse_check_button_pressed diff --git a/gml/object.go b/gml/object.go index bd6a3e7..7480f27 100644 --- a/gml/object.go +++ b/gml/object.go @@ -15,6 +15,7 @@ func ObjectGetIndex(name string) (object.ObjectIndex, bool) { return res, ok } -func ObjectInitTypes(objTypes []object.ObjectType) { - object.InitTypes(objTypes) +// ObjectInitTypes is required to be called so the engine can create game objects +func ObjectInitTypes(objectTypeToData []object.ObjectType, objectIndexList []object.ObjectIndex) { + object.InitTypes(objectTypeToData, objectIndexList) } diff --git a/gml/main_test.go b/gml/object_test.go similarity index 82% rename from gml/main_test.go rename to gml/object_test.go index 615e4f4..c0fbdc1 100644 --- a/gml/main_test.go +++ b/gml/object_test.go @@ -17,15 +17,11 @@ func (_ *DummyPlayer) ObjectName() string { return "DummyPlayer" } func (inst *DummyPlayer) Create() { inst.Size.X = 32 inst.Size.Y = 32 + inst.SetSolid(true) } func (_ *DummyPlayer) Update() {} -func (_ *DummyPlayer) Draw() {} +func (_ *DummyPlayer) Destroy() {} -func init() { - // Setup - ObjectInitTypes([]ObjectType{ - ObjDummyPlayer: new(DummyPlayer), - }) -} +func (_ *DummyPlayer) Draw() {} diff --git a/gml/room.go b/gml/room.go index b94ef7f..023a274 100644 --- a/gml/room.go +++ b/gml/room.go @@ -4,10 +4,10 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/room" ) +// todo(Jake): 2018-12-01 +// Remove this, change LoadRoom functions to return gml.RoomIndex type Room = room.Room -type RoomObject = room.RoomObject - func LoadRoom(name string) *Room { return room.LoadRoom(name) } diff --git a/gml/room_editor.go b/gml/room_editor.go index 9c4eb6c..22ed786 100644 --- a/gml/room_editor.go +++ b/gml/room_editor.go @@ -2,41 +2,20 @@ package gml -import ( - m "github.com/silbinarywolf/gml-go/gml/internal/math" - "github.com/silbinarywolf/gml-go/gml/internal/object" -) - func roomEditorUsername() string { return "" } -func EditorInit() { -} - +/* func EditorIsInitialized() bool { return false } -func EditorIsActive() bool { - return false -} - func EditorSetRoom(room *Room) { -} - -func EditorAddInstance(pos m.Vec, objectIndex object.ObjectIndex) *RoomObject { - return nil -} - -func EditorRemoveInstance(index int) { -} - -func EditorUpdate() { -} +}*/ -func EditorDraw() { +func editorLazyInit() { } -func EditorSave() { +func editorUpdate() { } diff --git a/gml/room_editor_debug.go b/gml/room_editor_debug.go index 23644fa..217c480 100644 --- a/gml/room_editor_debug.go +++ b/gml/room_editor_debug.go @@ -4,62 +4,177 @@ package gml import ( "bufio" + "encoding/json" + "fmt" "image/color" + "io/ioutil" "math" "os" + "path/filepath" + "sort" "strconv" "strings" + "time" "github.com/silbinarywolf/gml-go/gml/internal/file" - m "github.com/silbinarywolf/gml-go/gml/internal/math" + "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/object" + "github.com/silbinarywolf/gml-go/gml/internal/reditor" + "github.com/silbinarywolf/gml-go/gml/internal/room" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) +// +// NOTE(Jake): 2018-07-10 +// +// I'd like to split this out into its own package but before I can +// do that I need to figure out: +// - How I'll access Mouse / Keyboard inputs +// - Split out camera.go into an internal package +// +// + +//type roomInfo struct { +// *room.Room +//} + type roomEditor struct { - initialized bool - editingRoom *Room + spriteViewer debugSpriteViewer + + initialized bool + editingRoom *room.Room + editingLayer room.RoomLayer + objectIndexToData []object.ObjectType + //spriteList []*sprite.Sprite + //spriteMap map[string]*sprite.Sprite - camPos Vec - lastMousePos Vec + camPos Vec + lastMousePos Vec + lastMouseScreenPos Vec + + menuOpened reditor.Menu + menuLayerKind room.RoomLayerKind + hasUnsavedChanges bool - isEntityMenuOpen bool entityMenuFiltered []object.ObjectType - objectSelected object.ObjectType + //spriteMenuFiltered []*sprite.Sprite + + objectSelected object.ObjectType + spriteSelected SpriteIndex + + mouseHold [MbSize]bool + gridEnabled bool + + cameraStateBeforeEnteringEditingMode cameraManager + + // Callbacks + //exitEditorFunc func(room *room.Room) + + // + statusText string + statusTimer time.Time + + // Constants + roomDirectory string + + // Backing / temporary pools + tempLayers []room.RoomLayer +} + +type roomEditorConfig struct { + RoomSelected string `json:"RoomSelected,omitempty"` + LayerSelected string `json:"LayerSelected,omitempty"` + BrushSelected string `json:"BrushSelected,omitempty"` } +var ( + gRoomEditor *roomEditor +) + func newRoomEditor() *roomEditor { - // Create stub instances to use for rendering map view - idToEntityData := object.IDToEntityData() - objectIndexToData := make([]object.ObjectType, len(idToEntityData)) - for i, obj := range idToEntityData { - if obj == nil { - continue - } - objectIndex := obj.ObjectIndex() - inst := object.NewRawInstance(objectIndex, i, 0, new(object.Space), -1) + // NOTE(Jake): 2018-07-11 + // + // Create stub instances to use for rendering map view. + // + // This provides us: + // - The entity size (as set in Create()) + // - The default sprite of the object + // + objectIndexList := object.ObjectIndexList() + objectIndexToData := make([]object.ObjectType, len(objectIndexList)) + for i, objectIndex := range objectIndexList { + inst := object.NewRawInstance(objectIndex, i, 0, 0) inst.Create() objectIndexToData[i] = inst } return &roomEditor{ - initialized: true, - //editingRoom: nil, - objectIndexToData: objectIndexToData, + initialized: true, + objectIndexToData: objectIndexToData, + //objectNameToData: objectNameToData, + //spriteList: spriteList, + //spriteMap: spriteMap, lastMousePos: MousePosition(), entityMenuFiltered: make([]object.ObjectType, 0, len(objectIndexToData)), + //spriteMenuFiltered: make([]*sprite.Sprite, 0, len(spriteList)), + roomDirectory: file.AssetDirectory + "/" + room.RoomDirectoryBase + "/", + tempLayers: make([]room.RoomLayer, 0, 25), + gridEnabled: false, } } -var ( - gRoomEditor *roomEditor -) +func (editor *roomEditor) IsMenuOpen() bool { + return editor.menuOpened != reditor.MenuNone +} + +func (editor *roomEditor) calculateAndSortLayers() { + if editor.editingRoom == nil { + return + } + editor.tempLayers = editor.tempLayers[:0] + editor.layers() +} + +func (editor *roomEditor) MouseClearButton(mb int) { + editor.mouseHold[mb] = false +} + +func (editor *roomEditor) MouseCheckButton(mb int) bool { + return editor.mouseHold[mb] +} + +func (editor *roomEditor) layers() []room.RoomLayer { + // NOTE(Jake): 2018-08-05 + // + // At the start of each EditorUpdate() / frame, we reset + // the tempLayers length to 0 so this will only be calculated once + // per frame. + // + if len(editor.tempLayers) > 0 { + return editor.tempLayers + } + editingRoom := editor.editingRoom -func roomEditorUsername() string { - return file.DebugUsernameFileSafe() + // Put all layer types into one array and sort + layers := editor.tempLayers[:0] + for _, layer := range editingRoom.InstanceLayers { + layers = append(layers, layer) + } + for _, layer := range editingRoom.BackgroundLayers { + layers = append(layers, layer) + } + for _, layer := range editingRoom.SpriteLayers { + layers = append(layers, layer) + } + sort.Slice(layers, func(i, j int) bool { + return layers[i].GetConfig().Order < layers[j].GetConfig().Order + }) + editor.tempLayers = layers + return layers } -func roomEditorEditingRoom() *Room { +func roomEditorEditingRoom() *room.Room { return gRoomEditor.editingRoom } @@ -73,135 +188,269 @@ func snapToGrid(val float64, grid float64) float64 { return base * grid } -func EditorInit() { - if gRoomEditor != nil { - panic("EditorInit: Room Editor is already initialized.") +func editorLazyInit() { + if gRoomEditor == nil { + gRoomEditor = newRoomEditor() } - gRoomEditor = newRoomEditor() + // TODO(Jake): 2018-07-10 + // + // Load editor font (possibly by embedding data into `reditor`?) + // } -func EditorIsInitialized() bool { +/*func EditorIsInitialized() bool { return gRoomEditor != nil } -func EditorIsActive() bool { - return gRoomEditor != nil && roomEditorEditingRoom() != nil -} - func EditorSetRoom(room *Room) { - gRoomEditor.editingRoom = room - - // - CameraSetEnabled(0) - CameraSetViewSize(0, V(float64(windowWidth()), float64(windowHeight()))) + roomEditor := gRoomEditor + if roomEditor.editorChangeRoom(room) { + roomEditor.editorConfigLoad() + debugMenuOpenOrToggleClosed(debugMenuRoomEditor) + } } - -func EditorAddInstance(pos Vec, objectIndex object.ObjectIndex) *RoomObject { - room := roomEditorEditingRoom() +*/ +func (roomEditor *roomEditor) editorChangeRoom(room *Room) bool { + if roomEditor.editingRoom == room { + // If no changes + return false + } if room == nil { - return nil + roomEditor.editingRoom = nil + // Reset camera settings back + *gCameraManager = roomEditor.cameraStateBeforeEnteringEditingMode + debugMenuOpenOrToggleClosed(debugMenuNone) + // Execute custom user-code logic + //if gRoomEditor.exitEditorFunc != nil { + // gRoomEditor.exitEditorFunc(editingRoom) + //} + return false } - count := room.UserEntityCount - room.UserEntityCount++ - - // Get unique username - username := roomEditorUsername() + roomEditor.editingRoom = room + roomEditor.editingLayer = nil + roomEditor.cameraStateBeforeEnteringEditingMode = *gCameraManager + roomEditor.calculateAndSortLayers() + roomEditor.calculateRoomBounds() + // NOTE(Jake): 2018-07-09 + // + // If you move around as the player a bit then go into the + // editor. Retain the same camera position. // - //inst := roomEditor.objectIndexToData[objectIndex] - //baseObj := inst.BaseObject() - roomObj := &RoomObject{ - Filename: "entity_" + username + "_" + strconv.FormatInt(count, 10), - ObjectIndex: int32(objectIndex), - X: int32(pos.X), - Y: int32(pos.Y), - } - room.Instances = append(room.Instances, roomObj) - return roomObj + roomEditor.camPos = CameraGetViewPos(0) + CameraSetViewSize(0, geom.Vec{float64(windowWidth()), float64(windowHeight())}) + CameraSetViewTarget(0, nil) + return true } -func EditorRemoveInstance(index int) { - room := roomEditorEditingRoom() - - // Unordered delete instance - entryBeingDeleted := room.Instances[index] - lastEntry := room.Instances[len(room.Instances)-1] - room.Instances[index] = lastEntry - room.Instances = room.Instances[:len(room.Instances)-1] - - // Track deleted entities - room.DeletedInstances = append(room.DeletedInstances, entryBeingDeleted) -} +func editorUpdate() { + roomEditor := gRoomEditor + roomEditor.calculateAndSortLayers() // reset layers / recalculate sort order lazily with layers() + isMenuOpen := roomEditor.IsMenuOpen() + canUseBrush := true + grid := geom.Vec{32, 32} -func EditorUpdate() { - room := roomEditorEditingRoom() - if room == nil { - return + // Setup mouse left, this is so we can force it to false to disable brush strokes (ie. for UI clicks) + if MouseCheckPressed(MbLeft) { + roomEditor.mouseHold[MbLeft] = true + } + if !MouseCheckButton(MbLeft) { + roomEditor.mouseHold[MbLeft] = false + } + if MouseCheckPressed(MbMiddle) { + roomEditor.mouseHold[MbMiddle] = true + } + if !MouseCheckButton(MbMiddle) { + roomEditor.mouseHold[MbMiddle] = false + } + if MouseCheckPressed(MbRight) { + roomEditor.mouseHold[MbRight] = true + } + if !MouseCheckButton(MbRight) { + roomEditor.mouseHold[MbRight] = false } - roomEditor := gRoomEditor - isMenuOpen := roomEditor.isEntityMenuOpen - // NOTE(Jake): 2018-06-04 - // - // A hack to set the camera context - // - cameraSetActive(0) - defer cameraClearActive() + // Remove status text if time passed + if roomEditor.statusText != "" && + time.Since(roomEditor.statusTimer).Seconds() > 5 { + roomEditor.statusText = "" + } isHoldingControl := KeyboardCheck(VkControl) if isHoldingControl { - // Open entity select menu - if KeyboardCheckPressed(VkP) { - roomEditor.isEntityMenuOpen = !roomEditor.isEntityMenuOpen - if roomEditor.isEntityMenuOpen { + // Enable / disable grid + if KeyboardCheckPressed(VkG) { + roomEditor.gridEnabled = !roomEditor.gridEnabled + } + + switch l := roomEditor.editingLayer.(type) { + case nil: + // no-op + if KeyboardCheckPressed(VkP) { + roomEditor.setStatusText("No layer selected. Cannot perform CTRL+P action.") + } + case *room.RoomLayerInstance: + // Open entity select menu + if KeyboardCheckPressed(VkP) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuEntity + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } + + // Open order set menu + if KeyboardCheckPressed(VkO) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuSetOrder + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } + case *room.RoomLayerBackground: + // Open background select menu + if KeyboardCheckPressed(VkP) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuBackground + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } + + // Open order set menu + if KeyboardCheckPressed(VkO) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuSetOrder + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } + case *room.RoomLayerSprite: + // Open background select menu + if KeyboardCheckPressed(VkP) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuSprite + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } + + // Open order set menu + if KeyboardCheckPressed(VkO) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuSetOrder + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } + default: + panic(fmt.Sprintf("CTRL+Key: Unimplemented layer type: %T", l)) + } + + // Exit editor + if KeyboardCheckPressed(VkR) { + roomEditor.editorChangeRoom(nil) + return + } + + // Load map + if KeyboardCheckPressed(VkL) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuLoadRoom + ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone + } + } + + // New map + if KeyboardCheckPressed(VkN) { + if roomEditor.menuOpened == reditor.MenuNone { + roomEditor.menuOpened = reditor.MenuNewRoom ClearKeyboardString() + } else { + roomEditor.menuOpened = reditor.MenuNone } } // Save if KeyboardCheckPressed(VkS) { - EditorSave() - println("Saved room:", room.Filepath) + editorSave() } } // Close open menus if KeyboardCheckPressed(VkEscape) { - roomEditor.isEntityMenuOpen = false + roomEditor.menuOpened = reditor.MenuNone } // Handle filtering / selection - if roomEditor.isEntityMenuOpen { - typingText := KeyboardString() - roomEditor.entityMenuFiltered = roomEditor.entityMenuFiltered[:0] - for _, obj := range roomEditor.objectIndexToData { - if obj == nil { - continue - } - name := obj.ObjectName() - hasMatch := typingText == "" || - strings.Contains(name, typingText) - if !hasMatch { - continue - } - obj.BaseObject().ImageUpdate() - roomEditor.entityMenuFiltered = append(roomEditor.entityMenuFiltered, obj) - } + switch roomEditor.menuOpened { + case reditor.MenuNewRoom, + reditor.MenuLoadRoom, + reditor.MenuNewLayer, + reditor.MenuSetOrder: + if inputSelectPressed() { + if typingText := KeyboardString(); len(typingText) > 0 { + switch roomEditor.menuOpened { + case reditor.MenuNewRoom: + // Create new room if typed + roomEditor.newRoom(typingText) + case reditor.MenuLoadRoom: + // Load room if typed + roomEditor.loadRoom(typingText) + case reditor.MenuNewLayer: + // Create new room if typed + roomEditor.newLayerAndSelected(roomEditor.editingRoom, typingText, roomEditor.menuLayerKind) + case reditor.MenuSetOrder: + // + layer := roomEditor.editingLayer + if layer == nil { + roomEditor.setStatusText("Cannot open set order menu when not editing any layer.") + break + } + order, err := strconv.Atoi(typingText) + if err != nil { + roomEditor.setStatusText("Cannot set order to invalid value: " + typingText) + break + } + layer.GetConfig().Order = int32(order) + sort.Slice(roomEditor.editingRoom.InstanceLayers, func(i, j int) bool { + return roomEditor.editingRoom.InstanceLayers[i].Config.Order < roomEditor.editingRoom.InstanceLayers[j].Config.Order + }) + sort.Slice(roomEditor.editingRoom.BackgroundLayers, func(i, j int) bool { + return roomEditor.editingRoom.BackgroundLayers[i].Config.Order < roomEditor.editingRoom.BackgroundLayers[j].Config.Order + }) + sort.Slice(roomEditor.editingRoom.SpriteLayers, func(i, j int) bool { + return roomEditor.editingRoom.SpriteLayers[i].Config.Order < roomEditor.editingRoom.SpriteLayers[j].Config.Order + }) + roomEditor.hasUnsavedChanges = true + default: + panic("Unhandled menu type (ie. new room, new layer, set order)") + } - // - if KeyboardCheckPressed(VkEnter) && - len(roomEditor.entityMenuFiltered) > 0 { - selectedObj := roomEditor.entityMenuFiltered[0] - roomEditor.objectSelected = selectedObj - roomEditor.isEntityMenuOpen = false + } + // Close menu + roomEditor.menuOpened = reditor.MenuNone } } + /*editingRoom := roomEditor.editingRoom + if editingRoom == nil { + return + }*/ + if !isMenuOpen && !isHoldingControl { + camPos := &roomEditor.camPos { - // Move camera - camPos := &roomEditor.camPos + // Move camera with WASD var speed float64 = 4 if KeyboardCheck(VkShift) { speed = 8 @@ -219,261 +468,1579 @@ func EditorUpdate() { CameraSetViewPos(0, *camPos) } - lastMousePos := roomEditor.lastMousePos - roomEditor.lastMousePos = MousePosition() - - grid := V(32, 32) - - // Left click - if MouseCheckButton(MbLeft) && - roomEditor.objectSelected != nil { - objectIndexSelected := roomEditor.objectSelected.ObjectIndex() - // NOTE(Jake): 2018-06-10 - // - // We need to handle mouse click between the last mouse position - // and current mouse position so that there are no gaps when you're - // dragging the mouse across long distances. - // - rect := m.R(MousePosition(), lastMousePos) - for x := rect.Left(); x <= rect.Right(); x += grid.X { - for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { - mousePos := V(x, y) - - // Check to make sure we aren't placing over the top - // of an existing entity - hasCollision := false - for _, obj := range room.Instances { + lastMouseScreenPos := roomEditor.lastMouseScreenPos + roomEditor.lastMouseScreenPos = mouseScreenPosition() + + { + // Move camera with middle mouse + if MouseCheckButton(MbMiddle) { + mouseDistMoved := mouseScreenPosition() + mouseDistMoved.X -= lastMouseScreenPos.X + mouseDistMoved.Y -= lastMouseScreenPos.Y + sensitivity := -1.0 + if mouseDistMoved.X > 0 { + camPos.X += mouseDistMoved.X * sensitivity + } else if mouseDistMoved.X < 0 { + camPos.X += mouseDistMoved.X * sensitivity + } + if mouseDistMoved.Y > 0 { + camPos.Y += mouseDistMoved.Y * sensitivity + } else if mouseDistMoved.Y < 0 { + camPos.Y += mouseDistMoved.Y * sensitivity + } + CameraSetViewPos(0, *camPos) + } + } + } + + lastMousePos := roomEditor.lastMousePos + roomEditor.lastMousePos = MousePosition() + + // Draw + { + cameraSize := cameraGetActive().size + + { + // Fill screen with gray + DrawSetGUI(true) + DrawRectangle(geom.Vec{0, 0}, cameraSize, color.RGBA{153, 153, 153, 255}) + DrawSetGUI(false) + + // Draw black box using room bounds + if editingRoom := roomEditor.editingRoom; editingRoom != nil { + borderWidth := 2.0 + pos := geom.Vec{float64(editingRoom.Left), float64(editingRoom.Top)} + pos.X -= borderWidth + pos.Y -= borderWidth + size := geom.Vec{float64(editingRoom.Right - editingRoom.Left), float64(editingRoom.Bottom - editingRoom.Top)} + size.X += borderWidth * 2 + size.Y += borderWidth * 2 + DrawRectangleBorder(pos, size, color.Black, 2, color.White) + } + } + + // Draw layers + if editingRoom := roomEditor.editingRoom; editingRoom != nil { + for _, layer := range roomEditor.layers() { + switch layer := layer.(type) { + case *room.RoomLayerInstance: + // Draw room instances + for _, obj := range layer.Instances { inst := roomEditorObjectIndexToData(obj.ObjectIndex) if inst == nil { continue } - pos := V(float64(obj.X), float64(obj.Y)) - size := inst.BaseObject().Size - left := pos.X - right := left + float64(size.X) - top := pos.Y - bottom := top + float64(size.Y) - if mousePos.X >= left && mousePos.X < right && - mousePos.Y >= top && mousePos.Y < bottom { - hasCollision = true + baseObj := inst.BaseObject() + baseObj.X = float64(obj.X) + baseObj.Y = float64(obj.Y) + DrawSpriteScaled(baseObj.SpriteIndex(), 0, baseObj.Pos(), baseObj.ImageScale) + } + case *room.RoomLayerBackground: + if layer.SpriteName == "" { + // If no sprite, don't draw anything + break + } + // Draw bg + sprite := sprite.SpriteLoadByName(layer.SpriteName) + x := float64(layer.X) + y := float64(layer.Y) + width := float64(sprite.Size().X) + DrawSprite(sprite, 0, geom.Vec{x, y}) + { + // Tile left + x := x + for x > float64(editingRoom.Left) { + x -= width + DrawSprite(sprite, 0, geom.Vec{x, y}) } } + { + // Tile left + x := x + for x < float64(editingRoom.Right) { + x += width + DrawSprite(sprite, 0, geom.Vec{x, y}) + } + } + case *room.RoomLayerSprite: + // Draw room sprites + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite := sprite.SpriteLoadByName(spriteName) + DrawSprite(sprite, 0, geom.Vec{float64(obj.X), float64(obj.Y)}) + } + default: + panic(fmt.Sprintf("Unhandled type: %T", layer)) + } + } + // Old render code (before layers) + /*instances := editingRoom.Instances + for _, obj := range instances { + inst := roomEditorObjectIndexToData(obj.ObjectIndex) + if inst == nil { + continue + } + baseObj := inst.BaseObject() + baseObj.X = float64(obj.X) + baseObj.Y = float64(obj.Y) + inst.Draw() + }*/ + } - // - if !hasCollision { - // Snap to grid - mousePos.X = snapToGrid(mousePos.X, grid.X) - mousePos.Y = snapToGrid(mousePos.Y, grid.Y) + if canUseBrush && + !isMenuOpen { + // Place tile + switch layer := roomEditor.editingLayer.(type) { + case nil: + // no-op + case *room.RoomLayerInstance: + // Draw selected tile + if selectedObj := roomEditor.objectSelected; selectedObj != nil { + mousePos := MousePosition() + mousePos.X = snapToGrid(mousePos.X, grid.X) + mousePos.Y = snapToGrid(mousePos.Y, grid.Y) - roomObj := EditorAddInstance(mousePos, objectIndexSelected) - println("Create entity:", roomObj.String()) + // Draw + drawObject(selectedObj, mousePos) + } + case *room.RoomLayerSprite: + // Draw selected sprite + if selectedBrush := roomEditor.spriteSelected; selectedBrush.IsValid() { + pos := MousePosition() + if isHoldingControl { + maybeNewPos, ok := roomEditor.getSnapPosition(pos, selectedBrush.Size(), layer) + if ok { + pos = maybeNewPos + DrawSpriteExt(selectedBrush, 0, pos, geom.Vec{1, 1}, 0.85) + } + } else { + // Grid mode + pos.X = snapToGrid(pos.X, grid.X) + pos.Y = snapToGrid(pos.Y, grid.Y) + DrawSpriteExt(selectedBrush, 0, pos, geom.Vec{1, 1}, 0.85) } } + case *room.RoomLayerBackground: + // no-op + default: + panic(fmt.Sprintf("Unimplemented layer type: %T", layer)) } } - // Holding Right click - if MouseCheckButton(MbRight) { - // NOTE(Jake): 2018-06-10 - // - // We need to handle mouse click between the last mouse position - // and current mouse position so that there are no gaps when you're - // dragging the mouse across long distances. - // - rect := m.R(MousePosition(), lastMousePos) - for x := rect.Left(); x <= rect.Right(); x += grid.X { - for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { - mousePos := V(x, y) + if !isMenuOpen { + // Draw grid + if !isHoldingControl && + roomEditor.gridEnabled { + DrawSetGUI(true) + cameraPos := CameraGetViewPos(0) + windowWidth := float64(windowWidth()) + windowHeight := float64(windowHeight()) + xOffset := math.Mod(cameraPos.X, grid.X) + yOffset := math.Mod(cameraPos.Y, grid.Y) + for y := 0.0; y < windowHeight+grid.Y; y += grid.Y { + DrawRectangle(geom.Vec{0, y - yOffset}, geom.Vec{windowWidth + grid.X, 1}, color.White) + } + for x := 0.0; x < windowWidth+grid.X; x += grid.X { + DrawRectangle(geom.Vec{x - xOffset, 0}, geom.Vec{1, windowHeight + grid.Y}, color.White) + } + DrawSetGUI(false) + } + } - for i, obj := range room.Instances { - inst := roomEditorObjectIndexToData(obj.ObjectIndex) - if inst == nil { - continue + if roomEditor.menuOpened == reditor.MenuNone { + // Draw layer widget + if roomEditor.editingRoom != nil { + DrawSetGUI(true) + yStart := 0.0 + var x, y, width, height float64 + x = 0 + y = yStart + width = 320 + height = float64(len(roomEditor.layers())+1)*32.0 + 16 + DrawRectangle(geom.Vec{x, y}, geom.Vec{width, height}, color.RGBA{0, 0, 0, 220}) + y += 24 + DrawText(geom.Vec{x + 16, y}, "Layers:") + y += 16 + for _, layer := range roomEditor.layers() { + config := layer.GetConfig() + layerName := config.Name + layerType := "Unknown" + switch layer.(type) { + case *room.RoomLayerInstance: + layerType = "Instance" + case *room.RoomLayerBackground: + layerType = "Background" + case *room.RoomLayerSprite: + layerType = "Sprite" + default: + layerType = "Unhandled case" + } + layerText := layerName + " - " + layerType + " Layer (Order: " + strconv.Itoa(int(config.Order)) + ")" + if layer == roomEditor.editingLayer { + layerText += " [Selected]" + } + if drawTextButton(geom.Vec{x, y}, layerText) { + if roomEditor.editingLayer == layer { + roomEditor.editingLayer = nil + } else { + roomEditor.editingLayer = layer + } + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + } + y = yStart + height + switch layer := roomEditor.editingLayer.(type) { + case *room.RoomLayerSprite: + text := "Collision OFF" + if layer.Config.HasCollision { + text = "Collision ON" + } + if drawButton(geom.Vec{x, y}, text) { + layer.Config.HasCollision = !layer.Config.HasCollision + roomEditor.hasUnsavedChanges = true + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + } + if drawButton(geom.Vec{x, y}, "Add Instance Layer") { + ClearKeyboardString() + roomEditor.menuOpened = reditor.MenuNewLayer + roomEditor.menuLayerKind = room.RoomLayerKind_Instance + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + if drawButton(geom.Vec{x, y}, "Add Background Layer") { + ClearKeyboardString() + roomEditor.menuOpened = reditor.MenuNewLayer + roomEditor.menuLayerKind = room.RoomLayerKind_Background + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + if drawButton(geom.Vec{x, y}, "Add Sprite Layer") { + ClearKeyboardString() + roomEditor.menuOpened = reditor.MenuNewLayer + roomEditor.menuLayerKind = room.RoomLayerKind_Sprite + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + text := "Grid OFF" + if roomEditor.gridEnabled { + text = "Grid ON" + } + if drawButton(geom.Vec{x, y}, text) { + roomEditor.gridEnabled = !roomEditor.gridEnabled + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + if layer := roomEditor.editingLayer; layer != nil { + if drawButton(geom.Vec{x, y}, "Delete Selected Layer") { + switch layer := layer.(type) { + case *room.RoomLayerInstance: + layerName := layer.Config.Name + layerUUID := layer.Config.UUID + didDelete := false + records := roomEditor.editingRoom.InstanceLayers + for i, record := range records { + if layer == record { + // Unordered Remove + records[len(records)-1], records[i] = records[i], records[len(records)-1] + records = records[:len(records)-1] + didDelete = true + break + } + } + if !didDelete { + roomEditor.setStatusText("Failed to delete instance layer.") + break + } + roomEditor.editingLayer = nil + roomEditor.editingRoom.InstanceLayers = records + roomEditor.editingRoom.DeletedLayers = append(roomEditor.editingRoom.DeletedLayers, layerUUID) + roomEditor.setStatusText(fmt.Sprintf("Deleted instance \"%s\" layer. (UUID: \"%s\")", layerName, layerUUID)) + case *room.RoomLayerBackground: + layerName := layer.Config.Name + layerUUID := layer.Config.UUID + didDelete := false + records := roomEditor.editingRoom.BackgroundLayers + for i, record := range records { + if layer == record { + // Unordered Remove + records[len(records)-1], records[i] = records[i], records[len(records)-1] + records = records[:len(records)-1] + didDelete = true + break + } + } + if !didDelete { + roomEditor.setStatusText("Failed to delete instance layer.") + break + } + roomEditor.editingLayer = nil + roomEditor.editingRoom.BackgroundLayers = records + roomEditor.editingRoom.DeletedLayers = append(roomEditor.editingRoom.DeletedLayers, layerUUID) + roomEditor.setStatusText(fmt.Sprintf("Deleted instance \"%s\" layer. (UUID: \"%s\")", layerName, layerUUID)) + case *room.RoomLayerSprite: + layerName := layer.Config.Name + layerUUID := layer.Config.UUID + didDelete := false + records := roomEditor.editingRoom.SpriteLayers + for i, record := range records { + if layer == record { + // Unordered Remove + records[len(records)-1], records[i] = records[i], records[len(records)-1] + records = records[:len(records)-1] + didDelete = true + break + } + } + if !didDelete { + roomEditor.setStatusText("Failed to delete instance layer.") + break + } + roomEditor.editingLayer = nil + roomEditor.editingRoom.SpriteLayers = records + roomEditor.editingRoom.DeletedLayers = append(roomEditor.editingRoom.DeletedLayers, layerUUID) + roomEditor.setStatusText(fmt.Sprintf("Deleted instance \"%s\" layer. (UUID: \"%s\")", layerName, layerUUID)) + default: + roomEditor.setStatusText(fmt.Sprintf("Unhandled deletion case for: %T", layer)) + } + roomEditor.MouseClearButton(MbLeft) + } + y += 32 + } + DrawSetGUI(false) + } + } + + if canUseBrush && + !isMenuOpen { + // Place tile + switch layer := roomEditor.editingLayer.(type) { + case nil: + // no-op + case *room.RoomLayerInstance: + // Left click + if roomEditor.MouseCheckButton(MbLeft) && + roomEditor.objectSelected != nil { + objectIndexSelected := roomEditor.objectSelected.ObjectIndex() + // NOTE(Jake): 2018-06-10 + // + // We need to handle mouse click between the last mouse position + // and current mouse position so that there are no gaps when you're + // dragging the mouse across long distances. + // + rect := geom.R(MousePosition(), lastMousePos) + didCreate := false + for x := rect.Left(); x <= rect.Right(); x += grid.X { + for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { + mousePos := geom.Vec{x, y} + + // Check to make sure we aren't placing over the top + // of an existing entity + hasCollision := false + for _, obj := range layer.Instances { + inst := roomEditorObjectIndexToData(obj.ObjectIndex) + if inst == nil { + continue + } + pos := geom.Vec{float64(obj.X), float64(obj.Y)} + size := inst.BaseObject().Size + left := pos.X + right := left + float64(size.X) + top := pos.Y + bottom := top + float64(size.Y) + if mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom { + hasCollision = true + } + } + + // + if !hasCollision { + // Snap to grid + mousePos.X = snapToGrid(mousePos.X, grid.X) + mousePos.Y = snapToGrid(mousePos.Y, grid.Y) + + // Add instance + pos := mousePos + //count := editingRoom.UserEntityCount + //editingRoom.UserEntityCount++ + + // + roomObj := &room.RoomObject{ + UUID: reditor.UUID(), + ObjectIndex: int32(objectIndexSelected), + X: int32(pos.X), + Y: int32(pos.Y), + } + layer.Instances = append(layer.Instances, roomObj) + + println("Create entity:", roomObj.String()) + didCreate = true + } } - pos := V(float64(obj.X), float64(obj.Y)) - size := inst.BaseObject().Size - left := pos.X - right := left + float64(size.X) - top := pos.Y - bottom := top + float64(size.Y) - if mousePos.X >= left && mousePos.X < right && - mousePos.Y >= top && mousePos.Y < bottom { - EditorRemoveInstance(i) - println("Deleted entity:", obj.Filename) + } + if didCreate { + roomEditor.hasUnsavedChanges = true + roomEditor.calculateRoomBounds() + } + } + + // Holding Right click + if roomEditor.MouseCheckButton(MbRight) { + // NOTE(Jake): 2018-06-10 + // + // We need to handle mouse click between the last mouse position + // and current mouse position so that there are no gaps when you're + // dragging the mouse across long distances. + // + rect := geom.R(MousePosition(), lastMousePos) + for x := rect.Left(); x <= rect.Right(); x += grid.X { + for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { + mousePos := geom.Vec{x, y} + previousDeletedCount := len(layer.DeletedInstances) + + // Mark deleted entities + for i, obj := range layer.Instances { + inst := roomEditorObjectIndexToData(obj.ObjectIndex) + if inst == nil { + continue + } + pos := geom.Vec{float64(obj.X), float64(obj.Y)} + size := inst.BaseObject().Size + left := pos.X + right := left + float64(size.X) + top := pos.Y + bottom := top + float64(size.Y) + if mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom { + // + record := layer.Instances[i] + + // Track deleted entities for when you save + layer.DeletedInstances = append(layer.DeletedInstances, record) + roomEditor.hasUnsavedChanges = true + } + } + + // Handle deletes + if previousDeletedCount != len(layer.DeletedInstances) { + // NOTE(Jake): 2018-07-11 + // + // Iterate over newly deleted entities and delete them from + // the room + // + didDelete := false + for i := previousDeletedCount; i < len(layer.DeletedInstances); i++ { + record := layer.DeletedInstances[i] + for i, obj := range layer.Instances { + if obj == record { + { + // Unordered Remove + layer.Instances[len(layer.Instances)-1], layer.Instances[i] = layer.Instances[i], layer.Instances[len(layer.Instances)-1] + layer.Instances = layer.Instances[:len(layer.Instances)-1] + } + didDelete = true + println("Deleted entity: " + record.UUID) + break + } + } + } + if didDelete { + roomEditor.calculateRoomBounds() + roomEditor.hasUnsavedChanges = true + } + } + } + } + } + case *room.RoomLayerSprite: + // Left click + if roomEditor.MouseCheckButton(MbLeft) && + roomEditor.spriteSelected.IsValid() { + spriteName := roomEditor.spriteSelected.Name() + //spriteWidth := roomEditor.spriteSelected.Size().X + //spriteHeight := roomEditor.spriteSelected.Size().Y + + // NOTE(Jake): 2018-06-10 + // + // We need to handle mouse click between the last mouse position + // and current mouse position so that there are no gaps when you're + // dragging the mouse across long distances. + // + rect := geom.R(MousePosition(), lastMousePos) + didCreate := false + if isHoldingControl { + selectedBrush := roomEditor.spriteSelected + brushWidth := float64(selectedBrush.Size().X) / 2 + brushHeight := float64(selectedBrush.Size().Y) / 2 + + for x := rect.Left(); x <= rect.Right(); x += brushWidth { + for y := rect.Top(); y <= rect.Bottom(); y += brushHeight { + pos := geom.Vec{x, y} + maybeNewPos, ok := roomEditor.getSnapPosition(pos, selectedBrush.Size(), layer) + if !ok { + continue + } + pos = maybeNewPos + + brushRect := geom.Rect{} + brushRect.Vec = pos + brushRect.Size = roomEditor.spriteSelected.Size() + + // Check to make sure we aren't placing over the top + // of an existing entity + hasCollision := false + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite := sprite.SpriteLoadByName(spriteName) + + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() + + if brushRect.CollisionRectangle(other) { + hasCollision = true + } + } + + // + if !hasCollision { + // + roomSpriteObj := &room.RoomSpriteObject{ + UUID: reditor.UUID(), + SpriteName: spriteName, + X: int32(pos.X), + Y: int32(pos.Y), + } + layer.Sprites = append(layer.Sprites, roomSpriteObj) + + println("Create sprite:", roomSpriteObj.String()) + didCreate = true + } + } + } + } else { + for x := rect.Left(); x <= rect.Right(); x += grid.X { + for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { + pos := geom.Vec{x, y} + // Grid mode + pos.X = snapToGrid(pos.X, grid.X) + pos.Y = snapToGrid(pos.Y, grid.Y) + + brushRect := geom.Rect{} + brushRect.Vec = pos + brushRect.Size = roomEditor.spriteSelected.Size() + + // Check to make sure we aren't placing over the top + // of an existing entity + hasCollision := false + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite := sprite.SpriteLoadByName(spriteName) + + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() + + if brushRect.CollisionRectangle(other) { + hasCollision = true + } + } + + // + if !hasCollision { + // + roomSpriteObj := &room.RoomSpriteObject{ + UUID: reditor.UUID(), + SpriteName: spriteName, + X: int32(pos.X), + Y: int32(pos.Y), + } + layer.Sprites = append(layer.Sprites, roomSpriteObj) + + println("Create sprite:", roomSpriteObj.String()) + didCreate = true + } + } + } + } + if didCreate { + roomEditor.hasUnsavedChanges = true + roomEditor.calculateRoomBounds() + } + } + + // Holding Right click + if roomEditor.MouseCheckButton(MbRight) { + // NOTE(Jake): 2018-06-10 + // + // We need to handle mouse click between the last mouse position + // and current mouse position so that there are no gaps when you're + // dragging the mouse across long distances. + // + rect := geom.R(MousePosition(), lastMousePos) + for x := rect.Left(); x <= rect.Right(); x += grid.X { + for y := rect.Top(); y <= rect.Bottom(); y += grid.Y { + mousePos := geom.Vec{x, y} + previousDeletedCount := len(layer.DeletedSprites) + + // Mark deleted + for i, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite := sprite.SpriteLoadByName(spriteName) + + width := float64(sprite.Size().X) + height := float64(sprite.Size().Y) + left := float64(obj.X) + right := left + width + top := float64(obj.Y) + bottom := top + height + if mousePos.X >= left && mousePos.X < right && + mousePos.Y >= top && mousePos.Y < bottom { + // + record := layer.Sprites[i] + + // Track deleted entities for when you save + layer.DeletedSprites = append(layer.DeletedSprites, record) + roomEditor.hasUnsavedChanges = true + } + } + + // Handle deletes + if previousDeletedCount != len(layer.DeletedSprites) { + // NOTE(Jake): 2018-07-11 + // + // Iterate over newly deleted entities and delete them from + // the room + // + didDelete := false + for i := previousDeletedCount; i < len(layer.DeletedSprites); i++ { + record := layer.DeletedSprites[i] + for i, obj := range layer.Sprites { + if obj == record { + { + // Unordered Remove + layer.Sprites[len(layer.Sprites)-1], layer.Sprites[i] = layer.Sprites[i], layer.Sprites[len(layer.Sprites)-1] + layer.Sprites = layer.Sprites[:len(layer.Sprites)-1] + } + didDelete = true + println("Deleted entity: " + record.UUID) + break + } + } + } + if didDelete { + roomEditor.calculateRoomBounds() + roomEditor.hasUnsavedChanges = true + } + } + } + } + } + case *room.RoomLayerBackground: + if roomEditor.MouseCheckButton(MbLeft) { + mousePos := MousePosition() + mousePos.X = snapToGrid(mousePos.X, grid.X) + mousePos.Y = snapToGrid(mousePos.Y, grid.Y) + layer.X = int32(mousePos.X) + layer.Y = int32(mousePos.Y) + + roomEditor.calculateRoomBounds() + roomEditor.hasUnsavedChanges = true + } + default: + panic(fmt.Sprintf("Unimplemented layer type: %T", layer)) + } + } + + // Select menu + if roomEditor.menuOpened != reditor.MenuNone { + switch roomEditor.menuOpened { + case reditor.MenuEntity: + DrawSetGUI(true) + // Add black opacity over screen with menu open + DrawRectangle(geom.Vec{0, 0}, geom.Vec{2048, 2048}, color.RGBA{0, 0, 0, 190}) + + // + ui := geom.Vec{ + X: float64(windowWidth()) / 2, + Y: 32, + } + typingText := KeyboardString() + roomEditor.entityMenuFiltered = roomEditor.entityMenuFiltered[:0] + for _, obj := range roomEditor.objectIndexToData { + if obj == nil { + continue + } + name := obj.ObjectName() + hasMatch := hasFilterMatch(name, typingText) + if !hasMatch { + continue + } + // NOTE(Jake): 2018-07-11 + // + // Animating in the object list isn't particularly useful. + // + //obj.BaseObject().ImageUpdate() + roomEditor.entityMenuFiltered = append(roomEditor.entityMenuFiltered, obj) + } + + // + if inputSelectPressed() && + len(roomEditor.entityMenuFiltered) > 0 { + selectedObj := roomEditor.entityMenuFiltered[0] + // Set + roomEditor.objectSelected = selectedObj + roomEditor.editorConfigSave() + + roomEditor.menuOpened = reditor.MenuNone + } + + { + searchText := "Search for object (type + press enter)" + DrawText(geom.Vec{ui.X - (StringWidth(searchText) / 4), ui.Y}, searchText) + ui.Y += 24 + } + { + typingText := KeyboardString() + DrawText(ui, typingText) + DrawText(geom.Vec{ui.X + StringWidth(typingText), ui.Y}, "|") + ui.Y += 24 + } + previewSize := geom.Vec{32, 32} + for _, obj := range roomEditor.entityMenuFiltered { + // + baseObj := obj.BaseObject() + pos := baseObj.Vec + //size := baseObj.Size + //oldImageScale := baseObj.ImageScale + + // NOTE(Jake): 2018-07-10 + // + // I've already wasted time thinking about centering this + // and the text above. Lets not look into this until we + // feel the need. + // + // Also look at other similar UI experiences, because + // maybe we wont need to center this to get good UX. + // + pos.X = ui.X - 40 + pos.Y = ui.Y - (previewSize.Y / 2) + //baseObj.ImageScale.X = previewSize.X / float64(size.X) + //baseObj.ImageScale.Y = previewSize.Y / float64(size.Y) + //obj.Draw() + drawObjectPreview(obj, pos, previewSize) + name := obj.ObjectName() + DrawText(ui, name) + //baseObj.ImageScale = oldImageScale + ui.Y += previewSize.Y + 16 + } + case reditor.MenuSprite, + reditor.MenuBackground: + if spriteSelected, ok := roomEditor.spriteViewer.update(); ok { + switch roomEditor.menuOpened { + case reditor.MenuSprite: + roomEditor.spriteSelected = spriteSelected + roomEditor.editorConfigSave() + case reditor.MenuBackground: + switch layer := roomEditor.editingLayer.(type) { + case *room.RoomLayerBackground: + if layer.SpriteName != spriteSelected.Name() { + layer.SpriteName = spriteSelected.Name() + roomEditor.hasUnsavedChanges = true + } + default: + panic(fmt.Sprintf("MenuBackground: Unhandled layer type for %T", layer)) } } + roomEditor.menuOpened = reditor.MenuNone + } + case reditor.MenuNewRoom, + reditor.MenuLoadRoom, + reditor.MenuNewLayer, + reditor.MenuSetOrder: + searchText := "" + switch roomEditor.menuOpened { + case reditor.MenuNewRoom: + searchText = "Enter name for new room:" + case reditor.MenuLoadRoom: + searchText = "Enter name to load room:" + case reditor.MenuNewLayer: + searchText = "Enter name for new layer:" + case reditor.MenuSetOrder: + searchText = "Set value for order (ie. -1000, 100, 10000, 5, -3, 0):" + default: + panic("Invalid menu type, No search text defined") + } + + DrawSetGUI(true) + // Add black opacity over screen with menu open + DrawRectangle(geom.Vec{0, 0}, geom.Vec{2048, 2048}, color.RGBA{0, 0, 0, 190}) + + // + ui := geom.Vec{ + X: float64(windowWidth()) / 2, + Y: 32, + } + { + DrawText(geom.Vec{ui.X - (StringWidth(searchText) / 4), ui.Y}, searchText) + ui.Y += 24 + } + { + typingText := KeyboardString() + DrawText(ui, typingText) + DrawText(geom.Vec{ui.X + StringWidth(typingText), ui.Y}, "|") + ui.Y += 24 + } + } + DrawSetGUI(false) + } + + // Draw status + { + DrawSetGUI(true) + editingString := "" + if editingRoom := roomEditor.editingRoom; editingRoom != nil { + editingString += "Editing: " + filepath.Base(roomEditor.editingRoom.Filepath()) + } + { + mousePos := MousePosition() + mousePos.X = snapToGrid(mousePos.X, grid.X) + mousePos.Y = snapToGrid(mousePos.Y, grid.Y) + if editingString != "" { + editingString += " | " } + editingString += strconv.FormatFloat(mousePos.X, 'f', -1, 64) + "," + strconv.FormatFloat(mousePos.Y, 'f', -1, 64) + "px" + } + if text := roomEditor.statusText; text != "" { + editingString += " | " + text + } + if selectedObj := roomEditor.objectSelected; selectedObj != nil { + editingString += " | Selected: " + selectedObj.ObjectName() + //DrawText(geom.V(0, 16), "Selected: "+selectedObj.ObjectName()) } + editingString += " | Frame Usage: " + FrameUsage() + //editingString += " | CTRL+P = Open Entity Menu" + //editingString += " | CTRL+N = Create new room" + DrawRectangle(geom.Vec{0, cameraSize.Y - 32}, geom.Vec{StringWidth(editingString) + 16, 32}, color.RGBA{0, 0, 0, 128}) + DrawText(geom.Vec{0, cameraSize.Y - 16}, editingString) + + DrawSetGUI(false) } } } -func EditorDraw() { - room := roomEditorEditingRoom() - if room == nil { +func drawObjectPreview(inst object.ObjectType, pos geom.Vec, fitToSize geom.Vec) { + baseObj := inst.BaseObject() + sprite := baseObj.SpriteIndex() + spriteSize := sprite.Size() + scale := geom.Vec{fitToSize.X / float64(spriteSize.X), fitToSize.Y / float64(spriteSize.Y)} + if scale.X > 1 { + scale.X = 1 + } + if scale.Y > 1 { + scale.Y = 1 + } + DrawSpriteScaled(sprite, 0, pos, scale) +} + +func drawObject(inst object.ObjectType, pos geom.Vec) { + baseObj := inst.BaseObject() + //baseObj.Vec = pos + DrawSprite(baseObj.SpriteIndex(), 0, pos) +} + +func drawRoomObject(roomObject *room.RoomObject, pos geom.Vec) { + inst := roomEditorObjectIndexToData(roomObject.ObjectIndex) + if inst == nil { return } - roomEditor := gRoomEditor - isMenuOpen := roomEditor.isEntityMenuOpen + drawObject(inst, pos) +} - // NOTE(Jake): 2018-06-04 - // - // A hack to set the camera context - // - cameraSetActive(0) - defer cameraClearActive() - currentCamera := cameraGetActive() +func drawTextButton(pos geom.Vec, text string) bool { + // Config + paddingH := 32.0 + size := geom.Vec{StringWidth(text) + paddingH, 24} + + // Handle mouse over + isMouseOver := isMouseScreenOver(pos, size) + + // Draw highlight bg + if isMouseOver { + DrawRectangle(pos, size, color.White) + } + + // Draw Text + pos.X += paddingH * 0.5 + pos.Y += 16 + if isMouseOver { + DrawTextColor(pos, text, color.Black) + } else { + DrawTextColor(pos, text, color.White) + } + return MouseCheckPressed(MbLeft) && isMouseOver +} + +func (roomEditor *roomEditor) getSnapPosition(pos geom.Vec, brushSize geom.Size, layer *room.RoomLayerSprite) (geom.Vec, bool) { + offsetX := float64(brushSize.X) + offsetY := float64(brushSize.Y) + var horizTarget, vertTarget geom.Rect + var horizSide, vertSide int + //fmt.Printf("Reset\n") + if horizSide == 0 { + targetPos := pos + targetPos.X += offsetX + + brushRect := geom.Rect{} + brushRect.Vec = targetPos + brushRect.Size = brushSize + + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite := sprite.SpriteLoadByName(spriteName) + + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() + + if brushRect.CollisionRectangle(other) { + // Make sure mouse pos is on the left-side of record + if pos.X <= other.Left() { + if horizSide == 0 || + (other.DistancePoint(pos) < horizTarget.DistancePoint(pos)) { + horizTarget = other + horizSide = 1 + } + } + } + } + } + { + targetPos := pos + targetPos.X -= offsetX + + brushRect := geom.Rect{} + brushRect.Vec = targetPos + brushRect.Size = brushSize + + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite := sprite.SpriteLoadByName(spriteName) + + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() + + if brushRect.CollisionRectangle(other) { + // Make sure mouse pos is on the right-side of record + if pos.X > other.Right() { + if horizSide == 0 || + (other.DistancePoint(pos) < horizTarget.DistancePoint(pos)) { + horizTarget = other + horizSide = -1 + } + } + } + } + } + { + targetPos := pos + targetPos.Y += offsetY + + brushRect := geom.Rect{} + brushRect.Vec = targetPos + brushRect.Size = brushSize + + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite := sprite.SpriteLoadByName(spriteName) + + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() - instances := room.Instances - for _, obj := range instances { - inst := roomEditorObjectIndexToData(obj.ObjectIndex) - if inst == nil { - continue + if brushRect.CollisionRectangle(other) { + // Make sure mouse pos is on the top-side of record + if pos.Y < other.Top() { + if vertSide == 0 || + (other.DistancePoint(pos) < vertTarget.DistancePoint(pos)) { + vertTarget = other + vertSide = 1 + } + } + } } - baseObj := inst.BaseObject() - baseObj.X = float64(obj.X) - baseObj.Y = float64(obj.Y) - inst.Draw() } - if !isMenuOpen { - grid := V(32, 32) + { + targetPos := pos + targetPos.Y -= offsetY - // Draw selected - if selectedObj := roomEditor.objectSelected; selectedObj != nil { - mousePos := MousePosition() - mousePos.X = snapToGrid(mousePos.X, grid.X) - mousePos.Y = snapToGrid(mousePos.Y, grid.Y) + brushRect := geom.Rect{} + brushRect.Vec = targetPos + brushRect.Size = brushSize - // Draw - baseObj := selectedObj.BaseObject() - baseObj.Vec = mousePos - selectedObj.Draw() + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite := sprite.SpriteLoadByName(spriteName) + + other := geom.Rect{} + other.X = float64(obj.X) + other.Y = float64(obj.Y) + other.Size = sprite.Size() + + if brushRect.CollisionRectangle(other) { + // Make sure mouse pos is on the bottom-side of record + if pos.Y > other.Bottom() { + if vertSide == 0 || + (other.DistancePoint(pos) < vertTarget.DistancePoint(pos)) { + vertTarget = other + vertSide = -1 + } + } + } } } + if horizSide != 0 || vertSide != 0 { + //brushRect := geom.Rect{} + //brushRect.Vec = pos + //brushRect.Size = brushSize - // Entity select menu - if roomEditor.isEntityMenuOpen { - { - //screen := gScreen - //screen.Fill(color.NRGBA{0x00, 0x00, 0x00, 128}) - DrawRectangle(m.V(0, 0), m.V(2048, 2048), color.RGBA{0, 0, 0, 190}) + var horizDist, vertDist float64 + horizDist = 9999 + vertDist = 9999 + if horizSide != 0 { + horizDist = horizTarget.DistancePoint(pos) } - var x float64 = 128 - var y float64 = 32 - { - searchText := "Search for object (type + press enter)" - DrawText(m.V(128-(StringWidth(searchText)/4), y), searchText) - y += 24 + if vertSide != 0 { + vertDist = vertTarget.DistancePoint(pos) } - { - typingText := KeyboardString() - DrawText(m.V(x, y), typingText) - DrawText(m.V(x+StringWidth(typingText), y), "|") - y += 24 - } - previewSize := m.V(32, 32) - for _, obj := range roomEditor.entityMenuFiltered { - // - baseObj := obj.BaseObject() - pos := &baseObj.Vec - size := baseObj.Size - oldImageScale := baseObj.ImageScale - { - pos.X = x - 40 + currentCamera.X - pos.Y = y - (previewSize.Y / 2) + currentCamera.Y - baseObj.ImageScale.X = previewSize.X / float64(size.X) - baseObj.ImageScale.Y = previewSize.Y / float64(size.Y) - obj.Draw() - DrawText(m.V(x, y), obj.ObjectName()) + if horizDist < vertDist { + switch horizSide { + case 0: + // no-op + case -1: + pos.X = horizTarget.Right() + pos.Y = horizTarget.Top() + case 1: + pos.X = horizTarget.Left() - float64(brushSize.X) + pos.Y = horizTarget.Top() + default: + panic(fmt.Sprintf("unknown horiz side: %d", horizSide)) + } + } else { + switch vertSide { + case 0: + // no-op + case -1: + pos.X = vertTarget.Left() + pos.Y = vertTarget.Bottom() + case 1: + pos.X = vertTarget.Left() + pos.Y = vertTarget.Top() - float64(brushSize.Y) + default: + panic(fmt.Sprintf("unknown vert side: %d", vertSide)) } - baseObj.ImageScale = oldImageScale - y += 48 } + return pos, true } + return pos, false } -func EditorSave() { - room := roomEditorEditingRoom() - if room == nil { +func (roomEditor *roomEditor) calculateRoomBounds() { + // Reset room size + editingRoom := roomEditor.editingRoom + editingRoom.Left = 0 + editingRoom.Right = 0 + editingRoom.Top = 0 + editingRoom.Bottom = 0 + for _, layer := range roomEditor.layers() { + switch layer := layer.(type) { + case *room.RoomLayerInstance: + for _, obj := range layer.Instances { + inst := roomEditorObjectIndexToData(obj.ObjectIndex) + if inst == nil { + continue + } + x := int32(obj.X) + y := int32(obj.Y) + size := inst.BaseObject().Size + width := int32(size.X) + height := int32(size.Y) + if x < editingRoom.Left { + editingRoom.Left = x + } + if right := x + width; right > editingRoom.Right { + editingRoom.Right = right + } + if y < editingRoom.Top { + editingRoom.Top = y + } + if bottom := y + height; bottom > editingRoom.Bottom { + editingRoom.Bottom = bottom + height + } + } + case *room.RoomLayerSprite: + for _, obj := range layer.Sprites { + spriteName := obj.SpriteName + sprite := sprite.SpriteLoadByName(spriteName) + + x := int32(obj.X) + y := int32(obj.Y) + width := int32(sprite.Size().X) + height := int32(sprite.Size().Y) + if x < editingRoom.Left { + editingRoom.Left = x + } + if right := x + width; right > editingRoom.Right { + editingRoom.Right = right + } + if y < editingRoom.Top { + editingRoom.Top = y + } + if bottom := y + height; bottom > editingRoom.Bottom { + editingRoom.Bottom = bottom + height + } + } + } + } +} + +func (roomEditor *roomEditor) newRoom(name string) { + if roomEditor.hasUnsavedChanges { + roomEditor.setStatusText("Cannot create new room with unsaved changes. Restart the game and editor, then press CTRL+N") + return + } + roomFilepath := roomEditor.roomDirectory + reditor.UUID() + if _, err := os.Stat(roomFilepath); !os.IsNotExist(err) { + roomEditor.setStatusText("Cannot create room \"" + filepath.Base(roomFilepath) + "\". That room name is already taken and exists.") return } - roomDirectory := room.Filepath + editingRoom := &room.Room{ + Config: &room.RoomConfig{ + UUID: reditor.UUID(), + Name: name, + }, + } + if roomEditor.editingRoom != nil { + // Copy configs of current rooms layers + for _, layer := range roomEditor.editingRoom.InstanceLayers { + var copyLayer room.RoomLayerInstance + copyLayer.Config = new(room.RoomLayerConfig) + *copyLayer.Config = *layer.Config + editingRoom.InstanceLayers = append(editingRoom.InstanceLayers, ©Layer) + } + for _, layer := range roomEditor.editingRoom.SpriteLayers { + var copyLayer room.RoomLayerSprite + copyLayer.Config = new(room.RoomLayerConfig) + *copyLayer.Config = *layer.Config + editingRoom.SpriteLayers = append(editingRoom.SpriteLayers, ©Layer) + } + for _, layer := range roomEditor.editingRoom.BackgroundLayers { + var copyLayer room.RoomLayerBackground + copyLayer.Config = new(room.RoomLayerConfig) + *copyLayer.Config = *layer.Config + editingRoom.BackgroundLayers = append(editingRoom.BackgroundLayers, ©Layer) + } + } + roomEditor.editorChangeRoom(editingRoom) +} - // Delete objects - deletedInstances := room.DeletedInstances - // NOTE(Jake): 2018-06-10 - // - // Clear out 'DeletedInstances' before we save - // the map file as data. - // - // We don't want this information - // serialized. - // - room.DeletedInstances = nil - for _, obj := range deletedInstances { - fname := obj.Filename - filepath := roomDirectory + "/" + fname + ".txt" - err := os.Remove(filepath) +func (roomEditor *roomEditor) loadRoom(name string) { + if roomEditor.hasUnsavedChanges { + roomEditor.setStatusText("Cannot load room with unsaved changes. Restart the game and editor.") + return + } + roomFilepath := roomEditor.roomDirectory + name + if _, err := os.Stat(roomFilepath); os.IsNotExist(err) { + roomEditor.setStatusText("Room \"" + name + "\" does not exist.") + return + } + editingRoom := LoadRoom(name) + if editingRoom == nil { + roomEditor.setStatusText("Room \"" + name + "\" could not be loaded.") + return + } + roomEditor.editorChangeRoom(editingRoom) +} + +func (roomEditor *roomEditor) newLayerAndSelected(editingRoom *room.Room, text string, kind room.RoomLayerKind) { + text = strings.TrimSpace(text) + config := &room.RoomLayerConfig{ + Kind: kind, + UUID: reditor.UUID(), + Name: text, + } + switch kind { + case room.RoomLayerKind_Instance: + layer := &room.RoomLayerInstance{Config: config} + editingRoom.InstanceLayers = append(editingRoom.InstanceLayers, layer) + roomEditor.editingLayer = layer + case room.RoomLayerKind_Background: + layer := &room.RoomLayerBackground{Config: config} + editingRoom.BackgroundLayers = append(editingRoom.BackgroundLayers, layer) + roomEditor.editingLayer = layer + case room.RoomLayerKind_Sprite: + layer := &room.RoomLayerSprite{Config: config} + editingRoom.SpriteLayers = append(editingRoom.SpriteLayers, layer) + roomEditor.editingLayer = layer + default: + panic("Unhandled room layer kind: %s" + kind.String()) + } + roomEditor.editorConfigSave() +} + +func (roomEditor *roomEditor) setStatusText(text string) { + roomEditor.statusText = text + roomEditor.statusTimer = time.Now() + println(text) +} + +func inputSelectPressed() bool { + return KeyboardCheckPressed(VkEnter) || KeyboardCheckPressed(VkNumpadEnter) +} + +func hasFilterMatch(s string, filterBy string) bool { + return filterBy == "" || + strings.Index(s, filterBy) >= 0 +} + +func (roomEditor *roomEditor) editorConfigLoad() { + roomEditor.editingLayer = nil + configPath := debugConfigPath("room_editor") + fileData, err := file.OpenFile(configPath) + if err == nil { + bytes, err := ioutil.ReadAll(fileData) if err != nil { - println("Error deleting instance:", err.Error()) - continue + panic("Error loading " + configPath + "\n" + "Error: " + err.Error()) + } + editorConfig := roomEditorConfig{} + if err := json.Unmarshal(bytes, &editorConfig); err != nil { + panic("Error unmarshalling " + configPath + "\n" + "Error: " + err.Error()) + } + // Set layer from config + for _, layer := range roomEditor.layers() { + if layer.GetConfig().UUID == editorConfig.LayerSelected { + roomEditor.editingLayer = layer + break + } + } + // Set brush from config + switch roomEditor.editingLayer.(type) { + case *room.RoomLayerSprite: + obj := sprite.SpriteLoadByName(editorConfig.BrushSelected) + if obj.Name() == editorConfig.BrushSelected { + roomEditor.spriteSelected = obj + break + } + case *room.RoomLayerInstance: + for _, obj := range roomEditor.objectIndexToData { + if obj != nil && + obj.ObjectName() == editorConfig.BrushSelected { + roomEditor.objectSelected = obj + break + } + } + } + } else { + println("No editor config exists: " + configPath) + } + +} + +func (roomEditor *roomEditor) editorConfigSave() { + var editorConfig roomEditorConfig + if roomEditor.editingLayer != nil { + editorConfig.LayerSelected = roomEditor.editingLayer.GetConfig().UUID + switch roomEditor.editingLayer.(type) { + case *room.RoomLayerInstance: + if roomEditor.objectSelected != nil { + editorConfig.BrushSelected = roomEditor.objectSelected.ObjectName() + } + case *room.RoomLayerSprite: + if roomEditor.spriteSelected.IsValid() { + editorConfig.BrushSelected = roomEditor.spriteSelected.Name() + } + case *room.RoomLayerBackground: + // no-op } } + if roomEditor.editingRoom != nil { + editorConfig.RoomSelected = roomEditor.editingRoom.Config.UUID + } + + json, _ := json.MarshalIndent(editorConfig, "", "\t") + configPath := debugConfigPath("room_editor") + err := ioutil.WriteFile(configPath, json, 0644) + if err != nil { + println("Failed to write room editor config: " + configPath + "\n" + "Error: " + err.Error()) + } +} + +func editorSave() { + roomEditor := gRoomEditor + editingRoom := roomEditor.editingRoom + if editingRoom == nil { + return + } + roomDirectory := editingRoom.Filepath() + if !roomEditor.hasUnsavedChanges { + roomEditor.setStatusText("No changes made to:" + roomDirectory) + return + } - // Write objects + // Create room directory + if _, err := os.Stat(roomDirectory); os.IsNotExist(err) { + os.Mkdir(roomDirectory, 0700) + } + + // Write config { - for _, obj := range room.Instances { - data := roomEditorObjectIndexToData(obj.ObjectIndex) - fname := obj.Filename - filepath := roomDirectory + "/" + fname + ".txt" + json, _ := json.MarshalIndent(editingRoom.Config, "", "\t") + err := ioutil.WriteFile(roomDirectory+"/config.json", json, 0644) + if err != nil { + panic("Failed to write room config.json file: " + roomDirectory + "\n" + "Error: " + err.Error()) + } + } + + // NOTE(Jake): 2018-07-21 + // + // Not sure how I should handle errors yet... + // will need to see how it plays out in practice. + // + didErrorOccur := false - file, err := os.Create(filepath) - if err != nil { - println("Error writing file:", err.Error()) + // Handle instance layers + { + for _, layer := range roomEditor.layers() { + config := layer.GetConfig() + layerDirectory := roomDirectory + "/" + config.UUID - // todo(jake): 2018-06-10 - // - // Error recovery here? - // + // Create layer directory if it doesn't exist. + if _, err := os.Stat(layerDirectory); os.IsNotExist(err) { + os.Mkdir(layerDirectory, 0700) + } - continue - } - - name := data.ObjectName() - x := obj.X - y := obj.Y - - // - w := bufio.NewWriter(file) - w.WriteString(name) - w.WriteByte('\n') - w.WriteString(strconv.Itoa(int(x))) - w.WriteByte('\n') - w.WriteString(strconv.Itoa(int(y))) - w.WriteByte('\n') - // NOTE(Jake): 2018-06-11 - // - // Why are we writing the relative filename [x] times here? - // - // To stop git from confusing deleted entities with renaming/moving - // a file. (ie. you'd delete an entity, create a new entity, but git would handle - // it as a rename/move) - // - // I'm actually not sure if a Git rename/move could lead to merge conflicts... - // but I'm going to err on the side of caution here until I decide to investigate - // this further. - // - w.WriteString(fname) - w.WriteString(fname) - w.WriteString(fname) - w.WriteString(fname) - w.Flush() - - file.Close() + // Write config + { + json, _ := json.MarshalIndent(config, "", "\t") + err := ioutil.WriteFile(layerDirectory+"/config.json", json, 0644) + if err != nil { + break + } + } + + switch layer := layer.(type) { + case nil: + // no-op + case *room.RoomLayerInstance: + // Deleted instances + if deletedInstances := layer.DeletedInstances; len(deletedInstances) > 0 { + // NOTE(Jake): 2018-06-10 + // + // Clear out 'DeletedInstances' before we save + // the map file as data. + // + // We don't want this information + // serialized. + // + layer.DeletedInstances = nil + for _, obj := range deletedInstances { + uuid := obj.UUID + filepath := layerDirectory + "/" + uuid + ".txt" + if _, err := os.Stat(filepath); os.IsNotExist(err) { + // Ignore if it does not exist + continue + } + err := os.Remove(filepath) + if err != nil { + println("Error deleting instance:", err.Error()) + didErrorOccur = true + continue + } + } + } + + // Write instances + if instances := layer.Instances; len(instances) > 0 { + for _, obj := range instances { + data := roomEditorObjectIndexToData(obj.ObjectIndex) + uuid := obj.UUID + filepath := layerDirectory + "/" + uuid + ".txt" + + file, err := os.Create(filepath) + if err != nil { + println("Error writing file:", err.Error()) + didErrorOccur = true + // todo(jake): 2018-06-10 + // + // Error recovery here? + // + + continue + } + + name := data.ObjectName() + x := obj.X + y := obj.Y + + // + w := bufio.NewWriter(file) + w.WriteString(name) + w.WriteByte('\n') + w.WriteString(strconv.Itoa(int(x))) + w.WriteByte('\n') + w.WriteString(strconv.Itoa(int(y))) + w.WriteByte('\n') + // NOTE(Jake): 2018-06-11 + // + // Why are we writing the relative filename [x] times here? + // + // To stop git from confusing deleted entities with renaming/moving + // a file. (ie. you'd delete an entity, create a new entity, but git would handle + // it as a rename/move) + // + // I'm actually not sure if a Git rename/move could lead to merge conflicts... + // but I'm going to err' on the side of caution here until I decide to investigate + // this further. + // + for i := 0; i < 4; i++ { + w.WriteString(uuid) + } + w.Flush() + + file.Close() + } + } + case *room.RoomLayerBackground: + // Write everything + { + // NOTE(Jake): 2018-07-29 + // + // Ideally I'd want to disable the config data being saved into this + // as we already get that information from config.json. + // + json, _ := json.MarshalIndent(layer, "", "\t") + err := ioutil.WriteFile(layerDirectory+"/background.json", json, 0644) + if err != nil { + break + } + } + case *room.RoomLayerSprite: + // Deleted instances + if deletedRecords := layer.DeletedSprites; len(deletedRecords) > 0 { + // NOTE(Jake): 2018-06-10 + // + // Clear out 'DeletedInstances' before we save + // the map file as data. + // + // We don't want this information + // serialized. + // + layer.DeletedSprites = nil + for _, obj := range deletedRecords { + uuid := obj.UUID + filepath := layerDirectory + "/" + uuid + ".txt" + if _, err := os.Stat(filepath); os.IsNotExist(err) { + // Ignore if it does not exist + continue + } + err := os.Remove(filepath) + if err != nil { + println("Error deleting instance:", err.Error()) + didErrorOccur = true + continue + } + } + } + + // Write instances + if records := layer.Sprites; len(records) > 0 { + for _, obj := range records { + uuid := obj.UUID + filepath := layerDirectory + "/" + uuid + ".txt" + + file, err := os.Create(filepath) + if err != nil { + println("Error writing sprite object file:", err.Error()) + didErrorOccur = true + // todo(jake): 2018-06-10 + // + // Error recovery here? + // + + continue + } + + name := obj.SpriteName + x := obj.X + y := obj.Y + + // + w := bufio.NewWriter(file) + w.WriteString(name) + w.WriteByte('\n') + w.WriteString(strconv.Itoa(int(x))) + w.WriteByte('\n') + w.WriteString(strconv.Itoa(int(y))) + w.WriteByte('\n') + // NOTE(Jake): 2018-06-11 + // + // Why are we writing the relative filename [x] times here? + // + // To stop git from confusing deleted entities with renaming/moving + // a file. (ie. you'd delete an entity, create a new entity, but git would handle + // it as a rename/move) + // + // I'm actually not sure if a Git rename/move could lead to merge conflicts... + // but I'm going to err' on the side of caution here until I decide to investigate + // this further. + // + for i := 0; i < 4; i++ { + w.WriteString(uuid) + } + w.Flush() + + file.Close() + } + } + default: + panic(fmt.Sprintf("Unimplemented save logic for layer type: %T", layer)) + } + } + } + + // Handle deletion of layers + if deletedLayers := editingRoom.DeletedLayers; len(deletedLayers) > 0 { + // NOTE(Jake): 2018-08-05 + // + // Clear deleted layers out so they aren't serialized into the binary map + // + editingRoom.DeletedLayers = editingRoom.DeletedLayers[:0] + for _, layerUUID := range deletedLayers { + layerDirectory := roomDirectory + "/" + layerUUID + os.RemoveAll(layerDirectory) } } // Save data copy of file - room.DebugWriteDataFile(room.Filepath) + editingRoom.DebugWriteDataFile(editingRoom.Filepath()) + + // Save room! + baseName := filepath.Base(editingRoom.Filepath()) + if didErrorOccur { + roomEditor.setStatusText(baseName + " saved but with errors... Check console logs.") + } else { + roomEditor.setStatusText(baseName + " saved successfully!") + } + + roomEditor.hasUnsavedChanges = false } diff --git a/gml/room_instance.go b/gml/room_instance.go index 10f3a83..f21ff30 100644 --- a/gml/room_instance.go +++ b/gml/room_instance.go @@ -4,53 +4,65 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/object" ) -func RoomInstanceCreate(room *Room) *RoomInstance { - roomInst := gState.createNewRoomInstance(room) - return roomInst +type RoomInstance struct { + used bool + index int + room *Room + + instanceLayers []roomInstanceLayerInstance + spriteLayers []roomInstanceLayerSprite + drawLayers []roomInstanceLayerDraw } -func RoomInstanceEmptyCreate() *RoomInstance { +// todo(Jake): 2018-12-01: Remove this if it feels unnecessary or goes unused +// RoomInstanceName get the name of the room used by the room instance +/*func RoomInstanceName(roomInstanceIndex int) string { + roomInst := &gState.roomInstances[roomInstanceIndex] + if !roomInst.used { + return "" + } + return roomInst.room.Config.UUID +}*/ + +// RoomInstanceNew create a new empty room instance programmatically +func RoomInstanceNew() int { roomInst := gState.createNewRoomInstance(nil) - return roomInst + return roomInst.index } -type RoomInstance struct { - used bool - index int - room *Room - instanceManager instanceManager +// RoomInstanceCreate will create a new instance of the room given +// todo(Jake): 2018-12-01: #6: Change *Room to be gml.RoomIndex +func todo__roomInstanceCreate(room *Room) int { + roomInst := gState.createNewRoomInstance(room) + return roomInst.index } +// RoomInstanceDestroy destroys a room instance +func RoomInstanceDestroy(roomInstanceIndex int) { + roomInst := &gState.roomInstances[roomInstanceIndex] + gState.deleteRoomInstance(roomInst) +} + +// todo(Jake): 2018-12-01: Github #19: Remove this func (roomInst *RoomInstance) Index() int { return roomInst.index } -/*func (roomInst *RoomInstance) CreateSnapshot() []byte { - now := time.Now() - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - err := enc.Encode(roomInst.instanceManager.instances) - if err != nil { - panic(err) - } - result := buf.Bytes() - println("Time to encode:", time.Now().Sub(now).String(), ", Size in bytes:", len(result)) - return result +type roomInstanceObject interface { + BaseObject() *object.Object } -func (roomInst *RoomInstance) RestoreSnapshot(data []byte) { - now := time.Now() - var buf bytes.Buffer - buf.Write(data) - enc := gob.NewDecoder(&buf) - err := enc.Decode(roomInst.instanceManager.instances) - if err != nil { - panic(err) +/*func RoomInstanceInstances(inst roomInstanceObject) []object.ObjectType { + roomInstanceIndex := object.RoomInstanceIndex(inst.BaseObject()) + roomInst := RoomGetInstance(roomInstanceIndex) + if roomInst == nil { + return nil } - println("Time to decode:", time.Now().Sub(now).String()) + instanceLayer := &roomInst.instanceLayers[len(roomInst.instanceLayers)-1] + return instanceLayer.manager.instances }*/ -func RoomGetInstance(roomInstanceIndex int) *RoomInstance { +func roomGetInstance(roomInstanceIndex int) *RoomInstance { roomInst := &gState.roomInstances[roomInstanceIndex] if roomInst.used { return roomInst @@ -58,19 +70,14 @@ func RoomGetInstance(roomInstanceIndex int) *RoomInstance { return nil } -func (roomInst *RoomInstance) InstanceCreate(position Vec, objectIndex object.ObjectIndex) object.ObjectType { - return roomInst.instanceManager.InstanceCreate(position, objectIndex, roomInst.Index()) -} - -func (roomInst *RoomInstance) InstanceDestroy(inst object.ObjectType) { - manager := &roomInst.instanceManager - manager.InstanceDestroy(inst) -} - func (roomInst *RoomInstance) update(animationUpdate bool) { - roomInst.instanceManager.update(animationUpdate) + for _, layer := range roomInst.instanceLayers { + layer.update(animationUpdate) + } } func (roomInst *RoomInstance) draw() { - roomInst.instanceManager.draw() + for _, layer := range roomInst.drawLayers { + layer.draw() + } } diff --git a/gml/room_instance_iterator.go b/gml/room_instance_iterator.go new file mode 100644 index 0000000..a3b239e --- /dev/null +++ b/gml/room_instance_iterator.go @@ -0,0 +1,30 @@ +package gml + +type roomInstanceIteratorState struct { + roomInstanceIndex int +} + +func RoomInstanceIterator() roomInstanceIteratorState { + return roomInstanceIteratorState{ + roomInstanceIndex: 0, + } +} + +func (iterator *roomInstanceIteratorState) Next() bool { + for { + roomInst := &gState.roomInstances[iterator.roomInstanceIndex] + if iterator.roomInstanceIndex >= len(gState.roomInstances) { + return false + } + if !roomInst.used { + iterator.roomInstanceIndex++ + continue + } + iterator.roomInstanceIndex++ + return true + } +} + +func (iterator *roomInstanceIteratorState) Value() int { + return iterator.roomInstanceIndex +} diff --git a/gml/room_instance_layer.go b/gml/room_instance_layer.go new file mode 100644 index 0000000..e397a84 --- /dev/null +++ b/gml/room_instance_layer.go @@ -0,0 +1,23 @@ +package gml + +//type RoomInstanceLayer interface { +// update(animationUpdate bool) +// draw() +//} + +type roomInstanceLayerDrawBase struct { + drawOrder int32 +} + +func (layer *roomInstanceLayerDrawBase) order() int32 { + return layer.drawOrder +} + +type roomInstanceLayerUpdate interface { + update(animationUpdate bool) +} + +type roomInstanceLayerDraw interface { + draw() + order() int32 +} diff --git a/gml/room_instance_layer_background.go b/gml/room_instance_layer_background.go new file mode 100644 index 0000000..d71acc5 --- /dev/null +++ b/gml/room_instance_layer_background.go @@ -0,0 +1,43 @@ +package gml + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" +) + +type roomInstanceLayerBackground struct { + roomInstanceLayerDrawBase + name string + sprite sprite.SpriteIndex + x, y float64 + roomLeft float64 + roomRight float64 +} + +func (layer *roomInstanceLayerBackground) order() int32 { + return layer.drawOrder +} + +func (layer *roomInstanceLayerBackground) draw() { + sprite := layer.sprite + width := float64(sprite.Size().X) + x := layer.x + y := layer.y + DrawSprite(sprite, 0, geom.Vec{x, y}) + { + // Tile left + x := x + for x > float64(layer.roomLeft) { + x -= width + DrawSprite(sprite, 0, geom.Vec{x, y}) + } + } + { + // Tile left + x := x + for x < float64(layer.roomRight) { + x += width + DrawSprite(sprite, 0, geom.Vec{x, y}) + } + } +} diff --git a/gml/room_instance_layer_instance.go b/gml/room_instance_layer_instance.go new file mode 100644 index 0000000..551831e --- /dev/null +++ b/gml/room_instance_layer_instance.go @@ -0,0 +1,21 @@ +package gml + +type roomInstanceLayerInstance struct { + roomInstanceLayerDrawBase + index int + name string + manager instanceManager + //_parent *RoomInstance +} + +//func (layer *RoomInstanceLayerInstance) parent() *RoomInstance { +// return layer._parent +//} + +func (layer *roomInstanceLayerInstance) update(animationUpdate bool) { + layer.manager.update(animationUpdate) +} + +func (layer *roomInstanceLayerInstance) draw() { + layer.manager.draw() +} diff --git a/gml/room_instance_layer_sprite.go b/gml/room_instance_layer_sprite.go new file mode 100644 index 0000000..1cf5334 --- /dev/null +++ b/gml/room_instance_layer_sprite.go @@ -0,0 +1,42 @@ +package gml + +import ( + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" +) + +type roomInstanceLayerSpriteObject struct { + geom.Vec + sprite sprite.SpriteIndex +} + +func (record *roomInstanceLayerSpriteObject) Rect() geom.Rect { + r := geom.Rect{} + r.Vec = record.Vec + r.Size = record.sprite.Size() + return r +} + +type roomInstanceLayerSprite struct { + roomInstanceLayerDrawBase + name string + sprites []roomInstanceLayerSpriteObject + //spaces space.SpaceBucketArray + hasCollision bool +} + +func (layer *roomInstanceLayerSprite) order() int32 { + return layer.drawOrder +} + +func (layer *roomInstanceLayerSprite) draw() { + //screen := gScreen + for _, record := range layer.sprites { + /*position := maybeApplyOffsetByCamera(record.Vec) + frame := sprite.GetRawFrame(record.Sprite, 0) // int(math.Floor(subimage)) + op := ebiten.DrawImageOptions{} + op.GeoM.Translate(position.X, position.Y) + screen.DrawImage(frame, &op)*/ + DrawSprite(record.sprite, 0, geom.Vec{record.X, record.Y}) + } +} diff --git a/gml/sprite.go b/gml/sprite.go index a68523c..4fb43c7 100644 --- a/gml/sprite.go +++ b/gml/sprite.go @@ -4,10 +4,15 @@ import ( "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) -type Sprite = sprite.Sprite +type SpriteIndex = sprite.SpriteIndex type SpriteState = sprite.SpriteState -func LoadSprite(name string) *Sprite { - return sprite.LoadSprite(name) +func SpriteInitializeIndexToName(indexToName []string, nameToIndex map[string]SpriteIndex) { + sprite.SpriteInitializeIndexToName(indexToName, nameToIndex) +} + +func SpriteLoad(index SpriteIndex) SpriteIndex { + sprite.SpriteLoad(index) + return index } diff --git a/gml/sprite_selector_debug.go b/gml/sprite_selector_debug.go new file mode 100644 index 0000000..ff00c4f --- /dev/null +++ b/gml/sprite_selector_debug.go @@ -0,0 +1,110 @@ +// +build debug + +package gml + +import ( + "image/color" + "os" + "path/filepath" + + "github.com/silbinarywolf/gml-go/gml/internal/file" + "github.com/silbinarywolf/gml-go/gml/internal/geom" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" +) + +var ( + debugScratchSpriteList []sprite.SpriteIndex + debugSpriteViewerLoaded bool +) + +type debugSpriteViewer struct { +} + +func (viewer *debugSpriteViewer) lazyLoad() { + if debugSpriteViewerLoaded { + return + } + debugSpriteViewerLoaded = true + spritePath := file.AssetDirectory + "/" + sprite.SpriteDirectoryBase + err := filepath.Walk(spritePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + // Skip files + return nil + } + if path == spritePath { + // Skip self + return nil + } + name := filepath.Base(path) + sprite.SpriteLoadByName(name) + return nil + }) + if err != nil { + panic(err) + } +} + +func (viewer *debugSpriteViewer) update() (sprite.SpriteIndex, bool) { + viewer.lazyLoad() + + typingText := KeyboardString() + spriteMenuFiltered := debugScratchSpriteList[:0] + for spriteIndex, name := range sprite.SpriteNames() { + hasMatch := hasFilterMatch(name, typingText) + if !hasMatch { + continue + } + spriteMenuFiltered = append(spriteMenuFiltered, sprite.SpriteIndex(spriteIndex)) + } + + // Input + selected := KeyboardCheckPressed(VkEnter) || + KeyboardCheckPressed(VkNumpadEnter) + if selected && + len(spriteMenuFiltered) > 0 { + selectedSpr := spriteMenuFiltered[0] + ClearKeyboardString() + return selectedSpr, true + } + + // Draw + { + DrawSetGUI(true) + // Add black opacity over screen with menu open + DrawRectangle(geom.Vec{0, 0}, geom.Vec{2048, 2048}, color.RGBA{0, 0, 0, 190}) + + ui := geom.Vec{ + X: float64(windowWidth()) / 2, + Y: 32, + } + + { + searchText := "Search for image (type + press enter)" + DrawText(geom.Vec{ui.X - (StringWidth(searchText) / 4), ui.Y}, searchText) + ui.Y += 24 + } + { + typingText := KeyboardString() + DrawText(geom.Vec{ui.X, ui.Y}, typingText) + DrawText(geom.Vec{ui.X + StringWidth(typingText), ui.Y}, "|") + ui.Y += 24 + } + previewSize := geom.Vec{32, 32} + for _, spr := range spriteMenuFiltered { + var pos geom.Vec + pos.X = ui.X - 40 + pos.Y = ui.Y - (previewSize.Y / 2) + calcPreviewSize := previewSize + calcPreviewSize.X /= float64(spr.Size().X) + calcPreviewSize.Y /= float64(spr.Size().Y) + DrawSpriteScaled(spr, 0, pos, calcPreviewSize) + name := spr.Name() + DrawText(geom.Vec{ui.X, ui.Y}, name) + ui.Y += previewSize.Y + 16 + } + } + return sprite.SprUndefined, false +} diff --git a/gml/sprite_selector_nodebug.go b/gml/sprite_selector_nodebug.go new file mode 100644 index 0000000..489d220 --- /dev/null +++ b/gml/sprite_selector_nodebug.go @@ -0,0 +1,9 @@ +// +build !debug + +package gml + +type debugSpriteViewer struct { +} + +func (viewer *debugSpriteViewer) update() { +} diff --git a/gml/state.go b/gml/state.go index bd326f2..fadc7ae 100644 --- a/gml/state.go +++ b/gml/state.go @@ -1,16 +1,35 @@ package gml import ( + "sort" + "strconv" + + "github.com/silbinarywolf/gml-go/gml/internal/geom" "github.com/silbinarywolf/gml-go/gml/internal/object" + "github.com/silbinarywolf/gml-go/gml/internal/sprite" ) var ( gState *state = newState() + g_game gameState ) +type gameState struct { + hasGameRestarted bool +} + +func GameRestart() { + g_game.hasGameRestarted = true +} + type state struct { - globalInstances *instanceManager - roomInstances []RoomInstance + globalInstances *instanceManager + roomInstances []RoomInstance + instancesMarkedForDelete []object.ObjectType + isCreatingRoomInstance bool + gWidth int + gHeight int + frameBudgetNanosecondsUsed int64 } func newState() *state { @@ -20,25 +39,129 @@ func newState() *state { } } +// FrameUsage returns a string like "1% (55ns)" to tell you how much +// of your frame budget has been utilized. (Assumes 60FPS) +func FrameUsage() string { + frameBudgetUsed := gState.frameBudgetNanosecondsUsed + timeTaken := float64(frameBudgetUsed) / 16000000.0 + //fmt.Printf("Time used: %v / 16000000.0\n", frameBudgetUsed) + text := strconv.FormatFloat(timeTaken*100, 'f', 6, 64) + return text + "% (" + strconv.Itoa(int(gState.frameBudgetNanosecondsUsed)) + "ns)" +} + +// IsCreatingRoomInstance returns whether this instance was created by a room or not, rather +// than programmatically. +func IsCreatingRoomInstance() bool { + return gState.isCreatingRoomInstance +} + func (state *state) createNewRoomInstance(room *Room) *RoomInstance { state.roomInstances = append(state.roomInstances, RoomInstance{ used: true, room: room, }) + state.isCreatingRoomInstance = true + defer func() { + state.isCreatingRoomInstance = false + }() index := len(state.roomInstances) - 1 roomInst := &state.roomInstances[index] roomInst.index = index + if room == nil || + len(room.InstanceLayers) == 0 { + // Create default instance layer if... + // - No instance layers exist in the room data + // - Creating blank room + roomInst.instanceLayers = make([]roomInstanceLayerInstance, 1) + roomInst.instanceLayers[0] = roomInstanceLayerInstance{ + index: 0, + } + roomInst.drawLayers = append(roomInst.drawLayers, &roomInst.instanceLayers[0]) + } + // If non-blank room instance, use room data to create if roomInst.room != nil { - // Instantiate instances for this room - for _, obj := range roomInst.room.Instances { - roomInst.InstanceCreate(V(float64(obj.X), float64(obj.Y)), object.ObjectIndex(obj.ObjectIndex)) + // Instance layers + if len(room.InstanceLayers) > 0 { + roomInst.instanceLayers = make([]roomInstanceLayerInstance, len(room.InstanceLayers)) + for i := 0; i < len(room.InstanceLayers); i++ { + layerData := room.InstanceLayers[i] + roomInst.instanceLayers[i] = roomInstanceLayerInstance{ + index: i, + } + layer := &roomInst.instanceLayers[i] + layer.drawOrder = layerData.Config.Order + for _, obj := range layerData.Instances { + instanceCreateLayer(geom.Vec{float64(obj.X), float64(obj.Y)}, layer, roomInst, object.ObjectIndex(obj.ObjectIndex)) + } + roomInst.drawLayers = append(roomInst.drawLayers, layer) + } + } + // Background layers + for i := 0; i < len(room.BackgroundLayers); i++ { + layerData := room.BackgroundLayers[i] + spriteName := layerData.SpriteName + if spriteName == "" { + continue + } + layer := new(roomInstanceLayerBackground) + layer.x = float64(layerData.X) + layer.y = float64(layerData.Y) + layer.roomLeft = float64(room.Left) + layer.roomRight = float64(room.Right) + layer.sprite = sprite.SpriteLoadByName(spriteName) + layer.drawOrder = layerData.Config.Order + roomInst.drawLayers = append(roomInst.drawLayers, layer) } + // Sprite layers + for i := 0; i < len(room.SpriteLayers); i++ { + layerData := room.SpriteLayers[i] + hasCollision := layerData.Config.HasCollision + layer := roomInstanceLayerSprite{} + layer.hasCollision = hasCollision + layer.sprites = make([]roomInstanceLayerSpriteObject, 0, len(layerData.Sprites)) + for _, sprObj := range layerData.Sprites { + // Add draw sprite + spr := sprite.SpriteLoadByName(sprObj.SpriteName) + record := roomInstanceLayerSpriteObject{ + sprite: spr, + } + record.X = float64(sprObj.X) + record.Y = float64(sprObj.Y) + layer.sprites = append(layer.sprites, record) + } + layer.drawOrder = layerData.Config.Order + roomInst.spriteLayers = append(roomInst.spriteLayers, layer) + roomInst.drawLayers = append(roomInst.drawLayers, &roomInst.spriteLayers[len(roomInst.spriteLayers)-1]) + } + // Sort draw layers by order + sort.Slice(roomInst.drawLayers, func(i, j int) bool { + return roomInst.drawLayers[i].order() < roomInst.drawLayers[j].order() + }) } return roomInst } +func (state *state) deleteRoomInstance(roomInst *RoomInstance) { + for _, layer := range roomInst.instanceLayers { + // NOTE(Jake): 2018-08-21 + // + // Running Destroy() on each rather than InstanceDestroy() + // for speed purposes + // + for _, inst := range layer.manager.instances { + //InstanceDestroy() + inst.Destroy() + cameraInstanceDestroy(inst) + } + layer.manager.reset() + } + + roomInst.used = false + *roomInst = RoomInstance{} +} + func (state *state) update(animationUpdate bool) { // Simulate global instances state.globalInstances.update(animationUpdate) @@ -51,18 +174,10 @@ func (state *state) update(animationUpdate bool) { } roomInst.update(animationUpdate) } -} -func (state *state) draw() { - // Render global instances - state.globalInstances.draw() - - // Render each instance in each room instance - for i := 1; i < len(state.roomInstances); i++ { - roomInst := &state.roomInstances[i] - if !roomInst.used { - continue - } - roomInst.draw() + // Remove deleted entities + for _, inst := range state.instancesMarkedForDelete { + instanceRemove(inst) } + state.instancesMarkedForDelete = state.instancesMarkedForDelete[:0] } diff --git a/gml/state_headless.go b/gml/state_headless.go new file mode 100644 index 0000000..cbdecd3 --- /dev/null +++ b/gml/state_headless.go @@ -0,0 +1,7 @@ +// +build headless + +package gml + +// draw does not need to execute any logic for headless mode +func (state *state) draw() { +} diff --git a/gml/state_nonheadless.go b/gml/state_nonheadless.go new file mode 100644 index 0000000..f3db074 --- /dev/null +++ b/gml/state_nonheadless.go @@ -0,0 +1,42 @@ +// +build !headless + +package gml + +func (state *state) draw() { + for i := 0; i < len(gCameraManager.cameras); i++ { + view := &gCameraManager.cameras[i] + if !view.enabled { + continue + } + view.update() + cameraSetActive(i) + + cameraClear(i) + + // Render global instances + state.globalInstances.draw() + + if view.follow != nil { + // Render instances in same room as instance following + inst := view.follow.BaseObject() + roomInst := roomGetInstance(inst.RoomInstanceIndex()) + if roomInst == nil { + panic("RoomInstance this object belongs to has been destroyed") + } + roomInst.draw() + } else { + // Render each instance in each room instance + for i := 1; i < len(state.roomInstances); i++ { + roomInst := &state.roomInstances[i] + if !roomInst.used { + continue + } + roomInst.draw() + } + } + + // Render camera onto OS-window + cameraDraw(i) + } + cameraClearActive() +}