├── assets ├── stub.go ├── font │ ├── admtas.ttf │ ├── drake_10x10.png │ └── TerminusTTF-4.40.1.ttf ├── sprites │ ├── char.png │ ├── dead.png │ ├── loot.png │ ├── map.png │ ├── cursor1.png │ ├── cursor2.png │ ├── things.png │ ├── tileset.png │ ├── SKELETON.png │ ├── basictiles.png │ ├── characters.png │ ├── testsprite.png │ ├── SKELETON_GUARD.png │ └── credits.txt └── maps │ ├── maze.map │ └── 1.map ├── doc └── screenshot.png ├── .gitignore ├── librpg ├── patterns.go ├── cheats.go ├── statuseffects.go ├── item_test.go ├── common │ ├── cursor.go │ ├── rand.go │ ├── loadpic.go │ ├── strings.go │ ├── path.go │ ├── object.go │ ├── patterns.go │ └── objects.go ├── mob_test.go ├── dynamic.go ├── color_test.go ├── stats.go ├── title.go ├── regions.go ├── maps │ ├── miniworld.go │ └── map.go ├── names_test.go ├── names.go ├── inventory.go ├── newworld.go ├── menu.go ├── util.go ├── text.go ├── strings.go ├── items.go ├── load.go ├── spells.go ├── character.go ├── living.go └── world.go ├── cmd ├── mapmaker │ ├── README.md │ └── main.go ├── mapgen │ └── main.go └── aerpg │ └── main.go ├── .travis.yml ├── go.mod ├── LICENSE ├── makefile ├── go.sum └── README.md /assets/stub.go: -------------------------------------------------------------------------------- 1 | package assets 2 | -------------------------------------------------------------------------------- /doc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/doc/screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | walking 2 | pdata 3 | p.debug 4 | mapmaker 5 | testing 6 | /bin 7 | /maps 8 | -------------------------------------------------------------------------------- /assets/font/admtas.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/font/admtas.ttf -------------------------------------------------------------------------------- /assets/sprites/char.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/char.png -------------------------------------------------------------------------------- /assets/sprites/dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/dead.png -------------------------------------------------------------------------------- /assets/sprites/loot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/loot.png -------------------------------------------------------------------------------- /assets/sprites/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/map.png -------------------------------------------------------------------------------- /assets/sprites/cursor1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/cursor1.png -------------------------------------------------------------------------------- /assets/sprites/cursor2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/cursor2.png -------------------------------------------------------------------------------- /assets/sprites/things.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/things.png -------------------------------------------------------------------------------- /assets/sprites/tileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/tileset.png -------------------------------------------------------------------------------- /assets/font/drake_10x10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/font/drake_10x10.png -------------------------------------------------------------------------------- /assets/sprites/SKELETON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/SKELETON.png -------------------------------------------------------------------------------- /assets/sprites/basictiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/basictiles.png -------------------------------------------------------------------------------- /assets/sprites/characters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/characters.png -------------------------------------------------------------------------------- /assets/sprites/testsprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/testsprite.png -------------------------------------------------------------------------------- /assets/font/TerminusTTF-4.40.1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/font/TerminusTTF-4.40.1.ttf -------------------------------------------------------------------------------- /assets/sprites/SKELETON_GUARD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerth/rpg/HEAD/assets/sprites/SKELETON_GUARD.png -------------------------------------------------------------------------------- /assets/sprites/credits.txt: -------------------------------------------------------------------------------- 1 | dead.png, things.png, basictiles.png: Lanea Zimmerman 2 | 720 rpg icons: Lorc 3 | 4 | -------------------------------------------------------------------------------- /librpg/patterns.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | func init() { 9 | rand.Seed(time.Now().UnixNano()) 10 | } 11 | -------------------------------------------------------------------------------- /librpg/cheats.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import "github.com/aerth/rpc/librpg/common" 4 | 5 | func (w *World) RandomLootSomewhere() { 6 | loot := createLoot() 7 | w.NewLoot(common.FindRandomTile(w.Tiles), []Item{loot}) 8 | } 9 | -------------------------------------------------------------------------------- /librpg/statuseffects.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | type StatusEffect int 4 | 5 | const ( 6 | _ StatusEffect = iota 7 | E_POISON 8 | E_PARALYSIS 9 | E_FROZEN 10 | E_BURNED 11 | E_SLEEP 12 | E_CONFUSED 13 | E_TIRED 14 | E_DRUNK 15 | ) 16 | -------------------------------------------------------------------------------- /librpg/item_test.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func init() { 11 | rand.Seed(time.Now().UnixNano()) 12 | } 13 | 14 | func TestCreateLoot(t *testing.T) { 15 | i := createLoot() 16 | log.Println(i) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /cmd/mapmaker/README.md: -------------------------------------------------------------------------------- 1 | # mapmaker 2 | 3 | * 'snaps to grid' lol 4 | * left click block, right click tile 5 | * pgup pgdown select sprite 6 | * ENTER save json 7 | * SPACE delete hover 8 | * u undo, r redo (careful!) 9 | * caps lock special mode 10 | * 3 way intuitive controls 11 | * super hi tech 12 | 13 | -------------------------------------------------------------------------------- /librpg/common/cursor.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/faiface/pixel" 7 | ) 8 | 9 | func GetCursor(num int) *pixel.Sprite { 10 | pic, err := LoadPicture("sprites/cursor" + strconv.Itoa(num) + ".png") 11 | if err != nil { 12 | 13 | panic(err) 14 | } 15 | sprite := pixel.NewSprite(pic, pic.Bounds()) 16 | return sprite 17 | } 18 | -------------------------------------------------------------------------------- /librpg/common/rand.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | 7 | "github.com/faiface/pixel" 8 | ) 9 | 10 | func RandomColor() pixel.RGBA { 11 | 12 | r := rand.Float64() 13 | g := rand.Float64() 14 | b := rand.Float64() 15 | len := math.Sqrt(r*r + g*g + b*b) 16 | //if len == 0 { 17 | // goto again 18 | //} 19 | return pixel.RGB(r/len, g/len, b/len) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /librpg/mob_test.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/faiface/pixel" 8 | ) 9 | 10 | func TestMobCenter(t *testing.T) { 11 | w := NewWorld("world name", 1, "1") 12 | mob := w.NewEntity(SKELETON) 13 | mob.Rect = mob.Rect.Moved(pixel.V(100, 100)) 14 | if mob.Center() != pixel.V(100, 100) { 15 | t.FailNow() 16 | } 17 | log.Println(mob.Center()) 18 | } 19 | -------------------------------------------------------------------------------- /librpg/dynamic.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aerth/rpc/librpg/common" 7 | ) 8 | 9 | // doors 10 | // loot 11 | 12 | type DObjectType int 13 | 14 | type DObject struct { 15 | Object common.Object 16 | Contains []Item 17 | Until time.Time `json:"-"` 18 | Type DObjectType 19 | } 20 | 21 | const ( 22 | D_NIL DObjectType = iota 23 | D_LOOT 24 | D_DOOR 25 | D_PORTAL 26 | ) 27 | -------------------------------------------------------------------------------- /librpg/color_test.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/faiface/pixel" 8 | "golang.org/x/image/colornames" 9 | ) 10 | 11 | func init() { 12 | log.SetFlags(0) 13 | } 14 | func TestAlpha(t *testing.T) { 15 | color := pixel.ToRGBA(colornames.Red) 16 | log.Println(color) 17 | color = color.Mul(pixel.Alpha(0.2)) 18 | log.Println(color) 19 | color = color.Scaled((0.2)) 20 | log.Println(color) 21 | color = color.Mul(pixel.Alpha(0)) 22 | log.Println(color) 23 | } 24 | -------------------------------------------------------------------------------- /librpg/common/loadpic.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bytes" 5 | "image" 6 | 7 | "github.com/aerth/rpc/assets" 8 | "github.com/faiface/pixel" 9 | ) 10 | 11 | // loadPicture from embedded assets 12 | func LoadPicture(path string) (pixel.Picture, error) { 13 | b, err := assets.Asset(path) 14 | if err != nil { 15 | return nil, err 16 | } 17 | file := bytes.NewReader(b) 18 | img, _, err := image.Decode(file) 19 | if err != nil { 20 | return nil, err 21 | } 22 | return pixel.PictureDataFromImage(img), nil 23 | } 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | addons: 4 | apt: 5 | packages: 6 | - xorg-dev 7 | - libx11-dev 8 | - libxrandr-dev 9 | - libxinerama-dev 10 | - libxcursor-dev 11 | - libxi-dev 12 | go: 13 | - 1.8 14 | - 1.7.4 15 | - tip 16 | install: 17 | - go get -v github.com/jteeuwen/go-bindata 18 | - go get -t ./... 19 | before_script: 20 | - gofmt -l -s -w . 21 | - make 22 | script: 23 | - go test -i -race ./... 24 | - go test -v -race ./... 25 | after_script: 26 | - make clean 27 | 28 | -------------------------------------------------------------------------------- /librpg/stats.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import "fmt" 4 | 5 | type Stats struct { 6 | Strength float64 7 | Wisdom float64 8 | Intelligence float64 9 | Vitality float64 10 | XP uint64 11 | Kills uint64 12 | Score uint64 13 | } 14 | 15 | func (s Stats) String() string { 16 | f := ` 17 | === CHARACTER STATS === 18 | STR %v 19 | WIS %v 20 | INT %v 21 | VIT %v 22 | XP %v 23 | Kills %v 24 | Score %v 25 | ` 26 | 27 | return fmt.Sprintf(f, int(s.Strength), int(s.Wisdom), int(s.Intelligence), int(s.Vitality), 28 | s.XP, s.Kills, s.Score) 29 | } 30 | -------------------------------------------------------------------------------- /librpg/title.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/faiface/pixel" 7 | "github.com/faiface/pixel/pixelgl" 8 | "github.com/faiface/pixel/text" 9 | "golang.org/x/image/colornames" 10 | "golang.org/x/image/font/basicfont" 11 | ) 12 | 13 | func UI() *text.Text { 14 | var atlas *text.Atlas 15 | atlas = text.NewAtlas(basicfont.Face7x13, text.ASCII) 16 | textbox := text.New(pixel.V(0, 0), atlas) 17 | textbox.Color = colornames.Grey 18 | fmt.Fprintf(textbox, "Hello\n") 19 | return textbox 20 | 21 | } 22 | func textFrame(win *pixelgl.Window) { 23 | } 24 | -------------------------------------------------------------------------------- /librpg/regions.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/aerth/rpc/librpg/common" 7 | ) 8 | 9 | type Region struct { 10 | Name string 11 | ID int 12 | Portals map[string]int // region id 13 | Map []common.Object 14 | DObjects []*DObject 15 | } 16 | 17 | func (r Region) String() string { 18 | if r.Name == "" { 19 | return "unnamed region" 20 | } 21 | return "region " + r.Name 22 | } 23 | 24 | func (w *World) NewRegion(name string, mapobjects []common.Object) *Region { 25 | r := new(Region) 26 | r.ID = len(w.Regions) 27 | r.Map = mapobjects 28 | r.Name = name 29 | w.Regions = append(w.Regions, r) 30 | log.Println("added new region:", r) 31 | return r 32 | } 33 | -------------------------------------------------------------------------------- /cmd/mapgen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | "github.com/aerth/rpc/librpg/common" 10 | "github.com/aerth/rpc/librpg/maps" 11 | ) 12 | 13 | func SaveMap(olist []common.Object) { 14 | os.MkdirAll("maps", 0755) 15 | b, err := json.Marshal(olist) 16 | if err != nil { 17 | log.Println(err) 18 | return 19 | } 20 | f, err := ioutil.TempFile("maps", "map") 21 | if err != nil { 22 | log.Println(err) 23 | return 24 | } 25 | _, err = f.Write(b) 26 | if err != nil { 27 | log.Println(err) 28 | return 29 | } 30 | log.Println("saved file:", f.Name()) 31 | } 32 | func main() { 33 | olist := maps.GenerateMap("") 34 | SaveMap(olist) 35 | } 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aerth/rpc 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/aerth/rpg v0.0.0-20180527235611-ef3f328702fd 7 | github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482 8 | github.com/faiface/pixel v0.10.0 9 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 10 | golang.org/x/image v0.0.0-20220617043117-41969df76e82 11 | ) 12 | 13 | require ( 14 | github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 // indirect 15 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 // indirect 16 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect 17 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 // indirect 18 | github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 // indirect 19 | github.com/pkg/errors v0.8.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /librpg/maps/miniworld.go: -------------------------------------------------------------------------------- 1 | package maps 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/aerth/rpc/librpg/common" 7 | "github.com/faiface/pixel" 8 | ) 9 | 10 | type MiniWorld struct { 11 | Tiles []common.Object 12 | } 13 | 14 | // Tile scans tiles and returns the first tile located at dot 15 | func (w *MiniWorld) Tile(dot pixel.Vec) common.Object { 16 | if w.Tiles == nil { 17 | log.Println("nil tiles!") 18 | return common.Object{W: w, Type: common.O_BLOCK} 19 | } 20 | 21 | if len(w.Tiles) == 0 { 22 | log.Println("no tiles to look in") 23 | return common.Object{W: w, Type: common.O_BLOCK} 24 | } 25 | for i := len(w.Tiles) - 1; i >= 0; i-- { 26 | if w.Tiles[i].Rect.Contains(dot) { 27 | ob := w.Tiles[i] 28 | ob.W = w 29 | return ob 30 | } 31 | } 32 | // log.Println("no tiles found at location:", dot) 33 | // panic("bug") 34 | return common.Object{W: w, Type: common.O_BLOCK} 35 | } 36 | -------------------------------------------------------------------------------- /librpg/names_test.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestGenerateName(t *testing.T) { 11 | for i := 0; i < 100; i++ { 12 | log.Printf("%s from %s", GenerateName(), strings.Title(GenerateWord())) 13 | } 14 | } 15 | 16 | func TestRand(t *testing.T) { 17 | for i := 0; i < 100; i++ { 18 | 19 | switch rand.Intn(3) { 20 | case 0: 21 | log.Println("0") 22 | case 1: 23 | log.Println("1") 24 | case 2: 25 | log.Println("2") 26 | case 3: // never hits 27 | log.Println("3") 28 | } 29 | } 30 | } 31 | 32 | func TestGenerateItemName(t *testing.T) { 33 | for i := 0; i < 100; i++ { 34 | item := createItemLoot() 35 | log.Printf(GenerateItemName(), item) 36 | } 37 | } 38 | 39 | func TestMagicLoot(t *testing.T) { 40 | for i := 0; i < 100; i++ { 41 | inv := RandomLoot() 42 | for _, item := range inv { 43 | log.Println(item) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /librpg/common/strings.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output librpg/common/strings.go -type ObjectType ./librpg/common"; DO NOT EDIT. 2 | 3 | package common 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[O_NONE-0] 12 | _ = x[O_TILE-1] 13 | _ = x[O_BLOCK-2] 14 | _ = x[O_INVISIBLE-3] 15 | _ = x[O_SPECIAL-4] 16 | _ = x[O_WIN-5] 17 | _ = x[O_DYNAMIC-6] 18 | } 19 | 20 | const _ObjectType_name = "O_NONEO_TILEO_BLOCKO_INVISIBLEO_SPECIALO_WINO_DYNAMIC" 21 | 22 | var _ObjectType_index = [...]uint8{0, 6, 12, 19, 30, 39, 44, 53} 23 | 24 | func (i ObjectType) String() string { 25 | if i < 0 || i >= ObjectType(len(_ObjectType_index)-1) { 26 | return "ObjectType(" + strconv.FormatInt(int64(i), 10) + ")" 27 | } 28 | return _ObjectType_name[_ObjectType_index[i]:_ObjectType_index[i+1]] 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 aerth 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | 11 | -------------------------------------------------------------------------------- /librpg/common/path.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | astar "github.com/beefsack/go-astar" 5 | "github.com/faiface/pixel" 6 | ) 7 | 8 | func (o Object) PathNeighbors() []astar.Pather { 9 | neighbors := []astar.Pather{} 10 | of := 32.0 11 | //of = 24.0 12 | 13 | for _, offset := range [][]float64{ 14 | {-of, 0}, 15 | {of, 0}, 16 | {0, -of}, 17 | {0, of}, 18 | //{of, -of}, 19 | //{-of, -of}, 20 | //{of, of}, 21 | //{-of, of}, 22 | } { 23 | n := o.W.Tile(pixel.V(o.Rect.Center().X+offset[0], o.Rect.Center().Y+offset[1])) 24 | if n.Type == O_TILE { 25 | neighbors = append(neighbors, n) 26 | } 27 | } 28 | return neighbors 29 | } 30 | 31 | func (o Object) PathEstimatedCost(to astar.Pather) float64 { 32 | toT := to.(Object) 33 | absX := toT.Rect.Center().X - o.Rect.Center().X 34 | if absX < 0 { 35 | absX = -absX 36 | } 37 | absY := toT.Rect.Center().Y - o.Rect.Center().Y 38 | if absY < 0 { 39 | absY = -absY 40 | } 41 | return float64(absX + absY) 42 | 43 | } 44 | 45 | func (o Object) PathNeighborCost(to astar.Pather) float64 { 46 | toT := to.(Object) 47 | cost := tileCosts[toT.Type] 48 | return cost 49 | } 50 | 51 | // KindCosts map tile kinds to movement costs. 52 | 53 | var tileCosts = map[ObjectType]float64{ 54 | O_NONE: 30.00, 55 | O_BLOCK: 30.00, 56 | O_INVISIBLE: 3.00, 57 | O_SPECIAL: 0.00, 58 | O_TILE: 1.00, 59 | O_WIN: 0.00, 60 | O_DYNAMIC: 3.00, 61 | } 62 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | GRN=\e[32m 2 | RED=\e[31m 3 | RST=\e[0m 4 | 5 | build: 6 | GOBIN=${PWD}/bin/ go install ./cmd/... 7 | @${MAKE} success 8 | help: 9 | @echo 'debian: install libgl1-mesa-dev and xorg-dev packages' 10 | 11 | dev: clean generate embed-assets build 12 | 13 | generate: 14 | # stringer -output librpg/common/strings.go -type EntityType,EntityState,ItemType,ObjectType,animState,ActionType,StatusEffect,DObjectType librpg/common 15 | stringer -output librpg/strings.go -type ActionType,EntityState,EntityType,ItemType,animState,StatusEffect ./librpg/ 16 | stringer -output librpg/common/strings.go -type ObjectType ./librpg/common 17 | 18 | 19 | 20 | embed-assets: 21 | @test -x ${shell which go-bindata} && \ 22 | rm assets/assets.go || true 23 | @test -x ${shell which go-bindata} && \ 24 | go-bindata -o assets/assets.go -pkg assets -prefix assets/ ./assets/... 25 | 26 | success: 27 | @printf '\n\n' 28 | @printf '${GRN}you have a new game${RST}' 29 | @printf '\n\n' 30 | 31 | fail: 32 | @printf 'build failed. please install dependencies and try again.\nlibgl1-mesa-dev and xorg-dev' 33 | 34 | clean: 35 | rm -rf ./bin 36 | 37 | key: 38 | test -f rpg.key || ssh-keygen -f rpg.key 39 | 40 | pdf: 41 | test -f p.debug && \ 42 | go tool pprof -pdf p.debug > p.pdf && echo p.pdf created 43 | 44 | install: 45 | install mapmaker /usr/local/bin/ae-mapmaker 46 | install aerpg /usr/local/bin/aerpg 47 | install mapgen /usr/local/bin/ae-mapgen 48 | 49 | -------------------------------------------------------------------------------- /librpg/names.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | var ( 11 | vowels = []rune("aeiouy") // y! 12 | consonants = []rune("bcdfghjklmnpqrstvwxz") 13 | ) 14 | 15 | func init() { 16 | rand.Seed(time.Now().UnixNano()) 17 | } 18 | 19 | func GenerateWord() string { 20 | var name []rune 21 | length := rand.Intn(6) + 4 22 | pallet := append(vowels, consonants...) 23 | r := pallet[rand.Intn(len(pallet))] 24 | name = []rune{r} 25 | vowel := (strings.Index(string(name[0]), string(vowels)) != -1) 26 | for i := 0; i < length; i++ { 27 | if vowel && rand.Intn(10) > 3 { 28 | vowel = false 29 | name = append([]rune{consonants[rand.Intn(len(consonants))]}, name...) 30 | } else { 31 | vowel = true 32 | name = append([]rune{vowels[rand.Intn(len(vowels))]}, name...) 33 | } 34 | } 35 | return string(name) 36 | } 37 | 38 | func GenerateName() string { 39 | return strings.Title(GenerateWord()) + " " + strings.Title(GenerateWord()) 40 | } 41 | 42 | func GenerateItemName() string { 43 | // can be constants 44 | adjectives := []string{"Perfect", "Imperfect", "Broken", "Magical", "Rare", "Ethereal"} 45 | mult := []string{"Luck", "Health", "Intelligence"} 46 | 47 | // generate name 48 | var prefix, suffix string 49 | prefix = adjectives[rand.Intn(len(adjectives))] 50 | suffix = mult[rand.Intn(len(mult))] 51 | switch rand.Intn(100) { 52 | case 1, 2, 3, 4, 5, 6, 7, 8, 9, 10: 53 | return fmt.Sprintf("%s %%s", strings.Title(prefix)) 54 | case 20, 21, 22, 23, 24: 55 | return fmt.Sprintf("%s's %s %%s", strings.Title(GenerateWord()), strings.Title(prefix)) 56 | case 30, 60, 90: 57 | return fmt.Sprintf("%s %%s of %s", strings.Title(prefix), suffix) 58 | default: 59 | return "%s" 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /librpg/inventory.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/faiface/pixel" 7 | "github.com/faiface/pixel/imdraw" 8 | "github.com/faiface/pixel/pixelgl" 9 | "golang.org/x/image/colornames" 10 | ) 11 | 12 | func InventoryLoop(win *pixelgl.Window, world *World) { 13 | text := NewText(28) 14 | text.WriteString("\tGAME PAUSED\n") 15 | text.WriteString("\tESC or any key to return\n\tUse PGUP/PGDOWN to scroll\n\n") 16 | text.WriteString(world.Char.Stats.String()) 17 | text.WriteString(fmt.Sprintf("Level %v\nHealth: %v\nMana: %v\nXP: %v/%v", world.Char.Level, world.Char.Health, world.Char.Mana, world.Char.Stats.XP, world.Char.NextLevel())) 18 | for _, item := range world.Char.Inventory { 19 | if item.Effect != nil { 20 | text.WriteString(fmt.Sprintf("\n Effects: %q", item.Name)) 21 | 22 | } 23 | } 24 | imd := imdraw.New(nil) 25 | batch := pixel.NewBatch(&pixel.TrianglesData{}, nil) 26 | text.WriteString("\n\n===INVENTORY===\n" + FormatItemList(world.Char.Inventory)) 27 | var page = 600.00 28 | if win.Bounds().Max.Y < 800 { 29 | page = 500 30 | } 31 | xpage := 30.00 32 | for !win.Closed() { 33 | 34 | // controls 35 | switch { 36 | default: 37 | case win.JustPressed(pixelgl.KeyPageUp) || win.JustPressed(pixelgl.KeyUp): 38 | page -= 100 39 | if page < 500 { 40 | page = 500 41 | } 42 | case win.JustPressed(pixelgl.KeyPageDown) || win.JustPressed(pixelgl.KeyDown): 43 | page += 100 44 | case win.JustPressed(pixelgl.KeyLeft): 45 | xpage += 100 46 | case win.JustPressed(pixelgl.KeyRight): 47 | xpage -= 100 48 | } 49 | 50 | win.Clear(colornames.Black) 51 | imd.Color = pixel.ToRGBA(colornames.Darkslategrey).Scaled(0.01) 52 | 53 | imd.Push(pixel.V(0, 0), win.Bounds().Max) 54 | imd.Rectangle(0) 55 | imd.Color = colornames.Green 56 | imd.Push(pixel.V(0, 0), win.Bounds().Max) 57 | imd.Rectangle(30) 58 | imd.Color = colornames.Yellow 59 | imd.Push(pixel.V(0, 0), win.Bounds().Max) 60 | imd.Rectangle(20) 61 | imd.Color = colornames.Red 62 | imd.Push(pixel.V(0, 0), win.Bounds().Max) 63 | imd.Rectangle(10) 64 | 65 | // break loop 66 | if win.Typed() != "" || win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyEnter) { 67 | break 68 | } 69 | 70 | // draw text 71 | imd.Draw(win) 72 | batch.Draw(win) 73 | text.Draw(win, pixel.IM.Moved(pixel.V(xpage, page))) 74 | 75 | // update window 76 | win.Update() 77 | 78 | // set title 79 | win.SetTitle("AERPG inventory") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /librpg/newworld.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | 10 | "golang.org/x/image/colornames" 11 | 12 | "github.com/aerth/rpc/librpg/common" 13 | "github.com/aerth/rpg/assets" 14 | "github.com/faiface/pixel" 15 | "github.com/faiface/pixel/pixelgl" 16 | ) 17 | 18 | func NewGame(win *pixelgl.Window, difficulty int, leveltest string, worldseed string) { 19 | world := NewWorld(leveltest, difficulty, worldseed) 20 | if world == nil { 21 | log.Println("bad world") 22 | os.Exit(111) 23 | } 24 | // have window, have world 25 | if err := world.drawTiles("tileset.png"); err != nil { 26 | log.Println("bad tiles", err) 27 | os.Exit(111) 28 | } 29 | 30 | if TitleMenu(win) { 31 | os.Exit(0) 32 | } 33 | var camPos = pixel.V(0, 0) 34 | for !win.Closed() { 35 | if win.Pressed(pixelgl.KeyLeft) { 36 | camPos.X-- 37 | log.Println(camPos) 38 | } 39 | if win.Pressed(pixelgl.KeyRight) { 40 | camPos.X++ 41 | log.Println(camPos) 42 | } 43 | if win.Pressed(pixelgl.KeyUp) { 44 | camPos.Y++ 45 | log.Println(camPos) 46 | } 47 | if win.Pressed(pixelgl.KeyDown) { 48 | camPos.Y-- 49 | log.Println(camPos) 50 | } 51 | win.SetMatrix(pixel.IM.Moved(camPos)) 52 | win.Clear(colornames.Green) 53 | world.Draw(win) 54 | win.Update() 55 | } 56 | os.Exit(111) 57 | } 58 | 59 | // LoadMapFile from disk 60 | func (w *World) LoadMapFile(path string) error { 61 | b, err := ioutil.ReadFile(path) 62 | if err != nil { 63 | return err 64 | } 65 | return w.loadmap(b) 66 | } 67 | 68 | // LoadMap from embedded assets 69 | func (w *World) LoadMap(path string) error { 70 | b, err := assets.Asset(path) 71 | if err != nil { 72 | return err 73 | } 74 | return w.loadmap(b) 75 | } 76 | func (w *World) loadmap(b []byte) error { 77 | var things = []common.Object{} 78 | err := json.Unmarshal(b, &things) 79 | if err != nil { 80 | return fmt.Errorf("invalid map: %v", err) 81 | } 82 | return w.injectMap(things) 83 | } 84 | 85 | func (w *World) InjectMap(things []common.Object) error { 86 | return w.injectMap(things) 87 | } 88 | 89 | func (w *World) injectMap(things []common.Object) error { 90 | total := len(things) 91 | for i, t := range things { 92 | t.W = w 93 | t.Rect = common.DefaultSpriteRectangle.Moved(t.Loc) 94 | switch t.SpriteNum { 95 | case 53: // water 96 | t.Type = common.O_BLOCK 97 | default: 98 | } 99 | 100 | switch t.Type { 101 | case common.O_BLOCK: 102 | //log.Printf("%v/%v block object: %s %v %s", i, total, t.Loc, t.SpriteNum, t.Type) 103 | w.Blocks = append(w.Blocks, t) 104 | case common.O_TILE: 105 | //log.Printf("%v/%v tile object: %s %v %s", i, total, t.Loc, t.SpriteNum, t.Type) 106 | w.Tiles = append(w.Tiles, t) 107 | 108 | default: // 109 | log.Printf("%v/%v skipping bad object: %s %v %s", i, total, t.Loc, t.SpriteNum, t.Type) 110 | } 111 | } 112 | log.Printf("map has %v blocks, %v tiles", len(w.Blocks), len(w.Tiles)) 113 | if len(w.Blocks) == 0 && len(w.Tiles) == 0 { 114 | return fmt.Errorf("invalid map") 115 | } 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /librpg/common/object.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/faiface/pixel" 8 | "github.com/faiface/pixel/imdraw" 9 | "golang.org/x/image/colornames" 10 | ) 11 | 12 | var DefaultSpriteRectangle = pixel.R(-16, -16, 16, 16) 13 | 14 | type ObjectType int 15 | 16 | const ( 17 | O_NONE ObjectType = iota 18 | O_TILE 19 | O_BLOCK 20 | O_INVISIBLE 21 | O_SPECIAL 22 | O_WIN 23 | O_DYNAMIC // loot, doors 24 | ) 25 | 26 | type Object struct { 27 | Loc pixel.Vec `json:"L"` 28 | Rect pixel.Rect `json:"-"` 29 | Type ObjectType `json:"T"` 30 | P ObjectProperties `json:",omitempty"` 31 | SpriteNum int `json:"S,omitempty"` 32 | Sprite *pixel.Sprite `json:"-"` 33 | W interface { 34 | Tile(dot pixel.Vec) Object 35 | } `json:"-"` 36 | // Contains []Item `json:"-"` 37 | } 38 | 39 | func (o Object) String() string { 40 | return fmt.Sprintf("%s %s %s %v", o.Loc, o.Rect, o.Type, o.SpriteNum) 41 | } 42 | 43 | type ObjectProperties struct { 44 | Invisible bool `json:",omitempty"` 45 | // Tile bool `json:",omitempty"` 46 | // Block bool `json:",omitempty"` 47 | Special bool `json:",omitempty"` 48 | } 49 | 50 | func NewTile(loc pixel.Vec) Object { 51 | return Object{ 52 | Loc: loc, 53 | Rect: pixel.Rect{loc.Sub(pixel.V(16, 16)), loc.Add(pixel.V(16, 16))}, 54 | Type: O_TILE, 55 | } 56 | } 57 | func NewBlock(loc pixel.Vec) Object { 58 | return Object{ 59 | Loc: loc, 60 | Rect: pixel.Rect{loc.Sub(pixel.V(16, 16)), loc.Add(pixel.V(16, 16))}, 61 | Type: O_BLOCK, 62 | } 63 | } 64 | 65 | func NewTileBox(rect pixel.Rect) Object { 66 | return Object{ 67 | Rect: rect, 68 | Type: O_TILE, 69 | } 70 | } 71 | func NewBlockBox(rect pixel.Rect) Object { 72 | return Object{ 73 | Rect: rect, 74 | Type: O_BLOCK, 75 | } 76 | } 77 | 78 | var TransparentBlue = pixel.ToRGBA(colornames.Blue).Scaled(0.4) 79 | var TransparentRed = pixel.ToRGBA(colornames.Red).Scaled(0.4) 80 | var TransparentPurple = pixel.ToRGBA(colornames.Purple).Scaled(0.4) 81 | 82 | func (o Object) Highlight(win pixel.Target, color pixel.RGBA) { 83 | imd := imdraw.New(nil) 84 | imd.Color = color 85 | imd.Push(o.Rect.Min, o.Rect.Max) 86 | imd.Rectangle(1) 87 | imd.Draw(win) 88 | } 89 | func (o Object) Draw(win pixel.Target, spritesheet pixel.Picture, sheetFrames []*pixel.Sprite, scaled float64) { 90 | // r := pixel.Rect{o.Loc, o.w.Char.Rect.Center()} 91 | // sz := r.Size() 92 | // if sz.X > 1000 || sz.Y > 1000 { 93 | // return 94 | // } 95 | if o.P.Invisible { 96 | return 97 | } 98 | if o.Sprite == nil && o.Type != O_DYNAMIC { 99 | if 0 > o.SpriteNum || o.SpriteNum > len(sheetFrames) { 100 | log.Printf("unloadable sprite: %v/%v", o.SpriteNum, len(sheetFrames)) 101 | return 102 | } 103 | o.Sprite = sheetFrames[o.SpriteNum] 104 | } 105 | if o.Sprite == nil && o.Type == O_DYNAMIC { 106 | o.Sprite = sheetFrames[0] 107 | } 108 | // if o.Loc == pixel.ZV && o.Rect.Size().Y != 32 { 109 | // log.Println(o.Rect.Size(), "cool rectangle", o.SpriteNum) 110 | // DrawPattern(win, o.Sprite, o.Rect, 0) 111 | // } else { 112 | if scaled != 0.00 { 113 | o.Sprite.Draw(win, pixel.IM.Scaled(pixel.ZV, scaled).Moved(o.Loc)) 114 | return 115 | } 116 | 117 | o.Sprite.Draw(win, pixel.IM.Moved(o.Loc)) 118 | // } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /librpg/common/patterns.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "image/color" 5 | "log" 6 | 7 | "github.com/faiface/pixel" 8 | "github.com/faiface/pixel/imdraw" 9 | "github.com/faiface/pixel/pixelgl" 10 | "golang.org/x/image/colornames" 11 | ) 12 | 13 | func DrawPatternObject(spritenum int, objecttype ObjectType, bounds pixel.Rect, width float64) []Object { 14 | var objects []Object 15 | size := pixel.Rect{pixel.V(-16, -16), pixel.V(16, 16)} 16 | //size := DefaultSpriteRectangle 17 | for y := bounds.Min.Y; y < bounds.Max.Y; y = y + size.H() { 18 | for x := bounds.Min.X; x < bounds.Max.X; x = x + size.W() { 19 | o := Object{ 20 | Loc: pixel.V(x, y), 21 | Rect: size.Moved(pixel.V(x, y)), 22 | Type: objecttype, 23 | SpriteNum: spritenum, 24 | } 25 | objects = append(objects, o) 26 | } 27 | } 28 | return objects 29 | } 30 | 31 | func DrawPattern(canvas pixel.Target, sprite *pixel.Sprite, bounds pixel.Rect, width float64) { 32 | if bounds.Size() == pixel.ZV { 33 | return 34 | } 35 | var i int 36 | for y := bounds.Min.Y; y < bounds.Max.Y; y = y + sprite.Frame().H() { 37 | for x := bounds.Min.X; x < bounds.Max.X; x = x + sprite.Frame().W() { 38 | sprite.Draw(canvas, pixel.IM.Moved(pixel.V(x, y))) 39 | i++ 40 | } 41 | } 42 | //log.Printf("Draw pattern: %v iterations", i) 43 | } 44 | 45 | func Drawbg(canvas *pixelgl.Canvas) { 46 | imd := imdraw.New(nil) 47 | var ( 48 | bounds = canvas.Bounds() 49 | squaresize = 2.00 50 | ) 51 | for x := 0.00; x < bounds.W(); x = x + squaresize { 52 | batch := pixel.NewBatch(&pixel.TrianglesData{}, canvas) 53 | invert := int((x/squaresize))%2 ^ 1 54 | for y := 0.00; y < bounds.H(); y = y + squaresize { 55 | colored := float64(int((y/squaresize))%2 ^ invert) 56 | //xor := float64(int(colored) ^ invert)) 57 | imd.Clear() 58 | imd.Color = RandomColor() 59 | imd.Push(pixel.V(x, y+squaresize)) 60 | imd.Push(pixel.V(x, y+squaresize).Add(pixel.V(squaresize, squaresize))) 61 | imd.Rectangle(colored) 62 | imd.Draw(batch) 63 | 64 | } 65 | log.Printf("LOADING: %v", int(100*(x/bounds.W()))) 66 | batch.Draw(canvas) 67 | } 68 | } 69 | 70 | func DrawBase(canvas *pixelgl.Canvas) { 71 | batch := pixel.NewBatch(&pixel.TrianglesData{}, canvas) 72 | imd := imdraw.New(nil) 73 | imd.Clear() 74 | imd.Color = colornames.Black 75 | imd.Push(pixel.V(100.00, 100.00)) 76 | imd.Push(pixel.V(-100.00, -100.00)) 77 | imd.Rectangle(0) 78 | 79 | imd.Color = RandomColor() 80 | imd.Push(pixel.V(100.00, 100.00)) 81 | imd.Push(pixel.V(-100.00, -100.00)) 82 | imd.Rectangle(20) 83 | imd.Color = RandomColor() 84 | imd.Push(pixel.V(100.00, 100.00)) 85 | imd.Push(pixel.V(-100.00, -100.00)) 86 | imd.Rectangle(10) 87 | imd.Draw(batch) 88 | batch.Draw(canvas) 89 | } 90 | func DrawBar(imd *imdraw.IMDraw, color color.RGBA, cur, max float64, rect pixel.Rect) { 91 | rect = rect.Norm() 92 | imd.Color = color 93 | if max < cur { 94 | cur, max = max, cur 95 | } 96 | percent := cur / max 97 | // if rect.Max.Y-rect.Min.Y > 10 { 98 | // rect.Max.Y = rect.Min.Y + 10 99 | // } 100 | //one.Y++ 101 | imd.Push(rect.Min, rect.Max) 102 | imd.Rectangle(1) 103 | pt := pixel.V(rect.Min.X+((rect.Max.X-rect.Min.X)*percent), rect.Max.Y) 104 | 105 | if pt.X < rect.Min.X { 106 | pt.X = rect.Min.X 107 | } 108 | imd.Push(rect.Min, pt) 109 | imd.Rectangle(0) 110 | 111 | } 112 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aerth/rpg v0.0.0-20180527235611-ef3f328702fd h1:y1A0KCIu/fwdfTdDRmszKTpjO6cP9TTUPGaMeeJtNUo= 2 | github.com/aerth/rpg v0.0.0-20180527235611-ef3f328702fd/go.mod h1:pFg8k+xq5U7WZ+QkupPV3n8rjyCEde8Cf3lh8LPbvBk= 3 | github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482 h1:p4g4uok3+r6Tg6fxXEQUAcMAX/WdK6WhkQW9s0jaT7k= 4 | github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482/go.mod h1:Cu3t5VeqE8kXjUBeNXWQprfuaP5UCIc5ggGjgMx9KFc= 5 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0= 8 | github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g= 9 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= 10 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= 11 | github.com/faiface/pixel v0.10.0 h1:EHm3ZdQw2Ck4y51cZqFfqQpwLqNHOoXwbNEc9Dijql0= 12 | github.com/faiface/pixel v0.10.0/go.mod h1:lU0YYcW77vL0F1CG8oX51GXurymL45MXd57otHNLK7A= 13 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= 14 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= 15 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE= 16 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 17 | github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 h1:THttjeRn1iiz69E875U6gAik8KTWk/JYAHoSVpUxBBI= 18 | github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= 19 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 20 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 21 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 22 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 27 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 28 | golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 29 | golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 30 | golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw= 31 | golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= 32 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 33 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 34 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 35 | -------------------------------------------------------------------------------- /librpg/maps/map.go: -------------------------------------------------------------------------------- 1 | package maps 2 | 3 | import ( 4 | "crypto/md5" 5 | "log" 6 | "math" 7 | "math/big" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/aerth/rpc/librpg/common" 12 | "github.com/beefsack/go-astar" 13 | "github.com/faiface/pixel" 14 | ) 15 | 16 | var BOUNDS float64 = 700 17 | var numbers = "0123456789" 18 | 19 | // func MapSeedInit() { 20 | // rand.Seed(time.Now().UnixNano()) 21 | // // seed or random 22 | // if len(os.Args) == 2 { 23 | // if strings.HasPrefix(os.Args[1], "-h") { 24 | // fmt.Println("Usage:") 25 | // fmt.Println("\tmapgen [seed]") 26 | // fmt.Println("Example:") 27 | // fmt.Println("\tmapgen mycoolseed") 28 | 29 | // os.Exit(111) 30 | // } 31 | // hashb := md5.Sum([]byte(os.Args[1])) 32 | // hash := []byte(fmt.Sprintf("%x", hashb)) 33 | // var seed []byte 34 | // for _, b := range hash { 35 | // if bytes.IndexAny([]byte{b}, numbers) != -1 { 36 | // log.Println(string(b), "is a number") 37 | // seed = append(seed, b) 38 | // } else { 39 | // log.Println(string(b), "is a letter") 40 | // } 41 | 42 | // } 43 | // worldseed, err := strconv.ParseInt(string(seed), 10, 64) 44 | // if err != nil { 45 | // log.Println(err) 46 | // } 47 | // rand.Seed(worldseed) 48 | // log.Printf("Using world seed: %q -> %v", os.Args[1], worldseed) 49 | // log.Printf("Hash: %q", string(hash)) 50 | // } 51 | // // create maps dir if not exist 52 | // os.Mkdir("maps", 0755) 53 | // } 54 | func GenerateMap(seed string) []common.Object { 55 | if seed == "" { 56 | rand.Seed(time.Now().UnixMicro()) 57 | } else { 58 | b := md5.Sum([]byte(seed)) 59 | rand.Seed(big.NewInt(0).SetBytes((b[:])).Int64()) 60 | } 61 | var olist []common.Object 62 | t := common.O_TILE 63 | for i := 0; i < 100; i++ { 64 | 65 | currentThing := 20 // grass 66 | t = common.O_TILE 67 | if i%3 == 0 { 68 | currentThing = 53 // water 69 | t = common.O_BLOCK 70 | } 71 | xmin := randfloat() 72 | ymin := randfloat() 73 | xmax := randfloat() 74 | ymax := randfloat() 75 | box := pixel.R(xmin, ymin, xmax, ymax).Norm() 76 | log.Println(t, box) 77 | pattern := common.DrawPatternObject(currentThing, t, box, 100) 78 | for _, obj := range pattern { 79 | if common.GetObjects(olist, obj.Loc) == nil { 80 | olist = append(olist, obj) 81 | } 82 | } 83 | 84 | } 85 | 86 | // make dummy world for path finding 87 | world := &MiniWorld{Tiles: olist} 88 | 89 | // world.Tiles = common.GetTiles(olist) 90 | 91 | // detect islands, make bridges 92 | oldlist := olist 93 | olist = nil 94 | spot := world.Tile(common.FindRandomTile(oldlist)) 95 | for _, o := range oldlist { 96 | o.W = world 97 | _, _, found := astar.Path(o, spot) 98 | if o.Type == common.O_TILE && !found { 99 | log.Println("found island tile", o) 100 | } else { 101 | olist = append(olist, o) 102 | } 103 | } 104 | 105 | // fill in with water blocks 106 | waterworld := common.DrawPatternObject(53, common.O_BLOCK, pixel.R(-BOUNDS, -BOUNDS, BOUNDS, BOUNDS), 0) 107 | for _, water := range waterworld { 108 | if common.GetObjects(olist, water.Loc) == nil { 109 | olist = append(olist, water) 110 | } 111 | } 112 | 113 | return olist 114 | } 115 | 116 | func randfloat() float64 { 117 | step := 32.00 118 | f := float64(rand.Intn(int(BOUNDS))) 119 | f = math.Floor(f) 120 | f = float64(int(f/step)) * step 121 | switch rand.Intn(2) { 122 | case 0: 123 | f = -f 124 | default: 125 | } 126 | 127 | return f 128 | 129 | } 130 | -------------------------------------------------------------------------------- /librpg/menu.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | "golang.org/x/image/colornames" 10 | 11 | "github.com/aerth/rpc/librpg/common" 12 | "github.com/faiface/pixel" 13 | "github.com/faiface/pixel/imdraw" 14 | "github.com/faiface/pixel/pixelgl" 15 | ) 16 | 17 | type Button struct { 18 | Name string 19 | Frame pixel.Rect 20 | } 21 | 22 | var version = "0.0.95" 23 | 24 | func Version() string { 25 | return "AERPG " + version 26 | } 27 | 28 | func TitleMenu(win *pixelgl.Window) (breakloop bool) { 29 | title := NewTitleText(64) 30 | text := NewText(36) 31 | dot := pixel.V(30, 400) 32 | title.Dot = dot 33 | title.Orig = title.Dot 34 | 35 | dot = pixel.V(30, 200) 36 | text.Dot = dot 37 | text.Orig = text.Dot 38 | 39 | fmt.Fprintf(title, "AERPG v%s\nPRESS ENTER", version) 40 | fmt.Fprintln(text, "https://github.com/aerth/rpg\n\nCTRL-Q to QUIT") 41 | 42 | win.Clear(colornames.Black) 43 | 44 | win.SetTitle("AERPG (https://github.com/aerth/rpg)") 45 | log.Println("AERPG (https://github.com/aerth/rpg)") 46 | log.Println("update often") 47 | 48 | tick := time.Tick(time.Second) 49 | var frames = 0 50 | for !win.Closed() { 51 | 52 | win.Clear(colornames.Black) 53 | title.Color = RandomColor() 54 | text.Draw(win, pixel.IM) 55 | title.Draw(win, pixel.IM) 56 | frames++ 57 | if win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ) { 58 | return true 59 | } 60 | if win.JustPressed(pixelgl.KeyEnter) { 61 | return false 62 | } 63 | win.Update() 64 | select { 65 | case <-tick: 66 | win.SetTitle(fmt.Sprintf("AERPG (https://github.com/aerth/rpg) %v fps", frames)) 67 | frames = 0 68 | default: 69 | 70 | } 71 | } 72 | log.Println("thanks for playing") 73 | return true 74 | } 75 | 76 | func (w *World) IsButton(buttons []Button, point pixel.Vec) (Button, func(win pixel.Target, world *World), bool) { 77 | 78 | for _, button := range buttons { 79 | if button.Frame.Contains(point) { 80 | switch button.Name { 81 | case "manastorm": 82 | return button, func(win pixel.Target, world *World) { 83 | world.Action(w.Char, w.Char.Rect.Center(), ManaStorm, OUT) 84 | }, true 85 | case "magicbullet": 86 | return button, func(win pixel.Target, world *World) { 87 | world.Action(w.Char, w.Char.Rect.Center(), MagicBullet, world.Char.Dir) 88 | }, true 89 | 90 | default: 91 | return button, func(win pixel.Target, world *World) { 92 | world.Message(fmt.Sprintf("Bad button %s", point)) 93 | }, true 94 | case "reset": 95 | return button, func(win pixel.Target, world *World) { 96 | world.Reset() 97 | // world.Char.Inventory = []Item{} 98 | }, true 99 | 100 | } 101 | } 102 | } 103 | 104 | return Button{}, nil, false 105 | } 106 | 107 | func (w *World) Exit(code int) { 108 | os.Exit(code) 109 | } 110 | 111 | func (c *Character) DrawBars(target pixel.Target, bounds pixel.Rect) { 112 | var barheight = 10.00 113 | var startY = 50.00 114 | imd := imdraw.New(nil) 115 | xp := float64(c.Stats.XP) 116 | next := float64(c.NextLevel()) 117 | rect := bounds 118 | rect.Min.Y = startY 119 | rect.Max.Y = rect.Min.Y + barheight 120 | common.DrawBar(imd, colornames.Red, float64(c.Health), float64(255), rect) 121 | common.DrawBar(imd, colornames.Blue, float64(c.Mana), 255.00, rect.Moved(pixel.V(0, barheight+1))) 122 | common.DrawBar(imd, colornames.Purple, xp, next, rect.Moved(pixel.V(0, (barheight*2)+1))) 123 | imd.Draw(target) 124 | } 125 | -------------------------------------------------------------------------------- /librpg/util.go: -------------------------------------------------------------------------------- 1 | // aerth game 2 | // copyright 2017 aerth 3 | package rpg 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "math" 9 | "math/rand" 10 | "os" 11 | "time" 12 | 13 | "golang.org/x/image/font" 14 | 15 | _ "image/png" 16 | 17 | "github.com/faiface/pixel" 18 | "github.com/golang/freetype/truetype" 19 | ) 20 | 21 | func init() { 22 | rand.Seed(time.Now().UnixNano()) 23 | } 24 | 25 | // Direction LEFT RIGHT DOWN UP 26 | type Direction int 27 | 28 | type animState int 29 | 30 | const ( 31 | LEFT Direction = iota 32 | RIGHT 33 | DOWN 34 | UP 35 | IN 36 | OUT 37 | UPLEFT 38 | UPRIGHT 39 | DOWNLEFT 40 | DOWNRIGHT 41 | ) 42 | 43 | const ( 44 | WEST = LEFT 45 | EAST = RIGHT 46 | NORTH = UP 47 | SOUTH = DOWN 48 | ) 49 | 50 | const ( 51 | Idle animState = iota 52 | Running 53 | ) 54 | 55 | func (d Direction) String() string { 56 | switch d { 57 | case LEFT: 58 | return "left" 59 | case RIGHT: 60 | return "right" 61 | case UP: 62 | return "up" 63 | case DOWN: 64 | return "down" 65 | case DOWNLEFT: 66 | return "down-left" 67 | case DOWNRIGHT: 68 | return "down-right" 69 | case UPLEFT: 70 | return "up-left" 71 | case UPRIGHT: 72 | return "up-right" 73 | case IN: 74 | return "within" 75 | case OUT: 76 | return "without" 77 | default: 78 | return fmt.Sprintf("invalid direction: %v", int(d)) 79 | } 80 | } 81 | 82 | func UnitToDirection(v pixel.Vec) Direction { 83 | v.X = math.Floor(v.X + 0.5) 84 | v.Y = math.Floor(v.Y + 0.5) 85 | switch v { 86 | default: 87 | return OUT 88 | case LEFT.V(): 89 | return LEFT 90 | case UPLEFT.V(): 91 | return UPLEFT 92 | case DOWNLEFT.V(): 93 | return DOWNLEFT 94 | case RIGHT.V(): 95 | return RIGHT 96 | case UPRIGHT.V(): 97 | return UPRIGHT 98 | case DOWNRIGHT.V(): 99 | return DOWNRIGHT 100 | case DOWN.V(): 101 | return DOWN 102 | case UP.V(): 103 | return UP 104 | } 105 | } 106 | 107 | func (d Direction) V() pixel.Vec { 108 | switch d { 109 | case LEFT: 110 | return pixel.V(-1, 0) 111 | case RIGHT: 112 | return pixel.V(1, 0) 113 | case UP: 114 | return pixel.V(0, 1) 115 | case DOWN: 116 | return pixel.V(0, -1) 117 | case UPRIGHT: 118 | return pixel.V(1, 1) 119 | case UPLEFT: 120 | return pixel.V(-1, 1) 121 | case DOWNLEFT: 122 | return pixel.V(-1, -1) 123 | case DOWNRIGHT: 124 | return pixel.V(1, -1) 125 | default: 126 | return pixel.V(0, 0) 127 | } 128 | } 129 | 130 | func RandomColor() pixel.RGBA { 131 | 132 | r := rand.Float64() 133 | g := rand.Float64() 134 | b := rand.Float64() 135 | len := math.Sqrt(r*r + g*g + b*b) 136 | //if len == 0 { 137 | // goto again 138 | //} 139 | return pixel.RGB(r/len, g/len, b/len) 140 | 141 | } 142 | 143 | func LoadTTF(path string, size float64) (font.Face, error) { 144 | file, err := os.Open(path) 145 | if err != nil { 146 | return nil, err 147 | } 148 | defer file.Close() 149 | 150 | data, err := ioutil.ReadAll(file) 151 | if err != nil { 152 | return nil, err 153 | } 154 | 155 | font, err := truetype.Parse(data) 156 | if err != nil { 157 | return nil, err 158 | } 159 | 160 | return truetype.NewFace(font, &truetype.Options{ 161 | Size: size, 162 | GlyphCacheEntries: 1, 163 | }), nil 164 | } 165 | 166 | // Distance between two vectors 167 | func Distance(v1, v2 pixel.Vec) float64 { 168 | r := pixel.Rect{v1, v2}.Norm() 169 | v1 = r.Min 170 | v2 = r.Max 171 | h := (v1.X - v2.X) * (v1.X - v2.X) 172 | v := (v1.Y - v2.Y) * (v1.Y - v2.Y) 173 | return Sqrt(h + v) 174 | } 175 | 176 | // ? 177 | func Sqrt(x float64) float64 { 178 | z := float64(2.) 179 | s := float64(0) 180 | for i := 0; i < 10; i++ { 181 | z = z - (z*z-x)/(2*z) 182 | if math.Abs(z-s) < 1e-10 { 183 | break 184 | } 185 | s = z 186 | } 187 | return z 188 | } 189 | -------------------------------------------------------------------------------- /librpg/text.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "unicode" 7 | 8 | "github.com/aerth/rpg/assets" 9 | "github.com/faiface/pixel" 10 | "github.com/faiface/pixel/imdraw" 11 | "github.com/faiface/pixel/pixelgl" 12 | "github.com/faiface/pixel/text" 13 | "github.com/golang/freetype/truetype" 14 | "golang.org/x/image/colornames" 15 | "golang.org/x/image/font" 16 | "golang.org/x/image/font/gofont/goregular" 17 | ) 18 | 19 | var GraphicRanges = []*unicode.RangeTable{ 20 | unicode.L, unicode.M, unicode.N, unicode.P, unicode.S, unicode.Zs, 21 | } 22 | 23 | func NewTextSmooth(size float64) *text.Text { 24 | font := ttfFromBytesMust(goregular.TTF, size) 25 | basicAtlas := text.NewAtlas(font, text.ASCII, text.RangeTable(unicode.Common)) 26 | basicTxt := text.New(pixel.V(0, 0), basicAtlas) 27 | return basicTxt 28 | } 29 | 30 | func NewTitleText(size float64) *text.Text { 31 | b, err := assets.Asset("font/admtas.ttf") 32 | if err != nil { 33 | panic(err) 34 | } 35 | font := ttfFromBytesMust(b, size) 36 | basicAtlas := text.NewAtlas(font, text.ASCII, text.RangeTable(unicode.Common)) 37 | basicTxt := text.New(pixel.V(0, 0), basicAtlas) 38 | basicTxt.Dot = pixel.V(10, 10) 39 | basicTxt.Orig = pixel.V(10, 10) 40 | return basicTxt 41 | 42 | } 43 | func NewText(size float64) *text.Text { 44 | b, err := assets.Asset("font/TerminusTTF-4.40.1.ttf") 45 | if err != nil { 46 | panic(err) 47 | } 48 | font := ttfFromBytesMust(b, size) 49 | basicAtlas := text.NewAtlas(font, text.ASCII, text.RangeTable(unicode.Common)) 50 | basicTxt := text.New(pixel.V(0, 0), basicAtlas) 51 | basicTxt.Dot = pixel.V(10, 10) 52 | basicTxt.Orig = pixel.V(10, 10) 53 | return basicTxt 54 | } 55 | 56 | func DrawText(winbounds pixel.Rect, t *text.Text, canvas pixel.Target, format string, i ...interface{}) { 57 | imd := imdraw.New(nil) 58 | color := pixel.ToRGBA(colornames.Darkslategrey) 59 | imd.Color = color.Scaled(0.5) 60 | imd.Push(pixel.V(0, winbounds.Max.Y-50), pixel.V(winbounds.Max.XY())) 61 | imd.Rectangle(0) 62 | imd.Push(pixel.V(0, 0), pixel.V(winbounds.Max.X, 80)) 63 | imd.Rectangle(0) 64 | imd.Draw(canvas) 65 | t.Dot = pixel.V(10, 10) 66 | t.Orig = pixel.V(10, 10) 67 | fmt.Fprintf(t, format, i...) 68 | t.Draw(canvas, pixel.IM) 69 | } 70 | 71 | func ttfFromBytesMust(b []byte, size float64) font.Face { 72 | ttf, err := truetype.Parse(b) 73 | if err != nil { 74 | panic(err) 75 | } 76 | return truetype.NewFace(ttf, &truetype.Options{ 77 | Size: size, 78 | GlyphCacheEntries: 1, 79 | }) 80 | } 81 | func DrawScore(winbounds pixel.Rect, t *text.Text, canvas pixel.Target, format string, i ...interface{}) { 82 | imd := imdraw.New(nil) 83 | color := pixel.ToRGBA(colornames.Darkslategrey) 84 | imd.Color = color.Scaled(0.9) 85 | imd.Push(pixel.V(0, winbounds.Max.Y-50), pixel.V(winbounds.Max.XY())) 86 | imd.Rectangle(0) 87 | imd.Push(pixel.V(0, 0), pixel.V(winbounds.Max.X, 80)) 88 | imd.Rectangle(0) 89 | imd.Draw(canvas) 90 | t.Dot = pixel.V(10, winbounds.Max.Y-40) 91 | t.Orig = t.Dot 92 | fmt.Fprintf(t, format, i...) 93 | t.Draw(canvas, pixel.IM) 94 | } 95 | 96 | func (w *World) Message(s string) { 97 | log.Println(s) 98 | } 99 | 100 | func (w *World) TextBox(win *pixelgl.Window, msg string) { 101 | if w.text == nil { 102 | w.text = NewText(36) 103 | } else { 104 | w.text.Clear() 105 | } 106 | imd := imdraw.New(nil) 107 | color := pixel.ToRGBA(colornames.Darkslategrey) 108 | imd.Color = color.Scaled(0.9) 109 | winbounds := win.Bounds() 110 | imd.Push(pixel.ZV, pixel.V(winbounds.Max.XY())) 111 | imd.Rectangle(0) 112 | 113 | w.text.Dot = pixel.V(10, 400) 114 | w.text.Orig = w.text.Dot 115 | fmt.Fprintf(w.text, msg) 116 | for !win.Closed() { 117 | win.Clear(colornames.Black) 118 | imd.Draw(win) 119 | w.text.Draw(win, pixel.IM) 120 | if win.JustPressed(pixelgl.KeySpace) { 121 | break 122 | } 123 | win.Update() 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /librpg/common/objects.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | 7 | "github.com/faiface/pixel" 8 | ) 9 | 10 | //var DefaultSpriteRectangle = pixel.R(-16, 0, 16, 32) 11 | //var DefaultSpriteRectangle = pixel.R(-16, 0, 16, 32) 12 | 13 | // assumes only tiles are given 14 | func FindRandomTile(os []Object) pixel.Vec { 15 | if len(os) == 0 { 16 | panic("no objects") 17 | } 18 | ob := os[rand.Intn(len(os))] 19 | if ob.Loc != pixel.ZV && ob.SpriteNum != 0 && ob.Type == O_TILE { 20 | return ob.Rect.Center() 21 | } 22 | return FindRandomTile(os) 23 | } 24 | 25 | func GetObjects(objects []Object, position pixel.Vec) []Object { 26 | var good []Object 27 | for _, o := range objects { 28 | if o.Rect.Contains(position) { 29 | good = append(good, o) 30 | } 31 | } 32 | return good 33 | } 34 | 35 | func GetTiles(objects []Object) []Object { 36 | var tiles []Object 37 | for _, o := range objects { 38 | if o.Type == O_TILE { 39 | tiles = append(tiles, o) 40 | } 41 | } 42 | return tiles 43 | } 44 | 45 | func TilesAt(objects []Object, position pixel.Vec) []Object { 46 | var good []Object 47 | all := GetObjects(objects, position) 48 | if len(all) > 0 { 49 | for _, o := range all { 50 | if DefaultSpriteRectangle.Moved(o.Loc).Contains(position) && o.Type == O_TILE { 51 | good = append(good, o) 52 | } 53 | 54 | } 55 | } 56 | return good 57 | 58 | } 59 | func GetObjectsAt(objects []Object, position pixel.Vec) []Object { 60 | var good []Object 61 | all := GetObjects(objects, position) 62 | if len(all) > 0 { 63 | for _, o := range all { 64 | if DefaultSpriteRectangle.Moved(o.Loc).Contains(position) { 65 | good = append(good, o) 66 | } 67 | 68 | } 69 | } 70 | return good 71 | 72 | } 73 | 74 | func GetBlocks(objects []Object, position pixel.Vec) []Object { 75 | var bad []Object 76 | all := GetObjects(objects, position) 77 | if len(all) > 0 { 78 | for _, o := range all { 79 | if o.Type == O_BLOCK { 80 | bad = append(bad, o) 81 | } 82 | 83 | } 84 | } 85 | return bad 86 | } 87 | 88 | // GetNeighbors gets the neighboring tiles 89 | func (o Object) GetNeighbors() []Object { 90 | neighbors := []Object{} 91 | of := 32.0 92 | for _, offset := range [][]float64{ 93 | {-of, 0}, 94 | {of, 0}, 95 | {0, -of}, 96 | {0, of}, 97 | } { 98 | if n := o.W.Tile(pixel.V(o.Rect.Center().X+offset[0], o.Rect.Center().Y+offset[1])); n.Type == o.Type { 99 | neighbors = append(neighbors, n) 100 | } 101 | } 102 | return neighbors 103 | 104 | } 105 | func TileNear(all []Object, loc pixel.Vec) Object { 106 | tile := TilesAt(all, loc) 107 | snap := 32.00 108 | loc.X = float64(int(loc.X/snap)) * snap 109 | loc.Y = float64(int(loc.Y/snap)) * snap 110 | radius := 1.00 111 | oloc := loc 112 | if len(tile) > 0 { 113 | oloc = tile[0].Loc 114 | } 115 | log.Println("looking for loc:", loc) 116 | for i := 0; i < len(all); i++ { 117 | loc.X += radius * 16 118 | loc.Y += radius * 16 119 | if loc == oloc { 120 | continue 121 | } 122 | log.Println("Checking loc", loc) 123 | os := TilesAt(all, loc) 124 | if len(os) > 0 { 125 | if os[0].Loc == pixel.ZV || os[0].Loc == oloc { 126 | continue 127 | } 128 | return os[0] 129 | } 130 | loc2 := loc 131 | loc2.X = -loc.X 132 | os = TilesAt(all, loc.Scaled(-1)) 133 | if len(os) > 0 { 134 | if os[0].Loc == pixel.ZV || os[0].Loc == oloc { 135 | continue 136 | } 137 | return os[0] 138 | } 139 | os = TilesAt(all, loc2.Scaled(1)) 140 | if len(os) > 0 { 141 | if os[0].Loc == pixel.ZV || os[0].Loc == oloc { 142 | continue 143 | } 144 | return os[0] 145 | } 146 | 147 | os = TilesAt(all, loc2.Scaled(-1)) 148 | if len(os) > 0 { 149 | if os[0].Loc == pixel.ZV || os[0].Loc == oloc { 150 | continue 151 | } 152 | return os[0] 153 | } 154 | 155 | if i%4 == 0 { 156 | radius++ 157 | loc.X += 16 158 | } 159 | } 160 | log.Println("not found") 161 | return Object{Type: O_NONE} 162 | 163 | } 164 | -------------------------------------------------------------------------------- /librpg/strings.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output librpg/strings.go -type ActionType,EntityState,EntityType,ItemType,animState,StatusEffect ./librpg/"; DO NOT EDIT. 2 | 3 | package rpg 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[Talk-0] 12 | _ = x[Slash-1] 13 | _ = x[ManaStorm-2] 14 | _ = x[MagicBullet-3] 15 | _ = x[Arrow-4] 16 | } 17 | 18 | const _ActionType_name = "TalkSlashManaStormMagicBulletArrow" 19 | 20 | var _ActionType_index = [...]uint8{0, 4, 9, 18, 29, 34} 21 | 22 | func (i ActionType) String() string { 23 | if i < 0 || i >= ActionType(len(_ActionType_index)-1) { 24 | return "ActionType(" + strconv.FormatInt(int64(i), 10) + ")" 25 | } 26 | return _ActionType_name[_ActionType_index[i]:_ActionType_index[i+1]] 27 | } 28 | func _() { 29 | // An "invalid array index" compiler error signifies that the constant values have changed. 30 | // Re-run the stringer command to generate them again. 31 | var x [1]struct{} 32 | _ = x[S_IDLE-0] 33 | _ = x[S_RUN-1] 34 | _ = x[S_WANDER-2] 35 | _ = x[S_GUARD-3] 36 | _ = x[S_SUSPECT-4] 37 | _ = x[S_HUNT-5] 38 | _ = x[S_DEAD-6] 39 | } 40 | 41 | const _EntityState_name = "S_IDLES_RUNS_WANDERS_GUARDS_SUSPECTS_HUNTS_DEAD" 42 | 43 | var _EntityState_index = [...]uint8{0, 6, 11, 19, 26, 35, 41, 47} 44 | 45 | func (i EntityState) String() string { 46 | if i < 0 || i >= EntityState(len(_EntityState_index)-1) { 47 | return "EntityState(" + strconv.FormatInt(int64(i), 10) + ")" 48 | } 49 | return _EntityState_name[_EntityState_index[i]:_EntityState_index[i+1]] 50 | } 51 | func _() { 52 | // An "invalid array index" compiler error signifies that the constant values have changed. 53 | // Re-run the stringer command to generate them again. 54 | var x [1]struct{} 55 | _ = x[SKELETON-0] 56 | _ = x[SKELETON_GUARD-1] 57 | _ = x[DOBJECT-2] 58 | } 59 | 60 | const _EntityType_name = "SKELETONSKELETON_GUARDDOBJECT" 61 | 62 | var _EntityType_index = [...]uint8{0, 8, 22, 29} 63 | 64 | func (i EntityType) String() string { 65 | if i < 0 || i >= EntityType(len(_EntityType_index)-1) { 66 | return "EntityType(" + strconv.FormatInt(int64(i), 10) + ")" 67 | } 68 | return _EntityType_name[_EntityType_index[i]:_EntityType_index[i+1]] 69 | } 70 | func _() { 71 | // An "invalid array index" compiler error signifies that the constant values have changed. 72 | // Re-run the stringer command to generate them again. 73 | var x [1]struct{} 74 | _ = x[GOLD-1] 75 | _ = x[POTION-2] 76 | _ = x[FOOD-3] 77 | _ = x[WEAPON-4] 78 | _ = x[ARMOR-5] 79 | _ = x[SPECIAL-6] 80 | } 81 | 82 | const _ItemType_name = "GOLDPOTIONFOODWEAPONARMORSPECIAL" 83 | 84 | var _ItemType_index = [...]uint8{0, 4, 10, 14, 20, 25, 32} 85 | 86 | func (i ItemType) String() string { 87 | i -= 1 88 | if i < 0 || i >= ItemType(len(_ItemType_index)-1) { 89 | return "ItemType(" + strconv.FormatInt(int64(i+1), 10) + ")" 90 | } 91 | return _ItemType_name[_ItemType_index[i]:_ItemType_index[i+1]] 92 | } 93 | func _() { 94 | // An "invalid array index" compiler error signifies that the constant values have changed. 95 | // Re-run the stringer command to generate them again. 96 | var x [1]struct{} 97 | _ = x[Idle-0] 98 | _ = x[Running-1] 99 | } 100 | 101 | const _animState_name = "IdleRunning" 102 | 103 | var _animState_index = [...]uint8{0, 4, 11} 104 | 105 | func (i animState) String() string { 106 | if i < 0 || i >= animState(len(_animState_index)-1) { 107 | return "animState(" + strconv.FormatInt(int64(i), 10) + ")" 108 | } 109 | return _animState_name[_animState_index[i]:_animState_index[i+1]] 110 | } 111 | func _() { 112 | // An "invalid array index" compiler error signifies that the constant values have changed. 113 | // Re-run the stringer command to generate them again. 114 | var x [1]struct{} 115 | _ = x[E_POISON-1] 116 | _ = x[E_PARALYSIS-2] 117 | _ = x[E_FROZEN-3] 118 | _ = x[E_BURNED-4] 119 | _ = x[E_SLEEP-5] 120 | _ = x[E_CONFUSED-6] 121 | _ = x[E_TIRED-7] 122 | _ = x[E_DRUNK-8] 123 | } 124 | 125 | const _StatusEffect_name = "E_POISONE_PARALYSISE_FROZENE_BURNEDE_SLEEPE_CONFUSEDE_TIREDE_DRUNK" 126 | 127 | var _StatusEffect_index = [...]uint8{0, 8, 19, 27, 35, 42, 52, 59, 66} 128 | 129 | func (i StatusEffect) String() string { 130 | i -= 1 131 | if i < 0 || i >= StatusEffect(len(_StatusEffect_index)-1) { 132 | return "StatusEffect(" + strconv.FormatInt(int64(i+1), 10) + ")" 133 | } 134 | return _StatusEffect_name[_StatusEffect_index[i]:_StatusEffect_index[i+1]] 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lets make a game 2 | 3 | 2D, top-down, pixelized magic action RPG 4 | 5 | contributions very welcome (see roadmap) 6 | 7 | # [AERPG](https://github.com/aerth/rpg) 8 | 9 | **demo** 10 | 11 | 12 | [![Build Status](https://travis-ci.org/aerth/rpg.svg?branch=master)](https://travis-ci.org/aerth/rpg) 13 | 14 | ![screenshot](https://raw.githubusercontent.com/aerth/rpg/master/doc/screenshot.png) 15 | 16 | ## INCLUDED TOOLS 17 | 18 | * aerpg - play the demo rpg: explore map, kill skeletons, pick up loot, gain xp, collect magic items 19 | 20 | * mapmaker - create/edit a map: read source code for keymap 21 | 22 | * mapgen - generate a map: run `mapgen ______` to use specific seed such as `mapgen mycoolseed` 23 | 24 | ## FETCHING DEPENDENCIES 25 | 26 | * install Go: [go](https://golang.org) 27 | 28 | * install C dependencies: `apt-get install xorg-dev libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libopenal-dev libasound2-dev` 29 | 30 | * fetch this source code and dependencies: `go get -v -d -u github.com/aerth/rpg/cmd/...` 31 | 32 | ## INSTALLING AERPG GAME 33 | 34 | ### Requirements 35 | 36 | If you're using Windows and having trouble building Pixel, please check [this 37 | guide](https://github.com/faiface/pixel/wiki/Building-Pixel-on-Windows) on the 38 | [wiki](https://github.com/faiface/pixel/wiki). 39 | 40 | [PixelGL](https://godoc.org/github.com/faiface/pixel/pixelgl) backend uses OpenGL to render 41 | graphics. Because of that, OpenGL development libraries are needed for compilation. The dependencies 42 | are same as for [GLFW](https://github.com/go-gl/glfw). 43 | 44 | The OpenGL version used is **OpenGL 3.3**. 45 | 46 | - On macOS, you need Xcode or Command Line Tools for Xcode (`xcode-select --install`) for required 47 | headers and libraries. 48 | - On Ubuntu/Debian-like Linux distributions, you need `libgl1-mesa-dev` and `xorg-dev` packages. 49 | - On CentOS/Fedora-like Linux distributions, you need `libX11-devel libXcursor-devel libXrandr-devel 50 | libXinerama-devel mesa-libGL-devel libXi-devel` packages. 51 | - See [here](http://www.glfw.org/docs/latest/compile.html#compile_deps) for full details. 52 | 53 | **The combination of Go 1.8, macOS and latest XCode seems to be problematic** as mentioned in issue 54 | [#7](https://github.com/faiface/pixel/issues/7). This issue is probably not related to Pixel. 55 | **Upgrading to Go 1.8.1 fixes the issue.** 56 | 57 | ### Compiling 58 | 59 | ```go get -v -d github.com/aerth/rpg``` 60 | ```GOBIN=$PWD go install github.com/aerth/rpg/cmd/...``` 61 | 62 | ## keymap: 63 | 64 | * Pause, Inventory, Char Stats: `i` 65 | 66 | * Movement: `arrows`, `asdw`, `hjkl`, `hold right mouse` 67 | 68 | * Zoom: `mouse wheel` 69 | 70 | * Identify tile: `left click` 71 | 72 | * Pick up loot: `left click` 73 | 74 | * Attack (manastorm): `space` `middle click` 75 | 76 | * Attack (magic bullet): `B` `left click (point/shoot)` 77 | 78 | * Quit: ctrl+Q 79 | 80 | ## CHEATS 81 | 82 | * Toggle show enemy paths: `=` 83 | 84 | * Toggle fly mode: `caps lock` 85 | 86 | * Mana potion: `1` 87 | 88 | * Health potion: `2` 89 | 90 | * XP potion: `3` 91 | 92 | * Speed up time: `LSHIFT` 93 | 94 | * Slow motion: `TAB` 95 | 96 | * Random Loot: `ctrl+L` (random location), `ctrl+K` (under mouse) 97 | 98 | * Spawn fresh mob: `M` (watch FPS go down) 99 | 100 | ## ROADMAP 101 | 102 | * [ ] Regions (separated by doors) 103 | 104 | * [ ] Doors/Portals connect regions 105 | 106 | * [ ] Spawn tiles (instead of random tile) (should be Rectangle) 107 | 108 | * [ ] Map editor improvements (toolbar, pallet, fix offsets) 109 | 110 | * [ ] Text boxes (space to speed past conversations, pgup pgdown scroll) 111 | 112 | * [ ] Text Input (cheat codes, debug, chat, user input) 113 | 114 | * [x] Pick up loot 115 | 116 | * [ ] Drop item 117 | 118 | * [ ] proper Inventory and Wearing 119 | 120 | * [ ] Optimization 121 | 122 | * [ ] Replace spritesheets, allow texturepacks, skins 123 | 124 | * [ ] "Stage 1" map and missions, villages with markets, npcs, enemies, and a generated dungeon with bad guys and a boss 125 | 126 | * [ ] D2 style multiplayer co-operative and chat (no p2p) 127 | 128 | 129 | ### questions / support / donations 130 | 131 | donations support the author and will make more frequent updates 132 | 133 | BTC: ![https://blockchain.info/address/1ANjiTNvdEM6Me3yc4EBFSkDb4db4XW6pr](https://blockchain.info/qr?data=1ANjiTNvdEM6Me3yc4EBFSkDb4db4XW6pr&size=200) 134 | 135 | PayPal Me: https://www.paypal.me/aerth 136 | 137 | questions and comments can be directed to the email address published at https://github.com/aerth 138 | 139 | 140 | ### credits 141 | 142 | * font 'Admtas' by [adem taş](http://www.dafont.com/profile.php?user=980017) 143 | * font '[Terminus TTF Font](http://files.ax86.net/terminus-ttf/)' (SIL Open Font License, version 1.1) 144 | * [sprite generator](http://gaurav.munjal.us/Universal-LPC-Spritesheet-Character-Generator/) 145 | * [main character sprite](http://mmorpgmakerxb.com/p/characters-sprites-generator) 146 | * big thanks to the [pixel](https://github.com/faiface/pixel) library 147 | * additional credits in `assets/sprites/credits.txt` file 148 | 149 | -------------------------------------------------------------------------------- /assets/maps/maze.map: -------------------------------------------------------------------------------- 1 | [{"L":{"X":32,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":224},"T":1,"P":{},"S":20}] -------------------------------------------------------------------------------- /librpg/items.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/aerth/rpc/librpg/common" 12 | "github.com/faiface/pixel" 13 | ) 14 | 15 | type Item struct { 16 | Name string 17 | Type ItemType 18 | Properties ItemProperties 19 | Effect func(Stats) Stats 20 | Quantity uint64 21 | } 22 | 23 | type ItemProperties struct { 24 | Weight uint8 25 | Size pixel.Vec 26 | } 27 | 28 | func DefaultItemProperties() ItemProperties { 29 | return ItemProperties{} 30 | } 31 | 32 | func (i Item) String() (s string) { 33 | if i.Quantity > 1 { 34 | s += strconv.FormatInt(int64(i.Quantity), 10) 35 | s += " " 36 | } 37 | if i.Name != "" { 38 | s += i.Name 39 | if i.Effect != nil { 40 | switch i.Type { 41 | case ARMOR: 42 | s += " (+defence)" 43 | case POTION: 44 | s += " (+magic)" 45 | case WEAPON: 46 | s += " (+attack)" 47 | } 48 | } 49 | return s 50 | } 51 | s += i.Type.String() 52 | return s 53 | 54 | } 55 | 56 | type ItemType int 57 | 58 | const ( 59 | _ ItemType = iota 60 | GOLD 61 | POTION 62 | FOOD 63 | WEAPON 64 | ARMOR 65 | SPECIAL 66 | ) 67 | 68 | func MakeGold(amount uint64) Item { 69 | return Item{ 70 | Name: "gold", 71 | Type: GOLD, 72 | Quantity: amount, 73 | } 74 | } 75 | 76 | func createLoot() Item { 77 | item := Item{ 78 | Type: ItemType(rand.Intn(5)) + 1, 79 | Quantity: 1, 80 | } 81 | return item 82 | } 83 | 84 | func createItemLoot() Item { 85 | item := createLoot() 86 | if item.Type == GOLD || item.Type == FOOD { 87 | return createItemLoot() 88 | } 89 | return item 90 | } 91 | 92 | // just stack gold potions and food for now 93 | func (i Item) Stack(items []Item) []Item { 94 | var stacked []Item 95 | var foods, potions, goldlvl uint64 96 | 97 | //log.Println(items) 98 | //log.Println("Totalnum:", len(items)) 99 | for _, item := range items { 100 | //log.Println(item) 101 | switch item.Type { 102 | case FOOD: 103 | foods += item.Quantity 104 | case POTION: 105 | potions += item.Quantity 106 | case GOLD: 107 | //log.Printf("adding %v and %v", goldlvl, item.Quantity) 108 | goldlvl += item.Quantity 109 | default: 110 | //log.Printf("stacking item %s", item) 111 | stacked = append(stacked, item) 112 | //log.Printf("items are now %v", len(stacked)) 113 | } 114 | } 115 | 116 | switch i.Type { 117 | default: 118 | stacked = append(stacked, i) 119 | case GOLD: 120 | goldlvl += i.Quantity 121 | case POTION: 122 | potions += i.Quantity 123 | case FOOD: 124 | foods += i.Quantity 125 | } 126 | if potions > 0 { 127 | stacked = append(stacked, Item{Type: POTION, Quantity: potions}) 128 | } 129 | if foods > 0 { 130 | 131 | stacked = append(stacked, Item{Type: FOOD, Quantity: foods}) 132 | } 133 | if goldlvl > 0 { 134 | stacked = append(stacked, MakeGold(goldlvl)) 135 | } 136 | 137 | return stacked 138 | 139 | } 140 | 141 | func StackItems(itemsets ...[]Item) []Item { 142 | var backpack []Item 143 | for _, inventory := range itemsets { 144 | for _, item := range inventory { 145 | backpack = item.Stack(backpack) 146 | } 147 | } 148 | return backpack 149 | } 150 | 151 | func FormatItemList(items []Item) string { 152 | if len(items) == 0 { 153 | return "none" 154 | } 155 | var s string 156 | for i, item := range items { 157 | if i%4 == 0 { 158 | s += "\n" 159 | } 160 | s += item.String() + ", " 161 | } 162 | 163 | return strings.TrimSuffix(s, ", ") 164 | } 165 | 166 | func RandomLoot() []Item { 167 | switch rand.Intn(10) { 168 | default: // 0 169 | return []Item{} // no loot 170 | case 1, 2, 3, 4: 171 | return []Item{MakeGold(uint64(rand.Intn(255) + 1))} 172 | case 5, 6: 173 | return []Item{createLoot()} 174 | // case 8: 175 | case 7, 8, 9: 176 | return []Item{RandomMagicItem()} 177 | } 178 | } 179 | 180 | func RandomMagicItem() Item { 181 | // special item 182 | item := createLoot() 183 | switch item.Type { 184 | case GOLD, FOOD, POTION: 185 | default: 186 | item.Name = fmt.Sprintf(GenerateItemName(), item.Type.String()) 187 | item.Effect = RandomItemEffect(item.Type) 188 | } 189 | return item 190 | 191 | } 192 | 193 | func RandomItemEffect(t ItemType) func(Stats) Stats { 194 | fn := func(s Stats) Stats { 195 | return s 196 | } 197 | 198 | switch t { 199 | case ARMOR: 200 | fn = func(s Stats) Stats { 201 | s.Vitality += 0.1 202 | return s 203 | } 204 | case WEAPON: 205 | fn = func(s Stats) Stats { 206 | s.Strength += 0.1 207 | return s 208 | } 209 | case POTION: 210 | fn = func(s Stats) Stats { 211 | s.Intelligence += 0.1 212 | return s 213 | } 214 | default: 215 | fn = func(s Stats) Stats { 216 | return s 217 | } 218 | 219 | } 220 | return fn 221 | } 222 | 223 | func (w *World) NewLoot(location pixel.Vec, items []Item) { 224 | if len(items) < 1 { 225 | log.Println("not enough items to drop loot") 226 | return 227 | } 228 | if location == pixel.ZV { 229 | log.Println("warning: creating loot at 0,0 coordinates") 230 | } 231 | 232 | loot := &DObject{ 233 | Contains: items, 234 | Type: D_LOOT, 235 | Until: time.Now().Add(5 * time.Minute), 236 | Object: common.Object{ 237 | Loc: location, 238 | Rect: common.DefaultSpriteRectangle.Moved(location), 239 | }, 240 | } 241 | w.DObjects = append(w.DObjects, loot) 242 | 243 | } 244 | -------------------------------------------------------------------------------- /librpg/load.go: -------------------------------------------------------------------------------- 1 | // aerth game 2 | // copyright 2017 aerth 3 | package rpg 4 | 5 | import ( 6 | "math/rand" 7 | "time" 8 | 9 | _ "image/png" 10 | 11 | "github.com/aerth/rpc/librpg/common" 12 | "github.com/faiface/pixel" 13 | ) 14 | 15 | func init() { 16 | rand.Seed(time.Now().UnixNano()) 17 | } 18 | 19 | // loadCharacterSheet returns an animated spritesheet 20 | // 13W 21H 21 | func LoadEntitySheet(sheetPath string, framesx, framesy uint8) (sheet pixel.Picture, anims map[EntityState]map[Direction][]pixel.Rect, err error) { 22 | sheet, err = common.LoadPicture(sheetPath) 23 | frameWidth := float64(int(sheet.Bounds().Max.X / float64(framesx))) 24 | frameHeight := float64(int(sheet.Bounds().Max.Y / float64(framesy))) 25 | //log.Println(frameWidth, "width", frameHeight, "height") 26 | // create a array of frames inside the spritesheet 27 | var frames = []pixel.Rect{} 28 | for y := 0.00; y+frameHeight <= sheet.Bounds().Max.Y; y = y + frameHeight { 29 | for x := 0.00; x+float64(frameWidth) <= sheet.Bounds().Max.X; x = x + float64(frameWidth) { 30 | frames = append(frames, pixel.R( 31 | x, 32 | y, 33 | x+frameWidth, 34 | y+frameHeight, 35 | )) 36 | } 37 | } 38 | 39 | //log.Println("total skeleton frames", len(frames)) 40 | 41 | // 0-5 die 42 | // BLANK 6-12 43 | // 13-25 shoot right 44 | // 26-39 shoot down 45 | // 6-76 shoot left 46 | // 7-25 shoot up 47 | anims = make(map[EntityState]map[Direction][]pixel.Rect) 48 | anims[S_IDLE] = make(map[Direction][]pixel.Rect) 49 | anims[S_WANDER] = make(map[Direction][]pixel.Rect) 50 | anims[S_RUN] = make(map[Direction][]pixel.Rect) 51 | anims[S_GUARD] = make(map[Direction][]pixel.Rect) 52 | anims[S_SUSPECT] = make(map[Direction][]pixel.Rect) 53 | anims[S_HUNT] = make(map[Direction][]pixel.Rect) 54 | anims[S_DEAD] = make(map[Direction][]pixel.Rect) 55 | 56 | // spritesheet is right down left up 57 | anims[S_DEAD][LEFT] = frames[0:5] 58 | anims[S_DEAD][RIGHT] = frames[0:5] 59 | anims[S_DEAD][UP] = frames[0:5] 60 | anims[S_DEAD][DOWN] = frames[0:5] 61 | anims[S_IDLE][LEFT] = frames[143:144] 62 | anims[S_IDLE][UP] = frames[156:157] 63 | anims[S_IDLE][RIGHT] = frames[169:170] 64 | anims[S_IDLE][DOWN] = frames[182:183] 65 | anims[S_RUN][LEFT] = frames[143:152] 66 | anims[S_RUN][UP] = frames[156:165] 67 | anims[S_RUN][RIGHT] = frames[169:178] 68 | anims[S_RUN][DOWN] = frames[182:191] 69 | return sheet, anims, nil 70 | } 71 | 72 | // loadCharacterSheet returns an animated spritesheet 73 | func LoadCharacterSheet(sheetPath string, numframes uint8) (sheet pixel.Picture, anims map[Direction][]pixel.Rect, err error) { 74 | sheet, err = common.LoadPicture("sprites/char.png") 75 | if err != nil { 76 | panic(err) 77 | } 78 | frameWidth := int(sheet.Bounds().Max.X/float64(numframes)) * 2 79 | //log.Println(frameWidth, "width") 80 | // create a array of frames inside the spritesheet 81 | var frames = new([16]pixel.Rect) 82 | 83 | for i, x := 0, 0.0; x+float64(frameWidth) <= sheet.Bounds().Max.X; i, x = i+1, x+float64(frameWidth) { 84 | if i > 15 { 85 | break 86 | } 87 | frames[i] = pixel.R( 88 | x, 89 | 0, 90 | x+float64(frameWidth), 91 | sheet.Bounds().H(), 92 | ) 93 | } 94 | 95 | anims = make(map[Direction][]pixel.Rect) 96 | anims[LEFT] = frames[:4] 97 | anims[UPLEFT] = frames[:4] 98 | anims[RIGHT] = frames[4:8] 99 | anims[DOWNRIGHT] = frames[4:8] 100 | anims[DOWN] = frames[8:12] 101 | anims[DOWNLEFT] = frames[8:12] 102 | anims[UP] = frames[12:] 103 | anims[UPRIGHT] = frames[12:] 104 | return sheet, anims, nil 105 | } 106 | 107 | func LoadNewCharacterSheet(sheetPath string) (sheet pixel.Picture, anims map[Direction][]pixel.Rect, err error) { 108 | numframes := 16.00 109 | sheet, err = common.LoadPicture(sheetPath) 110 | if err != nil { 111 | panic(err) 112 | } 113 | var frames = new([16]pixel.Rect) 114 | frameWidth := sheet.Bounds().Max.X / float64(len(frames)) 115 | 116 | for i := 0.00; i < numframes; i++ { 117 | 118 | frames[int(i)] = pixel.R( 119 | (i+1.00)*frameWidth, 120 | 0, 121 | ((i+1.00)*frameWidth)+float64(frameWidth), 122 | sheet.Bounds().H(), 123 | ) 124 | 125 | } 126 | 127 | anims = make(map[Direction][]pixel.Rect) 128 | anims[LEFT] = frames[:4] 129 | anims[UPLEFT] = frames[:4] 130 | anims[RIGHT] = frames[4:8] 131 | anims[DOWNRIGHT] = frames[4:8] 132 | anims[DOWN] = frames[8:12] 133 | anims[DOWNLEFT] = frames[8:12] 134 | anims[UP] = frames[12:] 135 | anims[UPRIGHT] = frames[12:] 136 | return sheet, anims, nil 137 | 138 | } 139 | 140 | func LoadSpriteSheet(path string) (pixel.Picture, []*pixel.Sprite) { 141 | spritesheet, err := common.LoadPicture("sprites/" + path) 142 | /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 143 | * 1 144 | * 2 145 | * ... 146 | * 16 147 | */ 148 | if err != nil { 149 | panic(err) 150 | } 151 | var sheetFrames []pixel.Rect 152 | for x := spritesheet.Bounds().Min.X; x < spritesheet.Bounds().Max.X; x += 32 { 153 | for y := spritesheet.Bounds().Min.Y; y < spritesheet.Bounds().Max.Y; y += 32 { 154 | sheetFrames = append(sheetFrames, pixel.R(x, y, x+32, y+32)) 155 | } 156 | } 157 | var spritemap = []*pixel.Sprite{} 158 | for i := 0; i < len(sheetFrames); i++ { 159 | x := i 160 | spritemap = append(spritemap, pixel.NewSprite(spritesheet, sheetFrames[x])) 161 | } 162 | //log.Println(len(spritemap), "sprites loaded") 163 | return spritesheet, spritemap 164 | } 165 | -------------------------------------------------------------------------------- /librpg/spells.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "time" 7 | 8 | "golang.org/x/image/colornames" 9 | 10 | "github.com/faiface/pixel" 11 | "github.com/faiface/pixel/imdraw" 12 | ) 13 | 14 | type Animation struct { 15 | Name string 16 | Type ActionType 17 | loc pixel.Vec 18 | rect pixel.Rect 19 | radius float64 20 | step float64 21 | counter float64 22 | cols []pixel.RGBA 23 | until time.Time 24 | start time.Time 25 | damage float64 26 | direction Direction 27 | ticker <-chan time.Time 28 | level uint 29 | } 30 | type ActionType int 31 | 32 | const ( 33 | Talk ActionType = iota 34 | Slash 35 | ManaStorm 36 | MagicBullet 37 | Arrow 38 | ) 39 | 40 | func (w *World) Action(char *Character, loc pixel.Vec, t ActionType, dir Direction) { 41 | switch t { 42 | case Talk: 43 | log.Println("nothing to say yet") 44 | case Slash: 45 | log.Println("no weapon yet") 46 | case ManaStorm: 47 | cost := uint(2.5 * float64(char.Level)) 48 | if char.Mana < cost { 49 | w.Message("not enough mana") 50 | return 51 | } 52 | char.Mana -= cost 53 | w.NewAnimation(char.Rect.Center(), "manastorm", OUT) 54 | case MagicBullet: 55 | cost := uint(1) 56 | if char.Mana < cost { 57 | w.Message("not enough mana") 58 | return 59 | } 60 | 61 | char.Mana -= cost 62 | w.NewAnimation(char.Rect.Center(), "magicbullet", dir) 63 | default: // 64 | } 65 | } 66 | 67 | func init() { 68 | rand.Seed(time.Now().UnixNano()) 69 | } 70 | 71 | func (a *Animation) update(dt float64) { 72 | if time.Since(a.until) > time.Millisecond { 73 | a = nil 74 | return 75 | } 76 | switch a.Type { 77 | default: 78 | log.Println("nil animation?") 79 | return 80 | case MagicBullet: 81 | a.counter += dt 82 | for a.counter > a.step { 83 | if len(a.cols) == 0 { 84 | a.cols = []pixel.RGBA{RandomColor(), RandomColor(), RandomColor(), RandomColor(), RandomColor()} 85 | } 86 | a.counter -= a.step 87 | for i := len(a.cols) - 2; i >= 0; i-- { 88 | a.cols[i+1] = a.cols[i] 89 | } 90 | a.cols[0] = RandomColor().Scaled(0.3) 91 | a.cols[1] = RandomColor().Scaled(0.3) 92 | } 93 | if a.direction != OUT && a.direction != IN { 94 | a.loc = a.loc.Add((a.direction.V().Scaled(100 * dt))) 95 | a.rect = pixel.R(-a.radius, -a.radius, a.radius, a.radius).Moved(a.loc) 96 | } 97 | case ManaStorm: 98 | a.counter += dt 99 | for a.counter > a.step { 100 | if len(a.cols) == 0 { 101 | a.cols = []pixel.RGBA{RandomColor(), RandomColor(), RandomColor(), RandomColor(), RandomColor()} 102 | } 103 | a.counter -= a.step 104 | for i := len(a.cols) - 2; i >= 0; i-- { 105 | a.cols[i+1] = a.cols[i] 106 | } 107 | a.cols[0] = RandomColor().Scaled(0.3) 108 | a.cols[1] = RandomColor().Scaled(0.3) 109 | } 110 | case Arrow: 111 | a.counter += dt 112 | for a.counter > a.step { 113 | a.counter -= a.step 114 | a.direction = Direction(rand.Intn(5)) 115 | } 116 | a.loc = a.loc.Add((a.direction.V().Scaled(100 * dt))) 117 | a.rect = pixel.R(-100, -5, 100, 5).Moved(a.loc) 118 | 119 | } 120 | } 121 | 122 | func (a *Animation) draw(imd *imdraw.IMDraw) { 123 | if a == nil || time.Since(a.start) < time.Millisecond { 124 | return 125 | } 126 | switch a.Type { 127 | case MagicBullet, ManaStorm: 128 | for i := len(a.cols) - 1; i >= 0; i-- { 129 | imd.Color = a.cols[i] 130 | imd.Push(a.loc) 131 | imd.Circle(float64(i+2)*a.radius/float64(len(a.cols)), 0) 132 | } 133 | case Arrow: 134 | imd.Color = RandomColor() 135 | b := a.loc.Add(a.direction.V().Scaled(10)) 136 | imd.Push(a.loc, b) 137 | imd.Line(float64(a.level)) 138 | 139 | default: 140 | log.Println("bad animation?") 141 | } 142 | } 143 | func (w *World) NewAnimation(loc pixel.Vec, kind string, direction Direction) { 144 | switch kind { 145 | default: // 146 | log.Println("invalid animation type") 147 | return 148 | case "magicbullet": 149 | a := new(Animation) 150 | a.Type = MagicBullet 151 | a.loc = loc 152 | a.radius = 10 153 | a.step = 1.0 / 7 154 | a.rect = pixel.R(-a.radius, -a.radius, a.radius, a.radius).Moved(a.loc) 155 | a.cols = []pixel.RGBA{} 156 | a.start = time.Now() 157 | a.until = time.Now().Add(time.Second * 2) 158 | a.direction = direction 159 | a.damage = w.Char.Stats.Intelligence * 0.5 160 | w.Animations = append(w.Animations, a) 161 | 162 | case "manastorm": 163 | a := new(Animation) 164 | a.Type = ManaStorm 165 | a.loc = loc 166 | a.radius = (w.Char.Stats.Intelligence / 20) * 4 167 | a.step = 1.0 / 7 168 | a.damage = w.Char.Stats.Intelligence * 0.2 169 | a.rect = pixel.R(-a.radius, -a.radius, a.radius, a.radius).Moved(a.loc) 170 | a.cols = []pixel.RGBA{} 171 | a.start = time.Now() 172 | a.direction = direction 173 | dur := time.Duration(w.Char.Stats.Intelligence * float64(time.Millisecond) * 18) 174 | // log.Println(dur) 175 | a.until = time.Now().Add(dur) 176 | a.ticker = time.Tick(time.Millisecond * 200) 177 | w.Animations = append(w.Animations, a) 178 | 179 | case "arrow": 180 | a := new(Animation) 181 | a.Type = Arrow 182 | a.loc = loc 183 | a.damage = w.Char.Stats.Strength * 10 184 | a.rect = pixel.R(-100, -5, 100, 5) 185 | a.cols = []pixel.RGBA{pixel.ToRGBA(colornames.Red)} 186 | a.direction = direction 187 | dur := time.Second * 3 188 | a.until = time.Now().Add(dur) 189 | a.ticker = time.Tick(time.Millisecond * 500) 190 | a.level = w.Char.Level 191 | w.Animations = append(w.Animations, a) 192 | 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /librpg/character.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/aerth/rpc/librpg/common" 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/text" 13 | ) 14 | 15 | type Character struct { 16 | Phys charPhys // properties 17 | Stats Stats 18 | Sprite *pixel.Sprite // current stamp 19 | Matrix pixel.Matrix // location in canvas/map 20 | Frame pixel.Rect // size (for animation) 21 | Rect pixel.Rect // size (for collision) 22 | Dir Direction // Running direction (Idle down) 23 | Sheet pixel.Picture // all frames of animation (4 for each 4 direction, total 16) 24 | Anims map[Direction][]pixel.Rect // animation 25 | Rate float64 // animation 26 | counter float64 // in animation 27 | State animState // Idle or Running 28 | Inventory []Item // inventory 29 | Health uint // hp 30 | Mana uint // mp 31 | Invisible bool // hidden from enemies 32 | Level uint 33 | tick time.Time 34 | textbuf *text.Text 35 | W *World 36 | } 37 | 38 | type charPhys struct { 39 | RunSpeed float64 40 | Rect pixel.Rect 41 | Vel pixel.Vec 42 | Gravity float64 43 | CanFly bool 44 | Rate float64 45 | } 46 | 47 | var DefaultStats = Stats{ 48 | Intelligence: 60, 49 | Strength: 60, 50 | Wisdom: 60, 51 | Vitality: 60, 52 | } 53 | 54 | // DefaultPhys character 55 | var DefaultPhys = charPhys{ 56 | RunSpeed: 60.5, 57 | Rect: pixel.R(-8, -8, 8, 8), 58 | Gravity: 50.00, 59 | Rate: 2, 60 | } 61 | 62 | func (c *Character) StatsReport() string { 63 | s := (c.Stats.String()) 64 | s += fmt.Sprintf("Level %v\nHealth: %v\nMana: %v\nXP: %v/%v", c.Level, c.Health, c.Mana, c.Stats.XP, c.NextLevel()) 65 | for _, item := range c.Inventory { 66 | if item.Effect != nil { 67 | s += fmt.Sprintf("\n Effects: %q", item.Name) 68 | 69 | } 70 | } 71 | return s 72 | 73 | } 74 | 75 | func NewCharacter(skin string) *Character { 76 | // get main character asset 77 | sheet, anims, err := LoadCharacterSheet("sprites/"+skin+".png", 32) 78 | if err != nil { 79 | panic(fmt.Errorf("error loading character sheet: %v", err)) 80 | } 81 | c := new(Character) 82 | c.Sheet = sheet 83 | c.Anims = anims 84 | //log.Printf("Anims: %v", len(anims)) 85 | c.Sprite = pixel.NewSprite(nil, pixel.Rect{}) 86 | c.Rect = DefaultPhys.Rect 87 | c.State = Idle 88 | c.Frame = c.Anims[DOWN][0] 89 | c.Phys = DefaultPhys 90 | c.Rate = 0.1 91 | c.Health = 255 92 | c.Mana = 255 93 | c.Stats = DefaultStats 94 | return c 95 | } 96 | 97 | func (char *Character) Draw(t pixel.Target) { 98 | if char.Sprite == nil { 99 | char.Sprite = pixel.NewSprite(nil, pixel.Rect{}) 100 | } 101 | // draw the correct frame with the correct position and direction 102 | char.Sprite.Set(char.Sheet, char.Frame) 103 | char.Sprite.Draw(t, char.Matrix) 104 | 105 | } 106 | 107 | func (char *Character) Update(dt float64, dir Direction, world *World) { 108 | for _, i := range char.Inventory { 109 | if i.Effect != nil { 110 | char.Stats = i.Effect(char.Stats) 111 | } 112 | 113 | } 114 | if time.Since(char.tick) >= time.Second*1 { 115 | if char.Mana < 255 { 116 | char.Mana++ 117 | } 118 | char.tick = time.Now() 119 | } 120 | char.counter += dt 121 | // determine the new animation state 122 | 123 | var newState animState 124 | 125 | switch { 126 | default: 127 | newState = char.State 128 | case -2 < char.Phys.Vel.Len() && char.Phys.Vel.Len() < 2: 129 | char.Phys.Vel = pixel.ZV 130 | newState = Idle 131 | case char.Phys.Vel.Len() > 2: 132 | newState = Running 133 | case char.Phys.Vel.Len() < -2: 134 | newState = Running 135 | } 136 | 137 | // reset the time counter if the state changed 138 | if char.State != newState || char.Dir != dir { 139 | char.State = newState 140 | char.Dir = dir 141 | //log.Println(char.State, char.Dir) 142 | char.counter = 0 143 | } 144 | 145 | // determine the correct animation frame 146 | 147 | if char.State == Idle { 148 | char.Frame = char.Anims[dir][0] 149 | } else if char.State == Running { 150 | // count 0 1 2 3 0 1 2 3... 151 | i := int(math.Floor(char.counter / char.Rate)) 152 | 153 | char.Frame = char.Anims[dir][i%len(char.Anims[dir])] 154 | 155 | // gradually lose momentum 156 | char.Phys.Vel = pixel.Lerp(char.Phys.Vel, pixel.ZV, 1-math.Pow(1.0/char.Phys.Gravity, dt)) 157 | next := char.Rect.Moved(char.Phys.Vel.Scaled(dt)) 158 | 159 | f := func(nextblock pixel.Rect) bool { 160 | for _, c := range world.Blocks { 161 | area := c.Rect.Norm().Intersect(nextblock.Norm()).Norm().Area() 162 | if area != 0 { 163 | // log.Printf("%f %s %s blocked by: %v at rect: %s", area, char.Rect, nextblock, c.SpriteNum, c.Rect) 164 | return false 165 | } 166 | } 167 | return true 168 | } 169 | if char.Phys.CanFly { 170 | char.Rect = next 171 | return 172 | } 173 | // only walk on tiles 174 | f2 := func(nexttile pixel.Rect) bool { 175 | 176 | for _, c := range world.Tiles { 177 | if c.Type == common.O_TILE && c.Rect.Intersect(nexttile).Norm().Area() != 0 { 178 | return true 179 | } 180 | } 181 | // out of map 182 | // log.Println("no tile to step on", dot) 183 | return false 184 | } 185 | 186 | if f(next) && f2(next) { 187 | // log.Println("passed:", next) 188 | char.Rect = next 189 | 190 | } else { 191 | char.Phys.Vel = pixel.ZV 192 | } 193 | 194 | } 195 | } 196 | 197 | func (char *Character) Damage(n uint, from string) { 198 | if from != "" { 199 | from = fmt.Sprintf("from %s", from) 200 | } 201 | if char.Health < n { 202 | char.Health = 0 203 | log.Printf("Player took critical hit %s!", from) 204 | return 205 | } 206 | char.Health -= n 207 | char.W.Message(fmt.Sprintf("ouch! took %v damage, now at %v", n, char.Health)) 208 | } 209 | 210 | func (char *Character) ResetLocation() { 211 | char.Rect = DefaultPhys.Rect 212 | char.Phys.Vel = pixel.ZV 213 | 214 | } 215 | 216 | func (char *Character) CountGold() string { 217 | var madlootyo uint64 218 | for _, item := range char.Inventory { 219 | if item.Type == GOLD { 220 | madlootyo += item.Quantity 221 | } 222 | } 223 | return strconv.FormatInt(int64(madlootyo), 10) 224 | } 225 | 226 | func (char *Character) ExpUp(amount uint64) { 227 | log.Println("Gained experience:", amount) 228 | char.Stats.XP += amount 229 | char.Stats.Score += amount 230 | 231 | } 232 | 233 | func (w *World) checkLevel() { 234 | 235 | if w.Char.Stats.XP == 0 { 236 | return 237 | } 238 | nextlvl := w.Char.NextLevel() 239 | if w.Char.Stats.XP > nextlvl { 240 | w.Char.Level++ 241 | w.Char.Health = 255 242 | w.Message("LVL UP") 243 | log.Printf("level up (%v)! next lvl at %v xp", w.Char.Level, nextlvl) 244 | if xp := w.Char.Stats.XP - nextlvl; xp > 0 { 245 | w.Char.Stats.XP = xp 246 | } else { 247 | w.Char.Stats.XP = 0 248 | 249 | } 250 | switch w.Char.Level { 251 | default: 252 | w.Char.Stats.Intelligence += float64(10 * w.Char.Level) 253 | } 254 | log.Println(w.Char.Stats) 255 | } 256 | } 257 | 258 | func (c *Character) NextLevel() uint64 { 259 | return uint64(150 * c.Level) 260 | } 261 | 262 | func (c *Character) MaxHealth() uint64 { 263 | return uint64(c.Health * c.Level) 264 | } 265 | 266 | func (c *Character) PickUp(items []Item) string { 267 | 268 | c.Inventory = StackItems(c.Inventory, items) 269 | return fmt.Sprint(items) 270 | 271 | } 272 | -------------------------------------------------------------------------------- /cmd/mapmaker/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 aerth 2 | 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "math" 12 | "os" 13 | "time" 14 | 15 | "golang.org/x/image/colornames" 16 | 17 | rpg "github.com/aerth/rpc/librpg" 18 | "github.com/aerth/rpc/librpg/common" 19 | "github.com/faiface/pixel" 20 | "github.com/faiface/pixel/pixelgl" 21 | ) 22 | 23 | var LEVEL string 24 | 25 | func FlagInit() { 26 | log.SetFlags(log.Lshortfile) 27 | log.SetPrefix("> ") 28 | if flag.NArg() != 1 { 29 | fmt.Println("Which map name?") 30 | os.Exit(111) 31 | } 32 | LEVEL = flag.Arg(0) 33 | 34 | } 35 | 36 | var convert = flag.Bool("danger", false, "convert old to new (experimental)") 37 | var ( 38 | IM = pixel.IM 39 | ZV = pixel.ZV 40 | ) 41 | 42 | var helpText = "ENTER=save LEFT=block RIGHT=tile SHIFT=batch SPACE=del CAPS=highlight U=undo R=redo 4=turbo B=dontreplace" 43 | 44 | func loadSpriteSheet() (pixel.Picture, []*pixel.Sprite) { 45 | spritesheet, err := common.LoadPicture("sprites/tileset.png") 46 | /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 47 | * 1 48 | * 2 49 | * ... 50 | * 16 51 | */ 52 | if err != nil { 53 | 54 | panic(err) 55 | 56 | } 57 | var sheetFrames []pixel.Rect 58 | for x := spritesheet.Bounds().Min.X; x < spritesheet.Bounds().Max.X; x += 32 { 59 | for y := spritesheet.Bounds().Min.Y; y < spritesheet.Bounds().Max.Y; y += 32 { 60 | sheetFrames = append(sheetFrames, pixel.R(x, y, x+32, y+32)) 61 | } 62 | } 63 | 64 | var spritemap = []*pixel.Sprite{} 65 | for i := 0; i < len(sheetFrames); i++ { 66 | x := i 67 | spritemap = append(spritemap, pixel.NewSprite(spritesheet, sheetFrames[x])) 68 | } 69 | log.Println(len(spritemap), "sprites loaded") 70 | log.Println(spritemap[0].Frame()) 71 | return spritesheet, spritemap 72 | } 73 | 74 | func run() { 75 | flag.Parse() 76 | FlagInit() 77 | cfg := pixelgl.WindowConfig{ 78 | Title: "AERPG mapedit", 79 | Bounds: pixel.R(0, 0, 800, 600), 80 | Resizable: true, 81 | VSync: false, 82 | } 83 | win, err := pixelgl.NewWindow(cfg) 84 | if err != nil { 85 | panic(err) 86 | } 87 | var oldthings = []common.Object{} 88 | if b, err := ioutil.ReadFile(LEVEL); err == nil { 89 | err = json.Unmarshal(b, &oldthings) 90 | if err != nil { 91 | panic(err) 92 | } 93 | } 94 | var things []common.Object 95 | for _, v := range oldthings { 96 | if *convert { 97 | log.Println("Converting") 98 | v.Type = common.O_TILE 99 | if v.SpriteNum == 53 && v.Type == common.O_TILE { 100 | v.Type = common.O_BLOCK 101 | } 102 | } 103 | 104 | v.Rect = common.DefaultSpriteRectangle.Moved(v.Loc) 105 | 106 | things = append(things, v) 107 | 108 | } 109 | 110 | spritesheet, spritemap := loadSpriteSheet() 111 | 112 | batch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet) 113 | start := time.Now() 114 | second := time.Tick(time.Second) 115 | last := start 116 | frames := 0 117 | 118 | var ( 119 | camPos = pixel.ZV 120 | camSpeed = 500.0 121 | camZoom = 1.0 122 | camZoomSpeed = 1.2 123 | ) 124 | currentThing := 20 // 20 is grass, 0 should be transparent sprite 125 | text := rpg.NewTextSmooth(14) 126 | fmt.Fprint(text, helpText) 127 | cursor := common.GetCursor(2) 128 | undobuffer := []common.Object{} 129 | var turbo = false 130 | var highlight = true 131 | var box pixel.Rect 132 | var replace = true 133 | for !win.Closed() { 134 | dt := time.Since(last).Seconds() 135 | _ = dt 136 | last = time.Now() 137 | frames++ 138 | 139 | // cam := pixel.IM.Scaled(camPos, camZoom).Moved(win.Bounds().Center().Sub(camPos)) 140 | camZoom *= math.Pow(camZoomSpeed, win.MouseScroll().Y) 141 | 142 | // camera 143 | cam := pixel.IM.Scaled(pixel.ZV, camZoom).Moved(win.Bounds().Center()).Moved(camPos.Scaled(-camZoom)) 144 | win.SetMatrix(cam) 145 | 146 | // snap to grid 147 | snap := 32.00 // 16 for half grid ? 148 | mouse := cam.Unproject(win.MousePosition()) 149 | mouse.X = float64(int(mouse.X/snap)) * snap 150 | mouse.Y = float64(int(mouse.Y/snap)) * snap 151 | // mouse.X = mouse.X - 16 152 | // mouse.Y = mouse.Y - 16 153 | if win.JustPressed(pixelgl.Key4) { 154 | turbo = !turbo 155 | log.Println("turbo:", turbo) 156 | } 157 | if win.JustPressed(pixelgl.KeyCapsLock) { 158 | highlight = !highlight 159 | log.Println("highlight:", highlight) 160 | } 161 | 162 | if turbo { 163 | dt *= 8 164 | } 165 | 166 | if win.JustPressed(pixelgl.KeyU) { 167 | undobuffer = append(undobuffer, things[len(things)-1]) 168 | things = things[:len(things)-1] 169 | } 170 | if win.JustPressed(pixelgl.KeyR) { 171 | if len(undobuffer) > 0 { 172 | things = append(things, undobuffer[len(undobuffer)-1]) 173 | if !win.Pressed(pixelgl.KeyLeftShift) { 174 | undobuffer = undobuffer[:len(undobuffer)-1] 175 | } 176 | } else { 177 | log.Println("no undo buffer") 178 | } 179 | } 180 | 181 | deleteThing := func(loc pixel.Vec) []common.Object { 182 | var newthings []common.Object 183 | for _, thing := range things { 184 | if thing.Rect.Contains(mouse) { 185 | log.Println("deleting:", thing) 186 | } else { 187 | 188 | newthings = append(newthings, thing) 189 | } 190 | } 191 | return newthings 192 | } 193 | if win.Pressed(pixelgl.KeySpace) { 194 | things = deleteThing(mouse) 195 | } 196 | if win.JustPressed(pixelgl.KeyB) { 197 | replace = !replace 198 | log.Println("replace:", replace) 199 | } 200 | // draw big patch of grass 201 | if win.Pressed(pixelgl.KeyLeftControl) && (win.JustPressed(pixelgl.MouseButtonLeft) || win.JustPressed(pixelgl.MouseButtonRight)) { 202 | box.Min.Y = mouse.Y 203 | box.Min.X = mouse.X 204 | } else { 205 | if win.Pressed(pixelgl.KeyLeftShift) && win.Pressed(pixelgl.MouseButtonRight) || 206 | win.JustPressed(pixelgl.MouseButtonRight) { 207 | thing := common.NewBlock(mouse) 208 | thing.SpriteNum = currentThing 209 | log.Println("Stamping Block", mouse, thing.SpriteNum) 210 | if replace { 211 | undobuffer = append(undobuffer, thing) 212 | things = append(deleteThing(mouse), thing) 213 | } else { 214 | things = append(things, thing) 215 | 216 | } 217 | } 218 | if win.Pressed(pixelgl.KeyLeftShift) && win.Pressed(pixelgl.MouseButtonLeft) || 219 | win.JustPressed(pixelgl.MouseButtonLeft) { 220 | thing := common.NewTile(mouse) 221 | thing.SpriteNum = currentThing 222 | log.Println("Stamping Tile", mouse, thing.SpriteNum) 223 | if replace { 224 | undobuffer = append(undobuffer, thing) 225 | things = append(deleteThing(mouse), thing) 226 | } else { 227 | things = append(things, thing) 228 | } 229 | } 230 | } 231 | if win.JustPressed(pixelgl.KeyEnter) { 232 | b, err := json.Marshal(things) 233 | if err != nil { 234 | panic(err) 235 | } 236 | os.Rename(LEVEL, LEVEL+".old") 237 | if err := ioutil.WriteFile(LEVEL, b, 0644); err != nil { 238 | log.Println(LEVEL + " map saved") 239 | } 240 | } 241 | if win.JustPressed(pixelgl.KeyPageUp) { 242 | currentThing++ 243 | if currentThing > len(spritemap)-1 { 244 | currentThing = 0 245 | } 246 | log.Println("current sprite:", currentThing) 247 | } 248 | if win.JustPressed(pixelgl.KeyPageDown) { 249 | currentThing-- 250 | if currentThing <= 0 { 251 | currentThing = len(spritemap) - 1 252 | } 253 | log.Println("current sprite:", currentThing) 254 | } 255 | if win.Pressed(pixelgl.KeyLeft) || win.Pressed(pixelgl.KeyA) { 256 | camPos.X -= camSpeed * dt 257 | } 258 | if win.Pressed(pixelgl.KeyRight) || win.Pressed(pixelgl.KeyD) { 259 | camPos.X += camSpeed * dt 260 | } 261 | if win.Pressed(pixelgl.KeyDown) || win.Pressed(pixelgl.KeyS) { 262 | camPos.Y -= camSpeed * dt 263 | } 264 | if win.Pressed(pixelgl.KeyUp) || win.Pressed(pixelgl.KeyW) { 265 | camPos.Y += camSpeed * dt 266 | } 267 | 268 | // canvas.Clear(pixel.Alpha(0)) 269 | win.Clear(colornames.Green) 270 | batch.Clear() 271 | 272 | batch.Draw(win) 273 | if b := box.Size(); b.Len() != 0 { 274 | if win.Pressed(pixelgl.KeyLeftControl) { 275 | if win.JustReleased(pixelgl.MouseButtonLeft) { 276 | box.Max = mouse 277 | box = box.Norm() 278 | log.Println("drawing rectangle:", box, currentThing) 279 | things = append(DeleteThings(things, box), common.DrawPatternObject(currentThing, common.O_TILE, box, 100)...) 280 | } 281 | if win.JustReleased(pixelgl.MouseButtonRight) { 282 | box.Max = mouse 283 | box = box.Norm() 284 | log.Println("drawing rectangle:", box, currentThing) 285 | things = append(DeleteThings(things, box), common.DrawPatternObject(currentThing, common.O_BLOCK, box, 100)...) 286 | 287 | } 288 | } 289 | } 290 | 291 | for i := range things { 292 | things[i].Draw(batch, spritesheet, spritemap, 0) 293 | if highlight { 294 | color := common.TransparentRed 295 | if things[i].Type == common.O_TILE { 296 | color = common.TransparentBlue 297 | } 298 | things[i].Highlight(batch, color) 299 | } 300 | if things[i].Rect.Contains(mouse) { 301 | things[i].Highlight(batch, common.TransparentPurple) 302 | 303 | } 304 | 305 | } 306 | 307 | batch.Draw(win) 308 | 309 | // draw player spawn 310 | spritemap[182].Draw(win, IM.Scaled(ZV, 2).Moved(pixel.V(8, 8))) // incorrect offset 311 | 312 | // return cam 313 | win.SetMatrix(IM) 314 | spritemap[currentThing].Draw(win, IM.Scaled(ZV, 2).Moved(pixel.V(64, 64)).Moved(spritemap[0].Frame().Center())) 315 | text.Draw(win, IM.Moved(pixel.V(10, 10))) 316 | // cursor.Draw(win, IM.Moved(win.MousePosition()).Moved(pixel.V(32, -32))) 317 | 318 | cursor.Draw(win, pixel.IM.Scaled(pixel.ZV, 4).Moved(win.MousePosition()).Moved(pixel.V(0, -32))) 319 | win.Update() 320 | 321 | select { 322 | default: // 323 | case <-second: 324 | // log.Println("Offset:", offset) 325 | log.Println("Last DT", dt) 326 | log.Println("FPS:", frames) 327 | log.Printf("things: %v", len(things)) 328 | //log.Printf("dynamic things: %v", len(world.DObjects)) 329 | frames = 0 330 | } 331 | } 332 | } 333 | 334 | func main() { 335 | pixelgl.Run(run) 336 | } 337 | 338 | func DeleteThings(from []common.Object, at pixel.Rect) []common.Object { 339 | var cleaned []common.Object 340 | for _, o := range from { 341 | if !at.Contains(o.Loc) { 342 | cleaned = append(cleaned, o) 343 | } 344 | 345 | } 346 | return cleaned 347 | } 348 | -------------------------------------------------------------------------------- /librpg/living.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math" 7 | "math/rand" 8 | "time" 9 | 10 | "golang.org/x/image/colornames" 11 | 12 | "github.com/aerth/rpc/librpg/common" 13 | astar "github.com/beefsack/go-astar" 14 | "github.com/faiface/pixel" 15 | "github.com/faiface/pixel/imdraw" 16 | ) 17 | 18 | func init() { 19 | rand.Seed(time.Now().UnixNano()) 20 | } 21 | 22 | var DefaultEntityRectangle = pixel.R(-16, -16, 16, 16) 23 | 24 | type Entity struct { 25 | Name string 26 | Type EntityType 27 | Rate float64 28 | State animState 29 | Frame pixel.Rect // of sprite sheet 30 | counter float64 // inside animation 31 | Rect pixel.Rect // for damage, collisions 32 | Program EntityState 33 | P EntityProperties 34 | Phys ePhys 35 | Dir Direction // facing, attacking 36 | paths []pixel.Vec // path finding 37 | calculated time.Time // last path calculation time 38 | ticker <-chan time.Time // attack speed 39 | w *World // to read/write world and char 40 | imd *imdraw.IMDraw // health bar 41 | } 42 | 43 | func (e *Entity) pathcalc(target pixel.Vec) { 44 | var ( 45 | maxcost = 1000.00 46 | ) 47 | if !e.calculated.IsZero() && time.Since(e.calculated) < time.Millisecond { 48 | 49 | return 50 | } 51 | e.calculated = time.Now() 52 | 53 | // get tiles, give world 54 | tile := e.w.Tile(e.Rect.Center()) 55 | tile.W = e.w 56 | targett := e.w.Tile(target) 57 | targett.W = e.w 58 | 59 | // check 60 | if tile.Type == common.O_NONE { 61 | // bad spawn, respawn 62 | e.P.Health = 0 63 | return 64 | } 65 | if targett.Type == common.O_NONE { 66 | // player must be flying 67 | e.calculated = time.Now().Add(3 * time.Second) 68 | return 69 | } 70 | 71 | est := tile.PathEstimatedCost(targett) 72 | if est < 64 { 73 | //log.Println("direct to char", e, est) 74 | e.paths = []pixel.Vec{e.w.Char.Rect.Center(), e.w.Char.Rect.Center(), e.w.Char.Rect.Center()} 75 | return 76 | } 77 | 78 | if tile.PathEstimatedCost(targett) > 400 { 79 | // too far 80 | //log.Println("path too expensive, trying in 3 seconds") 81 | e.calculated = time.Now().Add(1 * time.Second) 82 | return 83 | } 84 | 85 | // calculate path 86 | path, distance, found := astar.Path(tile, targett) 87 | if found { 88 | if distance > maxcost { // cost path 89 | e.calculated = time.Now().Add(3 * time.Second) 90 | log.Println("too far") 91 | e.paths = nil 92 | return 93 | } 94 | //log.Println("distance:", distance) 95 | var paths []pixel.Vec 96 | 97 | for _, p := range path { 98 | 99 | //log.Println(p) 100 | center := p.(common.Object).Loc.Add(common.DefaultSpriteRectangle.Center()) 101 | paths = append(paths, center) 102 | } 103 | 104 | e.paths = paths 105 | 106 | return 107 | } 108 | //log.Println(e.Name, "no path found, distance:", distance) 109 | } 110 | 111 | type EntityType int 112 | type EntityState int 113 | 114 | const ( 115 | SKELETON EntityType = iota 116 | SKELETON_GUARD 117 | DOBJECT 118 | ) 119 | 120 | type EntityProperties struct { 121 | XP uint64 122 | Health, MaxHealth float64 123 | Mana float64 124 | Loot []Item 125 | IsDead bool 126 | Strength float64 127 | AttackSpeed uint64 128 | CanFly bool 129 | Friendly bool 130 | } 131 | 132 | const ( 133 | S_IDLE EntityState = iota 134 | S_RUN 135 | S_WANDER 136 | S_GUARD 137 | S_SUSPECT 138 | S_HUNT 139 | S_DEAD 140 | ) 141 | 142 | func (e *Entity) String() string { 143 | return fmt.Sprintf("%s at %v,%v", e.Name, int(e.Rect.Center().X), int(e.Rect.Center().Y)) 144 | } 145 | 146 | func (w *World) NewEntity(t EntityType) *Entity { 147 | n := len(w.Entities) 148 | var e *Entity 149 | switch t { 150 | default: 151 | log.Println("unimplemented entity type") 152 | case SKELETON, SKELETON_GUARD: 153 | if w.Sheets[t] == nil || w.Anims[t] == nil { 154 | log.Println("New sheet:", t) 155 | sheet, anims, err := LoadEntitySheet("sprites/"+t.String()+".png", 13, 21) 156 | if err != nil { 157 | panic(fmt.Errorf("error loading skeleton sheet: %v", err)) 158 | } 159 | w.Sheets[t] = sheet 160 | w.Anims[t] = anims 161 | log.Printf("New Skeleton Animation Frames: %v", len(anims[S_RUN])) 162 | } 163 | e = &Entity{ 164 | Name: fmt.Sprintf("%s #%v", t, n), 165 | w: w, 166 | Type: t, 167 | P: EntityProperties{ 168 | Health: 255, 169 | Mana: 255, 170 | Strength: 2, 171 | XP: 20, 172 | MaxHealth: 255, 173 | AttackSpeed: 550, 174 | }, 175 | Rect: DefaultEntityRectangle, 176 | State: Running, 177 | Frame: w.Anims[t][S_RUN][DOWN][0], 178 | Phys: DefaultMobPhys, 179 | Rate: 0.1, 180 | } 181 | e.ticker = time.Tick(time.Millisecond * time.Duration(e.P.AttackSpeed)) 182 | if t == SKELETON_GUARD { 183 | e.P.XP = 200 184 | e.P.MaxHealth = 1000 185 | e.P.Health = 1000 186 | e.P.Strength = 2 187 | } 188 | } 189 | 190 | if e == nil { 191 | return nil 192 | } 193 | 194 | e.P.Loot = RandomLoot() 195 | 196 | w.Entities = append(w.Entities, e) 197 | 198 | return e 199 | } 200 | 201 | type ePhys struct { 202 | RunSpeed float64 203 | Vel pixel.Vec 204 | Gravity float64 205 | Rate float64 206 | } 207 | 208 | // DefaultPhys character 209 | var DefaultMobPhys = ePhys{ 210 | RunSpeed: 40.5, 211 | Gravity: 50.00, 212 | Rate: 2, 213 | } 214 | 215 | func (e *Entity) Draw(t pixel.Target, w *World) { 216 | 217 | sprite := pixel.NewSprite(nil, pixel.Rect{}) 218 | // draw the correct frame with the correct position and direction 219 | 220 | scaling := 0.5 221 | if e.Type == SKELETON_GUARD { 222 | scaling = 0.7 223 | } 224 | sprite.Set(w.Sheets[e.Type], e.Frame) 225 | sprite.Draw(t, pixel.IM.Scaled(pixel.ZV, scaling).Moved(e.Rect.Center())) 226 | //sprite.Draw(t, pixel.IM.Scaled(pixel.ZV, 0.5).Moved(e.Rect.Center())) 227 | 228 | // HP bars 229 | if e.imd == nil { 230 | e.imd = imdraw.New(nil) 231 | } 232 | e.imd.Clear() 233 | rect := e.Rect.Norm() 234 | rect.Max.Y = rect.Min.Y + 2 235 | rect.Max.X = rect.Min.X + 30 236 | if e.P.Health > 0 { 237 | common.DrawBar(e.imd, colornames.Red, e.P.Health, e.P.MaxHealth, rect) 238 | e.imd.Draw(t) 239 | } 240 | /* good debug square 241 | e.imd.Color = colornames.Green 242 | e.imd.Push(e.Rect.Min, e.Rect.Max) 243 | e.imd.Rectangle(1) 244 | e.imd.Draw(t) 245 | */ 246 | } 247 | 248 | func (e *Entity) Center() pixel.Vec { 249 | return e.Rect.Center() 250 | } 251 | 252 | func (e *Entity) ChangeMind(dt float64) { 253 | if t := e.w.Tile(e.Center()); t.Type != common.O_TILE { 254 | e.Phys.Vel = pixel.ZV 255 | return 256 | } 257 | if e.w.Char.Invisible { 258 | e.Phys.Vel = pixel.ZV 259 | return 260 | } 261 | 262 | r := pixel.Rect{e.Rect.Center(), e.w.Char.Rect.Center()} 263 | if r.Size().Len() < e.Rect.Size().Len()/2 { 264 | // log.Println("in attack range", r.Size().Len()) 265 | e.Phys.Vel = e.Rect.Center().Sub(e.w.Char.Rect.Center()).Unit().Scaled(-e.Phys.RunSpeed) 266 | select { 267 | 268 | case <-e.ticker: 269 | e.w.Char.Damage(uint(rand.Intn(10*int(e.P.Strength))), e.Name) 270 | default: 271 | } 272 | 273 | return 274 | } 275 | 276 | if e.P.CanFly { 277 | log.Println("FLYING", e.Name) 278 | if !e.w.Char.Invisible { 279 | 280 | e.Phys.Vel = e.Rect.Center().Sub(e.w.Char.Rect.Center()).Unit().Scaled(-e.Phys.RunSpeed) 281 | } else { 282 | e.Phys.Vel = pixel.ZV 283 | } 284 | 285 | return 286 | } 287 | e.pathcalc(e.w.Char.Rect.Center()) 288 | if len(e.paths) > 2 { 289 | e.Phys.Vel = e.Rect.Center().Sub(e.paths[len(e.paths)-2]).Unit().Scaled(-e.Phys.RunSpeed) 290 | } 291 | } 292 | 293 | func (e *Entity) Update(dt float64) { 294 | blk := e.w.Tile(e.Rect.Center()) 295 | if blk.Type != common.O_TILE { 296 | old := e.Rect.Center() 297 | e.Rect = DefaultEntityRectangle.Moved(common.TileNear(e.w.Tiles, e.Center()).Loc) 298 | log.Println("Moved skel:", old, "to", e.Rect.Center()) 299 | return 300 | } 301 | 302 | e.counter += dt 303 | w := e.w 304 | i := int(math.Floor(e.counter / e.Rate)) 305 | //frame := i % len(e.Anims[e.Program][e.Dir]) 306 | if e.Phys.Vel.X != 0 || e.Phys.Vel.Y != 0 { 307 | e.Program = S_RUN 308 | } else { 309 | e.Program = S_IDLE 310 | } 311 | if len(w.Anims[e.Type][e.Program][e.Dir]) == 0 { 312 | log.Println("bad sprite:", e.Name, e.Program, e.Dir) 313 | return 314 | } 315 | // choose frame 316 | e.Frame = w.Anims[e.Type][e.Program][e.Dir][i%len(w.Anims[e.Type][e.Program][e.Dir])] 317 | 318 | // move 319 | next := e.Rect.Moved(e.Phys.Vel.Scaled(dt)) 320 | t := w.Tile(next.Center()) 321 | if t.Type == common.O_NONE && !e.P.CanFly { 322 | return 323 | } 324 | if !e.P.CanFly && t.Type == common.O_BLOCK { 325 | log.Println(e.Type, "got blocked", t.Loc) 326 | next = e.Rect.Moved(e.Phys.Vel.Scaled(-dt * 10)) 327 | if w.Tile(next.Center()).Type != common.O_TILE { 328 | log.Println("returning") 329 | return 330 | } 331 | } 332 | 333 | // log.Println(e.Name, "wants to go", next.Center(), "from", e.Rect.Center()) 334 | f := func(dot pixel.Vec) bool { 335 | if e.P.CanFly { 336 | return true 337 | } 338 | for _, c := range w.Blocks { 339 | if c.Type == common.O_BLOCK && c.Rect.Contains(dot) { 340 | return false 341 | } 342 | } 343 | return true 344 | } 345 | // only walk on tiles 346 | f2 := func(dot pixel.Vec) bool { 347 | if e.P.CanFly { 348 | return true 349 | } 350 | for _, c := range w.Tiles { 351 | if c.Type == common.O_TILE && c.Rect.Contains(dot) { 352 | return true 353 | } 354 | 355 | } 356 | return false 357 | } 358 | if f(next.Center()) && f2(next.Center()) { 359 | e.Rect = next 360 | } else { 361 | //log.Println("cant move", e.Name, "to ", next.Center(), w.Tile(next.Center()), e.paths[0]) 362 | if len(e.paths) > 0 { 363 | e.paths = e.paths[:len(e.paths)-1] 364 | } 365 | } 366 | 367 | } 368 | 369 | func (w *World) NewMobs(n int) { 370 | if w.Settings.NumEnemy == 0 { 371 | w.Settings.NumEnemy = n 372 | } 373 | if n != 0 { 374 | npc := w.NewEntity(SKELETON_GUARD) 375 | npc.Phys.RunSpeed = 10 376 | npc.P.Health = 2000 377 | npc.P.MaxHealth = 2000 378 | // npc.CanFly = true 379 | npc.Rect = npc.Rect.Moved(common.FindRandomTile(w.Tiles)) 380 | 381 | for i := 1; i < n; i++ { 382 | npc = w.NewEntity(SKELETON) 383 | npc.Rect = npc.Rect.Moved(common.FindRandomTile(w.Tiles)) 384 | } 385 | 386 | } 387 | 388 | } 389 | -------------------------------------------------------------------------------- /librpg/world.go: -------------------------------------------------------------------------------- 1 | package rpg 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "time" 8 | 9 | "golang.org/x/image/colornames" 10 | 11 | "github.com/aerth/rpc/librpg/common" 12 | "github.com/aerth/rpc/librpg/maps" 13 | "github.com/faiface/pixel" 14 | "github.com/faiface/pixel/imdraw" 15 | "github.com/faiface/pixel/text" 16 | ) 17 | 18 | var SpriteFrame = pixel.R(-100, -100, 100, 100) 19 | 20 | // World holds all information about a world 21 | type World struct { 22 | Name string 23 | Bounds pixel.Rect 24 | Regions []*Region 25 | DObjects []*DObject 26 | Tiles, Blocks []common.Object // sorted 27 | Background string // path to pic, will be repeated xy if not empty 28 | background *pixel.Sprite // sprite to repeat xy 29 | Batches map[EntityType]*pixel.Batch // one batch for every spritemap 30 | Color pixel.RGBA // clear window with this color 31 | Entities []*Entity 32 | Sheets map[EntityType]pixel.Picture 33 | Anims map[EntityType]map[EntityState]map[Direction][]pixel.Rect // frankenmap 34 | Char *Character 35 | Animations []*Animation 36 | Messages []string 37 | Settings WorldSettings 38 | imd *imdraw.IMDraw 39 | text *text.Text 40 | } 41 | 42 | type WorldSettings struct { 43 | NumEnemy int 44 | } 45 | 46 | func NewWorld(name string, difficulty int, seed string) *World { 47 | w := new(World) 48 | w.Name = name 49 | if name == "" && seed == "" { 50 | seed = fmt.Sprintf("%d", rand.Int63()) 51 | } 52 | if name == "" { 53 | w.Name = fmt.Sprintf("world seed %q", seed) 54 | } 55 | w.Color = RandomColor() 56 | w.Sheets = make(map[EntityType]pixel.Picture) 57 | w.Anims = make(map[EntityType]map[EntityState]map[Direction][]pixel.Rect) 58 | char := NewCharacter("char") 59 | char.Inventory = []Item{MakeGold(uint64(rand.Intn(7)))} // start with some loot 60 | char.W = w 61 | w.Char = char 62 | w.Batches = map[EntityType]*pixel.Batch{} 63 | 64 | // create sheets, animations , batch for each sprite map 65 | for _, t := range []EntityType{SKELETON, SKELETON_GUARD} { 66 | sheet, anims, err := LoadEntitySheet("sprites/"+t.String()+".png", 13, 21) 67 | if err != nil { 68 | panic(fmt.Errorf("error loading sheet: %s %v", t, err)) 69 | } 70 | 71 | w.Sheets[t] = sheet 72 | w.Anims[t] = anims 73 | w.Batches[t] = pixel.NewBatch(&pixel.TrianglesData{}, w.Sheets[t]) 74 | 75 | } 76 | if name != "" { 77 | log.Println("Loading map", name) 78 | if e := w.LoadMap("maps/" + name + ".map"); e != nil { 79 | // log.Println(e) 80 | if e2 := w.LoadMapFile(name); e2 != nil { 81 | log.Println("fatal:", e2) 82 | return nil 83 | } 84 | } 85 | } else { 86 | log.Println("generating map with seed:", seed) 87 | omap := maps.GenerateMap(seed) 88 | if e := w.injectMap(omap); e != nil { 89 | log.Println("injekcting mapL:", e) 90 | return nil 91 | } 92 | } 93 | if len(w.Tiles) == 0 { 94 | log.Println("Invalid map. No objects found") 95 | return nil 96 | } 97 | char.Rect = char.Rect.Moved(common.FindRandomTile(w.Tiles)) 98 | w.Settings.NumEnemy = difficulty 99 | return w 100 | } 101 | 102 | func (w World) String() string { 103 | return w.Name 104 | } 105 | func (w *World) Update(dt float64) { 106 | w.checkLevel() 107 | // clean dynamic objecfts 108 | dobjects := []*DObject{} 109 | for i := range w.DObjects { 110 | // not game time, could be. 111 | if time.Since(w.DObjects[i].Until) > time.Millisecond { 112 | continue 113 | } 114 | dobjects = append(dobjects, w.DObjects[i]) 115 | } 116 | 117 | w.DObjects = dobjects 118 | 119 | // clean mobs 120 | entities := []*Entity{} 121 | for i := range w.Entities { 122 | if len(w.Entities) < i || w.Entities[i] == nil { 123 | continue 124 | } 125 | w.Entities[i].ChangeMind(dt) 126 | w.Entities[i].Update(dt) 127 | if w.Entities[i].P.Health > 0 { 128 | entities = append(entities, w.Entities[i]) 129 | continue 130 | } 131 | 132 | // entity is dead, spawn another 133 | if len(w.Entities) > 10 { 134 | continue 135 | } 136 | npc := w.NewEntity(SKELETON_GUARD) 137 | npc.Rect = npc.Rect.Moved(common.FindRandomTile(w.Tiles)) 138 | entities = append(entities, npc) 139 | npc = w.NewEntity(SKELETON) 140 | npc.Rect = npc.Rect.Moved(common.FindRandomTile(w.Tiles)) 141 | entities = append(entities, npc) 142 | } 143 | w.Entities = entities 144 | 145 | // update animations 146 | if len(w.Animations) > 0 { 147 | for i := range w.Animations { 148 | w.Animations[i].update(dt) 149 | } 150 | 151 | } 152 | 153 | // animations effect entities 154 | for _, a := range w.Animations { 155 | if a == nil || time.Since(a.start) < time.Millisecond*300 || time.Since(a.until) > time.Millisecond { 156 | continue 157 | } 158 | 159 | // range each of the world's living things 160 | for i, v := range w.Entities { 161 | 162 | // see if they are in range 163 | if a.rect.Contains(v.Rect.Center()) { 164 | if a.ticker != nil { 165 | select { 166 | default: 167 | continue 168 | case <-a.ticker: 169 | // 170 | } 171 | } 172 | //w.Message(fmt.Sprintf("%s took %v damage", v.Name, a.damage)) 173 | w.Entities[i].P.Health -= a.damage 174 | if w.Entities[i].P.Health <= 0 { 175 | // entity damage should be function 176 | if w.Entities[i].P.IsDead { 177 | w.Entities[i].P.Health = 0 178 | continue 179 | } 180 | w.Entities[i].P.Health = 0 181 | w.Entities[i].P.IsDead = true 182 | 183 | // add a new dynamic object 'loot' to the world 184 | w.NewLoot(v.Center(), v.P.Loot) 185 | 186 | // increase player kill count 187 | w.Char.Stats.Kills++ 188 | 189 | // increase player experience 190 | w.Char.ExpUp(v.P.XP) 191 | 192 | //log.Println("Got new loot!:", FormatItemList(v.P.Loot)) 193 | //w.Char.Inventory = StackItems(w.Char.Inventory, v.P.Loot) 194 | 195 | } 196 | 197 | log.Printf("%s took %v damage, now at %v HP", 198 | w.Entities[i].Name, a.damage, w.Entities[i].P.Health) 199 | } 200 | } 201 | } 202 | 203 | } 204 | 205 | func (w *World) DrawEntity(n int) { 206 | w.Entities[n].Draw(w.Batches[w.Entities[n].Type], w) 207 | 208 | } 209 | 210 | // Tile scans tiles and returns the first tile located at dot 211 | func (w *World) Tile(dot pixel.Vec) common.Object { 212 | if w.Tiles == nil { 213 | log.Println("nil tiles!") 214 | return common.Object{W: w, Type: common.O_BLOCK} 215 | } 216 | 217 | if len(w.Tiles) == 0 { 218 | log.Println("no tiles to look in") 219 | return common.Object{W: w, Type: common.O_BLOCK} 220 | } 221 | for i := len(w.Tiles) - 1; i >= 0; i-- { 222 | if w.Tiles[i].Rect.Contains(dot) { 223 | ob := w.Tiles[i] 224 | ob.W = w 225 | return ob 226 | } 227 | } 228 | // log.Println("no tiles found at location:", dot) 229 | // panic("bug") 230 | return common.Object{W: w, Type: common.O_BLOCK} 231 | } 232 | 233 | // Block scans blocks and returns the first block located at dot 234 | func (w *World) Block(dot pixel.Vec) common.Object { 235 | for i := range w.Blocks { 236 | if w.Blocks[i].Rect.Contains(dot) { 237 | return w.Blocks[i] 238 | } 239 | } 240 | return common.Object{} 241 | } 242 | 243 | // Object at location 244 | func (w *World) Object(dot pixel.Vec) common.Object { 245 | if w.Blocks != nil { 246 | for _, v := range w.Blocks { 247 | if v.Rect.Contains(dot) { 248 | return v 249 | } 250 | } 251 | } 252 | for _, v := range w.Tiles { 253 | if v.Rect.Contains(dot) { 254 | return v 255 | } 256 | } 257 | return common.Object{Type: common.O_NONE} 258 | 259 | } 260 | 261 | /* 262 | // Object returns the object at dot, very expensive 263 | func (w *World) Object(dot pixel.Vec) Object { 264 | 265 | var ob Object 266 | for i := range w.Objects { 267 | if w.Objects[i].Rect.Contains(dot) { 268 | ob = w.Objects[i] 269 | if ob.Type == O_BLOCK { // prefer block over tile 270 | return ob 271 | } 272 | } 273 | } 274 | return ob 275 | 276 | } 277 | */ 278 | func (w *World) Draw(target pixel.Target) { 279 | for i := range w.Entities { 280 | w.DrawEntity(i) 281 | 282 | } 283 | for i := range w.Batches { 284 | w.Batches[i].Draw(target) 285 | } 286 | if w.imd == nil { 287 | w.imd = imdraw.New(nil) 288 | } 289 | 290 | /* 291 | w.imd.Clear() 292 | w.imd.Color = pixel.ToRGBA(colornames.Orange).Scaled(0.5) 293 | w.imd.Push(w.Char.Rect.Min, w.Char.Rect.Max) 294 | w.imd.Rectangle(3) 295 | w.imd.Draw(target) 296 | */ 297 | } 298 | 299 | func (w *World) ShowAnimations(imd *imdraw.IMDraw) { 300 | for i := range w.Animations { 301 | if w.Animations[i] != nil { 302 | w.Animations[i].draw(imd) 303 | } 304 | } 305 | 306 | } 307 | 308 | func (w *World) HighlightPaths(target pixel.Target) { 309 | imd := imdraw.New(nil) 310 | for i := range w.Entities { 311 | color := common.TransparentRed 312 | if len(w.Entities[i].paths) != 0 { 313 | for _, vv := range w.Entities[i].paths { 314 | //color = color.Scaled(0.3) 315 | imd.Color = color 316 | v := w.Tile(vv) 317 | imd.Push(v.Rect.Min, v.Rect.Max) 318 | imd.Rectangle(4) 319 | } 320 | } 321 | } 322 | color := colornames.Purple 323 | imd.Color = color 324 | imd.Push(w.Char.Rect.Min, w.Char.Rect.Max) 325 | imd.Rectangle(2) 326 | imd.Draw(target) 327 | 328 | } 329 | 330 | func (w *World) Clean() { 331 | for i := range w.Batches { 332 | w.Batches[i].Clear() 333 | } 334 | } 335 | 336 | func (w *World) CleanAnimations() { 337 | anims := []*Animation{} 338 | now := time.Now().UnixNano() 339 | for i := range w.Animations { 340 | if w.Animations[i] != nil && w.Animations[i].until.UnixNano() > now { 341 | anims = append(anims, w.Animations[i]) 342 | } else { 343 | //log.Println("removing animation", i) 344 | } 345 | } 346 | w.Animations = anims 347 | } 348 | func (w *World) Reset() { 349 | w.Char.Health = 255 350 | w.Char.Stats = DefaultStats 351 | w.Char.Level = 0 352 | w.Char.Mana = 255 353 | w.Char.Inventory = []Item{createLoot()} 354 | w.Char.Rect = DefaultPhys.Rect.Moved(common.FindRandomTile(w.Tiles)) 355 | w.Char.Phys.Vel = pixel.ZV 356 | w.Entities = nil 357 | w.NewMobs(w.Settings.NumEnemy) 358 | w.Animations = nil 359 | 360 | } 361 | 362 | func (w *World) IsLoot(location pixel.Vec) ([]Item, bool) { 363 | var got []Item 364 | var objects []*DObject 365 | var found = false 366 | for _, ob := range w.DObjects { 367 | if !found && ob.Type == D_LOOT && ob.Object.Rect.Contains(location) { 368 | got = ob.Contains 369 | found = true 370 | 371 | } else { 372 | objects = append(objects, ob) 373 | } 374 | 375 | } 376 | 377 | w.DObjects = objects 378 | return got, found 379 | } 380 | 381 | func (w *World) Init() *pixel.Batch { 382 | spritesheet, spritemap := LoadSpriteSheet("tileset.png") 383 | globebatch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet) 384 | for _, v := range w.Tiles { 385 | v.Draw(globebatch, spritesheet, spritemap, 0) 386 | } 387 | for _, v := range w.Blocks { 388 | v.Draw(globebatch, spritesheet, spritemap, 0) 389 | } 390 | return globebatch 391 | } 392 | 393 | func (w *World) drawTiles(path string) error { 394 | spritesheet, spritemap := LoadSpriteSheet(path) 395 | // layers (TODO: slice?) 396 | // batch sprite drawing 397 | globebatch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet) 398 | // water world 67 wood, 114 117 182 special, 121 135 dirt, 128 blank, 20 grass 399 | // rpg.DrawPattern(batch, spritemap[53], pixel.R(-3000, -3000, 3000, 3000), 100) 400 | 401 | globebatch.Clear() 402 | // draw it on to canvasglobe 403 | for _, o := range w.Tiles { 404 | o.Draw(globebatch, spritesheet, spritemap, 0) 405 | } 406 | for _, o := range w.Blocks { 407 | o.Draw(globebatch, spritesheet, spritemap, 0) 408 | } 409 | w.Batches[EntityType(-1)] = globebatch 410 | return nil 411 | } 412 | -------------------------------------------------------------------------------- /cmd/aerpg/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "math" 8 | "math/rand" 9 | "os" 10 | "runtime/pprof" 11 | "time" 12 | 13 | "golang.org/x/image/colornames" 14 | 15 | // _ "image/png" 16 | 17 | rpg "github.com/aerth/rpc/librpg" 18 | "github.com/aerth/rpc/librpg/common" 19 | "github.com/faiface/pixel" 20 | "github.com/faiface/pixel/imdraw" 21 | "github.com/faiface/pixel/pixelgl" 22 | ) 23 | 24 | func init() { 25 | rand.Seed(time.Now().UnixNano()) 26 | } 27 | 28 | var ( 29 | flagenemies = flag.Int("e", 2, "number of enemies to begin with") 30 | flaglevel = flag.String("test", "", "custom world test (filename)") 31 | flagseed = flag.String("seed", fmt.Sprintf("%d", rand.Int63()), "new random world") 32 | 33 | debug = flag.Bool("v", false, "extra logs") 34 | ) 35 | 36 | const ( 37 | LEFT = rpg.LEFT 38 | RIGHT = rpg.RIGHT 39 | UP = rpg.UP 40 | DOWN = rpg.DOWN 41 | UPLEFT = rpg.UPLEFT 42 | UPRIGHT = rpg.UPRIGHT 43 | DOWNLEFT = rpg.DOWNLEFT 44 | DOWNRIGHT = rpg.DOWNRIGHT 45 | ) 46 | 47 | var ( 48 | ZV = pixel.ZV 49 | IM = pixel.IM 50 | V = pixel.V 51 | R = pixel.R 52 | ) 53 | 54 | var ( 55 | defaultzoom = 3.0 56 | camZoomSpeed = 1.20 57 | ) 58 | 59 | func run() { 60 | if *debug { 61 | log.SetFlags(log.Lshortfile) 62 | } else { 63 | log.SetFlags(log.Lmicroseconds) 64 | 65 | } 66 | f, err := os.Create("p.debug") 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | pprof.StartCPUProfile(f) 71 | defer pprof.StopCPUProfile() 72 | 73 | rand.Seed(time.Now().UnixNano()) 74 | 75 | winbounds := pixel.R(0, 0, 800, 600) 76 | fmt.Println("Welcome to", rpg.Version()) 77 | fmt.Println("Source code: https://github.com/aerth/rpg") 78 | fmt.Println("Please select screen resolution:") 79 | fmt.Println("1. 800x600") 80 | fmt.Println("2. 1024x768") 81 | fmt.Println("3. 1280x800") 82 | fmt.Println("4. 1280x800 undecorated") 83 | fmt.Println("Hit CTRL+F during normal gameplay for full screen toggle") 84 | var screenres int 85 | _, err = fmt.Scanf("%d", &screenres) 86 | if err != nil { 87 | fmt.Println("... choosing 800x600") 88 | screenres = 0 89 | } 90 | 91 | // window options 92 | cfg := pixelgl.WindowConfig{ 93 | Title: rpg.Version(), 94 | Bounds: winbounds, 95 | Undecorated: false, 96 | VSync: true, 97 | } 98 | 99 | switch screenres { 100 | default: 101 | case 2: 102 | winbounds = pixel.R(0, 0, 1024, 768) 103 | case 3: 104 | winbounds = pixel.R(0, 0, 1280, 800) 105 | case 4: 106 | log.Println("undecorated!") 107 | winbounds = pixel.R(0, 0, 1280, 800) 108 | cfg.Undecorated = true 109 | } 110 | 111 | cfg.Bounds = winbounds 112 | 113 | // create window 114 | win, err := pixelgl.NewWindow(cfg) 115 | if err != nil { 116 | panic(err) 117 | } 118 | win.SetSmooth(false) 119 | buttons := []rpg.Button{ 120 | {Name: "manastorm", Frame: pixel.R(10, 10, 42, 42)}, 121 | {Name: "magicbullet", Frame: pixel.R(42, 10, 42+42, 42)}, 122 | } 123 | // START 124 | //world.Char.Rect = world.Char.Rect.Moved(V(33, 33)) 125 | // load world 126 | // worldbounds = pixel.R(float64(-4000), float64(-4000), float64(4000), float64(4000)) 127 | cursorsprite := common.GetCursor(1) 128 | 129 | // world generate 130 | world := rpg.NewWorld(*flaglevel, *flagenemies, *flagseed) 131 | if world == nil { 132 | return 133 | } 134 | // world tile sprite sheet 135 | spritesheet, spritemap := rpg.LoadSpriteSheet("tileset.png") 136 | 137 | // layers (TODO: slice?) 138 | // batch sprite drawing 139 | animbatch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet) 140 | 141 | // load loot sprite 142 | goldsheet, err := common.LoadPicture("sprites/loot.png") 143 | if err != nil { 144 | panic("need sprites/loot.png") 145 | } 146 | 147 | // full sprite 148 | goldsprite := pixel.NewSprite(goldsheet, goldsheet.Bounds()) 149 | 150 | // loot batch 151 | lootbatch := pixel.NewBatch(&pixel.TrianglesData{}, goldsheet) 152 | 153 | // Fill in with water: 154 | // water world 67 wood, 114 117 182 special, 121 135 dirt, 128 blank, 20 grass 155 | // rpg.DrawPattern(batch, spritemap[53], pixel.R(-3000, -3000, 3000, 3000), 100) 156 | 157 | // draw menu bar 158 | menubatch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet) 159 | common.DrawPattern(menubatch, spritemap[67], pixel.R(0, 0, win.Bounds().W()+20, 60), 100) 160 | for _, btn := range buttons { 161 | spritemap[200].Draw(menubatch, IM.Moved(btn.Frame.Center())) 162 | } 163 | 164 | // create Mobs 165 | world.NewMobs(*flagenemies) 166 | l := time.Now() 167 | var last = &l 168 | second := time.Tick(time.Second) 169 | frames := 0 170 | var camZoom = new(float64) 171 | var dt = new(float64) 172 | t1 := time.Now() 173 | fontsize := 36.00 174 | if win.Bounds().Max.X < 1100 { 175 | fontsize = 24.00 176 | } 177 | win.SetCursorVisible(false) 178 | text := rpg.NewText(fontsize) 179 | // start loop 180 | imd := imdraw.New(nil) 181 | rand.Seed(time.Now().UnixNano()) 182 | var fullscreen = false 183 | //var latest string 184 | //redrawWorld(world) 185 | log.Println("Reticulating Splines...") 186 | globebatch := world.Init() 187 | 188 | var statustxt string 189 | MainLoop: 190 | for !win.Closed() { 191 | // show title menu 192 | rpg.TitleMenu(win) 193 | 194 | // reset world 195 | world.Reset() 196 | var message = "Welcome to the world!\nCtrl+F Full Screen\nCtrl+Q Quit\n\nInventory: 'i'\nSPACE to continue" 197 | message = "" 198 | GameLoop: 199 | for !win.Closed() { 200 | 201 | *dt = time.Since(*last).Seconds() 202 | *last = time.Now() 203 | 204 | // check if ded 205 | if world.Char.Health < 1 { 206 | log.Println("GAME OVER") 207 | log.Printf("You survived for %s.\nYou acquired %s gold", time.Now().Sub(t1), world.Char.CountGold()) 208 | log.Printf("Skeletons killed: %v", world.Char.Stats.Kills) 209 | log.Println(world.Char.StatsReport()) 210 | 211 | break GameLoop 212 | } 213 | 214 | if message != "" { 215 | world.TextBox(win, message) 216 | message = "" 217 | } 218 | 219 | // zoom with mouse scroll, limit when not in debug mode 220 | *camZoom *= math.Pow(camZoomSpeed, win.MouseScroll().Y) 221 | if !*debug && *camZoom > 6.5 { 222 | *camZoom = 6.5 223 | } 224 | if !*debug && *camZoom < 2 { 225 | *camZoom = 2 226 | } 227 | if *debug { 228 | if *camZoom == 0 { 229 | *camZoom = 1 230 | } 231 | } 232 | cam := pixel.IM.Scaled(pixel.ZV, *camZoom).Moved(win.Bounds().Center()).Moved(world.Char.Rect.Center().Scaled(-*camZoom)) 233 | 234 | // drawing 235 | win.Clear(colornames.Blue) 236 | animbatch.Clear() 237 | // if key 238 | if win.JustPressed(pixelgl.KeyQ) && win.Pressed(pixelgl.KeyLeftControl) { 239 | break MainLoop 240 | } 241 | if win.JustPressed(pixelgl.KeyF) && win.Pressed(pixelgl.KeyLeftControl) { 242 | fullscreen = !fullscreen 243 | if fullscreen { 244 | win.SetMonitor(pixelgl.PrimaryMonitor()) 245 | } else { 246 | win.SetMonitor(nil) 247 | } 248 | menubatch.Clear() 249 | win.Update() 250 | newbounds := win.Bounds() 251 | common.DrawPattern(menubatch, spritemap[67], pixel.R(0, 0, newbounds.Max.X+20, 60), 100) 252 | for _, btn := range buttons { 253 | spritemap[200].Draw(menubatch, IM.Moved(btn.Frame.Center())) 254 | } 255 | menubatch.Draw(win) 256 | 257 | } 258 | // teleport random 259 | 260 | if win.JustPressed(pixelgl.Key8) { 261 | world.Char.Rect = common.DefaultSpriteRectangle.Moved(common.TileNear(world.Tiles, world.Char.Rect.Center()).Loc) 262 | } 263 | if win.JustPressed(pixelgl.KeyM) { 264 | world.NewMobs(1) 265 | } 266 | if win.Pressed(pixelgl.KeyLeftControl) && win.JustPressed(pixelgl.KeyL) { 267 | world.RandomLootSomewhere() 268 | } 269 | if win.Pressed(pixelgl.KeyLeftControl) && win.JustPressed(pixelgl.KeyK) { 270 | world.NewLoot(cam.Unproject(win.MousePosition()), []rpg.Item{rpg.RandomMagicItem()}) 271 | } 272 | 273 | // move all enemies (debug) 274 | if win.JustPressed(pixelgl.Key9) { 275 | for _, v := range world.Entities { 276 | v.Rect = rpg.DefaultEntityRectangle.Moved(common.TileNear(world.Tiles, v.Rect.Center()).Loc) 277 | } 278 | } 279 | if win.JustReleased(pixelgl.KeyI) { 280 | rpg.InventoryLoop(win, world) 281 | } 282 | 283 | if win.JustPressed(pixelgl.KeyEqual) { 284 | *debug = !*debug 285 | if *debug { 286 | log.SetFlags(log.Lshortfile) 287 | } else { 288 | log.SetFlags(0) 289 | } 290 | } 291 | 292 | // get direction, velocity 293 | dir := controlswitch(dt, world, win) 294 | 295 | // check collision, move character 296 | world.Char.Update(*dt, dir, world) 297 | 298 | // update everything else (entities, DObjects) 299 | world.Update(*dt) 300 | world.Clean() 301 | 302 | // camera mode (center on player) 303 | win.SetMatrix(cam) 304 | 305 | // draw map to win (tiles and blocks) 306 | globebatch.Draw(win) 307 | 308 | // highlight enemy paths 309 | if *debug { 310 | world.HighlightPaths(win) 311 | } 312 | 313 | // draw entities and objects (not tiles and blocks) 314 | world.Draw(win) 315 | 316 | // draw animations such as magic spells 317 | imd.Clear() 318 | world.CleanAnimations() 319 | world.ShowAnimations(imd) 320 | imd.Draw(win) 321 | 322 | // highlight player tiles (left right up down and center) 323 | if *debug { 324 | for _, o := range world.Tile(world.Char.Rect.Center()).PathNeighbors() { 325 | ob := o.(common.Object) 326 | ob.W = world 327 | ob.Highlight(win, common.TransparentPurple) 328 | } 329 | } 330 | 331 | // draw all groundscores 332 | lootbatch.Clear() 333 | for _, dob := range world.DObjects { 334 | dob.Object.Draw(lootbatch, goldsheet, []*pixel.Sprite{goldsprite}, 0.5) 335 | } 336 | lootbatch.Draw(win) 337 | 338 | // back to window matrix 339 | win.SetMatrix(pixel.IM) 340 | 341 | // draw player in center of screen 342 | world.Char.Matrix = pixel.IM.Scaled(pixel.ZV, *camZoom).Scaled(pixel.ZV, 0.5).Moved(pixel.V(0, 0)).Moved(win.Bounds().Center()) 343 | world.Char.Draw(win) 344 | 345 | // draw score board 346 | text.Clear() 347 | rpg.DrawScore(win.Bounds(), text, win, 348 | "%v HP · %v MP · %s GP · LVL %v · %v/%v XP · %v Kills %s", world.Char.Health, world.Char.Mana, world.Char.CountGold(), world.Char.Level, world.Char.Stats.XP, world.Char.NextLevel(), world.Char.Stats.Kills, statustxt) 349 | 350 | // draw menubar 351 | menubatch.Draw(win) 352 | if win.JustPressed(pixelgl.Key6) { 353 | //redrawWorld(world) 354 | } 355 | 356 | // draw health, mana, xp bars 357 | world.Char.DrawBars(win, win.Bounds()) 358 | 359 | cursorsprite.Draw(win, pixel.IM.Scaled(pixel.ZV, 4).Moved(win.MousePosition()).Moved(pixel.V(0, -16))) 360 | 361 | // done drawing 362 | if !win.Pressed(pixelgl.KeyLeftControl) && win.Pressed(pixelgl.MouseButtonRight) { 363 | mouseloc := win.MousePosition() 364 | 365 | mcam := pixel.IM.Moved(win.Bounds().Center()) 366 | mouseloc1 := mcam.Unproject(mouseloc) 367 | unit := mouseloc1.Unit() 368 | // log.Println("unit:", unit) 369 | dirmouse := rpg.UnitToDirection(unit) 370 | 371 | // log.Println("direction:", dir) 372 | 373 | switch dirmouse { 374 | 375 | case LEFT: 376 | world.Char.Phys.Vel.X = -world.Char.Phys.RunSpeed * (1 + *dt) 377 | world.Char.Dir = LEFT 378 | case RIGHT: 379 | world.Char.Phys.Vel.X = +world.Char.Phys.RunSpeed * (1 + *dt) 380 | world.Char.Dir = RIGHT 381 | case UP: 382 | world.Char.Phys.Vel.Y = +world.Char.Phys.RunSpeed * (1 + *dt) 383 | world.Char.Dir = UP 384 | case DOWN: 385 | world.Char.Phys.Vel.Y = -world.Char.Phys.RunSpeed * (1 + *dt) 386 | world.Char.Dir = DOWN 387 | case UPLEFT: 388 | world.Char.Phys.Vel.Y = +world.Char.Phys.RunSpeed * (1 + *dt) 389 | world.Char.Phys.Vel.X = -world.Char.Phys.RunSpeed * (1 + *dt) 390 | world.Char.Dir = UPLEFT 391 | case UPRIGHT: 392 | world.Char.Phys.Vel.X = +world.Char.Phys.RunSpeed * (1 + *dt) 393 | world.Char.Phys.Vel.Y = +world.Char.Phys.RunSpeed * (1 + *dt) 394 | world.Char.Dir = UPRIGHT 395 | case DOWNLEFT: 396 | world.Char.Phys.Vel.Y = -world.Char.Phys.RunSpeed * (1 + *dt) 397 | world.Char.Phys.Vel.X = -world.Char.Phys.RunSpeed * (1 + *dt) 398 | world.Char.Dir = DOWNLEFT 399 | case DOWNRIGHT: 400 | world.Char.Phys.Vel.X = +world.Char.Phys.RunSpeed * (1 + *dt) 401 | world.Char.Phys.Vel.Y = -world.Char.Phys.RunSpeed * (1 + *dt) 402 | world.Char.Dir = DOWNRIGHT 403 | default: 404 | } 405 | } 406 | if !win.Pressed(pixelgl.KeyLeftControl) && win.JustPressed(pixelgl.MouseButtonLeft) { 407 | mouseloc := win.MousePosition() 408 | if b, f, ok := world.IsButton(buttons, mouseloc); ok { 409 | log.Println(mouseloc) 410 | log.Printf("Clicked button: %q", b.Name) 411 | f(win, world) 412 | 413 | } else { 414 | 415 | // pick up loot 416 | if loot, ok := world.IsLoot(cam.Unproject(mouseloc)); ok { 417 | statustxt = world.Char.PickUp(loot) 418 | 419 | } else { 420 | mcam := pixel.IM.Moved(win.Bounds().Center()) 421 | mouseloc1 := mcam.Unproject(mouseloc) 422 | // magic bullet 423 | unit := mouseloc1.Unit() 424 | // log.Println("unit:", unit) 425 | dir := rpg.UnitToDirection(unit) 426 | // log.Println("direction:", dir) 427 | if dir == rpg.OUT || dir == rpg.IN { 428 | dir = world.Char.Dir 429 | } 430 | if world.Char.Mana > 0 { 431 | world.Action(world.Char, world.Char.Rect.Center(), rpg.MagicBullet, dir) 432 | } else { 433 | log.Println("Not enough mana") 434 | } 435 | } 436 | } 437 | } 438 | //spritemap[20].Draw(menubar, pixel.IM.Scaled(ZV, 10).Moved(pixel.V(30, 30))) 439 | //menubar.Draw(win, pixel.IM) 440 | win.Update() 441 | 442 | // fps, gps 443 | frames++ 444 | gps := world.Char.Rect.Center() 445 | select { 446 | default: //keep going 447 | case <-second: 448 | str := fmt.Sprintf(""+ 449 | "FPS: %d | GPS: (%v,%v) | VEL: (%v) | HP: (%v) ", 450 | frames, int(gps.X), int(gps.Y), int(world.Char.Phys.Vel.Len()), world.Char.Health) 451 | win.SetTitle(str) 452 | 453 | if *debug { 454 | log.Println(frames, "frames per second") 455 | log.Println(len(world.DObjects), "dynamic objects") 456 | log.Println(len(world.Animations), "animations") 457 | log.Println(len(world.Entities), "living entities") 458 | } 459 | frames = 0 460 | } 461 | 462 | } 463 | } 464 | log.Printf("You survived for %s.\nYou acquired %s gold", time.Now().Sub(t1), world.Char.CountGold()) 465 | log.Println("Inventory:", rpg.FormatItemList(world.Char.Inventory)) 466 | log.Printf("Skeletons killed: %v", world.Char.Stats.Kills) 467 | log.Println(world.Char.StatsReport()) 468 | 469 | } 470 | 471 | func controlswitch(dt *float64, w *rpg.World, win *pixelgl.Window) rpg.Direction { 472 | // Manastorm 473 | if win.JustPressed(pixelgl.KeySpace) || win.JustPressed(pixelgl.MouseButtonMiddle) { 474 | if w.Char.Mana > 0 { 475 | w.Action(w.Char, w.Char.Rect.Center(), rpg.ManaStorm, w.Char.Dir) 476 | } else { 477 | log.Println("Not enough mana") 478 | } 479 | } 480 | 481 | // Magic bullet 482 | if win.JustPressed(pixelgl.KeyB) { 483 | if w.Char.Mana > 0 { 484 | w.Action(w.Char, w.Char.Rect.Center(), rpg.MagicBullet, w.Char.Dir) 485 | } else { 486 | log.Println("Not enough mana") 487 | } 488 | } 489 | 490 | // Slow Cheat 491 | if win.Pressed(pixelgl.KeyTab) { 492 | *dt /= 8 493 | } 494 | // Fast Cheat 495 | if win.Pressed(pixelgl.KeyLeftShift) { 496 | *dt *= 4 497 | } 498 | 499 | // MP Cheat 500 | if win.Pressed(pixelgl.Key1) { 501 | w.Char.Mana += 1 502 | if w.Char.Mana > 255 { 503 | w.Char.Mana = 255 504 | } 505 | } 506 | 507 | // HP Cheat 508 | if win.Pressed(pixelgl.Key2) { 509 | w.Char.Health += 1 510 | if w.Char.Health > 255 { 511 | w.Char.Health = 255 512 | } 513 | } 514 | 515 | // XP Cheat 516 | if win.Pressed(pixelgl.Key3) { 517 | w.Char.Stats.XP += 10 518 | } 519 | 520 | // Fly mode 521 | if win.Pressed(pixelgl.KeyCapsLock) { 522 | w.Char.Phys.CanFly = !w.Char.Phys.CanFly 523 | } 524 | 525 | // control character with direction and velocity (collision check elsewhere) 526 | dir := w.Char.Dir 527 | if !win.Pressed(pixelgl.KeyLeftControl) && (win.Pressed(pixelgl.KeyLeft) || win.Pressed(pixelgl.KeyH) || win.Pressed(pixelgl.KeyA)) { 528 | w.Char.Phys.Vel.X = -w.Char.Phys.RunSpeed * (1 + *dt) 529 | dir = LEFT 530 | } 531 | if !win.Pressed(pixelgl.KeyLeftControl) && (win.Pressed(pixelgl.KeyRight) || win.Pressed(pixelgl.KeyL) || win.Pressed(pixelgl.KeyD)) { 532 | w.Char.Phys.Vel.X = +w.Char.Phys.RunSpeed * (1 + *dt) 533 | dir = RIGHT 534 | } 535 | if !win.Pressed(pixelgl.KeyLeftControl) && (win.Pressed(pixelgl.KeyDown) || win.Pressed(pixelgl.KeyJ) || win.Pressed(pixelgl.KeyS)) { 536 | w.Char.Phys.Vel.Y = -w.Char.Phys.RunSpeed * (1 + *dt) 537 | dir = DOWN 538 | 539 | } 540 | if !win.Pressed(pixelgl.KeyLeftControl) && (win.Pressed(pixelgl.KeyUp) || win.Pressed(pixelgl.KeyK) || win.Pressed(pixelgl.KeyW)) { 541 | w.Char.Phys.Vel.Y = +w.Char.Phys.RunSpeed * (1 + *dt) 542 | dir = UP 543 | } 544 | 545 | // TODO: fix double velocity on diag 546 | if !win.Pressed(pixelgl.KeyLeftControl) && (win.Pressed(pixelgl.KeyUp) && win.Pressed(pixelgl.KeyLeft)) { 547 | dir = rpg.UPLEFT 548 | } 549 | if !win.Pressed(pixelgl.KeyLeftControl) && (win.Pressed(pixelgl.KeyUp) && win.Pressed(pixelgl.KeyRight)) { 550 | dir = rpg.UPRIGHT 551 | } 552 | if !win.Pressed(pixelgl.KeyLeftControl) && (win.Pressed(pixelgl.KeyDown) && win.Pressed(pixelgl.KeyLeft)) { 553 | dir = rpg.DOWNLEFT 554 | } 555 | if !win.Pressed(pixelgl.KeyLeftControl) && (win.Pressed(pixelgl.KeyDown) && win.Pressed(pixelgl.KeyRight)) { 556 | dir = rpg.DOWNRIGHT 557 | } 558 | return dir 559 | } 560 | func main() { 561 | flag.Parse() 562 | pixelgl.Run(run) 563 | } 564 | -------------------------------------------------------------------------------- /assets/maps/1.map: -------------------------------------------------------------------------------- 1 | [{"L":{"X":224,"Y":-288},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":-288},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":-288},"T":2,"P":{},"S":53},{"L":{"X":224,"Y":-256},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":-256},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":-256},"T":2,"P":{},"S":53},{"L":{"X":224,"Y":-224},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":-224},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":-224},"T":2,"P":{},"S":53},{"L":{"X":224,"Y":-192},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":-192},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":-192},"T":2,"P":{},"S":53},{"L":{"X":224,"Y":-160},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":-160},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":-160},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-288},"T":2,"P":{},"S":53},{"L":{"X":192,"Y":-256},"T":2,"P":{},"S":53},{"L":{"X":192,"Y":-224},"T":2,"P":{},"S":53},{"L":{"X":192,"Y":-192},"T":2,"P":{},"S":53},{"L":{"X":192,"Y":-160},"T":2,"P":{},"S":53},{"L":{"X":192,"Y":-128},"T":2,"P":{},"S":53},{"L":{"X":224,"Y":-128},"T":2,"P":{},"S":53},{"L":{"X":192,"Y":-96},"T":2,"P":{},"S":53},{"L":{"X":224,"Y":-96},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-320,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-256,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-224,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-192,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-160,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":-288},"T":2,"P":{},"S":53},{"L":{"X":320,"Y":-256},"T":2,"P":{},"S":53},{"L":{"X":320,"Y":-224},"T":2,"P":{},"S":53},{"L":{"X":320,"Y":-192},"T":2,"P":{},"S":53},{"L":{"X":320,"Y":-160},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":-128},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":-128},"T":2,"P":{},"S":53},{"L":{"X":320,"Y":-128},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":-96},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":-96},"T":2,"P":{},"S":53},{"L":{"X":320,"Y":-96},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":192},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":224},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":256},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":-128,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":352},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":320,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":32,"Y":384},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":384},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":384},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":384},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":384},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":384},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":384},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":384},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":384},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":384},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":416},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":416},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":416},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":416},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":416},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":416},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":416},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":416},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":416},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":416},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":448},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":448},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":448},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":448},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":448},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":448},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":480},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":480},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":480},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":480},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":480},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":480},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":480},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":480},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":480},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":480},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":512},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":512},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":512},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":512},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":512},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":512},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":512},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":512},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":512},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":512},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":544},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":544},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":544},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":544},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":544},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":544},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":544},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":544},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":544},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":544},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":576},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":576},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":576},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":576},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":576},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":576},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":576},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":576},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":576},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":576},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":608},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":608},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":608},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":608},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":608},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":608},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":608},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":608},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":608},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":608},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":576,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":608,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":-320},"T":2,"P":{},"S":53},{"L":{"X":-96,"Y":-672},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-672},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-672},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-672},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-672},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-672},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-672},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-672},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-672},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-672},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-672},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-640},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-640},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-640},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-640},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-640},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-640},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-640},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-640},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-640},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-640},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-640},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-608},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-608},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-608},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-608},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-608},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-608},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-608},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-608},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-608},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-608},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-608},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-576},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-576},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-576},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-576},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-576},"T":1,"P":{},"S":20},{"L":{"X":64,"Y":-576},"T":1,"P":{},"S":20},{"L":{"X":96,"Y":-576},"T":1,"P":{},"S":20},{"L":{"X":128,"Y":-576},"T":1,"P":{},"S":20},{"L":{"X":160,"Y":-576},"T":1,"P":{},"S":20},{"L":{"X":192,"Y":-576},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":-576},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":-64,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":-32,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":0,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":32,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":-96,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":-352,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-96,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-64,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-32,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":0,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-96,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-64,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-32,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":0,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-96,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-64,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-32,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":0,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-96,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-64,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-32,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":0,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-576,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":288},"T":1,"P":{},"S":20},{"L":{"X":-512,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":-480,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":-448,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":-416,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":-384,"Y":320},"T":1,"P":{},"S":20},{"L":{"X":-544,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":320,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":-672},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":320,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":-640},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":320,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":256,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":288,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":320,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":-320},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":-320},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":-320},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":-320},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":128},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":160},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":192},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":224},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":256},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":288},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":288},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":288},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":288},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":288},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":288},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":288},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":288},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-96,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-64,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-32,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":0,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-96,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-64,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-32,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":0,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-96,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-64,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-32,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":0,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-288,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-96,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-64,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-32,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":0,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":352,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":384,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":416,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":448,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":480,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":512,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":544,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":576,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":320},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":352},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":384},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":416},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":448},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":480},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":608},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-544,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-512,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-480,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-448,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-416,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-384,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-352,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-320,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":-128},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-96},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-64},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-32},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":0},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":32},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-544},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-512},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-480},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-448},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-416},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-384},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-256},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-224},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-192},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":-160},"T":1,"P":{},"S":20},{"L":{"X":-672,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":-608},"T":2,"P":{},"S":53},{"L":{"X":-672,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":-576},"T":2,"P":{},"S":53},{"L":{"X":-672,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":-544},"T":2,"P":{},"S":53},{"L":{"X":-672,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":-512},"T":2,"P":{},"S":53},{"L":{"X":-672,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":-480},"T":2,"P":{},"S":53},{"L":{"X":-672,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":-448},"T":2,"P":{},"S":53},{"L":{"X":-672,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":-416},"T":2,"P":{},"S":53},{"L":{"X":-672,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":-384},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":192},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":192},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":224},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":224},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":256},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":256},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":288},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":288},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":512},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":544},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":640,"Y":576},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":-352},"T":2,"P":{},"S":53},{"L":{"X":-576,"Y":-320},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":128},"T":2,"P":{},"S":53},{"L":{"X":608,"Y":160},"T":2,"P":{},"S":53},{"L":{"X":192,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":224,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":256,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":288,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":320,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":352,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":384,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":416,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":448,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":480,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":512,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":544,"Y":640},"T":1,"P":{},"S":20},{"L":{"X":-288,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-256,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-224,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-192,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-160,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-128,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-96,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-64,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":0,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":32,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":64,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":96,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":128,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":160,"Y":640},"T":2,"P":{},"S":53},{"L":{"X":-608,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":-608,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":-352},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":-320},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":-288},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":64},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":96},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":128},"T":1,"P":{},"S":20},{"L":{"X":640,"Y":160},"T":1,"P":{},"S":20},{"L":{"X":-640,"Y":-256},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":-224},"T":2,"P":{},"S":53},{"L":{"X":-640,"Y":-192},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-636,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-604,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-572,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-540,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-508,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-476,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-444,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-412,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-380,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-348,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-316,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-284,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-252,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-220,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-188,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-156,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-124,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-92,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-60,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-28,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":4,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":36,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":68,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":100,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":132,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":164,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":196,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":228,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":260,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":292,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":324,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":356,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":388,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":420,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":452,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":484,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":516,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":548,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":580,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":612,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":644,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-700},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-668},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-668},"T":2,"P":{},"S":53},{"L":{"X":-636,"Y":-668},"T":2,"P":{},"S":53},{"L":{"X":-604,"Y":-668},"T":2,"P":{},"S":53},{"L":{"X":-572,"Y":-668},"T":2,"P":{},"S":53},{"L":{"X":612,"Y":-668},"T":2,"P":{},"S":53},{"L":{"X":644,"Y":-668},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-668},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-636},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-636},"T":2,"P":{},"S":53},{"L":{"X":-636,"Y":-636},"T":2,"P":{},"S":53},{"L":{"X":-604,"Y":-636},"T":2,"P":{},"S":53},{"L":{"X":-572,"Y":-636},"T":2,"P":{},"S":53},{"L":{"X":612,"Y":-636},"T":2,"P":{},"S":53},{"L":{"X":644,"Y":-636},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-636},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-604},"T":2,"P":{},"S":53},{"L":{"X":612,"Y":-604},"T":2,"P":{},"S":53},{"L":{"X":644,"Y":-604},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-604},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-572},"T":2,"P":{},"S":53},{"L":{"X":612,"Y":-572},"T":2,"P":{},"S":53},{"L":{"X":644,"Y":-572},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-572},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-540},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-540},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-508},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-508},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-476},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-476},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-444},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-444},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-412},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-412},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-380},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-380},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-348},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-348},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-348},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-316},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-316},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-316},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-284},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-284},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-284},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-252},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-252},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-252},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-220},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-220},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-220},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-188},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-188},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-188},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-156},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-156},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-156},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-124},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-124},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-124},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-92},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-92},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-92},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-60},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-60},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-60},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":-28},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":-28},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":-28},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":4},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":4},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":4},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":36},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":36},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":36},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":68},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":68},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":68},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":100},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":100},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":100},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":132},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":132},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":132},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":164},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":164},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":164},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":196},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":196},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":196},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":228},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":228},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":228},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":260},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":260},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":260},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":292},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":292},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":292},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":324},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":324},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":324},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":356},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":356},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":356},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":388},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":388},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":388},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":420},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":420},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":420},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":452},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":452},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":452},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":484},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":484},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":484},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":516},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":516},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":516},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":548},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":548},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":548},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":580},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":580},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":580},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":612},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":612},"T":2,"P":{},"S":53},{"L":{"X":612,"Y":612},"T":2,"P":{},"S":53},{"L":{"X":644,"Y":612},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":612},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":644},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":644},"T":2,"P":{},"S":53},{"L":{"X":-28,"Y":644},"T":2,"P":{},"S":53},{"L":{"X":580,"Y":644},"T":2,"P":{},"S":53},{"L":{"X":612,"Y":644},"T":2,"P":{},"S":53},{"L":{"X":644,"Y":644},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":644},"T":2,"P":{},"S":53},{"L":{"X":-700,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-668,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-636,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-604,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-572,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-540,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-508,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-476,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-444,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-412,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-380,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-348,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-316,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-284,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-252,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-220,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-188,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-156,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-124,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-92,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-60,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":-28,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":4,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":36,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":68,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":100,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":132,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":164,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":196,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":228,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":260,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":292,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":324,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":356,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":388,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":420,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":452,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":484,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":516,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":548,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":580,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":612,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":644,"Y":676},"T":2,"P":{},"S":53},{"L":{"X":676,"Y":676},"T":2,"P":{},"S":53}] --------------------------------------------------------------------------------