Rewrote screens to use static components
20 files changed, 906 insertions(+), 1073 deletions(-) D .woodpecker.yml M Cargo.lock M Cargo.toml M README.md D example.world R src/{blocks.rs => game/blocks.rs} A src/game/camera.rs A src/game/inventory.rs A src/game/mod.rs R src/{types.rs => game/types.rs} R src/{storage.rs => game/world.rs} M src/main.rs M src/p2p.rs M src/screens/build.rs D src/screens/inventory.rs M src/screens/map_creator.rs M src/screens/map_select.rs M src/screens/mod.rs M src/screens/welcome.rs M src/textures.rs
D .woodpecker.yml => .woodpecker.yml +0 -25
@@ 1,25 0,0 @@ platform: linux/arm64 pipeline: build: image: alpine:edge commands: - apk update - apk add rustup - curl https://sh.rustup.rs -sSf | sh -s -- -y - source $HOME/.cargo/env - cd $CI_WORKSPACE - cargo build --release publish: image: woodpeckerci/plugin-gitea-release settings: base_url: https://codeberg.org title: "ALPHA $CI_COMMIT_TAG" notes: "$CI_COMMIT_MESSAGE" prerelease: true files: - $CI_WORKSPACE/target/aarch64-unknown-linux-musl/release/little_town api_key: from_secret: API_KEY when: event: tag
M Cargo.lock => Cargo.lock +1 -1
@@ 1619,7 1619,7 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "little_town" version = "0.1.0" version = "0.2.0" dependencies = [ "async-std", "directories-next",
M Cargo.toml => Cargo.toml +1 -1
@@ 1,7 1,7 @@ [package] name = "little_town" description = "Build a small isometric town" version = "0.1.0" version = "0.2.0" edition = "2021" authors = [ "Jakob Meier <comcloudway@ccw.icu>" ] readme = "README.org"
M README.md => README.md +10 -6
@@ 23,17 23,20 @@ launch the game and the world should appear in the world list ## Roadmap - [x] Placing & Removing blocks - [x] Basic Welcome Screen - [ ] Inventory - [x] Inventory - [x] Basic block list - [ ] Inventory categories - [x] Inventory categories - [x] p2p local multiplayer (using mdns discovery) - [x] world selection & better storage - [x] World selection screen & loading from seperate files - [x] World creation screen - [ ] (Maybe) campaign mode (build & expand a city with limited ressources) - [ ] (MAYBE) campaign mode (build & expand a city with limited ressources) - [ ] Touch control - [ ] Settings menu (i.e for Keybindings) - [ ] Game music? - [ ] Add more assets - [ ] SketchTown Expansion pack - [ ] (MAYBE) [Sketch Desert](https://kenney.nl/assets/sketch-desert) ## Keymap @@ ### in-Game 50,7 53,7 @@ launch the game and the world should appear in the world list | North | `K` | | South | `J` | | West | `H` | | East | `L` || | East | `L` | #### Camera movement @@ | Action | Key(s) / MouseButton | 64,8 67,9 @@ launch the game and the world should appear in the world list | Action | Key(s) / MouseButton | |--------|----------------------| | Select Item & close inventory | Left mouse button (on item) | | Deselect Item | Left mouse button (on item) | | Close Inventory | `Q` or `<Escape>` | | Close Inventory | `Q`, `I` or `<Escape>` | | Page down | `<ArrowDown>` | | Page up | `<ArrowUp>` | ### Level Select screen | Action | Key(s) / MouseButton |
D example.world => example.world +0 -1
@@ 1,1 0,0 @@ {"local":{"build-map":"{\"grid\":{{\"x\":21,\"y\":0,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":17,\"y\":17,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":8,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":16,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":14,\"z\":2}:[\"GrassSlope\",\"South\"],{\"x\":6,\"y\":6,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":1,\"y\":3,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":17,\"y\":9,\"z\":1}:[\"GrassWaterRiver\",\"North\"],{\"x\":2,\"y\":12,\"z\":2}:[\"BuildingDoorWindowsBeige\",\"East\"],{\"x\":3,\"y\":18,\"z\":2}:[\"GrassRiver\",\"East\"],{\"x\":9,\"y\":16,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":1,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":4,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":6,\"y\":3,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":18,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":9,\"y\":13,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":21,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":19,\"z\":1}:[\"GrassWater\",\"East\"],{\"x\":16,\"y\":13,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":24,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":27,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":21,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":17,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":18,\"y\":23,\"z\":2}:[\"CastelWindow\",\"West\"],{\"x\":17,\"y\":21,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":6,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":14,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":5,\"y\":4,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":14,\"y\":21,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":13,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":1,\"z\":2}:[\"CastelWindow\",\"North\"],{\"x\":20,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":25,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":1,\"y\":1,\"z\":2}:[\"CastleBend\",\"East\"],{\"x\":23,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":14,\"z\":2}:[\"GrassSlope\",\"South\"],{\"x\":10,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":3,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":7,\"y\":16,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":21,\"y\":14,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":18,\"y\":20,\"z\":1}:[\"GrassRiverBend\",\"South\"],{\"x\":22,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":4,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":2,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":12,\"z\":1}:[\"GrassWater\",\"South\"],{\"x\":16,\"y\":20,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":10,\"y\":4,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":14,\"y\":15,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":20,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":16,\"z\":2}:[\"GrassSlopeConvex\",\"East\"],{\"x\":14,\"y\":6,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":24,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":11,\"z\":2}:[\"StructureArch\",\"West\"],{\"x\":16,\"y\":8,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":13,\"y\":22,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":24,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":9,\"z\":1}:[\"GrassRiverBend\",\"West\"],{\"x\":2,\"y\":3,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":21,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":12,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":17,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":16,\"y\":10,\"z\":1}:[\"GrassWaterConcave\",\"East\"],{\"x\":14,\"y\":10,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":8,\"y\":25,\"z\":2}:[\"BuildingDoorBeige\",\"South\"],{\"x\":16,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":24,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":11,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":3,\"y\":15,\"z\":2}:[\"GrassPathSlope\",\"East\"],{\"x\":5,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":3,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":0,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":5,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":16,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":7,\"y\":20,\"z\":2}:[\"Trees\",\"South\"],{\"x\":24,\"y\":24,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":3,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":16,\"y\":6,\"z\":1}:[\"GrassRiverBend\",\"East\"],{\"x\":6,\"y\":7,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":19,\"y\":4,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":5,\"y\":5,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":1,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":5,\"y\":3,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":23,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":6,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":6,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":16,\"y\":7,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":13,\"y\":16,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":7,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":11,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":25,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":24,\"y\":20,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":2,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":11,\"y\":26,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":17,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":20,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":11,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":1,\"y\":16,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":5,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":18,\"z\":1}:[\"GrassWaterRiver\",\"South\"],{\"x\":18,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":19,\"z\":2}:[\"Trees\",\"South\"],{\"x\":17,\"y\":15,\"z\":2}:[\"Tree\",\"South\"],{\"x\":19,\"y\":12,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":22,\"y\":21,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":14,\"y\":16,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":18,\"y\":17,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":5,\"y\":1,\"z\":2}:[\"CastleBend\",\"South\"],{\"x\":21,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":11,\"z\":2}:[\"StructureArch\",\"East\"],{\"x\":6,\"y\":9,\"z\":1}:[\"GrassRiverBridge\",\"West\"],{\"x\":11,\"y\":6,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":18,\"y\":13,\"z\":1}:[\"GrassWaterConcave\",\"East\"],{\"x\":19,\"y\":23,\"z\":3}:[\"CastleTowerPurple\",\"South\"],{\"x\":15,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":2,\"z\":1}:[\"GrassRiverBend\",\"South\"],{\"x\":20,\"y\":24,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":9,\"y\":4,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":4,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":7,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":1,\"y\":20,\"z\":2}:[\"GrassPath\",\"East\"],{\"x\":3,\"y\":12,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":16,\"y\":4,\"z\":2}:[\"Trees\",\"South\"],{\"x\":13,\"y\":14,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":18,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":20,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":18,\"z\":2}:[\"Trees\",\"South\"],{\"x\":4,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":16,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":26,\"z\":2}:[\"CastleWall\",\"South\"],{\"x\":23,\"y\":20,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":22,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":4,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":10,\"y\":2,\"z\":2}:[\"Trees\",\"East\"],{\"x\":19,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":5,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":2,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":12,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":23,\"y\":7,\"z\":2}:[\"Trees\",\"South\"],{\"x\":22,\"y\":11,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":5,\"z\":1}:[\"GrassRiverBend\",\"East\"],{\"x\":22,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":8,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":1,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":4,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":23,\"y\":27,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":14,\"y\":1,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":2,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":11,\"y\":3,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":11,\"y\":15,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":10,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":16,\"y\":5,\"z\":1}:[\"GrassRiverBend\",\"West\"],{\"x\":21,\"y\":6,\"z\":1}:[\"GrassPathSplit\",\"North\"],{\"x\":5,\"y\":24,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":8,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":3,\"y\":17,\"z\":3}:[\"Tree\",\"East\"],{\"x\":1,\"y\":19,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":10,\"y\":7,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":21,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":7,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":22,\"z\":2}:[\"CastleCorner\",\"North\"],{\"x\":1,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":27,\"z\":1}:[\"GrassCorner\",\"East\"],{\"x\":20,\"y\":6,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":1,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":3,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":6,\"y\":0,\"z\":2}:[\"Tree\",\"South\"],{\"x\":14,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":21,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":25,\"z\":3}:[\"CastleTowerPurple\",\"West\"],{\"x\":3,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":18,\"z\":1}:[\"GrassWaterConcave\",\"South\"],{\"x\":2,\"y\":20,\"z\":2}:[\"GrassPathSplit\",\"West\"],{\"x\":24,\"y\":17,\"z\":2}:[\"Tree\",\"South\"],{\"x\":9,\"y\":17,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":5,\"y\":19,\"z\":1}:[\"GrassWaterConcave\",\"East\"],{\"x\":2,\"y\":0,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":19,\"y\":7,\"z\":1}:[\"GrassPathBend\",\"South\"],{\"x\":13,\"y\":3,\"z\":1}:[\"GrassPathBend\",\"South\"],{\"x\":23,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":18,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":2,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":15,\"y\":20,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":6,\"y\":5,\"z\":1}:[\"GrassPathSplit\",\"North\"],{\"x\":4,\"y\":21,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":10,\"y\":21,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":22,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":18,\"z\":2}:[\"GrassRiverBridge\",\"East\"],{\"x\":7,\"y\":23,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":10,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":2,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":14,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":12,\"y\":22,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":23,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":27,\"z\":2}:[\"Trees\",\"South\"],{\"x\":19,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":10,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":21,\"y\":7,\"z\":2}:[\"BuildingDoorBeige\",\"South\"],{\"x\":7,\"y\":4,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":11,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":10,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":26,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":6,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":22,\"y\":19,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":2,\"y\":8,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":20,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":11,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":1,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":17,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":20,\"z\":1}:[\"GrassRiverSplit\",\"North\"],{\"x\":11,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":4,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":22,\"y\":6,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":11,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":23,\"z\":2}:[\"Trees\",\"South\"],{\"x\":2,\"y\":16,\"z\":2}:[\"GrassPath\",\"South\"],{\"x\":10,\"y\":5,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":18,\"y\":26,\"z\":2}:[\"CastleCorner\",\"East\"],{\"x\":21,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":2,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":20,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":1,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":16,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":17,\"z\":1}:[\"GrassWaterConvex\",\"North\"],{\"x\":11,\"y\":20,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":19,\"y\":13,\"z\":1}:[\"GrassWaterRiver\",\"South\"],{\"x\":6,\"y\":19,\"z\":1}:[\"GrassWater\",\"East\"],{\"x\":1,\"y\":0,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":19,\"y\":9,\"z\":1}:[\"GrassWaterConcave\",\"West\"],{\"x\":13,\"y\":6,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":1,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":19,\"z\":2}:[\"GrassSlope\",\"East\"],{\"x\":17,\"y\":8,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":15,\"y\":8,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":1,\"y\":0,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":19,\"y\":16,\"z\":1}:[\"GrassRiverSplit\",\"South\"],{\"x\":19,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":8,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":10,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":15,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":21,\"y\":18,\"z\":2}:[\"CastleWall\",\"North\"],{\"x\":24,\"y\":27,\"z\":1}:[\"GrassCorner\",\"South\"],{\"x\":18,\"y\":24,\"z\":2}:[\"CastleGate\",\"East\"],{\"x\":20,\"y\":0,\"z\":2}:[\"Trees\",\"South\"],{\"x\":15,\"y\":2,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":1,\"y\":7,\"z\":2}:[\"Trees\",\"East\"],{\"x\":19,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":7,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":6,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":19,\"z\":3}:[\"CastleTowerPurple\",\"South\"],{\"x\":24,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":2,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":21,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":15,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":8,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":15,\"y\":9,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":2,\"y\":25,\"z\":2}:[\"StructureArch\",\"West\"],{\"x\":13,\"y\":17,\"z\":1}:[\"GrassPathBend\",\"North\"],{\"x\":13,\"y\":0,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":9,\"y\":14,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":13,\"y\":19,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":24,\"y\":18,\"z\":2}:[\"CastleCorner\",\"West\"],{\"x\":5,\"y\":15,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":24,\"y\":24,\"z\":2}:[\"CastelWindow\",\"West\"],{\"x\":3,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":4,\"y\":22,\"z\":2}:[\"GrassSlopeConvex\",\"North\"],{\"x\":3,\"y\":4,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":6,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":18,\"y\":14,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":10,\"y\":3,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":24,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":16,\"z\":1}:[\"GrassWater\",\"South\"],{\"x\":4,\"y\":18,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":24,\"y\":7,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":3,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":7,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":18,\"z\":1}:[\"GrassWaterConvex\",\"South\"],{\"x\":6,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":25,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":15,\"y\":10,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":17,\"y\":6,\"z\":1}:[\"GrassRiverBend\",\"West\"],{\"x\":8,\"y\":12,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":19,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":4,\"z\":2}:[\"BuildingDoorWindowsBeige\",\"North\"],{\"x\":23,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":18,\"z\":2}:[\"CastleCorner\",\"North\"],{\"x\":19,\"y\":14,\"z\":1}:[\"GrassRiverBridge\",\"South\"],{\"x\":15,\"y\":7,\"z\":1}:[\"GrassPathBend\",\"North\"],{\"x\":7,\"y\":10,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":15,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":1,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":20,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":0,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":12,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":13,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":23,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":10,\"z\":2}:[\"BuildingDoorWindowsBeige\",\"North\"],{\"x\":6,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":21,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":18,\"y\":6,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":16,\"z\":2}:[\"GrassSlopeConcave\",\"East\"],{\"x\":9,\"y\":21,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":12,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":27,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":20,\"y\":20,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":5,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":2,\"z\":1}:[\"GrassRiverBridge\",\"East\"],{\"x\":23,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":8,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":19,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":10,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":8,\"y\":15,\"z\":1}:[\"GrassWater\",\"West\"],{\"x\":21,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":19,\"z\":2}:[\"RocksGrass\",\"East\"],{\"x\":1,\"y\":17,\"z\":3}:[\"BuildingDoorWindows\",\"East\"],{\"x\":22,\"y\":24,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":15,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":21,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":22,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":22,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":4,\"y\":9,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":13,\"y\":24,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":18,\"y\":4,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":11,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":14,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":24,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":4,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":17,\"z\":2}:[\"GrassSlope\",\"East\"],{\"x\":16,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":16,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":21,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":20,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":4,\"y\":24,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":19,\"y\":15,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":3,\"y\":1,\"z\":2}:[\"CastleGate\",\"North\"],{\"x\":18,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":14,\"z\":2}:[\"Trees\",\"East\"],{\"x\":23,\"y\":26,\"z\":2}:[\"CastleWall\",\"South\"],{\"x\":2,\"y\":3,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":17,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":23,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":2,\"y\":5,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":24,\"y\":19,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":8,\"y\":23,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":21,\"y\":19,\"z\":2}:[\"BuildingDoor\",\"East\"],{\"x\":4,\"y\":4,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":3,\"y\":2,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":15,\"y\":11,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":23,\"y\":25,\"z\":2}:[\"BuildingWindows\",\"South\"],{\"x\":7,\"y\":5,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":18,\"y\":10,\"z\":1}:[\"WaterCenter\",\"West\"],{\"x\":2,\"y\":3,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":11,\"y\":21,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":4,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":15,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":21,\"y\":2,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":6,\"y\":1,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":2,\"y\":24,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":21,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":8,\"y\":16,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":17,\"y\":0,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":2,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":5,\"y\":27,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":1,\"y\":26,\"z\":2}:[\"Tree\",\"South\"],{\"x\":7,\"y\":8,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":6,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":18,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":9,\"y\":15,\"z\":1}:[\"GrassWaterRiver\",\"North\"],{\"x\":11,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":7,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":21,\"y\":12,\"z\":2}:[\"Trees\",\"South\"],{\"x\":11,\"y\":24,\"z\":2}:[\"StructureArch\",\"West\"],{\"x\":16,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":22,\"z\":2}:[\"GrassCenter\",\"West\"],{\"x\":6,\"y\":4,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":22,\"y\":17,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":24,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":15,\"z\":1}:[\"GrassPathBend\",\"South\"],{\"x\":23,\"y\":16,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":7,\"y\":17,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":6,\"y\":16,\"z\":1}:[\"GrassWater\",\"North\"],{\"x\":8,\"y\":4,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":15,\"y\":3,\"z\":1}:[\"GrassRiverBend\",\"North\"],{\"x\":3,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":4,\"y\":4,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":24,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":11,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":24,\"z\":1}:[\"GrassPathSplit\",\"North\"],{\"x\":17,\"y\":1,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":20,\"y\":19,\"z\":2}:[\"CastleWall\",\"West\"],{\"x\":7,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":2,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":19,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":12,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":2,\"y\":4,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":18,\"y\":11,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":24,\"y\":22,\"z\":2}:[\"CastelWindow\",\"West\"],{\"x\":24,\"y\":25,\"z\":2}:[\"CastleWall\",\"West\"],{\"x\":4,\"y\":16,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":7,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":24,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":21,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":4,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":9,\"y\":0,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":18,\"y\":16,\"z\":1}:[\"GrassRiverBend\",\"North\"],{\"x\":16,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":24,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":3,\"y\":4,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":5,\"y\":18,\"z\":1}:[\"GrassWaterRiver\",\"East\"],{\"x\":2,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":0,\"z\":1}:[\"GrassCorner\",\"West\"],{\"x\":6,\"y\":2,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":12,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":16,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":5,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":7,\"y\":21,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":16,\"y\":25,\"z\":2}:[\"Trees\",\"South\"],{\"x\":2,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":20,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":13,\"z\":1}:[\"GrassWaterConcave\",\"South\"],{\"x\":22,\"y\":26,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":4,\"y\":3,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":2,\"y\":11,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":18,\"z\":2}:[\"BuildingDoorBeige\",\"East\"],{\"x\":19,\"y\":20,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":14,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":10,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":17,\"y\":26,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":14,\"y\":17,\"z\":1}:[\"GrassPathBend\",\"South\"],{\"x\":3,\"y\":23,\"z\":2}:[\"GrassSlopeConvex\",\"North\"],{\"x\":18,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":16,\"y\":14,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":1,\"y\":25,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":18,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":24,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":5,\"y\":16,\"z\":2}:[\"RocksGrass\",\"East\"],{\"x\":14,\"y\":4,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":1,\"y\":8,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":1,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":2,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":17,\"y\":22,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":11,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":19,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":2,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":9,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":7,\"y\":18,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":4,\"y\":2,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":9,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":16,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":7,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":16,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":24,\"y\":20,\"z\":2}:[\"CastelWindow\",\"West\"],{\"x\":4,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":17,\"z\":2}:[\"GrassPathSplit\",\"West\"],{\"x\":3,\"y\":0,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":17,\"y\":24,\"z\":1}:[\"GrassRiverBridge\",\"South\"],{\"x\":1,\"y\":18,\"z\":2}:[\"GrassRiverEnd\",\"East\"],{\"x\":10,\"y\":17,\"z\":1}:[\"GrassWater\",\"South\"],{\"x\":24,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":15,\"z\":2}:[\"GrassCenter\",\"North\"],{\"x\":17,\"y\":14,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":5,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":2,\"y\":22,\"z\":2}:[\"GrassPath\",\"South\"],{\"x\":3,\"y\":13,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":1,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":25,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":14,\"y\":2,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":4,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":9,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":14,\"y\":19,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":17,\"y\":2,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":5,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":1,\"y\":21,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":14,\"z\":2}:[\"GrassSlopeConvex\",\"East\"],{\"x\":21,\"y\":16,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":4,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":1,\"y\":19,\"z\":3}:[\"RocksGrass\",\"East\"],{\"x\":8,\"y\":22,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":8,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":18,\"y\":12,\"z\":1}:[\"GrassWater\",\"North\"],{\"x\":19,\"y\":23,\"z\":2}:[\"BuildingDoorWindows\",\"North\"],{\"x\":20,\"y\":20,\"z\":2}:[\"CastelWindow\",\"West\"],{\"x\":10,\"y\":23,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":2,\"y\":27,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":11,\"y\":7,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":20,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":26,\"z\":2}:[\"CastelWindow\",\"South\"],{\"x\":4,\"y\":1,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":2,\"y\":23,\"z\":2}:[\"GrassPathSlope\",\"North\"],{\"x\":9,\"y\":3,\"z\":1}:[\"GrassPathSplit\",\"East\"],{\"x\":19,\"y\":24,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":23,\"y\":18,\"z\":2}:[\"CastleWall\",\"South\"],{\"x\":23,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":6,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":23,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":16,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":1,\"z\":1}:[\"GrassRiverBend\",\"South\"],{\"x\":7,\"y\":12,\"z\":1}:[\"GrassRiverBend\",\"East\"],{\"x\":4,\"y\":4,\"z\":2}:[\"Trees\",\"East\"],{\"x\":1,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":9,\"y\":23,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":6,\"y\":24,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":16,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":0,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":8,\"y\":19,\"z\":1}:[\"GrassWaterConcave\",\"South\"],{\"x\":18,\"y\":3,\"z\":1}:[\"GrassRiverBend\",\"South\"],{\"x\":17,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":6,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":13,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":13,\"y\":11,\"z\":1}:[\"GrassPathCrossing\",\"West\"],{\"x\":20,\"y\":21,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":8,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":1,\"y\":17,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":13,\"y\":8,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":13,\"y\":20,\"z\":1}:[\"GrassRiverBridge\",\"East\"],{\"x\":3,\"y\":3,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":17,\"y\":23,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":5,\"y\":9,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":14,\"y\":11,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":9,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":17,\"z\":1}:[\"GrassWaterConcave\",\"North\"],{\"x\":7,\"y\":22,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":15,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":16,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":4,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":20,\"z\":2}:[\"GrassSlope\",\"East\"],{\"x\":16,\"y\":11,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":12,\"y\":21,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":21,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":21,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":4,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":8,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":16,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":4,\"y\":14,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":10,\"y\":20,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":13,\"y\":2,\"z\":1}:[\"GrassPathBend\",\"North\"],{\"x\":23,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":7,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":17,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":8,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":12,\"y\":23,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":24,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":0,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":15,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":18,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":9,\"y\":22,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":24,\"y\":23,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":11,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":5,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":23,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":11,\"z\":1}:[\"GrassRiverBridge\",\"South\"],{\"x\":1,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":5,\"y\":8,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":7,\"y\":24,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":19,\"y\":10,\"z\":1}:[\"GrassWaterConvex\",\"West\"],{\"x\":9,\"y\":20,\"z\":1}:[\"GrassRiverBend\",\"East\"],{\"x\":22,\"y\":18,\"z\":2}:[\"CastleGate\",\"South\"],{\"x\":4,\"y\":18,\"z\":2}:[\"GrassRiverSlope\",\"East\"],{\"x\":10,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":5,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":12,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":6,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":14,\"y\":14,\"z\":1}:[\"GrassPathSplit\",\"East\"],{\"x\":16,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":21,\"z\":2}:[\"GrassCenter\",\"West\"],{\"x\":8,\"y\":5,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":9,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":16,\"y\":3,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":20,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":15,\"z\":2}:[\"GrassPathBend\",\"North\"],{\"x\":18,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":23,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":1,\"y\":23,\"z\":2}:[\"GrassSlope\",\"North\"],{\"x\":22,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":25,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":7,\"y\":0,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":2,\"y\":1,\"z\":2}:[\"CastelWindow\",\"South\"],{\"x\":2,\"y\":1,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":21,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":10,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":1,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":14,\"y\":0,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":1,\"y\":1,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":16,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":13,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":18,\"y\":2,\"z\":1}:[\"GrassRiverBend\",\"North\"],{\"x\":10,\"y\":15,\"z\":1}:[\"GrassWaterConcave\",\"West\"],{\"x\":14,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":12,\"y\":19,\"z\":2}:[\"Tree\",\"South\"],{\"x\":3,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":8,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":9,\"z\":1}:[\"GrassRiverBend\",\"East\"],{\"x\":2,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":11,\"y\":22,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":6,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":13,\"y\":1,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":10,\"y\":2,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":15,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":11,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":18,\"y\":19,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":19,\"y\":22,\"z\":2}:[\"CastleWall\",\"North\"],{\"x\":24,\"y\":21,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":11,\"y\":8,\"z\":2}:[\"Tree\",\"South\"],{\"x\":2,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":19,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":23,\"y\":12,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":12,\"y\":25,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":1,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":15,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":10,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":3,\"y\":5,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":7,\"y\":1,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":13,\"y\":23,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":23,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":16,\"z\":1}:[\"GrassRiverBridge\",\"East\"],{\"x\":3,\"y\":22,\"z\":2}:[\"GrassSlopeConcave\",\"North\"],{\"x\":6,\"y\":5,\"z\":2}:[\"StructureArch\",\"East\"],{\"x\":12,\"y\":20,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":22,\"y\":20,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":10,\"y\":22,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":4,\"y\":17,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":15,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":4,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":3,\"y\":3,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":7,\"y\":15,\"z\":1}:[\"GrassWater\",\"West\"],{\"x\":5,\"y\":0,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":20,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":24,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":19,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":16,\"y\":12,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":9,\"y\":12,\"z\":1}:[\"GrassRiverBend\",\"West\"],{\"x\":1,\"y\":15,\"z\":3}:[\"Tree\",\"East\"],{\"x\":21,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":7,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":9,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":12,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":7,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":11,\"z\":1}:[\"GrassPathBend\",\"North\"],{\"x\":6,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":24,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":16,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":1,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":15,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":21,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":6,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":3,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":7,\"z\":1}:[\"GrassRiverBridge\",\"South\"],{\"x\":5,\"y\":1,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":3,\"y\":1,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":21,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":3,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":1,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":13,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":20,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":21,\"z\":2}:[\"GrassPath\",\"East\"],{\"x\":14,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":20,\"z\":2}:[\"BuildingDoorWindowsBeige\",\"North\"],{\"x\":21,\"y\":7,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":15,\"z\":1}:[\"GrassWaterConcave\",\"North\"],{\"x\":5,\"y\":3,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":6,\"y\":18,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":3,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":11,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":4,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":4,\"y\":3,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":2,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":24,\"y\":26,\"z\":2}:[\"CastleCorner\",\"South\"],{\"x\":8,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":4,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":21,\"y\":11,\"z\":1}:[\"GrassWaterConcave\",\"South\"],{\"x\":2,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":22,\"z\":2}:[\"CastleCorner\",\"South\"],{\"x\":22,\"y\":26,\"z\":2}:[\"CastleGate\",\"South\"],{\"x\":17,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":12,\"z\":2}:[\"Trees\",\"East\"],{\"x\":5,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":7,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":7,\"y\":25,\"z\":2}:[\"Tree\",\"South\"],{\"x\":7,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":11,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":16,\"y\":2,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":23,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":11,\"z\":1}:[\"GrassWaterConcave\",\"East\"],{\"x\":19,\"y\":26,\"z\":2}:[\"CastleWall\",\"South\"],{\"x\":8,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":3,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":20,\"y\":1,\"z\":1}:[\"GrassRiverBend\",\"North\"],{\"x\":14,\"y\":20,\"z\":1}:[\"GrassRiver\",\"West\"],{\"x\":2,\"y\":9,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":2,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":20,\"y\":5,\"z\":2}:[\"Tree\",\"South\"],{\"x\":21,\"y\":10,\"z\":1}:[\"GrassWaterConcave\",\"West\"],{\"x\":9,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":19,\"y\":6,\"z\":1}:[\"GrassPathSplit\",\"East\"],{\"x\":24,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":4,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":9,\"y\":19,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":24,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":13,\"z\":2}:[\"BuildingDoorBeige\",\"North\"],{\"x\":19,\"y\":5,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":8,\"y\":17,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":12,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":21,\"z\":2}:[\"GrassPathSlope\",\"East\"],{\"x\":4,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":7,\"y\":2,\"z\":1}:[\"GrassRiverBend\",\"South\"],{\"x\":16,\"y\":9,\"z\":1}:[\"GrassWaterConcave\",\"North\"],{\"x\":22,\"y\":27,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":2,\"y\":21,\"z\":2}:[\"GrassPathSplit\",\"East\"],{\"x\":6,\"y\":10,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":7,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":21,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":18,\"y\":9,\"z\":1}:[\"GrassWater\",\"West\"],{\"x\":22,\"y\":15,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":19,\"y\":27,\"z\":2}:[\"Tree\",\"South\"],{\"x\":20,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":1,\"z\":2}:[\"BuildingDoorBeige\",\"North\"],{\"x\":1,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":19,\"z\":2}:[\"GrassPath\",\"South\"],{\"x\":19,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":3,\"z\":1}:[\"GrassRiverBridge\",\"West\"],{\"x\":18,\"y\":18,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":2,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":10,\"z\":2}:[\"Trees\",\"East\"],{\"x\":6,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":7,\"y\":2,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":20,\"y\":10,\"z\":1}:[\"GrassWater\",\"West\"],{\"x\":3,\"y\":8,\"z\":1}:[\"GrassRiverBend\",\"West\"],{\"x\":22,\"y\":18,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":11,\"y\":24,\"z\":1}:[\"GrassPathSplit\",\"North\"],{\"x\":2,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":13,\"y\":12,\"z\":1}:[\"GrassPath\",\"South\"]},\"cam\":{\"center\":{\"x\":-33.666626,\"y\":-366.1667},\"scale\":0.29999992},\"show_inv\":false,\"inv\":{\"infinite_items\":true,\"contents\":{\"StructureHigh\":1,\"StructureArch\":1,\"RoofPointPurple\":1,\"GrassWaterConvex\":1,\"RoofRoundGreen\":1,\"RocksDirt\":1,\"CastleTowerBrown\":1,\"GrassCorner\":1,\"GrassRiver\":1,\"RoofPointGreen\":1,\"RoofRoundedGreen\":1,\"GrassWaterRiver\":1,\"RoofPointBrown\":1,\"RoofChurchPurple\":1,\"Trees\":1,\"BuildingWindowsBeige\":1,\"GrassRiverBridge\":1,\"GrassRiverCrossing\":1,\"Bridge\":1,\"GrassPath\":1,\"BuildingWindows\":1,\"WaterFall\":1,\"GrassPathEnd\":1,\"BuildingCenter\":1,\"WaterCenter\":1,\"BuildingDoorWindows\":1,\"RoofGableBrown\":1,\"GrassPathSlope\":1,\"RoofGableGreen\":1,\"GrassRiverEndSquare\":1,\"BuildingDoor\":1,\"RoofGablePurple\":1,\"CastleTowerPurple\":1,\"GrassRiverCorner\":1,\"GrassCenter\":1,\"GrassRiverSlope\":1,\"BuildingCorner\":1,\"GrassPathCorner\":1,\"GrassWater\":1,\"StructureLow\":1,\"RoofChuchBrown\":1,\"BuildingWindow\":1,\"RoofRoundBeige\":1,\"RoofPointBeige\":1,\"RoofSlantGreen\":1,\"RoofRoundedBeige\":1,\"GrassSlope\":1,\"CastleTower\":1,\"CastleTowerBeige\":1,\"GrassPathSplit\":1,\"CastelWindow\":1,\"RoofChurchGreen\":1,\"CastleBend\":1,\"RocksGrass\":1,\"CastleGateOpen\":1,\"RoofRoundBrown\":1,\"Tree\":1,\"GrassPathBend\":1,\"GrassRiverSplit\":1,\"CastleCenter\":1,\"CastleCorner\":1,\"CastleGate\":1,\"BuildingCenterBeige\":1,\"GrassWaterConcave\":1,\"RoofSlantBrown\":1,\"BuildingDoorWindowsBeige\":1,\"RoofSlantPurple\":1,\"GrassSlopeConvex\":1,\"DirtLow\":1,\"RoofRoundedPurple\":1,\"RoofSlantBeige\":1,\"GrassRiverBend\":1,\"RoofRoundPurple\":1,\"GrassRiverEnd\":1,\"RoofRoundedBrown\":1,\"GrassPathCrossing\":1,\"CastleWall\":1,\"BuildingDoorBeige\":1,\"CastleSlope\":1,\"BuildingCornerBeige\":1,\"RoofChurchBeige\":1,\"CastleTowerGreen\":1,\"RoofGableBeige\":1,\"DirtCenter\":1,\"BuildingWindowBeige\":1,\"GrassSlopeConcave\":1,\"GrassPathEndSquare\":1},\"selected\":\"RocksGrass\",\"direction\":\"South\",\"page\":7}}"}} \ No newline at end of file
R src/blocks.rs => src/game/blocks.rs +152 -83
@@ 2,9 2,83 @@ use macroquad::prelude::*; use crate::textures::DirectionalTexture; use nanoserde::{DeJson, SerJson}; use std::collections::HashMap; use crate::screens::build::{ TEXTURE_WIDTH, TEXTURE_Y_WHITESPACE, TEXTURE_DEPTH, TEXTURE_INNER_HEIGHT }; static mut CACHE: Option<HashMap<Block, DirectionalTexture>> = None; /// A block's face /// /'\ /// / A \ /// |\ /| /// | \./ | /// \ B|C | /// \.|./ /// A - Top /// B- Left /// C -Right #[derive(Debug)] pub enum Face { Top, Left, Right } impl Face { /// gets the face (if any) that was clicked /// coordinates have to be normalized, /// the top left should be (0,0) pub fn from_xy(x: f32, y: f32, scale: f32) -> Option<Self> { // test A face { let ox = (TEXTURE_WIDTH * scale / 2.0 - x).abs(); let oy = (TEXTURE_DEPTH * scale / 2.0 + TEXTURE_Y_WHITESPACE * scale - y).abs(); let y_max = TEXTURE_DEPTH * scale / 2.0; let x_max = TEXTURE_WIDTH * scale / 2.0; let grow = (0.0 - y_max)/(x_max - 0.0); let cy = grow * ox + y_max; if cy >= oy { return Some(Face::Top); } } // test B and C faces { let ox = TEXTURE_WIDTH * scale / 2.0 - x; let oy = y - (TEXTURE_DEPTH * scale / 2.0 + TEXTURE_Y_WHITESPACE * scale); let ox_abs = ox.abs(); let y_max = TEXTURE_DEPTH * scale / 2.0; let x_max = TEXTURE_WIDTH * scale / 2.0; let grow = (0.0 - y_max)/(x_max - 0.0); let y_top = grow * ox_abs + y_max; let y_bottom = grow * ox_abs + y_max + TEXTURE_INNER_HEIGHT * scale; if oy >= y_top && oy <= y_bottom { if x > TEXTURE_WIDTH * scale / 2.0 { // C face return Some(Face::Right); } else { // B face return Some(Face::Left); } } } None } } #[derive(Eq, Hash, Clone, PartialEq, SerJson, DeJson)] pub enum Block { @@ Bridge, 111,9 185,9 @@ impl Block { pub async fn get_texture(&self) -> DirectionalTexture { unsafe { if CACHE.is_none() { CACHE = Some(HashMap::new()); } if CACHE.is_none() { CACHE = Some(HashMap::new()); } if let Some(map) = &CACHE { if let Some(dt) = map.get(self) { @@ return dt.clone(); 319,92 393,87 @@ impl Block { Block::WaterFall ] } } impl Default for Block { fn default() -> Self { Block::StructureHigh } } /// Blocks can belong to multiple categories /// and may be displayed by category in inventory #[derive(Clone, PartialEq, Eq, DeJson, SerJson)] pub enum Category { /// Everything releated to buildings /// i.e. walls, levels, roofs Building, /// floor tiling /// i.e grass, dirt Blocks, /// category containing roofs for buildings Roofs, /// Trees & Bushes Plants, /// River, Lake and water Water, /// ungrouped blocks /// i.e bridges Other, /// all items All } impl Category { /// get a list of all categories pub fn all() -> Vec<Category> { vec![ Category::All, Category::Building, Category::Blocks, Category::Roofs, Category::Plants, Category::Water, Category::Other, ] } /// get name of category pub fn get_name(&self) -> &str { match self { Block::Bridge => "Bridge", Block::BuildingDoor | Block::BuildingDoorBeige => "Building door", Block::BuildingDoorWindows | Block::BuildingDoorWindowsBeige => "Building door", Block::BuildingCenter | Block::BuildingCenterBeige => "Building", Block::BuildingCorner | Block::BuildingCornerBeige => "Building corner", Block::BuildingWindow | Block::BuildingWindowBeige => "Building window", Block::BuildingWindows | Block::BuildingWindowsBeige => "Building windows", Block::CastleBend => "Castle bend", Block::CastleCenter | Block::CastleCorner => "Castle", Block::CastleGate => "Castle Gate", Block::CastleGateOpen => "Open castle gate", Block::CastleSlope => "Castle slope", Block::CastleTower | Block::CastleTowerBeige | Block::CastleTowerBrown | Block::CastleTowerGreen | Block::CastleTowerPurple => "Castle tower", Block::CastleWall => "Castle wall", Block::CastelWindow => "Castle window", Block::DirtCenter | Block::DirtLow => "Dirt", Block::GrassCenter | Block::GrassCorner => "Grass", Block::GrassPath | Block::GrassPathBend | Block::GrassPathCorner | Block::GrassPathCrossing | Block::GrassPathEnd | Block::GrassPathSplit | Block::GrassPathEndSquare => "Grass path", Block::GrassPathSlope => "Grass slope", Block::GrassRiver | Block::GrassRiverBend | Block::GrassRiverBridge | Block::GrassRiverCorner | Block::GrassRiverCrossing | Block::GrassRiverEnd | Block::GrassRiverEndSquare | Block::GrassRiverSplit | Block::GrassRiverSlope => "River", Block::GrassSlope | Block::GrassSlopeConcave | Block::GrassSlopeConvex => "Grass slope", Block::GrassWater | Block::GrassWaterConcave | Block::GrassWaterConvex | Block::GrassWaterRiver => "Grass water", Block::RocksDirt | Block::RocksGrass => "Rocks", Block::RoofChurchBeige | Block::RoofChuchBrown | Block::RoofChurchGreen | Block::RoofChurchPurple => "Church roof", Block::RoofGableBeige | Block::RoofGableBrown | Block::RoofGableGreen | Block::RoofGablePurple => "Gable roof", Block::RoofPointBeige | Block::RoofPointBrown | Block::RoofPointGreen | Block::RoofPointPurple => "Point roof", Block::RoofRoundBeige | Block::RoofRoundBrown | Block::RoofRoundGreen | Block::RoofRoundPurple => "Round roof", Block::RoofRoundedBeige | Block::RoofRoundedBrown | Block::RoofRoundedGreen | Block::RoofRoundedPurple => "Rounded roof", Block::RoofSlantBeige | Block::RoofSlantBrown | Block::RoofSlantGreen | Block::RoofSlantPurple => "Slant roof", Block::StructureArch | Block::StructureHigh | Block::StructureLow => "Scaffold", Block::Trees => "Trees", Block::Tree => "Tree", Block::WaterCenter => "Water", Block::WaterFall => "Waterfall" Category::Building => "Buildings", Category::Blocks => "Blocks", Category::Roofs => "Roofs", Category::Plants => "Plants", Category::Water => "Water & River", Category::Other => "Other", Category::All => "All" } } /// get a list of all blocks from this category pub fn get_blocks(&self) -> Vec<Block> { match self { Category::Building => vec![ ], Category::Blocks => vec![ ], Category::Roofs => vec![ ], Category::Plants => vec![ ], Category::Water => vec![ ], Category::Other => vec![ ], Category::All => Block::all() } } } impl Default for Block { impl Default for Category { fn default() -> Self { Block::StructureHigh Category::All } }
A src/game/camera.rs => src/game/camera.rs +19 -0
@@ 0,0 1,19 @@ use nanoserde::{DeJson, SerJson}; /// the game camera #[derive(SerJson, DeJson, Clone)] pub struct Camera { /// the centered pixel pub center: (f32, f32), /// scales the texture pub scale: f32 } impl Camera { /// initialize new camera pub fn new() -> Self { Self { center: (0.0, 0.0), scale: 1.0 } } }
A src/game/inventory.rs => src/game/inventory.rs +43 -0
@@ 0,0 1,43 @@ use crate::game::types::Direction; use crate::game::blocks::{ Block, Category }; use std::collections::HashMap; use nanoserde::{DeJson, SerJson}; /// The players inventory #[derive(SerJson, DeJson, Clone)] pub struct Inventory { /// items in inventory /// with their appropriate amount /// if amount is zero, the item should be removed /// if infinite_items is true, the amount wont decrease pub contents: HashMap<Block, usize>, /// which block is currently selected pub selected: Option<Block>, /// in which direction the block should be facing pub direction: Direction, /// category page currently being shown pub category: Category } impl Inventory { /// creates an empty inventory pub fn new() -> Self { Self { contents: HashMap::new(), selected: None, direction: Direction::North, category: Category::All } } /// adds an item to the inventory pub fn add_item(&mut self, block: &Block) { if let Some(slot) = self.contents.get_mut(&block) { *slot += 1; } else { self.contents.insert(block.clone(), 1); } } }
A src/game/mod.rs => src/game/mod.rs +35 -0
@@ 0,0 1,35 @@ pub mod camera; pub mod blocks; pub mod inventory; pub mod types; pub mod world; use crate::textures::AssetStore; use crate::screens::Screen; /// A basic Game component /// which can be drawn /// and has a handler for the event loop /// NOTE: draw and ev_loop are tehcnically the same, /// but the serve different purposes pub trait GameComponent { /// Callback used to redraw the component async fn draw(&self, _assets: &AssetStore) {} /// Callback used to perform background tasks /// e.g. keyboard input /// NOTE: this is currently not being run asynchronously /// when running large operations, spawn a separate thread fn ev_loop(&mut self) -> GameEvent { GameEvent::None } } /// Events send from the event loop pub enum GameEvent { /// Do nothing None, /// Exit game Quit, /// Change View ChangeScreen(Screen) }
R src/types.rs => src/game/types.rs +0 -29
@@ 1,5 1,3 @@ use crate::textures::AssetStore; use crate::Screen; use nanoserde::{DeJson, SerJson}; @@ /// three dimensional coordinate 41,30 39,3 @@ pub enum Direction { /// (bottom edge of screen) South } /// A basic Game component /// which can be drawn /// and has a handler for the event loop /// NOTE: draw and ev_loop are tehcnically the same, /// but the serve different purposes pub trait GameComponent { /// Callback used to redraw the component async fn draw(&self, assets: &AssetStore) {} /// Callback used to perform background tasks /// e.g. keyboard input /// NOTE: this is currently not being run asynchronously /// when running large operations, spawn a separate thread fn ev_loop(&mut self) -> GameEvent { GameEvent::None } } /// Events send from the event loop pub enum GameEvent { /// Do nothing None, /// Exit game Quit, /// Change View ChangeScreen(Screen) }
R src/storage.rs => src/game/world.rs +125 -51
@@ 1,3 1,12 @@ use super::inventory::Inventory; use super::camera::Camera; use super::types::{ Direction, Pos3 }; use super::blocks::Block; use std::collections::HashMap; use nanoserde::{SerJson, DeJson}; use directories_next::ProjectDirs; use std::path::Path; @@ use std::fs::{ 5,13 14,12 @@ use std::fs::{ OpenOptions, read_dir }; use crate::screens::build::BuildScreen; use std::io::{ BufReader, Read, Write }; use nanoserde::{DeJson, SerJson}; /// returns the OS specific data dir as a string @@ /// if there is no such dir, None will be returned 25,70 33,136 @@ fn get_data_dir() -> Option<String> { None } /// returns a list of all world file names pub fn get_world_list() -> Vec<String> { if let Some(dir) = get_data_dir() { if let Ok(rd) = &mut read_dir(dir) { let mut builder = Vec::new(); while let Some(entry) = rd.next() { if let Ok(entry) = entry { if let Ok(ftype) = entry.file_type() && ftype.is_file() { if let Some(name) = entry.file_name().to_str() { builder.push(name.to_string()); #[derive(SerJson, DeJson, Clone)] pub struct World { /// the map itself pub grid: HashMap<Pos3, (Block, Direction)>, /// camera position pub cam: Camera, /// the players inventory pub inventory: Inventory, /// when set to true, the player has an infinite amount of items pub sandbox: bool, } impl World { pub fn new(sandbox: bool) -> Self { let mut inv = Inventory::new(); if sandbox { for block in Block::all().iter() { inv.add_item(block); } } let mut map = HashMap::new(); map.insert(Pos3::new(0, 0, 0), (Block::default(), Direction::North)); Self { grid: map, cam: Camera::new(), inventory: inv, sandbox } } /// remove block from map /// and add item to inventory pub fn destroy_block(&mut self, pos: &Pos3) -> Option<(Block, Direction)> { if let Some(block) = self.grid.remove(pos) { self.inventory.add_item(&block.0); return Some(block.clone()); } None } /// removes the selected block (one) from the inventory /// can be used to remove items /// or place items pub fn place_block(&mut self, pos: &Pos3, block: Block, dir: Direction) -> bool { if let Some(count) = self.inventory.contents.get_mut(&block) { // player has block if *count > 0 { if !self.sandbox { *count -= 1; } self.grid.insert(pos.clone(), (block, dir)); return true; } } false } /// returns a list of all world file names pub fn get_list() -> Vec<String> { if let Some(dir) = get_data_dir() { if let Ok(rd) = &mut read_dir(dir) { let mut builder = Vec::new(); while let Some(entry) = rd.next() { if let Ok(entry) = entry { if let Ok(ftype) = entry.file_type() && ftype.is_file() { if let Some(name) = entry.file_name().to_str() { builder.push(name.to_string()); } } } } } return builder; return builder; } } Vec::new() } Vec::new() } /// tries loading a game state from a file pub fn read_map(name: &str) -> Option<BuildScreen> { let existing_maps = get_world_list(); if let Some(dir) = get_data_dir() && existing_maps.contains(&name.to_string()) { if let Ok(file) = File::open(Path::new(&dir).join(name)) { let mut buf_reader = BufReader::new(file); let mut contents = String::new(); if buf_reader.read_to_string(&mut contents).is_ok() { match BuildScreen::deserialize_json(&contents) { Ok(bscr) => return Some(bscr), Err(e) => println!("{:?}", e) /// tries loading the world from a file pub fn from_disk(name: &str) -> Option<Self> { let existing_maps = Self::get_list(); if let Some(dir) = get_data_dir() && existing_maps.contains(&name.to_string()) { if let Ok(file) = File::open(Path::new(&dir).join(name)) { let mut buf_reader = BufReader::new(file); let mut contents = String::new(); if buf_reader.read_to_string(&mut contents).is_ok() { match World::deserialize_json(&contents) { Ok(bscr) => return Some(bscr), Err(e) => println!("{:?}", e) } } else { println!("Faield to read file"); } } else { println!("Faield to read file"); println!("Failed to open file"); } } else { println!("Failed to open file"); println!("World not found"); } } else { println!("World not found"); None } None } /// tries saving the map to disk pub fn save_map(name: &str, bscr: &BuildScreen) { if let Some(dir) = get_data_dir() { let json = BuildScreen::serialize_json(&bscr); let mut file = OpenOptions::new() .write(true) .create(true) .truncate(true) .open(Path::new(&dir).join(name)); if let Ok(file) = &mut file { if file.write_all(json.as_bytes()).is_err() { println!("Failed to write file"); }; } else { println!("Failed to open file") /// tries saving the map to disk pub fn to_disk(&self, name: &str) { if let Some(dir) = get_data_dir() { let json = World::serialize_json(self); let mut file = OpenOptions::new() .write(true) .create(true) .truncate(true) .open(Path::new(&dir).join(name)); if let Ok(file) = &mut file { if file.write_all(json.as_bytes()).is_err() { println!("Failed to write file"); }; } else { println!("Failed to open file") } } } }
M src/main.rs => src/main.rs +4 -7
@@ 2,29 2,26 @@ #![feature(let_chains)] #![feature(async_fn_in_trait)] #![feature(associated_type_defaults)] #![feature(inherent_associated_types)] use macroquad::prelude::*; mod screens; #[macro_use] mod textures; mod types; mod blocks; mod draw; mod storage; mod ui; mod game; #[cfg(feature = "multiplayer")] mod p2p; use types::{ use game::{ GameComponent, GameEvent }; use textures::AssetStore; use screens::Screen; #[macroquad::main("Little Town")] async fn main() { let assets = AssetStore::init().await;
M src/p2p.rs => src/p2p.rs +11 -20
@@ 21,7 21,6 @@ use libp2p::{ Transport }; use std::{ collections::HashMap, sync::{ Arc, @@ RwLock, 36,8 35,9 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::time::Duration; use nanoserde::{SerJson, DeJson}; use crate::blocks::Block; use crate::types::{ use crate::game::blocks::Block; use crate::game::world::World; use crate::game::types::{ Pos3, Direction @@ }; 68,20 68,10 @@ pub enum GameAction { /// event send when a block has been removed /// contains block and position RemoveBlock(Pos3, Block), /// event send when a peer joins the map /// contains the inventory /// including blocks and their count /// the second argument is true /// if the user has an infinite amount of items TransmitInventory(HashMap<Block, usize>, bool), /// one of the peers requested an inventory transmission RequestInventory, /// event send when a peer joins the map /// contains the map /// including blocks, their direction and position TransmitMap(HashMap<Pos3, (Block, Direction)>), /// one of the peers requested a map transmission RequestMap, /// broadcast the current world state to all peers TransmitWorld(World), /// a peer can request a world transmission RequestWorld, } @@ enum ChannelEvent { 137,6 127,7 @@ impl Peer { /// adds a specific port to the setup /// has to be called before connect() is being called #[allow(dead_code)] pub fn with_port(mut self, port: usize) -> Self { self.port = Some(port); @@ self 283,12 274,12 @@ impl Peer { // hand on act to game instance let may_process = if is_host { match act { GameAction::TransmitMap(_) | GameAction::TransmitInventory(_, _) => false, GameAction::TransmitWorld(_) => false, _ => true } } else { match act { GameAction::RequestMap | GameAction::RequestInventory => false, GameAction::RequestWorld => false, _ => true } @@ }; 308,7 299,7 @@ impl Peer { if !is_host { // request data // in case host lost the request for act in [ GameAction::RequestMap, GameAction::RequestInventory ] { for act in [ GameAction::RequestWorld ] { let json = act.serialize_json(); swarm .behaviour_mut()
M src/screens/build.rs => src/screens/build.rs +461 -456
@@ 1,19 1,23 @@ use macroquad::prelude::*; use std::collections::HashMap; use crate::textures::AssetStore; use crate::types::{ use crate::game::{ GameComponent, GameEvent, Pos3, Direction }; use crate::blocks::Block; use super::{ inventory::Inventory, Screen, blocks::{Face, Block, Category}, world::World, types::{ Pos3, Direction }}; use crate::ui::{ ItemFrame, ButtonEvent, Widget, CenteredText, TextButton }; use crate::storage::save_map; use nanoserde::{DeJson, SerJson}; use super::Screen; #[cfg(feature = "multiplayer")] @@ use crate::p2p::{ 21,115 25,23 @@ use crate::p2p::{ GameAction }; /// simple Vec2 implementation /// because I need to derive Serialize and Deserialize #[derive(Copy, Clone, SerJson, DeJson)] struct CVec2 { x: f32, y: f32 } impl CVec2 { fn new(x: f32, y: f32) -> Self { Self { x, y } } } /// the game camera #[derive(SerJson, DeJson)] struct Camera { /// the centered pixel center: CVec2, /// scales the texture scale: f32 } impl Camera { /// initialize new camera fn new() -> Self { Self { center: CVec2::new(0.0, 0.0), scale: 1.0 } } } /// image width pub const TEXTURE_WIDTH: f32 = 256.0; /// actual width inside ob cube const TEXTURE_INNER_WIDTH: f32 = 223.0; // 216 pub const TEXTURE_INNER_WIDTH: f32 = 223.0; // 216 /// height of image pub const TEXTURE_HEIGHT: f32 = 352.0; /// empty space above texture pub const TEXTURE_Y_WHITESPACE: f32 = 130.0; /// actual height of cube const TEXTURE_INNER_HEIGHT: f32 = 108.0; // 110 pub const TEXTURE_INNER_HEIGHT: f32 = 108.0; // 110 /// height of top surface const TEXTURE_DEPTH: f32 = 100.0; /// The screen of the basic build game #[derive(SerJson, DeJson)] pub struct BuildScreen { grid: HashMap<Pos3, (Block, Direction)>, cam: Camera, show_inv: bool, inv: Inventory, mouse_position: Option<(f32, f32)>, #[cfg(feature="multiplayer")] #[nserde(skip)] multiplayer: Option<Peer>, #[nserde(skip)] file_name: Option<String> } impl BuildScreen { pub fn new(file_name: &str) -> Self { let mut this = Self { grid: HashMap::new(), cam: Camera::new(), show_inv: false, inv: Inventory::new(), mouse_position: None, file_name: Some(file_name.to_string()), #[cfg(feature="multiplayer")] multiplayer: None }; this.grid.insert(Pos3::new(0, 0, 0), (Block::default(), Direction::North)); pub const TEXTURE_DEPTH: f32 = 100.0; this } /// creates a new BuildScreen instance /// and starts the p2p multiplayer listener #[cfg(feature="multiplayer")] pub fn multiplayer() -> Self { Self { grid: HashMap::new(), cam: Camera::new(), show_inv: false, inv: Inventory::empty(), mouse_position: None, file_name: None, multiplayer: Some(Peer::client()) } } pub fn with_file_name(self, file_name: &str) -> Self { Self { file_name: Some(file_name.to_string()), ..self } } #[cfg(feature="multiplayer")] pub fn as_host(self) -> Self { Self { multiplayer: Some(Peer::host()), ..self } } } fn get_screen_coords(pos: &Pos3, scale: f32, center: CVec2) -> (f32, f32) { /// converts the 3 axis position of a block /// to a x-y screen coordinate fn get_screen_coords(pos: &Pos3, scale: f32, center: (f32, f32)) -> (f32, f32) { let w_i = TEXTURE_INNER_WIDTH * scale; let width = TEXTURE_WIDTH * scale; @@ let height = TEXTURE_HEIGHT * scale; 140,430 52,523 @@ fn get_screen_coords(pos: &Pos3, scale: f32, center: CVec2) -> (f32, f32) { let x = screen_width() / 2.0 + dx as f32 * w_i / 2.0 + center.x + center.0 - width / 2.0; let y = screen_height() / 2.0 + dy as f32 * h_i / 2.0 - pos.z as f32 * h_i + center.y + center.1 - height / 2.0; return (x,y); } /// A block's face /// /'\ /// / A \ /// |\ /| /// | \./ | /// \ B|C | /// \.|./ /// A - Top /// B- Left /// C -Right #[derive(Debug)] enum Face { Top, Left, Right struct InventoryWidgets { widgets_slots: Vec<ItemFrame>, widget_back: TextButton, widget_categories: TextButton, widget_page_indicator: CenteredText, /// current page /// set to zero when closing inventory /// the page count depends on the amount of items /// and on how many slots to draw per page /// see SLOTS_PER_PAGE for more page: usize, pub shown: bool } impl Face { /// gets the face (if any) that was clicked /// coordinates have to be normalized, /// the top left should be (0,0) fn from_xy(x: f32, y: f32, scale: f32) -> Option<Self> { // test A face { let ox = (TEXTURE_WIDTH * scale / 2.0 - x).abs(); let oy = (TEXTURE_DEPTH * scale / 2.0 + TEXTURE_Y_WHITESPACE * scale - y).abs(); let y_max = TEXTURE_DEPTH * scale / 2.0; let x_max = TEXTURE_WIDTH * scale / 2.0; let grow = (0.0 - y_max)/(x_max - 0.0); let cy = grow * ox + y_max; if cy >= oy { return Some(Face::Top); } impl InventoryWidgets { fn new() -> Self { let size = 0.14; Self { shown: false, widgets_slots: vec![ ItemFrame::new(0.35, 0.35, size), ItemFrame::new(0.5, 0.35, size), ItemFrame::new(0.65, 0.35, size), ItemFrame::new(0.35, 0.5, size), ItemFrame::new(0.5, 0.5, size), ItemFrame::new(0.65, 0.5, size), ItemFrame::new(0.35, 0.65, size), ItemFrame::new(0.5, 0.65, size), ItemFrame::new(0.65, 0.65, size), ], widget_back: TextButton::new("Back", 0.2, 0.2), widget_categories: TextButton::new("All", 0.8, 0.2), widget_page_indicator: CenteredText::new("0", 0.5, 0.8), page: 0 } // test B and C faces { let ox = TEXTURE_WIDTH * scale / 2.0 - x; let oy = y - (TEXTURE_DEPTH * scale / 2.0 + TEXTURE_Y_WHITESPACE * scale); let ox_abs = ox.abs(); let y_max = TEXTURE_DEPTH * scale / 2.0; let x_max = TEXTURE_WIDTH * scale / 2.0; let grow = (0.0 - y_max)/(x_max - 0.0); let y_top = grow * ox_abs + y_max; let y_bottom = grow * ox_abs + y_max + TEXTURE_INNER_HEIGHT * scale; if oy >= y_top && oy <= y_bottom { if x > TEXTURE_WIDTH * scale / 2.0 { // C face return Some(Face::Right); } else { // B face return Some(Face::Left); } } /// loads the first page for the last used category fn load_current_category(&mut self, inv: &mut Inventory) { self.load_category(inv.category.clone(), inv); } /// loads a new category fn load_category(&mut self, cat: Category, inv: &mut Inventory) { inv.category = cat; let title = inv.category.get_name(); self.widget_categories.set_text(title); let list = inv.category.get_blocks(); let page_count = list.len().div_ceil(9); self.widget_page_indicator.set_text(&format!("{}/{}", self.page+1, page_count)); self.widget_categories.set_text(&format!("{}", title)); self.load_page(0, inv); } /// loads the entries of the given page in the current category fn load_page(&mut self, page: usize, inv: &mut Inventory) { self.page = page; let list = inv.category.get_blocks(); let max_page = list.len().div_ceil(9); for (i, slot) in self.widgets_slots.iter_mut().enumerate() { if let Some(block) = list.get(i + page * 9) { let amount = if let Some(a) = inv.contents.get(block) { Some(*a) } else { None }; slot.update(&Some(block.clone()), &inv.direction, &amount); } else { slot.empty(); } } None self.widget_page_indicator.set_text(&format!("{}/{}", self.page+1, max_page)); } /// go one page down fn page_down(&mut self, inv: &mut Inventory) { let list = inv.category.get_blocks(); let mut page = self.page; if page <= 0 { page = list.len().div_ceil(9)-1; } else { page -= 1; } self.load_page(page, inv); } /// go one page up fn page_up(&mut self, inv: &mut Inventory) { let list = inv.category.get_blocks(); let mut page = self.page; let max_page = list.len().div_ceil(9); if page + 1 >= max_page { page = 0; } else { page += 1; } self.load_page(page, inv); } } /// The screen of the basic build game pub struct BuildScreen { /// the underlying world pub world: Option<World>, /// the world file name file_name: Option<String>, /// where the mouse is currently at mouse_position: Option<(f32, f32)>, /// the p2p backend used for multiplayer mode #[cfg(feature="multiplayer")] multiplayer: Option<Peer>, /// indicator widget showing the currently selected item and its orientation item_indicator_widget: ItemFrame, /// show the inventory popup inventory_widgets: InventoryWidgets } impl BuildScreen { /// attempts to load a world from disk pub fn from_disk(file_name: &str) -> Option<Self> { if let Some(world) = World::from_disk(file_name) { Some(Self { world: Some(world), file_name: Some(file_name.to_string()), ..Self::empty() }) } else { None } } /// create a new empty world pub fn new(file_name: &str, sandbox: bool) -> Self { Self { world: Some(World::new(sandbox)), file_name: Some(file_name.to_string()), ..Self::empty() } } /// Empty build screen /// can be used as a template for other initializers fn empty() -> Self { Self { world: None, mouse_position: None, file_name: None, #[cfg(feature="multiplayer")] multiplayer: None, item_indicator_widget: ItemFrame::new(0.95, 0.05, 0.05), inventory_widgets: InventoryWidgets::new() } } /// Creates an empty build screen /// and initializes the p2p backend /// when the World transmission is received, /// the build screen will be upgraded /// to a room with map #[cfg(feature="multiplayer")] pub fn join() -> Self { Self { multiplayer: Some(Peer::client()), ..Self::empty() } } /// adds a world to a build screen fn upgrade(&mut self, world: World) { self.world = Some(world); } /// Launch an existing build screen in game host mode /// initializes the p2p backend #[cfg(feature="multiplayer")] pub fn as_host(self) -> Self { Self { multiplayer: Some(Peer::host()), ..self } } } impl GameComponent for BuildScreen { async fn draw(&self, assets: &AssetStore) { if self.show_inv { // draw inventory self.inv.draw(&assets).await; return; } // order: x (inc) -> y (inc) -> z (inc) let mut render_order: Vec<(&Pos3, &(Block, Direction))> = self.grid.iter().collect(); render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 ); for (pos, (block, dir)) in render_order.iter() { let texture = block.get_texture().await.get_dir(dir); let width = TEXTURE_WIDTH * self.cam.scale; let height = TEXTURE_HEIGHT * self.cam.scale; let (x,y) = get_screen_coords(pos, self.cam.scale, self.cam.center); if x >= -width && x <= screen_width() && y >= -width && y <= screen_height() { // render block draw_texture_ex( texture, x, y, WHITE, DrawTextureParams { dest_size: Some(Vec2::new(width, height)), source: None, rotation: 0.0, flip_x: false, flip_y: false, pivot: Some(Vec2::new(width / 2.0, height / 2.0)) }); if let Some(world) = &self.world { if self.inventory_widgets.shown { // draw inventory self.inventory_widgets.widget_back.draw(&assets).await; self.inventory_widgets.widget_categories.draw(&assets).await; for slot in self.inventory_widgets.widgets_slots.iter() { slot.draw(&assets).await; } } // Draw selected item (top right) { let smallest = if screen_width() < screen_height() { screen_width() } else { screen_height() }; // use 70% of screen width for slots let width = smallest * 0.08; // only use 90% of slot width for the actual item // ensures enough space around item let slot_render_dim = width * 0.9; let tx = screen_width() - width; let ty = width - slot_render_dim; // draw background draw_texture_ex( assets.ui.panel_brown.0, tx, ty, WHITE, DrawTextureParams { dest_size: Some(Vec2::new(slot_render_dim, slot_render_dim)), ..Default::default() } ); if let Some(block) = &self.inv.selected { // draw block draw_texture_ex( block.get_texture().await.get_dir(&self.inv.direction), tx + slot_render_dim / 6.0, ty + width / 2.0 - (slot_render_dim * 2.0/3.0 * TEXTURE_HEIGHT / TEXTURE_WIDTH) / 2.0 - TEXTURE_DEPTH * 0.08 * 0.9, WHITE, DrawTextureParams { dest_size: Some(Vec2::new( slot_render_dim * 2.0/3.0, slot_render_dim * 2.0/3.0 * TEXTURE_HEIGHT / TEXTURE_WIDTH)), ..Default::default() self.inventory_widgets.widget_page_indicator.draw(&assets).await; return; } // order: x (inc) -> y (inc) -> z (inc) let mut render_order: Vec<(&Pos3, &(Block, Direction))> = world.grid.iter().collect(); render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 ); for (pos, (block, dir)) in render_order.iter() { let texture = block.get_texture().await.get_dir(dir); let width = TEXTURE_WIDTH * world.cam.scale; let height = TEXTURE_HEIGHT * world.cam.scale; let (x,y) = get_screen_coords(pos, world.cam.scale, world.cam.center); if x >= -width && x <= screen_width() && y >= -width && y <= screen_height() { // render block draw_texture_ex( texture, x, y, WHITE, DrawTextureParams { dest_size: Some(Vec2::new(width, height)), source: None, rotation: 0.0, flip_x: false, flip_y: false, pivot: Some(Vec2::new(width / 2.0, height / 2.0)) }); } ); } // Draw selected item (top right) self.item_indicator_widget.draw(&assets).await; } } fn ev_loop(&mut self) -> GameEvent { // process inventory events if is_key_down(KeyCode::I) { self.show_inv = true; } if is_mouse_button_pressed(MouseButton::Left) { let (mx,my) = mouse_position(); if let Some(world) = &mut self.world { if self.inventory_widgets.shown { // process input match self.inventory_widgets.widget_back.ev_loop() { ButtonEvent::LeftClick => { self.inventory_widgets.shown = false; } _ => () } if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) || is_key_pressed(KeyCode::I) { self.inventory_widgets.shown = false; } if is_key_pressed(KeyCode::Left) { self.inventory_widgets.page_down(&mut world.inventory); } if is_key_pressed(KeyCode::Right) { self.inventory_widgets.page_up(&mut world.inventory); } match self.inventory_widgets.widget_categories.ev_loop() { ButtonEvent::LeftClick => { // change category let categories = Category::all(); let current = &world.inventory.category; let index = categories.iter().position(|c| &c == ¤t).unwrap(); if index + 1 >= categories.len() { self.inventory_widgets.load_category( categories.get(0).unwrap().clone(), &mut world.inventory ); } else { self.inventory_widgets.load_category( categories.get(index+1).unwrap().clone(), &mut world.inventory ); } } _ => () } for (i, slot) in self.inventory_widgets.widgets_slots.iter_mut().enumerate() { match slot.ev_loop() { ButtonEvent::LeftClick => { // user selected block let items = world.inventory.category.get_blocks(); if let Some(block) = items.get(self.inventory_widgets.page * 9 + i) { world.inventory.selected = Some(block.clone()); self.inventory_widgets.shown = false; } }, _ => () } } let smallest = if screen_width() < screen_height() { screen_width() } else { screen_height() }; // use 70% of screen width for slots let width = smallest * 0.08; // only use 90% of slot width for the actual item // ensures enough space around item let slot_render_dim = width * 0.9; let tx = screen_width() - width; let ty = width - slot_render_dim; if mx >= tx && mx <= tx + slot_render_dim && my >= ty && my <= ty + slot_render_dim { self.show_inv = true; // process inventory events if is_key_down(KeyCode::I) { // open inventory self.inventory_widgets.shown = true; self.inventory_widgets.load_current_category(&mut world.inventory); } } if self.show_inv { match self.inv.ev_loop() { GameEvent::Quit => { self.show_inv = false; return GameEvent::None; match self.item_indicator_widget.ev_loop() { ButtonEvent::LeftClick => { // open inventory self.inventory_widgets.shown = true; self.inventory_widgets.load_current_category(&mut world.inventory); } _ => () } _ => return GameEvent::None } } if is_mouse_button_pressed(MouseButton::Left) || is_mouse_button_pressed(MouseButton::Right) { { let amount = if let Some(block) = &world.inventory.selected { if let Some(a) = world.inventory.contents.get(block) { Some(*a) } else { None } } else { None }; self.item_indicator_widget.update(&world.inventory.selected, &world.inventory.direction, &amount); } // determine which block / side was clicked let (mx, my) = mouse_position(); if is_mouse_button_pressed(MouseButton::Left) || is_mouse_button_pressed(MouseButton::Right) { // virtual render cycle let render_order: Vec<(&Pos3, &(Block, Direction))> = self.grid.iter().collect(); // determine which block / side was clicked let (mx, my) = mouse_position(); // list of positions in render que for given pixel // (mx, my) let mut in_path:Vec<&Pos3> = Vec::new(); // virtual render cycle let render_order: Vec<(&Pos3, &(Block, Direction))> = world.grid.iter().collect(); for (pos, _) in render_order.iter() { let (x,y) = get_screen_coords(pos, self.cam.scale, self.cam.center); // list of positions in render que for given pixel // (mx, my) let mut in_path:Vec<&Pos3> = Vec::new(); if mx >= x && mx <= x + TEXTURE_WIDTH * self.cam.scale && my >= y + TEXTURE_Y_WHITESPACE * self.cam.scale && my <= y + TEXTURE_HEIGHT * self.cam.scale { for (pos, _) in render_order.iter() { let (x,y) = get_screen_coords(pos, world.cam.scale, world.cam.center); // check if mouse is above transparent area // and skip block if necessary if Face::from_xy(mx-x, my-y, self.cam.scale).is_none() { continue; } if mx >= x && mx <= x + TEXTURE_WIDTH * world.cam.scale && my >= y + TEXTURE_Y_WHITESPACE * world.cam.scale && my <= y + TEXTURE_HEIGHT * world.cam.scale { // check if mouse is above transparent area // and skip block if necessary if Face::from_xy(mx-x, my-y, world.cam.scale).is_none() { continue; } // block in mouse path in_path.push(pos); // block in mouse path in_path.push(pos); } } } // weight axis in_path.sort_by_key(|pos| pos.x + pos.y*2 + pos.z*3 ); if let Some(pos) = in_path.last() { // position of clicked block // because it is the last block in de render queue // for a given pixel let mut pos = Pos3::new(pos.x, pos.y, pos.z); if is_mouse_button_pressed(MouseButton::Right) { // if grid has only one block // the block can not be removed // because the palyer would not be able to place // new blocks if no block exists if self.grid.len() > 1 { // destroy block if let Some((block, _)) = self.grid.remove(&pos) { self.inv.add(block.clone()); // transmit block destruction // if multiplayer mode enabled if let Some(mp) = &mut self.multiplayer { mp.perform_action(GameAction::RemoveBlock(pos.clone(), block)); // weight axis in_path.sort_by_key(|pos| pos.x + pos.y*2 + pos.z*3 ); if let Some(pos) = in_path.last() { // position of clicked block // because it is the last block in de render queue // for a given pixel let mut pos = Pos3::new(pos.x, pos.y, pos.z); if is_mouse_button_pressed(MouseButton::Right) { // if grid has only one block // the block can not be removed // because the palyer would not be able to place // new blocks if no block exists if world.grid.len() > 1 { // destroy block if let Some((block, _)) = world.destroy_block(&pos) { // transmit block destruction // if multiplayer mode enabled if let Some(mp) = &mut self.multiplayer { mp.perform_action(GameAction::RemoveBlock(pos.clone(), block)); } } } } } } if is_mouse_button_pressed(MouseButton::Left) { // place block let (x,y) = get_screen_coords(&pos, self.cam.scale, self.cam.center); let face = Face::from_xy(mx-x, my-y, self.cam.scale); match face.unwrap() { Face::Top => { pos.z+=1; }, Face::Left => { pos.x+=1; }, Face::Right => { pos.y+=1; } } if is_mouse_button_pressed(MouseButton::Left) { // place block let (x,y) = get_screen_coords(&pos, world.cam.scale, world.cam.center); let face = Face::from_xy(mx-x, my-y, world.cam.scale); match face.unwrap() { Face::Top => { pos.z+=1; }, Face::Left => { pos.x+=1; }, Face::Right => { pos.y+=1; } } if let Some(block) = self.inv.place() { self.grid.insert(pos.clone(), (block.clone(), self.inv.direction.clone())); if let Some(block) = &world.inventory.selected.clone() { if world.place_block(&pos, block.clone(), world.inventory.direction.clone()) { // transmit block placement // if multiplayer mode enabled if let Some(mp) = &mut self.multiplayer { mp.perform_action(GameAction::PlaceBlock(pos, block, self.inv.direction.clone())); // transmit block placement // if multiplayer mode enabled if let Some(mp) = &mut self.multiplayer { mp.perform_action(GameAction::PlaceBlock(pos, block.clone(), world.inventory.direction.clone())); } } } } } } } } // zoom with Ctrl-MouseWheel if is_key_down(KeyCode::LeftControl) { let mut scale = self.cam.scale; let (_, wy) = mouse_wheel(); // zoom with Ctrl-MouseWheel if is_key_down(KeyCode::LeftControl) { let mut scale = world.cam.scale; let (_, wy) = mouse_wheel(); scale += wy as f32 * 1.0/10.0; scale += wy as f32 * 1.0/10.0; if scale > 0.05 && scale < 6.0 { self.cam.scale = scale; } } else { let (mx, my) = mouse_wheel(); if scale > 0.05 && scale < 6.0 { world.cam.scale = scale; } } else { let (mx, my) = mouse_wheel(); self.cam.center.y += my * 5.0 * (1.0/self.cam.scale); self.cam.center.x += mx * 5.0 * (1.0/self.cam.scale); } world.cam.center.0 += mx * 5.0 * (1.0/world.cam.scale); world.cam.center.1 += my * 5.0 * (1.0/world.cam.scale); } // keyboard control if is_key_down(KeyCode::Down) { self.cam.center.y -= 1.0 * (1.0/self.cam.scale); } if is_key_down(KeyCode::Up) { self.cam.center.y += 1.0 * (1.0/self.cam.scale); } if is_key_down(KeyCode::Left) { self.cam.center.x += 1.0 * (1.0/self.cam.scale); } if is_key_down(KeyCode::Right) { self.cam.center.x -= 1.0 * (1.0/self.cam.scale); } if is_key_down(KeyCode::C) { self.cam.center = CVec2::new(0.0, 0.0); } if is_mouse_button_down(MouseButton::Middle) { let (nx, ny) = mouse_position(); if let Some((ox, oy)) = self.mouse_position { // keyboard control if is_key_down(KeyCode::Down) { world.cam.center.1 -= 1.0 * (1.0/world.cam.scale); } if is_key_down(KeyCode::Up) { world.cam.center.1 += 1.0 * (1.0/world.cam.scale); } if is_key_down(KeyCode::Left) { world.cam.center.0 += 1.0 * (1.0/world.cam.scale); } if is_key_down(KeyCode::Right) { world.cam.center.0 -= 1.0 * (1.0/world.cam.scale); } if is_key_down(KeyCode::C) { world.cam.center = (0.0, 0.0); } if is_mouse_button_down(MouseButton::Middle) { let (nx, ny) = mouse_position(); if let Some((ox, oy)) = self.mouse_position { let dx = nx - ox; let dy = ny - oy; let dx = nx - ox; let dy = ny - oy; self.cam.center.x += dx; self.cam.center.y += dy; } self.mouse_position = Some((nx, ny)); } if is_mouse_button_released(MouseButton::Middle) { self.mouse_position = None; } world.cam.center.0 += dx; world.cam.center.1 += dy; } self.mouse_position = Some((nx, ny)); } if is_mouse_button_released(MouseButton::Middle) { self.mouse_position = None; } // change block direction if is_key_pressed(KeyCode::K) { self.inv.direction = Direction::North; } if is_key_pressed(KeyCode::L) { self.inv.direction = Direction::East; } if is_key_pressed(KeyCode::J) { self.inv.direction = Direction::South; } if is_key_pressed(KeyCode::H) { self.inv.direction = Direction::West; } // change block direction if is_key_pressed(KeyCode::K) { world.inventory.direction = Direction::North; } if is_key_pressed(KeyCode::L) { world.inventory.direction = Direction::East; } if is_key_pressed(KeyCode::J) { world.inventory.direction = Direction::South; } if is_key_pressed(KeyCode::H) { world.inventory.direction = Direction::West; } // save the world if is_key_pressed(KeyCode::S) || is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) { // save game // save the world if is_key_pressed(KeyCode::S) || is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) { // save game if let Some(file_name) = &self.file_name { save_map(&file_name, &self); } if let Some(file_name) = &self.file_name { world.to_disk(&file_name); } if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) { if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) { if let Some(mp) = &mut self.multiplayer { mp.close(); } if let Some(mp) = &mut self.multiplayer { mp.close(); } // close game return GameEvent::ChangeScreen(Screen::default()); } } // close game return GameEvent::ChangeScreen(Screen::default()); } } if let Some(mp) = &mut self.multiplayer { if let Some(task) = mp.get_next_task() { // match action // and perform neccesarry action match task { GameAction::RequestMap => { mp.perform_action(GameAction::TransmitMap(self.grid.clone())); }, GameAction::RequestInventory => { mp.perform_action(GameAction::TransmitInventory(self.inv.contents.clone(), self.inv.has_infinite_items())); GameAction::RequestWorld => { if let Some(world) = &mut self.world { mp.perform_action( GameAction::TransmitWorld(world.clone())); } }, GameAction::PlaceBlock(pos, block, dir) => { // remove block from inventory if self.inv.has_infinite_items() { if let Some(slot) = self.inv.contents.get_mut(&block) { if *slot > 1 { *slot -=1; } else if *slot == 0 { self.inv.contents.remove(&block); } } if let Some(world) = &mut self.world { // remove block from inventory world.place_block(&pos, block, dir); } self.grid.insert(pos, (block, dir)); }, GameAction::RemoveBlock(pos, block) => { if self.inv.has_infinite_items() { if let Some(slot) = self.inv.contents.get_mut(&block) { *slot += 1; } else { self.inv.contents.insert(block, 1); } GameAction::RemoveBlock(pos, _block) => { if let Some(world) = &mut self.world { world.destroy_block(&pos); } self.grid.remove(&pos); }, GameAction::TransmitMap(grid) => { self.grid = grid; GameAction::TransmitWorld(world) => { self.upgrade(world); }, GameAction::TransmitInventory(inv, infty) => { self.inv.contents = inv; self.inv.toggle_infinite_items(infty); if self.grid.len() == 0 { mp.perform_action(GameAction::RequestMap); } } } } } GameEvent::None } }
D src/screens/inventory.rs => src/screens/inventory.rs +0 -340
@@ 1,340 0,0 @@ use macroquad::prelude::*; use crate::types::{ GameComponent, GameEvent, Direction }; use super::build::{ TEXTURE_WIDTH, TEXTURE_HEIGHT, TEXTURE_Y_WHITESPACE }; use crate::textures::AssetStore; use crate::blocks::Block; use std::collections::HashMap; use nanoserde::{DeJson, SerJson}; use crate::draw::{ TextButton, CenteredText, Widget, ButtonEvent }; /// draws centered text /// with the given font and font size pub fn draw_centered_text(text: &str, x: f32, y: f32, font: Option<Font>, font_size: u16, color: Color) { let mut title_params = TextParams { font_size, color, ..Default::default() }; if let Some(font) = font { title_params.font = font; } let title_center = get_text_center( text, font, font_size, 1.0, 0.0); draw_text_ex( text, x - title_center.x, y - title_center.y, title_params); } /// draws a wide button with text pub fn draw_wide_button(text: &str, pressed: bool, x: f32, y: f32, assets: &AssetStore) { draw_texture( if pressed { assets.ui.long_button.1 } else { assets.ui.long_button.0 }, x - 190.0 / 2.0, y, WHITE); draw_centered_text(text, x, y + 49.0 / 2.0 - 2.0, Some(assets.font), 60, LIGHTGRAY); } /// The players inventory #[derive(SerJson, DeJson)] pub struct Inventory { /// when set to true, the player has an infinite amount of items infinite_items: bool, /// items in inventory /// with their appropriate amount /// if amount is zero, the item should be removed /// if infinite_items is true, the amount wont decrease pub contents: HashMap<Block, usize>, /// which block is currently selected pub selected: Option<Block>, /// in which direction the block should be facing pub direction: Direction, /// current page /// set to zero when closing inventory /// the page count depends on the amount of items /// and on how many slots to draw per page /// see SLOTS_PER_PAGE for more page: usize, } impl Inventory { pub fn has_infinite_items(&self) -> bool { self.infinite_items } pub fn toggle_infinite_items(&mut self, toggle: bool) { self.infinite_items = toggle; } /// creates an empty inventory pub fn empty() -> Self { Self { infinite_items: false, contents: HashMap::new(), selected: None, direction: Direction::North, page: 0, } } pub fn new() -> Self { let mut this = Self::empty(); this.infinite_items = true; if this.infinite_items { for block in Block::all() { this.contents.insert(block, 1); } } this } /// adds a block to the players inventory pub fn add(&mut self, block: Block) { if !self.infinite_items { *self.contents.get_mut(&block).unwrap_or(&mut 0)+=1; } } /// removes the selected block (one) from the inventory /// can be used to remove items /// or place items pub fn place(&mut self) -> Option<Block> { if let Some(block) = &self.selected { if !self.infinite_items { let cv: &usize = self.contents.get(&block).unwrap_or(&0); if cv > &0 { *self.contents.get_mut(&block).unwrap_or(&mut 0) -= 1; } else { self.contents.remove(&block); return None; } } return Some(block.clone()); } return None; } } /// how many slots to draw per inventory page const SLOTS_PER_PAGE: usize = 9; impl GameComponent for Inventory { async fn draw(&self, assets: &AssetStore) { let mid_x = screen_width()/2.0; let mid_y = screen_height()/2.0; let smallest = if screen_width() < screen_height() { screen_width() } else { screen_height() }; // use 70% of screen width for slots let width = smallest * 0.7; let hslot_count = ((SLOTS_PER_PAGE as f32).sqrt()) as usize; let slot_dim = width / hslot_count as f32; // only use 90% of slot width for the actual item // ensures enough space around item let slot_render_dim = slot_dim * 0.9; let mut list: Vec<(&Block, &usize)> = self.contents.iter().collect(); list.sort_by(|a,b| a.0.get_name().cmp(b.0.get_name())); let ox = mid_x - (hslot_count as f32 /2.0) * slot_dim; let oy = mid_y - (hslot_count as f32 /2.0) * slot_dim; for x in 0..hslot_count { for y in 0..hslot_count { let tx = ox + slot_dim * x as f32 + (slot_dim - slot_render_dim) / 2.0; let ty = oy + slot_dim * y as f32 + (slot_dim - slot_render_dim) / 2.0; let mut bg = assets.ui.panel_brown.0; if let Some(block) = list.get(self.page * SLOTS_PER_PAGE + x + y*hslot_count) && let Some(sb) = &self.selected && sb == block.0 { // selected block // draw blue background bg = assets.ui.panel_blue.0; } // draw background draw_texture_ex( bg, tx, ty, WHITE, DrawTextureParams { dest_size: Some(Vec2::new(slot_render_dim, slot_render_dim)), ..Default::default() } ); // draw brown foreground draw_texture_ex( assets.ui.panel_brown.1, tx + 4.0 + 2.0/2.0, ty + 4.0 + 2.0/2.0, WHITE, DrawTextureParams { dest_size: Some(Vec2::new(slot_render_dim - 4.0 * 2.0 - 4.0, slot_render_dim - 4.0 * 2.0 - 4.0)), ..Default::default() } ); if let Some((block, amount)) = list.get(self.page * SLOTS_PER_PAGE + x + y * hslot_count) { // draw block draw_texture_ex( block.get_texture().await.get_dir(&self.direction), tx + slot_render_dim / 6.0, ty + slot_dim / 2.0 - TEXTURE_HEIGHT / 2.0 + TEXTURE_Y_WHITESPACE / 2.0, WHITE, DrawTextureParams { dest_size: Some(Vec2::new( slot_render_dim * 2.0/3.0, slot_render_dim * 2.0/3.0 * TEXTURE_HEIGHT / TEXTURE_WIDTH)), ..Default::default() } ); // draw amount let text = if self.infinite_items { "inf.".to_string() } else { amount.to_string() }; draw_centered_text( &text, tx + slot_render_dim / 6.0 + slot_render_dim * 2.0/3.0, ty + slot_dim / 2.0 - TEXTURE_HEIGHT / 2.0 + TEXTURE_Y_WHITESPACE / 2.0 + slot_render_dim * 2.0/3.0 * TEXTURE_HEIGHT / TEXTURE_WIDTH - 4.0, Some(assets.font), 30, WHITE ); } } } // display page indicator let page_count = self.contents.len().div_ceil(SLOTS_PER_PAGE); draw_centered_text( &format!("{}/{}", self.page+1, page_count), mid_x, mid_y + hslot_count as f32 /2.0 * slot_dim + slot_dim/3.0, Some(assets.font), 60, WHITE ); } fn ev_loop(&mut self) -> GameEvent { // close inventory if is_key_down(KeyCode::Q) { return GameEvent::Quit; } // change block direction if is_key_pressed(KeyCode::W) { self.direction = Direction::North; } if is_key_pressed(KeyCode::D) { self.direction = Direction::East; } if is_key_pressed(KeyCode::S) { self.direction = Direction::South; } if is_key_pressed(KeyCode::A) { self.direction = Direction::West; } // process page naviagtion let page_count = self.contents.len().div_ceil(SLOTS_PER_PAGE); if self.page >= page_count { // reset page if page is now empty self.page = 0; } if is_key_pressed(KeyCode::Left) { if self.page == 0 { if page_count > 0 { self.page = page_count - 1; } } else { self.page -= 1; } } if is_key_pressed(KeyCode::Right) { if self.page +1 >= page_count { self.page = 0; } else { self.page += 1; } } // detect pressed item if is_mouse_button_pressed(MouseButton::Left) { let (mx,my) = mouse_position(); // virtual render cycle let mid_x = screen_width()/2.0; let mid_y = screen_height()/2.0; let smallest = if screen_width() < screen_height() { screen_width() } else { screen_height() }; // use 70% of screen width for slots let width = smallest * 0.7; let hslot_count = ((SLOTS_PER_PAGE as f32).sqrt()) as usize; let slot_dim = width / hslot_count as f32; // only use 90% of slot width for the actual item // ensures enough space around item let slot_render_dim = slot_dim * 0.9; let ox = mid_x - (hslot_count as f32 /2.0) * slot_dim; let oy = mid_y - (hslot_count as f32 /2.0) * slot_dim; for x in 0..hslot_count { for y in 0..hslot_count { let tx = ox + slot_dim * x as f32 + (slot_dim - slot_render_dim) / 2.0; let ty = oy + slot_dim * y as f32 + (slot_dim - slot_render_dim) / 2.0; if mx >= tx && mx <= tx + slot_render_dim && my >= ty && my <= ty + slot_render_dim { // clicked this item let mut list: Vec<(&Block, &usize)> = self.contents.iter().collect(); list.sort_by(|a,b| a.0.get_name().cmp(b.0.get_name())); if let Some((block, _)) = list.get(self.page * SLOTS_PER_PAGE + x + y * hslot_count) { self.selected = Some((*block).clone()); } else { self.selected = None; } return GameEvent::Quit; } } } } GameEvent::None } }
M src/screens/map_creator.rs => src/screens/map_creator.rs +10 -13
@@ 1,19 1,15 @@ use macroquad::prelude::*; use crate::types::{ use crate::game::{ GameComponent, GameEvent GameEvent, world::World }; use crate::textures::AssetStore; use super::map_select::SelectScreen; use super::build::BuildScreen; use super::Screen; use crate::storage::{ get_world_list, save_map }; use crate::draw::{ use crate::ui::{ TextButton, Widget, @@ ButtonEvent, 76,7 72,7 @@ impl GameComponent for MapCreatorScreen { { // process text input if is_key_pressed(KeyCode::Delete) { if is_key_pressed(KeyCode::Backspace) { self.name.pop(); @@ if self.name.len() == 0 { 247,12 243,13 @@ impl GameComponent for MapCreatorScreen { match self.widget_create.ev_loop() { ButtonEvent::LeftClick => { // TODO check text if self.name.len() > 0 && self.name.len() <= 10 { let name = format!("{}.world", self.name); if !get_world_list().contains(&name) { let bscr = BuildScreen::new(&name); save_map(&name, &bscr); if !World::get_list().contains(&name) { let bscr = BuildScreen::new(&name, true); if let Some(world) = bscr.world { world.to_disk(&name); } return GameEvent::ChangeScreen(Screen::Select(SelectScreen::new())); } }
M src/screens/map_select.rs => src/screens/map_select.rs +22 -26
@@ 1,21 1,19 @@ use macroquad::prelude::*; use crate::types::{ use crate::game::{ GameComponent, GameEvent GameEvent, world::World }; use crate::textures::AssetStore; use crate::draw::{ use crate::ui::{ TextButton, Widget, ButtonEvent, SelectableText }; use crate::storage::{ get_world_list, read_map }; use super::{ Screen, build::BuildScreen, map_creator::MapCreatorScreen }; @@ 40,14 38,14 @@ pub struct SelectScreen { /// list of slots level_select_widgets: Vec<SelectableText>, /// currently selected slot selected: Option<usize>, selected: Option<String>, /// currently visible page page: usize } impl SelectScreen { /// create a new MapSelectorScreen instance pub fn new() -> Self { let levels = get_world_list(); let levels = World::get_list(); Self { list: levels.clone(), @@ widget_back: TextButton::new("Back", 0.1, 0.1) 117,10 115,9 @@ impl GameComponent for SelectScreen { { match self.widget_host.ev_loop() { ButtonEvent::LeftClick => { if let Some(index) = self.selected { let file_name = self.list.get(index+self.page).unwrap(); if let Some(scr) = read_map(&file_name) { return GameEvent::ChangeScreen(Screen::Build(scr.with_file_name(&file_name).as_host())); if let Some(file_name) = &self.selected { if let Some(scr) = BuildScreen::from_disk(&file_name) { return GameEvent::ChangeScreen(Screen::Build(scr.as_host())); } } @@ } 129,41 126,40 @@ impl GameComponent for SelectScreen { } match self.widget_new.ev_loop() { ButtonEvent::LeftClick => { return GameEvent::ChangeScreen(Screen::Create(MapCreatorScreen::new())) return GameEvent::ChangeScreen(Screen::Create(MapCreatorScreen::new())) }, _ => () } match self.widget_play.ev_loop() { ButtonEvent::LeftClick => { if let Some(index) = self.selected { let file_name = self.list.get(index+self.page).unwrap(); if let Some(scr) = read_map(&file_name) { return GameEvent::ChangeScreen(Screen::Build(scr.with_file_name(&file_name))); if let Some(file_name) = &self.selected { if let Some(scr) = BuildScreen::from_disk(&file_name) { return GameEvent::ChangeScreen(Screen::Build(scr)); } } } _ => () } for (i, slot) in self.level_select_widgets.iter_mut().enumerate() { for slot in self.level_select_widgets.iter_mut() { slot.set_selected(false); match slot.ev_loop() { ButtonEvent::LeftClick => { if let Some(sel) = self.selected && sel == i+self.page { if let Some(sel) = &self.selected && sel == &slot.get_text() { // already selected // deselect self.selected = None; } else { // user selected this item self.selected = Some(i + self.page); self.selected = Some(slot.get_text().to_string()); } slot.set_selected(false); }, _ => () } } if let Some(selection) = self.selected { for (i, slot) in self.level_select_widgets.iter_mut().enumerate() { if i == selection { if let Some(selection) = &self.selected { for slot in self.level_select_widgets.iter_mut() { if &slot.get_text() == selection { slot.set_selected(true); } @@ } 175,7 171,7 @@ impl GameComponent for SelectScreen { } if is_key_pressed(KeyCode::Down) { if self.page < self.list.len() - self.level_select_widgets.len() { if self.list.len() >= self.level_select_widgets.len() && self.page < self.list.len() - self.level_select_widgets.len() { self.page += 1; // update widget text
M src/screens/mod.rs => src/screens/mod.rs +7 -9
@@ 1,6 1,5 @@ mod welcome; pub mod build; mod inventory; mod map_select; mod map_creator; @@ 8,12 7,11 @@ use welcome::WelcomeScreen; use build::BuildScreen; use map_select::SelectScreen; use map_creator::MapCreatorScreen; use crate::types::{ GameComponent, GameEvent }; use crate::textures::AssetStore; use crate::game::{ GameEvent, GameComponent }; /// Possible screens @@ /// that can be shown 25,7 23,7 @@ pub enum Screen { /// Map selection screen Select(SelectScreen), /// Map creation screen Create(MapCreatorScreen) Create(MapCreatorScreen), } impl GameComponent for Screen { @@ async fn draw(&self, assets: &AssetStore) { 33,7 31,7 @@ impl GameComponent for Screen { Screen::Welcome(w) => w.draw(&assets).await, Screen::Build(b) => b.draw(&assets).await, Screen::Select(s) => s.draw(&assets).await, Screen::Create(c) => c.draw(&assets).await Screen::Create(c) => c.draw(&assets).await, } } @@ fn ev_loop(&mut self) -> GameEvent { 41,7 39,7 @@ impl GameComponent for Screen { Screen::Welcome(w) => w.ev_loop(), Screen::Build(b) => b.ev_loop(), Screen::Select(s) => s.ev_loop(), Screen::Create(c) => c.ev_loop() Screen::Create(c) => c.ev_loop(), } } }
M src/screens/welcome.rs => src/screens/welcome.rs +3 -3
@@ 1,10 1,10 @@ use macroquad::prelude::*; use crate::types::{ use crate::game::{ GameComponent, GameEvent }; use crate::textures::AssetStore; use crate::draw::{ use crate::ui::{ TextButton, CenteredText, @@ Widget, 77,7 77,7 @@ impl GameComponent for WelcomeScreen { _ => () } match self.widget_join.ev_loop() { ButtonEvent::LeftClick => return GameEvent::ChangeScreen(Screen::Build(BuildScreen::multiplayer())), ButtonEvent::LeftClick => return GameEvent::ChangeScreen(Screen::Build(BuildScreen::join())), _ => () } match self.widget_quit.ev_loop() {
M src/textures.rs => src/textures.rs +2 -2
@@ 1,5 1,5 @@ use macroquad::prelude::*; use crate::types::Direction; use crate::game::types::Direction; /// Collection of textures required by game @@ pub struct AssetStore { 46,7 46,7 @@ macro_rules! include_base_tile { ($name:literal, $dir:literal) => { Texture2D::from_image( &Image::from_file_with_format( include_bytes!(concat!("../assets/base/Tiles/", $name, "_", $dir, ".png")), include_bytes!(concat!("../../assets/base/Tiles/", $name, "_", $dir, ".png")), Some(ImageFormat::Png) ) )