diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d6f3ff..7a162b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: run: cargo install cargo-tarpaulin - name: Generate coverage - run: cargo tarpaulin --verbose --workspace --out Lcov --output-dir ./coverage + run: cargo tarpaulin --verbose --workspace --out Lcov --output-dir ./coverage --exclude-files "game/src/main.rs" - name: Upload coverage uses: codecov/codecov-action@v3 diff --git a/Cargo.lock b/Cargo.lock index b822ee0..1207e5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -325,7 +325,7 @@ dependencies = [ [[package]] name = "ferari" -version = "0.1.0" +version = "1.0.2" dependencies = [ "crossbeam-channel", "image", @@ -439,6 +439,15 @@ dependencies = [ "slab", ] +[[package]] +name = "game" +version = "1.0.2" +dependencies = [ + "crossbeam-channel", + "ferari", + "minifb", +] + [[package]] name = "getrandom" version = "0.2.16" diff --git a/Cargo.toml b/Cargo.toml index 6573492..402459e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,3 @@ -[package] -name = "ferari" -version = "0.1.0" -edition = "2021" -description = "Fast Engine for Rendering Axonometric Rust-based Isometry" -authors = ["Rodion Suvorov , Ilhom Kombaev , Vyacheslav Kochergin , Dmitri Kuznetsov "] -license = "MIT" -repository = "https://github.com/suvorovrain/Ferari" -readme = "README.md" - -[dependencies] -minifb = "0.28" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -image = "0.25" -crossbeam-channel = "0.5.15" - -[dev-dependencies] +[workspace] +members = ["engine", "game"] +resolver = "2" diff --git a/LICENSE b/LICENSE index 3c6f9ea..a3e6016 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Rodion Suvorov, Ilhom Kombaev, Vyacheslav Kochergin, Dmitri Kuznetsov +Copyright (c) 2025 21pack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ea69325..2ce0dbb 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,52 @@ -# Ferari -![CI Status](https://github.com/suvorovrain/Ferari/actions/workflows/ci.yml/badge.svg) +# GoFerari + +![CI Status](https://github.com/21pack/GoFerari/actions/workflows/ci.yml/badge.svg) + +Game on [Ferari](https://github.com/suvorovrain/Ferari) (Fast Engine for Rendering Axonometric Rust-based Isometry). + +Currently, x86_64 Linux and ARM64 macOS platforms are supported. + +![demo](/demo.gif) -Fast Engine for Rendering Axonometric Rust-based Isometry. ## Description -An isometric engine that allows you to create simple games with static objects and mobs. -## Authors -* Rodion Suvorov. [GitHub](https://github.com/suvorovrain), [Contact](https://t.me/suvorovrain). -* Ilhom Kombaev. [GitHub](https://github.com/homka122), [Contact](https://t.me/homka122). -* Vyacheslav Kochergin. [GitHub](https://github.com/VyacheslavIurevich), [Contact](https://t.me/se4life). -* Dmitri Kuznetsov. [GitHub](https://github.com/f1i3g3), [Contact](https://t.me/f1i3g3). -## Platforms -Currently, x86_64 Linux and Windows platforms are supported + +An isometric game in puzzle genre such as [Sokoban](https://en.wikipedia.org/wiki/Sokoban). The aim of the game is to get the boxes onto storage locations. + +### Mechanics + +* Player: You control the worker. Move up, down, left, or right. +* Boxes: Push the boxes, one at a time. You cannot pull them or push two boxes at once. +* Walls: Solid grey blocks form the immovable barriers. They confine your and boxes movement. +* Target Docks: Indented floor tiles mark the delivery targets where the boxes must be placed. +* Box States: + * Light Box: A box that is not yet on a target. + * Dark Box: A box that has been successfully pushed onto a target dock. + ## Usage -* Download release archive from GitHub releases page -* Put your custom map information into `input.json` file in the root of unpacked archive, according format from example in the root of repository + +### Install + +* Download release archive from GitHub releases page. +* Clone and build this repo. + +### Play + +```shell +./play.sh +``` + +### Control + +The player moves on a grid-based system with fixed-direction controls. This means each key consistently moves the character in one cardinal direction, regardless of the on-screen perspective. + +* `WASD` or `arrow control`: movement; +* `<-`(`A`) + `->`(`D`): go to menu; +* `esc`: close game. + ## Dependencies + +### Linux + ```shell curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh sudo apt install cargo @@ -23,23 +55,29 @@ rustup component add rustfmt rustup component add clippy cargo install cargo-tarpaulin ``` -## Development + +### macOS + +```shell +brew install rustup +rustup-init +``` + +## Development (Ferari) + * See [CONTRIBUTING.md](./CONTRIBUTING.md) -* Compile & run via `cargo run` +* Compile & run game via `cargo run -p game --release` * View docs via `cargo doc` (use --document-private-items if you want) * Format your code via `cargo fmt` * Everything else - in CI -## Benchmark - -### Machine info: -OS: Kubuntu 24.04 -CPU: Intel Core i7-12700H -RAM: 16 GB -| Mobs Count | Average FPS | -|-------------|-------------| -| 500 | 40 | -| 5000 | 15 | -| 10000 | 10 | -| 100000 | 4 | -| 1000000 | 0.7 | +## Authors + +* **Maxim Rodionov:** [GitHub](https://github.com/RodionovMaxim05), [Telegram](https://t.me/Maxoon22) +* **Dmitri Chirkov:** [GitHub](https://github.com/kinokotakenoko9), [Telegram](https://t.me/chdmitri) +* **Nikita Shchutskii:** [GitHub](https://github.com/ns-58), [Telegram](https://t.me/szcz00) +* **Vladimir Zaikin:** [GitHub](https://github.com/Friend-zva), [Telegram](https://t.me/vo_va_w) + +## License + +Distributed under the [MIT License](https://choosealicense.com/licenses/mit/). See [`LICENSE`](LICENSE) for more information. diff --git a/assets/assetsgen.py b/assets/assetsgen.py new file mode 100644 index 0000000..57c522c --- /dev/null +++ b/assets/assetsgen.py @@ -0,0 +1,109 @@ +from PIL import Image +import json + + +SIZE_TILE = 256 + + +def create_scaled_atlas_alphabet(atlas_path, json_data, output_path, scale=4): + atlas = Image.open(atlas_path) + with open(json_data, "r") as f: + data_tiles = json.load(f) + + tile_size = data_tiles["meta"]["tile_size"] + tile_size_new = tile_size * scale + + width_new, height_new = SIZE_TILE * 4, SIZE_TILE * 9 + atlas_new = Image.new("RGBA", (width_new, height_new)) + + tiles_data_new = {} + tiles_data_new["frames"] = {} + tiles_data_new["meta"] = { + "image": "atlas_x2.png", + "tile_size": SIZE_TILE, + "version": 1, + } + + for i, (tile_name, coords) in enumerate(data_tiles["frames"].items()): + x, y, w, h = coords["x"], coords["y"], coords["w"], coords["h"] + tile = atlas.crop((x, y, x + w, y + h)) + + resized_tile = tile.resize( + (tile_size_new, tile_size_new), Image.Resampling.NEAREST + ) + + col, row = i % 4, i // 4 + x_new = col * SIZE_TILE + 60 + y_new = row * SIZE_TILE + 20 + + atlas_new.paste(resized_tile, (x_new, y_new)) + + tiles_data_new["frames"][tile_name] = { + "x": col * SIZE_TILE, + "y": row * SIZE_TILE, + "w": SIZE_TILE, + "h": SIZE_TILE, + } + + atlas_new.save(output_path) + + new_json_path = output_path.replace(".png", ".json") + with open(new_json_path, "w") as f: + json.dump(tiles_data_new, f, indent=2) + + +def create_atlas_ground(atlas_path, json_data, output_path): + atlas = Image.open(atlas_path) + with open(json_data, "r") as f: + data_tiles = json.load(f) + + tile_size = data_tiles["meta"]["tile_size"] + + width_new, height_new = tile_size * 4, tile_size * 2 + atlas_new = Image.new("RGBA", (width_new, height_new)) + + tiles_data_new = {} + tiles_data_new["frames"] = {} + tiles_data_new["meta"] = { + "image": "atlas_x2.png", + "tile_size": SIZE_TILE, + "version": 1, + } + + for i, (tile_name, coords) in enumerate(data_tiles["frames"].items()): + x, y, w, h = coords["x"], coords["y"], coords["w"], coords["h"] + tile = atlas.crop((x, y, x + w, y + h)) + + resized_tile = tile + + col, row = i % 4, i // 4 + x_new = col * SIZE_TILE + y_new = row * SIZE_TILE + + tile_size_new = SIZE_TILE + + if tile_name == "wall_tile": + x_new += 22 + y_new -= 14 + tile_size_new = tile_size - 48 + resized_tile = tile.resize( + (tile_size_new, tile_size_new), Image.Resampling.NEAREST + ) + + atlas_new.paste(resized_tile, (x_new, y_new)) + + tiles_data_new["frames"][tile_name] = { + "x": col * SIZE_TILE, + "y": row * SIZE_TILE, + "w": SIZE_TILE, + "h": SIZE_TILE, + } + + atlas_new.save(output_path) + + new_json_path = output_path.replace(".png", ".json") + with open(new_json_path, "w") as f: + json.dump(tiles_data_new, f, indent=2) + + +create_atlas_ground("assets/atlas.png", "assets/atlas.json", "assets/atlas_x2.png") diff --git a/assets/entities/IsometricTRPGAssetPack_OutlinedEntities.png b/assets/entities/IsometricTRPGAssetPack_OutlinedEntities.png deleted file mode 100644 index ffa1eeb..0000000 Binary files a/assets/entities/IsometricTRPGAssetPack_OutlinedEntities.png and /dev/null differ diff --git a/assets/entities/atlas.json b/assets/entities/atlas.json index 8a8d829..2e03572 100644 --- a/assets/entities/atlas.json +++ b/assets/entities/atlas.json @@ -1,81 +1,3117 @@ { "frames": { - "knight_0_0": { - "x": 3, - "y": 4, - "w": 10, - "h": 10 - }, - "knight_0_1": { - "x": 19, - "y": 4, - "w": 10, - "h": 10 - }, - "knight_1_0": { - "x": 4, - "y": 29, - "w": 10, - "h": 10 - }, - "knight_1_1": { - "x": 20, - "y": 29, - "w": 10, - "h": 10 - }, - "imp_20_0": { - "x": 5, - "y": 345, - "w": 8, - "h": 8 - }, - "imp_20_1": { - "x": 21, - "y": 345, - "w": 8, - "h": 8 - }, - "imp_21_0": { - "x": 5, - "y": 369, - "w": 10, - "h": 10 - }, - "imp_21_1": { - "x": 21, - "y": 369, - "w": 10, - "h": 10 - }, - "ghost_30_0": { - "x": 6, - "y": 520, - "w": 10, - "h": 10 - }, - "ghost_30_1": { - "x": 22, - "y": 520, - "w": 10, - "h": 10 - }, - "ghost_31_0": { - "x": 7, - "y": 536, - "w": 10, - "h": 10 - }, - "ghost_31_1": { - "x": 23, - "y": 537, - "w": 10, - "h": 10 + "walkingforward_se_0": { + "x": 13, + "y": 16, + "w": 100, + "h": 100 + }, + "walkingforward_se_1": { + "x": 139, + "y": 16, + "w": 100, + "h": 100 + }, + "walkingforward_se_2": { + "x": 265, + "y": 16, + "w": 100, + "h": 100 + }, + "walkingforward_se_3": { + "x": 391, + "y": 16, + "w": 100, + "h": 100 + }, + "walkingforward_se_4": { + "x": 517, + "y": 16, + "w": 100, + "h": 100 + }, + "walkingforward_se_5": { + "x": 13, + "y": 148, + "w": 100, + "h": 100 + }, + "walkingforward_se_6": { + "x": 139, + "y": 148, + "w": 100, + "h": 100 + }, + "walkingforward_se_7": { + "x": 265, + "y": 148, + "w": 100, + "h": 100 + }, + "walkingforward_se_8": { + "x": 391, + "y": 148, + "w": 100, + "h": 100 + }, + "walkingforward_se_9": { + "x": 517, + "y": 148, + "w": 100, + "h": 100 + }, + "walkingforward_se_10": { + "x": 13, + "y": 280, + "w": 100, + "h": 100 + }, + "walkingforward_se_11": { + "x": 139, + "y": 280, + "w": 100, + "h": 100 + }, + "walkingforward_se_12": { + "x": 265, + "y": 280, + "w": 100, + "h": 100 + }, + "walkingforward_se_13": { + "x": 391, + "y": 280, + "w": 100, + "h": 100 + }, + "walkingforward_se_14": { + "x": 517, + "y": 280, + "w": 100, + "h": 100 + }, + "walkingforward_se_15": { + "x": 13, + "y": 412, + "w": 100, + "h": 100 + }, + "walkingforward_se_16": { + "x": 139, + "y": 412, + "w": 100, + "h": 100 + }, + "walkingforward_se_17": { + "x": 265, + "y": 412, + "w": 100, + "h": 100 + }, + "walkingforward_se_18": { + "x": 391, + "y": 412, + "w": 100, + "h": 100 + }, + "walkingforward_se_19": { + "x": 517, + "y": 412, + "w": 100, + "h": 100 + }, + "walkingforward_se_20": { + "x": 13, + "y": 544, + "w": 100, + "h": 100 + }, + "walkingforward_se_21": { + "x": 139, + "y": 544, + "w": 100, + "h": 100 + }, + "walkingforward_se_22": { + "x": 265, + "y": 544, + "w": 100, + "h": 100 + }, + "walkingforward_se_23": { + "x": 391, + "y": 544, + "w": 100, + "h": 100 + }, + "walkingforward_ne_0": { + "x": 13, + "y": 676, + "w": 100, + "h": 100 + }, + "walkingforward_ne_1": { + "x": 139, + "y": 676, + "w": 100, + "h": 100 + }, + "walkingforward_ne_2": { + "x": 265, + "y": 676, + "w": 100, + "h": 100 + }, + "walkingforward_ne_3": { + "x": 391, + "y": 676, + "w": 100, + "h": 100 + }, + "walkingforward_ne_4": { + "x": 517, + "y": 676, + "w": 100, + "h": 100 + }, + "walkingforward_ne_5": { + "x": 13, + "y": 808, + "w": 100, + "h": 100 + }, + "walkingforward_ne_6": { + "x": 139, + "y": 808, + "w": 100, + "h": 100 + }, + "walkingforward_ne_7": { + "x": 265, + "y": 808, + "w": 100, + "h": 100 + }, + "walkingforward_ne_8": { + "x": 391, + "y": 808, + "w": 100, + "h": 100 + }, + "walkingforward_ne_9": { + "x": 517, + "y": 808, + "w": 100, + "h": 100 + }, + "walkingforward_ne_10": { + "x": 13, + "y": 940, + "w": 100, + "h": 100 + }, + "walkingforward_ne_11": { + "x": 139, + "y": 940, + "w": 100, + "h": 100 + }, + "walkingforward_ne_12": { + "x": 265, + "y": 940, + "w": 100, + "h": 100 + }, + "walkingforward_ne_13": { + "x": 391, + "y": 940, + "w": 100, + "h": 100 + }, + "walkingforward_ne_14": { + "x": 517, + "y": 940, + "w": 100, + "h": 100 + }, + "walkingforward_ne_15": { + "x": 13, + "y": 1072, + "w": 100, + "h": 100 + }, + "walkingforward_ne_16": { + "x": 139, + "y": 1072, + "w": 100, + "h": 100 + }, + "walkingforward_ne_17": { + "x": 265, + "y": 1072, + "w": 100, + "h": 100 + }, + "walkingforward_ne_18": { + "x": 391, + "y": 1072, + "w": 100, + "h": 100 + }, + "walkingforward_ne_19": { + "x": 517, + "y": 1072, + "w": 100, + "h": 100 + }, + "walkingforward_ne_20": { + "x": 13, + "y": 1204, + "w": 100, + "h": 100 + }, + "walkingforward_ne_21": { + "x": 139, + "y": 1204, + "w": 100, + "h": 100 + }, + "walkingforward_ne_22": { + "x": 265, + "y": 1204, + "w": 100, + "h": 100 + }, + "walkingforward_ne_23": { + "x": 391, + "y": 1204, + "w": 100, + "h": 100 + }, + "walkingforward_nw_0": { + "x": 13, + "y": 1336, + "w": 100, + "h": 100 + }, + "walkingforward_nw_1": { + "x": 139, + "y": 1336, + "w": 100, + "h": 100 + }, + "walkingforward_nw_2": { + "x": 265, + "y": 1336, + "w": 100, + "h": 100 + }, + "walkingforward_nw_3": { + "x": 391, + "y": 1336, + "w": 100, + "h": 100 + }, + "walkingforward_nw_4": { + "x": 517, + "y": 1336, + "w": 100, + "h": 100 + }, + "walkingforward_nw_5": { + "x": 13, + "y": 1468, + "w": 100, + "h": 100 + }, + "walkingforward_nw_6": { + "x": 139, + "y": 1468, + "w": 100, + "h": 100 + }, + "walkingforward_nw_7": { + "x": 265, + "y": 1468, + "w": 100, + "h": 100 + }, + "walkingforward_nw_8": { + "x": 391, + "y": 1468, + "w": 100, + "h": 100 + }, + "walkingforward_nw_9": { + "x": 517, + "y": 1468, + "w": 100, + "h": 100 + }, + "walkingforward_nw_10": { + "x": 13, + "y": 1600, + "w": 100, + "h": 100 + }, + "walkingforward_nw_11": { + "x": 139, + "y": 1600, + "w": 100, + "h": 100 + }, + "walkingforward_nw_12": { + "x": 265, + "y": 1600, + "w": 100, + "h": 100 + }, + "walkingforward_nw_13": { + "x": 391, + "y": 1600, + "w": 100, + "h": 100 + }, + "walkingforward_nw_14": { + "x": 517, + "y": 1600, + "w": 100, + "h": 100 + }, + "walkingforward_nw_15": { + "x": 13, + "y": 1732, + "w": 100, + "h": 100 + }, + "walkingforward_nw_16": { + "x": 139, + "y": 1732, + "w": 100, + "h": 100 + }, + "walkingforward_nw_17": { + "x": 265, + "y": 1732, + "w": 100, + "h": 100 + }, + "walkingforward_nw_18": { + "x": 391, + "y": 1732, + "w": 100, + "h": 100 + }, + "walkingforward_nw_19": { + "x": 517, + "y": 1732, + "w": 100, + "h": 100 + }, + "walkingforward_nw_20": { + "x": 13, + "y": 1864, + "w": 100, + "h": 100 + }, + "walkingforward_nw_21": { + "x": 139, + "y": 1864, + "w": 100, + "h": 100 + }, + "walkingforward_nw_22": { + "x": 265, + "y": 1864, + "w": 100, + "h": 100 + }, + "walkingforward_nw_23": { + "x": 391, + "y": 1864, + "w": 100, + "h": 100 + }, + "walkingforward_sw_0": { + "x": 13, + "y": 1996, + "w": 100, + "h": 100 + }, + "walkingforward_sw_1": { + "x": 139, + "y": 1996, + "w": 100, + "h": 100 + }, + "walkingforward_sw_2": { + "x": 265, + "y": 1996, + "w": 100, + "h": 100 + }, + "walkingforward_sw_3": { + "x": 391, + "y": 1996, + "w": 100, + "h": 100 + }, + "walkingforward_sw_4": { + "x": 517, + "y": 1996, + "w": 100, + "h": 100 + }, + "walkingforward_sw_5": { + "x": 13, + "y": 2128, + "w": 100, + "h": 100 + }, + "walkingforward_sw_6": { + "x": 139, + "y": 2128, + "w": 100, + "h": 100 + }, + "walkingforward_sw_7": { + "x": 265, + "y": 2128, + "w": 100, + "h": 100 + }, + "walkingforward_sw_8": { + "x": 391, + "y": 2128, + "w": 100, + "h": 100 + }, + "walkingforward_sw_9": { + "x": 517, + "y": 2128, + "w": 100, + "h": 100 + }, + "walkingforward_sw_10": { + "x": 13, + "y": 2260, + "w": 100, + "h": 100 + }, + "walkingforward_sw_11": { + "x": 139, + "y": 2260, + "w": 100, + "h": 100 + }, + "walkingforward_sw_12": { + "x": 265, + "y": 2260, + "w": 100, + "h": 100 + }, + "walkingforward_sw_13": { + "x": 391, + "y": 2260, + "w": 100, + "h": 100 + }, + "walkingforward_sw_14": { + "x": 517, + "y": 2260, + "w": 100, + "h": 100 + }, + "walkingforward_sw_15": { + "x": 13, + "y": 2392, + "w": 100, + "h": 100 + }, + "walkingforward_sw_16": { + "x": 139, + "y": 2392, + "w": 100, + "h": 100 + }, + "walkingforward_sw_17": { + "x": 265, + "y": 2392, + "w": 100, + "h": 100 + }, + "walkingforward_sw_18": { + "x": 391, + "y": 2392, + "w": 100, + "h": 100 + }, + "walkingforward_sw_19": { + "x": 517, + "y": 2392, + "w": 100, + "h": 100 + }, + "walkingforward_sw_20": { + "x": 13, + "y": 2524, + "w": 100, + "h": 100 + }, + "walkingforward_sw_21": { + "x": 139, + "y": 2524, + "w": 100, + "h": 100 + }, + "walkingforward_sw_22": { + "x": 265, + "y": 2524, + "w": 100, + "h": 100 + }, + "walkingforward_sw_23": { + "x": 391, + "y": 2524, + "w": 100, + "h": 100 + }, + "walkingback_se_0": { + "x": 13, + "y": 2656, + "w": 100, + "h": 100 + }, + "walkingback_se_1": { + "x": 139, + "y": 2656, + "w": 100, + "h": 100 + }, + "walkingback_se_2": { + "x": 265, + "y": 2656, + "w": 100, + "h": 100 + }, + "walkingback_se_3": { + "x": 391, + "y": 2656, + "w": 100, + "h": 100 + }, + "walkingback_se_4": { + "x": 517, + "y": 2656, + "w": 100, + "h": 100 + }, + "walkingback_se_5": { + "x": 13, + "y": 2788, + "w": 100, + "h": 100 + }, + "walkingback_se_6": { + "x": 139, + "y": 2788, + "w": 100, + "h": 100 + }, + "walkingback_se_7": { + "x": 265, + "y": 2788, + "w": 100, + "h": 100 + }, + "walkingback_se_8": { + "x": 391, + "y": 2788, + "w": 100, + "h": 100 + }, + "walkingback_se_9": { + "x": 517, + "y": 2788, + "w": 100, + "h": 100 + }, + "walkingback_se_10": { + "x": 13, + "y": 2920, + "w": 100, + "h": 100 + }, + "walkingback_se_11": { + "x": 139, + "y": 2920, + "w": 100, + "h": 100 + }, + "walkingback_se_12": { + "x": 265, + "y": 2920, + "w": 100, + "h": 100 + }, + "walkingback_se_13": { + "x": 391, + "y": 2920, + "w": 100, + "h": 100 + }, + "walkingback_se_14": { + "x": 517, + "y": 2920, + "w": 100, + "h": 100 + }, + "walkingback_se_15": { + "x": 13, + "y": 3052, + "w": 100, + "h": 100 + }, + "walkingback_se_16": { + "x": 139, + "y": 3052, + "w": 100, + "h": 100 + }, + "walkingback_se_17": { + "x": 265, + "y": 3052, + "w": 100, + "h": 100 + }, + "walkingback_se_18": { + "x": 391, + "y": 3052, + "w": 100, + "h": 100 + }, + "walkingback_se_19": { + "x": 517, + "y": 3052, + "w": 100, + "h": 100 + }, + "walkingback_se_20": { + "x": 13, + "y": 3184, + "w": 100, + "h": 100 + }, + "walkingback_se_21": { + "x": 139, + "y": 3184, + "w": 100, + "h": 100 + }, + "walkingback_se_22": { + "x": 265, + "y": 3184, + "w": 100, + "h": 100 + }, + "walkingback_ne_0": { + "x": 13, + "y": 3316, + "w": 100, + "h": 100 + }, + "walkingback_ne_1": { + "x": 139, + "y": 3316, + "w": 100, + "h": 100 + }, + "walkingback_ne_2": { + "x": 265, + "y": 3316, + "w": 100, + "h": 100 + }, + "walkingback_ne_3": { + "x": 391, + "y": 3316, + "w": 100, + "h": 100 + }, + "walkingback_ne_4": { + "x": 517, + "y": 3316, + "w": 100, + "h": 100 + }, + "walkingback_ne_5": { + "x": 13, + "y": 3448, + "w": 100, + "h": 100 + }, + "walkingback_ne_6": { + "x": 139, + "y": 3448, + "w": 100, + "h": 100 + }, + "walkingback_ne_7": { + "x": 265, + "y": 3448, + "w": 100, + "h": 100 + }, + "walkingback_ne_8": { + "x": 391, + "y": 3448, + "w": 100, + "h": 100 + }, + "walkingback_ne_9": { + "x": 517, + "y": 3448, + "w": 100, + "h": 100 + }, + "walkingback_ne_10": { + "x": 13, + "y": 3580, + "w": 100, + "h": 100 + }, + "walkingback_ne_11": { + "x": 139, + "y": 3580, + "w": 100, + "h": 100 + }, + "walkingback_ne_12": { + "x": 265, + "y": 3580, + "w": 100, + "h": 100 + }, + "walkingback_ne_13": { + "x": 391, + "y": 3580, + "w": 100, + "h": 100 + }, + "walkingback_ne_14": { + "x": 517, + "y": 3580, + "w": 100, + "h": 100 + }, + "walkingback_ne_15": { + "x": 13, + "y": 3712, + "w": 100, + "h": 100 + }, + "walkingback_ne_16": { + "x": 139, + "y": 3712, + "w": 100, + "h": 100 + }, + "walkingback_ne_17": { + "x": 265, + "y": 3712, + "w": 100, + "h": 100 + }, + "walkingback_ne_18": { + "x": 391, + "y": 3712, + "w": 100, + "h": 100 + }, + "walkingback_ne_19": { + "x": 517, + "y": 3712, + "w": 100, + "h": 100 + }, + "walkingback_ne_20": { + "x": 13, + "y": 3844, + "w": 100, + "h": 100 + }, + "walkingback_ne_21": { + "x": 139, + "y": 3844, + "w": 100, + "h": 100 + }, + "walkingback_ne_22": { + "x": 265, + "y": 3844, + "w": 100, + "h": 100 + }, + "walkingback_nw_0": { + "x": 13, + "y": 3976, + "w": 100, + "h": 100 + }, + "walkingback_nw_1": { + "x": 139, + "y": 3976, + "w": 100, + "h": 100 + }, + "walkingback_nw_2": { + "x": 265, + "y": 3976, + "w": 100, + "h": 100 + }, + "walkingback_nw_3": { + "x": 391, + "y": 3976, + "w": 100, + "h": 100 + }, + "walkingback_nw_4": { + "x": 517, + "y": 3976, + "w": 100, + "h": 100 + }, + "walkingback_nw_5": { + "x": 13, + "y": 4108, + "w": 100, + "h": 100 + }, + "walkingback_nw_6": { + "x": 139, + "y": 4108, + "w": 100, + "h": 100 + }, + "walkingback_nw_7": { + "x": 265, + "y": 4108, + "w": 100, + "h": 100 + }, + "walkingback_nw_8": { + "x": 391, + "y": 4108, + "w": 100, + "h": 100 + }, + "walkingback_nw_9": { + "x": 517, + "y": 4108, + "w": 100, + "h": 100 + }, + "walkingback_nw_10": { + "x": 13, + "y": 4240, + "w": 100, + "h": 100 + }, + "walkingback_nw_11": { + "x": 139, + "y": 4240, + "w": 100, + "h": 100 + }, + "walkingback_nw_12": { + "x": 265, + "y": 4240, + "w": 100, + "h": 100 + }, + "walkingback_nw_13": { + "x": 391, + "y": 4240, + "w": 100, + "h": 100 + }, + "walkingback_nw_14": { + "x": 517, + "y": 4240, + "w": 100, + "h": 100 + }, + "walkingback_nw_15": { + "x": 13, + "y": 4372, + "w": 100, + "h": 100 + }, + "walkingback_nw_16": { + "x": 139, + "y": 4372, + "w": 100, + "h": 100 + }, + "walkingback_nw_17": { + "x": 265, + "y": 4372, + "w": 100, + "h": 100 + }, + "walkingback_nw_18": { + "x": 391, + "y": 4372, + "w": 100, + "h": 100 + }, + "walkingback_nw_19": { + "x": 517, + "y": 4372, + "w": 100, + "h": 100 + }, + "walkingback_nw_20": { + "x": 13, + "y": 4504, + "w": 100, + "h": 100 + }, + "walkingback_nw_21": { + "x": 139, + "y": 4504, + "w": 100, + "h": 100 + }, + "walkingback_nw_22": { + "x": 265, + "y": 4504, + "w": 100, + "h": 100 + }, + "walkingback_sw_0": { + "x": 13, + "y": 4636, + "w": 100, + "h": 100 + }, + "walkingback_sw_1": { + "x": 139, + "y": 4636, + "w": 100, + "h": 100 + }, + "walkingback_sw_2": { + "x": 265, + "y": 4636, + "w": 100, + "h": 100 + }, + "walkingback_sw_3": { + "x": 391, + "y": 4636, + "w": 100, + "h": 100 + }, + "walkingback_sw_4": { + "x": 517, + "y": 4636, + "w": 100, + "h": 100 + }, + "walkingback_sw_5": { + "x": 13, + "y": 4768, + "w": 100, + "h": 100 + }, + "walkingback_sw_6": { + "x": 139, + "y": 4768, + "w": 100, + "h": 100 + }, + "walkingback_sw_7": { + "x": 265, + "y": 4768, + "w": 100, + "h": 100 + }, + "walkingback_sw_8": { + "x": 391, + "y": 4768, + "w": 100, + "h": 100 + }, + "walkingback_sw_9": { + "x": 517, + "y": 4768, + "w": 100, + "h": 100 + }, + "walkingback_sw_10": { + "x": 13, + "y": 4900, + "w": 100, + "h": 100 + }, + "walkingback_sw_11": { + "x": 139, + "y": 4900, + "w": 100, + "h": 100 + }, + "walkingback_sw_12": { + "x": 265, + "y": 4900, + "w": 100, + "h": 100 + }, + "walkingback_sw_13": { + "x": 391, + "y": 4900, + "w": 100, + "h": 100 + }, + "walkingback_sw_14": { + "x": 517, + "y": 4900, + "w": 100, + "h": 100 + }, + "walkingback_sw_15": { + "x": 13, + "y": 5032, + "w": 100, + "h": 100 + }, + "walkingback_sw_16": { + "x": 139, + "y": 5032, + "w": 100, + "h": 100 + }, + "walkingback_sw_17": { + "x": 265, + "y": 5032, + "w": 100, + "h": 100 + }, + "walkingback_sw_18": { + "x": 391, + "y": 5032, + "w": 100, + "h": 100 + }, + "walkingback_sw_19": { + "x": 517, + "y": 5032, + "w": 100, + "h": 100 + }, + "walkingback_sw_20": { + "x": 13, + "y": 5164, + "w": 100, + "h": 100 + }, + "walkingback_sw_21": { + "x": 139, + "y": 5164, + "w": 100, + "h": 100 + }, + "walkingback_sw_22": { + "x": 265, + "y": 5164, + "w": 100, + "h": 100 + }, + "running_se_0": { + "x": 13, + "y": 5296, + "w": 100, + "h": 100 + }, + "running_se_1": { + "x": 139, + "y": 5296, + "w": 100, + "h": 100 + }, + "running_se_2": { + "x": 265, + "y": 5296, + "w": 100, + "h": 100 + }, + "running_se_3": { + "x": 391, + "y": 5296, + "w": 100, + "h": 100 + }, + "running_se_4": { + "x": 13, + "y": 5428, + "w": 100, + "h": 100 + }, + "running_se_5": { + "x": 139, + "y": 5428, + "w": 100, + "h": 100 + }, + "running_se_6": { + "x": 265, + "y": 5428, + "w": 100, + "h": 100 + }, + "running_se_7": { + "x": 391, + "y": 5428, + "w": 100, + "h": 100 + }, + "running_se_8": { + "x": 13, + "y": 5560, + "w": 100, + "h": 100 + }, + "running_se_9": { + "x": 139, + "y": 5560, + "w": 100, + "h": 100 + }, + "running_se_10": { + "x": 265, + "y": 5560, + "w": 100, + "h": 100 + }, + "running_se_11": { + "x": 391, + "y": 5560, + "w": 100, + "h": 100 + }, + "running_se_12": { + "x": 13, + "y": 5692, + "w": 100, + "h": 100 + }, + "running_se_13": { + "x": 139, + "y": 5692, + "w": 100, + "h": 100 + }, + "running_ne_0": { + "x": 13, + "y": 5824, + "w": 100, + "h": 100 + }, + "running_ne_1": { + "x": 139, + "y": 5824, + "w": 100, + "h": 100 + }, + "running_ne_2": { + "x": 265, + "y": 5824, + "w": 100, + "h": 100 + }, + "running_ne_3": { + "x": 391, + "y": 5824, + "w": 100, + "h": 100 + }, + "running_ne_4": { + "x": 13, + "y": 5956, + "w": 100, + "h": 100 + }, + "running_ne_5": { + "x": 139, + "y": 5956, + "w": 100, + "h": 100 + }, + "running_ne_6": { + "x": 265, + "y": 5956, + "w": 100, + "h": 100 + }, + "running_ne_7": { + "x": 391, + "y": 5956, + "w": 100, + "h": 100 + }, + "running_ne_8": { + "x": 13, + "y": 6088, + "w": 100, + "h": 100 + }, + "running_ne_9": { + "x": 139, + "y": 6088, + "w": 100, + "h": 100 + }, + "running_ne_10": { + "x": 265, + "y": 6088, + "w": 100, + "h": 100 + }, + "running_ne_11": { + "x": 391, + "y": 6088, + "w": 100, + "h": 100 + }, + "running_ne_12": { + "x": 13, + "y": 6220, + "w": 100, + "h": 100 + }, + "running_ne_13": { + "x": 139, + "y": 6220, + "w": 100, + "h": 100 + }, + "running_nw_0": { + "x": 13, + "y": 6352, + "w": 100, + "h": 100 + }, + "running_nw_1": { + "x": 139, + "y": 6352, + "w": 100, + "h": 100 + }, + "running_nw_2": { + "x": 265, + "y": 6352, + "w": 100, + "h": 100 + }, + "running_nw_3": { + "x": 391, + "y": 6352, + "w": 100, + "h": 100 + }, + "running_nw_4": { + "x": 13, + "y": 6484, + "w": 100, + "h": 100 + }, + "running_nw_5": { + "x": 139, + "y": 6484, + "w": 100, + "h": 100 + }, + "running_nw_6": { + "x": 265, + "y": 6484, + "w": 100, + "h": 100 + }, + "running_nw_7": { + "x": 391, + "y": 6484, + "w": 100, + "h": 100 + }, + "running_nw_8": { + "x": 13, + "y": 6616, + "w": 100, + "h": 100 + }, + "running_nw_9": { + "x": 139, + "y": 6616, + "w": 100, + "h": 100 + }, + "running_nw_10": { + "x": 265, + "y": 6616, + "w": 100, + "h": 100 + }, + "running_nw_11": { + "x": 391, + "y": 6616, + "w": 100, + "h": 100 + }, + "running_nw_12": { + "x": 13, + "y": 6748, + "w": 100, + "h": 100 + }, + "running_nw_13": { + "x": 139, + "y": 6748, + "w": 100, + "h": 100 + }, + "running_sw_0": { + "x": 13, + "y": 6880, + "w": 100, + "h": 100 + }, + "running_sw_1": { + "x": 139, + "y": 6880, + "w": 100, + "h": 100 + }, + "running_sw_2": { + "x": 265, + "y": 6880, + "w": 100, + "h": 100 + }, + "running_sw_3": { + "x": 391, + "y": 6880, + "w": 100, + "h": 100 + }, + "running_sw_4": { + "x": 13, + "y": 7012, + "w": 100, + "h": 100 + }, + "running_sw_5": { + "x": 139, + "y": 7012, + "w": 100, + "h": 100 + }, + "running_sw_6": { + "x": 265, + "y": 7012, + "w": 100, + "h": 100 + }, + "running_sw_7": { + "x": 391, + "y": 7012, + "w": 100, + "h": 100 + }, + "running_sw_8": { + "x": 13, + "y": 7144, + "w": 100, + "h": 100 + }, + "running_sw_9": { + "x": 139, + "y": 7144, + "w": 100, + "h": 100 + }, + "running_sw_10": { + "x": 265, + "y": 7144, + "w": 100, + "h": 100 + }, + "running_sw_11": { + "x": 391, + "y": 7144, + "w": 100, + "h": 100 + }, + "running_sw_12": { + "x": 13, + "y": 7276, + "w": 100, + "h": 100 + }, + "running_sw_13": { + "x": 139, + "y": 7276, + "w": 100, + "h": 100 + }, + "pushing_se_0": { + "x": 13, + "y": 7408, + "w": 100, + "h": 100 + }, + "pushing_se_1": { + "x": 139, + "y": 7408, + "w": 100, + "h": 100 + }, + "pushing_se_2": { + "x": 265, + "y": 7408, + "w": 100, + "h": 100 + }, + "pushing_se_3": { + "x": 391, + "y": 7408, + "w": 100, + "h": 100 + }, + "pushing_se_4": { + "x": 517, + "y": 7408, + "w": 100, + "h": 100 + }, + "pushing_se_5": { + "x": 643, + "y": 7408, + "w": 100, + "h": 100 + }, + "pushing_se_6": { + "x": 769, + "y": 7408, + "w": 100, + "h": 100 + }, + "pushing_se_7": { + "x": 13, + "y": 7540, + "w": 100, + "h": 100 + }, + "pushing_se_8": { + "x": 139, + "y": 7540, + "w": 100, + "h": 100 + }, + "pushing_se_9": { + "x": 265, + "y": 7540, + "w": 100, + "h": 100 + }, + "pushing_se_10": { + "x": 391, + "y": 7540, + "w": 100, + "h": 100 + }, + "pushing_se_11": { + "x": 517, + "y": 7540, + "w": 100, + "h": 100 + }, + "pushing_se_12": { + "x": 643, + "y": 7540, + "w": 100, + "h": 100 + }, + "pushing_se_13": { + "x": 769, + "y": 7540, + "w": 100, + "h": 100 + }, + "pushing_se_14": { + "x": 13, + "y": 7672, + "w": 100, + "h": 100 + }, + "pushing_se_15": { + "x": 139, + "y": 7672, + "w": 100, + "h": 100 + }, + "pushing_se_16": { + "x": 265, + "y": 7672, + "w": 100, + "h": 100 + }, + "pushing_se_17": { + "x": 391, + "y": 7672, + "w": 100, + "h": 100 + }, + "pushing_se_18": { + "x": 517, + "y": 7672, + "w": 100, + "h": 100 + }, + "pushing_se_19": { + "x": 643, + "y": 7672, + "w": 100, + "h": 100 + }, + "pushing_se_20": { + "x": 769, + "y": 7672, + "w": 100, + "h": 100 + }, + "pushing_se_21": { + "x": 13, + "y": 7804, + "w": 100, + "h": 100 + }, + "pushing_se_22": { + "x": 139, + "y": 7804, + "w": 100, + "h": 100 + }, + "pushing_se_23": { + "x": 265, + "y": 7804, + "w": 100, + "h": 100 + }, + "pushing_se_24": { + "x": 391, + "y": 7804, + "w": 100, + "h": 100 + }, + "pushing_se_25": { + "x": 517, + "y": 7804, + "w": 100, + "h": 100 + }, + "pushing_se_26": { + "x": 643, + "y": 7804, + "w": 100, + "h": 100 + }, + "pushing_se_27": { + "x": 769, + "y": 7804, + "w": 100, + "h": 100 + }, + "pushing_se_28": { + "x": 13, + "y": 7936, + "w": 100, + "h": 100 + }, + "pushing_se_29": { + "x": 139, + "y": 7936, + "w": 100, + "h": 100 + }, + "pushing_se_30": { + "x": 265, + "y": 7936, + "w": 100, + "h": 100 + }, + "pushing_se_31": { + "x": 391, + "y": 7936, + "w": 100, + "h": 100 + }, + "pushing_se_32": { + "x": 517, + "y": 7936, + "w": 100, + "h": 100 + }, + "pushing_se_33": { + "x": 643, + "y": 7936, + "w": 100, + "h": 100 + }, + "pushing_se_34": { + "x": 769, + "y": 7936, + "w": 100, + "h": 100 + }, + "pushing_se_35": { + "x": 13, + "y": 8068, + "w": 100, + "h": 100 + }, + "pushing_se_36": { + "x": 139, + "y": 8068, + "w": 100, + "h": 100 + }, + "pushing_ne_0": { + "x": 13, + "y": 8200, + "w": 100, + "h": 100 + }, + "pushing_ne_1": { + "x": 139, + "y": 8200, + "w": 100, + "h": 100 + }, + "pushing_ne_2": { + "x": 265, + "y": 8200, + "w": 100, + "h": 100 + }, + "pushing_ne_3": { + "x": 391, + "y": 8200, + "w": 100, + "h": 100 + }, + "pushing_ne_4": { + "x": 517, + "y": 8200, + "w": 100, + "h": 100 + }, + "pushing_ne_5": { + "x": 643, + "y": 8200, + "w": 100, + "h": 100 + }, + "pushing_ne_6": { + "x": 769, + "y": 8200, + "w": 100, + "h": 100 + }, + "pushing_ne_7": { + "x": 13, + "y": 8332, + "w": 100, + "h": 100 + }, + "pushing_ne_8": { + "x": 139, + "y": 8332, + "w": 100, + "h": 100 + }, + "pushing_ne_9": { + "x": 265, + "y": 8332, + "w": 100, + "h": 100 + }, + "pushing_ne_10": { + "x": 391, + "y": 8332, + "w": 100, + "h": 100 + }, + "pushing_ne_11": { + "x": 517, + "y": 8332, + "w": 100, + "h": 100 + }, + "pushing_ne_12": { + "x": 643, + "y": 8332, + "w": 100, + "h": 100 + }, + "pushing_ne_13": { + "x": 769, + "y": 8332, + "w": 100, + "h": 100 + }, + "pushing_ne_14": { + "x": 13, + "y": 8464, + "w": 100, + "h": 100 + }, + "pushing_ne_15": { + "x": 139, + "y": 8464, + "w": 100, + "h": 100 + }, + "pushing_ne_16": { + "x": 265, + "y": 8464, + "w": 100, + "h": 100 + }, + "pushing_ne_17": { + "x": 391, + "y": 8464, + "w": 100, + "h": 100 + }, + "pushing_ne_18": { + "x": 517, + "y": 8464, + "w": 100, + "h": 100 + }, + "pushing_ne_19": { + "x": 643, + "y": 8464, + "w": 100, + "h": 100 + }, + "pushing_ne_20": { + "x": 769, + "y": 8464, + "w": 100, + "h": 100 + }, + "pushing_ne_21": { + "x": 13, + "y": 8596, + "w": 100, + "h": 100 + }, + "pushing_ne_22": { + "x": 139, + "y": 8596, + "w": 100, + "h": 100 + }, + "pushing_ne_23": { + "x": 265, + "y": 8596, + "w": 100, + "h": 100 + }, + "pushing_ne_24": { + "x": 391, + "y": 8596, + "w": 100, + "h": 100 + }, + "pushing_ne_25": { + "x": 517, + "y": 8596, + "w": 100, + "h": 100 + }, + "pushing_ne_26": { + "x": 643, + "y": 8596, + "w": 100, + "h": 100 + }, + "pushing_ne_27": { + "x": 769, + "y": 8596, + "w": 100, + "h": 100 + }, + "pushing_ne_28": { + "x": 13, + "y": 8728, + "w": 100, + "h": 100 + }, + "pushing_ne_29": { + "x": 139, + "y": 8728, + "w": 100, + "h": 100 + }, + "pushing_ne_30": { + "x": 265, + "y": 8728, + "w": 100, + "h": 100 + }, + "pushing_ne_31": { + "x": 391, + "y": 8728, + "w": 100, + "h": 100 + }, + "pushing_ne_32": { + "x": 517, + "y": 8728, + "w": 100, + "h": 100 + }, + "pushing_ne_33": { + "x": 643, + "y": 8728, + "w": 100, + "h": 100 + }, + "pushing_ne_34": { + "x": 769, + "y": 8728, + "w": 100, + "h": 100 + }, + "pushing_ne_35": { + "x": 13, + "y": 8860, + "w": 100, + "h": 100 + }, + "pushing_ne_36": { + "x": 139, + "y": 8860, + "w": 100, + "h": 100 + }, + "pushing_nw_0": { + "x": 13, + "y": 8992, + "w": 100, + "h": 100 + }, + "pushing_nw_1": { + "x": 139, + "y": 8992, + "w": 100, + "h": 100 + }, + "pushing_nw_2": { + "x": 265, + "y": 8992, + "w": 100, + "h": 100 + }, + "pushing_nw_3": { + "x": 391, + "y": 8992, + "w": 100, + "h": 100 + }, + "pushing_nw_4": { + "x": 517, + "y": 8992, + "w": 100, + "h": 100 + }, + "pushing_nw_5": { + "x": 643, + "y": 8992, + "w": 100, + "h": 100 + }, + "pushing_nw_6": { + "x": 769, + "y": 8992, + "w": 100, + "h": 100 + }, + "pushing_nw_7": { + "x": 13, + "y": 9124, + "w": 100, + "h": 100 + }, + "pushing_nw_8": { + "x": 139, + "y": 9124, + "w": 100, + "h": 100 + }, + "pushing_nw_9": { + "x": 265, + "y": 9124, + "w": 100, + "h": 100 + }, + "pushing_nw_10": { + "x": 391, + "y": 9124, + "w": 100, + "h": 100 + }, + "pushing_nw_11": { + "x": 517, + "y": 9124, + "w": 100, + "h": 100 + }, + "pushing_nw_12": { + "x": 643, + "y": 9124, + "w": 100, + "h": 100 + }, + "pushing_nw_13": { + "x": 769, + "y": 9124, + "w": 100, + "h": 100 + }, + "pushing_nw_14": { + "x": 13, + "y": 9256, + "w": 100, + "h": 100 + }, + "pushing_nw_15": { + "x": 139, + "y": 9256, + "w": 100, + "h": 100 + }, + "pushing_nw_16": { + "x": 265, + "y": 9256, + "w": 100, + "h": 100 + }, + "pushing_nw_17": { + "x": 391, + "y": 9256, + "w": 100, + "h": 100 + }, + "pushing_nw_18": { + "x": 517, + "y": 9256, + "w": 100, + "h": 100 + }, + "pushing_nw_19": { + "x": 643, + "y": 9256, + "w": 100, + "h": 100 + }, + "pushing_nw_20": { + "x": 769, + "y": 9256, + "w": 100, + "h": 100 + }, + "pushing_nw_21": { + "x": 13, + "y": 9388, + "w": 100, + "h": 100 + }, + "pushing_nw_22": { + "x": 139, + "y": 9388, + "w": 100, + "h": 100 + }, + "pushing_nw_23": { + "x": 265, + "y": 9388, + "w": 100, + "h": 100 + }, + "pushing_nw_24": { + "x": 391, + "y": 9388, + "w": 100, + "h": 100 + }, + "pushing_nw_25": { + "x": 517, + "y": 9388, + "w": 100, + "h": 100 + }, + "pushing_nw_26": { + "x": 643, + "y": 9388, + "w": 100, + "h": 100 + }, + "pushing_nw_27": { + "x": 769, + "y": 9388, + "w": 100, + "h": 100 + }, + "pushing_nw_28": { + "x": 13, + "y": 9520, + "w": 100, + "h": 100 + }, + "pushing_nw_29": { + "x": 139, + "y": 9520, + "w": 100, + "h": 100 + }, + "pushing_nw_30": { + "x": 265, + "y": 9520, + "w": 100, + "h": 100 + }, + "pushing_nw_31": { + "x": 391, + "y": 9520, + "w": 100, + "h": 100 + }, + "pushing_nw_32": { + "x": 517, + "y": 9520, + "w": 100, + "h": 100 + }, + "pushing_nw_33": { + "x": 643, + "y": 9520, + "w": 100, + "h": 100 + }, + "pushing_nw_34": { + "x": 769, + "y": 9520, + "w": 100, + "h": 100 + }, + "pushing_nw_35": { + "x": 13, + "y": 9652, + "w": 100, + "h": 100 + }, + "pushing_nw_36": { + "x": 139, + "y": 9652, + "w": 100, + "h": 100 + }, + "pushing_sw_0": { + "x": 13, + "y": 9784, + "w": 100, + "h": 100 + }, + "pushing_sw_1": { + "x": 139, + "y": 9784, + "w": 100, + "h": 100 + }, + "pushing_sw_2": { + "x": 265, + "y": 9784, + "w": 100, + "h": 100 + }, + "pushing_sw_3": { + "x": 391, + "y": 9784, + "w": 100, + "h": 100 + }, + "pushing_sw_4": { + "x": 517, + "y": 9784, + "w": 100, + "h": 100 + }, + "pushing_sw_5": { + "x": 643, + "y": 9784, + "w": 100, + "h": 100 + }, + "pushing_sw_6": { + "x": 769, + "y": 9784, + "w": 100, + "h": 100 + }, + "pushing_sw_7": { + "x": 13, + "y": 9916, + "w": 100, + "h": 100 + }, + "pushing_sw_8": { + "x": 139, + "y": 9916, + "w": 100, + "h": 100 + }, + "pushing_sw_9": { + "x": 265, + "y": 9916, + "w": 100, + "h": 100 + }, + "pushing_sw_10": { + "x": 391, + "y": 9916, + "w": 100, + "h": 100 + }, + "pushing_sw_11": { + "x": 517, + "y": 9916, + "w": 100, + "h": 100 + }, + "pushing_sw_12": { + "x": 643, + "y": 9916, + "w": 100, + "h": 100 + }, + "pushing_sw_13": { + "x": 769, + "y": 9916, + "w": 100, + "h": 100 + }, + "pushing_sw_14": { + "x": 13, + "y": 10048, + "w": 100, + "h": 100 + }, + "pushing_sw_15": { + "x": 139, + "y": 10048, + "w": 100, + "h": 100 + }, + "pushing_sw_16": { + "x": 265, + "y": 10048, + "w": 100, + "h": 100 + }, + "pushing_sw_17": { + "x": 391, + "y": 10048, + "w": 100, + "h": 100 + }, + "pushing_sw_18": { + "x": 517, + "y": 10048, + "w": 100, + "h": 100 + }, + "pushing_sw_19": { + "x": 643, + "y": 10048, + "w": 100, + "h": 100 + }, + "pushing_sw_20": { + "x": 769, + "y": 10048, + "w": 100, + "h": 100 + }, + "pushing_sw_21": { + "x": 13, + "y": 10180, + "w": 100, + "h": 100 + }, + "pushing_sw_22": { + "x": 139, + "y": 10180, + "w": 100, + "h": 100 + }, + "pushing_sw_23": { + "x": 265, + "y": 10180, + "w": 100, + "h": 100 + }, + "pushing_sw_24": { + "x": 391, + "y": 10180, + "w": 100, + "h": 100 + }, + "pushing_sw_25": { + "x": 517, + "y": 10180, + "w": 100, + "h": 100 + }, + "pushing_sw_26": { + "x": 643, + "y": 10180, + "w": 100, + "h": 100 + }, + "pushing_sw_27": { + "x": 769, + "y": 10180, + "w": 100, + "h": 100 + }, + "pushing_sw_28": { + "x": 13, + "y": 10312, + "w": 100, + "h": 100 + }, + "pushing_sw_29": { + "x": 139, + "y": 10312, + "w": 100, + "h": 100 + }, + "pushing_sw_30": { + "x": 265, + "y": 10312, + "w": 100, + "h": 100 + }, + "pushing_sw_31": { + "x": 391, + "y": 10312, + "w": 100, + "h": 100 + }, + "pushing_sw_32": { + "x": 517, + "y": 10312, + "w": 100, + "h": 100 + }, + "pushing_sw_33": { + "x": 643, + "y": 10312, + "w": 100, + "h": 100 + }, + "pushing_sw_34": { + "x": 769, + "y": 10312, + "w": 100, + "h": 100 + }, + "pushing_sw_35": { + "x": 13, + "y": 10444, + "w": 100, + "h": 100 + }, + "pushing_sw_36": { + "x": 139, + "y": 10444, + "w": 100, + "h": 100 + }, + "idle_se_0": { + "x": 13, + "y": 10576, + "w": 100, + "h": 100 + }, + "idle_se_1": { + "x": 139, + "y": 10576, + "w": 100, + "h": 100 + }, + "idle_se_2": { + "x": 265, + "y": 10576, + "w": 100, + "h": 100 + }, + "idle_se_3": { + "x": 391, + "y": 10576, + "w": 100, + "h": 100 + }, + "idle_se_4": { + "x": 517, + "y": 10576, + "w": 100, + "h": 100 + }, + "idle_se_5": { + "x": 643, + "y": 10576, + "w": 100, + "h": 100 + }, + "idle_se_6": { + "x": 13, + "y": 10708, + "w": 100, + "h": 100 + }, + "idle_se_7": { + "x": 139, + "y": 10708, + "w": 100, + "h": 100 + }, + "idle_se_8": { + "x": 265, + "y": 10708, + "w": 100, + "h": 100 + }, + "idle_se_9": { + "x": 391, + "y": 10708, + "w": 100, + "h": 100 + }, + "idle_se_10": { + "x": 517, + "y": 10708, + "w": 100, + "h": 100 + }, + "idle_se_11": { + "x": 643, + "y": 10708, + "w": 100, + "h": 100 + }, + "idle_se_12": { + "x": 13, + "y": 10840, + "w": 100, + "h": 100 + }, + "idle_se_13": { + "x": 139, + "y": 10840, + "w": 100, + "h": 100 + }, + "idle_se_14": { + "x": 265, + "y": 10840, + "w": 100, + "h": 100 + }, + "idle_se_15": { + "x": 391, + "y": 10840, + "w": 100, + "h": 100 + }, + "idle_se_16": { + "x": 517, + "y": 10840, + "w": 100, + "h": 100 + }, + "idle_se_17": { + "x": 643, + "y": 10840, + "w": 100, + "h": 100 + }, + "idle_se_18": { + "x": 13, + "y": 10972, + "w": 100, + "h": 100 + }, + "idle_se_19": { + "x": 139, + "y": 10972, + "w": 100, + "h": 100 + }, + "idle_se_20": { + "x": 265, + "y": 10972, + "w": 100, + "h": 100 + }, + "idle_se_21": { + "x": 391, + "y": 10972, + "w": 100, + "h": 100 + }, + "idle_se_22": { + "x": 517, + "y": 10972, + "w": 100, + "h": 100 + }, + "idle_se_23": { + "x": 643, + "y": 10972, + "w": 100, + "h": 100 + }, + "idle_se_24": { + "x": 13, + "y": 11104, + "w": 100, + "h": 100 + }, + "idle_se_25": { + "x": 139, + "y": 11104, + "w": 100, + "h": 100 + }, + "idle_se_26": { + "x": 265, + "y": 11104, + "w": 100, + "h": 100 + }, + "idle_se_27": { + "x": 391, + "y": 11104, + "w": 100, + "h": 100 + }, + "idle_se_28": { + "x": 517, + "y": 11104, + "w": 100, + "h": 100 + }, + "idle_se_29": { + "x": 643, + "y": 11104, + "w": 100, + "h": 100 + }, + "idle_se_30": { + "x": 13, + "y": 11236, + "w": 100, + "h": 100 + }, + "idle_ne_0": { + "x": 13, + "y": 11368, + "w": 100, + "h": 100 + }, + "idle_ne_1": { + "x": 139, + "y": 11368, + "w": 100, + "h": 100 + }, + "idle_ne_2": { + "x": 265, + "y": 11368, + "w": 100, + "h": 100 + }, + "idle_ne_3": { + "x": 391, + "y": 11368, + "w": 100, + "h": 100 + }, + "idle_ne_4": { + "x": 517, + "y": 11368, + "w": 100, + "h": 100 + }, + "idle_ne_5": { + "x": 643, + "y": 11368, + "w": 100, + "h": 100 + }, + "idle_ne_6": { + "x": 13, + "y": 11500, + "w": 100, + "h": 100 + }, + "idle_ne_7": { + "x": 139, + "y": 11500, + "w": 100, + "h": 100 + }, + "idle_ne_8": { + "x": 265, + "y": 11500, + "w": 100, + "h": 100 + }, + "idle_ne_9": { + "x": 391, + "y": 11500, + "w": 100, + "h": 100 + }, + "idle_ne_10": { + "x": 517, + "y": 11500, + "w": 100, + "h": 100 + }, + "idle_ne_11": { + "x": 643, + "y": 11500, + "w": 100, + "h": 100 + }, + "idle_ne_12": { + "x": 13, + "y": 11632, + "w": 100, + "h": 100 + }, + "idle_ne_13": { + "x": 139, + "y": 11632, + "w": 100, + "h": 100 + }, + "idle_ne_14": { + "x": 265, + "y": 11632, + "w": 100, + "h": 100 + }, + "idle_ne_15": { + "x": 391, + "y": 11632, + "w": 100, + "h": 100 + }, + "idle_ne_16": { + "x": 517, + "y": 11632, + "w": 100, + "h": 100 + }, + "idle_ne_17": { + "x": 643, + "y": 11632, + "w": 100, + "h": 100 + }, + "idle_ne_18": { + "x": 13, + "y": 11764, + "w": 100, + "h": 100 + }, + "idle_ne_19": { + "x": 139, + "y": 11764, + "w": 100, + "h": 100 + }, + "idle_ne_20": { + "x": 265, + "y": 11764, + "w": 100, + "h": 100 + }, + "idle_ne_21": { + "x": 391, + "y": 11764, + "w": 100, + "h": 100 + }, + "idle_ne_22": { + "x": 517, + "y": 11764, + "w": 100, + "h": 100 + }, + "idle_ne_23": { + "x": 643, + "y": 11764, + "w": 100, + "h": 100 + }, + "idle_ne_24": { + "x": 13, + "y": 11896, + "w": 100, + "h": 100 + }, + "idle_ne_25": { + "x": 139, + "y": 11896, + "w": 100, + "h": 100 + }, + "idle_ne_26": { + "x": 265, + "y": 11896, + "w": 100, + "h": 100 + }, + "idle_ne_27": { + "x": 391, + "y": 11896, + "w": 100, + "h": 100 + }, + "idle_ne_28": { + "x": 517, + "y": 11896, + "w": 100, + "h": 100 + }, + "idle_ne_29": { + "x": 643, + "y": 11896, + "w": 100, + "h": 100 + }, + "idle_ne_30": { + "x": 13, + "y": 12028, + "w": 100, + "h": 100 + }, + "idle_nw_0": { + "x": 13, + "y": 12160, + "w": 100, + "h": 100 + }, + "idle_nw_1": { + "x": 139, + "y": 12160, + "w": 100, + "h": 100 + }, + "idle_nw_2": { + "x": 265, + "y": 12160, + "w": 100, + "h": 100 + }, + "idle_nw_3": { + "x": 391, + "y": 12160, + "w": 100, + "h": 100 + }, + "idle_nw_4": { + "x": 517, + "y": 12160, + "w": 100, + "h": 100 + }, + "idle_nw_5": { + "x": 643, + "y": 12160, + "w": 100, + "h": 100 + }, + "idle_nw_6": { + "x": 13, + "y": 12292, + "w": 100, + "h": 100 + }, + "idle_nw_7": { + "x": 139, + "y": 12292, + "w": 100, + "h": 100 + }, + "idle_nw_8": { + "x": 265, + "y": 12292, + "w": 100, + "h": 100 + }, + "idle_nw_9": { + "x": 391, + "y": 12292, + "w": 100, + "h": 100 + }, + "idle_nw_10": { + "x": 517, + "y": 12292, + "w": 100, + "h": 100 + }, + "idle_nw_11": { + "x": 643, + "y": 12292, + "w": 100, + "h": 100 + }, + "idle_nw_12": { + "x": 13, + "y": 12424, + "w": 100, + "h": 100 + }, + "idle_nw_13": { + "x": 139, + "y": 12424, + "w": 100, + "h": 100 + }, + "idle_nw_14": { + "x": 265, + "y": 12424, + "w": 100, + "h": 100 + }, + "idle_nw_15": { + "x": 391, + "y": 12424, + "w": 100, + "h": 100 + }, + "idle_nw_16": { + "x": 517, + "y": 12424, + "w": 100, + "h": 100 + }, + "idle_nw_17": { + "x": 643, + "y": 12424, + "w": 100, + "h": 100 + }, + "idle_nw_18": { + "x": 13, + "y": 12556, + "w": 100, + "h": 100 + }, + "idle_nw_19": { + "x": 139, + "y": 12556, + "w": 100, + "h": 100 + }, + "idle_nw_20": { + "x": 265, + "y": 12556, + "w": 100, + "h": 100 + }, + "idle_nw_21": { + "x": 391, + "y": 12556, + "w": 100, + "h": 100 + }, + "idle_nw_22": { + "x": 517, + "y": 12556, + "w": 100, + "h": 100 + }, + "idle_nw_23": { + "x": 643, + "y": 12556, + "w": 100, + "h": 100 + }, + "idle_nw_24": { + "x": 13, + "y": 12688, + "w": 100, + "h": 100 + }, + "idle_nw_25": { + "x": 139, + "y": 12688, + "w": 100, + "h": 100 + }, + "idle_nw_26": { + "x": 265, + "y": 12688, + "w": 100, + "h": 100 + }, + "idle_nw_27": { + "x": 391, + "y": 12688, + "w": 100, + "h": 100 + }, + "idle_nw_28": { + "x": 517, + "y": 12688, + "w": 100, + "h": 100 + }, + "idle_nw_29": { + "x": 643, + "y": 12688, + "w": 100, + "h": 100 + }, + "idle_nw_30": { + "x": 13, + "y": 12820, + "w": 100, + "h": 100 + }, + "idle_sw_0": { + "x": 13, + "y": 12952, + "w": 100, + "h": 100 + }, + "idle_sw_1": { + "x": 139, + "y": 12952, + "w": 100, + "h": 100 + }, + "idle_sw_2": { + "x": 265, + "y": 12952, + "w": 100, + "h": 100 + }, + "idle_sw_3": { + "x": 391, + "y": 12952, + "w": 100, + "h": 100 + }, + "idle_sw_4": { + "x": 517, + "y": 12952, + "w": 100, + "h": 100 + }, + "idle_sw_5": { + "x": 643, + "y": 12952, + "w": 100, + "h": 100 + }, + "idle_sw_6": { + "x": 13, + "y": 13084, + "w": 100, + "h": 100 + }, + "idle_sw_7": { + "x": 139, + "y": 13084, + "w": 100, + "h": 100 + }, + "idle_sw_8": { + "x": 265, + "y": 13084, + "w": 100, + "h": 100 + }, + "idle_sw_9": { + "x": 391, + "y": 13084, + "w": 100, + "h": 100 + }, + "idle_sw_10": { + "x": 517, + "y": 13084, + "w": 100, + "h": 100 + }, + "idle_sw_11": { + "x": 643, + "y": 13084, + "w": 100, + "h": 100 + }, + "idle_sw_12": { + "x": 13, + "y": 13216, + "w": 100, + "h": 100 + }, + "idle_sw_13": { + "x": 139, + "y": 13216, + "w": 100, + "h": 100 + }, + "idle_sw_14": { + "x": 265, + "y": 13216, + "w": 100, + "h": 100 + }, + "idle_sw_15": { + "x": 391, + "y": 13216, + "w": 100, + "h": 100 + }, + "idle_sw_16": { + "x": 517, + "y": 13216, + "w": 100, + "h": 100 + }, + "idle_sw_17": { + "x": 643, + "y": 13216, + "w": 100, + "h": 100 + }, + "idle_sw_18": { + "x": 13, + "y": 13348, + "w": 100, + "h": 100 + }, + "idle_sw_19": { + "x": 139, + "y": 13348, + "w": 100, + "h": 100 + }, + "idle_sw_20": { + "x": 265, + "y": 13348, + "w": 100, + "h": 100 + }, + "idle_sw_21": { + "x": 391, + "y": 13348, + "w": 100, + "h": 100 + }, + "idle_sw_22": { + "x": 517, + "y": 13348, + "w": 100, + "h": 100 + }, + "idle_sw_23": { + "x": 643, + "y": 13348, + "w": 100, + "h": 100 + }, + "idle_sw_24": { + "x": 13, + "y": 13480, + "w": 100, + "h": 100 + }, + "idle_sw_25": { + "x": 139, + "y": 13480, + "w": 100, + "h": 100 + }, + "idle_sw_26": { + "x": 265, + "y": 13480, + "w": 100, + "h": 100 + }, + "idle_sw_27": { + "x": 391, + "y": 13480, + "w": 100, + "h": 100 + }, + "idle_sw_28": { + "x": 517, + "y": 13480, + "w": 100, + "h": 100 + }, + "idle_sw_29": { + "x": 643, + "y": 13480, + "w": 100, + "h": 100 + }, + "idle_sw_30": { + "x": 13, + "y": 13612, + "w": 100, + "h": 100 + }, + "box": { + "x": 1010, + "y": 0, + "w": 64, + "h": 64 + }, + "green_box": { + "x": 1074, + "y": 0, + "w": 64, + "h": 64 } }, "meta": { "image": "atlas.png", - "tile_size": 16, + "tile_size": 128, "version": 1 } -} \ No newline at end of file +} diff --git a/assets/entities/atlas.png b/assets/entities/atlas.png index 651983e..485296f 100644 Binary files a/assets/entities/atlas.png and b/assets/entities/atlas.png differ diff --git a/assets/entities/genanim.py b/assets/entities/genanim.py new file mode 100644 index 0000000..f8803f5 --- /dev/null +++ b/assets/entities/genanim.py @@ -0,0 +1,216 @@ +import json + +s = 1 +sprite_frame = (126 * s, 132 * s) +sprite_size = (100 * s, 100 * s) +sprite_offset = (13, 16) +# sprite_offset = (0, 0) + +frames_map = { + "walkingforward_se": [ + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + ], + "walkingforward_ne": [ + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + ], + "walkingforward_nw": [ + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + ], + "walkingforward_sw": [ + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + ], + "walkingback_se": [ + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 0, 0, 0, 0], + ], + "walkingback_ne": [ + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 0, 0, 0, 0], + ], + "walkingback_nw": [ + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 0, 0, 0, 0], + ], + "walkingback_sw": [ + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 0, 0, 0, 0], + ], + "running_se": [ + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 0, 0, 0, 0, 0], + ], + "running_ne": [ + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 0, 0, 0, 0, 0], + ], + "running_nw": [ + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 0, 0, 0, 0, 0], + ], + "running_sw": [ + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0], + [1, 1, 0, 0, 0, 0, 0], + ], + "pushing_se": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 0, 0], + ], + "pushing_ne": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 0, 0], + ], + "pushing_nw": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 0, 0], + ], + "pushing_sw": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 0, 0], + ], + "idle_se": [ + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 0, 0, 0, 0, 0, 0], + ], + "idle_ne": [ + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 0, 0, 0, 0, 0, 0], + ], + "idle_nw": [ + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 0, 0, 0, 0, 0, 0], + ], + "idle_sw": [ + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [1, 0, 0, 0, 0, 0, 0], + ], +} + + +def generate_atlas_json(): + output_frames = {} + global_row_cursor = 0 + + for anim_key, matrix in frames_map.items(): + animation_idx = 0 + + for local_row_idx, row_data in enumerate(matrix): + for local_col_idx, has_sprite in enumerate(row_data): + if has_sprite == 1: + current_global_row = global_row_cursor + local_row_idx + + px_x = (local_col_idx * sprite_frame[0]) + sprite_offset[0] + px_y = (current_global_row * sprite_frame[1]) + sprite_offset[1] + + frame_key = f"{anim_key}_{animation_idx}" + + output_frames[frame_key] = { + "x": px_x, + "y": px_y, + "w": sprite_size[0], + "h": sprite_size[1], + } + + animation_idx += 1 + + global_row_cursor += len(matrix) + + final_json = { + "frames": output_frames, + "meta": {"image": "atlas.png", "tile_size": sprite_frame[0], "version": 1}, + } + + return final_json + + +if __name__ == "__main__": + data = generate_atlas_json() + + output_path = "atlas.json" + with open(output_path, "w") as f: + json.dump(data, f, indent=4) + + print(f"Generated {output_path}. May delete current frame config") + +s = """ +, + "box": { + "x": 1010, + "y": 0, + "w": 64, + "h": 64 + }, + "green_box": { + "x": 1074, + "y": 0, + "w": 64, + "h": 64 + } +""" diff --git a/assets/tiles/atlas.json b/assets/tiles/atlas.json index 82c37d7..af6c357 100644 --- a/assets/tiles/atlas.json +++ b/assets/tiles/atlas.json @@ -1,75 +1,249 @@ { "frames": { - "dirt_tile_big_0_0": { + "floor": { "x": 0, - "y": 1, - "w": 16, - "h": 16 + "y": 1152, + "w": 128, + "h": 128 }, - "grass_tile_big_0_1": { - "x": 16, + "concrete": { + "x": 256, + "y": 1152, + "w": 128, + "h": 128 + }, + "target": { + "x": 128, + "y": 1152, + "w": 128, + "h": 128 + }, + "wall_tile": { + "x": 0, + "y": 2304, + "w": 128, + "h": 128 + }, + "letter_A": { + "x": 0, + "y": 0, + "w": 128, + "h": 128 + }, + "letter_B": { + "x": 128, "y": 0, - "w": 16, - "h": 17 - }, - "rock_tile_big_0_2": { - "x": 32, - "y": 16, - "w": 16, - "h": 16 - }, - "sand_tile_big_0_3": { - "x": 48, - "y": 16, - "w": 16, - "h": 16 - }, - "dirt_tile_small_1_0": { + "w": 128, + "h": 128 + }, + "letter_C": { + "x": 256, + "y": 0, + "w": 128, + "h": 128 + }, + "letter_D": { + "x": 384, + "y": 0, + "w": 128, + "h": 128 + }, + "letter_E": { + "x": 0, + "y": 128, + "w": 128, + "h": 128 + }, + "letter_F": { + "x": 128, + "y": 128, + "w": 128, + "h": 128 + }, + "letter_G": { + "x": 256, + "y": 128, + "w": 128, + "h": 128 + }, + "letter_H": { + "x": 384, + "y": 128, + "w": 128, + "h": 128 + }, + "letter_I": { + "x": 0, + "y": 256, + "w": 128, + "h": 128 + }, + "letter_J": { + "x": 128, + "y": 256, + "w": 128, + "h": 128 + }, + "letter_K": { + "x": 256, + "y": 256, + "w": 128, + "h": 128 + }, + "letter_L": { + "x": 384, + "y": 256, + "w": 128, + "h": 128 + }, + "letter_M": { + "x": 0, + "y": 384, + "w": 128, + "h": 128 + }, + "letter_N": { + "x": 128, + "y": 384, + "w": 128, + "h": 128 + }, + "letter_O": { + "x": 256, + "y": 384, + "w": 128, + "h": 128 + }, + "letter_P": { + "x": 384, + "y": 384, + "w": 128, + "h": 128 + }, + "letter_Q": { + "x": 0, + "y": 512, + "w": 128, + "h": 128 + }, + "letter_R": { + "x": 128, + "y": 512, + "w": 128, + "h": 128 + }, + "letter_S": { + "x": 256, + "y": 512, + "w": 128, + "h": 128 + }, + "letter_T": { + "x": 384, + "y": 512, + "w": 128, + "h": 128 + }, + "letter_U": { "x": 0, - "y": 33, - "w": 16, - "h": 16 - }, - "grass_tile_small_1_1": { - "x": 16, - "y": 33, - "w": 16, - "h": 16 - }, - "rock_tile_small_1_2": { - "x": 32, - "y": 33, - "w": 16, - "h": 16 - }, - "sand_tile_small_1_3": { - "x": 48, - "y": 33, - "w": 16, - "h": 16 - }, - "cactus_long_3_9": { - "x": 148, - "y": 52, - "w": 10, - "h": 16 - }, - "fence_rising_11_10": { - "x": 162, - "y": 153, - "w": 16, - "h": 16 - }, - "fence_falling_10_10": { - "x": 162, - "y": 136, - "w": 16, - "h": 16 + "y": 640, + "w": 128, + "h": 128 + }, + "letter_V": { + "x": 128, + "y": 640, + "w": 128, + "h": 128 + }, + "letter_W": { + "x": 256, + "y": 640, + "w": 128, + "h": 128 + }, + "letter_X": { + "x": 384, + "y": 640, + "w": 128, + "h": 128 + }, + "letter_Y": { + "x": 0, + "y": 768, + "w": 128, + "h": 128 + }, + "letter_Z": { + "x": 128, + "y": 768, + "w": 128, + "h": 128 + }, + "letter_1": { + "x": 256, + "y": 768, + "w": 128, + "h": 128 + }, + "letter_2": { + "x": 384, + "y": 768, + "w": 128, + "h": 128 + }, + "letter_3": { + "x": 0, + "y": 896, + "w": 128, + "h": 128 + }, + "letter_4": { + "x": 128, + "y": 896, + "w": 128, + "h": 128 + }, + "letter_5": { + "x": 256, + "y": 896, + "w": 128, + "h": 128 + }, + "letter_6": { + "x": 384, + "y": 896, + "w": 128, + "h": 128 + }, + "letter_7": { + "x": 0, + "y": 1024, + "w": 128, + "h": 128 + }, + "letter_8": { + "x": 128, + "y": 1024, + "w": 128, + "h": 128 + }, + "letter_9": { + "x": 256, + "y": 1024, + "w": 128, + "h": 128 + }, + "letter_0": { + "x": 384, + "y": 1024, + "w": 128, + "h": 128 } }, "meta": { "image": "atlas.png", - "tile_size": 16, + "tile_size": 128, "version": 1 } -} \ No newline at end of file +} diff --git a/assets/tiles/atlas.png b/assets/tiles/atlas.png index b7b7f89..9ef0bed 100644 Binary files a/assets/tiles/atlas.png and b/assets/tiles/atlas.png differ diff --git a/demo.gif b/demo.gif new file mode 100644 index 0000000..1b7f088 Binary files /dev/null and b/demo.gif differ diff --git a/engine/Cargo.toml b/engine/Cargo.toml new file mode 100644 index 0000000..5a9d47e --- /dev/null +++ b/engine/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "ferari" +version = "1.0.2" +edition = "2021" +description = "Fast Engine for Rendering Axonometric Rust-based Isometry" +authors = ["Rodion Suvorov , Ilhom Kombaev , Vyacheslav Kochergin , Dmitri Kuznetsov "] +license = "MIT" +repository = "https://github.com/suvorovrain/Ferari" +readme = "README.md" + +[lib] +name = "ferari" +path = "lib.rs" + +[dependencies] +minifb = "0.28" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +image = "0.25" +crossbeam-channel = "0.5.15" + +[dev-dependencies] diff --git a/src/assets/atlas.rs b/engine/assets/atlas.rs similarity index 59% rename from src/assets/atlas.rs rename to engine/assets/atlas.rs index d6974de..0feee52 100644 --- a/src/assets/atlas.rs +++ b/engine/assets/atlas.rs @@ -184,86 +184,38 @@ mod tests { // Test atlas JSON parsing on example #[test] - fn test_load_entities_atlas() { - let atlas = Atlas::load("assets/tiles/atlas.json").unwrap(); + fn test_load_tiles_atlas() { + let atlas = Atlas::load("../assets/tiles/atlas.json").unwrap(); - assert_eq!(atlas.tile_size, 16); + assert_eq!(atlas.tile_size, 128); assert_eq!(atlas.version, 1); - assert_eq!(atlas.frame_count(), 11); + assert_eq!(atlas.frame_count(), 40); - assert!(atlas.contains_frame("dirt_tile_big_0_0")); - assert!(atlas.contains_frame("grass_tile_big_0_1")); - assert!(atlas.contains_frame("rock_tile_big_0_2")); - assert!(atlas.contains_frame("sand_tile_big_0_3")); - assert!(atlas.contains_frame("dirt_tile_small_1_0")); - assert!(atlas.contains_frame("grass_tile_small_1_1")); - assert!(atlas.contains_frame("rock_tile_small_1_2")); - assert!(atlas.contains_frame("sand_tile_small_1_3")); - assert!(atlas.contains_frame("cactus_long_3_9")); - assert!(atlas.contains_frame("fence_rising_11_10")); - assert!(atlas.contains_frame("fence_falling_10_10")); + assert!(atlas.contains_frame("floor")); + assert!(atlas.contains_frame("target")); + assert!(atlas.contains_frame("letter_1")); - let dirt_big_frame = atlas.get_frame("dirt_tile_big_0_0").unwrap(); - assert_eq!(dirt_big_frame.name, "dirt_tile_big_0_0"); + let dirt_big_frame = atlas.get_frame("floor").unwrap(); + assert_eq!(dirt_big_frame.name, "floor"); assert_eq!(dirt_big_frame.x, 0); - assert_eq!(dirt_big_frame.y, 1); - assert_eq!(dirt_big_frame.w, 16); - assert_eq!(dirt_big_frame.h, 16); - - let grass_big_frame = atlas.get_frame("grass_tile_big_0_1").unwrap(); - assert_eq!(grass_big_frame.name, "grass_tile_big_0_1"); - assert_eq!(grass_big_frame.x, 16); - assert_eq!(grass_big_frame.y, 0); - assert_eq!(grass_big_frame.w, 16); - assert_eq!(grass_big_frame.h, 17); - - let dirt_small_frame = atlas.get_frame("dirt_tile_small_1_0").unwrap(); - assert_eq!(dirt_small_frame.name, "dirt_tile_small_1_0"); - assert_eq!(dirt_small_frame.x, 0); - assert_eq!(dirt_small_frame.y, 33); - assert_eq!(dirt_small_frame.w, 16); - assert_eq!(dirt_small_frame.h, 16); - - let cactus_frame = atlas.get_frame("cactus_long_3_9").unwrap(); - assert_eq!(cactus_frame.name, "cactus_long_3_9"); - assert_eq!(cactus_frame.x, 148); - assert_eq!(cactus_frame.y, 52); - assert_eq!(cactus_frame.w, 10); - assert_eq!(cactus_frame.h, 16); - - let fence_rising_frame = atlas.get_frame("fence_rising_11_10").unwrap(); - assert_eq!(fence_rising_frame.name, "fence_rising_11_10"); - assert_eq!(fence_rising_frame.x, 162); - assert_eq!(fence_rising_frame.y, 153); - assert_eq!(fence_rising_frame.w, 16); - assert_eq!(fence_rising_frame.h, 16); - - let fence_falling_frame = atlas.get_frame("fence_falling_10_10").unwrap(); - assert_eq!(fence_falling_frame.name, "fence_falling_10_10"); - assert_eq!(fence_falling_frame.x, 162); - assert_eq!(fence_falling_frame.y, 136); - assert_eq!(fence_falling_frame.w, 16); - assert_eq!(fence_falling_frame.h, 16); - - let mut frame_names: Vec = atlas.iter_frames().map(|f| f.name.clone()).collect(); - frame_names.sort(); - assert_eq!( - frame_names, - vec![ - "cactus_long_3_9", - "dirt_tile_big_0_0", - "dirt_tile_small_1_0", - "fence_falling_10_10", - "fence_rising_11_10", - "grass_tile_big_0_1", - "grass_tile_small_1_1", - "rock_tile_big_0_2", - "rock_tile_small_1_2", - "sand_tile_big_0_3", - "sand_tile_small_1_3" - ] - ); + assert_eq!(dirt_big_frame.y, 1152); + assert_eq!(dirt_big_frame.w, 128); + assert_eq!(dirt_big_frame.h, 128); + + let grass_big_frame = atlas.get_frame("target").unwrap(); + assert_eq!(grass_big_frame.name, "target"); + assert_eq!(grass_big_frame.x, 128); + assert_eq!(grass_big_frame.y, 1152); + assert_eq!(grass_big_frame.w, 128); + assert_eq!(grass_big_frame.h, 128); + + let dirt_small_frame = atlas.get_frame("letter_1").unwrap(); + assert_eq!(dirt_small_frame.name, "letter_1"); + assert_eq!(dirt_small_frame.x, 256); + assert_eq!(dirt_small_frame.y, 768); + assert_eq!(dirt_small_frame.w, 128); + assert_eq!(dirt_small_frame.h, 128); assert!(!atlas.image.is_empty()); } diff --git a/src/assets/gamemap.rs b/engine/assets/gamemap.rs similarity index 69% rename from src/assets/gamemap.rs rename to engine/assets/gamemap.rs index 25cfffa..f51763f 100644 --- a/src/assets/gamemap.rs +++ b/engine/assets/gamemap.rs @@ -1,9 +1,12 @@ use serde::Deserialize; -use std::collections::HashMap; +use std::collections::{HashMap, LinkedList}; use std::error::Error; use std::fs::File; use std::io::BufReader; use std::path::Path; +use std::vec; + +// TODO: delete mobs from json! // ============================ // JSON-level structs @@ -63,6 +66,25 @@ pub struct JsonObject { pub shadow: bool, } +/// Tile type that defines its properties in game logic. +#[derive(Default, Deserialize, Debug, Clone)] +pub enum TileType { + /// Passable empty tile + #[default] + #[serde(rename = "empty")] + Empty, + /// Impenetrable wall + #[serde(rename = "wall")] + Wall, + /// Target point + #[serde(rename = "target")] + Target, + + /// Link to some id (e.g. map id ) + #[serde(rename = "link")] + Link(u32), +} + /// Tile data from JSON. #[derive(Deserialize, Debug, Clone)] pub struct JsonTile { @@ -72,6 +94,10 @@ pub struct JsonTile { pub y: u32, /// Asset identifier for the tile's appearance pub asset: String, + + /// Logical tile type (default is `Empty`) + #[serde(default)] + pub tile_type: TileType, } /// Meta information about the game map from JSON. @@ -177,6 +203,8 @@ pub struct Tile { pub y: u32, /// Asset identifier for the tile's appearance pub asset: String, + /// Logical type of tile + pub tile_type: TileType, } /// Game map, as parsed and ready to use. @@ -195,6 +223,14 @@ pub struct GameMap { pub objects: HashMap, /// Mapping of tiles' names to their definitions pub tiles: HashMap, + /// Vector representing x,y positions for all targets + pub target_positions: LinkedList<(u32, u32)>, + /// Mapping of (link) tyle coordinates x,y to due id + pub links: HashMap<(u32, u32), u32>, + /// 1D vector representing the map's logical tile types + pub walk_map: Vec, + /// 1D vector indicating if a tile is occupied by a collidable static object + pub object_collidable_map: Vec, } // ============================ @@ -216,6 +252,10 @@ impl GameMap { let reader = BufReader::new(file); let map_json: JsonMap = serde_json::from_reader(reader)?; + let width = map_json.meta.size[0] as usize; + let height = map_json.meta.size[1] as usize; + + // Process Mobs let mut mobs = HashMap::new(); for (name, mob_data) in map_json.mobs { let behaviour = mob_data.behaviour.as_ref().map(|b| Behaviour { @@ -239,7 +279,10 @@ impl GameMap { mobs.insert(name, mob); } + // Process Objects and build Collidable map + // TODO: make normal asset parsing let mut objects = HashMap::new(); + let mut object_collidable_map = vec![false; width * height]; for (name, obj_data) in map_json.objects { let object = Object { name: name.clone(), @@ -249,13 +292,40 @@ impl GameMap { collidable: obj_data.collidable, shadow: obj_data.shadow, }; + + let idx = (object.y) as usize * width + (object.x) as usize; + if idx < (width * height) && object.collidable { + object_collidable_map[idx] = true; + } + objects.insert(name, object); } + // Process Tiles and build Walk map let mut tiles = HashMap::new(); + let mut walk_map = vec![TileType::Empty; width * height]; + let mut target_positions = LinkedList::new(); + let mut links = HashMap::new(); for (name, tile_data) in map_json.tiles { - let tile = - Tile { name: name.clone(), x: tile_data.x, y: tile_data.y, asset: tile_data.asset }; + let tile = Tile { + name: name.clone(), + x: tile_data.x, + y: tile_data.y, + asset: tile_data.asset, + tile_type: tile_data.tile_type, + }; + + let idx = tile.y as usize * width + tile.x as usize; + if idx < (width * height) { + walk_map[idx] = tile.tile_type.clone(); + } + match tile.tile_type { + TileType::Target => target_positions.push_front((tile.x, tile.y)), + TileType::Link(id) => { + links.insert((tile_data.x, tile_data.y), id); + } + _ => (), + } tiles.insert(name, tile); } @@ -266,6 +336,10 @@ impl GameMap { mobs, objects, tiles, + walk_map, + object_collidable_map, + target_positions, + links, }) } @@ -369,6 +443,40 @@ impl GameMap { pub fn iter_tiles(&self) -> impl Iterator { self.tiles.values() } + + /// Checks whether the tile with coordinates `(target_x, target_y)` is passable. + /// + /// # Arguments + /// + /// * `target_x`, `target_y` - tile coordinates + /// + /// # Returns + /// + /// * `true` if the tile is within the map bounds and is not a wall. + /// * `false` — otherwise. + pub fn is_walkable(&self, target_x: i32, target_y: i32) -> bool { + let width = self.size[0] as i32; + let height = self.size[1] as i32; + + if target_x < 0 || target_y < 0 || target_x >= width || target_y >= height { + return false; + } + + let idx = (target_y as usize) * (width as usize) + (target_x as usize); + !matches!(self.walk_map[idx], TileType::Wall) + } + + /// Checks whether there is a collidable object at the given tile coordinates. + /// + /// # Returns + /// + /// * `true` if a collidable object exists at this position, `false` otherwise. + pub fn has_collidable_object_at(&self, tile_x: i32, tile_y: i32) -> bool { + let width = self.size[0] as i32; + + let idx = (tile_y as usize) * (width as usize) + (tile_x as usize); + self.object_collidable_map[idx] + } } impl Mob { @@ -418,21 +526,21 @@ mod tests { // Test game map parsing on example #[test] fn test_load_game_map() { - let game_map = GameMap::load("input.json").unwrap(); + let game_map = GameMap::load("../game_levels/menu.json").unwrap(); - assert_eq!(game_map.name, "demo_map"); - assert_eq!(game_map.tile_size, 16); - assert_eq!(game_map.size, [25, 25]); + assert_eq!(game_map.name, "menu"); + assert_eq!(game_map.tile_size, 128); + assert_eq!(game_map.size, [6, 5]); assert_eq!(game_map.mob_count(), 6); - assert_eq!(game_map.object_count(), 3); - assert_eq!(game_map.tile_count(), 625); + assert_eq!(game_map.object_count(), 0); + assert_eq!(game_map.tile_count(), 16); let player = game_map.get_mob("player").unwrap(); assert_eq!(player.name, "player"); assert_eq!(player.x_start, 0); - assert_eq!(player.y_start, 0); - assert_eq!(player.asset, "knight_0_0"); + assert_eq!(player.y_start, 2); + assert_eq!(player.asset, "running_se_0"); assert_eq!(player.is_player, true); assert!(player.behaviour.is_some()); @@ -440,119 +548,30 @@ mod tests { assert_eq!(player_behaviour.behaviour_type, BehaviourType::Controlled); assert_eq!(player_behaviour.direction, None); assert_eq!(player_behaviour.speed, None); - assert_eq!(player.start_position(), (0, 0)); + assert_eq!(player.start_position(), (0, 2)); - let mob_1 = game_map.get_mob("mob_1").unwrap(); - assert_eq!(mob_1.name, "mob_1"); - assert_eq!(mob_1.x_start, 440); - assert_eq!(mob_1.y_start, 470); - assert_eq!(mob_1.asset, "imp_20_0"); + let mob_1 = game_map.get_mob("box_1").unwrap(); + assert_eq!(mob_1.name, "box_1"); + assert_eq!(mob_1.x_start, 0); + assert_eq!(mob_1.y_start, 3); + assert_eq!(mob_1.asset, "box"); assert_eq!(mob_1.is_player, false); - assert!(mob_1.behaviour.is_some()); - - let mob_1_behaviour = mob_1.behaviour.as_ref().unwrap(); - assert_eq!(mob_1_behaviour.behaviour_type, BehaviourType::Walker); - assert_eq!(mob_1_behaviour.direction, Some("left".to_string())); - assert_eq!(mob_1_behaviour.speed, Some(0.5)); - assert_eq!(mob_1.start_position(), (440, 470)); - - let mob_2 = game_map.get_mob("mob_2").unwrap(); - assert_eq!(mob_2.name, "mob_2"); - assert_eq!(mob_2.x_start, 400); - assert_eq!(mob_2.y_start, 460); - assert_eq!(mob_2.asset, "ghost_30_0"); - assert_eq!(mob_2.is_player, false); - assert!(mob_2.behaviour.is_some()); - - let mob_2_behaviour = mob_2.behaviour.as_ref().unwrap(); - assert_eq!(mob_2_behaviour.behaviour_type, BehaviourType::Walker); - assert_eq!(mob_2_behaviour.direction, Some("right".to_string())); - assert_eq!(mob_2_behaviour.speed, Some(0.42)); - assert_eq!(mob_2.start_position(), (400, 460)); - - let mob_4 = game_map.get_mob("mob_4").unwrap(); - assert_eq!(mob_4.name, "mob_4"); - assert_eq!(mob_4.x_start, 420); - assert_eq!(mob_4.y_start, 470); - assert_eq!(mob_4.asset, "imp_20_0"); - assert_eq!(mob_4.is_player, false); - assert!(mob_4.behaviour.is_some()); - - let mob_5 = game_map.get_mob("mob_5").unwrap(); - assert_eq!(mob_5.name, "mob_5"); - assert_eq!(mob_5.x_start, 493); - assert_eq!(mob_5.y_start, 470); - assert_eq!(mob_5.asset, "imp_20_0"); - assert_eq!(mob_5.is_player, false); - assert!(mob_5.behaviour.is_some()); - - let mob_6 = game_map.get_mob("mob_6").unwrap(); - assert_eq!(mob_6.name, "mob_6"); - assert_eq!(mob_6.x_start, 540); - assert_eq!(mob_6.y_start, 470); - assert_eq!(mob_6.asset, "imp_20_0"); - assert_eq!(mob_6.is_player, false); - assert!(mob_6.behaviour.is_some()); - - let obj_1 = game_map.get_object("obj_1").unwrap(); - assert_eq!(obj_1.name, "obj_1"); - assert_eq!(obj_1.x, 2); - assert_eq!(obj_1.y, 1); - assert_eq!(obj_1.asset, "cactus_long_3_9"); - assert_eq!(obj_1.collidable, false); - assert_eq!(obj_1.shadow, false); - assert_eq!(obj_1.position(), (2, 1)); - - let obj_2 = game_map.get_object("obj_2").unwrap(); - assert_eq!(obj_2.name, "obj_2"); - assert_eq!(obj_2.x, 4); - assert_eq!(obj_2.y, 14); - assert_eq!(obj_2.asset, "fence_rising_11_10"); - assert_eq!(obj_2.collidable, false); - assert_eq!(obj_2.shadow, false); - assert_eq!(obj_2.position(), (4, 14)); - - let obj_3 = game_map.get_object("obj_3").unwrap(); - assert_eq!(obj_3.name, "obj_3"); - assert_eq!(obj_3.x, 8); - assert_eq!(obj_3.y, 15); - assert_eq!(obj_3.asset, "fence_falling_10_10"); - assert_eq!(obj_3.collidable, false); - assert_eq!(obj_3.shadow, false); - assert_eq!(obj_3.position(), (8, 15)); - - let tile_1 = game_map.get_tile("tile_1").unwrap(); - assert_eq!(tile_1.name, "tile_1"); + + let tile_1 = game_map.get_tile("letter_1").unwrap(); + assert_eq!(tile_1.name, "letter_1"); assert_eq!(tile_1.x, 0); assert_eq!(tile_1.y, 0); - assert_eq!(tile_1.asset, "grass_tile_big_0_1"); + assert_eq!(tile_1.asset, "letter_C"); assert_eq!(tile_1.position(), (0, 0)); - let tile_625 = game_map.get_tile("tile_625").unwrap(); - assert_eq!(tile_625.name, "tile_625"); - assert_eq!(tile_625.x, 24); - assert_eq!(tile_625.y, 24); - assert_eq!(tile_625.asset, "grass_tile_big_0_1"); - assert_eq!(tile_625.position(), (24, 24)); - let mob_names: Vec = game_map.iter_mobs().map(|m| m.name.clone()).collect(); - assert_eq!(mob_names.len(), 6); // was 3 + assert_eq!(mob_names.len(), 6); assert!(mob_names.contains(&"player".to_string())); - assert!(mob_names.contains(&"mob_1".to_string())); - assert!(mob_names.contains(&"mob_2".to_string())); - assert!(mob_names.contains(&"mob_4".to_string())); - assert!(mob_names.contains(&"mob_5".to_string())); - assert!(mob_names.contains(&"mob_6".to_string())); - - let object_names: Vec = game_map.iter_objects().map(|o| o.name.clone()).collect(); - assert_eq!(object_names.len(), 3); - assert!(object_names.contains(&"obj_1".to_string())); - assert!(object_names.contains(&"obj_2".to_string())); - assert!(object_names.contains(&"obj_3".to_string())); + assert!(mob_names.contains(&"box_1".to_string())); + assert!(mob_names.contains(&"box_2".to_string())); let tile_names: Vec = game_map.iter_tiles().map(|t| t.name.clone()).collect(); - assert_eq!(tile_names.len(), 625); - assert!(tile_names.contains(&"tile_1".to_string())); - assert!(tile_names.contains(&"tile_625".to_string())); + assert_eq!(tile_names.len(), 16); + assert!(tile_names.contains(&"letter_1".to_string())); } } diff --git a/src/assets/mod.rs b/engine/assets/mod.rs similarity index 65% rename from src/assets/mod.rs rename to engine/assets/mod.rs index 48fa519..d18e08c 100644 --- a/src/assets/mod.rs +++ b/engine/assets/mod.rs @@ -5,4 +5,4 @@ pub use atlas::{Atlas, Frame}; pub use gamemap::{GameMap, Object, Tile}; #[cfg(test)] -pub use gamemap::{Behaviour, BehaviourType, Mob}; +pub use gamemap::{Behaviour, BehaviourType, Mob, TileType}; diff --git a/src/draw.rs b/engine/draw.rs similarity index 96% rename from src/draw.rs rename to engine/draw.rs index 769b3e2..b833d0b 100644 --- a/src/draw.rs +++ b/engine/draw.rs @@ -1,9 +1,7 @@ use crossbeam_channel::Receiver; use minifb::{Key, Window, WindowOptions}; -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, -}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::thread; use std::time::Duration; diff --git a/src/input.rs b/engine/input.rs similarity index 80% rename from src/input.rs rename to engine/input.rs index 4f61bc6..f10e3db 100644 --- a/src/input.rs +++ b/engine/input.rs @@ -55,17 +55,27 @@ impl InputState { /// Updates the input state by querying the current key states from the window. /// - /// This method checks the current state of the tracked keys (W, A, S, D, Escape) - /// in the provided window and updates the internal values accordingly. + /// This method checks the current state of movement and control keys in the provided + /// window and updates the internal atomic flags accordingly. Both `WASD` and `arrow keys` + /// are supported for directional input: + /// - Up: `W` or `↑` + /// - Down: `S` or `↓` + /// - Left: `A` or `←` + /// - Right: `D` or `→` + /// + /// The Escape key is used for pausing or exiting the game. /// /// # Parameters /// /// * `window` - A reference to the minifb `Window` to query for key states pub fn update(&self, window: &Window) { - self.up.store(window.is_key_down(Key::W), Ordering::Relaxed); - self.down.store(window.is_key_down(Key::S), Ordering::Relaxed); - self.left.store(window.is_key_down(Key::A), Ordering::Relaxed); - self.right.store(window.is_key_down(Key::D), Ordering::Relaxed); + self.up.store(window.is_key_down(Key::W) || window.is_key_down(Key::Up), Ordering::Relaxed); + self.down + .store(window.is_key_down(Key::S) || window.is_key_down(Key::Down), Ordering::Relaxed); + self.left + .store(window.is_key_down(Key::A) || window.is_key_down(Key::Left), Ordering::Relaxed); + self.right + .store(window.is_key_down(Key::D) || window.is_key_down(Key::Right), Ordering::Relaxed); self.escape.store(window.is_key_down(Key::Escape), Ordering::Relaxed); } @@ -88,6 +98,12 @@ impl InputState { } } +impl Default for InputState { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/engine/lib.rs b/engine/lib.rs new file mode 100644 index 0000000..dab9f3f --- /dev/null +++ b/engine/lib.rs @@ -0,0 +1,8 @@ +pub mod assets; +pub mod draw; +pub mod input; +pub mod render; +pub mod time; +pub mod world; + +pub use render::{Render, RenderableEntity}; diff --git a/src/render/mod.rs b/engine/render/mod.rs similarity index 67% rename from src/render/mod.rs rename to engine/render/mod.rs index ff55c92..48df8f9 100644 --- a/src/render/mod.rs +++ b/engine/render/mod.rs @@ -1,3 +1,4 @@ #[allow(clippy::module_inception)] mod render; pub use render::Render; +pub use render::RenderableEntity; diff --git a/src/render/render.rs b/engine/render/render.rs similarity index 66% rename from src/render/render.rs rename to engine/render/render.rs index 61e19d3..dec665c 100644 --- a/src/render/render.rs +++ b/engine/render/render.rs @@ -1,6 +1,23 @@ use crate::assets::{Atlas, Frame, GameMap, Object, Tile}; -use crate::time::Time; -use crate::world::{Camera, Unit}; +use crate::world::Camera; + +/// Represents an entity that can be rendered +#[derive(Clone)] +pub struct RenderableEntity { + pub x: f32, + pub y: f32, + pub sprite_name: String, +} + +impl RenderableEntity { + pub fn new(x: f32, y: f32, sprite_name: String) -> Self { + Self { x, y, sprite_name } + } + + pub fn with_sprite(x: f32, y: f32, sprite_name: &str) -> Self { + Self::new(x, y, sprite_name.to_string()) + } +} /// The `Render` struct handles isometric projection rendering with shadow mapping /// and dynamic entity animation. It maintains world buffers and uses atlas for @@ -16,6 +33,8 @@ pub struct Render { pub world_buf: Vec, /// Shadow intensity map for shadow calculations pub shadow_map: Vec, + /// Temporary shadow buffer for dynamic objects in current frame + pub dynamic_shadow_buf: Vec, } impl Render { @@ -45,6 +64,7 @@ impl Render { shadow_map: shadow, world_height: height, world_width: width, + dynamic_shadow_buf: vec![0; height * width], } } @@ -60,7 +80,7 @@ impl Render { pub fn init(&mut self, game: &GameMap, static_atlas: &Atlas) { let mut tiles: Vec = (*game).clone().tiles.into_values().collect(); tiles.sort_by(|a, b| (a.x + a.y).cmp(&(b.x + b.y))); - // base screen offsets + // Base screen offsets let offset_x = self.world_width as i32 / 2; let offset_y = (self.world_height as i32 / 2) - (static_atlas.tile_size as i32); @@ -69,7 +89,7 @@ impl Render { let fw = frame.w as i32; let fh = frame.h as i32; - // isometric projection + // Isometric projection let screen_x = (tile.x as i32 - tile.y as i32) * (fw / 2) + offset_x; let screen_y = (tile.x as i32 + tile.y as i32) * (fh / 4) + offset_y - (fh / 2); @@ -82,15 +102,36 @@ impl Render { let offset_x = self.world_width as i32 / 2; let offset_y = (self.world_height as i32 / 2) - (static_atlas.tile_size as i32); - for object in objects { + const TEXTURE_OFFSET: i32 = 64; // TODO: CURRENTLY DEPENDS ON TEXTURES + + // First render all shadows using references + for object in &objects { if let Some(frame) = static_atlas.get_frame(&object.asset) { let fw = frame.w as i32; let fh = frame.h as i32; - // isometric projection + // Isometric projection let screen_x = (object.x as i32 - object.y as i32) * (fw / 2) + offset_x; - let screen_y = (object.x as i32 + object.y as i32) * (fh / 4) + offset_y - (fh / 2); + let screen_y = (object.x as i32 + object.y as i32) * (fh / 4) + offset_y + - (fh / 2) + - TEXTURE_OFFSET; self.render_shadow(frame, screen_x, screen_y, static_atlas); + } + } + + // Apply blur once after all shadows are rendered + self.soft_blur_shadows(); + + // Then render all objects using references + for object in &objects { + if let Some(frame) = static_atlas.get_frame(&object.asset) { + let fw = frame.w as i32; + let fh = frame.h as i32; + + let screen_x = (object.x as i32 - object.y as i32) * (fw / 2) + offset_x; + let screen_y = (object.x as i32 + object.y as i32) * (fh / 4) + offset_y + - (fh / 2) + - TEXTURE_OFFSET; self.render_object(frame, screen_x, screen_y, static_atlas); } } @@ -106,19 +147,17 @@ impl Render { /// * `visible_things` - List of units visible in the current frame /// * `camera` - Camera configuration defining viewport and position /// * `buf` - Output pixel buffer to render into - /// * `time` - Current time for animation timing pub fn render_frame( &mut self, - visible_things: &[Unit], + visible_entities: &[RenderableEntity], camera: &Camera, buf: &mut [u32], - time: &Time, ) { // TODO: ADD STATE HANDLING let world_w = self.world_width as i32; let world_h = self.world_height as i32; - // left-top + // Left-top let half_w = camera.width as i32 / 2; let half_h = camera.height as i32 / 2; @@ -128,7 +167,7 @@ impl Render { let cam_right = camera.center_x as i32 + half_w; let cam_bottom = camera.center_y as i32 + half_h; - // clear buf + // Clear buffer for px in buf.iter_mut() { *px = 0; } @@ -136,14 +175,14 @@ impl Render { let view_w = (cam_right - cam_left) as usize; let view_h = (cam_bottom - cam_top) as usize; - // assert sizes + // Assert sizes assert_eq!( buf.len(), (camera.width as usize) * (camera.height as usize), "Buffer size must match camera viewport" ); - // copy visible world + // Copy visible world for y in 0..view_h { let world_y = cam_top + y as i32; if world_y < 0 || world_y >= world_h { @@ -162,25 +201,63 @@ impl Render { } } - // dynamic objects - for (i, unit) in visible_things.iter().enumerate() { - let name_model = if i == 0 { "knight_0" } else { "imp_20" }; - let period = 0.4; - let cycles = (time.total / period).floor() as u32; - let animation_num = if cycles.is_multiple_of(2) { "_0" } else { "_1" }; - let full_name = name_model.to_string() + animation_num; - if let Some(frame) = self.entity_atlas.get_frame(&full_name) { + // Dynamic objects + + // Sort entities by depth for correct rendering order + let mut sorted_entities: Vec<&RenderableEntity> = visible_entities.iter().collect(); + sorted_entities.sort_by(|a, b| { + // Primary sort by Y coordinate (higher Y = closer to camera) + let y_cmp = a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal); + if y_cmp != std::cmp::Ordering::Equal { + y_cmp + } else { + // Secondary sort by X coordinate if Y is equal + a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal) + } + }); + + // Reset the shadow temporary buffer + self.dynamic_shadow_buf.fill(0); + + // Collect all shadow rendering data first + let mut shadow_render_data = Vec::new(); + for entity in sorted_entities.iter() { + if let Some(frame) = self.entity_atlas.get_frame(&entity.sprite_name) { let fw = frame.w as i32; let fh = frame.h as i32; let screen_x = - (unit.x as i32 - camera.center_x as i32) + camera.width as i32 / 2 - fw / 2; + (entity.x as i32 - camera.center_x as i32) + camera.width as i32 / 2 - fw / 2; let screen_y = - (unit.y as i32 - camera.center_y as i32) + camera.height as i32 / 2 - fh / 2; - self.render_shadow_unit(frame, screen_x, screen_y, buf, camera); - self.render_unit(frame, screen_x, screen_y, buf, camera); + (entity.y as i32 - camera.center_y as i32) + camera.height as i32 / 2 - fh; + + shadow_render_data.push((frame.clone(), screen_x, screen_y)); } } + + // Render shadows + for (frame, screen_x, screen_y) in &shadow_render_data { + self.render_shadow_unit(frame, *screen_x, *screen_y, buf, camera); + } + + // Then render all objects + for (frame, screen_x, screen_y) in &shadow_render_data { + self.render_unit(frame, *screen_x, *screen_y, buf, camera); + } + } + + /// Gets shadow intensity at world coordinates + pub fn get_shadow_intensity(&self, world_x: i32, world_y: i32) -> f32 { + if world_x >= 0 + && world_y >= 0 + && world_x < self.world_width as i32 + && world_y < self.world_height as i32 + { + let shadow_idx = (world_y * self.world_width as i32 + world_x) as usize; + self.shadow_map[shadow_idx] as f32 / 255.0 + } else { + 0.0 + } } /// Renders a unit to the world buffer @@ -212,7 +289,8 @@ impl Render { } let color = self.entity_atlas.image.get_pixel(src_x as u32, src_y as u32); - if color[3] == 0 { + let src_a = color[3] as u32; + if src_a == 0 { continue; } @@ -231,24 +309,32 @@ impl Render { let world_x = (camera.center_x as i32 - camera.width as i32 / 2) + dest_x; let world_y = (camera.center_y as i32 - camera.height as i32 / 2) + dest_y; - let mut brightness = 1.0; - if world_x >= 0 - && world_y >= 0 - && world_x < self.world_width as i32 - && world_y < self.world_height as i32 - { - let shadow_idx = (world_y * self.world_width as i32 + world_x) as usize; - let shadow_val = self.shadow_map[shadow_idx] as f32 / 255.0; - brightness = 1.0 - 0.6 * shadow_val; - } + let shadow_intensity = self.get_shadow_intensity(world_x, world_y); + let brightness = 1.0 - 0.6 * shadow_intensity; let [r, g, b, _] = color.0; - let r = (r as f32 * brightness) as u32; - let g = (g as f32 * brightness) as u32; - let b = (b as f32 * brightness) as u32; + let src_r = (r as f32 * brightness) as u32; + let src_g = (g as f32 * brightness) as u32; + let src_b = (b as f32 * brightness) as u32; + + let dst_pixel = buf[dest_idx]; + + let dst_r = (dst_pixel >> 16) & 0xFF; + let dst_g = (dst_pixel >> 8) & 0xFF; + let dst_b = dst_pixel & 0xFF; - buf[dest_idx] = (0xFF << 24) | (r << 16) | (g << 8) | b; + let alpha_factor = src_a as f32 / 255.0; + let inv_alpha_factor = 1.0 - alpha_factor; + + let new_r = (src_r as f32 * alpha_factor + dst_r as f32 * inv_alpha_factor) + .min(255.0) as u32; + let new_g = (src_g as f32 * alpha_factor + dst_g as f32 * inv_alpha_factor) + .min(255.0) as u32; + let new_b = (src_b as f32 * alpha_factor + dst_b as f32 * inv_alpha_factor) + .min(255.0) as u32; + + buf[dest_idx] = (0xFF << 24) | (new_r << 16) | (new_g << 8) | new_b; } } } @@ -347,13 +433,12 @@ impl Render { /// /// * `frame` - Sprite frame to render from the entity atlas /// * `screen_x` - X position in screen coordinates (output buffer space) - /// * `screen_y` - Y position in screen coordinates (output buffer space) + /// * `screen_y` - Y position in screen coordinates (output buffer space) /// * `atlas` - Sprite atlas for map elements fn render_shadow(&mut self, frame: &Frame, screen_x: i32, screen_y: i32, atlas: &Atlas) { let (atlas_w, atlas_h) = atlas.image.dimensions(); - let light_dir_x = 1.0; - let light_dir_y = 0.5; + let light_dir_y = 0.0; let shadow_scale = 0.5; for dy in 0..frame.h as i32 { @@ -384,12 +469,17 @@ impl Render { } let dest_index = (dest_y * self.world_width as i32 + dest_x) as usize; + + self.shadow_map[dest_index] = self.shadow_map[dest_index].saturating_add(8).min(48); + let dst = self.world_buf[dest_index]; - self.shadow_map[dest_index] = self.shadow_map[dest_index].saturating_add(64); + let shadow_strength = self.shadow_map[dest_index] as f32 / 255.0; + let darken_factor = 1.0 - 0.4 * shadow_strength; + + let r = ((dst >> 16) & 0xFF) as f32 * darken_factor; + let g = ((dst >> 8) & 0xFF) as f32 * darken_factor; + let b = (dst & 0xFF) as f32 * darken_factor; - let r = ((dst >> 16) & 0xFF) as f32 * 0.5; - let g = ((dst >> 8) & 0xFF) as f32 * 0.5; - let b = (dst & 0xFF) as f32 * 0.5; self.world_buf[dest_index] = (0xFF << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32); } @@ -406,7 +496,7 @@ impl Render { /// * `buf` - Output pixel buffer to render into /// * `camera` - Camera configuration defining viewport and position fn render_shadow_unit( - &self, + &mut self, frame: &Frame, screen_x: i32, screen_y: i32, @@ -416,7 +506,7 @@ impl Render { let (atlas_w, atlas_h) = self.entity_atlas.image.dimensions(); let light_dir_x = 1.0; - let light_dir_y = 0.5; + let light_dir_y = 0.0; let shadow_scale = 0.5; for dy in 0..frame.h as i32 { @@ -433,8 +523,11 @@ impl Render { } let height_factor = (frame.h as f32 - dy as f32) * shadow_scale; - let dest_x = screen_x + dx + (light_dir_x * height_factor) as i32; - let dest_y = screen_y + dy + (light_dir_y * height_factor) as i32; + + let shadow_x = screen_x as f32 + dx as f32 + (light_dir_x * height_factor); + let shadow_y = screen_y as f32 + dy as f32 + (light_dir_y * height_factor); + let dest_x = shadow_x.round() as i32; + let dest_y = shadow_y.round() as i32; if dest_x < 0 || dest_y < 0 @@ -444,17 +537,89 @@ impl Render { continue; } + // Convert camera coordinates to world coordinates + let world_x = (camera.center_x as i32 - camera.width as i32 / 2) + dest_x; + let world_y = (camera.center_y as i32 - camera.height as i32 / 2) + dest_y; + let dest_idx = (dest_y * camera.width as i32 + dest_x) as usize; + + // Check if there's already a shadow from a static object + let mut has_shadow = false; + if world_x >= 0 + && world_y >= 0 + && world_x < self.world_width as i32 + && world_y < self.world_height as i32 + { + let shadow_idx = (world_y * self.world_width as i32 + world_x) as usize; + // Check static shadow + if self.shadow_map[shadow_idx] > 16 { + has_shadow = true; + } + + // Check dynamic shadow (in world coordinates) + if self.dynamic_shadow_buf[shadow_idx] > 0 { + has_shadow = true; + } else { + // Mark that there's now a dynamic shadow here + self.dynamic_shadow_buf[shadow_idx] = 64; + } + } + + if has_shadow { + continue; + } + let dst = buf[dest_idx]; - let r = ((dst >> 16) & 0xFF) as f32 * 0.5; - let g = ((dst >> 8) & 0xFF) as f32 * 0.5; - let b = (dst & 0xFF) as f32 * 0.5; + const SHADOW_INTNS: f32 = 0.5; + let r = ((dst >> 16) & 0xFF) as f32 * SHADOW_INTNS; + let g = ((dst >> 8) & 0xFF) as f32 * SHADOW_INTNS; + let b = (dst & 0xFF) as f32 * SHADOW_INTNS; + buf[dest_idx] = (0xFF << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32); } } } + + /// Soft blur for shadow areas only + pub fn soft_blur_shadows(&mut self) { + let width = self.world_width as i32; + let height = self.world_height as i32; + let mut blurred = self.shadow_map.clone(); + + for y in 1..height - 1 { + for x in 1..width - 1 { + let idx = (y * width + x) as usize; + + if self.shadow_map[idx] > 0 { + let mut sum = 0u32; + let mut count = 0u32; + + for dy in -1..=1 { + for dx in -1..=1 { + let sx = x + dx; + let sy = y + dy; + if sx >= 0 && sy >= 0 && sx < width && sy < height { + let sidx = (sy * width + sx) as usize; + sum += self.shadow_map[sidx] as u32; + count += 1; + } + } + } + + blurred[idx] = (sum / count) as u8; + } + } + } + + self.shadow_map = blurred; + } + + pub fn create_entity(&self, x: f32, y: f32, sprite_name: &str) -> RenderableEntity { + RenderableEntity::with_sprite(x, y, sprite_name) + } } + #[cfg(test)] mod tests { use super::*; diff --git a/src/time.rs b/engine/time.rs similarity index 97% rename from src/time.rs rename to engine/time.rs index 3b74a1d..c61bdca 100644 --- a/src/time.rs +++ b/engine/time.rs @@ -44,6 +44,12 @@ impl Time { } } +impl Default for Time { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/world/camera.rs b/engine/world/camera.rs similarity index 92% rename from src/world/camera.rs rename to engine/world/camera.rs index 5fcceac..0392abb 100644 --- a/src/world/camera.rs +++ b/engine/world/camera.rs @@ -20,7 +20,7 @@ impl Camera { /// # Arguments /// /// * `center_x` - The x-coordinate of the camera's center position - /// * `center_y` - The y-coordinate of the camera's center position + /// * `center_y` - The y-coordinate of the camera's center position /// * `width` - The width of the camera's viewport in pixels /// * `height` - The height of the camera's viewport in pixels /// @@ -44,8 +44,8 @@ impl Camera { /// /// `true` if the point is visible within the camera's viewport, `false` otherwise. pub fn is_visible(&self, x: f32, y: f32) -> bool { - ((self.center_x - x).abs() < (self.width as f32) / 2.0) - && ((self.center_y - y).abs() < (self.height as f32) / 2.0) + ((self.center_x - x).abs() < self.width as f32) + && ((self.center_y - y).abs() < self.height as f32) } } @@ -86,7 +86,7 @@ mod tests { /// Test points that should be outside the camera's viewport #[test] fn test_is_visible_out_of_bounds() { - let camera = Camera::new(100.0, 200.0, 800, 600); + let camera = Camera::new(100.0, 200.0, 400, 300); // Points outside the viewport assert!(!camera.is_visible(500.0, 200.0)); assert!(!camera.is_visible(-300.0, 200.0)); @@ -98,7 +98,7 @@ mod tests { /// Test edge cases with points very close to the viewport boundaries #[test] fn test_is_visible_edge_cases() { - let camera = Camera::new(100.0, 200.0, 800, 600); + let camera = Camera::new(100.0, 200.0, 400, 300); // Points near the boundary (should be visible since it's < width/2) assert!(camera.is_visible(100.0 + 399.9, 200.0)); // Just inside near right edge @@ -118,17 +118,17 @@ mod tests { fn test_is_visible_different_camera_sizes() { let small_camera = Camera::new(0.0, 0.0, 100, 100); assert!(small_camera.is_visible(25.0, 25.0)); - assert!(!small_camera.is_visible(60.0, 60.0)); + assert!(!small_camera.is_visible(110.0, 110.0)); let large_camera = Camera::new(0.0, 0.0, 2000, 2000); assert!(large_camera.is_visible(500.0, 500.0)); - assert!(!large_camera.is_visible(1100.0, 1100.0)); + assert!(!large_camera.is_visible(2100.0, 2200.0)); } /// Test camera with different center positions #[test] fn test_is_visible_different_centers() { - let negative_center_camera = Camera::new(-100.0, -200.0, 800, 600); + let negative_center_camera = Camera::new(-100.0, -200.0, 400, 300); assert!(negative_center_camera.is_visible(-100.0, -200.0)); assert!(negative_center_camera.is_visible(-300.0, -200.0)); assert!(negative_center_camera.is_visible(100.0, -200.0)); diff --git a/engine/world/mod.rs b/engine/world/mod.rs new file mode 100644 index 0000000..cb52a2a --- /dev/null +++ b/engine/world/mod.rs @@ -0,0 +1,5 @@ +mod camera; +mod state; + +pub use self::state::*; +pub use camera::Camera; diff --git a/engine/world/state.rs b/engine/world/state.rs new file mode 100644 index 0000000..4e17089 --- /dev/null +++ b/engine/world/state.rs @@ -0,0 +1,546 @@ +use crate::assets::GameMap; + +/// Represents the current game state containing all units. +/// +/// The `State` struct manages the player unit and all mob units in the game, +/// tracking their positions and movement speeds for game simulation. +#[derive(Debug, Default)] +pub struct State { + /// The player-controlled unit + pub player: Player, + + /// Collection of all non-player mobile units + pub mobs: Vec, + + /// 1D vector representing the 2D map. Stores the index of the mob + /// within `self.mobs` that currently occupies the tile, or `None` + pub mob_grid: Vec>, + + /// Width of the game map (in tiles), used for calculating 1D index from 2D coordinates + pub grid_width: i32, +} + +#[derive(Default, Debug, Clone, Copy, PartialEq)] +pub enum Direction { + NE, // North East (Up) + #[default] + SE, // South East (Right) + SW, // South West (Down) + NW, // North West (Left) +} + +impl Direction { + pub fn as_str(&self) -> &'static str { + match self { + Direction::NE => "ne", + Direction::SE => "se", + Direction::SW => "sw", + Direction::NW => "nw", + } + } +} + +/// State for discrete movement and animation. +#[derive(Default, Debug, Clone)] +pub enum UnitMovement { + /// Standing still + #[default] + Idle, + + /// Moving from one tile to another + Moving { + /// Initial position + start_x: f32, + start_y: f32, + + /// Target position + target_x: f32, + target_y: f32, + + /// Movement progress since starting + elapsed_time: f32, + /// Total movement time + duration: f32, + }, + + /// Pushing box from one tile to another. same as `Moving`, but with different animation + Pushing { + /// Initial position + start_x: f32, + start_y: f32, + + /// Target position + target_x: f32, + target_y: f32, + + /// Movement progress since starting + elapsed_time: f32, + /// Total movement time + duration: f32, + + /// Target position after pushing + recoil_target_x: f32, + recoil_target_y: f32, + }, + + /// Approaching box befor pushing it from one tile to another. same as `Moving`, but with different animation + PrePushing { + /// Initial position + start_x: f32, + start_y: f32, + + /// Target position + target_x: f32, + target_y: f32, + + /// Movement progress since starting + elapsed_time: f32, + /// Total movement time + duration: f32, + + /// Context for the pushed box animation + box_idx: usize, + player_next_tx: i32, + player_next_ty: i32, + box_next_tx: i32, + box_next_ty: i32, + push_dx: i32, + push_dy: i32, + }, + + /// Going back after pushing box from one tile to another. same as `Moving`, but with different animation + PostPushing { + /// Initial position + start_x: f32, + start_y: f32, + + /// Target position + target_x: f32, + target_y: f32, + + /// Movement progress since starting + elapsed_time: f32, + /// Total movement time + duration: f32, + }, +} + +/// Represents a unit entity in the game world with position and movement capabilities. +/// +/// Units can be either player-controlled or game-controlled mobs. Each unit has +/// a position in 2D space and speed components for movement simulation. +#[derive(Debug, Default, Clone)] +pub struct Unit { + /// Pixel coordinates (for animation) + pub pixel_x: f32, + pub pixel_y: f32, + + /// Logical coordinates (tile-based) + pub tile_x: i32, + pub tile_y: i32, + + /// Horizontal movement speed + pub x_speed: f32, + /// Vertical movement speed + pub y_speed: f32, + + /// State for movement + pub movement: UnitMovement, + /// Current facing direction + pub direction: Direction, +} + +impl Unit { + /// Creates a new `Unit` with the specified position and movement parameters. + /// + /// # Arguments + /// + /// * `pixel_x` - Initial X-coordinate position + /// * `pixel_x` - Initial Y-coordinate position + /// * `tile_x` - Initial tile-based X-coordinate position + /// * `tile_y` - Initial tile-based Y-coordinate position + /// * `x_speed` - Initial horizontal movement speed + /// * `y_speed` - Initial vertical movement speed + /// + /// # Returns + /// + /// A new `Unit` instance with the specified properties. + #[allow(dead_code)] + pub fn new( + pixel_x: f32, + pixel_y: f32, + tile_x: i32, + tile_y: i32, + x_speed: f32, + y_speed: f32, + ) -> Self { + Self { pixel_x, pixel_y, tile_x, tile_y, x_speed, y_speed, ..Default::default() } + } +} + +/// Represents the player's state. Primarily wraps a `Unit`. +#[derive(Debug, Default)] +pub struct Player { + /// Player's data + pub unit: Unit, +} + +impl Player { + pub fn new(unit: Unit) -> Self { + Self { unit } + } +} + +/// Converts tile coordinates to pixel coordinates of the tile center +/// in the world buffer coordinate system. +fn tile_to_world_buf_pos( + tile_x: i32, + tile_y: i32, + tile_size: u32, + world_width: u32, + world_height: u32, +) -> (f32, f32) { + let ts = tile_size as i32; + let ww = world_width as i32; + let wh = world_height as i32; + + let offset_x = ww / 2; + let offset_y = wh / 2 - ts; + + let x = (tile_x - tile_y) * (ts / 2) + offset_x; + let y = (tile_x + tile_y) * (ts / 4) + offset_y - (ts / 2); + + let todo_remove_me = ts / 8; + + let center_x = x + ts / 2; + let center_y = y + ts / 4 + todo_remove_me; + + (center_x as f32, center_y as f32) +} + +impl State { + /// Creates a new `State` by getting unit data from a `GameMap`. + /// + /// This constructor processes all units defined in the game map, identifying + /// the player unit and initializing all mob units with their starting positions + /// and movement behaviors. + /// + /// # Arguments + /// + /// * `game_map` - Reference to the `GameMap` containing unit definitions + /// + /// # Returns + /// + /// A new `State` instance with: + /// - Player unit initialized from the mob marked as `is_player` + /// - All other mobs initialized with their respective behaviors and speeds + /// + /// # Behavior + /// + /// - The player unit is given fixed movement speeds (10.0 in both directions) + /// - Mob units derive their movement from behavior definitions: + /// - "right": positive x_speed + /// - "left": negative x_speed + /// - "up": negative y_speed + /// - "down": positive y_speed + /// - Mobs without behavior definitions get zero movement speed + /// - Mobs without specified speed default to 0.0 + pub fn new(game_map: &GameMap) -> Self { + let mut player: Option = None; + let mut mobs: Vec = Vec::new(); + + let world_width = game_map.size[0] * game_map.tile_size * 2; + let world_height = game_map.size[1] * game_map.tile_size * 2; + + for mob_data in game_map.iter_mobs() { + let (world_x, world_y) = tile_to_world_buf_pos( + mob_data.x_start as i32, + mob_data.y_start as i32, + game_map.tile_size, + world_width, + world_height, + ); + + let mut unit = Unit { + pixel_x: world_x, + pixel_y: world_y, + tile_x: mob_data.x_start as i32, + tile_y: mob_data.y_start as i32, + ..Default::default() + }; + + if mob_data.is_player { + unit.x_speed = 10.0; + unit.y_speed = 10.0; + player = Some(unit); + continue; + } + + if let Some(beh) = &mob_data.behaviour { + let mob_direction = beh.direction.as_deref().unwrap_or("none"); + let mob_speed = beh.speed.unwrap_or(0.0); + + match mob_direction { + "right" => unit.x_speed = mob_speed, + "left" => unit.x_speed = -mob_speed, + "up" => unit.y_speed = -mob_speed, + "down" => unit.y_speed = mob_speed, + _ => {} + } + } + + mobs.push(unit); + } + + let width = game_map.size[0] as usize; + let height = game_map.size[1] as usize; + let grid_width = width as i32; + + let mut mob_grid = vec![None; width * height]; + + for (i, unit) in mobs.iter().enumerate() { + let idx = (unit.tile_y * grid_width + unit.tile_x) as usize; + mob_grid[idx] = Some(i); + } + + Self { player: Player::new(player.unwrap()), mobs, mob_grid, grid_width } + } + + /// Updates the `mob_grid` to reflect a mob's movement from one tile to another. + /// + /// # Arguments + /// + /// * `mob_index` - The index of the mob being moved within `self.mobs`. + /// * `old_x` - The tile X-coordinate of the mob's starting position. + /// * `old_y` - The tile Y-coordinate of the mob's starting position. + /// * `new_x` - The tile X-coordinate of the mob's destination position. + /// * `new_y` - The tile Y-coordinate of the mob's destination position. + pub fn update_mob_pos( + &mut self, + mob_index: usize, + old_x: i32, + old_y: i32, + new_x: i32, + new_y: i32, + ) { + let old_idx = (old_y * self.grid_width + old_x) as usize; + let new_idx = (new_y * self.grid_width + new_x) as usize; + + if let Some(current_idx) = self.mob_grid.get(old_idx) { + if *current_idx == Some(mob_index) { + self.mob_grid[old_idx] = None; + } + } + + if new_idx < self.mob_grid.len() { + self.mob_grid[new_idx] = Some(mob_index); + } + } + + /// Retrieves the index of the mob occupying the given tile coordinates. + /// + /// # Arguments + /// + /// * `x` - Target tile X-coordinate + /// * `y` - Target tile Y-coordinate + /// + /// # Returns + /// + /// `Some(index)` if a mob is present at `(x, y)`. + /// Returns `None` if the tile is empty or if the provided + /// coordinates are out of the map bounds. + pub fn get_mob_at(&self, x: i32, y: i32) -> Option { + if x < 0 || y < 0 { + return None; + } + + let idx = (y * self.grid_width + x) as usize; + if idx < self.mob_grid.len() { + self.mob_grid[idx] + } else { + None + } + } +} + +#[cfg(test)] +mod state_tests { + use std::collections::{HashMap, LinkedList}; + + use crate::assets::{Behaviour, BehaviourType, GameMap, Mob, TileType}; + use crate::world::State; + + fn make_test_map() -> GameMap { + let mut mobs = std::collections::HashMap::new(); + + mobs.insert( + "player".to_string(), + Mob { + name: "player".to_string(), + x_start: 0, + y_start: 0, + asset: "knight".to_string(), + is_player: true, + behaviour: None, + }, + ); + + mobs.insert( + "mob_right".to_string(), + Mob { + name: "mob_right".to_string(), + x_start: 1, + y_start: 0, + asset: "imp".to_string(), + is_player: false, + behaviour: Some(Behaviour { + behaviour_type: BehaviourType::Walker, + direction: Some("right".to_string()), + speed: Some(1.0), + }), + }, + ); + + mobs.insert( + "mob_up".to_string(), + Mob { + name: "mob_up".to_string(), + x_start: 0, + y_start: 1, + asset: "ghost".to_string(), + is_player: false, + behaviour: Some(Behaviour { + behaviour_type: BehaviourType::Walker, + direction: Some("up".to_string()), + speed: Some(0.5), + }), + }, + ); + + GameMap { + name: "test_map".to_string(), + tile_size: 16, + size: [5, 5], + mobs, + objects: std::collections::HashMap::new(), + tiles: std::collections::HashMap::new(), + walk_map: vec![TileType::Empty; 25], + target_positions: LinkedList::new(), + links: HashMap::new(), + object_collidable_map: vec![false; 0], + } + } + + #[test] + fn test_state_new_creates_player_and_mobs() { + let map = make_test_map(); + let state = State::new(&map); + + assert_eq!(state.player.unit.tile_x, 0); + assert_eq!(state.player.unit.tile_y, 0); + assert_eq!(state.player.unit.x_speed, 10.0); + assert_eq!(state.player.unit.y_speed, 10.0); + + assert_eq!(state.mobs.len(), 2); + + let mob_right = state.mobs.iter().find(|m| m.x_speed > 0.0).unwrap(); + assert_eq!(mob_right.x_speed, 1.0); + assert_eq!(mob_right.y_speed, 0.0); + assert_eq!(mob_right.tile_x, 1); + assert_eq!(mob_right.tile_y, 0); + + let mob_up = state.mobs.iter().find(|m| m.y_speed < 0.0).unwrap(); + assert_eq!(mob_up.x_speed, 0.0); + assert_eq!(mob_up.y_speed, -0.5); + assert_eq!(mob_up.tile_x, 0); + assert_eq!(mob_up.tile_y, 1); + } + + #[test] + fn test_state_with_no_mobs_other_than_player() { + let mut map = make_test_map(); + map.mobs.retain(|_, mob| mob.is_player); + let state = State::new(&map); + + assert_eq!(state.player.unit.tile_x, 0); + assert_eq!(state.player.unit.tile_y, 0); + assert!(state.mobs.is_empty()); + } + + #[test] + fn test_mob_with_unknown_or_none_behaviour_defaults_to_zero_speed() { + let mut mobs = std::collections::HashMap::new(); + + mobs.insert( + "player".to_string(), + Mob { + name: "player".to_string(), + x_start: 0, + y_start: 0, + asset: "knight".to_string(), + is_player: true, + behaviour: None, + }, + ); + mobs.insert( + "mob_none".to_string(), + Mob { + name: "mob_none".to_string(), + x_start: 1, + y_start: 1, + asset: "dummy".to_string(), + is_player: false, + behaviour: None, + }, + ); + mobs.insert( + "mob_unknown".to_string(), + Mob { + name: "mob_unknown".to_string(), + x_start: 2, + y_start: 2, + asset: "dummy".to_string(), + is_player: false, + behaviour: Some(Behaviour { + behaviour_type: BehaviourType::Unknown, + direction: Some("left".to_string()), + speed: Some(2.0), + }), + }, + ); + + let map = GameMap { + name: "test_map".to_string(), + tile_size: 16, + size: [5, 5], + mobs, + objects: std::collections::HashMap::new(), + tiles: std::collections::HashMap::new(), + walk_map: vec![TileType::Empty; 25], + target_positions: LinkedList::new(), + links: HashMap::new(), + object_collidable_map: vec![false; 0], + }; + + let state = State::new(&map); + assert_eq!(state.mobs.len(), 2); + + let mob_none = state.mobs.iter().find(|m| m.tile_x == 1 && m.tile_y == 1).unwrap(); + assert_eq!(mob_none.x_speed, 0.0); + assert_eq!(mob_none.y_speed, 0.0); + + let mob_unknown = state.mobs.iter().find(|m| m.tile_x == 2 && m.tile_y == 2).unwrap(); + assert_eq!(mob_unknown.x_speed, -2.0); + assert_eq!(mob_unknown.y_speed, 0.0); + } + + #[test] + fn test_player_position_does_not_change_from_map() { + let map = make_test_map(); + let state = State::new(&map); + + let player_map = map.get_mob("player").unwrap(); + assert_eq!(state.player.unit.tile_x, player_map.x_start as i32); + assert_eq!(state.player.unit.tile_y, player_map.y_start as i32); + } +} diff --git a/examples/input homka.json b/examples/input homka.json deleted file mode 100644 index 4c29265..0000000 --- a/examples/input homka.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "meta": { - "name": "demo_map", - "tile_size": 16, - "size": [ - 1, - 2 - ] - }, - "tiles": { - "1": { - "x": 0, - "y": 0, - "asset": "dirt_tile_big_0_0" - }, - "2": { - "x": 0, - "y": 1, - "asset": "dirt_tile_big_0_0" - }, - "3": { - "x": 0, - "y": 2, - "asset": "dirt_tile_big_0_0" - }, - "4": { - "x": 1, - "y": 1, - "asset": "dirt_tile_big_0_0" - }, - "5": { - "x": 2, - "y": 0, - "asset": "dirt_tile_big_0_0" - }, - "6": { - "x": 2, - "y": 1, - "asset": "dirt_tile_big_0_0" - }, - "7": { - "x": 2, - "y": 2, - "asset": "dirt_tile_big_0_0" - }, - "8": { - "x": 4, - "y": 0, - "asset": "dirt_tile_big_0_0" - }, - "9": { - "x": 4, - "y": 1, - "asset": "dirt_tile_big_0_0" - }, - "10": { - "x": 4, - "y": 2, - "asset": "dirt_tile_big_0_0" - }, - "11": { - "x": 5, - "y": 2, - "asset": "dirt_tile_big_0_0" - }, - "12": { - "x": 6, - "y": 0, - "asset": "dirt_tile_big_0_0" - }, - "13": { - "x": 6, - "y": 1, - "asset": "dirt_tile_big_0_0" - }, - "14": { - "x": 6, - "y": 2, - "asset": "dirt_tile_big_0_0" - }, - "18": { - "x": 8, - "y": 0, - "asset": "dirt_tile_big_0_0" - }, - "15": { - "x": 10, - "y": 0, - "asset": "dirt_tile_big_0_0" - }, - "16": { - "x": 9, - "y": 1, - "asset": "dirt_tile_big_0_0" - }, - "17": { - "x": 9, - "y": 2, - "asset": "dirt_tile_big_0_0" - } - }, - "objects": { - "obj_1": { - "x": 2, - "y": 1, - "asset": "cactus_long_3_9" - } - }, - "mobs": { - "player": { - "x_start": 0, - "y_start": 0, - "asset": "knight_0_0", - "is_player": true, - "behaviour": { - "type": "controlled" - } - }, - "mob_1": { - "x_start": 3, - "y_start": 3, - "asset": "imp_20_0", - "is_player": false, - "behaviour": { - "type": "walker", - "direction": "left", - "speed": 12 - } - } - } -} \ No newline at end of file diff --git a/examples/input.json b/examples/input.json deleted file mode 100644 index 0db790e..0000000 --- a/examples/input.json +++ /dev/null @@ -1,3220 +0,0 @@ -{ - "meta": { - "name": "demo_map", - "tile_size": 16, - "size": [ - 25, - 25 - ] - }, - "tiles": { - "tile_1": { - "x": 0, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_2": { - "x": 1, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_3": { - "x": 2, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_4": { - "x": 3, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_5": { - "x": 4, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_6": { - "x": 5, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_7": { - "x": 6, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_8": { - "x": 7, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_9": { - "x": 8, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_10": { - "x": 9, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_11": { - "x": 10, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_12": { - "x": 11, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_13": { - "x": 12, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_14": { - "x": 13, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_15": { - "x": 14, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_16": { - "x": 15, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_17": { - "x": 16, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_18": { - "x": 17, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_19": { - "x": 18, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_20": { - "x": 19, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_21": { - "x": 20, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_22": { - "x": 21, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_23": { - "x": 22, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_24": { - "x": 23, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_25": { - "x": 24, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_26": { - "x": 0, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_27": { - "x": 1, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_28": { - "x": 2, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_29": { - "x": 3, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_30": { - "x": 4, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_31": { - "x": 5, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_32": { - "x": 6, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_33": { - "x": 7, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_34": { - "x": 8, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_35": { - "x": 9, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_36": { - "x": 10, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_37": { - "x": 11, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_38": { - "x": 12, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_39": { - "x": 13, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_40": { - "x": 14, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_41": { - "x": 15, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_42": { - "x": 16, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_43": { - "x": 17, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_44": { - "x": 18, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_45": { - "x": 19, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_46": { - "x": 20, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_47": { - "x": 21, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_48": { - "x": 22, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_49": { - "x": 23, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_50": { - "x": 24, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_51": { - "x": 0, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_52": { - "x": 1, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_53": { - "x": 2, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_54": { - "x": 3, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_55": { - "x": 4, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_56": { - "x": 5, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_57": { - "x": 6, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_58": { - "x": 7, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_59": { - "x": 8, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_60": { - "x": 9, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_61": { - "x": 10, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_62": { - "x": 11, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_63": { - "x": 12, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_64": { - "x": 13, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_65": { - "x": 14, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_66": { - "x": 15, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_67": { - "x": 16, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_68": { - "x": 17, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_69": { - "x": 18, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_70": { - "x": 19, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_71": { - "x": 20, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_72": { - "x": 21, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_73": { - "x": 22, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_74": { - "x": 23, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_75": { - "x": 24, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_76": { - "x": 0, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_77": { - "x": 1, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_78": { - "x": 2, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_79": { - "x": 3, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_80": { - "x": 4, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_81": { - "x": 5, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_82": { - "x": 6, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_83": { - "x": 7, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_84": { - "x": 8, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_85": { - "x": 9, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_86": { - "x": 10, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_87": { - "x": 11, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_88": { - "x": 12, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_89": { - "x": 13, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_90": { - "x": 14, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_91": { - "x": 15, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_92": { - "x": 16, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_93": { - "x": 17, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_94": { - "x": 18, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_95": { - "x": 19, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_96": { - "x": 20, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_97": { - "x": 21, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_98": { - "x": 22, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_99": { - "x": 23, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_100": { - "x": 24, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_101": { - "x": 0, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_102": { - "x": 1, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_103": { - "x": 2, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_104": { - "x": 3, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_105": { - "x": 4, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_106": { - "x": 5, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_107": { - "x": 6, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_108": { - "x": 7, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_109": { - "x": 8, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_110": { - "x": 9, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_111": { - "x": 10, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_112": { - "x": 11, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_113": { - "x": 12, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_114": { - "x": 13, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_115": { - "x": 14, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_116": { - "x": 15, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_117": { - "x": 16, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_118": { - "x": 17, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_119": { - "x": 18, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_120": { - "x": 19, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_121": { - "x": 20, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_122": { - "x": 21, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_123": { - "x": 22, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_124": { - "x": 23, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_125": { - "x": 24, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_126": { - "x": 0, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_127": { - "x": 1, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_128": { - "x": 2, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_129": { - "x": 3, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_130": { - "x": 4, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_131": { - "x": 5, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_132": { - "x": 6, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_133": { - "x": 7, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_134": { - "x": 8, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_135": { - "x": 9, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_136": { - "x": 10, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_137": { - "x": 11, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_138": { - "x": 12, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_139": { - "x": 13, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_140": { - "x": 14, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_141": { - "x": 15, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_142": { - "x": 16, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_143": { - "x": 17, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_144": { - "x": 18, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_145": { - "x": 19, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_146": { - "x": 20, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_147": { - "x": 21, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_148": { - "x": 22, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_149": { - "x": 23, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_150": { - "x": 24, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_151": { - "x": 0, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_152": { - "x": 1, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_153": { - "x": 2, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_154": { - "x": 3, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_155": { - "x": 4, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_156": { - "x": 5, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_157": { - "x": 6, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_158": { - "x": 7, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_159": { - "x": 8, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_160": { - "x": 9, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_161": { - "x": 10, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_162": { - "x": 11, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_163": { - "x": 12, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_164": { - "x": 13, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_165": { - "x": 14, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_166": { - "x": 15, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_167": { - "x": 16, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_168": { - "x": 17, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_169": { - "x": 18, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_170": { - "x": 19, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_171": { - "x": 20, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_172": { - "x": 21, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_173": { - "x": 22, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_174": { - "x": 23, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_175": { - "x": 24, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_176": { - "x": 0, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_177": { - "x": 1, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_178": { - "x": 2, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_179": { - "x": 3, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_180": { - "x": 4, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_181": { - "x": 5, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_182": { - "x": 6, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_183": { - "x": 7, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_184": { - "x": 8, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_185": { - "x": 9, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_186": { - "x": 10, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_187": { - "x": 11, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_188": { - "x": 12, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_189": { - "x": 13, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_190": { - "x": 14, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_191": { - "x": 15, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_192": { - "x": 16, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_193": { - "x": 17, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_194": { - "x": 18, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_195": { - "x": 19, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_196": { - "x": 20, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_197": { - "x": 21, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_198": { - "x": 22, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_199": { - "x": 23, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_200": { - "x": 24, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_201": { - "x": 0, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_202": { - "x": 1, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_203": { - "x": 2, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_204": { - "x": 3, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_205": { - "x": 4, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_206": { - "x": 5, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_207": { - "x": 6, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_208": { - "x": 7, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_209": { - "x": 8, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_210": { - "x": 9, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_211": { - "x": 10, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_212": { - "x": 11, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_213": { - "x": 12, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_214": { - "x": 13, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_215": { - "x": 14, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_216": { - "x": 15, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_217": { - "x": 16, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_218": { - "x": 17, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_219": { - "x": 18, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_220": { - "x": 19, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_221": { - "x": 20, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_222": { - "x": 21, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_223": { - "x": 22, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_224": { - "x": 23, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_225": { - "x": 24, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_226": { - "x": 0, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_227": { - "x": 1, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_228": { - "x": 2, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_229": { - "x": 3, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_230": { - "x": 4, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_231": { - "x": 5, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_232": { - "x": 6, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_233": { - "x": 7, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_234": { - "x": 8, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_235": { - "x": 9, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_236": { - "x": 10, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_237": { - "x": 11, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_238": { - "x": 12, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_239": { - "x": 13, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_240": { - "x": 14, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_241": { - "x": 15, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_242": { - "x": 16, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_243": { - "x": 17, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_244": { - "x": 18, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_245": { - "x": 19, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_246": { - "x": 20, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_247": { - "x": 21, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_248": { - "x": 22, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_249": { - "x": 23, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_250": { - "x": 24, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_251": { - "x": 0, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_252": { - "x": 1, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_253": { - "x": 2, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_254": { - "x": 3, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_255": { - "x": 4, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_256": { - "x": 5, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_257": { - "x": 6, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_258": { - "x": 7, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_259": { - "x": 8, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_260": { - "x": 9, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_261": { - "x": 10, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_262": { - "x": 11, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_263": { - "x": 12, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_264": { - "x": 13, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_265": { - "x": 14, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_266": { - "x": 15, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_267": { - "x": 16, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_268": { - "x": 17, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_269": { - "x": 18, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_270": { - "x": 19, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_271": { - "x": 20, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_272": { - "x": 21, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_273": { - "x": 22, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_274": { - "x": 23, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_275": { - "x": 24, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_276": { - "x": 0, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_277": { - "x": 1, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_278": { - "x": 2, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_279": { - "x": 3, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_280": { - "x": 4, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_281": { - "x": 5, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_282": { - "x": 6, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_283": { - "x": 7, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_284": { - "x": 8, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_285": { - "x": 9, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_286": { - "x": 10, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_287": { - "x": 11, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_288": { - "x": 12, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_289": { - "x": 13, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_290": { - "x": 14, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_291": { - "x": 15, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_292": { - "x": 16, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_293": { - "x": 17, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_294": { - "x": 18, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_295": { - "x": 19, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_296": { - "x": 20, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_297": { - "x": 21, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_298": { - "x": 22, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_299": { - "x": 23, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_300": { - "x": 24, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_301": { - "x": 0, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_302": { - "x": 1, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_303": { - "x": 2, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_304": { - "x": 3, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_305": { - "x": 4, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_306": { - "x": 5, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_307": { - "x": 6, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_308": { - "x": 7, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_309": { - "x": 8, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_310": { - "x": 9, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_311": { - "x": 10, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_312": { - "x": 11, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_313": { - "x": 12, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_314": { - "x": 13, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_315": { - "x": 14, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_316": { - "x": 15, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_317": { - "x": 16, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_318": { - "x": 17, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_319": { - "x": 18, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_320": { - "x": 19, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_321": { - "x": 20, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_322": { - "x": 21, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_323": { - "x": 22, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_324": { - "x": 23, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_325": { - "x": 24, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_326": { - "x": 0, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_327": { - "x": 1, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_328": { - "x": 2, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_329": { - "x": 3, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_330": { - "x": 4, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_331": { - "x": 5, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_332": { - "x": 6, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_333": { - "x": 7, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_334": { - "x": 8, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_335": { - "x": 9, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_336": { - "x": 10, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_337": { - "x": 11, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_338": { - "x": 12, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_339": { - "x": 13, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_340": { - "x": 14, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_341": { - "x": 15, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_342": { - "x": 16, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_343": { - "x": 17, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_344": { - "x": 18, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_345": { - "x": 19, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_346": { - "x": 20, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_347": { - "x": 21, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_348": { - "x": 22, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_349": { - "x": 23, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_350": { - "x": 24, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_351": { - "x": 0, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_352": { - "x": 1, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_353": { - "x": 2, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_354": { - "x": 3, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_355": { - "x": 4, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_356": { - "x": 5, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_357": { - "x": 6, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_358": { - "x": 7, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_359": { - "x": 8, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_360": { - "x": 9, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_361": { - "x": 10, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_362": { - "x": 11, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_363": { - "x": 12, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_364": { - "x": 13, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_365": { - "x": 14, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_366": { - "x": 15, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_367": { - "x": 16, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_368": { - "x": 17, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_369": { - "x": 18, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_370": { - "x": 19, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_371": { - "x": 20, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_372": { - "x": 21, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_373": { - "x": 22, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_374": { - "x": 23, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_375": { - "x": 24, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_376": { - "x": 0, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_377": { - "x": 1, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_378": { - "x": 2, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_379": { - "x": 3, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_380": { - "x": 4, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_381": { - "x": 5, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_382": { - "x": 6, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_383": { - "x": 7, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_384": { - "x": 8, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_385": { - "x": 9, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_386": { - "x": 10, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_387": { - "x": 11, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_388": { - "x": 12, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_389": { - "x": 13, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_390": { - "x": 14, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_391": { - "x": 15, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_392": { - "x": 16, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_393": { - "x": 17, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_394": { - "x": 18, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_395": { - "x": 19, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_396": { - "x": 20, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_397": { - "x": 21, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_398": { - "x": 22, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_399": { - "x": 23, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_400": { - "x": 24, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_401": { - "x": 0, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_402": { - "x": 1, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_403": { - "x": 2, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_404": { - "x": 3, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_405": { - "x": 4, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_406": { - "x": 5, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_407": { - "x": 6, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_408": { - "x": 7, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_409": { - "x": 8, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_410": { - "x": 9, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_411": { - "x": 10, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_412": { - "x": 11, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_413": { - "x": 12, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_414": { - "x": 13, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_415": { - "x": 14, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_416": { - "x": 15, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_417": { - "x": 16, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_418": { - "x": 17, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_419": { - "x": 18, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_420": { - "x": 19, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_421": { - "x": 20, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_422": { - "x": 21, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_423": { - "x": 22, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_424": { - "x": 23, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_425": { - "x": 24, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_426": { - "x": 0, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_427": { - "x": 1, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_428": { - "x": 2, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_429": { - "x": 3, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_430": { - "x": 4, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_431": { - "x": 5, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_432": { - "x": 6, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_433": { - "x": 7, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_434": { - "x": 8, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_435": { - "x": 9, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_436": { - "x": 10, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_437": { - "x": 11, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_438": { - "x": 12, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_439": { - "x": 13, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_440": { - "x": 14, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_441": { - "x": 15, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_442": { - "x": 16, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_443": { - "x": 17, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_444": { - "x": 18, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_445": { - "x": 19, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_446": { - "x": 20, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_447": { - "x": 21, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_448": { - "x": 22, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_449": { - "x": 23, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_450": { - "x": 24, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_451": { - "x": 0, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_452": { - "x": 1, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_453": { - "x": 2, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_454": { - "x": 3, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_455": { - "x": 4, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_456": { - "x": 5, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_457": { - "x": 6, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_458": { - "x": 7, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_459": { - "x": 8, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_460": { - "x": 9, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_461": { - "x": 10, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_462": { - "x": 11, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_463": { - "x": 12, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_464": { - "x": 13, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_465": { - "x": 14, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_466": { - "x": 15, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_467": { - "x": 16, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_468": { - "x": 17, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_469": { - "x": 18, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_470": { - "x": 19, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_471": { - "x": 20, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_472": { - "x": 21, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_473": { - "x": 22, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_474": { - "x": 23, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_475": { - "x": 24, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_476": { - "x": 0, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_477": { - "x": 1, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_478": { - "x": 2, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_479": { - "x": 3, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_480": { - "x": 4, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_481": { - "x": 5, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_482": { - "x": 6, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_483": { - "x": 7, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_484": { - "x": 8, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_485": { - "x": 9, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_486": { - "x": 10, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_487": { - "x": 11, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_488": { - "x": 12, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_489": { - "x": 13, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_490": { - "x": 14, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_491": { - "x": 15, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_492": { - "x": 16, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_493": { - "x": 17, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_494": { - "x": 18, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_495": { - "x": 19, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_496": { - "x": 20, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_497": { - "x": 21, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_498": { - "x": 22, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_499": { - "x": 23, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_500": { - "x": 24, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_501": { - "x": 0, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_502": { - "x": 1, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_503": { - "x": 2, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_504": { - "x": 3, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_505": { - "x": 4, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_506": { - "x": 5, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_507": { - "x": 6, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_508": { - "x": 7, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_509": { - "x": 8, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_510": { - "x": 9, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_511": { - "x": 10, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_512": { - "x": 11, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_513": { - "x": 12, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_514": { - "x": 13, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_515": { - "x": 14, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_516": { - "x": 15, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_517": { - "x": 16, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_518": { - "x": 17, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_519": { - "x": 18, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_520": { - "x": 19, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_521": { - "x": 20, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_522": { - "x": 21, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_523": { - "x": 22, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_524": { - "x": 23, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_525": { - "x": 24, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_526": { - "x": 0, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_527": { - "x": 1, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_528": { - "x": 2, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_529": { - "x": 3, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_530": { - "x": 4, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_531": { - "x": 5, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_532": { - "x": 6, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_533": { - "x": 7, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_534": { - "x": 8, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_535": { - "x": 9, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_536": { - "x": 10, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_537": { - "x": 11, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_538": { - "x": 12, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_539": { - "x": 13, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_540": { - "x": 14, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_541": { - "x": 15, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_542": { - "x": 16, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_543": { - "x": 17, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_544": { - "x": 18, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_545": { - "x": 19, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_546": { - "x": 20, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_547": { - "x": 21, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_548": { - "x": 22, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_549": { - "x": 23, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_550": { - "x": 24, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_551": { - "x": 0, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_552": { - "x": 1, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_553": { - "x": 2, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_554": { - "x": 3, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_555": { - "x": 4, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_556": { - "x": 5, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_557": { - "x": 6, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_558": { - "x": 7, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_559": { - "x": 8, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_560": { - "x": 9, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_561": { - "x": 10, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_562": { - "x": 11, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_563": { - "x": 12, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_564": { - "x": 13, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_565": { - "x": 14, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_566": { - "x": 15, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_567": { - "x": 16, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_568": { - "x": 17, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_569": { - "x": 18, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_570": { - "x": 19, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_571": { - "x": 20, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_572": { - "x": 21, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_573": { - "x": 22, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_574": { - "x": 23, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_575": { - "x": 24, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_576": { - "x": 0, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_577": { - "x": 1, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_578": { - "x": 2, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_579": { - "x": 3, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_580": { - "x": 4, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_581": { - "x": 5, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_582": { - "x": 6, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_583": { - "x": 7, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_584": { - "x": 8, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_585": { - "x": 9, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_586": { - "x": 10, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_587": { - "x": 11, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_588": { - "x": 12, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_589": { - "x": 13, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_590": { - "x": 14, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_591": { - "x": 15, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_592": { - "x": 16, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_593": { - "x": 17, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_594": { - "x": 18, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_595": { - "x": 19, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_596": { - "x": 20, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_597": { - "x": 21, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_598": { - "x": 22, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_599": { - "x": 23, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_600": { - "x": 24, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_601": { - "x": 0, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_602": { - "x": 1, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_603": { - "x": 2, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_604": { - "x": 3, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_605": { - "x": 4, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_606": { - "x": 5, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_607": { - "x": 6, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_608": { - "x": 7, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_609": { - "x": 8, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_610": { - "x": 9, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_611": { - "x": 10, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_612": { - "x": 11, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_613": { - "x": 12, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_614": { - "x": 13, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_615": { - "x": 14, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_616": { - "x": 15, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_617": { - "x": 16, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_618": { - "x": 17, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_619": { - "x": 18, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_620": { - "x": 19, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_621": { - "x": 20, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_622": { - "x": 21, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_623": { - "x": 22, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_624": { - "x": 23, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_625": { - "x": 24, - "y": 24, - "asset": "grass_tile_big_0_1" - } - }, - "objects": { - "obj_1": { - "x": 2, - "y": 1, - "asset": "cactus_long_3_9" - }, - "obj_2": { - "x": 4, - "y": 14, - "asset": "fence_rising_11_10" - }, - "obj_3": { - "x": 8, - "y": 15, - "asset": "fence_falling_10_10" - } - }, - "mobs": { - "player": { - "x_start": 0, - "y_start": 0, - "asset": "knight_0_0", - "is_player": true, - "behaviour": { - "type": "controlled" - } - }, - "mob_4": { - "x_start": 420, - "y_start": 470, - "asset": "imp_20_0", - "is_player": false, - "behaviour": { - "type": "walker", - "direction": "left", - "speed": 0.5 - } - }, - "mob_5": { - "x_start": 493, - "y_start": 470, - "asset": "imp_20_0", - "is_player": false, - "behaviour": { - "type": "walker", - "direction": "left", - "speed": 0.5 - } - }, - "mob_6": { - "x_start": 540, - "y_start": 470, - "asset": "imp_20_0", - "is_player": false, - "behaviour": { - "type": "walker", - "direction": "left", - "speed": 0.5 - } - }, - "mob_1": { - "x_start": 440, - "y_start": 470, - "asset": "imp_20_0", - "is_player": false, - "behaviour": { - "type": "walker", - "direction": "left", - "speed": 0.5 - } - }, - "mob_2": { - "x_start": 400, - "y_start": 460, - "asset": "ghost_30_0", - "is_player": false, - "behaviour": { - "type": "walker", - "direction": "right", - "speed": 0.42 - } - } - } -} \ No newline at end of file diff --git a/examples/mobgen.py b/examples/mobgen.py deleted file mode 100644 index 092be4c..0000000 --- a/examples/mobgen.py +++ /dev/null @@ -1,32 +0,0 @@ -import json, random - -with open("input.json", "r", encoding="utf-8") as f: - base = json.load(f) - -def gen_mobs(n): - mobs = { - "player": base["mobs"]["player"] - } - for i in range(1, n + 1): - mob_type = random.choice(["imp_20_0", "ghost_30_0"]) - speed = 0.5 if "imp" in mob_type else 0.42 - direction = random.choice(["left", "right"]) - mobs[f"mob_{i}"] = { - "x_start": random.randint(100, 600), - "y_start": random.randint(100, 600), - "asset": mob_type, - "is_player": False, - "behaviour": { - "type": "walker", - "direction": direction, - "speed": speed - } - } - return mobs - -for n in [500, 5000, 10000, 100000, 1000000]: - new_map = base.copy() - new_map["mobs"] = gen_mobs(n) - with open(f"demo_map_{n}.json", "w", encoding="utf-8") as f: - json.dump(new_map, f, indent=2) - print(f"demo_map_{n}.json created") diff --git a/game/Cargo.toml b/game/Cargo.toml new file mode 100644 index 0000000..cc0aaac --- /dev/null +++ b/game/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "game" +version = "1.0.2" +edition = "2021" + +[dependencies] +minifb = "0.28" +ferari = { path = "../engine" } +crossbeam-channel = "0.5.15" diff --git a/game/src/behaviour.rs b/game/src/behaviour.rs new file mode 100644 index 0000000..cc700be --- /dev/null +++ b/game/src/behaviour.rs @@ -0,0 +1,1238 @@ +use crate::{initiator::lerp, input::InputSnapshot, MOVEMENT_SPEEDUP, TILE_SIZE}; + +use ferari::world::{Direction, State, Unit, UnitMovement}; + +/// Data required to start a box-pushing animation. +type PushTransition = (usize, i32, i32, i32, i32, i32, i32); + +/// Pixel coordinates for recoil animation (start_x, start_y, target_x, target_y). +type PostPushTransition = (f32, f32, f32, f32); + +/// Result of player animation update: busy status and optional transition data. +#[derive(Default)] +struct PlayerAnimationResult { + /// Whether the player is currently busy with an animation + pub player_is_busy: bool, + /// Parameters to initiate a box-pushing sequence + pub transition_to_push: Option, + /// Pixel coordinates to start a recoil animation + pub transition_to_post: Option, +} + +/// Time to walk one tile (player walking). +const MOVE_DURATION: f32 = 0.35 / MOVEMENT_SPEEDUP; +/// Time to approach a box before pushing. +const PRE_PUSH_DURATION: f32 = 0.40 / MOVEMENT_SPEEDUP; +/// Recoil animation time after pushing (not speed-scaled). +const POST_PUSH_DURATION: f32 = 0.50; +/// Time for a box to slide one tile. +const BOX_MOVE_DURATION: f32 = 1.25 / MOVEMENT_SPEEDUP; +/// Duration of the player's pushing animation. +const PUSH_DURATION: f32 = BOX_MOVE_DURATION; +/// Offset between a box and the pushing player. +const PUSH_OFFSET: f32 = 0.25; + +/// Computes the pixel offset (in isometric coordinates) for moving in a given direction by a specified magnitude. +/// +/// The direction is specified as integer deltas (`dir_x`, `dir_y`), where only one of the components is +/// non-zero (e.g., `(1, 0)` for southeast). +/// The `magnitude` parameter scales the movement: `1.0` corresponds to moving exactly one tile. +/// +/// # Arguments +/// +/// * `dir_x`, `dir_y` - The X and Y component of the direction (-1, 0, or 1) +/// * `magnitude` - The scaling factor for the movement +/// +/// # Returns +/// +/// A tuple `(offset_x, offset_y)` in pixel coordinates. +fn get_offset(dir_x: i32, dir_y: i32, magnitude: f32) -> (f32, f32) { + let tile_w = TILE_SIZE as f32; + let tile_h = (TILE_SIZE as f32) * 0.5; + + let step_x = tile_w * 0.5 * magnitude; + let step_y = tile_h * 0.5 * magnitude; + + match (dir_x, dir_y) { + (1, 0) => (step_x, step_y), + (-1, 0) => (-step_x, -step_y), + (0, -1) => (step_x, -step_y), + (0, 1) => (-step_x, step_y), + _ => (0.0, 0.0), + } +} + +/// Maps a logical `Direction` enum value to its corresponding tile-based movement delta. +/// +/// # Arguments +/// +/// * `dir` - The direction to convert +/// +/// # Returns +/// +/// A tuple `(dx, dy)` where: +/// - `dx`: change in tile X-coordinate (+1 for SE/NW axis, 0 otherwise) +/// - `dy`: change in tile Y-coordinate (+1 for SW/NE axis, 0 otherwise) +fn get_dir_delta(dir: Direction) -> (i32, i32) { + match dir { + Direction::SE => (1, 0), + Direction::NW => (-1, 0), + Direction::NE => (0, -1), + Direction::SW => (0, 1), + } +} + +/// Updates the player's animation state based on elapsed time and input. +/// +/// This function handles all player animation phases: idle, walking, pre-pushing, +/// pushing (including chain-push logic), and post-pushing recoil. +/// +/// # Arguments +/// +/// * `unit` - mutable reference to the player's unit to update its pixel position and movement state +/// * `delta` - time elapsed since the last frame (in seconds) +/// * `input_state` - current input snapshot used to detect continuous push input +/// * `mob_grid` - read-only grid indicating which mob (if any) occupies each tile +/// * `game` - game map used for walkability and collision checks during chain pushes +/// * `map_width` - width of the map in tiles +/// +/// # Returns +/// +/// A [`PlayerAnimationResult`] containing: +/// * `player_is_busy`: `true` if the player is currently in any non-idle animation. +/// * `transition_to_push`: parameters to initiate a box-pushing sequence +/// (if a PrePushing or Pushing animation just completed and chain-push is possible). +/// * `transition_to_post`: pixel coordinates to start a recoil animation +/// (if Pushing just completed without chain-push). +fn update_player_animation( + unit: &mut Unit, + delta: f32, + input_state: &InputSnapshot, + mob_grid: &[Option], + game: &ferari::assets::GameMap, + map_width: usize, +) -> PlayerAnimationResult { + let mut player_is_busy = false; + + // Walking between phases of approaching or going back to box/tile. + let mut transition_to_push: Option<(usize, i32, i32, i32, i32, i32, i32)> = None; + let mut transition_to_post: Option<(f32, f32, f32, f32)> = None; + + match &mut unit.movement { + UnitMovement::Moving { start_x, start_y, target_x, target_y, elapsed_time, duration } => { + *elapsed_time += delta; + let progress = (*elapsed_time / *duration).min(1.0); + unit.pixel_x = lerp(*start_x, *target_x, progress); + unit.pixel_y = lerp(*start_y, *target_y, progress); + + if progress >= 1.0 { + unit.pixel_x = *target_x; + unit.pixel_y = *target_y; + unit.movement = UnitMovement::Idle; + } else { + player_is_busy = true; + } + } + + UnitMovement::PrePushing { + start_x, + start_y, + target_x, + target_y, + elapsed_time, + duration, + box_idx, + player_next_tx, + player_next_ty, + box_next_tx, + box_next_ty, + push_dx, + push_dy, + } => { + *elapsed_time += delta; + let progress = (*elapsed_time / *duration).min(1.0); + unit.pixel_x = lerp(*start_x, *target_x, progress); + unit.pixel_y = lerp(*start_y, *target_y, progress); + + if progress >= 1.0 { + transition_to_push = Some(( + *box_idx, + *player_next_tx, + *player_next_ty, + *box_next_tx, + *box_next_ty, + *push_dx, + *push_dy, + )); + unit.pixel_x = *target_x; + unit.pixel_y = *target_y; + player_is_busy = true; + } else { + player_is_busy = true; + } + } + + UnitMovement::Pushing { + start_x, + start_y, + target_x, + target_y, + elapsed_time, + duration, + recoil_target_x, + recoil_target_y, + } => { + *elapsed_time += delta; + let progress = (*elapsed_time / *duration).min(1.0); + unit.pixel_x = lerp(*start_x, *target_x, progress); + unit.pixel_y = lerp(*start_y, *target_y, progress); + + if progress >= 1.0 { + unit.pixel_x = *target_x; + unit.pixel_y = *target_y; + + let (dx, dy) = get_dir_delta(unit.direction); + + let continuing_input = match unit.direction { + Direction::SE => input_state.right, + Direction::NW => input_state.left, + Direction::NE => input_state.up, + Direction::SW => input_state.down, + }; + + let mut can_continue = false; + + if continuing_input { + let current_p_tx = unit.tile_x; + let current_p_ty = unit.tile_y; + + let next_box_tx = current_p_tx + (dx * 2); + let next_box_ty = current_p_ty + (dy * 2); + + let next_p_tx = current_p_tx + dx; + let next_p_ty = current_p_ty + dy; + + let walkable = game.is_walkable(next_box_tx, next_box_ty) + && !game.has_collidable_object_at(next_box_tx, next_box_ty); + + if next_box_ty >= 0 && next_box_tx >= 0 { + let next_box_idx = + (next_box_ty as usize) * map_width + (next_box_tx as usize); + let mob_blocking = mob_grid.get(next_box_idx).copied().flatten().is_some(); + + if walkable && !mob_blocking { + let current_box_pos_idx = + (next_p_ty as usize) * map_width + (next_p_tx as usize); + + if let Some(box_idx) = mob_grid[current_box_pos_idx] { + can_continue = true; + transition_to_push = Some(( + box_idx, + next_p_tx, + next_p_ty, + next_box_tx, + next_box_ty, + dx, + dy, + )); + } + } + } + } + + if !can_continue { + transition_to_post = + Some((*target_x, *target_y, *recoil_target_x, *recoil_target_y)); + } + + player_is_busy = true; + } else { + player_is_busy = true; + } + } + + UnitMovement::PostPushing { + start_x, + start_y, + target_x, + target_y, + elapsed_time, + duration, + } => { + *elapsed_time += delta; + let progress = (*elapsed_time / *duration).min(1.0); + unit.pixel_x = lerp(*start_x, *target_x, progress); + unit.pixel_y = lerp(*start_y, *target_y, progress); + + if progress >= 1.0 { + unit.pixel_x = *target_x; + unit.pixel_y = *target_y; + unit.movement = UnitMovement::Idle; + } else { + player_is_busy = true; + } + } + + UnitMovement::Idle => {} + } + + PlayerAnimationResult { player_is_busy, transition_to_push, transition_to_post } +} + +/// Applies the state changes required to begin a box-pushing animation sequence. +/// +/// This function: +/// 1. Updates `mob_grid` to reflect the box's new position. +/// 2. Starts the box's `Moving` animation toward its destination. +/// 3. Starts the player's `Pushing` animation (with recoil target for post-push). +/// +/// # Arguments +/// +/// * `state` - mutable game state to update player, mobs, and grid +/// * `box_idx` - index of the box in `state.mobs` to be pushed +/// * `(p_tx, p_ty)` - player's new tile coordinates after moving adjacent to the box +/// * `(b_tx, b_ty)` - box's new tile coordinates after being pushed +/// * `(dx, dy)` - direction of the push as tile deltas +/// * `map_width` - map width in tiles +/// * `delta` - frame time +fn apply_push_transition( + state: &mut State, + box_idx: usize, + (p_tx, p_ty): (i32, i32), + (b_tx, b_ty): (i32, i32), + (dx, dy): (i32, i32), + map_width: usize, + delta: f32, +) { + let old_box_idx = (p_ty as usize) * map_width + (p_tx as usize); + let new_box_idx = (b_ty as usize) * map_width + (b_tx as usize); + + if state.mob_grid[old_box_idx] == Some(box_idx) { + state.mob_grid[old_box_idx] = None; + state.mob_grid[new_box_idx] = Some(box_idx); + + let box_unit = &mut state.mobs[box_idx]; + let (bx, by) = (box_unit.pixel_x.round(), box_unit.pixel_y.round()); + let (offset_x, offset_y) = get_offset(dx, dy, 1.0); + let (target_bx, target_by) = (bx + offset_x, by + offset_y); + + box_unit.movement = UnitMovement::Moving { + start_x: bx, + start_y: by, + target_x: target_bx, + target_y: target_by, + elapsed_time: 0.0 - delta, + duration: BOX_MOVE_DURATION, + }; + box_unit.tile_x = b_tx; + box_unit.tile_y = b_ty; + + let (over_x, over_y) = get_offset(dx, dy, PUSH_OFFSET); + let push_target_x = bx + over_x; + let push_target_y = by + over_y; + + let settle_x = bx; + let settle_y = by; + + let player = &mut state.player.unit; + player.movement = UnitMovement::Pushing { + start_x: player.pixel_x, + start_y: player.pixel_y, + target_x: push_target_x, + target_y: push_target_y, + elapsed_time: 0.0, + duration: PUSH_DURATION, + recoil_target_x: settle_x, + recoil_target_y: settle_y, + }; + player.tile_x = p_tx; + player.tile_y = p_ty; + } +} + +/// Initiates the player's recoil animation after completing a push. +/// +/// This sets the player's movement state to `PostPushing`, animating from +/// the pushing endpoint back to the settled position over the specified duration. +/// +/// # Arguments +/// +/// * `player` - mutable reference to the player's unit +/// * `(start_x, start_y)` - starting pixel position (end of push animation) +/// * `(target_x, target_y)` - target pixel position (settled recoil position) +fn apply_post_push_transition( + player: &mut Unit, + (start_x, start_y): (f32, f32), + (target_x, target_y): (f32, f32), +) { + player.movement = UnitMovement::PostPushing { + start_x, + start_y, + target_x, + target_y, + elapsed_time: 0.0, + duration: POST_PUSH_DURATION, + }; +} + +/// Updates animations for all non-player mobile units (e.g., boxes). +/// +/// For each mob in `Moving` state, this function: +/// - Advances the animation by `delta` seconds. +/// - Interpolates the pixel position using linear interpolation (`lerp`). +/// - Sets the mob to `Idle` when the animation completes. +/// +/// # Arguments +/// +/// * `mobs` - slice of mutable mob units to update +/// * `delta` - time elapsed since the last frame +fn update_mob_animations(mobs: &mut [Unit], delta: f32) { + for unit in mobs.iter_mut() { + if let UnitMovement::Moving { + start_x, + start_y, + target_x, + target_y, + elapsed_time, + duration, + } = &mut unit.movement + { + *elapsed_time += delta; + let progress = (*elapsed_time / *duration).min(1.0); + + unit.pixel_x = lerp(*start_x, *target_x, progress); + unit.pixel_y = lerp(*start_y, *target_y, progress); + + if progress >= 1.0 { + unit.pixel_x = *target_x; + unit.pixel_y = *target_y; + unit.movement = UnitMovement::Idle; + } + } + } +} + +/// Initiates the "approach" animation before pushing a box. +/// +/// The player moves halfway toward the box (0.5 tile magnitude) to prepare for the push. +/// Sets the player's movement state to `PrePushing` with parameters needed to transition +/// to full pushing once the approach completes. +/// +/// # Arguments +/// +/// * `player` - mutable reference to the player's unit +/// * `(dx, dy)` - push direction as tile deltas +/// * `box_idx` - index of the box to be pushed +/// * `(player_next_tx, player_next_ty)` - player's tile position after moving adjacent to the box +/// * `(box_next_tx, box_next_ty)` - box's destination tile after being pushed +fn start_pre_push_animation( + player: &mut Unit, + (dx, dy): (i32, i32), + box_idx: usize, + (player_next_tx, player_next_ty): (i32, i32), + (box_next_tx, box_next_ty): (i32, i32), +) { + let (px, py) = (player.pixel_x, player.pixel_y); + let (offset_x, offset_y) = get_offset(dx, dy, PUSH_OFFSET); + let mid_x = px + offset_x; + let mid_y = py + offset_y; + + player.movement = UnitMovement::PrePushing { + start_x: px, + start_y: py, + target_x: mid_x, + target_y: mid_y, + elapsed_time: 0.0, + duration: PRE_PUSH_DURATION, + box_idx, + player_next_tx, + player_next_ty, + box_next_tx, + box_next_ty, + push_dx: dx, + push_dy: dy, + }; +} + +/// Initiates a standard walking animation for the player. +/// +/// Moves the player by one full tile in the specified direction. +/// Updates both the animation state (pixel position over time) and the logical tile position. +/// +/// # Arguments +/// +/// * `player` - mutable reference to the player's unit +/// * `(dx, dy)` - movement direction as tile deltas +/// * `(next_tx, next_ty)` - destination tile coordinates +fn start_walking_animation( + player: &mut Unit, + (dx, dy): (i32, i32), + (next_tx, next_ty): (i32, i32), +) { + let (px, py) = (player.pixel_x, player.pixel_y); + let (offset_x, offset_y) = get_offset(dx, dy, 1.0); + + player.movement = UnitMovement::Moving { + start_x: px, + start_y: py, + target_x: px + offset_x, + target_y: py + offset_y, + elapsed_time: 0.0, + duration: MOVE_DURATION, + }; + player.tile_x = next_tx; + player.tile_y = next_ty; +} + +/// Advances the game simulation by one time step. +/// +/// This is the core game loop function that: +/// 1. Updates all animations (player and mobs). +/// 2. Processes player input (if not busy with animations). +/// 3. Handles movement and box-pushing logic with collision detection. +/// +/// # Arguments +/// +/// * `curr_state` - mutable game state (player, mobs, grid) +/// * `input_state` - current player input (directional buttons) +/// * `delta` - time elapsed since last frame (for animation timing) +/// * `game` - static game map (walkability, collidable objects) +/// +/// # Returns +/// +/// * `Some(0)` if a special key combination is pressed. +/// * `None` otherwise (successful step with or without movement). +/// +/// # Animation States Handled +/// +/// - `Idle`: ready for input. +/// - `Moving`: walking between tiles. +/// - `PrePushing`: approaching a box to push. +/// - `Pushing`: actively pushing a box (supports chain-pushes). +/// - `PostPushing`: recoiling after a push. +pub fn make_step( + curr_state: &mut State, + input_state: &InputSnapshot, + delta: f32, + game: &ferari::assets::GameMap, +) -> Option { + let map_width = game.size[0] as usize; + + // ============================================ + // ANIMATION UPDATE (player + all mobs) + // ============================================ + + // Player update + let PlayerAnimationResult { mut player_is_busy, transition_to_push, transition_to_post } = + update_player_animation( + &mut curr_state.player.unit, + delta, + input_state, + &curr_state.mob_grid, + game, + map_width, + ); + + if let Some((box_idx, p_tx, p_ty, b_tx, b_ty, dx, dy)) = transition_to_push { + player_is_busy = true; + + apply_push_transition( + curr_state, + box_idx, + (p_tx, p_ty), + (b_tx, b_ty), + (dx, dy), + map_width, + delta, + ); + } + + if let Some((sx, sy, tx, ty)) = transition_to_post { + player_is_busy = true; + + apply_post_push_transition(&mut curr_state.player.unit, (sx, sy), (tx, ty)); + } + + // Mob (box) update + update_mob_animations(&mut curr_state.mobs, delta); + + // ============================================ + // INPUT PROCESSING + // ============================================ + + if input_state.left && input_state.right { + return Some(0); + } + + // If the player is busy (the animation has not finished), the input is not processed + if player_is_busy { + return None; + } + + let (mut dx, mut dy) = (0, 0); + let mut new_dir = curr_state.player.unit.direction; + + if input_state.right { + dx = 1; + dy = 0; + new_dir = Direction::SE; + } else if input_state.left { + dx = -1; + dy = 0; + new_dir = Direction::NW; + } else if input_state.up { + dx = 0; + dy = -1; + new_dir = Direction::NE; + } else if input_state.down { + dx = 0; + dy = 1; + new_dir = Direction::SW; + } + + curr_state.player.unit.direction = new_dir; + + if dx == 0 && dy == 0 { + return None; + } + + // Current player coordinates + let p_tx = curr_state.player.unit.tile_x; + let p_ty = curr_state.player.unit.tile_y; + + // Target player coordinates + let next_tx = p_tx + dx; + let next_ty = p_ty + dy; + + // ============================================ + // COLLISION AND MOVEMENT LOGIC + // ============================================ + + // Checking map boundaries and static walls + if !game.is_walkable(next_tx, next_ty) || game.has_collidable_object_at(next_tx, next_ty) { + return None; + } + + // Checking dynamic objects + let next_idx = (next_ty as usize) * map_width + (next_tx as usize); + let mob_in_front = curr_state.mob_grid.get(next_idx).copied().flatten(); + + if let Some(box_idx) = mob_in_front { + // Trying to push the box + + let behind_tx = next_tx + dx; + let behind_ty = next_ty + dy; + + // Checking the statics behind the box + if !game.is_walkable(behind_tx, behind_ty) + || game.has_collidable_object_at(behind_tx, behind_ty) + { + return None; + } + + // Checking the dynamics behind the box + let behind_idx = (behind_ty as usize) * map_width + (behind_tx as usize); + if curr_state.mob_grid.get(behind_idx).copied().flatten().is_some() { + return None; + } + + // perform pre push + start_pre_push_animation( + &mut curr_state.player.unit, + (dx, dy), + box_idx, + (next_tx, next_ty), + (behind_tx, behind_ty), + ); + } else { + start_walking_animation(&mut curr_state.player.unit, (dx, dy), (next_tx, next_ty)); + } + + None +} + +#[cfg(test)] +mod offset_dir_tests { + use super::*; + + #[test] + fn test_get_offset_southeast() { + let (offset_x, offset_y) = get_offset(1, 0, 1.0); + + let tile_w = TILE_SIZE as f32; + let tile_h = (TILE_SIZE as f32) * 0.5; + let expected_x = tile_w * 0.5; + let expected_y = tile_h * 0.5; + + assert_eq!(offset_x, expected_x); + assert_eq!(offset_y, expected_y); + } + + #[test] + fn test_get_offset_northwest() { + let (offset_x, offset_y) = get_offset(-1, 0, 1.0); + + let tile_w = TILE_SIZE as f32; + let tile_h = (TILE_SIZE as f32) * 0.5; + let expected_x = -tile_w * 0.5; + let expected_y = -tile_h * 0.5; + + assert_eq!(offset_x, expected_x); + assert_eq!(offset_y, expected_y); + } + + #[test] + fn test_get_dir_delta_ne() { + assert_eq!(get_dir_delta(Direction::NE), (0, -1)); + } + + #[test] + fn test_get_dir_delta_sw() { + assert_eq!(get_dir_delta(Direction::SW), (0, 1)); + } + + #[test] + fn test_get_dir_delta_all_directions() { + let test_cases = vec![ + (Direction::SE, (1, 0)), + (Direction::NW, (-1, 0)), + (Direction::NE, (0, -1)), + (Direction::SW, (0, 1)), + ]; + + for (direction, expected) in test_cases { + assert_eq!(get_dir_delta(direction), expected); + } + } +} + +#[cfg(test)] +mod update_mod_tests { + use super::*; + use ferari::world::Direction; + + fn create_test_mob_with_movement(movement: UnitMovement) -> Unit { + Unit { + pixel_x: 0.0, + pixel_y: 0.0, + tile_x: 0, + tile_y: 0, + x_speed: 10.0, + y_speed: 10.0, + movement, + direction: Direction::SE, + } + } + + #[test] + fn test_update_mob_animations_single_mob_completion() { + let mut mobs = vec![create_test_mob_with_movement(UnitMovement::Moving { + start_x: 0.0, + start_y: 0.0, + target_x: 100.0, + target_y: 100.0, + elapsed_time: 0.5, + duration: 1.0, + })]; + + let delta = 0.6; + + update_mob_animations(&mut mobs, delta); + + assert!(matches!(mobs[0].movement, UnitMovement::Idle)); + assert_eq!(mobs[0].pixel_x, 100.0); + assert_eq!(mobs[0].pixel_y, 100.0) + } +} + +#[cfg(test)] +mod pre_push_tests { + use super::*; + + #[test] + fn test_start_pre_push_animation_basic() { + let mut player = Unit { + pixel_x: 100.0, + pixel_y: 100.0, + tile_x: 5, + tile_y: 5, + x_speed: 10.0, + y_speed: 10.0, + movement: UnitMovement::Idle, + direction: Direction::SE, + }; + let original_x = player.pixel_x; + let original_y = player.pixel_y; + + let direction = (1, 0); + let box_idx = 42; + let player_next_tile = (6, 5); + let box_next_tile = (7, 5); + + start_pre_push_animation(&mut player, direction, box_idx, player_next_tile, box_next_tile); + + match &player.movement { + UnitMovement::PrePushing { + start_x, + start_y, + target_x, + target_y, + elapsed_time, + duration, + box_idx: actual_box_idx, + player_next_tx, + player_next_ty, + box_next_tx, + box_next_ty, + push_dx, + push_dy, + } => { + assert_eq!(*start_x, original_x); + assert_eq!(*start_y, original_y); + + let (expected_offset_x, expected_offset_y) = get_offset(1, 0, PUSH_OFFSET); + assert_eq!(*target_x, original_x + expected_offset_x); + assert_eq!(*target_y, original_y + expected_offset_y); + + assert_eq!(*elapsed_time, 0.0); + assert_eq!(*duration, PRE_PUSH_DURATION); + + assert_eq!(*actual_box_idx, box_idx); + assert_eq!(*player_next_tx, player_next_tile.0); + assert_eq!(*player_next_ty, player_next_tile.1); + assert_eq!(*box_next_tx, box_next_tile.0); + assert_eq!(*box_next_ty, box_next_tile.1); + assert_eq!(*push_dx, direction.0); + assert_eq!(*push_dy, direction.1); + } + other => panic!("Expected PrePushing movement, got {:?}", other), + } + + assert_eq!(player.pixel_x, original_x); + assert_eq!(player.pixel_y, original_y); + } +} + +#[cfg(test)] +mod post_push_tests { + use super::*; + + #[test] + fn test_apply_post_push_transition() { + let mut player = Unit { + pixel_x: 0.0, + pixel_y: 0.0, + tile_x: 0, + tile_y: 0, + x_speed: 10.0, + y_speed: 10.0, + movement: UnitMovement::Idle, + direction: Direction::SE, + }; + + let start = (10.0, 20.0); + let target = (30.0, 40.0); + + apply_post_push_transition(&mut player, start, target); + + match &player.movement { + UnitMovement::PostPushing { + start_x, + start_y, + target_x, + target_y, + elapsed_time, + duration, + } => { + assert_eq!(*start_x, start.0); + assert_eq!(*start_y, start.1); + assert_eq!(*target_x, target.0); + assert_eq!(*target_y, target.1); + + assert_eq!(*elapsed_time, 0.0); + assert_eq!(*duration, POST_PUSH_DURATION); + } + other => { + panic!("Expected PostPushing movement, got {:?}", other); + } + } + } +} + +#[cfg(test)] +mod push_tests { + use super::*; + use ferari::world::Player; + + #[test] + fn test_apply_push_transition() { + let map_width = 10; + let player_tile = (5, 5); + let box_tile = (6, 5); + + let mut state = State { + player: Player { + unit: Unit { + pixel_x: 100.0, + pixel_y: 100.0, + tile_x: player_tile.0, + tile_y: player_tile.1, + movement: UnitMovement::Idle, + direction: Direction::SE, + x_speed: 10.0, + y_speed: 10.0, + }, + }, + mobs: vec![ + Unit { + pixel_x: 164.0, + pixel_y: 132.0, + tile_x: box_tile.0, + tile_y: box_tile.1, + movement: UnitMovement::Idle, + direction: Direction::SE, + x_speed: 10.0, + y_speed: 10.0, + }, + Unit { + pixel_x: 200.0, + pixel_y: 200.0, + tile_x: 7, + tile_y: 5, + movement: UnitMovement::Idle, + direction: Direction::SE, + x_speed: 10.0, + y_speed: 10.0, + }, + ], + mob_grid: vec![None; map_width * map_width], + ..State::default() + }; + + let box_idx = 0; + let direction = (1, 0); + let delta = 0.016; + + let old_box_idx = box_tile.1 as usize * map_width + box_tile.1 as usize; + state.mob_grid[old_box_idx] = Some(0); + + let original_player_pixel_x = state.player.unit.pixel_x; + let original_player_pixel_y = state.player.unit.pixel_y; + let original_box_pixel_x = state.mobs[0].pixel_x; + let original_box_pixel_y = state.mobs[0].pixel_y; + + apply_push_transition( + &mut state, + box_idx, + player_tile, + box_tile, + direction, + map_width, + delta, + ); + + let new_box_idx = box_tile.1 as usize * map_width + box_tile.1 as usize + 1; + assert_eq!(state.mob_grid[old_box_idx], None); + assert_eq!(state.mob_grid[new_box_idx], Some(0)); + + match &state.mobs[0].movement { + UnitMovement::Moving { + start_x, + start_y, + target_x, + target_y, + elapsed_time, + duration, + } => { + assert_eq!(*start_x, original_box_pixel_x.round()); + assert_eq!(*start_y, original_box_pixel_y.round()); + + let (offset_x, offset_y) = get_offset(1, 0, 1.0); + assert_eq!(*target_x, original_box_pixel_x.round() + offset_x); + assert_eq!(*target_y, original_box_pixel_y.round() + offset_y); + + assert_eq!(*elapsed_time, 0.0 - delta); + assert_eq!(*duration, BOX_MOVE_DURATION); + } + other => panic!("Expected Moving movement, got {:?}", other), + } + + assert_eq!(state.mobs[0].tile_x, box_tile.0); + assert_eq!(state.mobs[0].tile_y, box_tile.1); + + match &state.player.unit.movement { + UnitMovement::Pushing { + start_x, + start_y, + target_x, + target_y, + elapsed_time, + duration, + recoil_target_x, + recoil_target_y, + } => { + assert_eq!(*start_x, original_player_pixel_x); + assert_eq!(*start_y, original_player_pixel_y); + + let (push_offset_x, push_offset_y) = get_offset(1, 0, PUSH_OFFSET); + assert_eq!(*target_x, original_box_pixel_x.round() + push_offset_x); + assert_eq!(*target_y, original_box_pixel_y.round() + push_offset_y); + + assert_eq!(*recoil_target_x, original_box_pixel_x.round()); + assert_eq!(*recoil_target_y, original_box_pixel_y.round()); + + assert_eq!(*elapsed_time, 0.0); + assert_eq!(*duration, PUSH_DURATION); + } + other => panic!("Expected Pushing movement, got {:?}", other), + } + + assert_eq!(state.player.unit.tile_x, player_tile.0); + assert_eq!(state.player.unit.tile_y, player_tile.1); + + assert!(matches!(state.mobs[1].movement, UnitMovement::Idle)); + assert_eq!(state.mobs[1].pixel_x, 200.0); + assert_eq!(state.mobs[1].pixel_y, 200.0); + } +} + +#[cfg(test)] +mod walking_tests { + use super::*; + + #[test] + fn test_start_walking_animation() { + let next_tile = (6, 5); + let direction = (1, 0); + + let mut player = Unit { + pixel_x: 100.0, + pixel_y: 100.0, + tile_x: next_tile.0 - 1, + tile_y: next_tile.1, + x_speed: 10.0, + y_speed: 10.0, + movement: UnitMovement::Idle, + direction: Direction::SE, + }; + + start_walking_animation(&mut player, direction, next_tile); + + match &player.movement { + UnitMovement::Moving { + start_x, + start_y, + target_x, + target_y, + elapsed_time, + duration, + } => { + assert_eq!(*start_x, 100.0); + assert_eq!(*start_y, 100.0); + + let (offset_x, offset_y) = get_offset(1, 0, 1.0); + assert_eq!(*target_x, 100.0 + offset_x); + assert_eq!(*target_y, 100.0 + offset_y); + + assert_eq!(*elapsed_time, 0.0); + assert_eq!(*duration, MOVE_DURATION); + } + other => panic!("Expected Moving movement, got {:?}", other), + } + + assert_eq!(player.tile_x, 6); + assert_eq!(player.tile_y, 5); + + assert_eq!(player.pixel_x, 100.0); + assert_eq!(player.pixel_y, 100.0); + } +} + +#[cfg(test)] +mod update_player_tests { + use super::*; + use ferari::assets::GameMap; + + fn create_test_map() -> GameMap { + GameMap::load("../game_levels/level1.json").unwrap() + } + + fn create_unit_with_movement(movement: UnitMovement) -> Unit { + Unit { + pixel_x: 496.0, + pixel_y: 664.0, + tile_x: 1, + tile_y: 1, + x_speed: 10.0, + y_speed: 10.0, + movement, + direction: Direction::SW, + } + } + + #[test] + fn test_update_player_animation_pre_pushing() { + let mut unit = create_unit_with_movement(UnitMovement::PrePushing { + start_x: 496.0, + start_y: 664.0, + target_x: 432.0, + target_y: 696.0, + elapsed_time: 0.8, + duration: 1.0, + box_idx: 0, + player_next_tx: 1, + player_next_ty: 2, + box_next_tx: 1, + box_next_ty: 3, + push_dx: 0, + push_dy: 1, + }); + + let delta = 0.3; + let input_state = + InputSnapshot { up: false, left: false, down: true, right: false, escape: false }; + + let game_map = create_test_map(); + let map_width = game_map.size[0] as usize; + let mob_grid = vec![None; map_width * game_map.size[1] as usize]; + + let result = update_player_animation( + &mut unit, + delta, + &input_state, + &mob_grid, + &game_map, + map_width, + ); + + assert!(result.player_is_busy); + + match result.transition_to_push { + Some((box_idx, p_tx, p_ty, b_tx, b_ty, dx, dy)) => { + assert_eq!(box_idx, 0); + assert_eq!(p_tx, 1); + assert_eq!(p_ty, 2); + assert_eq!(b_tx, 1); + assert_eq!(b_ty, 3); + assert_eq!(dx, 0); + assert_eq!(dy, 1); + } + None => panic!("Expected transition_to_push after PrePushing"), + } + + assert_eq!(unit.pixel_x, 432.0); + assert_eq!(unit.pixel_y, 696.0); + + assert!(result.transition_to_post.is_none()); + } + + #[test] + fn test_update_player_animation_pushing() { + let mut unit = create_unit_with_movement(UnitMovement::Pushing { + start_x: 496.0, + start_y: 664.0, + target_x: 432.0, + target_y: 696.0, + elapsed_time: 0.9, + duration: 1.0, + recoil_target_x: 496.0, + recoil_target_y: 664.0, + }); + + let delta = 0.2; + let input_state = + InputSnapshot { up: false, left: false, down: true, right: false, escape: false }; + + let game_map = create_test_map(); + let map_width = game_map.size[0] as usize; + let mut mob_grid = vec![None; map_width * game_map.size[1] as usize]; + + let cur_box_idx = 2 * map_width + 1; + mob_grid[cur_box_idx] = Some(0); + + let result = update_player_animation( + &mut unit, + delta, + &input_state, + &mob_grid, + &game_map, + map_width, + ); + + assert!(result.player_is_busy); + + match result.transition_to_push { + Some((box_idx, p_tx, p_ty, b_tx, b_ty, dx, dy)) => { + assert_eq!(box_idx, 0); + assert_eq!(p_tx, 1); + assert_eq!(p_ty, 2); + assert_eq!(b_tx, 1); + assert_eq!(b_ty, 3); + assert_eq!(dx, 0); + assert_eq!(dy, 1); + } + None => panic!("Expected transition_to_push if enabled pushing"), + } + + assert_eq!(unit.pixel_x, 432.0); + assert_eq!(unit.pixel_y, 696.0); + + assert!(result.transition_to_post.is_none()); + } +} + +#[cfg(test)] +mod make_step_tests { + use super::*; + use ferari::assets::GameMap; + + fn create_test_map() -> GameMap { + GameMap::load("../game_levels/level2.json").unwrap() + } + + #[test] + fn test_make_step_simple_walk() { + let game_map = create_test_map(); + let mut state = State::new(&game_map); + + let input_state = + InputSnapshot { up: false, left: false, down: false, right: true, escape: false }; + let delta = 0.016; + let result = make_step(&mut state, &input_state, delta, &game_map); + assert!(result.is_none()); + + match &state.player.unit.movement { + UnitMovement::Moving { .. } => {} + other => panic!("Expected Moving movement, got {:?}", other), + } + + assert_eq!(state.player.unit.direction, Direction::SE); + assert_eq!(state.player.unit.tile_x, 3); + assert_eq!(state.player.unit.tile_y, 3); + } + + #[test] + fn test_make_step_push_box() { + let game_map = create_test_map(); + let mut state = State::new(&game_map); + + let map_width = game_map.size[0] as usize; + let box_front_idx = 1 * map_width + 2; + state.mob_grid[box_front_idx] = Some(0); + + let input_state = + InputSnapshot { up: false, left: true, down: false, right: false, escape: false }; + let delta = 0.016; + let result = make_step(&mut state, &input_state, delta, &game_map); + assert!(result.is_none()); + + match &state.player.unit.movement { + UnitMovement::PrePushing { .. } => {} + other => panic!("Expected PrePushing movement, got {:?}", other), + } + + assert_eq!(state.player.unit.direction, Direction::NW); + assert_eq!(state.player.unit.tile_x, 2); + assert_eq!(state.player.unit.tile_y, 3); + } +} diff --git a/game/src/initiator.rs b/game/src/initiator.rs new file mode 100644 index 0000000..e5de58b --- /dev/null +++ b/game/src/initiator.rs @@ -0,0 +1,336 @@ +use crate::{ + world::{Camera, Unit}, + MOVEMENT_SPEEDUP, +}; + +use ferari::world::{Player, State, UnitMovement}; + +/// Returns a list of game objects that are currently visible within the camera's view. +/// +/// This function filters all game units (player and mobs) to only include those +/// that fall within the camera's current field of view. The visibility is determined +/// by the camera's position and viewport dimensions. +/// +/// # Arguments +/// +/// * `cur_state` - The current game state containing all units +/// * `camera` - The camera that defines the visible area of the game world +/// +/// # Returns +/// +/// A vector containing all [`Unit`] objects that are currently visible to the camera. +/// The player unit is always included first, followed by any visible mobs. +pub fn get_visible_objects(cur_state: &State, camera: &Camera) -> Vec { + let mut units = Vec::new(); + units.push(cur_state.player.unit.clone()); + units.extend(cur_state.mobs.clone()); + + units.into_iter().filter(|mob| camera.is_visible(mob.pixel_x, mob.pixel_y)).collect() +} + +/// Performs linear interpolation between two scalar values. +/// +/// Given start value `a`, end value `b`, and interpolation factor `t` (typically in `[0.0, 1.0]`), +/// this function computes a value that lies `t` percent of the way from `a` to `b`. +/// +/// # Formula +/// +/// ```text +/// result = a * (1 - t) + b * t +/// ``` +/// +/// # Arguments +/// +/// * `a` - the starting value +/// * `b` - the ending value +/// * `t` - the interpolation parameter +/// +/// # Returns +/// +/// The interpolated value between `a` and `b`. +pub fn lerp(a: f32, b: f32, t: f32) -> f32 { + a * (1.0 - t) + b * t +} + +/// Generates the appropriate sprite name for the player based on their current state and time. +/// +/// The sprite name encodes: +/// - The animation type (e.g., `"idle"`, `"running"`, `"pushing"`) +/// - The player's facing direction (e.g., `"SE"`, `"NW"`) +/// - The current frame index within the animation cycle +/// +/// The animation frame is determined by elapsed `total_time`, scaled by `MOVEMENT_SPEEDUP` +/// to maintain consistent animation speed under time-warping effects. +/// +/// # Arguments +/// +/// * `player` – reference to the player object containing movement and direction state +/// * `total_time` – total elapsed game time in seconds +/// +/// # Returns +/// +/// A string in the format `"{animation}_{direction}_{frame_index}"`, suitable for +/// loading the correct sprite from asset resources (e.g., `"running_se_5"`). +pub fn get_player_sprite(player: &Player, total_time: f64) -> String { + let k = 1.0 / 1000.0 / MOVEMENT_SPEEDUP as f64; + let (prefix, total_frames, period) = match player.unit.movement { + UnitMovement::Moving { .. } => ("running", 14, 45.0 * k), + UnitMovement::Pushing { .. } => ("pushing", 37, 25.0 * k), + UnitMovement::Idle => ("idle", 31, 45.0 * k), + UnitMovement::PrePushing { .. } => ("walkingforward", 24, 30.0 * k), + UnitMovement::PostPushing { .. } => ("walkingback", 23, 30.0 * k), + }; + + let dir_suffix = player.unit.direction.as_str(); + + let cycles = (total_time / period).floor() as u32; + let frame_idx = cycles % total_frames; + + format!("{}_{}_{}", prefix, dir_suffix, frame_idx) +} + +#[cfg(test)] +mod visible_objects_tests { + use super::*; + + #[derive(Clone)] + struct DummyUnit { + x: f32, + y: f32, + } + impl DummyUnit { + fn to_real_unit(&self) -> Unit { + Unit { + pixel_x: self.x, + pixel_y: self.y, + tile_x: 0, + tile_y: 0, + x_speed: 0.0, + y_speed: 0.0, + movement: UnitMovement::default(), + direction: ferari::world::Direction::default(), + } + } + } + + #[derive(Clone)] + struct DummyState { + player: DummyUnit, + mobs: Vec, + } + + impl DummyState { + fn to_real_state(&self) -> State { + State { + player: Player { unit: self.player.to_real_unit() }, + mobs: self.mobs.iter().map(|m| m.to_real_unit()).collect(), + mob_grid: vec![None; 128 * 128], + grid_width: 128, + } + } + } + + #[test] + fn test_get_visible_objects_player_included() { + let dummy_state = DummyState { player: DummyUnit { x: 0.0, y: 0.0 }, mobs: vec![] }; + let state = dummy_state.to_real_state(); + + let camera = Camera::new(0.0, 0.0, 800, 600); + let visible = get_visible_objects(&state, &camera); + + assert_eq!(visible.len(), 1); + assert_eq!(visible[0].pixel_x, state.player.unit.pixel_x); + assert_eq!(visible[0].pixel_y, state.player.unit.pixel_y); + } + + #[test] + fn test_get_visible_objects_mobs_visible() { + let dummy_state = DummyState { + player: DummyUnit { x: 0.0, y: 0.0 }, + mobs: vec![DummyUnit { x: 10.0, y: 10.0 }, DummyUnit { x: 1000.0, y: 1000.0 }], + }; + let state = dummy_state.to_real_state(); + let camera = Camera::new(0.0, 0.0, 50, 50); + + let visible = get_visible_objects(&state, &camera); + assert_eq!(visible.len(), 2); + assert_eq!(visible[1].pixel_x, 10.0); + assert_eq!(visible[1].pixel_y, 10.0); + } + + #[test] + fn test_get_visible_objects_mobs_outside_not_included() { + let dummy_state = DummyState { + player: DummyUnit { x: 0.0, y: 0.0 }, + mobs: vec![DummyUnit { x: 100.0, y: 100.0 }], + }; + let state = dummy_state.to_real_state(); + let camera = Camera::new(0.0, 0.0, 50, 50); + + let visible = get_visible_objects(&state, &camera); + assert_eq!(visible.len(), 1); + assert_eq!(visible[0].pixel_x, state.player.unit.pixel_x); + } + + #[test] + fn test_get_visible_objects_multiple_mobs() { + let dummy_state = DummyState { + player: DummyUnit { x: 0.0, y: 0.0 }, + mobs: vec![ + DummyUnit { x: 5.0, y: 5.0 }, + DummyUnit { x: 20.0, y: 20.0 }, + DummyUnit { x: 100.0, y: 100.0 }, + ], + }; + let state = dummy_state.to_real_state(); + let camera = Camera::new(0.0, 0.0, 50, 50); + + let visible = get_visible_objects(&state, &camera); + assert_eq!(visible.len(), 3); + let positions: Vec<_> = visible.iter().map(|u| (u.pixel_x, u.pixel_y)).collect(); + assert!(positions.contains(&(0.0, 0.0))); + assert!(positions.contains(&(5.0, 5.0))); + assert!(positions.contains(&(20.0, 20.0))); + } +} + +#[cfg(test)] +mod lerp_tests { + use super::*; + + #[test] + fn test_lerp_basic() { + assert_eq!(lerp(0.0, 10.0, 0.0), 0.0); + assert_eq!(lerp(0.0, 10.0, 1.0), 10.0); + assert_eq!(lerp(0.0, 10.0, 0.5), 5.0); + } + + #[test] + fn test_lerp_negative_values() { + assert_eq!(lerp(-10.0, 10.0, 0.5), 0.0); + assert_eq!(lerp(-10.0, 0.0, 0.5), -5.0); + assert_eq!(lerp(10.0, -10.0, 0.5), 0.0); + } + + #[test] + fn test_lerp_extreme_t() { + assert_eq!(lerp(0.0, 10.0, 2.0), 20.0); // t > 1.0 + assert_eq!(lerp(0.0, 10.0, -1.0), -10.0); // t < 0.0 + } +} + +#[cfg(test)] +mod player_sprite_tests { + use super::*; + use ferari::world::Direction; + + #[derive(Clone)] + struct DummyPlayer { + movement: UnitMovement, + direction: Direction, + } + impl DummyPlayer { + fn to_real_player(&self) -> Player { + Player { + unit: Unit { + pixel_x: 0.0, + pixel_y: 0.0, + tile_x: 0, + tile_y: 0, + x_speed: 0.0, + y_speed: 0.0, + movement: self.movement.clone(), + direction: self.direction.clone(), + }, + } + } + } + + fn create_test_movement(movement_type: &str) -> UnitMovement { + match movement_type { + "moving" => UnitMovement::Moving { + start_x: 0.0, + start_y: 0.0, + target_x: 1.0, + target_y: 1.0, + elapsed_time: 0.0, + duration: 1.0, + }, + "pushing" => UnitMovement::Pushing { + start_x: 0.0, + start_y: 0.0, + target_x: 1.0, + target_y: 1.0, + elapsed_time: 0.0, + duration: 1.0, + recoil_target_x: 0.0, + recoil_target_y: 0.0, + }, + "pre_pushing" => UnitMovement::PrePushing { + start_x: 0.0, + start_y: 0.0, + target_x: 1.0, + target_y: 1.0, + elapsed_time: 0.0, + duration: 1.0, + box_idx: 0, + player_next_tx: 0, + player_next_ty: 0, + box_next_tx: 0, + box_next_ty: 0, + push_dx: 0, + push_dy: 0, + }, + "post_pushing" => UnitMovement::PostPushing { + start_x: 0.0, + start_y: 0.0, + target_x: 1.0, + target_y: 1.0, + elapsed_time: 0.0, + duration: 1.0, + }, + "idle" => UnitMovement::Idle, + _ => panic!("Unknown movement type"), + } + } + + #[test] + fn test_get_player_sprite_running() { + let period = 45.0 * (1.0 / 1000.0); + let dummy_player = + DummyPlayer { movement: create_test_movement("moving"), direction: Direction::SE }; + let player = dummy_player.to_real_player(); + + let sprite = get_player_sprite(&player, 0.0); + assert_eq!(sprite, "running_se_0"); + + let sprite1 = get_player_sprite(&player, period * 13.0); + assert_eq!(sprite1, "running_se_13"); + + let sprite2 = get_player_sprite(&player, period * 14.0); + assert_eq!(sprite2, "running_se_0"); + + let sprite3 = get_player_sprite(&player, period * 27.0); + assert_eq!(sprite3, "running_se_12"); + } + + #[test] + fn test_get_player_sprite_pushing() { + let period = 25.0 * (1.0 / 1000.0); + let dummy_player = + DummyPlayer { movement: create_test_movement("pushing"), direction: Direction::SE }; + let player = dummy_player.to_real_player(); + + let sprite = get_player_sprite(&player, 0.0); + assert_eq!(sprite, "pushing_se_0"); + + let sprite1 = get_player_sprite(&player, period * 1.0); + assert_eq!(sprite1, "pushing_se_1"); + + let sprite2 = get_player_sprite(&player, period * 3.0); + assert_eq!(sprite2, "pushing_se_3"); + + let sprite3 = get_player_sprite(&player, period * 37.0); + assert_eq!(sprite3, "pushing_se_0"); + } +} diff --git a/game/src/main.rs b/game/src/main.rs new file mode 100644 index 0000000..9359db1 --- /dev/null +++ b/game/src/main.rs @@ -0,0 +1,292 @@ +use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +#[cfg(target_os = "macos")] +use minifb::{Key, Window, WindowOptions}; + +use crossbeam_channel::bounded; + +use crate::behaviour::make_step; +use crate::initiator::{get_player_sprite, get_visible_objects}; + +use ferari::assets; +#[cfg(target_os = "linux")] +use ferari::draw; +use ferari::input; +use ferari::render; +use ferari::time; +use ferari::world; +mod behaviour; +mod initiator; + +use ferari::render::RenderableEntity; + +/// Animation and movement speedup. +pub const MOVEMENT_SPEEDUP: f32 = 1.0; +/// Upscaling factor for display. +pub const UPSCALE: usize = 2; +/// Logical screen width in pixels. +pub const LOGIC_WIDTH: usize = 1280 / UPSCALE; +/// Logical screen height in pixels. +pub const LOGIC_HEIGHT: usize = 720 / UPSCALE; +/// Tile size in pixels. +pub const TILE_SIZE: usize = 128; +/// Filesystem paths to all available game levels. +const LEVEL_PATHS: &[&str] = &[ + "game_levels/menu.json", + "game_levels/level1.json", + "game_levels/level2.json", + "game_levels/level3.json", + "game_levels/level4.json", + "game_levels/level5.json", +]; +/// Target frame duration for a stable gameplay loop. +const FRAME_TIME: Duration = Duration::from_micros(16667); // ~60 FPS + +#[cfg(target_os = "macos")] +macro_rules! update_window { + ($window:ident, $running:ident, $rx_frame:ident, $input_state:ident, $width:ident, $height:ident) => { + // handle input in this thread + $input_state.update(&$window); + + if let Ok(frame) = $rx_frame.try_recv() { + $window.update_with_buffer(&frame, $width, $height).unwrap(); + } else { + $window.update(); + } + + if $window.is_key_down(Key::Escape) || !$window.is_open() { + $running.store(false, Ordering::Release); + break; + } + + thread::sleep(Duration::from_millis(1)); + }; +} + +/// Initializes all core systems required to start a new game level. +/// +/// This function sets up the rendering pipeline, camera, and game state based on +/// the provided level data and graphical assets. +/// +/// # Arguments +/// +/// * `game` – the preloaded game map containing layout, walkability, and object placement +/// * `entities_atlas` – texture atlas containing sprites for dynamic entities +/// * `tiles_atlas` – texture atlas containing static tile graphics +/// +/// # Returns +/// +/// A tuple containing: +/// * [`ferari::Render`] - the fully initialized renderer with pre-rendered static background. +/// * [`world::Camera`] - a camera centered on the level, configured to the logical screen dimensions. +/// * [`world::State`] - the initial game state (player position, mob positions, grid occupancy, etc.). +fn init_level( + game: assets::GameMap, + entities_atlas: assets::Atlas, + tiles_atlas: assets::Atlas, +) -> (ferari::Render, world::Camera, world::State) { + // init world_buf + let world_width = game.size[0] as usize * TILE_SIZE * 2; + let world_height = game.size[1] as usize * TILE_SIZE * 2; + let world_buf: Vec = vec![195213255; world_width * world_height]; + + // init render + let shadow_map: Vec = vec![0; world_width * world_height]; + let mut render = + render::Render::new(world_buf, world_height, world_width, entities_atlas, shadow_map); + + // init camera + let camera = world::Camera::new( + (world_width / 2) as f32, + (world_height / 2) as f32, + LOGIC_WIDTH as u16, + LOGIC_HEIGHT as u16, + ); + + // init state of game + let state = world::State::new(&game); + + // prerender + render.init(&game, &tiles_atlas); + + (render, camera, state) +} + +fn main() { + // Need to find root directory + let project_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".."); + + let assets_path = project_root.join("assets"); + + // parse atlases + let tiles_path = assets_path.join("tiles/atlas.json"); + let entities_path = assets_path.join("entities/atlas.json"); + + let tiles_atlas = assets::Atlas::load(tiles_path.to_str().unwrap()).unwrap(); + let entities_atlas = assets::Atlas::load(entities_path.to_str().unwrap()).unwrap(); + + // parse game descr + let menu_path = project_root.join("game_levels/menu.json"); + let mut game = assets::GameMap::load(menu_path).unwrap(); + + let mut cur_level = 0; + let mut cur_level2 = 0; + + // init draw + let input_state = Arc::new(input::InputState::new()); + let running: Arc = Arc::new(AtomicBool::new(true)); + let (tx_frame, rx_frame) = bounded::>(2); + + // framebuffer (`render <-> draw` connection) + let mut back_buffer: Vec = vec![0; LOGIC_WIDTH * LOGIC_HEIGHT]; + + // init time + let mut time = time::Time::new(); + + // Platform-specific draw thread + + #[cfg(target_os = "linux")] + { + let input_state = input_state.clone(); + let running = running.clone(); + + thread::spawn(move || { + draw::run_draw_thread( + rx_frame, + input_state, + running, + LOGIC_WIDTH, + LOGIC_HEIGHT, + UPSCALE, + ); + }); + } + + #[cfg(target_os = "macos")] + let mut window = Window::new( + "Ferari", + LOGIC_WIDTH * UPSCALE, + LOGIC_HEIGHT * UPSCALE, + WindowOptions::default(), + ) + .unwrap(); + + let (mut render, mut camera, mut state) = + init_level(game.clone(), entities_atlas.clone(), tiles_atlas.clone()); + + // game loop + while running.load(Ordering::Acquire) { + if cur_level != cur_level2 { + let level_path = project_root.join(LEVEL_PATHS[cur_level2 as usize]); + + let loaded_game = assets::GameMap::load(level_path).unwrap(); + game = loaded_game; + cur_level = cur_level2; + + (render, camera, state) = + init_level(game.clone(), entities_atlas.clone(), tiles_atlas.clone()); + } + + #[cfg(target_os = "macos")] + update_window!(window, running, rx_frame, input_state, LOGIC_WIDTH, LOGIC_HEIGHT); + + time.update(); + + // process input + let input = input_state.read(); + if input.escape { + running.store(false, Ordering::Release); + } + + match make_step(&mut state, &input, time.delta, &game) { + None => (), + Some(id) => cur_level2 = id, + } + + camera.center_x = state.player.unit.pixel_x.floor(); + camera.center_y = state.player.unit.pixel_y.floor(); + + let units_for_render = get_visible_objects(&state, &camera); + if units_for_render.is_empty() { + continue; + } + + let mut suc_boxes = vec![]; + + // menu + if cur_level == 0 { + let player_idle = matches!(state.player.unit.movement, world::UnitMovement::Idle); + + for unit in &state.mobs { + if matches!(unit.movement, world::UnitMovement::Idle) & player_idle { + let pos = (unit.tile_x as u32, unit.tile_y as u32); + + if let Some(&id) = game.links.get(&pos) { + cur_level2 = id; + suc_boxes.push((unit.tile_x, unit.tile_y)); + break; + } + } + } + } + // gameplay level: check box placement and win condition + else { + let goal_count = game.target_positions.len(); + + if goal_count == 0 { + cur_level2 = 0; + } else { + let mut placed_count = 0; + let player_idle = matches!(state.player.unit.movement, world::UnitMovement::Idle); + + for unit in &state.mobs { + if matches!(unit.movement, world::UnitMovement::Idle) { + let pos = (unit.tile_x as u32, unit.tile_y as u32); + + if game.target_positions.contains(&pos) { + suc_boxes.push((unit.tile_x, unit.tile_y)); + placed_count += 1; + } + } + } + + if placed_count == goal_count && player_idle { + cur_level2 = 0; + } + } + } + + // frame render + let visible_entities: Vec = units_for_render + .into_iter() + .enumerate() + .map(|(i, unit)| { + let sprite_name = if i == 0 { + get_player_sprite(&state.player, time.total as f64) + } else if suc_boxes.contains(&(unit.tile_x, unit.tile_y)) { + "green_box".to_string() + } else { + "box".to_string() + }; + + RenderableEntity::new(unit.pixel_x, unit.pixel_y, sprite_name) + }) + .collect(); + + render.render_frame(&visible_entities, &camera, &mut back_buffer); + + // draw frame + if tx_frame.try_send(back_buffer.clone()).is_err() { + // idle + } + + // fps limit + thread::sleep(FRAME_TIME); + } + + println!("Main loop exited"); +} diff --git a/game_levels/genlevel.py b/game_levels/genlevel.py new file mode 100644 index 0000000..f13cc26 --- /dev/null +++ b/game_levels/genlevel.py @@ -0,0 +1,133 @@ +import json + +# ========================================== +# CONFIGURATION +# ========================================== + +# ------------------------------------------ +# LEGEND: +# ' ' or '-' : Floor +# '#' : Wall (creates Concrete tile + Wall object) +# '.' : Target (creates Target tile) +# '$' : Box (creates Floor tile + Box mob) +# '*' : Box on Target (creates Target tile + Box mob) +# '@' : Player (creates Floor tile + Player mob) +# '+' : Player on Target (creates Target tile + Player mob) +# ------------------------------------------ + +LEVEL_NAME = "level2" +OUTPUT_FILENAME = f"{LEVEL_NAME}.json" + +LEVEL_LAYOUT = [ + "# #", + "# #", + ".$$ ", + " $@.", + "#. #" +] + +# ========================================== +# GENERATOR LOGIC +# ========================================== + +def generate_json(name, layout, filename): + height = len(layout) + width = max(len(row) for row in layout) if height > 0 else 0 + + data = { + "meta": { + "name": name, + "tile_size": 128, + "size": [width, height] + }, + "tiles": {}, + "objects": {}, + "mobs": {} + } + + tile_counter = 1 + wall_counter = 1 + box_counter = 1 + + player_found = False + + for y, row in enumerate(layout): + # Pad row with spaces if it's shorter than the max width + padded_row = row.ljust(width, ' ') + + for x, char in enumerate(padded_row): + + # 1. DETERMINE BASE TILE + # ---------------------- + tile_key = f"tile_{tile_counter}" + tile_asset = "floor" # Default + tile_type = "empty" # Default + + # Logic for specific tiles + if char == '#': + tile_asset = "concrete" # Requirement: Under wall is concrete + elif char in ['.', '*', '+']: + tile_asset = "target" + tile_type = "target" + + data["tiles"][tile_key] = { + "x": x, + "y": y, + "asset": tile_asset, + "tile_type": tile_type + } + tile_counter += 1 + + # 2. DETERMINE STATIC OBJECTS (Walls) + # ---------------------- + if char == '#': + wall_key = f"wall_{wall_counter}" + data["objects"][wall_key] = { + "x": x, + "y": y, + "asset": "wall_tile", + "collidable": True + } + wall_counter += 1 + + # 3. DETERMINE MOBS (Player / Boxes) + # ---------------------- + + # Check for Player (@) or Player on Target (+) + if char in ['@', '+']: + if player_found: + print(f"WARNING: Multiple players found at {x},{y}. Overwriting previous.") + + data["mobs"]["player"] = { + "x_start": x, + "y_start": y, + "asset": "idle_se_0", + "is_player": True, + "behaviour": { + "type": "controlled" + } + } + player_found = True + + # Check for Box ($) or Box on Target (*) + if char in ['$', '*']: + box_key = f"box_{box_counter}" + data["mobs"][box_key] = { + "x_start": x, + "y_start": y, + "asset": "box", + "is_player": False + } + box_counter += 1 + + # Write to file + try: + with open(filename, 'w') as f: + json.dump(data, f, indent=4) + print(f"Success! Generated '{filename}' with dimensions {width}x{height}.") + print(f"Stats: {wall_counter-1} Walls, {box_counter-1} Boxes.") + except IOError as e: + print(f"Error writing file: {e}") + +if __name__ == "__main__": + generate_json(LEVEL_NAME, LEVEL_LAYOUT, OUTPUT_FILENAME) diff --git a/game_levels/level1.json b/game_levels/level1.json new file mode 100644 index 0000000..02f66c6 --- /dev/null +++ b/game_levels/level1.json @@ -0,0 +1,135 @@ +{ + "meta": { + "name": "level1", + "tile_size": 128, + "size": [3, 6] + }, + "tiles": { + "tile_1": { + "x": 0, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_2": { + "x": 1, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_3": { + "x": 2, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_4": { + "x": 0, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_5": { + "x": 1, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_6": { + "x": 2, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_7": { + "x": 0, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_8": { + "x": 1, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_9": { + "x": 2, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_10": { + "x": 0, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_11": { + "x": 1, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_12": { + "x": 2, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_13": { + "x": 0, + "y": 4, + "asset": "floor", + "tile_type": "empty" + }, + "tile_14": { + "x": 1, + "y": 4, + "asset": "target", + "tile_type": "target" + }, + "tile_15": { + "x": 2, + "y": 4, + "asset": "floor", + "tile_type": "empty" + }, + "tile_16": { + "x": 0, + "y": 5, + "asset": "floor", + "tile_type": "empty" + }, + "tile_17": { + "x": 1, + "y": 5, + "asset": "floor", + "tile_type": "empty" + }, + "tile_18": { + "x": 2, + "y": 5, + "asset": "floor", + "tile_type": "empty" + } + }, + "objects": {}, + "mobs": { + "player": { + "x_start": 1, + "y_start": 0, + "asset": "idle_se_0", + "is_player": true, + "behaviour": { + "type": "controlled" + } + }, + "box_1": { + "x_start": 1, + "y_start": 1, + "asset": "box", + "is_player": false + } + } +} diff --git a/game_levels/level2.json b/game_levels/level2.json new file mode 100644 index 0000000..9fb7219 --- /dev/null +++ b/game_levels/level2.json @@ -0,0 +1,196 @@ +{ + "meta": { + "name": "level2", + "tile_size": 128, + "size": [4, 5] + }, + "tiles": { + "tile_1": { + "x": 0, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_2": { + "x": 1, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_3": { + "x": 2, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_4": { + "x": 3, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_5": { + "x": 0, + "y": 1, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_6": { + "x": 1, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_7": { + "x": 2, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_8": { + "x": 3, + "y": 1, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_9": { + "x": 0, + "y": 2, + "asset": "target", + "tile_type": "target" + }, + "tile_10": { + "x": 1, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_11": { + "x": 2, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_12": { + "x": 3, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_13": { + "x": 0, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_14": { + "x": 1, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_15": { + "x": 2, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_16": { + "x": 3, + "y": 3, + "asset": "target", + "tile_type": "target" + }, + "tile_17": { + "x": 0, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_18": { + "x": 1, + "y": 4, + "asset": "target", + "tile_type": "target" + }, + "tile_19": { + "x": 2, + "y": 4, + "asset": "floor", + "tile_type": "empty" + }, + "tile_20": { + "x": 3, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + } + }, + "objects": { + "wall_1": { + "x": 0, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_2": { + "x": 3, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_3": { + "x": 0, + "y": 1, + "asset": "wall_tile", + "collidable": true + }, + "wall_4": { + "x": 3, + "y": 1, + "asset": "wall_tile", + "collidable": true + }, + "wall_5": { + "x": 0, + "y": 4, + "asset": "wall_tile", + "collidable": true + }, + "wall_6": { + "x": 3, + "y": 4, + "asset": "wall_tile", + "collidable": true + } + }, + "mobs": { + "box_1": { + "x_start": 1, + "y_start": 2, + "asset": "box", + "is_player": false + }, + "box_2": { + "x_start": 2, + "y_start": 2, + "asset": "box", + "is_player": false + }, + "box_3": { + "x_start": 1, + "y_start": 3, + "asset": "box", + "is_player": false + }, + "player": { + "x_start": 2, + "y_start": 3, + "asset": "idle_se_0", + "is_player": true, + "behaviour": { + "type": "controlled" + } + } + } +} diff --git a/game_levels/level3.json b/game_levels/level3.json new file mode 100644 index 0000000..5fdb256 --- /dev/null +++ b/game_levels/level3.json @@ -0,0 +1,232 @@ +{ + "meta": { + "name": "level3", + "tile_size": 128, + "size": [4, 6] + }, + "tiles": { + "tile_1": { + "x": 0, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_2": { + "x": 1, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_3": { + "x": 2, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_4": { + "x": 3, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_5": { + "x": 0, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_6": { + "x": 1, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_7": { + "x": 2, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_8": { + "x": 3, + "y": 1, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_9": { + "x": 0, + "y": 2, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_10": { + "x": 1, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_11": { + "x": 2, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_12": { + "x": 3, + "y": 2, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_13": { + "x": 0, + "y": 3, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_14": { + "x": 1, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_15": { + "x": 2, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_16": { + "x": 3, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_17": { + "x": 0, + "y": 4, + "asset": "target", + "tile_type": "target" + }, + "tile_18": { + "x": 1, + "y": 4, + "asset": "floor", + "tile_type": "empty" + }, + "tile_19": { + "x": 2, + "y": 4, + "asset": "floor", + "tile_type": "empty" + }, + "tile_20": { + "x": 3, + "y": 4, + "asset": "floor", + "tile_type": "empty" + }, + "tile_21": { + "x": 0, + "y": 5, + "asset": "target", + "tile_type": "target" + }, + "tile_22": { + "x": 1, + "y": 5, + "asset": "target", + "tile_type": "target" + }, + "tile_23": { + "x": 2, + "y": 5, + "asset": "target", + "tile_type": "target" + }, + "tile_24": { + "x": 3, + "y": 5, + "asset": "target", + "tile_type": "target" + } + }, + "objects": { + "wall_1": { + "x": 0, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_2": { + "x": 3, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_3": { + "x": 3, + "y": 1, + "asset": "wall_tile", + "collidable": true + }, + "wall_4": { + "x": 0, + "y": 2, + "asset": "wall_tile", + "collidable": true + }, + "wall_5": { + "x": 3, + "y": 2, + "asset": "wall_tile", + "collidable": true + }, + "wall_6": { + "x": 0, + "y": 3, + "asset": "wall_tile", + "collidable": true + } + }, + "mobs": { + "player": { + "x_start": 0, + "y_start": 1, + "asset": "idle_se_0", + "is_player": true, + "behaviour": { + "type": "controlled" + } + }, + "box_1": { + "x_start": 1, + "y_start": 1, + "asset": "box", + "is_player": false + }, + "box_2": { + "x_start": 1, + "y_start": 2, + "asset": "box", + "is_player": false + }, + "box_3": { + "x_start": 2, + "y_start": 3, + "asset": "box", + "is_player": false + }, + "box_4": { + "x_start": 1, + "y_start": 4, + "asset": "box", + "is_player": false + }, + "box_5": { + "x_start": 2, + "y_start": 5, + "asset": "box", + "is_player": false + } + } +} diff --git a/game_levels/level4.json b/game_levels/level4.json new file mode 100644 index 0000000..dffb40e --- /dev/null +++ b/game_levels/level4.json @@ -0,0 +1,145 @@ +{ + "meta": { + "name": "level4", + "tile_size": 128, + "size": [ + 4, + 4 + ] + }, + "tiles": { + "tile_1": { + "x": 0, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_2": { + "x": 1, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_3": { + "x": 2, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_4": { + "x": 3, + "y": 0, + "asset": "target", + "tile_type": "target" + }, + "tile_5": { + "x": 0, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_6": { + "x": 1, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_7": { + "x": 2, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_8": { + "x": 3, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_9": { + "x": 0, + "y": 2, + "asset": "target", + "tile_type": "target" + }, + "tile_10": { + "x": 1, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_11": { + "x": 2, + "y": 2, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_12": { + "x": 3, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_13": { + "x": 0, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_14": { + "x": 1, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_15": { + "x": 2, + "y": 3, + "asset": "target", + "tile_type": "target" + }, + "tile_16": { + "x": 3, + "y": 3, + "asset": "floor", + "tile_type": "empty" + } + }, + "objects": { + "wall_1": { + "x": 2, + "y": 2, + "asset": "wall_tile", + "collidable": true + } + }, + "mobs": { + "player": { + "x_start": 0, + "y_start": 0, + "asset": "idle_se_0", + "is_player": true, + "behaviour": { + "type": "controlled" + } + }, + "box_1": { + "x_start": 0, + "y_start": 1, + "asset": "box", + "is_player": false + }, + "box_2": { + "x_start": 1, + "y_start": 1, + "asset": "box", + "is_player": false + }, + "box_3": { + "x_start": 2, + "y_start": 1, + "asset": "box", + "is_player": false + } + } +} diff --git a/game_levels/level5.json b/game_levels/level5.json new file mode 100644 index 0000000..1884ef0 --- /dev/null +++ b/game_levels/level5.json @@ -0,0 +1,1336 @@ +{ + "meta": { + "name": "level5", + "tile_size": 128, + "size": [12, 12] + }, + "tiles": { + "tile_1": { + "x": 0, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_2": { + "x": 1, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_3": { + "x": 2, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_4": { + "x": 3, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_5": { + "x": 4, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_6": { + "x": 5, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_7": { + "x": 6, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_8": { + "x": 7, + "y": 0, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_9": { + "x": 8, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_10": { + "x": 9, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_11": { + "x": 10, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_12": { + "x": 11, + "y": 0, + "asset": "floor", + "tile_type": "empty" + }, + "tile_13": { + "x": 0, + "y": 1, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_14": { + "x": 1, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_15": { + "x": 2, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_16": { + "x": 3, + "y": 1, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_17": { + "x": 4, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_18": { + "x": 5, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_19": { + "x": 6, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_20": { + "x": 7, + "y": 1, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_21": { + "x": 8, + "y": 1, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_22": { + "x": 9, + "y": 1, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_23": { + "x": 10, + "y": 1, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_24": { + "x": 11, + "y": 1, + "asset": "floor", + "tile_type": "empty" + }, + "tile_25": { + "x": 0, + "y": 2, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_26": { + "x": 1, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_27": { + "x": 2, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_28": { + "x": 3, + "y": 2, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_29": { + "x": 4, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_30": { + "x": 5, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_31": { + "x": 6, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_32": { + "x": 7, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_33": { + "x": 8, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_34": { + "x": 9, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_35": { + "x": 10, + "y": 2, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_36": { + "x": 11, + "y": 2, + "asset": "floor", + "tile_type": "empty" + }, + "tile_37": { + "x": 0, + "y": 3, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_38": { + "x": 1, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_39": { + "x": 2, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_40": { + "x": 3, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_41": { + "x": 4, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_42": { + "x": 5, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_43": { + "x": 6, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_44": { + "x": 7, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_45": { + "x": 8, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_46": { + "x": 9, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_47": { + "x": 10, + "y": 3, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_48": { + "x": 11, + "y": 3, + "asset": "floor", + "tile_type": "empty" + }, + "tile_49": { + "x": 0, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_50": { + "x": 1, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_51": { + "x": 2, + "y": 4, + "asset": "floor", + "tile_type": "empty" + }, + "tile_52": { + "x": 3, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_53": { + "x": 4, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_54": { + "x": 5, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_55": { + "x": 6, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_56": { + "x": 7, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_57": { + "x": 8, + "y": 4, + "asset": "floor", + "tile_type": "empty" + }, + "tile_58": { + "x": 9, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_59": { + "x": 10, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_60": { + "x": 11, + "y": 4, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_61": { + "x": 0, + "y": 5, + "asset": "floor", + "tile_type": "empty" + }, + "tile_62": { + "x": 1, + "y": 5, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_63": { + "x": 2, + "y": 5, + "asset": "floor", + "tile_type": "empty" + }, + "tile_64": { + "x": 3, + "y": 5, + "asset": "floor", + "tile_type": "empty" + }, + "tile_65": { + "x": 4, + "y": 5, + "asset": "target", + "tile_type": "target" + }, + "tile_66": { + "x": 5, + "y": 5, + "asset": "target", + "tile_type": "target" + }, + "tile_67": { + "x": 6, + "y": 5, + "asset": "target", + "tile_type": "target" + }, + "tile_68": { + "x": 7, + "y": 5, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_69": { + "x": 8, + "y": 5, + "asset": "floor", + "tile_type": "empty" + }, + "tile_70": { + "x": 9, + "y": 5, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_71": { + "x": 10, + "y": 5, + "asset": "floor", + "tile_type": "empty" + }, + "tile_72": { + "x": 11, + "y": 5, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_73": { + "x": 0, + "y": 6, + "asset": "floor", + "tile_type": "empty" + }, + "tile_74": { + "x": 1, + "y": 6, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_75": { + "x": 2, + "y": 6, + "asset": "floor", + "tile_type": "empty" + }, + "tile_76": { + "x": 3, + "y": 6, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_77": { + "x": 4, + "y": 6, + "asset": "target", + "tile_type": "target" + }, + "tile_78": { + "x": 5, + "y": 6, + "asset": "target", + "tile_type": "target" + }, + "tile_79": { + "x": 6, + "y": 6, + "asset": "target", + "tile_type": "target" + }, + "tile_80": { + "x": 7, + "y": 6, + "asset": "floor", + "tile_type": "empty" + }, + "tile_81": { + "x": 8, + "y": 6, + "asset": "floor", + "tile_type": "empty" + }, + "tile_82": { + "x": 9, + "y": 6, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_83": { + "x": 10, + "y": 6, + "asset": "floor", + "tile_type": "empty" + }, + "tile_84": { + "x": 11, + "y": 6, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_85": { + "x": 0, + "y": 7, + "asset": "floor", + "tile_type": "empty" + }, + "tile_86": { + "x": 1, + "y": 7, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_87": { + "x": 2, + "y": 7, + "asset": "floor", + "tile_type": "empty" + }, + "tile_88": { + "x": 3, + "y": 7, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_89": { + "x": 4, + "y": 7, + "asset": "target", + "tile_type": "target" + }, + "tile_90": { + "x": 5, + "y": 7, + "asset": "target", + "tile_type": "target" + }, + "tile_91": { + "x": 6, + "y": 7, + "asset": "target", + "tile_type": "target" + }, + "tile_92": { + "x": 7, + "y": 7, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_93": { + "x": 8, + "y": 7, + "asset": "floor", + "tile_type": "empty" + }, + "tile_94": { + "x": 9, + "y": 7, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_95": { + "x": 10, + "y": 7, + "asset": "floor", + "tile_type": "empty" + }, + "tile_96": { + "x": 11, + "y": 7, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_97": { + "x": 0, + "y": 8, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_98": { + "x": 1, + "y": 8, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_99": { + "x": 2, + "y": 8, + "asset": "floor", + "tile_type": "empty" + }, + "tile_100": { + "x": 3, + "y": 8, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_101": { + "x": 4, + "y": 8, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_102": { + "x": 5, + "y": 8, + "asset": "floor", + "tile_type": "empty" + }, + "tile_103": { + "x": 6, + "y": 8, + "asset": "floor", + "tile_type": "empty" + }, + "tile_104": { + "x": 7, + "y": 8, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_105": { + "x": 8, + "y": 8, + "asset": "floor", + "tile_type": "empty" + }, + "tile_106": { + "x": 9, + "y": 8, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_107": { + "x": 10, + "y": 8, + "asset": "floor", + "tile_type": "empty" + }, + "tile_108": { + "x": 11, + "y": 8, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_109": { + "x": 0, + "y": 9, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_110": { + "x": 1, + "y": 9, + "asset": "floor", + "tile_type": "empty" + }, + "tile_111": { + "x": 2, + "y": 9, + "asset": "floor", + "tile_type": "empty" + }, + "tile_112": { + "x": 3, + "y": 9, + "asset": "floor", + "tile_type": "empty" + }, + "tile_113": { + "x": 4, + "y": 9, + "asset": "floor", + "tile_type": "empty" + }, + "tile_114": { + "x": 5, + "y": 9, + "asset": "floor", + "tile_type": "empty" + }, + "tile_115": { + "x": 6, + "y": 9, + "asset": "floor", + "tile_type": "empty" + }, + "tile_116": { + "x": 7, + "y": 9, + "asset": "floor", + "tile_type": "empty" + }, + "tile_117": { + "x": 8, + "y": 9, + "asset": "floor", + "tile_type": "empty" + }, + "tile_118": { + "x": 9, + "y": 9, + "asset": "floor", + "tile_type": "empty" + }, + "tile_119": { + "x": 10, + "y": 9, + "asset": "floor", + "tile_type": "empty" + }, + "tile_120": { + "x": 11, + "y": 9, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_121": { + "x": 0, + "y": 10, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_122": { + "x": 1, + "y": 10, + "asset": "floor", + "tile_type": "empty" + }, + "tile_123": { + "x": 2, + "y": 10, + "asset": "floor", + "tile_type": "empty" + }, + "tile_124": { + "x": 3, + "y": 10, + "asset": "floor", + "tile_type": "empty" + }, + "tile_125": { + "x": 4, + "y": 10, + "asset": "floor", + "tile_type": "empty" + }, + "tile_126": { + "x": 5, + "y": 10, + "asset": "floor", + "tile_type": "empty" + }, + "tile_127": { + "x": 6, + "y": 10, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_128": { + "x": 7, + "y": 10, + "asset": "floor", + "tile_type": "empty" + }, + "tile_129": { + "x": 8, + "y": 10, + "asset": "floor", + "tile_type": "empty" + }, + "tile_130": { + "x": 9, + "y": 10, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_131": { + "x": 10, + "y": 10, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_132": { + "x": 11, + "y": 10, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_133": { + "x": 0, + "y": 11, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_134": { + "x": 1, + "y": 11, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_135": { + "x": 2, + "y": 11, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_136": { + "x": 3, + "y": 11, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_137": { + "x": 4, + "y": 11, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_138": { + "x": 5, + "y": 11, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_139": { + "x": 6, + "y": 11, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_140": { + "x": 7, + "y": 11, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_141": { + "x": 8, + "y": 11, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_142": { + "x": 9, + "y": 11, + "asset": "concrete", + "tile_type": "empty" + }, + "tile_143": { + "x": 10, + "y": 11, + "asset": "floor", + "tile_type": "empty" + }, + "tile_144": { + "x": 11, + "y": 11, + "asset": "floor", + "tile_type": "empty" + } + }, + "objects": { + "wall_1": { + "x": 0, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_2": { + "x": 1, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_3": { + "x": 2, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_4": { + "x": 3, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_5": { + "x": 4, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_6": { + "x": 5, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_7": { + "x": 6, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_8": { + "x": 7, + "y": 0, + "asset": "wall_tile", + "collidable": true + }, + "wall_9": { + "x": 0, + "y": 1, + "asset": "wall_tile", + "collidable": true + }, + "wall_10": { + "x": 3, + "y": 1, + "asset": "wall_tile", + "collidable": true + }, + "wall_11": { + "x": 7, + "y": 1, + "asset": "wall_tile", + "collidable": true + }, + "wall_12": { + "x": 8, + "y": 1, + "asset": "wall_tile", + "collidable": true + }, + "wall_13": { + "x": 9, + "y": 1, + "asset": "wall_tile", + "collidable": true + }, + "wall_14": { + "x": 10, + "y": 1, + "asset": "wall_tile", + "collidable": true + }, + "wall_15": { + "x": 0, + "y": 2, + "asset": "wall_tile", + "collidable": true + }, + "wall_16": { + "x": 3, + "y": 2, + "asset": "wall_tile", + "collidable": true + }, + "wall_17": { + "x": 10, + "y": 2, + "asset": "wall_tile", + "collidable": true + }, + "wall_18": { + "x": 0, + "y": 3, + "asset": "wall_tile", + "collidable": true + }, + "wall_19": { + "x": 10, + "y": 3, + "asset": "wall_tile", + "collidable": true + }, + "wall_20": { + "x": 0, + "y": 4, + "asset": "wall_tile", + "collidable": true + }, + "wall_21": { + "x": 1, + "y": 4, + "asset": "wall_tile", + "collidable": true + }, + "wall_22": { + "x": 3, + "y": 4, + "asset": "wall_tile", + "collidable": true + }, + "wall_23": { + "x": 4, + "y": 4, + "asset": "wall_tile", + "collidable": true + }, + "wall_24": { + "x": 5, + "y": 4, + "asset": "wall_tile", + "collidable": true + }, + "wall_25": { + "x": 6, + "y": 4, + "asset": "wall_tile", + "collidable": true + }, + "wall_26": { + "x": 7, + "y": 4, + "asset": "wall_tile", + "collidable": true + }, + "wall_27": { + "x": 9, + "y": 4, + "asset": "wall_tile", + "collidable": true + }, + "wall_28": { + "x": 10, + "y": 4, + "asset": "wall_tile", + "collidable": true + }, + "wall_29": { + "x": 11, + "y": 4, + "asset": "wall_tile", + "collidable": true + }, + "wall_30": { + "x": 1, + "y": 5, + "asset": "wall_tile", + "collidable": true + }, + "wall_31": { + "x": 7, + "y": 5, + "asset": "wall_tile", + "collidable": true + }, + "wall_32": { + "x": 9, + "y": 5, + "asset": "wall_tile", + "collidable": true + }, + "wall_33": { + "x": 11, + "y": 5, + "asset": "wall_tile", + "collidable": true + }, + "wall_34": { + "x": 1, + "y": 6, + "asset": "wall_tile", + "collidable": true + }, + "wall_35": { + "x": 3, + "y": 6, + "asset": "wall_tile", + "collidable": true + }, + "wall_36": { + "x": 9, + "y": 6, + "asset": "wall_tile", + "collidable": true + }, + "wall_37": { + "x": 11, + "y": 6, + "asset": "wall_tile", + "collidable": true + }, + "wall_38": { + "x": 1, + "y": 7, + "asset": "wall_tile", + "collidable": true + }, + "wall_39": { + "x": 3, + "y": 7, + "asset": "wall_tile", + "collidable": true + }, + "wall_40": { + "x": 7, + "y": 7, + "asset": "wall_tile", + "collidable": true + }, + "wall_41": { + "x": 9, + "y": 7, + "asset": "wall_tile", + "collidable": true + }, + "wall_42": { + "x": 11, + "y": 7, + "asset": "wall_tile", + "collidable": true + }, + "wall_43": { + "x": 0, + "y": 8, + "asset": "wall_tile", + "collidable": true + }, + "wall_44": { + "x": 1, + "y": 8, + "asset": "wall_tile", + "collidable": true + }, + "wall_45": { + "x": 3, + "y": 8, + "asset": "wall_tile", + "collidable": true + }, + "wall_46": { + "x": 4, + "y": 8, + "asset": "wall_tile", + "collidable": true + }, + "wall_47": { + "x": 7, + "y": 8, + "asset": "wall_tile", + "collidable": true + }, + "wall_48": { + "x": 9, + "y": 8, + "asset": "wall_tile", + "collidable": true + }, + "wall_49": { + "x": 11, + "y": 8, + "asset": "wall_tile", + "collidable": true + }, + "wall_50": { + "x": 0, + "y": 9, + "asset": "wall_tile", + "collidable": true + }, + "wall_51": { + "x": 11, + "y": 9, + "asset": "wall_tile", + "collidable": true + }, + "wall_52": { + "x": 0, + "y": 10, + "asset": "wall_tile", + "collidable": true + }, + "wall_53": { + "x": 6, + "y": 10, + "asset": "wall_tile", + "collidable": true + }, + "wall_54": { + "x": 9, + "y": 10, + "asset": "wall_tile", + "collidable": true + }, + "wall_55": { + "x": 10, + "y": 10, + "asset": "wall_tile", + "collidable": true + }, + "wall_56": { + "x": 11, + "y": 10, + "asset": "wall_tile", + "collidable": true + }, + "wall_57": { + "x": 0, + "y": 11, + "asset": "wall_tile", + "collidable": true + }, + "wall_58": { + "x": 1, + "y": 11, + "asset": "wall_tile", + "collidable": true + }, + "wall_59": { + "x": 2, + "y": 11, + "asset": "wall_tile", + "collidable": true + }, + "wall_60": { + "x": 3, + "y": 11, + "asset": "wall_tile", + "collidable": true + }, + "wall_61": { + "x": 4, + "y": 11, + "asset": "wall_tile", + "collidable": true + }, + "wall_62": { + "x": 5, + "y": 11, + "asset": "wall_tile", + "collidable": true + }, + "wall_63": { + "x": 6, + "y": 11, + "asset": "wall_tile", + "collidable": true + }, + "wall_64": { + "x": 7, + "y": 11, + "asset": "wall_tile", + "collidable": true + }, + "wall_65": { + "x": 8, + "y": 11, + "asset": "wall_tile", + "collidable": true + }, + "wall_66": { + "x": 9, + "y": 11, + "asset": "wall_tile", + "collidable": true + } + }, + "mobs": { + "box_1": { + "x_start": 2, + "y_start": 2, + "asset": "box", + "is_player": false + }, + "box_2": { + "x_start": 5, + "y_start": 2, + "asset": "box", + "is_player": false + }, + "box_3": { + "x_start": 6, + "y_start": 2, + "asset": "box", + "is_player": false + }, + "box_4": { + "x_start": 5, + "y_start": 3, + "asset": "box", + "is_player": false + }, + "box_5": { + "x_start": 8, + "y_start": 5, + "asset": "box", + "is_player": false + }, + "box_6": { + "x_start": 2, + "y_start": 7, + "asset": "box", + "is_player": false + }, + "player": { + "x_start": 10, + "y_start": 7, + "asset": "idle_se_0", + "is_player": true, + "behaviour": { + "type": "controlled" + } + }, + "box_7": { + "x_start": 8, + "y_start": 8, + "asset": "box", + "is_player": false + }, + "box_8": { + "x_start": 2, + "y_start": 9, + "asset": "box", + "is_player": false + }, + "box_9": { + "x_start": 4, + "y_start": 9, + "asset": "box", + "is_player": false + } + } +} diff --git a/game_levels/menu.json b/game_levels/menu.json new file mode 100644 index 0000000..e0cc1dd --- /dev/null +++ b/game_levels/menu.json @@ -0,0 +1,146 @@ +{ + "meta": { + "name": "menu", + "tile_size": 128, + "size": [6, 5] + }, + "tiles": { + "letter_1": { + "x": 0, + "y": 0, + "asset": "letter_C" + }, + "letter_2": { + "x": 1, + "y": 0, + "asset": "letter_H" + }, + "letter_3": { + "x": 2, + "y": 0, + "asset": "letter_O" + }, + "letter_4": { + "x": 3, + "y": 0, + "asset": "letter_O" + }, + "letter_5": { + "x": 4, + "y": 0, + "asset": "letter_S" + }, + "letter_6": { + "x": 5, + "y": 0, + "asset": "letter_E" + }, + "letter_7": { + "x": 0, + "y": 1, + "asset": "letter_L" + }, + "letter_8": { + "x": 1, + "y": 1, + "asset": "letter_E" + }, + "letter_9": { + "x": 2, + "y": 1, + "asset": "letter_V" + }, + "letter_10": { + "x": 3, + "y": 1, + "asset": "letter_E" + }, + "letter_11": { + "x": 4, + "y": 1, + "asset": "letter_L" + }, + "letter_12": { + "x": 0, + "y": 4, + "asset": "letter_1", + "tile_type": { + "link": 1 + } + }, + "letter_13": { + "x": 1, + "y": 4, + "asset": "letter_2", + "tile_type": { + "link": 2 + } + }, + "letter_14": { + "x": 2, + "y": 4, + "asset": "letter_3", + "tile_type": { + "link": 3 + } + }, + "letter_15": { + "x": 3, + "y": 4, + "asset": "letter_4", + "tile_type": { + "link": 4 + } + }, + "letter_16": { + "x": 4, + "y": 4, + "asset": "letter_5", + "tile_type": { + "link": 5 + } + } + }, + "objects": {}, + "mobs": { + "player": { + "x_start": 0, + "y_start": 2, + "asset": "running_se_0", + "is_player": true, + "behaviour": { + "type": "controlled" + } + }, + "box_1": { + "x_start": 0, + "y_start": 3, + "asset": "box", + "is_player": false + }, + "box_2": { + "x_start": 1, + "y_start": 3, + "asset": "box", + "is_player": false + }, + "box_3": { + "x_start": 2, + "y_start": 3, + "asset": "box", + "is_player": false + }, + "box_4": { + "x_start": 3, + "y_start": 3, + "asset": "box", + "is_player": false + }, + "box_5": { + "x_start": 4, + "y_start": 3, + "asset": "box", + "is_player": false + } + } +} diff --git a/input.json b/input.json deleted file mode 100644 index 0db790e..0000000 --- a/input.json +++ /dev/null @@ -1,3220 +0,0 @@ -{ - "meta": { - "name": "demo_map", - "tile_size": 16, - "size": [ - 25, - 25 - ] - }, - "tiles": { - "tile_1": { - "x": 0, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_2": { - "x": 1, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_3": { - "x": 2, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_4": { - "x": 3, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_5": { - "x": 4, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_6": { - "x": 5, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_7": { - "x": 6, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_8": { - "x": 7, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_9": { - "x": 8, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_10": { - "x": 9, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_11": { - "x": 10, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_12": { - "x": 11, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_13": { - "x": 12, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_14": { - "x": 13, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_15": { - "x": 14, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_16": { - "x": 15, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_17": { - "x": 16, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_18": { - "x": 17, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_19": { - "x": 18, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_20": { - "x": 19, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_21": { - "x": 20, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_22": { - "x": 21, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_23": { - "x": 22, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_24": { - "x": 23, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_25": { - "x": 24, - "y": 0, - "asset": "grass_tile_big_0_1" - }, - "tile_26": { - "x": 0, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_27": { - "x": 1, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_28": { - "x": 2, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_29": { - "x": 3, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_30": { - "x": 4, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_31": { - "x": 5, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_32": { - "x": 6, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_33": { - "x": 7, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_34": { - "x": 8, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_35": { - "x": 9, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_36": { - "x": 10, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_37": { - "x": 11, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_38": { - "x": 12, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_39": { - "x": 13, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_40": { - "x": 14, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_41": { - "x": 15, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_42": { - "x": 16, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_43": { - "x": 17, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_44": { - "x": 18, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_45": { - "x": 19, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_46": { - "x": 20, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_47": { - "x": 21, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_48": { - "x": 22, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_49": { - "x": 23, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_50": { - "x": 24, - "y": 1, - "asset": "grass_tile_big_0_1" - }, - "tile_51": { - "x": 0, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_52": { - "x": 1, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_53": { - "x": 2, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_54": { - "x": 3, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_55": { - "x": 4, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_56": { - "x": 5, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_57": { - "x": 6, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_58": { - "x": 7, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_59": { - "x": 8, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_60": { - "x": 9, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_61": { - "x": 10, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_62": { - "x": 11, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_63": { - "x": 12, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_64": { - "x": 13, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_65": { - "x": 14, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_66": { - "x": 15, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_67": { - "x": 16, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_68": { - "x": 17, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_69": { - "x": 18, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_70": { - "x": 19, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_71": { - "x": 20, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_72": { - "x": 21, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_73": { - "x": 22, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_74": { - "x": 23, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_75": { - "x": 24, - "y": 2, - "asset": "grass_tile_big_0_1" - }, - "tile_76": { - "x": 0, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_77": { - "x": 1, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_78": { - "x": 2, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_79": { - "x": 3, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_80": { - "x": 4, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_81": { - "x": 5, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_82": { - "x": 6, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_83": { - "x": 7, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_84": { - "x": 8, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_85": { - "x": 9, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_86": { - "x": 10, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_87": { - "x": 11, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_88": { - "x": 12, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_89": { - "x": 13, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_90": { - "x": 14, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_91": { - "x": 15, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_92": { - "x": 16, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_93": { - "x": 17, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_94": { - "x": 18, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_95": { - "x": 19, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_96": { - "x": 20, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_97": { - "x": 21, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_98": { - "x": 22, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_99": { - "x": 23, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_100": { - "x": 24, - "y": 3, - "asset": "grass_tile_big_0_1" - }, - "tile_101": { - "x": 0, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_102": { - "x": 1, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_103": { - "x": 2, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_104": { - "x": 3, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_105": { - "x": 4, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_106": { - "x": 5, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_107": { - "x": 6, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_108": { - "x": 7, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_109": { - "x": 8, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_110": { - "x": 9, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_111": { - "x": 10, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_112": { - "x": 11, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_113": { - "x": 12, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_114": { - "x": 13, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_115": { - "x": 14, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_116": { - "x": 15, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_117": { - "x": 16, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_118": { - "x": 17, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_119": { - "x": 18, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_120": { - "x": 19, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_121": { - "x": 20, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_122": { - "x": 21, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_123": { - "x": 22, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_124": { - "x": 23, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_125": { - "x": 24, - "y": 4, - "asset": "grass_tile_big_0_1" - }, - "tile_126": { - "x": 0, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_127": { - "x": 1, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_128": { - "x": 2, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_129": { - "x": 3, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_130": { - "x": 4, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_131": { - "x": 5, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_132": { - "x": 6, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_133": { - "x": 7, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_134": { - "x": 8, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_135": { - "x": 9, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_136": { - "x": 10, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_137": { - "x": 11, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_138": { - "x": 12, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_139": { - "x": 13, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_140": { - "x": 14, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_141": { - "x": 15, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_142": { - "x": 16, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_143": { - "x": 17, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_144": { - "x": 18, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_145": { - "x": 19, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_146": { - "x": 20, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_147": { - "x": 21, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_148": { - "x": 22, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_149": { - "x": 23, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_150": { - "x": 24, - "y": 5, - "asset": "grass_tile_big_0_1" - }, - "tile_151": { - "x": 0, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_152": { - "x": 1, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_153": { - "x": 2, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_154": { - "x": 3, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_155": { - "x": 4, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_156": { - "x": 5, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_157": { - "x": 6, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_158": { - "x": 7, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_159": { - "x": 8, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_160": { - "x": 9, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_161": { - "x": 10, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_162": { - "x": 11, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_163": { - "x": 12, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_164": { - "x": 13, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_165": { - "x": 14, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_166": { - "x": 15, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_167": { - "x": 16, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_168": { - "x": 17, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_169": { - "x": 18, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_170": { - "x": 19, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_171": { - "x": 20, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_172": { - "x": 21, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_173": { - "x": 22, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_174": { - "x": 23, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_175": { - "x": 24, - "y": 6, - "asset": "grass_tile_big_0_1" - }, - "tile_176": { - "x": 0, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_177": { - "x": 1, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_178": { - "x": 2, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_179": { - "x": 3, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_180": { - "x": 4, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_181": { - "x": 5, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_182": { - "x": 6, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_183": { - "x": 7, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_184": { - "x": 8, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_185": { - "x": 9, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_186": { - "x": 10, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_187": { - "x": 11, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_188": { - "x": 12, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_189": { - "x": 13, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_190": { - "x": 14, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_191": { - "x": 15, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_192": { - "x": 16, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_193": { - "x": 17, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_194": { - "x": 18, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_195": { - "x": 19, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_196": { - "x": 20, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_197": { - "x": 21, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_198": { - "x": 22, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_199": { - "x": 23, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_200": { - "x": 24, - "y": 7, - "asset": "grass_tile_big_0_1" - }, - "tile_201": { - "x": 0, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_202": { - "x": 1, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_203": { - "x": 2, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_204": { - "x": 3, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_205": { - "x": 4, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_206": { - "x": 5, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_207": { - "x": 6, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_208": { - "x": 7, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_209": { - "x": 8, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_210": { - "x": 9, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_211": { - "x": 10, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_212": { - "x": 11, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_213": { - "x": 12, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_214": { - "x": 13, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_215": { - "x": 14, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_216": { - "x": 15, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_217": { - "x": 16, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_218": { - "x": 17, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_219": { - "x": 18, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_220": { - "x": 19, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_221": { - "x": 20, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_222": { - "x": 21, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_223": { - "x": 22, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_224": { - "x": 23, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_225": { - "x": 24, - "y": 8, - "asset": "grass_tile_big_0_1" - }, - "tile_226": { - "x": 0, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_227": { - "x": 1, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_228": { - "x": 2, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_229": { - "x": 3, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_230": { - "x": 4, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_231": { - "x": 5, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_232": { - "x": 6, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_233": { - "x": 7, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_234": { - "x": 8, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_235": { - "x": 9, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_236": { - "x": 10, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_237": { - "x": 11, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_238": { - "x": 12, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_239": { - "x": 13, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_240": { - "x": 14, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_241": { - "x": 15, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_242": { - "x": 16, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_243": { - "x": 17, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_244": { - "x": 18, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_245": { - "x": 19, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_246": { - "x": 20, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_247": { - "x": 21, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_248": { - "x": 22, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_249": { - "x": 23, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_250": { - "x": 24, - "y": 9, - "asset": "grass_tile_big_0_1" - }, - "tile_251": { - "x": 0, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_252": { - "x": 1, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_253": { - "x": 2, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_254": { - "x": 3, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_255": { - "x": 4, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_256": { - "x": 5, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_257": { - "x": 6, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_258": { - "x": 7, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_259": { - "x": 8, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_260": { - "x": 9, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_261": { - "x": 10, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_262": { - "x": 11, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_263": { - "x": 12, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_264": { - "x": 13, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_265": { - "x": 14, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_266": { - "x": 15, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_267": { - "x": 16, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_268": { - "x": 17, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_269": { - "x": 18, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_270": { - "x": 19, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_271": { - "x": 20, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_272": { - "x": 21, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_273": { - "x": 22, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_274": { - "x": 23, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_275": { - "x": 24, - "y": 10, - "asset": "grass_tile_big_0_1" - }, - "tile_276": { - "x": 0, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_277": { - "x": 1, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_278": { - "x": 2, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_279": { - "x": 3, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_280": { - "x": 4, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_281": { - "x": 5, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_282": { - "x": 6, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_283": { - "x": 7, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_284": { - "x": 8, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_285": { - "x": 9, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_286": { - "x": 10, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_287": { - "x": 11, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_288": { - "x": 12, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_289": { - "x": 13, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_290": { - "x": 14, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_291": { - "x": 15, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_292": { - "x": 16, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_293": { - "x": 17, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_294": { - "x": 18, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_295": { - "x": 19, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_296": { - "x": 20, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_297": { - "x": 21, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_298": { - "x": 22, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_299": { - "x": 23, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_300": { - "x": 24, - "y": 11, - "asset": "grass_tile_big_0_1" - }, - "tile_301": { - "x": 0, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_302": { - "x": 1, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_303": { - "x": 2, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_304": { - "x": 3, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_305": { - "x": 4, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_306": { - "x": 5, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_307": { - "x": 6, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_308": { - "x": 7, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_309": { - "x": 8, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_310": { - "x": 9, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_311": { - "x": 10, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_312": { - "x": 11, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_313": { - "x": 12, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_314": { - "x": 13, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_315": { - "x": 14, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_316": { - "x": 15, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_317": { - "x": 16, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_318": { - "x": 17, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_319": { - "x": 18, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_320": { - "x": 19, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_321": { - "x": 20, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_322": { - "x": 21, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_323": { - "x": 22, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_324": { - "x": 23, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_325": { - "x": 24, - "y": 12, - "asset": "grass_tile_big_0_1" - }, - "tile_326": { - "x": 0, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_327": { - "x": 1, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_328": { - "x": 2, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_329": { - "x": 3, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_330": { - "x": 4, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_331": { - "x": 5, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_332": { - "x": 6, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_333": { - "x": 7, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_334": { - "x": 8, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_335": { - "x": 9, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_336": { - "x": 10, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_337": { - "x": 11, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_338": { - "x": 12, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_339": { - "x": 13, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_340": { - "x": 14, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_341": { - "x": 15, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_342": { - "x": 16, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_343": { - "x": 17, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_344": { - "x": 18, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_345": { - "x": 19, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_346": { - "x": 20, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_347": { - "x": 21, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_348": { - "x": 22, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_349": { - "x": 23, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_350": { - "x": 24, - "y": 13, - "asset": "grass_tile_big_0_1" - }, - "tile_351": { - "x": 0, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_352": { - "x": 1, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_353": { - "x": 2, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_354": { - "x": 3, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_355": { - "x": 4, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_356": { - "x": 5, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_357": { - "x": 6, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_358": { - "x": 7, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_359": { - "x": 8, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_360": { - "x": 9, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_361": { - "x": 10, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_362": { - "x": 11, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_363": { - "x": 12, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_364": { - "x": 13, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_365": { - "x": 14, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_366": { - "x": 15, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_367": { - "x": 16, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_368": { - "x": 17, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_369": { - "x": 18, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_370": { - "x": 19, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_371": { - "x": 20, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_372": { - "x": 21, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_373": { - "x": 22, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_374": { - "x": 23, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_375": { - "x": 24, - "y": 14, - "asset": "grass_tile_big_0_1" - }, - "tile_376": { - "x": 0, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_377": { - "x": 1, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_378": { - "x": 2, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_379": { - "x": 3, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_380": { - "x": 4, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_381": { - "x": 5, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_382": { - "x": 6, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_383": { - "x": 7, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_384": { - "x": 8, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_385": { - "x": 9, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_386": { - "x": 10, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_387": { - "x": 11, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_388": { - "x": 12, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_389": { - "x": 13, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_390": { - "x": 14, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_391": { - "x": 15, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_392": { - "x": 16, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_393": { - "x": 17, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_394": { - "x": 18, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_395": { - "x": 19, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_396": { - "x": 20, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_397": { - "x": 21, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_398": { - "x": 22, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_399": { - "x": 23, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_400": { - "x": 24, - "y": 15, - "asset": "grass_tile_big_0_1" - }, - "tile_401": { - "x": 0, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_402": { - "x": 1, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_403": { - "x": 2, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_404": { - "x": 3, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_405": { - "x": 4, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_406": { - "x": 5, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_407": { - "x": 6, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_408": { - "x": 7, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_409": { - "x": 8, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_410": { - "x": 9, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_411": { - "x": 10, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_412": { - "x": 11, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_413": { - "x": 12, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_414": { - "x": 13, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_415": { - "x": 14, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_416": { - "x": 15, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_417": { - "x": 16, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_418": { - "x": 17, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_419": { - "x": 18, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_420": { - "x": 19, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_421": { - "x": 20, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_422": { - "x": 21, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_423": { - "x": 22, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_424": { - "x": 23, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_425": { - "x": 24, - "y": 16, - "asset": "grass_tile_big_0_1" - }, - "tile_426": { - "x": 0, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_427": { - "x": 1, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_428": { - "x": 2, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_429": { - "x": 3, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_430": { - "x": 4, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_431": { - "x": 5, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_432": { - "x": 6, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_433": { - "x": 7, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_434": { - "x": 8, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_435": { - "x": 9, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_436": { - "x": 10, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_437": { - "x": 11, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_438": { - "x": 12, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_439": { - "x": 13, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_440": { - "x": 14, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_441": { - "x": 15, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_442": { - "x": 16, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_443": { - "x": 17, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_444": { - "x": 18, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_445": { - "x": 19, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_446": { - "x": 20, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_447": { - "x": 21, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_448": { - "x": 22, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_449": { - "x": 23, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_450": { - "x": 24, - "y": 17, - "asset": "grass_tile_big_0_1" - }, - "tile_451": { - "x": 0, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_452": { - "x": 1, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_453": { - "x": 2, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_454": { - "x": 3, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_455": { - "x": 4, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_456": { - "x": 5, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_457": { - "x": 6, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_458": { - "x": 7, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_459": { - "x": 8, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_460": { - "x": 9, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_461": { - "x": 10, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_462": { - "x": 11, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_463": { - "x": 12, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_464": { - "x": 13, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_465": { - "x": 14, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_466": { - "x": 15, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_467": { - "x": 16, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_468": { - "x": 17, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_469": { - "x": 18, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_470": { - "x": 19, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_471": { - "x": 20, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_472": { - "x": 21, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_473": { - "x": 22, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_474": { - "x": 23, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_475": { - "x": 24, - "y": 18, - "asset": "grass_tile_big_0_1" - }, - "tile_476": { - "x": 0, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_477": { - "x": 1, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_478": { - "x": 2, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_479": { - "x": 3, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_480": { - "x": 4, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_481": { - "x": 5, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_482": { - "x": 6, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_483": { - "x": 7, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_484": { - "x": 8, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_485": { - "x": 9, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_486": { - "x": 10, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_487": { - "x": 11, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_488": { - "x": 12, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_489": { - "x": 13, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_490": { - "x": 14, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_491": { - "x": 15, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_492": { - "x": 16, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_493": { - "x": 17, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_494": { - "x": 18, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_495": { - "x": 19, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_496": { - "x": 20, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_497": { - "x": 21, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_498": { - "x": 22, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_499": { - "x": 23, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_500": { - "x": 24, - "y": 19, - "asset": "grass_tile_big_0_1" - }, - "tile_501": { - "x": 0, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_502": { - "x": 1, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_503": { - "x": 2, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_504": { - "x": 3, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_505": { - "x": 4, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_506": { - "x": 5, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_507": { - "x": 6, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_508": { - "x": 7, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_509": { - "x": 8, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_510": { - "x": 9, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_511": { - "x": 10, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_512": { - "x": 11, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_513": { - "x": 12, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_514": { - "x": 13, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_515": { - "x": 14, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_516": { - "x": 15, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_517": { - "x": 16, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_518": { - "x": 17, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_519": { - "x": 18, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_520": { - "x": 19, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_521": { - "x": 20, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_522": { - "x": 21, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_523": { - "x": 22, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_524": { - "x": 23, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_525": { - "x": 24, - "y": 20, - "asset": "grass_tile_big_0_1" - }, - "tile_526": { - "x": 0, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_527": { - "x": 1, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_528": { - "x": 2, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_529": { - "x": 3, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_530": { - "x": 4, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_531": { - "x": 5, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_532": { - "x": 6, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_533": { - "x": 7, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_534": { - "x": 8, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_535": { - "x": 9, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_536": { - "x": 10, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_537": { - "x": 11, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_538": { - "x": 12, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_539": { - "x": 13, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_540": { - "x": 14, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_541": { - "x": 15, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_542": { - "x": 16, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_543": { - "x": 17, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_544": { - "x": 18, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_545": { - "x": 19, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_546": { - "x": 20, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_547": { - "x": 21, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_548": { - "x": 22, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_549": { - "x": 23, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_550": { - "x": 24, - "y": 21, - "asset": "grass_tile_big_0_1" - }, - "tile_551": { - "x": 0, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_552": { - "x": 1, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_553": { - "x": 2, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_554": { - "x": 3, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_555": { - "x": 4, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_556": { - "x": 5, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_557": { - "x": 6, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_558": { - "x": 7, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_559": { - "x": 8, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_560": { - "x": 9, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_561": { - "x": 10, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_562": { - "x": 11, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_563": { - "x": 12, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_564": { - "x": 13, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_565": { - "x": 14, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_566": { - "x": 15, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_567": { - "x": 16, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_568": { - "x": 17, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_569": { - "x": 18, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_570": { - "x": 19, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_571": { - "x": 20, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_572": { - "x": 21, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_573": { - "x": 22, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_574": { - "x": 23, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_575": { - "x": 24, - "y": 22, - "asset": "grass_tile_big_0_1" - }, - "tile_576": { - "x": 0, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_577": { - "x": 1, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_578": { - "x": 2, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_579": { - "x": 3, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_580": { - "x": 4, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_581": { - "x": 5, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_582": { - "x": 6, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_583": { - "x": 7, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_584": { - "x": 8, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_585": { - "x": 9, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_586": { - "x": 10, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_587": { - "x": 11, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_588": { - "x": 12, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_589": { - "x": 13, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_590": { - "x": 14, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_591": { - "x": 15, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_592": { - "x": 16, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_593": { - "x": 17, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_594": { - "x": 18, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_595": { - "x": 19, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_596": { - "x": 20, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_597": { - "x": 21, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_598": { - "x": 22, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_599": { - "x": 23, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_600": { - "x": 24, - "y": 23, - "asset": "grass_tile_big_0_1" - }, - "tile_601": { - "x": 0, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_602": { - "x": 1, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_603": { - "x": 2, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_604": { - "x": 3, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_605": { - "x": 4, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_606": { - "x": 5, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_607": { - "x": 6, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_608": { - "x": 7, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_609": { - "x": 8, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_610": { - "x": 9, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_611": { - "x": 10, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_612": { - "x": 11, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_613": { - "x": 12, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_614": { - "x": 13, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_615": { - "x": 14, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_616": { - "x": 15, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_617": { - "x": 16, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_618": { - "x": 17, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_619": { - "x": 18, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_620": { - "x": 19, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_621": { - "x": 20, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_622": { - "x": 21, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_623": { - "x": 22, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_624": { - "x": 23, - "y": 24, - "asset": "grass_tile_big_0_1" - }, - "tile_625": { - "x": 24, - "y": 24, - "asset": "grass_tile_big_0_1" - } - }, - "objects": { - "obj_1": { - "x": 2, - "y": 1, - "asset": "cactus_long_3_9" - }, - "obj_2": { - "x": 4, - "y": 14, - "asset": "fence_rising_11_10" - }, - "obj_3": { - "x": 8, - "y": 15, - "asset": "fence_falling_10_10" - } - }, - "mobs": { - "player": { - "x_start": 0, - "y_start": 0, - "asset": "knight_0_0", - "is_player": true, - "behaviour": { - "type": "controlled" - } - }, - "mob_4": { - "x_start": 420, - "y_start": 470, - "asset": "imp_20_0", - "is_player": false, - "behaviour": { - "type": "walker", - "direction": "left", - "speed": 0.5 - } - }, - "mob_5": { - "x_start": 493, - "y_start": 470, - "asset": "imp_20_0", - "is_player": false, - "behaviour": { - "type": "walker", - "direction": "left", - "speed": 0.5 - } - }, - "mob_6": { - "x_start": 540, - "y_start": 470, - "asset": "imp_20_0", - "is_player": false, - "behaviour": { - "type": "walker", - "direction": "left", - "speed": 0.5 - } - }, - "mob_1": { - "x_start": 440, - "y_start": 470, - "asset": "imp_20_0", - "is_player": false, - "behaviour": { - "type": "walker", - "direction": "left", - "speed": 0.5 - } - }, - "mob_2": { - "x_start": 400, - "y_start": 460, - "asset": "ghost_30_0", - "is_player": false, - "behaviour": { - "type": "walker", - "direction": "right", - "speed": 0.42 - } - } - } -} \ No newline at end of file diff --git a/play.sh b/play.sh new file mode 100755 index 0000000..7176401 --- /dev/null +++ b/play.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cargo run -p game --release diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index dc9efcb..0000000 --- a/src/main.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::thread; -use std::time::Duration; - -use crossbeam_channel::bounded; - -use crate::world::{get_visible_objects, make_step}; - -mod assets; -mod draw; -mod input; -mod render; -mod time; -mod world; - -/// Logical screen width in pixels. -const LOGIC_WIDTH: usize = 200; -/// Logical screen height in pixels. -const LOGIC_HEIGHT: usize = 200; -/// Tile size in pixels. -const TILE_SIZE: usize = 16; -/// Upscaling factor for display. -const UPSCALE: usize = 5; - -fn main() { - // parse atlases - let tiles_atlas = assets::Atlas::load("assets/tiles/atlas.json").unwrap(); - let entity_atlas = assets::Atlas::load("assets/entities/atlas.json").unwrap(); - - // parse game descr - let game = assets::GameMap::load("examples/input.json").unwrap(); - - // init draw - let input_state = Arc::new(input::InputState::new()); - let running = Arc::new(AtomicBool::new(true)); - let (tx_frame, rx_frame) = bounded::>(2); - - // framebuffer (`render <-> draw` connection) - let mut back_buffer: Vec = vec![0; LOGIC_WIDTH * LOGIC_HEIGHT]; - - { - let input_state = input_state.clone(); - let running = running.clone(); - - thread::spawn(move || { - draw::run_draw_thread( - rx_frame, - input_state, - running, - LOGIC_WIDTH, - LOGIC_HEIGHT, - UPSCALE, - ); - }); - } - - // init world_buf - let world_width = game.size[0] as usize * TILE_SIZE * 2; - let world_height = game.size[1] as usize * TILE_SIZE * 2; - - let world_buf: Vec = vec![195213255; world_width * world_height]; - // init render - let shadow_map: Vec = vec![0; world_width * world_height]; - let mut render = - render::Render::new(world_buf, world_height, world_width, entity_atlas, shadow_map); - - // init camera - let mut camera = world::Camera::new( - (world_width / 2) as f32, - (world_height / 2) as f32, - LOGIC_WIDTH as u16, - LOGIC_HEIGHT as u16, - ); - - // init time - let mut time = time::Time::new(); - - // init state of game - let mut state = world::State::new(&game); - - // prerender - render.init(&game, &tiles_atlas); - render.render_frame(&Vec::new(), &camera, &mut back_buffer, &time); - state.player.x = camera.center_x; - state.player.y = camera.center_y; - // game loop - while running.load(Ordering::Acquire) { - time.update(); - - // test gradient - // let r = ((time.total).sin() * 127.0 + 128.0) as u32; - // let g = ((time.total + 2.0).sin() * 127.0 + 128.0) as u32; - // let b = ((time.total + 4.0).sin() * 127.0 + 128.0) as u32; - // let color = (r << 16) | (g << 8) | b; - - // for px in back_buffer.iter_mut() { - // *px = color; - // } - - // process input - let input = input_state.read(); - if input.escape { - running.store(false, Ordering::Release); - } - - make_step(&mut state, &input); - - camera.center_x = state.player.x; - camera.center_y = state.player.y; - - let units_for_render = get_visible_objects(&state, &camera); - - if units_for_render.is_empty() { - continue; - } - - // frame render - render.render_frame(&units_for_render, &camera, &mut back_buffer, &time); - - // draw frame - if tx_frame.try_send(back_buffer.clone()).is_err() { - // idle - } - - // fps limit - thread::sleep(Duration::from_millis(16)); // ~60 FPS - } - - println!("Main loop exited"); -} diff --git a/src/mod.rs b/src/mod.rs deleted file mode 100644 index c51750f..0000000 --- a/src/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod assets { - pub mod atlas; - pub mod gamemap; -} -pub mod world { - pub mod camera; -} -pub mod time; -pub mod input; -pub mod draw; diff --git a/src/world/behaviour.rs b/src/world/behaviour.rs deleted file mode 100644 index fbe5c20..0000000 --- a/src/world/behaviour.rs +++ /dev/null @@ -1,215 +0,0 @@ -use crate::input::InputSnapshot; - -use super::State; - -/// Calculates the absolute value (length) of a 2D vector. -/// -/// # Arguments -/// * `vec` - A tuple representing a 2D vector (x, y) -/// -/// # Returns -/// * The length of the vector as f32 -fn abs_vector(vec: (f32, f32)) -> f32 { - let (dx, dy) = vec; - (dx * dx + dy * dy).sqrt() -} - -/// Normalizes a 2D vector to unit length. -/// -/// If the vector's length is not more than 0.1, returns a zero vector -/// to avoid division by very small numbers. -/// -/// # Arguments -/// * `vec` - A tuple representing a 2D vector (x, y) -/// -/// # Returns -/// * A normalized vector as tuple (x, y) or zero vector if length is small -fn normalize_vector(vec: (f32, f32)) -> (f32, f32) { - let (dx, dy) = vec; - let distance = abs_vector(vec); - if distance > 0.1 { - (dx / distance, dy / distance) - } else { - (0., 0.) - } -} - -/// Updates the game state for one simulation step. -/// -/// Handles player movement based on input and mob behaviour. -/// -/// # Arguments -/// * `curr_state` - Mutable reference to the current game state -/// * `input_state` - Reference to the current input snapshot -pub fn make_step(curr_state: &mut State, input_state: &InputSnapshot) { - let player_speed = 0.75; - let collision_distance = 10.0; - - let player = &mut curr_state.player; - - let mut player_move_vec = (0.0, 0.0); - player_move_vec.0 += if input_state.right { 1.0 } else { 0.0 }; - player_move_vec.0 += if input_state.left { -1.0 } else { 0.0 }; - player_move_vec.1 += if input_state.up { -1.0 } else { 0.0 }; - player_move_vec.1 += if input_state.down { 1.0 } else { 0.0 }; - - let norm = normalize_vector(player_move_vec); - player.x += norm.0 * player_speed; - player.y += norm.1 * player_speed; - - // make that mob go to player - for mob in &mut curr_state.mobs { - let vec_to = (player.x - mob.x, player.y - mob.y); - if abs_vector(vec_to) <= collision_distance { - let vec_from = (mob.x - player.x, mob.y - player.y); - let norm = normalize_vector(vec_from); - mob.x = player.x + norm.0 * collision_distance; - mob.y = player.y + norm.1 * collision_distance; - continue; - } - let norm = normalize_vector(vec_to); - // length of vec_move is |speed| - let mob_speed = (if mob.x_speed != 0. { mob.x_speed } else { mob.y_speed }).abs(); - let vec_move = (norm.0 * mob_speed, norm.1 * mob_speed); - - mob.x += vec_move.0; - mob.y += vec_move.1; - } -} - -#[cfg(test)] -mod tests { - use super::State; - use crate::assets::GameMap; - - use super::*; - - #[test] - fn test_abs_vector_zero() { - assert_eq!(abs_vector((0.0, 0.0)), 0.0); - } - - #[test] - fn test_abs_vector_nonzero() { - let len = abs_vector((3.0, 4.0)); - assert!((len - 5.0).abs() < 1e-5); - } - - #[test] - fn test_normalize_vector_basic() { - let n = normalize_vector((3.0, 4.0)); - assert!(((n.0 * n.0 + n.1 * n.1).sqrt() - 1.0).abs() < 1e-5); - } - - #[test] - fn test_normalize_vector_small_vector_returns_zero() { - let n = normalize_vector((0.01, 0.01)); - assert_eq!(n, (0.0, 0.0)); - } - - fn make_test_state() -> State { - let game_map = GameMap::load("input.json").expect("failed to load game map for tests"); - - let mut state = State::new(&game_map); - - state.player.x = 0.0; - state.player.y = 0.0; - state.player.x_speed = 0.0; - state.player.y_speed = 0.0; - - if state.mobs.is_empty() { - state.mobs.push(crate::world::Unit { - x: 100.0, - y: 0.0, - x_speed: -0.5, - y_speed: 0.0, - ..Default::default() - }); - } - - state - } - - #[test] - fn test_player_moves_right() { - let mut state = make_test_state(); - - let input = crate::input::InputSnapshot { - up: false, - down: false, - left: false, - right: true, - escape: false, - }; - - make_step(&mut state, &input); - - assert!((state.player.x - 0.75).abs() < 1e-5); - assert!((state.player.y - 0.0).abs() < 1e-5); - } - - #[test] - fn test_player_moves_up_left_diagonal() { - let mut state = make_test_state(); - - let input = crate::input::InputSnapshot { - up: true, - down: false, - left: true, - right: false, - escape: false, - }; - - make_step(&mut state, &input); - - let dx = state.player.x; - let dy = state.player.y; - let len = (dx * dx + dy * dy).sqrt(); - assert!((len - 0.75).abs() < 1e-5); - assert!(dx < 0.0 && dy < 0.0); - } - - #[test] - fn test_mob_moves_toward_player() { - let mut state = make_test_state(); - state.mobs[0].x = 50.0; - state.mobs[0].y = 0.0; - state.mobs[0].x_speed = -0.5; - state.mobs[0].y_speed = 0.0; - - let input = crate::input::InputSnapshot { - up: false, - down: false, - left: false, - right: false, - escape: false, - }; - - make_step(&mut state, &input); - - assert!(state.mobs[0].x < 50.0); - assert!(state.mobs[0].y.abs() < 1e-3); - } - - #[test] - fn test_collision_pushes_mob_back() { - let mut state = make_test_state(); - - state.mobs[0].x = 2.0; - state.mobs[0].y = 0.0; - - let input = crate::input::InputSnapshot { - up: false, - down: false, - left: false, - right: false, - escape: false, - }; - - make_step(&mut state, &input); - - let vec_from = (state.mobs[0].x - state.player.x, state.mobs[0].y - state.player.y); - let dist = (vec_from.0 * vec_from.0 + vec_from.1 * vec_from.1).sqrt(); - assert!((dist - 10.0).abs() < 1e-3); - } -} diff --git a/src/world/initiator.rs b/src/world/initiator.rs deleted file mode 100644 index 4fb0274..0000000 --- a/src/world/initiator.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::world::{Camera, Unit}; - -use super::State; - -/// Returns a list of game objects that are currently visible within the camera's view. -/// -/// This function filters all game units (player and mobs) to only include those -/// that fall within the camera's current field of view. The visibility is determined -/// by the camera's position and viewport dimensions. -/// -/// # Arguments -/// -/// * `cur_state` - The current game state containing all units -/// * `camera` - The camera that defines the visible area of the game world -/// -/// # Returns -/// -/// A vector containing all [`Unit`] objects that are currently visible to the camera. -/// The player unit is always included first, followed by any visible mobs. -pub fn get_visible_objects(cur_state: &State, camera: &Camera) -> Vec { - let mut units = Vec::new(); - units.push(cur_state.player.clone()); - units.extend(cur_state.mobs.clone()); - - units.into_iter().filter(|mob| camera.is_visible(mob.x, mob.y)).collect() -} - -#[cfg(test)] -mod visible_objects_tests { - use super::*; - - #[derive(Clone)] - struct DummyUnit { - x: f32, - y: f32, - x_speed: f32, - y_speed: f32, - } - - #[derive(Clone)] - struct DummyState { - player: DummyUnit, - mobs: Vec, - } - - impl DummyState { - fn to_real_state(&self) -> State { - State { - player: Unit { - x: self.player.x, - y: self.player.y, - x_speed: self.player.x_speed, - y_speed: self.player.y_speed, - }, - mobs: self - .mobs - .iter() - .map(|m| Unit { x: m.x, y: m.y, x_speed: m.x_speed, y_speed: m.y_speed }) - .collect(), - } - } - } - - #[test] - fn test_get_visible_objects_player_included() { - let dummy_state = DummyState { - player: DummyUnit { x: 0.0, y: 0.0, x_speed: 0.0, y_speed: 0.0 }, - mobs: vec![], - }; - let state = dummy_state.to_real_state(); - - let camera = Camera::new(0.0, 0.0, 800, 600); - let visible = get_visible_objects(&state, &camera); - - assert_eq!(visible.len(), 1); - assert_eq!(visible[0].x, state.player.x); - assert_eq!(visible[0].y, state.player.y); - } - - #[test] - fn test_get_visible_objects_mobs_visible() { - let dummy_state = DummyState { - player: DummyUnit { x: 0.0, y: 0.0, x_speed: 0.0, y_speed: 0.0 }, - mobs: vec![ - DummyUnit { x: 10.0, y: 10.0, x_speed: 0.0, y_speed: 0.0 }, - DummyUnit { x: 1000.0, y: 1000.0, x_speed: 0.0, y_speed: 0.0 }, - ], - }; - let state = dummy_state.to_real_state(); - let camera = Camera::new(0.0, 0.0, 50, 50); - - let visible = get_visible_objects(&state, &camera); - assert_eq!(visible.len(), 2); - assert_eq!(visible[1].x, 10.0); - assert_eq!(visible[1].y, 10.0); - } - - #[test] - fn test_get_visible_objects_mobs_outside_not_included() { - let dummy_state = DummyState { - player: DummyUnit { x: 0.0, y: 0.0, x_speed: 0.0, y_speed: 0.0 }, - mobs: vec![DummyUnit { x: 100.0, y: 100.0, x_speed: 0.0, y_speed: 0.0 }], - }; - let state = dummy_state.to_real_state(); - let camera = Camera::new(0.0, 0.0, 50, 50); - - let visible = get_visible_objects(&state, &camera); - assert_eq!(visible.len(), 1); - assert_eq!(visible[0].x, state.player.x); - } - - #[test] - fn test_get_visible_objects_multiple_mobs() { - let dummy_state = DummyState { - player: DummyUnit { x: 0.0, y: 0.0, x_speed: 0.0, y_speed: 0.0 }, - mobs: vec![ - DummyUnit { x: 5.0, y: 5.0, x_speed: 0.0, y_speed: 0.0 }, - DummyUnit { x: 20.0, y: 20.0, x_speed: 0.0, y_speed: 0.0 }, - DummyUnit { x: 100.0, y: 100.0, x_speed: 0.0, y_speed: 0.0 }, - ], - }; - let state = dummy_state.to_real_state(); - let camera = Camera::new(0.0, 0.0, 50, 50); - - let visible = get_visible_objects(&state, &camera); - assert_eq!(visible.len(), 3); - let positions: Vec<_> = visible.iter().map(|u| (u.x, u.y)).collect(); - assert!(positions.contains(&(0.0, 0.0))); - assert!(positions.contains(&(5.0, 5.0))); - assert!(positions.contains(&(20.0, 20.0))); - } -} diff --git a/src/world/mod.rs b/src/world/mod.rs deleted file mode 100644 index 9e4a47f..0000000 --- a/src/world/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod behaviour; -mod camera; -mod initiator; -mod state; - -pub use self::state::*; -pub use behaviour::make_step; -pub use camera::Camera; -pub use initiator::get_visible_objects; diff --git a/src/world/state.rs b/src/world/state.rs deleted file mode 100644 index 62d9b7e..0000000 --- a/src/world/state.rs +++ /dev/null @@ -1,293 +0,0 @@ -use crate::assets::GameMap; - -/// Represents the current game state containing all units. -/// -/// The `State` struct manages the player unit and all mob units in the game, -/// tracking their positions and movement speeds for game simulation. -#[derive(Debug, Default)] -pub struct State { - /// The player-controlled unit - pub player: Unit, - /// Collection of all non-player mobile units - pub mobs: Vec, -} - -/// Represents a unit entity in the game world with position and movement capabilities. -/// -/// Units can be either player-controlled or game-controlled mobs. Each unit has -/// a position in 2D space and speed components for movement simulation. -#[derive(Debug, Clone, Default)] -pub struct Unit { - /// X-coordinate position in the game world - pub x: f32, - /// Y-coordinate position in the game world - pub y: f32, - /// Horizontal movement speed - pub x_speed: f32, - /// Vertical movement speed - pub y_speed: f32, -} - -impl Unit { - /// Creates a new `Unit` with the specified position and movement parameters. - /// - /// # Arguments - /// - /// * `x` - Initial X-coordinate position - /// * `y` - Initial Y-coordinate position - /// * `x_speed` - Initial horizontal movement speed - /// * `y_speed` - Initial vertical movement speed - /// - /// # Returns - /// - /// A new `Unit` instance with the specified properties. - #[allow(dead_code)] - pub fn new(x: f32, y: f32, x_speed: f32, y_speed: f32) -> Self { - Self { x, y, x_speed, y_speed } - } -} - -impl State { - /// Creates a new `State` by getting unit data from a `GameMap`. - /// - /// This constructor processes all units defined in the game map, identifying - /// the player unit and initializing all mob units with their starting positions - /// and movement behaviors. - /// - /// # Arguments - /// - /// * `game_map` - Reference to the `GameMap` containing unit definitions - /// - /// # Returns - /// - /// A new `State` instance with: - /// - Player unit initialized from the mob marked as `is_player` - /// - All other mobs initialized with their respective behaviors and speeds - /// - /// # Behavior - /// - /// - The player unit is given fixed movement speeds (10.0 in both directions) - /// - Mob units derive their movement from behavior definitions: - /// - "right": positive x_speed - /// - "left": negative x_speed - /// - "up": negative y_speed - /// - "down": positive y_speed - /// - Mobs without behavior definitions get zero movement speed - /// - Mobs without specified speed default to 0.0 - pub fn new(game_map: &GameMap) -> Self { - let mut player: Option = None; - let mut mobs: Vec = Vec::new(); - - for mob in game_map.iter_mobs() { - if mob.is_player { - player = Some(Unit { - x: mob.x_start as f32, - y: mob.y_start as f32, - x_speed: 10., - y_speed: 10., - }); - continue; - } - - if let Some(beh) = &mob.behaviour { - let mob_direction = beh.direction.as_deref().unwrap_or("none"); - let mob_speed = beh.speed.unwrap_or(0.0); - - mobs.push(Unit { - x: mob.x_start as f32, - y: mob.y_start as f32, - x_speed: match mob_direction { - "right" => mob_speed, - "left" => -mob_speed, - _ => 0.0, - }, - y_speed: match mob_direction { - "up" => -mob_speed, - "down" => mob_speed, - _ => 0.0, - }, - }); - } else { - mobs.push(Unit { - x: mob.x_start as f32, - y: mob.y_start as f32, - x_speed: 0.0, - y_speed: 0.0, - }); - } - } - - Self { player: player.unwrap(), mobs } - } -} - -#[cfg(test)] -mod state_tests { - use crate::assets::{Behaviour, BehaviourType, GameMap, Mob}; - use crate::world::State; - - fn make_test_map() -> GameMap { - let mut mobs = std::collections::HashMap::new(); - - mobs.insert( - "player".to_string(), - Mob { - name: "player".to_string(), - x_start: 0, - y_start: 0, - asset: "knight".to_string(), - is_player: true, - behaviour: None, - }, - ); - - mobs.insert( - "mob_right".to_string(), - Mob { - name: "mob_right".to_string(), - x_start: 10, - y_start: 0, - asset: "imp".to_string(), - is_player: false, - behaviour: Some(Behaviour { - behaviour_type: BehaviourType::Walker, - direction: Some("right".to_string()), - speed: Some(1.0), - }), - }, - ); - - mobs.insert( - "mob_up".to_string(), - Mob { - name: "mob_up".to_string(), - x_start: 0, - y_start: 10, - asset: "ghost".to_string(), - is_player: false, - behaviour: Some(Behaviour { - behaviour_type: BehaviourType::Walker, - direction: Some("up".to_string()), - speed: Some(0.5), - }), - }, - ); - - GameMap { - name: "test_map".to_string(), - tile_size: 16, - size: [5, 5], - mobs, - objects: std::collections::HashMap::new(), - tiles: std::collections::HashMap::new(), - } - } - - #[test] - fn test_state_new_creates_player_and_mobs() { - let map = make_test_map(); - let state = State::new(&map); - - assert_eq!(state.player.x, 0.0); - assert_eq!(state.player.y, 0.0); - assert_eq!(state.player.x_speed, 10.0); - assert_eq!(state.player.y_speed, 10.0); - - assert_eq!(state.mobs.len(), 2); - - let mob_right = state.mobs.iter().find(|m| m.x_speed > 0.0).unwrap(); - assert_eq!(mob_right.x_speed, 1.0); - assert_eq!(mob_right.y_speed, 0.0); - assert_eq!(mob_right.x, 10.0); - assert_eq!(mob_right.y, 0.0); - - let mob_up = state.mobs.iter().find(|m| m.y_speed < 0.0).unwrap(); - assert_eq!(mob_up.x_speed, 0.0); - assert_eq!(mob_up.y_speed, -0.5); - assert_eq!(mob_up.x, 0.0); - assert_eq!(mob_up.y, 10.0); - } - - #[test] - fn test_state_with_no_mobs_other_than_player() { - let mut map = make_test_map(); - map.mobs.retain(|_, mob| mob.is_player); - let state = State::new(&map); - - assert_eq!(state.player.x, 0.0); - assert_eq!(state.player.y, 0.0); - assert!(state.mobs.is_empty()); - } - - #[test] - fn test_mob_with_unknown_or_none_behaviour_defaults_to_zero_speed() { - let mut mobs = std::collections::HashMap::new(); - mobs.insert( - "player".to_string(), - Mob { - name: "player".to_string(), - x_start: 0, - y_start: 0, - asset: "knight".to_string(), - is_player: true, - behaviour: None, - }, - ); - mobs.insert( - "mob_none".to_string(), - Mob { - name: "mob_none".to_string(), - x_start: 5, - y_start: 5, - asset: "dummy".to_string(), - is_player: false, - behaviour: None, - }, - ); - mobs.insert( - "mob_unknown".to_string(), - Mob { - name: "mob_unknown".to_string(), - x_start: 10, - y_start: 10, - asset: "dummy".to_string(), - is_player: false, - behaviour: Some(Behaviour { - behaviour_type: BehaviourType::Unknown, - direction: Some("left".to_string()), - speed: Some(2.0), - }), - }, - ); - - let map = GameMap { - name: "test_map".to_string(), - tile_size: 16, - size: [5, 5], - mobs, - objects: std::collections::HashMap::new(), - tiles: std::collections::HashMap::new(), - }; - - let state = State::new(&map); - assert_eq!(state.mobs.len(), 2); - - let mob_none = state.mobs.iter().find(|m| m.x == 5.0).unwrap(); - assert_eq!(mob_none.x_speed, 0.0); - assert_eq!(mob_none.y_speed, 0.0); - - let mob_unknown = state.mobs.iter().find(|m| m.x == 10.0).unwrap(); - assert_eq!(mob_unknown.x_speed, -2.0); - assert_eq!(mob_unknown.y_speed, 0.0); - } - - #[test] - fn test_player_position_does_not_change_from_map() { - let map = make_test_map(); - let state = State::new(&map); - - let player_map = map.get_mob("player").unwrap(); - assert_eq!(state.player.x, player_map.x_start as f32); - assert_eq!(state.player.y, player_map.y_start as f32); - } -}