From e499ed2847796f5cbdc18ed67af2edc9737e9ae1 Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Tue, 28 Feb 2023 16:25:10 +0100 Subject: [PATCH] Rewrote screens to use static components --- .woodpecker.yml | 25 - Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 16 +- example.world | 1 - src/{ => game}/blocks.rs | 235 ++++++---- src/game/camera.rs | 19 + src/game/inventory.rs | 43 ++ src/game/mod.rs | 35 ++ src/game/types.rs | 41 ++ src/game/world.rs | 168 +++++++ src/main.rs | 11 +- src/p2p.rs | 31 +- src/screens/build.rs | 917 +++++++++++++++++++------------------ src/screens/inventory.rs | 340 -------------- src/screens/map_creator.rs | 23 +- src/screens/map_select.rs | 48 +- src/screens/mod.rs | 16 +- src/screens/welcome.rs | 6 +- src/storage.rs | 94 ---- src/textures.rs | 4 +- src/types.rs | 70 --- 22 files changed, 990 insertions(+), 1157 deletions(-) delete mode 100644 .woodpecker.yml delete mode 100644 example.world rename src/{ => game}/blocks.rs (76%) create mode 100644 src/game/camera.rs create mode 100644 src/game/inventory.rs create mode 100644 src/game/mod.rs create mode 100644 src/game/types.rs create mode 100644 src/game/world.rs delete mode 100644 src/screens/inventory.rs delete mode 100644 src/storage.rs delete mode 100644 src/types.rs diff --git a/.woodpecker.yml b/.woodpecker.yml deleted file mode 100644 index 472d7e1..0000000 --- a/.woodpecker.yml +++ /dev/null @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 4858d68..5dbe97f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1619,7 +1619,7 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "little_town" -version = "0.1.0" +version = "0.2.0" dependencies = [ "async-std", "directories-next", diff --git a/Cargo.toml b/Cargo.toml index 4038144..233de27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 " ] readme = "README.org" diff --git a/README.md b/README.md index f23777e..e5341ca 100644 --- a/README.md +++ b/README.md @@ -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 `` | +| Close Inventory | `Q`, `I` or `` | +| Page down | `` | +| Page up | `` | ### Level Select screen | Action | Key(s) / MouseButton | diff --git a/example.world b/example.world deleted file mode 100644 index f96acc1..0000000 --- a/example.world +++ /dev/null @@ -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 diff --git a/src/blocks.rs b/src/game/blocks.rs similarity index 76% rename from src/blocks.rs rename to src/game/blocks.rs index 17bb871..59048e5 100644 --- a/src/blocks.rs +++ b/src/game/blocks.rs @@ -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> = 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 { + + // 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 { + 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 { + 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 } } diff --git a/src/game/camera.rs b/src/game/camera.rs new file mode 100644 index 0000000..4d98814 --- /dev/null +++ b/src/game/camera.rs @@ -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 + } + } +} diff --git a/src/game/inventory.rs b/src/game/inventory.rs new file mode 100644 index 0000000..5845bff --- /dev/null +++ b/src/game/inventory.rs @@ -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, + /// which block is currently selected + pub selected: Option, + /// 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); + } + } +} diff --git a/src/game/mod.rs b/src/game/mod.rs new file mode 100644 index 0000000..d3cb4a1 --- /dev/null +++ b/src/game/mod.rs @@ -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) +} diff --git a/src/game/types.rs b/src/game/types.rs new file mode 100644 index 0000000..6d50002 --- /dev/null +++ b/src/game/types.rs @@ -0,0 +1,41 @@ +use nanoserde::{DeJson, SerJson}; + +/// three dimensional coordinate +/// z +/// | +/// |__ y +/// / +/// / +/// x +#[derive(Debug,Eq,Hash,PartialEq, DeJson, SerJson, Clone)] +pub struct Pos3 { + pub x: i64, + pub y: i64, + pub z: i64 +} +impl Pos3 { + pub fn new(x: i64, y: i64, z: i64) -> Self { + Self { + x, + y, + z + } + } +} + +/// Directions +#[derive(Clone, DeJson, SerJson)] +pub enum Direction { + /// Left + /// (left edge of screen) + West, + /// Right + /// (right edge of screen) + East, + /// Up + /// (top edge of screen) + North, + /// Down + /// (bottom edge of screen) + South +} diff --git a/src/game/world.rs b/src/game/world.rs new file mode 100644 index 0000000..e41f1e9 --- /dev/null +++ b/src/game/world.rs @@ -0,0 +1,168 @@ +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::{ + File, + OpenOptions, + read_dir +}; +use std::io::{ + BufReader, + Read, + Write +}; + + +/// returns the OS specific data dir as a string +/// if there is no such dir, None will be returned +fn get_data_dir() -> Option { + if let Some(pd) = ProjectDirs::from("icu", "ccw", "little_town") { + if let Some(path) = pd.data_dir().to_str() { + return Some(path.to_string()); + } + } + + None +} + +#[derive(SerJson, DeJson, Clone)] +pub struct World { + /// the map itself + pub grid: HashMap, + /// 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 { + 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; + } + } + Vec::new() + } + + /// tries loading the world from a file + pub fn from_disk(name: &str) -> Option { + 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!("Failed to open file"); + } + } else { + println!("World not found"); + } + + None + } + + + /// 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") + } + } + } + +} diff --git a/src/main.rs b/src/main.rs index 54fe92e..f3d1afe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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; diff --git a/src/p2p.rs b/src/p2p.rs index a49bb18..2f7b3c2 100644 --- a/src/p2p.rs +++ b/src/p2p.rs @@ -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, 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), - /// 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() diff --git a/src/screens/build.rs b/src/screens/build.rs index 7fc4c97..39bb167 100644 --- a/src/screens/build.rs +++ b/src/screens/build.rs @@ -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, - cam: Camera, - show_inv: bool, - inv: Inventory, - mouse_position: Option<(f32, f32)>, - #[cfg(feature="multiplayer")] - #[nserde(skip)] - multiplayer: Option, - #[nserde(skip)] - file_name: Option -} -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, + 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 { - - // 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, + /// the world file name + file_name: Option, + /// where the mouse is currently at + mouse_position: Option<(f32, f32)>, + /// the p2p backend used for multiplayer mode + #[cfg(feature="multiplayer")] + multiplayer: Option, + /// 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 { + 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 } } diff --git a/src/screens/inventory.rs b/src/screens/inventory.rs deleted file mode 100644 index 5fb0e8f..0000000 --- a/src/screens/inventory.rs +++ /dev/null @@ -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_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, - /// which block is currently selected - pub selected: Option, - /// 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 { - 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 - } -} diff --git a/src/screens/map_creator.rs b/src/screens/map_creator.rs index d0f4447..4d0e43f 100644 --- a/src/screens/map_creator.rs +++ b/src/screens/map_creator.rs @@ -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())); } } diff --git a/src/screens/map_select.rs b/src/screens/map_select.rs index b199c23..857f5ab 100644 --- a/src/screens/map_select.rs +++ b/src/screens/map_select.rs @@ -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, /// currently selected slot - selected: Option, + selected: Option, /// 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 diff --git a/src/screens/mod.rs b/src/screens/mod.rs index 2446bf4..e1fd393 100644 --- a/src/screens/mod.rs +++ b/src/screens/mod.rs @@ -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(), } } } diff --git a/src/screens/welcome.rs b/src/screens/welcome.rs index 97cc234..e227fde 100644 --- a/src/screens/welcome.rs +++ b/src/screens/welcome.rs @@ -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() { diff --git a/src/storage.rs b/src/storage.rs deleted file mode 100644 index cb5e072..0000000 --- a/src/storage.rs +++ /dev/null @@ -1,94 +0,0 @@ -use directories_next::ProjectDirs; -use std::path::Path; -use std::fs::{ - File, - 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 -fn get_data_dir() -> Option { - if let Some(pd) = ProjectDirs::from("icu", "ccw", "little_town") { - if let Some(path) = pd.data_dir().to_str() { - return Some(path.to_string()); - } - } - - None -} - -/// returns a list of all world file names -pub fn get_world_list() -> Vec { - 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; - } - } - Vec::new() -} - -/// tries loading a game state from a file -pub fn read_map(name: &str) -> Option { - 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) - } - } else { - println!("Faield to read file"); - } - } else { - println!("Failed to open file"); - } - } else { - println!("World not found"); - } - - 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") - } - } -} diff --git a/src/textures.rs b/src/textures.rs index 22b8380..20689c4 100644 --- a/src/textures.rs +++ b/src/textures.rs @@ -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) ) ) diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 5260271..0000000 --- a/src/types.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::textures::AssetStore; -use crate::Screen; -use nanoserde::{DeJson, SerJson}; - -/// three dimensional coordinate -/// z -/// | -/// |__ y -/// / -/// / -/// x -#[derive(Debug,Eq,Hash,PartialEq, DeJson, SerJson, Clone)] -pub struct Pos3 { - pub x: i64, - pub y: i64, - pub z: i64 -} -impl Pos3 { - pub fn new(x: i64, y: i64, z: i64) -> Self { - Self { - x, - y, - z - } - } -} - -/// Directions -#[derive(Clone, DeJson, SerJson)] -pub enum Direction { - /// Left - /// (left edge of screen) - West, - /// Right - /// (right edge of screen) - East, - /// Up - /// (top edge of screen) - North, - /// Down - /// (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) -} -- 2.38.5