~comcloudway/little_town

e499ed2847796f5cbdc18ed67af2edc9737e9ae1 — Jakob Meier 1 year, 8 months ago 22adb12 v0.2.0
Rewrote screens to use static components
20 files changed, 906 insertions(+), 1073 deletions(-)

D .woodpecker.yml
M Cargo.lock
M Cargo.toml
M README.md
D example.world
R src/{blocks.rs => game/blocks.rs}
A src/game/camera.rs
A src/game/inventory.rs
A src/game/mod.rs
R src/{types.rs => game/types.rs}
R src/{storage.rs => game/world.rs}
M src/main.rs
M src/p2p.rs
M src/screens/build.rs
D src/screens/inventory.rs
M src/screens/map_creator.rs
M src/screens/map_select.rs
M src/screens/mod.rs
M src/screens/welcome.rs
M src/textures.rs
D .woodpecker.yml => .woodpecker.yml +0 -25
@@ 1,25 0,0 @@
platform: linux/arm64
pipeline:
  build:
    image: alpine:edge
    commands:
      - apk update
      - apk add rustup
      - curl https://sh.rustup.rs -sSf | sh -s -- -y
      - source $HOME/.cargo/env
      - cd $CI_WORKSPACE
      - cargo build --release

  publish:
    image: woodpeckerci/plugin-gitea-release
    settings:
      base_url: https://codeberg.org
      title: "ALPHA $CI_COMMIT_TAG"
      notes: "$CI_COMMIT_MESSAGE"
      prerelease: true
      files:
        - $CI_WORKSPACE/target/aarch64-unknown-linux-musl/release/little_town
      api_key:
        from_secret: API_KEY
    when:
      event: tag

M Cargo.lock => Cargo.lock +1 -1
@@ 1619,7 1619,7 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"

[[package]]
name = "little_town"
version = "0.1.0"
version = "0.2.0"
dependencies = [
 "async-std",
 "directories-next",

M Cargo.toml => Cargo.toml +1 -1
@@ 1,7 1,7 @@
[package]
name = "little_town"
description = "Build a small isometric town"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
authors = [ "Jakob Meier <comcloudway@ccw.icu>" ]
readme = "README.org"

M README.md => README.md +10 -6
@@ 23,17 23,20 @@ launch the game and the world should appear in the world list
## Roadmap
- [x] Placing & Removing blocks
- [x] Basic Welcome Screen
- [ ] Inventory
- [x] Inventory
  - [x] Basic block list
  - [ ] Inventory categories
  - [x] Inventory categories
- [x] p2p local multiplayer (using mdns discovery)
- [x] world selection & better storage
  - [x] World selection screen & loading from seperate files
  - [x] World creation screen
- [ ] (Maybe) campaign mode (build & expand a city with limited ressources)
- [ ] (MAYBE) campaign mode (build & expand a city with limited ressources)
- [ ] Touch control
- [ ] Settings menu (i.e for Keybindings)
- [ ] Game music?
- [ ] Add more assets
  - [ ] SketchTown Expansion pack
  - [ ] (MAYBE) [Sketch Desert](https://kenney.nl/assets/sketch-desert)

## Keymap
### in-Game


@@ 50,7 53,7 @@ launch the game and the world should appear in the world list
| North | `K` |
| South | `J` |
| West | `H` |
| East | `L` ||
| East | `L` |

#### Camera movement
| Action | Key(s) / MouseButton |


@@ 64,8 67,9 @@ launch the game and the world should appear in the world list
| Action | Key(s) / MouseButton |
|--------|----------------------|
| Select Item & close inventory | Left mouse button (on item) |
| Deselect Item | Left mouse button (on item) |
| Close Inventory | `Q` or `<Escape>` |
| Close Inventory | `Q`, `I` or `<Escape>` |
| Page down | `<ArrowDown>` |
| Page up | `<ArrowUp>` |

### Level Select screen
| Action | Key(s) / MouseButton |

D example.world => example.world +0 -1
@@ 1,1 0,0 @@
{"local":{"build-map":"{\"grid\":{{\"x\":21,\"y\":0,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":17,\"y\":17,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":8,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":16,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":14,\"z\":2}:[\"GrassSlope\",\"South\"],{\"x\":6,\"y\":6,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":1,\"y\":3,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":17,\"y\":9,\"z\":1}:[\"GrassWaterRiver\",\"North\"],{\"x\":2,\"y\":12,\"z\":2}:[\"BuildingDoorWindowsBeige\",\"East\"],{\"x\":3,\"y\":18,\"z\":2}:[\"GrassRiver\",\"East\"],{\"x\":9,\"y\":16,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":1,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":4,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":6,\"y\":3,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":18,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":9,\"y\":13,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":21,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":19,\"z\":1}:[\"GrassWater\",\"East\"],{\"x\":16,\"y\":13,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":24,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":27,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":21,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":17,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":18,\"y\":23,\"z\":2}:[\"CastelWindow\",\"West\"],{\"x\":17,\"y\":21,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":6,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":14,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":5,\"y\":4,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":14,\"y\":21,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":13,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":1,\"z\":2}:[\"CastelWindow\",\"North\"],{\"x\":20,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":25,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":1,\"y\":1,\"z\":2}:[\"CastleBend\",\"East\"],{\"x\":23,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":14,\"z\":2}:[\"GrassSlope\",\"South\"],{\"x\":10,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":3,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":7,\"y\":16,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":21,\"y\":14,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":18,\"y\":20,\"z\":1}:[\"GrassRiverBend\",\"South\"],{\"x\":22,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":4,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":2,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":12,\"z\":1}:[\"GrassWater\",\"South\"],{\"x\":16,\"y\":20,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":10,\"y\":4,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":14,\"y\":15,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":20,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":16,\"z\":2}:[\"GrassSlopeConvex\",\"East\"],{\"x\":14,\"y\":6,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":24,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":11,\"z\":2}:[\"StructureArch\",\"West\"],{\"x\":16,\"y\":8,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":13,\"y\":22,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":24,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":9,\"z\":1}:[\"GrassRiverBend\",\"West\"],{\"x\":2,\"y\":3,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":21,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":12,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":17,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":16,\"y\":10,\"z\":1}:[\"GrassWaterConcave\",\"East\"],{\"x\":14,\"y\":10,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":8,\"y\":25,\"z\":2}:[\"BuildingDoorBeige\",\"South\"],{\"x\":16,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":24,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":11,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":3,\"y\":15,\"z\":2}:[\"GrassPathSlope\",\"East\"],{\"x\":5,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":3,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":0,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":5,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":16,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":7,\"y\":20,\"z\":2}:[\"Trees\",\"South\"],{\"x\":24,\"y\":24,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":3,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":16,\"y\":6,\"z\":1}:[\"GrassRiverBend\",\"East\"],{\"x\":6,\"y\":7,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":19,\"y\":4,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":5,\"y\":5,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":1,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":5,\"y\":3,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":23,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":6,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":6,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":16,\"y\":7,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":13,\"y\":16,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":7,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":11,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":25,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":24,\"y\":20,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":2,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":11,\"y\":26,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":17,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":20,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":11,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":1,\"y\":16,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":5,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":18,\"z\":1}:[\"GrassWaterRiver\",\"South\"],{\"x\":18,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":19,\"z\":2}:[\"Trees\",\"South\"],{\"x\":17,\"y\":15,\"z\":2}:[\"Tree\",\"South\"],{\"x\":19,\"y\":12,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":22,\"y\":21,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":14,\"y\":16,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":18,\"y\":17,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":5,\"y\":1,\"z\":2}:[\"CastleBend\",\"South\"],{\"x\":21,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":11,\"z\":2}:[\"StructureArch\",\"East\"],{\"x\":6,\"y\":9,\"z\":1}:[\"GrassRiverBridge\",\"West\"],{\"x\":11,\"y\":6,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":18,\"y\":13,\"z\":1}:[\"GrassWaterConcave\",\"East\"],{\"x\":19,\"y\":23,\"z\":3}:[\"CastleTowerPurple\",\"South\"],{\"x\":15,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":2,\"z\":1}:[\"GrassRiverBend\",\"South\"],{\"x\":20,\"y\":24,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":9,\"y\":4,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":4,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":7,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":1,\"y\":20,\"z\":2}:[\"GrassPath\",\"East\"],{\"x\":3,\"y\":12,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":16,\"y\":4,\"z\":2}:[\"Trees\",\"South\"],{\"x\":13,\"y\":14,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":18,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":20,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":18,\"z\":2}:[\"Trees\",\"South\"],{\"x\":4,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":16,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":26,\"z\":2}:[\"CastleWall\",\"South\"],{\"x\":23,\"y\":20,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":22,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":4,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":10,\"y\":2,\"z\":2}:[\"Trees\",\"East\"],{\"x\":19,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":5,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":2,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":12,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":23,\"y\":7,\"z\":2}:[\"Trees\",\"South\"],{\"x\":22,\"y\":11,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":5,\"z\":1}:[\"GrassRiverBend\",\"East\"],{\"x\":22,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":8,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":1,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":4,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":23,\"y\":27,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":14,\"y\":1,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":2,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":11,\"y\":3,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":11,\"y\":15,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":10,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":16,\"y\":5,\"z\":1}:[\"GrassRiverBend\",\"West\"],{\"x\":21,\"y\":6,\"z\":1}:[\"GrassPathSplit\",\"North\"],{\"x\":5,\"y\":24,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":8,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":3,\"y\":17,\"z\":3}:[\"Tree\",\"East\"],{\"x\":1,\"y\":19,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":10,\"y\":7,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":21,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":7,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":22,\"z\":2}:[\"CastleCorner\",\"North\"],{\"x\":1,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":27,\"z\":1}:[\"GrassCorner\",\"East\"],{\"x\":20,\"y\":6,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":1,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":3,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":6,\"y\":0,\"z\":2}:[\"Tree\",\"South\"],{\"x\":14,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":21,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":25,\"z\":3}:[\"CastleTowerPurple\",\"West\"],{\"x\":3,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":18,\"z\":1}:[\"GrassWaterConcave\",\"South\"],{\"x\":2,\"y\":20,\"z\":2}:[\"GrassPathSplit\",\"West\"],{\"x\":24,\"y\":17,\"z\":2}:[\"Tree\",\"South\"],{\"x\":9,\"y\":17,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":5,\"y\":19,\"z\":1}:[\"GrassWaterConcave\",\"East\"],{\"x\":2,\"y\":0,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":19,\"y\":7,\"z\":1}:[\"GrassPathBend\",\"South\"],{\"x\":13,\"y\":3,\"z\":1}:[\"GrassPathBend\",\"South\"],{\"x\":23,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":18,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":2,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":15,\"y\":20,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":6,\"y\":5,\"z\":1}:[\"GrassPathSplit\",\"North\"],{\"x\":4,\"y\":21,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":10,\"y\":21,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":22,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":18,\"z\":2}:[\"GrassRiverBridge\",\"East\"],{\"x\":7,\"y\":23,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":10,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":2,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":14,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":12,\"y\":22,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":23,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":27,\"z\":2}:[\"Trees\",\"South\"],{\"x\":19,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":10,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":21,\"y\":7,\"z\":2}:[\"BuildingDoorBeige\",\"South\"],{\"x\":7,\"y\":4,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":11,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":10,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":26,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":6,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":22,\"y\":19,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":2,\"y\":8,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":20,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":11,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":1,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":17,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":20,\"z\":1}:[\"GrassRiverSplit\",\"North\"],{\"x\":11,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":4,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":22,\"y\":6,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":11,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":23,\"z\":2}:[\"Trees\",\"South\"],{\"x\":2,\"y\":16,\"z\":2}:[\"GrassPath\",\"South\"],{\"x\":10,\"y\":5,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":18,\"y\":26,\"z\":2}:[\"CastleCorner\",\"East\"],{\"x\":21,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":2,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":20,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":1,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":16,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":17,\"z\":1}:[\"GrassWaterConvex\",\"North\"],{\"x\":11,\"y\":20,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":19,\"y\":13,\"z\":1}:[\"GrassWaterRiver\",\"South\"],{\"x\":6,\"y\":19,\"z\":1}:[\"GrassWater\",\"East\"],{\"x\":1,\"y\":0,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":19,\"y\":9,\"z\":1}:[\"GrassWaterConcave\",\"West\"],{\"x\":13,\"y\":6,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":1,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":19,\"z\":2}:[\"GrassSlope\",\"East\"],{\"x\":17,\"y\":8,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":15,\"y\":8,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":1,\"y\":0,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":19,\"y\":16,\"z\":1}:[\"GrassRiverSplit\",\"South\"],{\"x\":19,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":8,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":10,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":15,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":21,\"y\":18,\"z\":2}:[\"CastleWall\",\"North\"],{\"x\":24,\"y\":27,\"z\":1}:[\"GrassCorner\",\"South\"],{\"x\":18,\"y\":24,\"z\":2}:[\"CastleGate\",\"East\"],{\"x\":20,\"y\":0,\"z\":2}:[\"Trees\",\"South\"],{\"x\":15,\"y\":2,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":1,\"y\":7,\"z\":2}:[\"Trees\",\"East\"],{\"x\":19,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":7,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":6,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":19,\"z\":3}:[\"CastleTowerPurple\",\"South\"],{\"x\":24,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":2,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":21,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":15,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":8,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":15,\"y\":9,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":2,\"y\":25,\"z\":2}:[\"StructureArch\",\"West\"],{\"x\":13,\"y\":17,\"z\":1}:[\"GrassPathBend\",\"North\"],{\"x\":13,\"y\":0,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":9,\"y\":14,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":13,\"y\":19,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":24,\"y\":18,\"z\":2}:[\"CastleCorner\",\"West\"],{\"x\":5,\"y\":15,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":24,\"y\":24,\"z\":2}:[\"CastelWindow\",\"West\"],{\"x\":3,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":4,\"y\":22,\"z\":2}:[\"GrassSlopeConvex\",\"North\"],{\"x\":3,\"y\":4,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":6,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":18,\"y\":14,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":10,\"y\":3,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":24,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":16,\"z\":1}:[\"GrassWater\",\"South\"],{\"x\":4,\"y\":18,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":24,\"y\":7,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":3,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":7,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":18,\"z\":1}:[\"GrassWaterConvex\",\"South\"],{\"x\":6,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":25,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":15,\"y\":10,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":17,\"y\":6,\"z\":1}:[\"GrassRiverBend\",\"West\"],{\"x\":8,\"y\":12,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":19,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":4,\"z\":2}:[\"BuildingDoorWindowsBeige\",\"North\"],{\"x\":23,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":18,\"z\":2}:[\"CastleCorner\",\"North\"],{\"x\":19,\"y\":14,\"z\":1}:[\"GrassRiverBridge\",\"South\"],{\"x\":15,\"y\":7,\"z\":1}:[\"GrassPathBend\",\"North\"],{\"x\":7,\"y\":10,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":15,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":1,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":20,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":0,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":12,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":13,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":23,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":10,\"z\":2}:[\"BuildingDoorWindowsBeige\",\"North\"],{\"x\":6,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":21,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":18,\"y\":6,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":16,\"z\":2}:[\"GrassSlopeConcave\",\"East\"],{\"x\":9,\"y\":21,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":12,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":27,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":20,\"y\":20,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":5,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":2,\"z\":1}:[\"GrassRiverBridge\",\"East\"],{\"x\":23,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":8,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":19,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":10,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":8,\"y\":15,\"z\":1}:[\"GrassWater\",\"West\"],{\"x\":21,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":19,\"z\":2}:[\"RocksGrass\",\"East\"],{\"x\":1,\"y\":17,\"z\":3}:[\"BuildingDoorWindows\",\"East\"],{\"x\":22,\"y\":24,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":15,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":21,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":22,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":22,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":4,\"y\":9,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":13,\"y\":24,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":18,\"y\":4,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":11,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":14,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":24,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":4,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":17,\"z\":2}:[\"GrassSlope\",\"East\"],{\"x\":16,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":16,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":21,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":20,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":4,\"y\":24,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":19,\"y\":15,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":3,\"y\":1,\"z\":2}:[\"CastleGate\",\"North\"],{\"x\":18,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":14,\"z\":2}:[\"Trees\",\"East\"],{\"x\":23,\"y\":26,\"z\":2}:[\"CastleWall\",\"South\"],{\"x\":2,\"y\":3,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":17,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":23,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":2,\"y\":5,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":24,\"y\":19,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":8,\"y\":23,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":21,\"y\":19,\"z\":2}:[\"BuildingDoor\",\"East\"],{\"x\":4,\"y\":4,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":3,\"y\":2,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":15,\"y\":11,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":23,\"y\":25,\"z\":2}:[\"BuildingWindows\",\"South\"],{\"x\":7,\"y\":5,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":18,\"y\":10,\"z\":1}:[\"WaterCenter\",\"West\"],{\"x\":2,\"y\":3,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":11,\"y\":21,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":4,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":15,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":21,\"y\":2,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":6,\"y\":1,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":2,\"y\":24,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":21,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":8,\"y\":16,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":17,\"y\":0,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":2,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":5,\"y\":27,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":1,\"y\":26,\"z\":2}:[\"Tree\",\"South\"],{\"x\":7,\"y\":8,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":6,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":2,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":18,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":9,\"y\":15,\"z\":1}:[\"GrassWaterRiver\",\"North\"],{\"x\":11,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":7,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":21,\"y\":12,\"z\":2}:[\"Trees\",\"South\"],{\"x\":11,\"y\":24,\"z\":2}:[\"StructureArch\",\"West\"],{\"x\":16,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":22,\"z\":2}:[\"GrassCenter\",\"West\"],{\"x\":6,\"y\":4,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":22,\"y\":17,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":24,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":15,\"z\":1}:[\"GrassPathBend\",\"South\"],{\"x\":23,\"y\":16,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":7,\"y\":17,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":6,\"y\":16,\"z\":1}:[\"GrassWater\",\"North\"],{\"x\":8,\"y\":4,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":15,\"y\":3,\"z\":1}:[\"GrassRiverBend\",\"North\"],{\"x\":3,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":4,\"y\":4,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":24,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":11,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":24,\"z\":1}:[\"GrassPathSplit\",\"North\"],{\"x\":17,\"y\":1,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":20,\"y\":19,\"z\":2}:[\"CastleWall\",\"West\"],{\"x\":7,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":2,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":19,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":12,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":2,\"y\":4,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":18,\"y\":11,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":24,\"y\":22,\"z\":2}:[\"CastelWindow\",\"West\"],{\"x\":24,\"y\":25,\"z\":2}:[\"CastleWall\",\"West\"],{\"x\":4,\"y\":16,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":7,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":24,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":21,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":4,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":9,\"y\":0,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":18,\"y\":16,\"z\":1}:[\"GrassRiverBend\",\"North\"],{\"x\":16,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":24,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":3,\"y\":4,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":5,\"y\":18,\"z\":1}:[\"GrassWaterRiver\",\"East\"],{\"x\":2,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":0,\"z\":1}:[\"GrassCorner\",\"West\"],{\"x\":6,\"y\":2,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":12,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":16,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":5,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":7,\"y\":21,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":16,\"y\":25,\"z\":2}:[\"Trees\",\"South\"],{\"x\":2,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":20,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":13,\"z\":1}:[\"GrassWaterConcave\",\"South\"],{\"x\":22,\"y\":26,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":4,\"y\":3,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":2,\"y\":11,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":18,\"z\":2}:[\"BuildingDoorBeige\",\"East\"],{\"x\":19,\"y\":20,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":14,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":10,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":17,\"y\":26,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":14,\"y\":17,\"z\":1}:[\"GrassPathBend\",\"South\"],{\"x\":3,\"y\":23,\"z\":2}:[\"GrassSlopeConvex\",\"North\"],{\"x\":18,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":1,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":16,\"y\":14,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":1,\"y\":25,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":18,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":24,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":5,\"y\":16,\"z\":2}:[\"RocksGrass\",\"East\"],{\"x\":14,\"y\":4,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":1,\"y\":8,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":1,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":2,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":17,\"y\":22,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":11,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":19,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":2,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":9,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":7,\"y\":18,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":4,\"y\":2,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":9,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":16,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":7,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":16,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":24,\"y\":20,\"z\":2}:[\"CastelWindow\",\"West\"],{\"x\":4,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":17,\"z\":2}:[\"GrassPathSplit\",\"West\"],{\"x\":3,\"y\":0,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":17,\"y\":24,\"z\":1}:[\"GrassRiverBridge\",\"South\"],{\"x\":1,\"y\":18,\"z\":2}:[\"GrassRiverEnd\",\"East\"],{\"x\":10,\"y\":17,\"z\":1}:[\"GrassWater\",\"South\"],{\"x\":24,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":15,\"z\":2}:[\"GrassCenter\",\"North\"],{\"x\":17,\"y\":14,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":5,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":2,\"y\":22,\"z\":2}:[\"GrassPath\",\"South\"],{\"x\":3,\"y\":13,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":1,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":25,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":14,\"y\":2,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":4,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":9,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":14,\"y\":19,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":17,\"y\":2,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":5,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":1,\"y\":21,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":14,\"z\":2}:[\"GrassSlopeConvex\",\"East\"],{\"x\":21,\"y\":16,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":4,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":1,\"y\":19,\"z\":3}:[\"RocksGrass\",\"East\"],{\"x\":8,\"y\":22,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":8,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":18,\"y\":12,\"z\":1}:[\"GrassWater\",\"North\"],{\"x\":19,\"y\":23,\"z\":2}:[\"BuildingDoorWindows\",\"North\"],{\"x\":20,\"y\":20,\"z\":2}:[\"CastelWindow\",\"West\"],{\"x\":10,\"y\":23,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":2,\"y\":27,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":11,\"y\":7,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":20,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":26,\"z\":2}:[\"CastelWindow\",\"South\"],{\"x\":4,\"y\":1,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":2,\"y\":23,\"z\":2}:[\"GrassPathSlope\",\"North\"],{\"x\":9,\"y\":3,\"z\":1}:[\"GrassPathSplit\",\"East\"],{\"x\":19,\"y\":24,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":23,\"y\":18,\"z\":2}:[\"CastleWall\",\"South\"],{\"x\":23,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":6,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":23,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":16,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":1,\"z\":1}:[\"GrassRiverBend\",\"South\"],{\"x\":7,\"y\":12,\"z\":1}:[\"GrassRiverBend\",\"East\"],{\"x\":4,\"y\":4,\"z\":2}:[\"Trees\",\"East\"],{\"x\":1,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":9,\"y\":23,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":6,\"y\":24,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":16,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":0,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":8,\"y\":19,\"z\":1}:[\"GrassWaterConcave\",\"South\"],{\"x\":18,\"y\":3,\"z\":1}:[\"GrassRiverBend\",\"South\"],{\"x\":17,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":6,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":13,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":13,\"y\":11,\"z\":1}:[\"GrassPathCrossing\",\"West\"],{\"x\":20,\"y\":21,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":8,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":1,\"y\":17,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":13,\"y\":8,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":13,\"y\":20,\"z\":1}:[\"GrassRiverBridge\",\"East\"],{\"x\":3,\"y\":3,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":17,\"y\":23,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":5,\"y\":9,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":14,\"y\":11,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":9,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":17,\"z\":1}:[\"GrassWaterConcave\",\"North\"],{\"x\":7,\"y\":22,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":15,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":16,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":4,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":20,\"z\":2}:[\"GrassSlope\",\"East\"],{\"x\":16,\"y\":11,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":12,\"y\":21,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":21,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":21,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":4,\"y\":11,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":8,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":16,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":4,\"y\":14,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":10,\"y\":20,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":13,\"y\":2,\"z\":1}:[\"GrassPathBend\",\"North\"],{\"x\":23,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":7,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":12,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":17,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":8,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":12,\"y\":23,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":24,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":0,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":15,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":18,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":9,\"y\":22,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":24,\"y\":23,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":11,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":9,\"y\":5,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":23,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":11,\"z\":1}:[\"GrassRiverBridge\",\"South\"],{\"x\":1,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":5,\"y\":8,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":7,\"y\":24,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":19,\"y\":10,\"z\":1}:[\"GrassWaterConvex\",\"West\"],{\"x\":9,\"y\":20,\"z\":1}:[\"GrassRiverBend\",\"East\"],{\"x\":22,\"y\":18,\"z\":2}:[\"CastleGate\",\"South\"],{\"x\":4,\"y\":18,\"z\":2}:[\"GrassRiverSlope\",\"East\"],{\"x\":10,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":5,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":12,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":6,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":14,\"y\":14,\"z\":1}:[\"GrassPathSplit\",\"East\"],{\"x\":16,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":21,\"z\":2}:[\"GrassCenter\",\"West\"],{\"x\":8,\"y\":5,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":9,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":16,\"y\":3,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":20,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":15,\"z\":2}:[\"GrassPathBend\",\"North\"],{\"x\":18,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":23,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":1,\"y\":23,\"z\":2}:[\"GrassSlope\",\"North\"],{\"x\":22,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":25,\"z\":1}:[\"GrassPathSplit\",\"West\"],{\"x\":7,\"y\":0,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":2,\"y\":1,\"z\":2}:[\"CastelWindow\",\"South\"],{\"x\":2,\"y\":1,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":21,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":10,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":1,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":14,\"y\":0,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":1,\"y\":1,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":16,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":13,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":18,\"y\":2,\"z\":1}:[\"GrassRiverBend\",\"North\"],{\"x\":10,\"y\":15,\"z\":1}:[\"GrassWaterConcave\",\"West\"],{\"x\":14,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":12,\"y\":19,\"z\":2}:[\"Tree\",\"South\"],{\"x\":3,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":8,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":9,\"z\":1}:[\"GrassRiverBend\",\"East\"],{\"x\":2,\"y\":7,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":11,\"y\":22,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":6,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":13,\"y\":1,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":10,\"y\":2,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":15,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":11,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":18,\"y\":19,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":19,\"y\":22,\"z\":2}:[\"CastleWall\",\"North\"],{\"x\":24,\"y\":21,\"z\":2}:[\"CastleWall\",\"East\"],{\"x\":11,\"y\":8,\"z\":2}:[\"Tree\",\"South\"],{\"x\":2,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":19,\"z\":2}:[\"GrassCenter\",\"East\"],{\"x\":23,\"y\":12,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":12,\"y\":25,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":1,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":15,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":26,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":10,\"y\":24,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":3,\"y\":5,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":7,\"y\":1,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":13,\"y\":23,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":23,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":16,\"z\":1}:[\"GrassRiverBridge\",\"East\"],{\"x\":3,\"y\":22,\"z\":2}:[\"GrassSlopeConcave\",\"North\"],{\"x\":6,\"y\":5,\"z\":2}:[\"StructureArch\",\"East\"],{\"x\":12,\"y\":20,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":22,\"y\":20,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":10,\"y\":22,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":4,\"y\":17,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":15,\"y\":16,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":4,\"z\":1}:[\"GrassPathBend\",\"East\"],{\"x\":3,\"y\":3,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":7,\"y\":15,\"z\":1}:[\"GrassWater\",\"West\"],{\"x\":5,\"y\":0,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":20,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":24,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":19,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":16,\"y\":12,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":9,\"y\":12,\"z\":1}:[\"GrassRiverBend\",\"West\"],{\"x\":1,\"y\":15,\"z\":3}:[\"Tree\",\"East\"],{\"x\":21,\"y\":25,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":18,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":7,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":9,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":12,\"y\":0,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":14,\"y\":7,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":11,\"z\":1}:[\"GrassPathBend\",\"North\"],{\"x\":6,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":24,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":16,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":12,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":7,\"y\":1,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":15,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":21,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":6,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":3,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":7,\"z\":1}:[\"GrassRiverBridge\",\"South\"],{\"x\":5,\"y\":1,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":3,\"y\":1,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":21,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":1,\"y\":3,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":1,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":13,\"z\":1}:[\"GrassPathBend\",\"West\"],{\"x\":20,\"y\":8,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":21,\"z\":2}:[\"GrassPath\",\"East\"],{\"x\":14,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":20,\"z\":2}:[\"BuildingDoorWindowsBeige\",\"North\"],{\"x\":21,\"y\":7,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":22,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":15,\"z\":1}:[\"GrassWaterConcave\",\"North\"],{\"x\":5,\"y\":3,\"z\":0}:[\"StructureLow\",\"North\"],{\"x\":6,\"y\":18,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":3,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":11,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":4,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":4,\"y\":3,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":2,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":24,\"y\":26,\"z\":2}:[\"CastleCorner\",\"South\"],{\"x\":8,\"y\":9,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":24,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":4,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":21,\"y\":11,\"z\":1}:[\"GrassWaterConcave\",\"South\"],{\"x\":2,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":22,\"z\":2}:[\"CastleCorner\",\"South\"],{\"x\":22,\"y\":26,\"z\":2}:[\"CastleGate\",\"South\"],{\"x\":17,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":12,\"z\":2}:[\"Trees\",\"East\"],{\"x\":5,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":7,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":7,\"y\":25,\"z\":2}:[\"Tree\",\"South\"],{\"x\":7,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":6,\"y\":11,\"z\":1}:[\"GrassPathSplit\",\"South\"],{\"x\":16,\"y\":2,\"z\":1}:[\"GrassPath\",\"West\"],{\"x\":23,\"y\":18,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":11,\"z\":1}:[\"GrassWaterConcave\",\"East\"],{\"x\":19,\"y\":26,\"z\":2}:[\"CastleWall\",\"South\"],{\"x\":8,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":19,\"y\":21,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":3,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":17,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":25,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":3,\"y\":0,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":20,\"y\":1,\"z\":1}:[\"GrassRiverBend\",\"North\"],{\"x\":14,\"y\":20,\"z\":1}:[\"GrassRiver\",\"West\"],{\"x\":2,\"y\":9,\"z\":2}:[\"RocksGrass\",\"South\"],{\"x\":2,\"y\":1,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":20,\"y\":5,\"z\":2}:[\"Tree\",\"South\"],{\"x\":21,\"y\":10,\"z\":1}:[\"GrassWaterConcave\",\"West\"],{\"x\":9,\"y\":6,\"z\":1}:[\"GrassCenter\",\"North\"],{\"x\":6,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":19,\"y\":6,\"z\":1}:[\"GrassPathSplit\",\"East\"],{\"x\":24,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":13,\"y\":4,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":9,\"y\":19,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":24,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":5,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":10,\"y\":27,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":20,\"y\":23,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":21,\"y\":13,\"z\":2}:[\"BuildingDoorBeige\",\"North\"],{\"x\":19,\"y\":5,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":8,\"y\":17,\"z\":1}:[\"WaterCenter\",\"North\"],{\"x\":12,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":4,\"y\":21,\"z\":2}:[\"GrassPathSlope\",\"East\"],{\"x\":4,\"y\":23,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":7,\"y\":2,\"z\":1}:[\"GrassRiverBend\",\"South\"],{\"x\":16,\"y\":9,\"z\":1}:[\"GrassWaterConcave\",\"North\"],{\"x\":22,\"y\":27,\"z\":1}:[\"GrassPath\",\"North\"],{\"x\":2,\"y\":21,\"z\":2}:[\"GrassPathSplit\",\"East\"],{\"x\":6,\"y\":10,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":7,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":21,\"z\":1}:[\"GrassPath\",\"East\"],{\"x\":18,\"y\":9,\"z\":1}:[\"GrassWater\",\"West\"],{\"x\":22,\"y\":15,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":19,\"y\":27,\"z\":2}:[\"Tree\",\"South\"],{\"x\":20,\"y\":22,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":23,\"y\":12,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":10,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":15,\"y\":1,\"z\":2}:[\"BuildingDoorBeige\",\"North\"],{\"x\":1,\"y\":13,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":8,\"y\":14,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":2,\"y\":19,\"z\":2}:[\"GrassPath\",\"South\"],{\"x\":19,\"y\":26,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":11,\"y\":4,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":3,\"y\":15,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":17,\"y\":3,\"z\":1}:[\"GrassRiverBridge\",\"West\"],{\"x\":18,\"y\":18,\"z\":1}:[\"GrassRiver\",\"South\"],{\"x\":2,\"y\":19,\"z\":1}:[\"GrassCenter\",\"South\"],{\"x\":5,\"y\":10,\"z\":2}:[\"Trees\",\"East\"],{\"x\":6,\"y\":2,\"z\":1}:[\"GrassRiver\",\"East\"],{\"x\":7,\"y\":2,\"z\":0}:[\"StructureLow\",\"South\"],{\"x\":20,\"y\":10,\"z\":1}:[\"GrassWater\",\"West\"],{\"x\":3,\"y\":8,\"z\":1}:[\"GrassRiverBend\",\"West\"],{\"x\":22,\"y\":18,\"z\":1}:[\"GrassPath\",\"South\"],{\"x\":11,\"y\":24,\"z\":1}:[\"GrassPathSplit\",\"North\"],{\"x\":2,\"y\":22,\"z\":1}:[\"GrassCenter\",\"West\"],{\"x\":3,\"y\":20,\"z\":1}:[\"GrassCenter\",\"East\"],{\"x\":13,\"y\":12,\"z\":1}:[\"GrassPath\",\"South\"]},\"cam\":{\"center\":{\"x\":-33.666626,\"y\":-366.1667},\"scale\":0.29999992},\"show_inv\":false,\"inv\":{\"infinite_items\":true,\"contents\":{\"StructureHigh\":1,\"StructureArch\":1,\"RoofPointPurple\":1,\"GrassWaterConvex\":1,\"RoofRoundGreen\":1,\"RocksDirt\":1,\"CastleTowerBrown\":1,\"GrassCorner\":1,\"GrassRiver\":1,\"RoofPointGreen\":1,\"RoofRoundedGreen\":1,\"GrassWaterRiver\":1,\"RoofPointBrown\":1,\"RoofChurchPurple\":1,\"Trees\":1,\"BuildingWindowsBeige\":1,\"GrassRiverBridge\":1,\"GrassRiverCrossing\":1,\"Bridge\":1,\"GrassPath\":1,\"BuildingWindows\":1,\"WaterFall\":1,\"GrassPathEnd\":1,\"BuildingCenter\":1,\"WaterCenter\":1,\"BuildingDoorWindows\":1,\"RoofGableBrown\":1,\"GrassPathSlope\":1,\"RoofGableGreen\":1,\"GrassRiverEndSquare\":1,\"BuildingDoor\":1,\"RoofGablePurple\":1,\"CastleTowerPurple\":1,\"GrassRiverCorner\":1,\"GrassCenter\":1,\"GrassRiverSlope\":1,\"BuildingCorner\":1,\"GrassPathCorner\":1,\"GrassWater\":1,\"StructureLow\":1,\"RoofChuchBrown\":1,\"BuildingWindow\":1,\"RoofRoundBeige\":1,\"RoofPointBeige\":1,\"RoofSlantGreen\":1,\"RoofRoundedBeige\":1,\"GrassSlope\":1,\"CastleTower\":1,\"CastleTowerBeige\":1,\"GrassPathSplit\":1,\"CastelWindow\":1,\"RoofChurchGreen\":1,\"CastleBend\":1,\"RocksGrass\":1,\"CastleGateOpen\":1,\"RoofRoundBrown\":1,\"Tree\":1,\"GrassPathBend\":1,\"GrassRiverSplit\":1,\"CastleCenter\":1,\"CastleCorner\":1,\"CastleGate\":1,\"BuildingCenterBeige\":1,\"GrassWaterConcave\":1,\"RoofSlantBrown\":1,\"BuildingDoorWindowsBeige\":1,\"RoofSlantPurple\":1,\"GrassSlopeConvex\":1,\"DirtLow\":1,\"RoofRoundedPurple\":1,\"RoofSlantBeige\":1,\"GrassRiverBend\":1,\"RoofRoundPurple\":1,\"GrassRiverEnd\":1,\"RoofRoundedBrown\":1,\"GrassPathCrossing\":1,\"CastleWall\":1,\"BuildingDoorBeige\":1,\"CastleSlope\":1,\"BuildingCornerBeige\":1,\"RoofChurchBeige\":1,\"CastleTowerGreen\":1,\"RoofGableBeige\":1,\"DirtCenter\":1,\"BuildingWindowBeige\":1,\"GrassSlopeConcave\":1,\"GrassPathEndSquare\":1},\"selected\":\"RocksGrass\",\"direction\":\"South\",\"page\":7}}"}}
\ No newline at end of file

R src/blocks.rs => src/game/blocks.rs +152 -83
@@ 2,9 2,83 @@ use macroquad::prelude::*;
use crate::textures::DirectionalTexture;
use nanoserde::{DeJson, SerJson};
use std::collections::HashMap;
use crate::screens::build::{
    TEXTURE_WIDTH,
    TEXTURE_Y_WHITESPACE,
    TEXTURE_DEPTH,
    TEXTURE_INNER_HEIGHT
};

static mut CACHE: Option<HashMap<Block, DirectionalTexture>> = None;

/// A block's face
///   /'\
///  / A \
/// |\   /|
/// | \./ |
/// \ B|C |
///  \.|./
///  A - Top
///  B- Left
///  C -Right
#[derive(Debug)]
pub enum Face {
    Top,
    Left,
    Right
}
impl Face {
    /// gets the face (if any) that was clicked
    /// coordinates have to be normalized,
    /// the top left should be (0,0)
    pub fn from_xy(x: f32, y: f32, scale: f32) -> Option<Self> {

        // test A face
        {
            let ox = (TEXTURE_WIDTH * scale / 2.0 - x).abs();
            let oy = (TEXTURE_DEPTH * scale / 2.0 + TEXTURE_Y_WHITESPACE * scale - y).abs();

            let y_max = TEXTURE_DEPTH * scale / 2.0;
            let x_max = TEXTURE_WIDTH * scale / 2.0;

            let grow = (0.0 - y_max)/(x_max - 0.0);
            let cy = grow * ox + y_max;

            if cy >= oy {
                return Some(Face::Top);
            }
        }

        // test B and C faces
        {
            let ox = TEXTURE_WIDTH * scale / 2.0 - x;
            let oy = y - (TEXTURE_DEPTH * scale / 2.0 + TEXTURE_Y_WHITESPACE * scale);

            let ox_abs = ox.abs();

            let y_max = TEXTURE_DEPTH * scale / 2.0;
            let x_max = TEXTURE_WIDTH * scale / 2.0;

            let grow = (0.0 - y_max)/(x_max - 0.0);

            let y_top = grow * ox_abs + y_max;
            let y_bottom = grow * ox_abs + y_max + TEXTURE_INNER_HEIGHT * scale;

            if oy >= y_top && oy <= y_bottom {
                if x > TEXTURE_WIDTH * scale / 2.0 {
                    // C face
                    return Some(Face::Right);
                } else {
                    // B face
                    return Some(Face::Left);
                }
            }
        }

        None
    }
}

#[derive(Eq, Hash, Clone, PartialEq, SerJson, DeJson)]
pub enum Block {
    Bridge,


@@ 111,9 185,9 @@ impl Block {
    pub async fn get_texture(&self) -> DirectionalTexture {

        unsafe {
        if CACHE.is_none() {
            CACHE = Some(HashMap::new());
        }
            if CACHE.is_none() {
                CACHE = Some(HashMap::new());
            }
            if let Some(map) = &CACHE {
                if let Some(dt) = map.get(self) {
                    return dt.clone();


@@ 319,92 393,87 @@ impl Block {
            Block::WaterFall
        ]
    }
}
impl Default for Block {
    fn default() -> Self {
        Block::StructureHigh
    }
}

/// Blocks can belong to multiple categories
/// and may be displayed by category in inventory
#[derive(Clone, PartialEq, Eq, DeJson, SerJson)]
pub enum Category {
    /// Everything releated to buildings
    /// i.e. walls, levels, roofs
    Building,
    /// floor tiling
    /// i.e grass, dirt
    Blocks,
    /// category containing roofs for buildings
    Roofs,
    /// Trees & Bushes
    Plants,
    /// River, Lake and water
    Water,
    /// ungrouped blocks
    /// i.e bridges
    Other,
    /// all items
    All
}
impl Category {
    /// get a list of all categories
    pub fn all() -> Vec<Category> {
        vec![
            Category::All,
            Category::Building,
            Category::Blocks,
            Category::Roofs,
            Category::Plants,
            Category::Water,
            Category::Other,
        ]
    }
    /// get name of category
    pub fn get_name(&self) -> &str {
        match self {
            Block::Bridge => "Bridge",
            Block::BuildingDoor | Block::BuildingDoorBeige => "Building door",
            Block::BuildingDoorWindows | Block::BuildingDoorWindowsBeige => "Building door",
            Block::BuildingCenter | Block::BuildingCenterBeige => "Building",
            Block::BuildingCorner | Block::BuildingCornerBeige => "Building corner",
            Block::BuildingWindow | Block::BuildingWindowBeige => "Building window",
            Block::BuildingWindows | Block::BuildingWindowsBeige => "Building windows",
            Block::CastleBend => "Castle bend",
            Block::CastleCenter | Block::CastleCorner => "Castle",
            Block::CastleGate => "Castle Gate",
            Block::CastleGateOpen => "Open castle gate",
            Block::CastleSlope => "Castle slope",
            Block::CastleTower
                | Block::CastleTowerBeige
                | Block::CastleTowerBrown
                | Block::CastleTowerGreen
                | Block::CastleTowerPurple => "Castle tower",
            Block::CastleWall => "Castle wall",
            Block::CastelWindow => "Castle window",

            Block::DirtCenter | Block::DirtLow => "Dirt",
            Block::GrassCenter | Block::GrassCorner => "Grass",
            Block::GrassPath
                | Block::GrassPathBend
                | Block::GrassPathCorner
                | Block::GrassPathCrossing
                | Block::GrassPathEnd
                | Block::GrassPathSplit
                | Block::GrassPathEndSquare => "Grass path",
            Block::GrassPathSlope => "Grass slope",
            Block::GrassRiver
                | Block::GrassRiverBend
                | Block::GrassRiverBridge
                | Block::GrassRiverCorner
                | Block::GrassRiverCrossing
                | Block::GrassRiverEnd
                | Block::GrassRiverEndSquare
                | Block::GrassRiverSplit
                | Block::GrassRiverSlope => "River",
            Block::GrassSlope
                | Block::GrassSlopeConcave
                | Block::GrassSlopeConvex => "Grass slope",
            Block::GrassWater
                | Block::GrassWaterConcave
                | Block::GrassWaterConvex
                | Block::GrassWaterRiver => "Grass water",
            Block::RocksDirt | Block::RocksGrass => "Rocks",
            Block::RoofChurchBeige
                | Block::RoofChuchBrown
                | Block::RoofChurchGreen
                | Block::RoofChurchPurple => "Church roof",
            Block::RoofGableBeige
                | Block::RoofGableBrown
                | Block::RoofGableGreen
                | Block::RoofGablePurple => "Gable roof",
            Block::RoofPointBeige
                | Block::RoofPointBrown
                | Block::RoofPointGreen
                | Block::RoofPointPurple => "Point roof",
            Block::RoofRoundBeige
                | Block::RoofRoundBrown
                | Block::RoofRoundGreen
                | Block::RoofRoundPurple => "Round roof",
            Block::RoofRoundedBeige
                | Block::RoofRoundedBrown
                | Block::RoofRoundedGreen
                | Block::RoofRoundedPurple => "Rounded roof",
            Block::RoofSlantBeige
                | Block::RoofSlantBrown
                | Block::RoofSlantGreen
                | Block::RoofSlantPurple => "Slant roof",
            Block::StructureArch
                | Block::StructureHigh
                | Block::StructureLow => "Scaffold",
            Block::Trees => "Trees",
            Block::Tree => "Tree",
            Block::WaterCenter => "Water",
            Block::WaterFall => "Waterfall"
            Category::Building => "Buildings",
            Category::Blocks => "Blocks",
            Category::Roofs => "Roofs",
            Category::Plants => "Plants",
            Category::Water => "Water & River",
            Category::Other => "Other",
            Category::All => "All"
        }
    }
    /// get a list of all blocks from this category
    pub fn get_blocks(&self) -> Vec<Block> {
        match self {
            Category::Building => vec![

            ],
            Category::Blocks => vec![

            ],
            Category::Roofs => vec![

            ],
            Category::Plants => vec![

            ],
            Category::Water => vec![

            ],
            Category::Other => vec![

            ],
            Category::All => Block::all()
        }
    }
}
impl Default for Block {
impl Default for Category {
    fn default() -> Self {
        Block::StructureHigh
        Category::All
    }
}

A src/game/camera.rs => src/game/camera.rs +19 -0
@@ 0,0 1,19 @@
use nanoserde::{DeJson, SerJson};

/// the game camera
#[derive(SerJson, DeJson, Clone)]
pub struct Camera {
    /// the centered pixel
    pub center: (f32, f32),
    /// scales the texture
    pub scale: f32
}
impl Camera {
    /// initialize new camera
    pub fn new() -> Self {
        Self {
            center: (0.0, 0.0),
            scale: 1.0
        }
    }
}

A src/game/inventory.rs => src/game/inventory.rs +43 -0
@@ 0,0 1,43 @@
use crate::game::types::Direction;
use crate::game::blocks::{
    Block,
    Category
};
use std::collections::HashMap;
use nanoserde::{DeJson, SerJson};

/// The players inventory
#[derive(SerJson, DeJson, Clone)]
pub struct Inventory {
    /// items in inventory
    /// with their appropriate amount
    /// if amount is zero, the item should be removed
    /// if infinite_items is true, the amount wont decrease
    pub contents: HashMap<Block, usize>,
    /// which block is currently selected
    pub selected: Option<Block>,
    /// in which direction the block should be facing
    pub direction: Direction,
    /// category page currently being shown
    pub category: Category
}

impl Inventory {
    /// creates an empty inventory
    pub fn new() -> Self {
        Self {
            contents: HashMap::new(),
            selected: None,
            direction: Direction::North,
            category: Category::All
        }
    }
    /// adds an item to the inventory
    pub fn add_item(&mut self, block: &Block) {
        if let Some(slot) = self.contents.get_mut(&block) {
            *slot += 1;
        } else {
            self.contents.insert(block.clone(), 1);
        }
    }
}

A src/game/mod.rs => src/game/mod.rs +35 -0
@@ 0,0 1,35 @@
pub mod camera;
pub mod blocks;
pub mod inventory;
pub mod types;
pub mod world;

use crate::textures::AssetStore;
use crate::screens::Screen;

/// A basic Game component
/// which can be drawn
/// and has a handler for the event loop
/// NOTE: draw and ev_loop are tehcnically the same,
/// but the serve different purposes
pub trait GameComponent {
    /// Callback used to redraw the component
    async fn draw(&self, _assets: &AssetStore) {}
    /// Callback used to perform background tasks
    /// e.g. keyboard input
    /// NOTE: this is currently not being run asynchronously
    /// when running large operations, spawn a separate thread
    fn ev_loop(&mut self) -> GameEvent {
        GameEvent::None
    }
}

/// Events send from the event loop
pub enum GameEvent {
    /// Do nothing
    None,
    /// Exit game
    Quit,
    /// Change View
    ChangeScreen(Screen)
}

R src/types.rs => src/game/types.rs +0 -29
@@ 1,5 1,3 @@
use crate::textures::AssetStore;
use crate::Screen;
use nanoserde::{DeJson, SerJson};

/// three dimensional coordinate


@@ 41,30 39,3 @@ pub enum Direction {
    /// (bottom edge of screen)
    South
}

/// A basic Game component
/// which can be drawn
/// and has a handler for the event loop
/// NOTE: draw and ev_loop are tehcnically the same,
/// but the serve different purposes
pub trait GameComponent {
    /// Callback used to redraw the component
    async fn draw(&self, assets: &AssetStore) {}
    /// Callback used to perform background tasks
    /// e.g. keyboard input
    /// NOTE: this is currently not being run asynchronously
    /// when running large operations, spawn a separate thread
    fn ev_loop(&mut self) -> GameEvent {
        GameEvent::None
    }
}

/// Events send from the event loop
pub enum GameEvent {
    /// Do nothing
    None,
    /// Exit game
    Quit,
    /// Change View
    ChangeScreen(Screen)
}

R src/storage.rs => src/game/world.rs +125 -51
@@ 1,3 1,12 @@
use super::inventory::Inventory;
use super::camera::Camera;
use super::types::{
    Direction,
    Pos3
};
use super::blocks::Block;
use std::collections::HashMap;
use nanoserde::{SerJson, DeJson};
use directories_next::ProjectDirs;
use std::path::Path;
use std::fs::{


@@ 5,13 14,12 @@ use std::fs::{
    OpenOptions,
    read_dir
};
use crate::screens::build::BuildScreen;
use std::io::{
    BufReader,
    Read,
    Write
};
use nanoserde::{DeJson, SerJson};


/// returns the OS specific data dir as a string
/// if there is no such dir, None will be returned


@@ 25,70 33,136 @@ fn get_data_dir() -> Option<String> {
    None
}

/// returns a list of all world file names
pub fn get_world_list() -> Vec<String> {
    if let Some(dir) = get_data_dir() {
        if let Ok(rd) = &mut read_dir(dir) {
            let mut builder = Vec::new();

            while let Some(entry) = rd.next() {
                if let Ok(entry) = entry {
                    if let Ok(ftype) = entry.file_type() && ftype.is_file() {
                        if let Some(name) = entry.file_name().to_str() {
                            builder.push(name.to_string());
#[derive(SerJson, DeJson, Clone)]
pub struct World {
    /// the map itself
    pub grid: HashMap<Pos3, (Block, Direction)>,
    /// camera position
    pub cam: Camera,
    /// the players inventory
    pub inventory: Inventory,
    /// when set to true, the player has an infinite amount of items
    pub sandbox: bool,
}
impl World {
    pub fn new(sandbox: bool) -> Self {
        let mut inv = Inventory::new();

        if sandbox {
            for block in Block::all().iter() {
                inv.add_item(block);
            }
        }

        let mut map = HashMap::new();
        map.insert(Pos3::new(0, 0, 0), (Block::default(), Direction::North));

        Self {
            grid: map,
            cam: Camera::new(),
            inventory: inv,
            sandbox
        }
    }

    /// remove block from map
    /// and add item to inventory
    pub fn destroy_block(&mut self, pos: &Pos3) -> Option<(Block, Direction)> {
        if let Some(block) = self.grid.remove(pos) {
            self.inventory.add_item(&block.0);
            return Some(block.clone());
        }

        None
    }

    /// removes the selected block (one) from the inventory
    /// can be used to remove items
    /// or place items
    pub fn place_block(&mut self, pos: &Pos3, block: Block, dir: Direction) -> bool {
        if let Some(count) = self.inventory.contents.get_mut(&block) {
            // player has block
            if *count > 0 {
                if !self.sandbox {
                    *count -= 1;
                }

                self.grid.insert(pos.clone(), (block, dir));

                return true;
            }
        }

        false
    }

    /// returns a list of all world file names
    pub fn get_list() -> Vec<String> {
        if let Some(dir) = get_data_dir() {
            if let Ok(rd) = &mut read_dir(dir) {
                let mut builder = Vec::new();

                while let Some(entry) = rd.next() {
                    if let Ok(entry) = entry {
                        if let Ok(ftype) = entry.file_type() && ftype.is_file() {
                            if let Some(name) = entry.file_name().to_str() {
                                builder.push(name.to_string());
                            }
                        }
                    }
                }
            }

            return builder;
                return builder;
            }
        }
        Vec::new()
    }
    Vec::new()
}

/// tries loading a game state from a file
pub fn read_map(name: &str) -> Option<BuildScreen> {
    let existing_maps = get_world_list();
    if let Some(dir) = get_data_dir() && existing_maps.contains(&name.to_string()) {
        if let Ok(file) = File::open(Path::new(&dir).join(name)) {
            let mut buf_reader = BufReader::new(file);
            let mut contents = String::new();
            if buf_reader.read_to_string(&mut contents).is_ok() {
                match BuildScreen::deserialize_json(&contents) {
                    Ok(bscr) => return Some(bscr),
                    Err(e) => println!("{:?}", e)
    /// tries loading the world from a file
    pub fn from_disk(name: &str) -> Option<Self> {
        let existing_maps = Self::get_list();
        if let Some(dir) = get_data_dir() && existing_maps.contains(&name.to_string()) {
            if let Ok(file) = File::open(Path::new(&dir).join(name)) {
                let mut buf_reader = BufReader::new(file);
                let mut contents = String::new();
                if buf_reader.read_to_string(&mut contents).is_ok() {
                    match World::deserialize_json(&contents) {
                        Ok(bscr) => return Some(bscr),
                        Err(e) => println!("{:?}", e)
                    }
                } else {
                    println!("Faield to read file");
                }
            } else {
                println!("Faield to read file");
                println!("Failed to open file");
            }
        } else {
            println!("Failed to open file");
            println!("World not found");
        }
    } else {
        println!("World not found");

        None
    }

    None
}

/// tries saving the map to disk
pub fn save_map(name: &str, bscr: &BuildScreen) {
    if let Some(dir) = get_data_dir() {
        let json = BuildScreen::serialize_json(&bscr);

        let mut file =  OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(Path::new(&dir).join(name));

        if let Ok(file) = &mut file {
            if file.write_all(json.as_bytes()).is_err() {
                println!("Failed to write file");
            };
        } else {
            println!("Failed to open file")
    /// tries saving the map to disk
    pub fn to_disk(&self, name: &str) {
        if let Some(dir) = get_data_dir() {
            let json = World::serialize_json(self);

            let mut file =  OpenOptions::new()
                .write(true)
                .create(true)
                .truncate(true)
                .open(Path::new(&dir).join(name));

            if let Ok(file) = &mut file {
                if file.write_all(json.as_bytes()).is_err() {
                    println!("Failed to write file");
                };
            } else {
                println!("Failed to open file")
            }
        }
    }

}

M src/main.rs => src/main.rs +4 -7
@@ 2,29 2,26 @@
#![feature(let_chains)]
#![feature(async_fn_in_trait)]
#![feature(associated_type_defaults)]
#![feature(inherent_associated_types)]

use macroquad::prelude::*;

mod screens;
#[macro_use]
mod textures;
mod types;
mod blocks;
mod draw;
mod storage;
mod ui;
mod game;

#[cfg(feature = "multiplayer")]
mod p2p;

use types::{
use game::{
    GameComponent,
    GameEvent
};
use textures::AssetStore;
use screens::Screen;



#[macroquad::main("Little Town")]
async fn main() {
    let assets = AssetStore::init().await;

M src/p2p.rs => src/p2p.rs +11 -20
@@ 21,7 21,6 @@ use libp2p::{
    Transport
};
use std::{
    collections::HashMap,
    sync::{
        Arc,
        RwLock,


@@ 36,8 35,9 @@ use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::time::Duration;
use nanoserde::{SerJson, DeJson};
use crate::blocks::Block;
use crate::types::{
use crate::game::blocks::Block;
use crate::game::world::World;
use crate::game::types::{
    Pos3,
    Direction
};


@@ 68,20 68,10 @@ pub enum GameAction {
    /// event send when a block has been removed
    /// contains block and position
    RemoveBlock(Pos3, Block),
    /// event send when a peer joins the map
    /// contains the inventory
    /// including blocks and their count
    /// the second argument is true
    /// if the user has an infinite amount of items
    TransmitInventory(HashMap<Block, usize>, bool),
    /// one of the peers requested an inventory transmission
    RequestInventory,
    /// event send when a peer joins the map
    /// contains the map
    /// including blocks, their direction and position
    TransmitMap(HashMap<Pos3, (Block, Direction)>),
    /// one of the peers requested a map transmission
    RequestMap,
    /// broadcast the current world state to all peers
    TransmitWorld(World),
    /// a peer can request a world transmission
    RequestWorld,
}

enum ChannelEvent {


@@ 137,6 127,7 @@ impl Peer {

    /// adds a specific port to the setup
    /// has to be called before connect() is being called
    #[allow(dead_code)]
    pub fn with_port(mut self, port: usize) -> Self {
        self.port = Some(port);
        self


@@ 283,12 274,12 @@ impl Peer {
                                // hand on act to game instance
                                let may_process = if is_host {
                                    match act {
                                        GameAction::TransmitMap(_) | GameAction::TransmitInventory(_, _) => false,
                                        GameAction::TransmitWorld(_) => false,
                                        _ => true
                                    }
                                } else {
                                    match act {
                                        GameAction::RequestMap | GameAction::RequestInventory => false,
                                        GameAction::RequestWorld => false,
                                        _ => true
                                    }
                                };


@@ 308,7 299,7 @@ impl Peer {
                            if !is_host {
                                // request data
                                // in case host lost the request
                                for act in [ GameAction::RequestMap, GameAction::RequestInventory ] {
                                for act in [ GameAction::RequestWorld ] {
                                    let json = act.serialize_json();
                                    swarm
                                        .behaviour_mut()

M src/screens/build.rs => src/screens/build.rs +461 -456
@@ 1,19 1,23 @@
use macroquad::prelude::*;
use std::collections::HashMap;
use crate::textures::AssetStore;
use crate::types::{
use crate::game::{
    GameComponent,
    GameEvent,
    Pos3,
    Direction
};
use crate::blocks::Block;
use super::{
    inventory::Inventory,
    Screen,
    blocks::{Face, Block, Category},
    world::World,
    types::{
        Pos3,
        Direction
    }};
use crate::ui::{
    ItemFrame,
    ButtonEvent,
    Widget,
    CenteredText,
    TextButton
};
use crate::storage::save_map;
use nanoserde::{DeJson, SerJson};
use super::Screen;

#[cfg(feature = "multiplayer")]
use crate::p2p::{


@@ 21,115 25,23 @@ use crate::p2p::{
    GameAction
};

/// simple Vec2 implementation
/// because I need to derive Serialize and Deserialize
#[derive(Copy, Clone, SerJson, DeJson)]
struct CVec2 {
    x: f32,
    y: f32
}
impl CVec2 {
    fn new(x: f32, y: f32) -> Self {
        Self {
            x, y
        }
    }
}

/// the game camera
#[derive(SerJson, DeJson)]
struct Camera {
    /// the centered pixel
    center: CVec2,
    /// scales the texture
    scale: f32
}
impl Camera {
    /// initialize new camera
    fn new() -> Self {
        Self {
            center: CVec2::new(0.0, 0.0),
            scale: 1.0
        }
    }
}

/// image width
pub const TEXTURE_WIDTH: f32 = 256.0;
/// actual width inside ob cube
const TEXTURE_INNER_WIDTH: f32 = 223.0; // 216
pub const TEXTURE_INNER_WIDTH: f32 = 223.0; // 216
/// height of image
pub const TEXTURE_HEIGHT: f32 = 352.0;
/// empty space above texture
pub const TEXTURE_Y_WHITESPACE: f32 = 130.0;
/// actual height of cube
const TEXTURE_INNER_HEIGHT: f32 = 108.0; // 110
pub const TEXTURE_INNER_HEIGHT: f32 = 108.0; // 110
/// height of top surface
const TEXTURE_DEPTH: f32 = 100.0;

/// The screen of the basic build game
#[derive(SerJson, DeJson)]
pub struct BuildScreen {
    grid: HashMap<Pos3, (Block, Direction)>,
    cam: Camera,
    show_inv: bool,
    inv: Inventory,
    mouse_position: Option<(f32, f32)>,
    #[cfg(feature="multiplayer")]
    #[nserde(skip)]
    multiplayer: Option<Peer>,
    #[nserde(skip)]
    file_name: Option<String>
}
impl BuildScreen {
    pub fn new(file_name: &str) -> Self {
        let mut this = Self {
            grid: HashMap::new(),
            cam: Camera::new(),
            show_inv: false,
            inv: Inventory::new(),
            mouse_position: None,
            file_name: Some(file_name.to_string()),
            #[cfg(feature="multiplayer")]
            multiplayer: None
        };

        this.grid.insert(Pos3::new(0, 0, 0), (Block::default(), Direction::North));
pub const TEXTURE_DEPTH: f32 = 100.0;

        this
    }
    /// creates a new BuildScreen instance
    /// and starts the p2p multiplayer listener
    #[cfg(feature="multiplayer")]
    pub fn multiplayer() -> Self {
            Self {
                grid: HashMap::new(),
                cam: Camera::new(),
                show_inv: false,
                inv: Inventory::empty(),
                mouse_position: None,
                file_name: None,
                multiplayer: Some(Peer::client())
        }
    }

    pub fn with_file_name(self, file_name: &str) -> Self {
        Self {
            file_name: Some(file_name.to_string()),
            ..self
        }
    }

    #[cfg(feature="multiplayer")]
    pub fn as_host(self) -> Self {
        Self {
            multiplayer: Some(Peer::host()),
            ..self
        }
    }
}

fn get_screen_coords(pos: &Pos3, scale: f32, center: CVec2) -> (f32, f32) {
/// converts the 3 axis position of a block
/// to a x-y screen coordinate
fn get_screen_coords(pos: &Pos3, scale: f32, center: (f32, f32)) -> (f32, f32) {
    let w_i = TEXTURE_INNER_WIDTH * scale;
    let width = TEXTURE_WIDTH * scale;
    let height = TEXTURE_HEIGHT * scale;


@@ 140,430 52,523 @@ fn get_screen_coords(pos: &Pos3, scale: f32, center: CVec2) -> (f32, f32) {

    let x = screen_width() / 2.0
        + dx as f32 * w_i / 2.0
        + center.x
        + center.0
        - width / 2.0;
    let y = screen_height() / 2.0
        + dy as f32 * h_i / 2.0
        - pos.z as f32 * h_i
        + center.y
        + center.1
        - height / 2.0;

    return (x,y);
}

/// A block's face
///   /'\
///  / A \
/// |\   /|
/// | \./ |
/// \ B|C |
///  \.|./
///  A - Top
///  B- Left
///  C -Right
#[derive(Debug)]
enum Face {
    Top,
    Left,
    Right
struct InventoryWidgets {
    widgets_slots: Vec<ItemFrame>,
    widget_back: TextButton,
    widget_categories: TextButton,
    widget_page_indicator: CenteredText,
    /// current page
    /// set to zero when closing inventory
    /// the page count depends on the amount of items
    /// and on how many slots to draw per page
    /// see SLOTS_PER_PAGE for more
    page: usize,
    pub shown: bool
}
impl Face {
    /// gets the face (if any) that was clicked
    /// coordinates have to be normalized,
    /// the top left should be (0,0)
    fn from_xy(x: f32, y: f32, scale: f32) -> Option<Self> {

        // test A face
        {
            let ox = (TEXTURE_WIDTH * scale / 2.0 - x).abs();
            let oy = (TEXTURE_DEPTH * scale / 2.0 + TEXTURE_Y_WHITESPACE * scale - y).abs();

            let y_max = TEXTURE_DEPTH * scale / 2.0;
            let x_max = TEXTURE_WIDTH * scale / 2.0;

            let grow = (0.0 - y_max)/(x_max - 0.0);
            let cy = grow * ox + y_max;

            if cy >= oy {
                return Some(Face::Top);
            }
impl InventoryWidgets {
    fn new() -> Self {
        let size = 0.14;
        Self {
            shown: false,
            widgets_slots: vec![
                ItemFrame::new(0.35, 0.35, size),
                ItemFrame::new(0.5, 0.35, size),
                ItemFrame::new(0.65, 0.35, size),

                ItemFrame::new(0.35, 0.5, size),
                ItemFrame::new(0.5, 0.5, size),
                ItemFrame::new(0.65, 0.5, size),

                ItemFrame::new(0.35, 0.65, size),
                ItemFrame::new(0.5, 0.65, size),
                ItemFrame::new(0.65, 0.65, size),
            ],
            widget_back: TextButton::new("Back", 0.2, 0.2),
            widget_categories: TextButton::new("All", 0.8, 0.2),
            widget_page_indicator: CenteredText::new("0", 0.5, 0.8),
            page: 0
        }

        // test B and C faces
        {
            let ox = TEXTURE_WIDTH * scale / 2.0 - x;
            let oy = y - (TEXTURE_DEPTH * scale / 2.0 + TEXTURE_Y_WHITESPACE * scale);

            let ox_abs = ox.abs();

            let y_max = TEXTURE_DEPTH * scale / 2.0;
            let x_max = TEXTURE_WIDTH * scale / 2.0;

            let grow = (0.0 - y_max)/(x_max - 0.0);

            let y_top = grow * ox_abs + y_max;
            let y_bottom = grow * ox_abs + y_max + TEXTURE_INNER_HEIGHT * scale;

            if oy >= y_top && oy <= y_bottom {
                if x > TEXTURE_WIDTH * scale / 2.0 {
                    // C face
                    return Some(Face::Right);
                } else {
                    // B face
                    return Some(Face::Left);
                }
    }
    /// loads the first page for the last used category
    fn load_current_category(&mut self, inv: &mut Inventory) {
        self.load_category(inv.category.clone(), inv);
    }
    /// loads a new category
    fn load_category(&mut self, cat: Category, inv: &mut Inventory) {
        inv.category = cat;
        let title = inv.category.get_name();
        self.widget_categories.set_text(title);
        let list = inv.category.get_blocks();
        let page_count = list.len().div_ceil(9);
        self.widget_page_indicator.set_text(&format!("{}/{}", self.page+1, page_count));
        self.widget_categories.set_text(&format!("{}", title));

        self.load_page(0, inv);
    }
    /// loads the entries of the given page in the current category
    fn load_page(&mut self, page: usize, inv: &mut Inventory) {
        self.page = page;
        let list = inv.category.get_blocks();
        let max_page = list.len().div_ceil(9);
        for (i, slot) in self.widgets_slots.iter_mut().enumerate() {
            if let Some(block) = list.get(i + page * 9) {
                let amount = if let Some(a) = inv.contents.get(block) { Some(*a) } else { None };
                slot.update(&Some(block.clone()), &inv.direction, &amount);
            } else {
                slot.empty();
            }
        }

        None
        self.widget_page_indicator.set_text(&format!("{}/{}", self.page+1, max_page));
    }
    /// go one page down
    fn page_down(&mut self, inv: &mut Inventory) {
        let list = inv.category.get_blocks();
        let mut page = self.page;
        if page <= 0 {
            page = list.len().div_ceil(9)-1;
        } else {
            page -= 1;
        }
        self.load_page(page, inv);
    }
    /// go one page up
    fn page_up(&mut self, inv: &mut Inventory) {
        let list = inv.category.get_blocks();
        let mut page = self.page;
        let max_page = list.len().div_ceil(9);
        if page + 1 >= max_page {
            page = 0;
        } else {
            page += 1;
        }
        self.load_page(page, inv);
    }
}

/// The screen of the basic build game
pub struct BuildScreen {
    /// the underlying world
    pub world: Option<World>,
    /// the world file name
    file_name: Option<String>,
    /// where the mouse is currently at
    mouse_position: Option<(f32, f32)>,
    /// the p2p backend used for multiplayer mode
    #[cfg(feature="multiplayer")]
    multiplayer: Option<Peer>,
    /// indicator widget showing the currently selected item and its orientation
    item_indicator_widget: ItemFrame,
    /// show the inventory popup
    inventory_widgets: InventoryWidgets
}
impl BuildScreen {
    /// attempts to load a world from disk
    pub fn from_disk(file_name: &str) -> Option<Self> {
        if let Some(world) = World::from_disk(file_name) {
            Some(Self {
                world: Some(world),
                file_name: Some(file_name.to_string()),
                ..Self::empty()
            })
        } else {
            None
        }
    }
    /// create a new empty world
    pub fn new(file_name: &str, sandbox: bool) -> Self {
        Self {
            world: Some(World::new(sandbox)),
            file_name: Some(file_name.to_string()),
            ..Self::empty()
        }
    }
    /// Empty build screen
    /// can be used as a template for other initializers
    fn empty() -> Self {
        Self {
            world: None,
            mouse_position: None,
            file_name: None,
            #[cfg(feature="multiplayer")]
            multiplayer: None,
            item_indicator_widget: ItemFrame::new(0.95, 0.05, 0.05),
            inventory_widgets: InventoryWidgets::new()
        }
    }
    /// Creates an empty build screen
    /// and initializes the p2p backend
    /// when the World transmission is received,
    /// the build screen will be upgraded
    /// to a room with map
    #[cfg(feature="multiplayer")]
    pub fn join() -> Self {
        Self {
            multiplayer: Some(Peer::client()),
            ..Self::empty()
        }
    }
    /// adds a world to a build screen
    fn upgrade(&mut self, world: World) {
        self.world = Some(world);
    }
    /// Launch an existing build screen in game host mode
    /// initializes the p2p backend
    #[cfg(feature="multiplayer")]
    pub fn as_host(self) -> Self {
        Self {
            multiplayer: Some(Peer::host()),
            ..self
        }
    }
}
impl GameComponent for BuildScreen {
    async fn draw(&self, assets: &AssetStore) {
        if self.show_inv {
            // draw inventory
            self.inv.draw(&assets).await;
            return;
        }
        // order: x (inc) -> y (inc) -> z (inc)
        let mut render_order: Vec<(&Pos3, &(Block, Direction))> = self.grid.iter().collect();
        render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 );

        for (pos, (block, dir)) in render_order.iter() {
            let texture = block.get_texture().await.get_dir(dir);
            let width = TEXTURE_WIDTH * self.cam.scale;
            let height = TEXTURE_HEIGHT * self.cam.scale;

            let (x,y) = get_screen_coords(pos, self.cam.scale, self.cam.center);

            if x >= -width && x <= screen_width()
                && y >= -width && y <= screen_height() {
                    // render block
                    draw_texture_ex(
                        texture,
                        x,
                        y,
                        WHITE,
                        DrawTextureParams {
                            dest_size: Some(Vec2::new(width, height)),
                            source: None,
                            rotation: 0.0,
                            flip_x: false,
                            flip_y: false,
                            pivot: Some(Vec2::new(width / 2.0, height / 2.0))
                        });
        if let Some(world) = &self.world {
            if self.inventory_widgets.shown {
                // draw inventory
                self.inventory_widgets.widget_back.draw(&assets).await;
                self.inventory_widgets.widget_categories.draw(&assets).await;

                for slot in self.inventory_widgets.widgets_slots.iter() {
                    slot.draw(&assets).await;
                }
        }

        // Draw selected item (top right)
        {
            let smallest = if screen_width() < screen_height() {
                screen_width()
            } else {
                screen_height()
            };
            // use 70% of screen width for slots
            let width = smallest * 0.08;
            // only use 90% of slot width for the actual item
            // ensures enough space around item
            let slot_render_dim = width * 0.9;

            let tx = screen_width() - width;
            let ty = width - slot_render_dim;

            // draw background
            draw_texture_ex(
                assets.ui.panel_brown.0,
                tx,
                ty,
                WHITE,
                DrawTextureParams {
                    dest_size: Some(Vec2::new(slot_render_dim, slot_render_dim)),
                    ..Default::default()
                }
            );

            if let Some(block) = &self.inv.selected {
                // draw block
                draw_texture_ex(
                    block.get_texture().await.get_dir(&self.inv.direction),
                    tx + slot_render_dim / 6.0,
                    ty + width / 2.0
                        - (slot_render_dim * 2.0/3.0 * TEXTURE_HEIGHT / TEXTURE_WIDTH) / 2.0
                        - TEXTURE_DEPTH * 0.08 * 0.9,
                    WHITE,
                    DrawTextureParams {
                        dest_size: Some(Vec2::new(
                            slot_render_dim * 2.0/3.0,
                            slot_render_dim * 2.0/3.0 * TEXTURE_HEIGHT / TEXTURE_WIDTH)),
                        ..Default::default()
                self.inventory_widgets.widget_page_indicator.draw(&assets).await;

                return;
            }

            // order: x (inc) -> y (inc) -> z (inc)
            let mut render_order: Vec<(&Pos3, &(Block, Direction))> = world.grid.iter().collect();
            render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 );

            for (pos, (block, dir)) in render_order.iter() {
                let texture = block.get_texture().await.get_dir(dir);
                let width = TEXTURE_WIDTH * world.cam.scale;
                let height = TEXTURE_HEIGHT * world.cam.scale;

                let (x,y) = get_screen_coords(pos, world.cam.scale, world.cam.center);

                if x >= -width && x <= screen_width()
                    && y >= -width && y <= screen_height() {
                        // render block
                        draw_texture_ex(
                            texture,
                            x,
                            y,
                            WHITE,
                            DrawTextureParams {
                                dest_size: Some(Vec2::new(width, height)),
                                source: None,
                                rotation: 0.0,
                                flip_x: false,
                                flip_y: false,
                                pivot: Some(Vec2::new(width / 2.0, height / 2.0))
                            });
                    }
                );
            }

            // Draw selected item (top right)
            self.item_indicator_widget.draw(&assets).await;
        }
    }
    fn ev_loop(&mut self) -> GameEvent {
        // process inventory events
        if is_key_down(KeyCode::I) {
            self.show_inv = true;
        }
        if is_mouse_button_pressed(MouseButton::Left) {
            let (mx,my) = mouse_position();
        if let Some(world) = &mut self.world {

            if self.inventory_widgets.shown {
                // process input
                match self.inventory_widgets.widget_back.ev_loop() {
                    ButtonEvent::LeftClick => {
                        self.inventory_widgets.shown = false;
                    }
                    _ => ()
                }
                if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) || is_key_pressed(KeyCode::I) {
                    self.inventory_widgets.shown = false;
                }

                if is_key_pressed(KeyCode::Left) {
                    self.inventory_widgets.page_down(&mut world.inventory);
                }
                if is_key_pressed(KeyCode::Right) {
                    self.inventory_widgets.page_up(&mut world.inventory);
                }

                match self.inventory_widgets.widget_categories.ev_loop() {
                    ButtonEvent::LeftClick => {
                        // change category
                        let categories = Category::all();
                        let current = &world.inventory.category;
                        let index = categories.iter().position(|c| &c == &current).unwrap();
                        if index + 1 >= categories.len() {
                            self.inventory_widgets.load_category(
                                categories.get(0).unwrap().clone(),
                                &mut world.inventory
                            );
                        } else {
                            self.inventory_widgets.load_category(
                                categories.get(index+1).unwrap().clone(),
                                &mut world.inventory
                            );
                        }
                    }
                    _ => ()
                }

                for (i, slot) in self.inventory_widgets.widgets_slots.iter_mut().enumerate() {
                    match slot.ev_loop() {
                        ButtonEvent::LeftClick => {
                            // user selected block
                            let items = world.inventory.category.get_blocks();
                            if let Some(block) = items.get(self.inventory_widgets.page * 9 + i) {
                                world.inventory.selected = Some(block.clone());
                                self.inventory_widgets.shown = false;
                            }
                        },
                        _ => ()
                    }
                }

            let smallest = if screen_width() < screen_height() {
                screen_width()
            } else {
                screen_height()
            };
            // use 70% of screen width for slots
            let width = smallest * 0.08;
            // only use 90% of slot width for the actual item
            // ensures enough space around item
            let slot_render_dim = width * 0.9;

            let tx = screen_width() - width;
            let ty = width - slot_render_dim;

            if mx >= tx && mx <= tx + slot_render_dim
                && my >= ty && my <= ty + slot_render_dim {
                    self.show_inv = true;

                // process inventory events
                if is_key_down(KeyCode::I) {
                    // open inventory
                    self.inventory_widgets.shown = true;
                    self.inventory_widgets.load_current_category(&mut world.inventory);
                }
        }
        if self.show_inv {
            match self.inv.ev_loop() {
                GameEvent::Quit => {
                    self.show_inv = false;
                    return GameEvent::None;

                match self.item_indicator_widget.ev_loop() {
                    ButtonEvent::LeftClick => {
                        // open inventory
                        self.inventory_widgets.shown = true;
                        self.inventory_widgets.load_current_category(&mut world.inventory);
                    }
                    _ => ()
                }
                _ => return GameEvent::None
            }
        }

        if is_mouse_button_pressed(MouseButton::Left)
            || is_mouse_button_pressed(MouseButton::Right) {
                {
                    let amount = if let Some(block) = &world.inventory.selected {
                        if let Some(a) = world.inventory.contents.get(block) {
                            Some(*a)
                        } else { None }
                    } else { None };
                    self.item_indicator_widget.update(&world.inventory.selected, &world.inventory.direction, &amount);
                }

                // determine which block / side was clicked
                let (mx, my) = mouse_position();
                if is_mouse_button_pressed(MouseButton::Left)
                    || is_mouse_button_pressed(MouseButton::Right) {

                // virtual render cycle
                let render_order: Vec<(&Pos3, &(Block, Direction))> = self.grid.iter().collect();
                        // determine which block / side was clicked
                        let (mx, my) = mouse_position();

                // list of positions in render que for given pixel
                // (mx, my)
                let mut in_path:Vec<&Pos3> = Vec::new();
                        // virtual render cycle
                        let render_order: Vec<(&Pos3, &(Block, Direction))> = world.grid.iter().collect();

                for (pos, _) in render_order.iter() {
                    let (x,y) = get_screen_coords(pos, self.cam.scale, self.cam.center);
                        // list of positions in render que for given pixel
                        // (mx, my)
                        let mut in_path:Vec<&Pos3> = Vec::new();

                    if mx >= x
                        && mx <= x + TEXTURE_WIDTH * self.cam.scale
                        && my >= y + TEXTURE_Y_WHITESPACE * self.cam.scale
                        && my <= y + TEXTURE_HEIGHT * self.cam.scale {
                        for (pos, _) in render_order.iter() {
                            let (x,y) = get_screen_coords(pos, world.cam.scale, world.cam.center);

                            // check if mouse is above transparent area
                            // and skip block if necessary
                            if Face::from_xy(mx-x, my-y, self.cam.scale).is_none() {
                                continue;
                            }
                            if mx >= x
                                && mx <= x + TEXTURE_WIDTH * world.cam.scale
                                && my >= y + TEXTURE_Y_WHITESPACE * world.cam.scale
                                && my <= y + TEXTURE_HEIGHT * world.cam.scale {

                                    // check if mouse is above transparent area
                                    // and skip block if necessary
                                    if Face::from_xy(mx-x, my-y, world.cam.scale).is_none() {
                                        continue;
                                    }

                            // block in mouse path
                            in_path.push(pos);
                                    // block in mouse path
                                    in_path.push(pos);
                                }
                        }
                }
                // weight axis
                in_path.sort_by_key(|pos| pos.x + pos.y*2 + pos.z*3 );
                if let Some(pos) = in_path.last() {
                    // position of clicked block
                    // because it is the last block in de render queue
                    // for a given pixel
                    let mut pos = Pos3::new(pos.x, pos.y, pos.z);

                    if is_mouse_button_pressed(MouseButton::Right) {
                        // if grid has only one block
                        // the block can not be removed
                        // because the palyer would not be able to place
                        // new blocks if no block exists
                        if self.grid.len() > 1 {

                            // destroy block
                            if let Some((block, _)) = self.grid.remove(&pos) {
                                self.inv.add(block.clone());

                                // transmit block destruction
                                // if multiplayer mode enabled
                                if let Some(mp) = &mut self.multiplayer {
                                    mp.perform_action(GameAction::RemoveBlock(pos.clone(), block));
                        // weight axis
                        in_path.sort_by_key(|pos| pos.x + pos.y*2 + pos.z*3 );
                        if let Some(pos) = in_path.last() {
                            // position of clicked block
                            // because it is the last block in de render queue
                            // for a given pixel
                            let mut pos = Pos3::new(pos.x, pos.y, pos.z);

                            if is_mouse_button_pressed(MouseButton::Right) {
                                // if grid has only one block
                                // the block can not be removed
                                // because the palyer would not be able to place
                                // new blocks if no block exists
                                if world.grid.len() > 1 {

                                    // destroy block
                                    if let Some((block, _)) = world.destroy_block(&pos) {
                                        // transmit block destruction
                                        // if multiplayer mode enabled
                                        if let Some(mp) = &mut self.multiplayer {
                                            mp.perform_action(GameAction::RemoveBlock(pos.clone(), block));
                                        }
                                    }
                                }
                            }
                        }
                    }

                    if is_mouse_button_pressed(MouseButton::Left) {
                        // place block
                        let (x,y) = get_screen_coords(&pos, self.cam.scale, self.cam.center);
                        let face = Face::from_xy(mx-x, my-y, self.cam.scale);

                        match face.unwrap() {
                            Face::Top => {
                                pos.z+=1;
                            },
                            Face::Left => {
                                pos.x+=1;
                            },
                            Face::Right => {
                                pos.y+=1;
                            }
                        }
                            if is_mouse_button_pressed(MouseButton::Left) {
                                // place block
                                let (x,y) = get_screen_coords(&pos, world.cam.scale, world.cam.center);
                                let face = Face::from_xy(mx-x, my-y, world.cam.scale);

                                match face.unwrap() {
                                    Face::Top => {
                                        pos.z+=1;
                                    },
                                    Face::Left => {
                                        pos.x+=1;
                                    },
                                    Face::Right => {
                                        pos.y+=1;
                                    }
                                }

                        if let Some(block) = self.inv.place() {
                            self.grid.insert(pos.clone(), (block.clone(), self.inv.direction.clone()));
                                if let Some(block) = &world.inventory.selected.clone() {
                                    if world.place_block(&pos, block.clone(), world.inventory.direction.clone()) {

                            // transmit block placement
                            // if multiplayer mode enabled
                            if let Some(mp) = &mut self.multiplayer {
                                mp.perform_action(GameAction::PlaceBlock(pos, block, self.inv.direction.clone()));
                                        // transmit block placement
                                        // if multiplayer mode enabled
                                        if let Some(mp) = &mut self.multiplayer {
                                            mp.perform_action(GameAction::PlaceBlock(pos, block.clone(), world.inventory.direction.clone()));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

        // zoom with Ctrl-MouseWheel
        if is_key_down(KeyCode::LeftControl) {
            let mut scale = self.cam.scale;
            let (_, wy) = mouse_wheel();
                // zoom with Ctrl-MouseWheel
                if is_key_down(KeyCode::LeftControl) {
                    let mut scale = world.cam.scale;
                    let (_, wy) = mouse_wheel();

            scale += wy as f32 * 1.0/10.0;
                    scale += wy as f32 * 1.0/10.0;

            if scale > 0.05 && scale < 6.0 {
                self.cam.scale = scale;
            }
        } else {
            let (mx, my) = mouse_wheel();
                    if scale > 0.05 && scale < 6.0 {
                        world.cam.scale = scale;
                    }
                } else {
                    let (mx, my) = mouse_wheel();

            self.cam.center.y += my * 5.0 * (1.0/self.cam.scale);
            self.cam.center.x += mx * 5.0 * (1.0/self.cam.scale);
        }
                    world.cam.center.0 += mx * 5.0 * (1.0/world.cam.scale);
                    world.cam.center.1 += my * 5.0 * (1.0/world.cam.scale);
                }

        // keyboard control
        if is_key_down(KeyCode::Down) {
            self.cam.center.y -= 1.0 * (1.0/self.cam.scale);
        }
        if is_key_down(KeyCode::Up) {
            self.cam.center.y += 1.0 * (1.0/self.cam.scale);
        }
        if is_key_down(KeyCode::Left) {
            self.cam.center.x += 1.0 * (1.0/self.cam.scale);
        }
        if is_key_down(KeyCode::Right) {
            self.cam.center.x -= 1.0 * (1.0/self.cam.scale);
        }
        if is_key_down(KeyCode::C) {
            self.cam.center = CVec2::new(0.0, 0.0);
        }
        if is_mouse_button_down(MouseButton::Middle) {
            let (nx, ny) = mouse_position();
            if let Some((ox, oy)) = self.mouse_position {
                // keyboard control
                if is_key_down(KeyCode::Down) {
                    world.cam.center.1 -= 1.0 * (1.0/world.cam.scale);
                }
                if is_key_down(KeyCode::Up) {
                    world.cam.center.1 += 1.0 * (1.0/world.cam.scale);
                }
                if is_key_down(KeyCode::Left) {
                    world.cam.center.0 += 1.0 * (1.0/world.cam.scale);
                }
                if is_key_down(KeyCode::Right) {
                    world.cam.center.0 -= 1.0 * (1.0/world.cam.scale);
                }
                if is_key_down(KeyCode::C) {
                    world.cam.center = (0.0, 0.0);
                }
                if is_mouse_button_down(MouseButton::Middle) {
                    let (nx, ny) = mouse_position();
                    if let Some((ox, oy)) = self.mouse_position {

                let dx = nx - ox;
                let dy = ny - oy;
                        let dx = nx - ox;
                        let dy = ny - oy;

                self.cam.center.x += dx;
                self.cam.center.y += dy;
            }
            self.mouse_position = Some((nx, ny));
        }
        if is_mouse_button_released(MouseButton::Middle) {
            self.mouse_position = None;
        }
                        world.cam.center.0 += dx;
                        world.cam.center.1 += dy;
                    }
                    self.mouse_position = Some((nx, ny));
                }
                if is_mouse_button_released(MouseButton::Middle) {
                    self.mouse_position = None;
                }

        // change block direction
        if is_key_pressed(KeyCode::K) {
            self.inv.direction = Direction::North;
        }
        if is_key_pressed(KeyCode::L) {
            self.inv.direction = Direction::East;
        }
        if is_key_pressed(KeyCode::J) {
            self.inv.direction = Direction::South;
        }
        if is_key_pressed(KeyCode::H) {
            self.inv.direction = Direction::West;
        }
                // change block direction
                if is_key_pressed(KeyCode::K) {
                    world.inventory.direction = Direction::North;
                }
                if is_key_pressed(KeyCode::L) {
                    world.inventory.direction = Direction::East;
                }
                if is_key_pressed(KeyCode::J) {
                    world.inventory.direction = Direction::South;
                }
                if is_key_pressed(KeyCode::H) {
                    world.inventory.direction = Direction::West;
                }

        // save the world
        if is_key_pressed(KeyCode::S)
            || is_key_pressed(KeyCode::Q)
            || is_key_pressed(KeyCode::Escape) {
            // save game
                // save the world
                if is_key_pressed(KeyCode::S)
                    || is_key_pressed(KeyCode::Q)
                    || is_key_pressed(KeyCode::Escape) {
                        // save game

            if let Some(file_name) = &self.file_name {
                save_map(&file_name, &self);
            }
                        if let Some(file_name) = &self.file_name {
                            world.to_disk(&file_name);
                        }

            if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) {
                        if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) {

                if let Some(mp) = &mut self.multiplayer {
                    mp.close();
                }
                            if let Some(mp) = &mut self.multiplayer {
                                mp.close();
                            }

                            // close game
                            return GameEvent::ChangeScreen(Screen::default());
                        }
                    }

                // close game
                return GameEvent::ChangeScreen(Screen::default());
            }
        }

        if let Some(mp) = &mut self.multiplayer {
            if let Some(task) = mp.get_next_task() {
                // match action
                // and perform neccesarry action
                match task {
                    GameAction::RequestMap => {
                        mp.perform_action(GameAction::TransmitMap(self.grid.clone()));
                    },
                    GameAction::RequestInventory => {
                        mp.perform_action(GameAction::TransmitInventory(self.inv.contents.clone(), self.inv.has_infinite_items()));
                    GameAction::RequestWorld => {
                        if let Some(world) = &mut self.world {
                            mp.perform_action(
                                GameAction::TransmitWorld(world.clone()));
                        }
                    },
                    GameAction::PlaceBlock(pos, block, dir) => {
                        // remove block from inventory
                        if self.inv.has_infinite_items() {
                            if let Some(slot) = self.inv.contents.get_mut(&block) {
                                if *slot > 1 {
                                    *slot -=1;
                                } else if *slot == 0 {
                                    self.inv.contents.remove(&block);
                                }
                            }
                        if let Some(world) = &mut self.world {
                            // remove block from inventory
                            world.place_block(&pos, block, dir);
                        }

                        self.grid.insert(pos, (block, dir));
                    },
                    GameAction::RemoveBlock(pos, block) => {
                        if self.inv.has_infinite_items() {
                            if let Some(slot) = self.inv.contents.get_mut(&block) {
                                *slot += 1;
                            } else {
                                self.inv.contents.insert(block, 1);
                            }
                    GameAction::RemoveBlock(pos, _block) => {
                        if let Some(world) = &mut self.world {
                            world.destroy_block(&pos);
                        }

                        self.grid.remove(&pos);
                    },
                    GameAction::TransmitMap(grid) => {
                        self.grid = grid;
                    GameAction::TransmitWorld(world) => {
                        self.upgrade(world);
                    },
                    GameAction::TransmitInventory(inv, infty) => {
                        self.inv.contents = inv;
                        self.inv.toggle_infinite_items(infty);

                        if self.grid.len() == 0 {
                            mp.perform_action(GameAction::RequestMap);
                        }
                    }
                }
            }
        }

        GameEvent::None
    }
}

D src/screens/inventory.rs => src/screens/inventory.rs +0 -340
@@ 1,340 0,0 @@
use macroquad::prelude::*;
use crate::types::{
    GameComponent,
    GameEvent,
    Direction
};
use super::build::{
    TEXTURE_WIDTH,
    TEXTURE_HEIGHT,
    TEXTURE_Y_WHITESPACE
};
use crate::textures::AssetStore;
use crate::blocks::Block;
use std::collections::HashMap;
use nanoserde::{DeJson, SerJson};
use crate::draw::{
    TextButton,
    CenteredText,
    Widget,
    ButtonEvent
};

/// draws centered text
/// with the given font and font size
pub fn draw_centered_text(text: &str, x: f32, y: f32, font: Option<Font>, font_size: u16, color: Color) {
    let mut title_params = TextParams {
        font_size,
        color,
        ..Default::default()
    };
    if let Some(font) = font {
        title_params.font = font;
    }
    let title_center = get_text_center(
        text,
        font,
        font_size,
        1.0,
        0.0);
    draw_text_ex(
        text,
        x - title_center.x,
        y - title_center.y,
        title_params);
}

/// draws a wide button with text
pub fn draw_wide_button(text: &str, pressed: bool, x: f32, y: f32, assets: &AssetStore) {
    draw_texture(
        if pressed {
            assets.ui.long_button.1
        } else { assets.ui.long_button.0 },
        x - 190.0 / 2.0,
        y,
        WHITE);

    draw_centered_text(text,
                       x, y + 49.0 / 2.0 - 2.0,
                       Some(assets.font), 60, LIGHTGRAY);
}

/// The players inventory
#[derive(SerJson, DeJson)]
pub struct Inventory {
    /// when set to true, the player has an infinite amount of items
    infinite_items: bool,
    /// items in inventory
    /// with their appropriate amount
    /// if amount is zero, the item should be removed
    /// if infinite_items is true, the amount wont decrease
    pub contents: HashMap<Block, usize>,
    /// which block is currently selected
    pub selected: Option<Block>,
    /// in which direction the block should be facing
    pub direction: Direction,
    /// current page
    /// set to zero when closing inventory
    /// the page count depends on the amount of items
    /// and on how many slots to draw per page
    /// see SLOTS_PER_PAGE for more
    page: usize,
}
impl Inventory {
    pub fn has_infinite_items(&self) -> bool {
        self.infinite_items
    }
    pub fn toggle_infinite_items(&mut self, toggle: bool) {
        self.infinite_items = toggle;
    }
    /// creates an empty inventory
    pub fn empty() -> Self {
        Self {
            infinite_items: false,
            contents: HashMap::new(),
            selected: None,
            direction: Direction::North,
            page: 0,
        }
    }
    pub fn new() -> Self {
        let mut this = Self::empty();
        this.infinite_items = true;

        if this.infinite_items {
            for block in Block::all() {
                this.contents.insert(block, 1);
            }
        }

        this
    }
    /// adds a block to the players inventory
    pub fn add(&mut self, block: Block) {
        if !self.infinite_items {
            *self.contents.get_mut(&block).unwrap_or(&mut 0)+=1;
        }
    }
    /// removes the selected block (one) from the inventory
    /// can be used to remove items
    /// or place items
    pub fn place(&mut self) -> Option<Block> {
        if let Some(block) = &self.selected {

            if !self.infinite_items {
                let cv: &usize = self.contents.get(&block).unwrap_or(&0);

                if cv > &0 {
                    *self.contents.get_mut(&block).unwrap_or(&mut 0) -= 1;
                } else {
                    self.contents.remove(&block);
                    return None;
                }
            }

            return Some(block.clone());
        }

        return None;
    }
}

/// how many slots to draw per inventory page
const SLOTS_PER_PAGE: usize = 9;


impl GameComponent for Inventory {
    async fn draw(&self, assets: &AssetStore) {
        let mid_x = screen_width()/2.0;
        let mid_y = screen_height()/2.0;

        let smallest = if screen_width() < screen_height() {
            screen_width()
        } else {
            screen_height()
        };
        // use 70% of screen width for slots
        let width = smallest * 0.7;
        let hslot_count = ((SLOTS_PER_PAGE as f32).sqrt()) as usize;
        let slot_dim = width / hslot_count as f32;
        // only use 90% of slot width for the actual item
        // ensures enough space around item
        let slot_render_dim = slot_dim * 0.9;

        let mut list: Vec<(&Block, &usize)> = self.contents.iter().collect();
        list.sort_by(|a,b| a.0.get_name().cmp(b.0.get_name()));

        let ox = mid_x - (hslot_count as f32 /2.0) * slot_dim;
        let oy = mid_y - (hslot_count as f32 /2.0) * slot_dim;
        for x in 0..hslot_count {
            for y in 0..hslot_count {
                let tx = ox + slot_dim * x as f32 + (slot_dim - slot_render_dim) / 2.0;
                let ty = oy + slot_dim * y as f32 + (slot_dim - slot_render_dim) / 2.0;

                let mut bg = assets.ui.panel_brown.0;
                if let Some(block) = list.get(self.page * SLOTS_PER_PAGE + x + y*hslot_count)
                    && let Some(sb) = &self.selected && sb == block.0 {
                        // selected block
                        // draw blue background
                        bg = assets.ui.panel_blue.0;
                }
                // draw background
                draw_texture_ex(
                    bg,
                    tx,
                    ty,
                    WHITE,
                    DrawTextureParams {
                        dest_size: Some(Vec2::new(slot_render_dim, slot_render_dim)),
                        ..Default::default()
                    }
                );

                // draw brown foreground
                draw_texture_ex(
                    assets.ui.panel_brown.1,
                    tx + 4.0 + 2.0/2.0,
                    ty + 4.0 + 2.0/2.0,
                    WHITE,
                    DrawTextureParams {
                        dest_size: Some(Vec2::new(slot_render_dim - 4.0 * 2.0 - 4.0, slot_render_dim - 4.0 * 2.0 - 4.0)),
                        ..Default::default()
                    }
                );

                if let Some((block, amount)) = list.get(self.page * SLOTS_PER_PAGE + x + y * hslot_count) {
                    // draw block
                    draw_texture_ex(
                        block.get_texture().await.get_dir(&self.direction),
                        tx + slot_render_dim / 6.0,
                        ty + slot_dim / 2.0 - TEXTURE_HEIGHT / 2.0 + TEXTURE_Y_WHITESPACE / 2.0,
                        WHITE,
                        DrawTextureParams {
                            dest_size: Some(Vec2::new(
                                slot_render_dim * 2.0/3.0,
                                slot_render_dim * 2.0/3.0 * TEXTURE_HEIGHT / TEXTURE_WIDTH)),
                            ..Default::default()
                        }
                    );

                    // draw amount
                    let text = if self.infinite_items { "inf.".to_string() } else { amount.to_string() };
                    draw_centered_text(
                        &text,
                        tx + slot_render_dim / 6.0 + slot_render_dim * 2.0/3.0,
                        ty + slot_dim / 2.0
                            - TEXTURE_HEIGHT / 2.0 + TEXTURE_Y_WHITESPACE / 2.0
                            + slot_render_dim * 2.0/3.0 * TEXTURE_HEIGHT / TEXTURE_WIDTH
                            - 4.0,
                        Some(assets.font),
                        30,
                        WHITE
                    );
                }
            }
        }

        // display page indicator
        let page_count = self.contents.len().div_ceil(SLOTS_PER_PAGE);

        draw_centered_text(
            &format!("{}/{}", self.page+1, page_count),
            mid_x,
            mid_y + hslot_count as f32 /2.0 * slot_dim + slot_dim/3.0,
            Some(assets.font),
            60,
            WHITE
        );
    }
    fn ev_loop(&mut self) -> GameEvent {

        // close inventory
        if is_key_down(KeyCode::Q) {
            return GameEvent::Quit;
        }

        // change block direction
        if is_key_pressed(KeyCode::W) {
            self.direction = Direction::North;
        }
        if is_key_pressed(KeyCode::D) {
            self.direction = Direction::East;
        }
        if is_key_pressed(KeyCode::S) {
            self.direction = Direction::South;
        }
        if is_key_pressed(KeyCode::A) {
            self.direction = Direction::West;
        }

        // process page naviagtion
        let page_count = self.contents.len().div_ceil(SLOTS_PER_PAGE);
        if self.page >= page_count {
            // reset page if page is now empty
            self.page = 0;
        }
        if is_key_pressed(KeyCode::Left) {
            if self.page == 0 {
                if page_count > 0 {
                    self.page = page_count - 1;
                }
            } else {
                self.page -= 1;
            }
        }
        if is_key_pressed(KeyCode::Right) {
            if self.page +1 >= page_count {
                self.page = 0;
            } else {
                self.page += 1;
            }
        }

        // detect pressed item
        if is_mouse_button_pressed(MouseButton::Left) {
            let (mx,my) = mouse_position();
            // virtual render cycle
            let mid_x = screen_width()/2.0;
            let mid_y = screen_height()/2.0;

            let smallest = if screen_width() < screen_height() {
                screen_width()
            } else {
                screen_height()
            };
            // use 70% of screen width for slots
            let width = smallest * 0.7;
            let hslot_count = ((SLOTS_PER_PAGE as f32).sqrt()) as usize;
            let slot_dim = width / hslot_count as f32;
            // only use 90% of slot width for the actual item
            // ensures enough space around item
            let slot_render_dim = slot_dim * 0.9;

            let ox = mid_x - (hslot_count as f32 /2.0) * slot_dim;
            let oy = mid_y - (hslot_count as f32 /2.0) * slot_dim;
            for x in 0..hslot_count {
                for y in 0..hslot_count {
                    let tx = ox + slot_dim * x as f32 + (slot_dim - slot_render_dim) / 2.0;
                    let ty = oy + slot_dim * y as f32 + (slot_dim - slot_render_dim) / 2.0;

                    if mx >= tx && mx <= tx + slot_render_dim
                        && my >= ty && my <= ty + slot_render_dim {
                            // clicked this item
                            let mut list: Vec<(&Block, &usize)> = self.contents.iter().collect();
                            list.sort_by(|a,b| a.0.get_name().cmp(b.0.get_name()));

                            if let Some((block, _)) = list.get(self.page * SLOTS_PER_PAGE + x + y * hslot_count) {
                                self.selected = Some((*block).clone());
                            } else {
                                self.selected = None;
                            }

                            return GameEvent::Quit;
                        }
                }
            }
        }

        GameEvent::None
    }
}

M src/screens/map_creator.rs => src/screens/map_creator.rs +10 -13
@@ 1,19 1,15 @@
use macroquad::prelude::*;
use crate::types::{
use crate::game::{
    GameComponent,
    GameEvent
    GameEvent,
    world::World
};
use crate::textures::AssetStore;
use super::map_select::SelectScreen;
use super::build::BuildScreen;
use super::Screen;

use crate::storage::{
    get_world_list,
    save_map
};

use crate::draw::{
use crate::ui::{
    TextButton,
    Widget,
    ButtonEvent,


@@ 76,7 72,7 @@ impl GameComponent for MapCreatorScreen {

        {
            // process text input
            if is_key_pressed(KeyCode::Delete) {
            if is_key_pressed(KeyCode::Backspace) {
                self.name.pop();

                if self.name.len() == 0 {


@@ 247,12 243,13 @@ impl GameComponent for MapCreatorScreen {

        match self.widget_create.ev_loop() {
            ButtonEvent::LeftClick => {
                // TODO check text
                if self.name.len() > 0 && self.name.len() <= 10 {
                    let name = format!("{}.world", self.name);
                    if !get_world_list().contains(&name) {
                        let bscr = BuildScreen::new(&name);
                        save_map(&name, &bscr);
                    if !World::get_list().contains(&name) {
                        let bscr = BuildScreen::new(&name, true);
                        if let Some(world) = bscr.world {
                            world.to_disk(&name);
                        }
                        return GameEvent::ChangeScreen(Screen::Select(SelectScreen::new()));
                    }
                }

M src/screens/map_select.rs => src/screens/map_select.rs +22 -26
@@ 1,21 1,19 @@
use macroquad::prelude::*;
use crate::types::{
use crate::game::{
    GameComponent,
    GameEvent
    GameEvent,
    world::World
};
use crate::textures::AssetStore;
use crate::draw::{
use crate::ui::{
    TextButton,
    Widget,
    ButtonEvent,
    SelectableText
};
use crate::storage::{
    get_world_list,
    read_map
};
use super::{
    Screen,
    build::BuildScreen,
    map_creator::MapCreatorScreen
};



@@ 40,14 38,14 @@ pub struct SelectScreen {
    /// list of slots
    level_select_widgets: Vec<SelectableText>,
    /// currently selected slot
    selected: Option<usize>,
    selected: Option<String>,
    /// currently visible page
    page: usize
}
impl SelectScreen {
    /// create a new MapSelectorScreen instance
    pub fn new() -> Self {
        let levels = get_world_list();
        let levels = World::get_list();
        Self {
            list: levels.clone(),
            widget_back: TextButton::new("Back", 0.1, 0.1)


@@ 117,10 115,9 @@ impl GameComponent for SelectScreen {
        {
            match self.widget_host.ev_loop() {
                ButtonEvent::LeftClick => {
                    if let Some(index) = self.selected {
                        let file_name = self.list.get(index+self.page).unwrap();
                        if let Some(scr) = read_map(&file_name) {
                            return GameEvent::ChangeScreen(Screen::Build(scr.with_file_name(&file_name).as_host()));
                    if let Some(file_name) = &self.selected {
                        if let Some(scr) = BuildScreen::from_disk(&file_name) {
                            return GameEvent::ChangeScreen(Screen::Build(scr.as_host()));
                        }
                    }
                }


@@ 129,41 126,40 @@ impl GameComponent for SelectScreen {
        }
        match self.widget_new.ev_loop() {
            ButtonEvent::LeftClick => {
            return GameEvent::ChangeScreen(Screen::Create(MapCreatorScreen::new()))
                return GameEvent::ChangeScreen(Screen::Create(MapCreatorScreen::new()))
            },
            _ => ()
        }
        match self.widget_play.ev_loop() {
            ButtonEvent::LeftClick => {
                if let Some(index) = self.selected {
                    let file_name = self.list.get(index+self.page).unwrap();
                    if let Some(scr) = read_map(&file_name) {
                        return GameEvent::ChangeScreen(Screen::Build(scr.with_file_name(&file_name)));
                if let Some(file_name) = &self.selected {
                    if let Some(scr) = BuildScreen::from_disk(&file_name) {
                        return GameEvent::ChangeScreen(Screen::Build(scr));
                    }
                }
            }
            _ => ()
        }

        for (i, slot) in self.level_select_widgets.iter_mut().enumerate() {
        for slot in self.level_select_widgets.iter_mut() {
            slot.set_selected(false);
            match slot.ev_loop() {
                ButtonEvent::LeftClick => {
                    if let Some(sel) = self.selected && sel == i+self.page {
                    if let Some(sel) = &self.selected && sel == &slot.get_text() {
                        // already selected
                        // deselect
                        self.selected = None;
                    } else {
                        // user selected this item
                        self.selected = Some(i + self.page);
                        self.selected = Some(slot.get_text().to_string());
                    }
                    slot.set_selected(false);
                },
                _ => ()
            }
        }
        if let Some(selection) = self.selected {
            for (i, slot) in self.level_select_widgets.iter_mut().enumerate() {
                if i == selection {
        if let Some(selection) = &self.selected {
            for slot in self.level_select_widgets.iter_mut() {
                if &slot.get_text() == selection {
                    slot.set_selected(true);
                }
            }


@@ 175,7 171,7 @@ impl GameComponent for SelectScreen {
        }

        if is_key_pressed(KeyCode::Down) {
            if self.page < self.list.len() - self.level_select_widgets.len() {
            if self.list.len() >= self.level_select_widgets.len() && self.page < self.list.len() - self.level_select_widgets.len() {
                self.page += 1;

                // update widget text

M src/screens/mod.rs => src/screens/mod.rs +7 -9
@@ 1,6 1,5 @@
mod welcome;
pub mod build;
mod inventory;
mod map_select;
mod map_creator;



@@ 8,12 7,11 @@ use welcome::WelcomeScreen;
use build::BuildScreen;
use map_select::SelectScreen;
use map_creator::MapCreatorScreen;

use crate::types::{
    GameComponent,
    GameEvent
};
use crate::textures::AssetStore;
use crate::game::{
    GameEvent,
    GameComponent
};

/// Possible screens
/// that can be shown


@@ 25,7 23,7 @@ pub enum Screen {
    /// Map selection screen
    Select(SelectScreen),
    /// Map creation screen
    Create(MapCreatorScreen)
    Create(MapCreatorScreen),
}
impl GameComponent for Screen {
    async fn draw(&self, assets: &AssetStore) {


@@ 33,7 31,7 @@ impl GameComponent for Screen {
            Screen::Welcome(w) => w.draw(&assets).await,
            Screen::Build(b) => b.draw(&assets).await,
            Screen::Select(s) => s.draw(&assets).await,
            Screen::Create(c) => c.draw(&assets).await
            Screen::Create(c) => c.draw(&assets).await,
        }
    }
    fn ev_loop(&mut self) -> GameEvent {


@@ 41,7 39,7 @@ impl GameComponent for Screen {
            Screen::Welcome(w) => w.ev_loop(),
            Screen::Build(b) => b.ev_loop(),
            Screen::Select(s) => s.ev_loop(),
            Screen::Create(c) => c.ev_loop()
            Screen::Create(c) => c.ev_loop(),
        }
    }
}

M src/screens/welcome.rs => src/screens/welcome.rs +3 -3
@@ 1,10 1,10 @@
use macroquad::prelude::*;
use crate::types::{
use crate::game::{
    GameComponent,
    GameEvent
};
use crate::textures::AssetStore;
use crate::draw::{
use crate::ui::{
    TextButton,
    CenteredText,
    Widget,


@@ 77,7 77,7 @@ impl GameComponent for WelcomeScreen {
            _ => ()
        }
        match self.widget_join.ev_loop() {
            ButtonEvent::LeftClick => return GameEvent::ChangeScreen(Screen::Build(BuildScreen::multiplayer())),
            ButtonEvent::LeftClick => return GameEvent::ChangeScreen(Screen::Build(BuildScreen::join())),
            _ => ()
        }
        match self.widget_quit.ev_loop() {

M src/textures.rs => src/textures.rs +2 -2
@@ 1,5 1,5 @@
use macroquad::prelude::*;
use crate::types::Direction;
use crate::game::types::Direction;

/// Collection of textures required by game
pub struct AssetStore {


@@ 46,7 46,7 @@ macro_rules! include_base_tile {
    ($name:literal, $dir:literal) => {
        Texture2D::from_image(
            &Image::from_file_with_format(
                include_bytes!(concat!("../assets/base/Tiles/", $name, "_", $dir, ".png")),
                include_bytes!(concat!("../../assets/base/Tiles/", $name, "_", $dir, ".png")),
                Some(ImageFormat::Png)
            )
        )