add the dungeon keeper's lament

This commit is contained in:
Corwin 2023-07-19 17:41:17 +01:00
parent a7f9fdf011
commit 0fac43746f
No known key found for this signature in database
65 changed files with 5074 additions and 0 deletions

View file

@ -0,0 +1,14 @@
[unstable]
build-std = ["core", "alloc"]
build-std-features = ["compiler-builtins-mem"]
[build]
target = "thumbv4t-none-eabi"
[target.thumbv4t-none-eabi]
rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"]
runner = "mgba-qt"
[target.armv4t-none-eabi]
rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"]
runner = "mgba-qt"

View file

@ -0,0 +1,479 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "agb"
version = "0.16.0"
dependencies = [
"agb_fixnum",
"agb_hashmap",
"agb_image_converter",
"agb_macros",
"agb_sound_converter",
"bare-metal",
"bilge",
"bitflags 2.3.3",
"rustc-hash",
]
[[package]]
name = "agb_fixnum"
version = "0.16.0"
dependencies = [
"agb_macros",
]
[[package]]
name = "agb_hashmap"
version = "0.16.0"
dependencies = [
"rustc-hash",
]
[[package]]
name = "agb_image_converter"
version = "0.16.0"
dependencies = [
"asefile",
"fontdue",
"image",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "agb_macros"
version = "0.16.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "agb_sound_converter"
version = "0.16.0"
dependencies = [
"hound",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ahash"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
]
[[package]]
name = "arbitrary-int"
version = "1.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe8e2a586ecd6eb29477a0c25b19742acca4fa5e39c92e127656616810c20579"
[[package]]
name = "asefile"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6277c3125ad5045ff11474a50dd43b31baffdd32c17e580137f176b8025fde71"
dependencies = [
"bitflags 1.3.2",
"byteorder",
"flate2",
"image",
"log",
"nohash",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bare-metal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603"
[[package]]
name = "base64"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "bilge"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79ce1664066c385d913b974a9f59746a78b7e550e9ffb7984eda322ab8bf5e08"
dependencies = [
"arbitrary-int",
"bilge-impl",
]
[[package]]
name = "bilge-impl"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60fd0a3dc3001795b19260220070e6e8cbb3d8013351f382cc38533a41ca6e27"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
[[package]]
name = "bytemuck"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "deflate"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
dependencies = [
"adler32",
"byteorder",
]
[[package]]
name = "flate2"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
dependencies = [
"crc32fast",
"miniz_oxide 0.7.1",
]
[[package]]
name = "fontdue"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc"
dependencies = [
"hashbrown",
"ttf-parser",
]
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]]
name = "hound"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1"
[[package]]
name = "image"
version = "0.23.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"num-iter",
"num-rational",
"num-traits",
"png",
]
[[package]]
name = "libflate"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18"
dependencies = [
"adler32",
"crc32fast",
"libflate_lz77",
]
[[package]]
name = "libflate_lz77"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf"
dependencies = [
"rle-decode-fast",
]
[[package]]
name = "log"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "miniz_oxide"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
"adler32",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "nohash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0f889fb66f7acdf83442c35775764b51fed3c606ab9cee51500dbde2cf528ca"
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "png"
version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"deflate",
"miniz_oxide 0.3.7",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rle-decode-fast"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "slotmap"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
dependencies = [
"version_check",
]
[[package]]
name = "syn"
version = "2.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "the-dungeon-keepers-lament"
version = "0.1.0"
dependencies = [
"agb",
"proc-macro2",
"quote",
"slotmap",
"tiled",
]
[[package]]
name = "tiled"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "181e2402be287d74e951a8e81c8798d0737cc94b1a089237bb3b838be76c6b5b"
dependencies = [
"base64",
"libflate",
"xml-rs",
]
[[package]]
name = "ttf-parser"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
[[package]]
name = "unicode-ident"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "xml-rs"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a56c84a8ccd4258aed21c92f70c0f6dea75356b6892ae27c24139da456f9336"

View file

@ -0,0 +1,25 @@
[package]
name = "the-dungeon-keepers-lament"
version = "0.1.0"
authors = [""]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
agb = { version = "0.16.0", path = "../../agb" }
slotmap = { version = "1", default-features = false }
[profile.dev]
opt-level = 3
debug = true
[profile.release]
opt-level = 3
lto = "fat"
debug = true
[build-dependencies]
tiled = { version = "0.11", default-features = false }
quote = "1"
proc-macro2 = "1"

View file

@ -0,0 +1,350 @@
use quote::{quote, TokenStreamExt};
use std::{
env,
fs::File,
io::{BufWriter, Write},
str::FromStr,
};
use proc_macro2::TokenStream;
const LEVEL_NAMES: &[&str] = &[
"level1",
"level2",
"level3",
"level4",
"level5",
"level6",
"level_switch",
"level_spikes",
"level_spikes2",
"squid_force_button",
"level_squid_intro",
"level_squid2",
"level_squid1",
"level_squid_item",
"level_squid_button",
"level_squid_drop",
"level_spikes3",
"level_around",
"level_squidprogramming",
];
fn main() {
let out_dir = env::var("OUT_DIR").expect("OUT_DIR environment variable must be specified");
let mut tile_loader = tiled::Loader::new();
let ui_map = load_tmx(&mut tile_loader, "maps/UI.tmx");
let ui_tiles = export_tiles(&ui_map, quote!(ui));
let levels = LEVEL_NAMES
.iter()
.map(|level| load_level(&mut tile_loader, &format!("maps/levels/{level}.tmx")))
.collect::<Vec<_>>();
let levels_tiles = levels.iter().map(|level| &level.0);
let levels_data = levels.iter().map(|level| &level.1);
let tilemaps_output = quote! {
use agb::display::tiled::TileSetting;
pub const UI_BACKGROUND_MAP: &[TileSetting] = #ui_tiles;
pub const LEVELS_MAP: &[&[TileSetting]] = &[#(#levels_tiles),*];
};
let levels_output = quote! {
pub const LEVELS: &[Level] = &[#(#levels_data),*];
};
{
let tilemaps_output_file = File::create(format!("{out_dir}/tilemaps.rs"))
.expect("Failed to open tilemaps.rs for writing");
let mut tilemaps_writer = BufWriter::new(tilemaps_output_file);
write!(&mut tilemaps_writer, "{tilemaps_output}").unwrap();
}
{
let levels_output_file = File::create(format!("{out_dir}/levels.rs"))
.expect("Failed to open levels.rs for writing");
let mut levels_output_writer = BufWriter::new(levels_output_file);
write!(&mut levels_output_writer, "{levels_output}").unwrap();
}
}
fn load_level(loader: &mut tiled::Loader, filename: &str) -> (TokenStream, Level) {
let level_map = load_tmx(loader, filename);
let tiles = export_tiles(&level_map, quote!(level));
let data = export_level(&level_map);
(tiles, data)
}
fn load_tmx(loader: &mut tiled::Loader, filename: &str) -> tiled::Map {
println!("cargo:rerun-if-changed={filename}");
loader.load_tmx_map(filename).expect("failed to load map")
}
enum Entity {
Sword,
Slime,
Hero,
Stairs,
Door,
Key,
Switch,
SwitchPressed,
SwitchedOpenDoor,
SwitchedClosedDoor,
SpikesUp,
SpikesDown,
SquidUp,
SquidDown,
}
impl FromStr for Entity {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
use Entity::*;
Ok(match s {
"SWORD" => Sword,
"SLIME" => Slime,
"HERO" => Hero,
"STAIRS" => Stairs,
"DOOR" => Door,
"KEY" => Key,
"SWITCH" => Switch,
"SWITCH_PRESSED" => SwitchPressed,
"DOOR_SWITCHED" => SwitchedClosedDoor,
"DOOR_SWITCHED_OPEN" => SwitchedOpenDoor,
"SPIKES" => SpikesUp,
"SPIKES_DOWN" => SpikesDown,
"SQUID_UP" => SquidUp,
"SQUID_DOWN" => SquidDown,
_ => return Err(()),
})
}
}
impl quote::ToTokens for Entity {
fn to_tokens(&self, tokens: &mut TokenStream) {
use Entity::*;
tokens.append_all(match self {
Sword => quote!(Item::Sword),
Slime => quote!(Item::Slime),
Hero => quote!(Item::Hero),
Stairs => quote!(Item::Stairs),
Door => quote!(Item::Door),
Key => quote!(Item::Key),
Switch => quote!(Item::Switch),
SwitchPressed => quote!(Item::SwitchPressed),
SwitchedOpenDoor => quote!(Item::SwitchedOpenDoor),
SwitchedClosedDoor => quote!(Item::SwitchedClosedDoor),
SpikesUp => quote!(Item::SpikesUp),
SpikesDown => quote!(Item::SpikesDown),
SquidUp => quote!(Item::SquidUp),
SquidDown => quote!(Item::SquidDown),
})
}
}
enum Direction {
Up,
Down,
Left,
Right,
}
impl TryFrom<char> for Direction {
type Error = ();
fn try_from(c: char) -> Result<Self, Self::Error> {
use Direction::*;
Ok(match c {
'U' => Up,
'D' => Down,
'L' => Left,
'R' => Right,
_ => return Err(()),
})
}
}
impl quote::ToTokens for Direction {
fn to_tokens(&self, tokens: &mut TokenStream) {
use Direction::*;
tokens.append_all(match self {
Up => quote!(Direction::Up),
Down => quote!(Direction::Down),
Left => quote!(Direction::Left),
Right => quote!(Direction::Right),
});
}
}
struct EntityWithPosition(Entity, (i32, i32));
impl quote::ToTokens for EntityWithPosition {
fn to_tokens(&self, tokens: &mut TokenStream) {
let pos_x = self.1 .0;
let pos_y = self.1 .1;
let location = quote!(Vector2D::new(#pos_x, #pos_y));
let item = &self.0;
tokens.append_all(quote!(Entity(#item, #location)))
}
}
struct Level {
starting_items: Vec<Entity>,
fixed_positions: Vec<EntityWithPosition>,
directions: Vec<Direction>,
wall_bitmap: Vec<u8>,
name: String,
}
impl quote::ToTokens for Level {
fn to_tokens(&self, tokens: &mut TokenStream) {
let wall_bitmap = &self.wall_bitmap;
let fixed_positions = &self.fixed_positions;
let directions = &self.directions;
let starting_items = &self.starting_items;
let name = &self.name;
tokens.append_all(quote! {
Level::new(
Map::new(11, 10, &[#(#wall_bitmap),*]),
&[#(#fixed_positions),*],
&[#(#directions),*],
&[#(#starting_items),*],
#name,
)
})
}
}
fn export_level(map: &tiled::Map) -> Level {
let objects = map.get_layer(1).unwrap().as_object_layer().unwrap();
let fixed_positions = objects.objects().map(|obj| {
let entity: Entity = obj
.name
.parse()
.unwrap_or_else(|_| panic!("unknown object type {}", obj.name));
let x = (obj.x / 16.0) as i32;
let y = (obj.y / 16.0) as i32;
EntityWithPosition(entity, (x, y))
});
let Some(tiled::PropertyValue::StringValue(starting_items)) = map.properties.get("ITEMS")
else {
panic!("Starting items must be a string")
};
let Some(tiled::PropertyValue::StringValue(level_name)) = map.properties.get("NAME") else {
panic!("Level name must be a string")
};
let starting_items = starting_items.split(',').map(|starting_item| {
starting_item
.parse()
.unwrap_or_else(|_| panic!("unknown object type {}", starting_item))
});
let Some(tiled::PropertyValue::StringValue(directions)) = map.properties.get("DIRECTIONS")
else {
panic!("Starting items must be a string")
};
let directions = directions.chars().map(|starting_item| {
starting_item
.try_into()
.unwrap_or_else(|_| panic!("unknown object type {}", starting_item))
});
let Some(tiled::TileLayer::Finite(tiles)) = map.get_layer(0).unwrap().as_tile_layer() else {
panic!("Not a finite layer")
};
let are_walls = (0..10 * 11).map(|id| {
let tile_x = id % 11;
let tile_y = id / 11;
let is_wall = tiles
.get_tile(tile_x * 2, tile_y * 2)
.map(|tile| {
let tileset = tile.get_tileset();
let tile_data = &tileset.get_tile(tile.id()).unwrap();
tile_data
.user_type
.as_ref()
.map(|user_type| user_type == "WALL")
.unwrap_or(false)
})
.unwrap_or(true);
is_wall
});
Level {
starting_items: starting_items.collect(),
fixed_positions: fixed_positions.collect(),
directions: directions.collect(),
wall_bitmap: bool_to_bit(&are_walls.collect::<Vec<_>>()),
name: level_name.clone(),
}
}
fn export_tiles(map: &tiled::Map, background: TokenStream) -> TokenStream {
let map_tiles = map.get_layer(0).unwrap().as_tile_layer().unwrap();
let width = map_tiles.width().unwrap();
let height = map_tiles.height().unwrap();
let map_tiles = (0..(height * width)).map(|pos| {
let x = pos % width;
let y = pos / width;
let tile = map_tiles.get_tile(x as i32, y as i32);
match tile {
Some(tile) => {
let tile_id = tile.id() as u16;
let vflip = tile.flip_h;
let hflip = tile.flip_v;
let palette_id =
quote! { backgrounds::#background.palette_assignments[#tile_id as usize] };
quote! { TileSetting::new(#tile_id, #vflip, #hflip, #palette_id) }
}
None => {
quote! { TileSetting::new(1023, false, false, 0) }
}
}
});
quote! {&[#(#map_tiles),*]}
}
fn bool_to_bit(bools: &[bool]) -> Vec<u8> {
bools
.chunks(8)
.map(|x| {
x.iter()
.enumerate()
.fold(0u8, |bits, (idx, &bit)| bits | ((bit as u8) << idx))
})
.collect()
}
#[test]
fn check_bool_to_bit() {
let bools = [true, false, false, false, true, true, true, true];
assert_eq!(bool_to_bit(&bools), [0b11110001]);
}

Binary file not shown.

View file

@ -0,0 +1,115 @@
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(__start)
EXTERN(__RUST_INTERRUPT_HANDLER)
EXTERN(__agbabi_memset)
EXTERN(__agbabi_memcpy)
MEMORY {
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K
rom (rx) : ORIGIN = 0x08000000, LENGTH = 32M
}
__text_start = ORIGIN(rom);
SECTIONS {
. = __text_start;
.text : {
KEEP(*(.crt0));
*(.crt0 .crt0*);
*(.text .text*);
. = ALIGN(4);
} > rom
__text_end = .;
.rodata : {
*(.rodata .rodata.*);
. = ALIGN(4);
} > rom
__iwram_rom_start = .;
.iwram : {
__iwram_data_start = ABSOLUTE(.);
*(.iwram .iwram.*);
. = ALIGN(4);
*(.text_iwram .text_iwram.*);
. = ALIGN(4);
__iwram_data_end = ABSOLUTE(.);
} > iwram AT>rom
. = __iwram_rom_start + (__iwram_data_end - __iwram_data_start);
__ewram_rom_start = .;
.ewram : {
__ewram_data_start = ABSOLUTE(.);
*(.ewram .ewram.*);
. = ALIGN(4);
*(.data .data.*);
. = ALIGN(4);
__ewram_data_end = ABSOLUTE(.);
} > ewram AT>rom
.bss : {
*(.bss .bss.*);
. = ALIGN(4);
__iwram_end = ABSOLUTE(.);
} > iwram
__iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start;
__iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2;
__ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start;
__ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2;
.shstrtab : {
*(.shstrtab)
}
/* debugging sections */
/* Stabs */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
.debug_ranges 0 : { *(.debug_ranges) }
/* discard anything not already mentioned */
/DISCARD/ : { *(*) }
}

View file

@ -0,0 +1,113 @@
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(__start)
EXTERN(__RUST_INTERRUPT_HANDLER)
EXTERN(__agbabi_memset)
EXTERN(__agbabi_memcpy)
MEMORY {
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K
}
__text_start = ORIGIN(ewram);
SECTIONS {
. = __text_start;
.text : {
KEEP(*(.crt0));
*(.crt0 .crt0*);
*(.text .text*);
. = ALIGN(4);
} > rom
__text_end = .;
.rodata : {
*(.rodata .rodata.*);
. = ALIGN(4);
} > ewram
__iwram_rom_start = .;
.iwram : {
__iwram_data_start = ABSOLUTE(.);
*(.iwram .iwram.*);
. = ALIGN(4);
*(.text_iwram .text_iwram.*);
. = ALIGN(4);
__iwram_data_end = ABSOLUTE(.);
} > iwram AT>ewram
. = __iwram_rom_start + (__iwram_data_end - __iwram_data_start);
__ewram_rom_start = .;
.ewram : {
__ewram_data_start = ABSOLUTE(.);
*(.ewram .ewram.*);
. = ALIGN(4);
*(.data .data.*);
. = ALIGN(4);
__ewram_data_end = ABSOLUTE(.);
} > ewram AT>ewram
.bss : {
*(.bss .bss.*);
. = ALIGN(4);
__iwram_end = ABSOLUTE(.);
} > iwram
__iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start;
__iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2;
__ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start;
__ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2;
.shstrtab : {
*(.shstrtab)
}
/* debugging sections */
/* Stabs */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
.debug_ranges 0 : { *(.debug_ranges) }
/* discard anything not already mentioned */
/DISCARD/ : { *(*) }
}

Binary file not shown.

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" source="UI.tsx"/>
<layer id="1" name="UI" width="30" height="20">
<data encoding="csv">
1,97,98,99,100,101,102,75,3,4,5,6,3,4,5,6,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
1073741928,112,113,114,115,116,117,1073741927,0,0,0,0,0,0,0,0,0,0,0,0,0,22,23,24,25,26,27,28,29,30,
17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,21,21,21,21,21,21,21,20,
18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,21,21,21,21,21,21,21,20,
17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,21,21,21,21,21,21,21,19,
17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,21,21,21,21,21,21,21,19,
16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,21,21,21,21,21,21,21,20,
17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,21,21,21,21,21,21,21,20,
18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,21,21,21,21,21,21,21,19,
17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,38,39,42,43,42,43,44,45,
16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,53,54,55,56,57,58,59,60,
17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,21,21,21,21,21,21,21,20,
18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1073741865,21,21,21,21,21,21,21,20,
18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,21,21,21,21,21,21,21,19,
16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1073741846,21,21,21,21,21,21,21,19,
17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,21,21,21,21,21,21,21,20,
18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,21,21,21,21,21,21,21,20,
16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,21,21,21,21,21,21,21,20,
104,61,62,63,64,65,66,67,68,69,70,91,92,93,94,95,96,70,103,0,0,41,21,21,21,21,21,21,21,20,
31,76,77,78,79,80,81,82,83,84,85,106,107,108,109,110,111,85,118,35,36,46,47,48,49,47,48,49,50,51
</data>
</layer>
</map>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.10.1" name="UI" tilewidth="8" tileheight="8" tilecount="60" columns="15">
<image source="ui_tiles.png" width="120" height="32"/>
</tileset>

View file

@ -0,0 +1,12 @@
{
"automappingRulesFile": "",
"commands": [
],
"compatibilityVersion": 1100,
"extensionsPath": "extensions",
"folders": [
"."
],
"propertyTypes": [
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -0,0 +1,176 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.10.1" name="level" tilewidth="8" tileheight="8" tilecount="324" columns="18">
<image source="level.png" width="144" height="144"/>
<tile id="0" type="WALL"/>
<tile id="1" type="WALL"/>
<tile id="2" type="WALL"/>
<tile id="3" type="WALL"/>
<tile id="4" type="WALL"/>
<tile id="5" type="WALL"/>
<tile id="6" type="WALL"/>
<tile id="7" type="WALL"/>
<tile id="8" type="WALL"/>
<tile id="9" type="WALL"/>
<tile id="10" type="WALL"/>
<tile id="11" type="WALL"/>
<tile id="12" type="WALL"/>
<tile id="13" type="WALL"/>
<tile id="14" type="WALL"/>
<tile id="15" type="WALL"/>
<tile id="16" type="WALL"/>
<tile id="17" type="WALL"/>
<tile id="18" type="WALL"/>
<tile id="19" type="WALL"/>
<tile id="20" type="WALL"/>
<tile id="21" type="WALL"/>
<tile id="22" type="WALL"/>
<tile id="23" type="WALL"/>
<tile id="24" type="WALL"/>
<tile id="25" type="WALL"/>
<tile id="26" type="WALL"/>
<tile id="27" type="WALL"/>
<tile id="28" type="WALL"/>
<tile id="29" type="WALL"/>
<tile id="30" type="WALL"/>
<tile id="31" type="WALL"/>
<tile id="32" type="WALL"/>
<tile id="33" type="WALL"/>
<tile id="34" type="WALL"/>
<tile id="35" type="WALL"/>
<tile id="36" type="WALL"/>
<tile id="37" type="WALL"/>
<tile id="38" type="FLOOR"/>
<tile id="39" type="FLOOR"/>
<tile id="40" type="FLOOR"/>
<tile id="41" type="FLOOR"/>
<tile id="42" type="FLOOR"/>
<tile id="43" type="FLOOR"/>
<tile id="44" type="FLOOR"/>
<tile id="45" type="FLOOR"/>
<tile id="46" type="FLOOR"/>
<tile id="47" type="FLOOR"/>
<tile id="48" type="FLOOR"/>
<tile id="49" type="FLOOR"/>
<tile id="50" type="FLOOR"/>
<tile id="51" type="FLOOR"/>
<tile id="52" type="WALL"/>
<tile id="53" type="WALL"/>
<tile id="54" type="WALL"/>
<tile id="55" type="WALL"/>
<tile id="56" type="FLOOR"/>
<tile id="57" type="FLOOR"/>
<tile id="58" type="FLOOR"/>
<tile id="59" type="FLOOR"/>
<tile id="60" type="FLOOR"/>
<tile id="61" type="FLOOR"/>
<tile id="62" type="FLOOR"/>
<tile id="63" type="FLOOR"/>
<tile id="64" type="FLOOR"/>
<tile id="65" type="FLOOR"/>
<tile id="66" type="FLOOR"/>
<tile id="67" type="FLOOR"/>
<tile id="68" type="FLOOR"/>
<tile id="69" type="FLOOR"/>
<tile id="70" type="WALL"/>
<tile id="71" type="WALL"/>
<tile id="72" type="WALL"/>
<tile id="73" type="WALL"/>
<tile id="74" type="WALL"/>
<tile id="75" type="WALL"/>
<tile id="76" type="WALL"/>
<tile id="77" type="WALL"/>
<tile id="78" type="WALL"/>
<tile id="79" type="WALL"/>
<tile id="80" type="WALL"/>
<tile id="81" type="WALL"/>
<tile id="82" type="WALL"/>
<tile id="83" type="WALL"/>
<tile id="84" type="WALL"/>
<tile id="85" type="WALL"/>
<tile id="86" type="WALL"/>
<tile id="87" type="WALL"/>
<tile id="88" type="WALL"/>
<tile id="89" type="WALL"/>
<tile id="90" type="WALL"/>
<tile id="91" type="WALL"/>
<tile id="92" type="WALL"/>
<tile id="93" type="WALL"/>
<tile id="94" type="WALL"/>
<tile id="95" type="WALL"/>
<tile id="96" type="WALL"/>
<tile id="97" type="WALL"/>
<tile id="98" type="WALL"/>
<tile id="99" type="WALL"/>
<tile id="100" type="WALL"/>
<tile id="101" type="WALL"/>
<tile id="102" type="WALL"/>
<tile id="103" type="WALL"/>
<tile id="104" type="WALL"/>
<tile id="105" type="WALL"/>
<tile id="106" type="WALL"/>
<tile id="107" type="WALL"/>
<tile id="108" type="WALL"/>
<tile id="109" type="WALL"/>
<tile id="110" type="WALL"/>
<tile id="111" type="WALL"/>
<tile id="112" type="WALL"/>
<tile id="113" type="WALL"/>
<tile id="114" type="WALL"/>
<tile id="115" type="WALL"/>
<tile id="116" type="WALL"/>
<tile id="117" type="WALL"/>
<tile id="118" type="WALL"/>
<tile id="119" type="WALL"/>
<tile id="126" type="WALL"/>
<tile id="127" type="WALL"/>
<tile id="128" type="WALL"/>
<tile id="129" type="WALL"/>
<tile id="130" type="WALL"/>
<tile id="131" type="WALL"/>
<tile id="132" type="WALL"/>
<tile id="133" type="WALL"/>
<tile id="134" type="WALL"/>
<tile id="135" type="WALL"/>
<tile id="136" type="WALL"/>
<tile id="137" type="WALL"/>
<tile id="144" type="WALL"/>
<tile id="145" type="WALL"/>
<tile id="146" type="WALL"/>
<tile id="147" type="WALL"/>
<tile id="148" type="WALL"/>
<tile id="149" type="WALL"/>
<tile id="150" type="WALL"/>
<tile id="151" type="WALL"/>
<tile id="152" type="WALL"/>
<tile id="153" type="WALL"/>
<tile id="162" type="WALL"/>
<tile id="163" type="WALL"/>
<tile id="164" type="WALL"/>
<tile id="165" type="WALL"/>
<tile id="166" type="WALL"/>
<tile id="167" type="WALL"/>
<tile id="168" type="WALL"/>
<tile id="169" type="WALL"/>
<tile id="170" type="WALL"/>
<tile id="171" type="WALL"/>
<tile id="180" type="WALL"/>
<tile id="181" type="WALL"/>
<tile id="182" type="WALL"/>
<tile id="183" type="WALL"/>
<tile id="184" type="WALL"/>
<tile id="185" type="WALL"/>
<tile id="186" type="WALL"/>
<tile id="187" type="WALL"/>
<tile id="188" type="WALL"/>
<tile id="189" type="WALL"/>
<tile id="198" type="WALL"/>
<tile id="199" type="WALL"/>
<tile id="200" type="WALL"/>
<tile id="201" type="WALL"/>
<tile id="202" type="WALL"/>
<tile id="203" type="WALL"/>
<tile id="204" type="WALL"/>
<tile id="205" type="WALL"/>
<tile id="206" type="WALL"/>
<tile id="207" type="WALL"/>
</tileset>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="5">
<properties>
<property name="DIRECTIONS" value="RRRRRR"/>
<property name="ITEMS" value="KEY"/>
<property name="NAME" value="Keys open doors"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Background" width="22" height="20" offsetx="0" offsety="0.181818">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,0,0,
0,0,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,0,0,
0,0,37,38,39,40,41,42,43,44,45,52,47,48,49,50,51,52,53,54,0,0,
0,0,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,0,0,
0,0,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,0,0,
0,0,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Objects">
<object id="1" name="STAIRS" x="135.932" y="73.1136">
<point/>
</object>
<object id="2" name="HERO" x="57.7273" y="72.1818">
<point/>
</object>
<object id="4" name="DOOR" x="104.75" y="72.25">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="3">
<properties>
<property name="DIRECTIONS" value="DDDL"/>
<property name="ITEMS" value="DOOR"/>
<property name="NAME" value="You can't go through locked doors"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Background" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,1,2,3,16,17,18,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,19,20,21,34,35,36,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,37,38,39,52,53,54,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,55,56,57,70,71,72,0,0,0,0,0,0,
0,0,0,0,0,0,1,2,6,7,1073741951,1073741952,43,44,147,148,0,0,0,0,0,0,
0,0,0,0,0,0,19,20,24,25,1073741933,1073741934,61,62,165,166,0,0,0,0,0,0,
0,0,0,0,0,0,37,38,2147483694,2147483693,41,42,47,48,183,184,0,0,0,0,0,0,
0,0,0,0,0,0,55,56,2147483712,2147483711,59,60,65,66,201,202,0,0,0,0,0,0,
0,0,0,0,0,0,73,74,75,76,109,110,41,42,165,166,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,94,127,128,59,60,183,184,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,37,38,45,46,147,148,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,55,56,63,64,165,166,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,73,74,75,76,89,90,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,91,92,93,94,107,108,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Objects">
<object id="1" name="HERO" x="104.789" y="38.731">
<point/>
</object>
<object id="2" name="STAIRS" x="87.575" y="71.4371">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="5">
<properties>
<property name="DIRECTIONS" value="RRDURRRRR"/>
<property name="ITEMS" value="DOOR"/>
<property name="NAME" value="Keys open more than one door"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Tile Layer 1" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,2,3,4,5,6,7,8,9,10,15,16,17,18,0,0,0,0,
0,0,0,0,19,20,21,22,23,24,25,26,27,28,33,34,35,36,0,0,0,0,
0,0,0,0,37,38,39,40,41,42,43,44,45,46,51,52,53,54,0,0,0,0,
0,0,0,0,55,56,57,58,59,60,61,62,63,64,69,70,71,72,0,0,0,0,
0,0,0,0,73,74,109,110,1073741885,1073741886,2147483758,2147483757,81,82,87,88,89,90,0,0,0,0,
0,0,0,0,91,92,127,128,1073741867,1073741868,2147483776,2147483775,99,100,105,106,107,108,0,0,0,0,
0,0,0,0,0,0,73,74,75,76,2147483722,2147483721,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,94,2147483740,2147483739,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="1" name="HERO" x="56" y="72.375">
<point/>
</object>
<object id="2" name="KEY" x="72" y="87.375">
<point/>
</object>
<object id="3" name="DOOR" x="101.625" y="72.875">
<point/>
</object>
<object id="4" name="STAIRS" x="118.875" y="72">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="4" nextobjectid="4">
<properties>
<property name="DIRECTIONS" value="LLLLLLL"/>
<property name="ITEMS" value="SWORD"/>
<property name="NAME" value="You need a sword to kill slimes"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Tile Layer 1" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,0,0,
0,0,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,0,0,
0,0,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,0,0,
0,0,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,0,0,
0,0,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,0,0,
0,0,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="3" name="Object Layer 1">
<object id="1" name="HERO" x="136" y="72">
<point/>
</object>
<object id="2" name="SLIME" x="72" y="73.3333">
<point/>
</object>
<object id="3" name="STAIRS" x="41.6667" y="73.3333">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="4">
<properties>
<property name="DIRECTIONS" value="LLRRRRR"/>
<property name="ITEMS" value="SLIME,SWORD"/>
<property name="NAME" value="It takes time to kill slimes"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Background" width="22" height="20" offsetx="0" offsety="0.181818">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,0,0,
0,0,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,0,0,
0,0,37,38,39,40,41,42,43,44,45,52,47,48,49,50,51,52,53,54,0,0,
0,0,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,0,0,
0,0,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,0,0,
0,0,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Objects">
<object id="1" name="STAIRS" x="136.182" y="72.3636">
<point/>
</object>
<object id="2" name="HERO" x="72.7273" y="72.1818">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="6">
<properties>
<property name="DIRECTIONS" value="URULUUUUL"/>
<property name="ITEMS" value="DOOR"/>
<property name="NAME" value="You can only hold one item"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Backgrounds" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,2,3,4,5,5,6,12,13,14,15,16,17,18,0,0,0,0,
0,0,0,0,19,20,21,22,23,23,24,30,31,32,33,34,35,36,0,0,0,0,
0,0,0,0,37,38,39,40,41,42,43,44,39,40,43,44,1073741989,1073741990,0,0,0,0,
0,0,0,0,55,56,57,58,59,60,61,62,57,58,61,62,1073741971,1073741972,0,0,0,0,
0,0,0,0,73,74,75,76,77,78,109,110,43,44,111,112,113,114,0,0,0,0,
0,0,0,0,91,92,93,94,95,96,127,128,61,62,129,130,131,132,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,145,146,45,46,47,48,147,148,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,163,164,63,64,65,66,165,166,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,181,182,47,48,43,44,183,184,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,199,200,65,66,61,62,201,202,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,1073741987,1073741988,49,50,51,52,1073741989,1073741990,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,1073741969,1073741970,67,68,69,70,1073741971,1073741972,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,73,74,85,86,87,88,89,90,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,91,92,103,104,105,106,107,108,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Objects">
<object id="1" name="HERO" x="103.918" y="105.197">
<point/>
</object>
<object id="2" name="STAIRS" x="88" y="40.6774">
<point/>
</object>
<object id="3" name="SLIME" x="103.749" y="40">
<point/>
</object>
<object id="4" name="SWORD" x="103.749" y="89.11">
<point/>
</object>
<object id="5" name="KEY" x="121.191" y="75.7318">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="8">
<properties>
<property name="DIRECTIONS" value="RULDRULDRULD"/>
<property name="ITEMS" value="DOOR,SWORD,SLIME,SQUID_UP"/>
<property name="NAME" value="Now they're just going in circles"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Backgrounds" width="22" height="20">
<data encoding="csv">
0,0,0,0,1,2,7,8,3,4,13,7,8,9,10,11,12,13,14,15,3221225564,3221225563,
0,0,0,0,19,20,25,26,21,22,31,25,26,27,28,29,30,31,32,33,3221225546,3221225545,
0,0,0,0,37,38,41,42,45,46,47,48,49,50,51,52,41,42,41,42,147,148,
0,0,0,0,55,56,59,60,63,64,65,66,67,68,69,70,59,60,59,60,165,166,
1,2,1073741917,1073741918,1073741951,1073741952,41,42,41,42,43,44,45,46,47,48,49,50,51,52,183,184,
19,20,1073741899,1073741900,1073741933,1073741934,59,60,59,60,61,62,63,64,65,66,67,68,69,70,201,202,
145,146,41,42,41,42,45,46,41,42,41,42,41,42,45,46,41,42,49,50,147,148,
163,164,59,60,59,60,63,64,59,60,59,60,59,60,63,64,57,58,59,60,165,166,
181,182,39,40,41,42,43,44,45,46,47,48,49,50,45,46,2147483758,2147483757,77,78,2147483722,2147483721,
199,200,57,58,59,60,61,62,63,64,65,66,67,68,63,64,2147483776,2147483775,95,96,2147483740,2147483739,
73,74,109,110,49,50,39,40,41,42,43,44,45,46,47,48,147,148,0,0,0,0,
91,92,127,128,67,68,57,58,59,60,61,62,63,64,65,66,165,166,0,0,0,0,
0,0,37,38,41,42,39,40,41,42,43,44,45,46,47,48,183,184,0,0,0,0,
0,0,55,56,59,60,57,58,59,60,61,62,63,64,65,66,201,202,0,0,0,0,
0,0,73,74,109,110,43,44,45,46,47,48,49,50,51,52,147,148,0,0,0,0,
0,0,91,92,127,128,61,62,63,64,65,66,67,68,69,70,165,166,0,0,0,0,
0,0,0,0,181,182,41,42,43,44,45,46,47,48,2147483758,2147483757,2147483722,2147483721,0,0,0,0,
0,0,0,0,199,200,59,60,61,62,63,64,65,66,2147483776,2147483775,2147483740,2147483739,0,0,0,0,
0,0,0,0,73,74,83,84,77,78,79,80,81,82,2147483722,2147483721,0,0,0,0,0,0,
0,0,0,0,91,92,101,102,95,96,97,98,99,100,2147483740,2147483739,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Objects">
<object id="6" name="HERO" x="72" y="72">
<point/>
</object>
<object id="7" name="STAIRS" x="120" y="40">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="7">
<properties>
<property name="DIRECTIONS" value="RRRRRRDDRR"/>
<property name="ITEMS" value="DOOR_SWITCHED_OPEN,SWITCH"/>
<property name="NAME" value="Be careful of the spikes"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Tile Layer 1" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,0,0,
0,0,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,0,0,
0,0,37,38,39,40,47,48,39,48,43,44,47,48,51,52,51,52,1073741895,1073741896,0,0,
0,0,55,56,57,58,65,66,69,70,61,62,65,66,61,70,69,70,1073741877,1073741878,0,0,
0,0,145,146,51,52,47,48,1073741881,1073741882,51,48,47,48,47,48,43,44,147,148,0,0,
0,0,163,164,69,70,65,66,1073741863,1073741864,61,70,65,66,65,66,61,62,165,166,0,0,
0,0,181,182,39,52,47,48,43,44,51,52,39,50,51,52,51,50,183,184,0,0,
0,0,199,200,69,70,65,66,61,62,69,70,61,62,69,70,69,70,201,202,0,0,
0,0,1073741987,1073741988,51,52,43,48,51,52,39,50,51,52,51,52,39,50,53,54,0,0,
0,0,1073741969,1073741970,69,70,61,62,69,70,69,70,69,70,69,70,69,70,71,72,0,0,
0,0,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,0,0,
0,0,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="1" name="HERO" x="38.0855" y="71.8675">
<point/>
</object>
<object id="2" name="SPIKES_DOWN" x="120.066" y="90.8026">
<point/>
</object>
<object id="3" name="SWITCH" x="86.284" y="89.7268">
<point/>
</object>
<object id="4" name="STAIRS" x="120.496" y="108.447">
<point/>
</object>
<object id="6" name="SPIKES" x="103.283" y="106.51">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="18">
<properties>
<property name="DIRECTIONS" value="ULULULULULULULUL"/>
<property name="ITEMS" value="DOOR_SWITCHED_OPEN,SWITCH,SWITCH,DOOR_SWITCHED"/>
<property name="NAME" value="Why do people leave things in awkward places?"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Tile Layer 1" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,2,3,4,5,6,9,10,11,12,13,14,15,16,17,18,0,0,
0,0,0,0,19,20,21,22,23,24,27,28,29,30,31,32,33,34,35,36,0,0,
0,0,0,0,37,38,39,40,51,52,51,52,41,42,45,46,39,40,53,54,0,0,
0,0,0,0,55,56,57,58,69,70,69,70,59,60,63,64,57,58,71,72,0,0,
0,0,0,0,2147483762,2147483761,2147483760,2147483759,51,52,39,40,39,40,51,52,45,46,147,148,0,0,
0,0,0,0,2147483780,2147483779,2147483778,2147483777,69,70,57,58,57,58,69,70,63,64,165,166,0,0,
0,0,0,0,145,146,39,40,39,40,45,46,51,52,45,46,45,46,183,184,0,0,
0,0,0,0,163,164,57,58,57,58,63,64,69,70,63,64,63,64,201,202,0,0,
0,0,0,0,181,182,45,46,41,42,51,52,45,46,51,52,39,40,53,54,0,0,
0,0,0,0,199,200,63,64,59,60,69,70,63,64,69,70,57,58,71,72,0,0,
0,0,0,0,73,74,75,76,77,78,79,80,81,82,83,86,87,88,89,90,0,0,
0,0,0,0,91,92,93,94,95,96,97,98,99,100,101,104,105,106,107,108,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="7" name="HERO" x="134.753" y="104.844">
<point/>
</object>
<object id="8" name="STAIRS" x="56" y="57.7214">
<point/>
</object>
<object id="10" name="SPIKES_DOWN" x="73.2137" y="71.2773">
<point/>
</object>
<object id="12" name="SLIME" x="71.4924" y="56">
<point/>
</object>
<object id="15" name="SPIKES" x="88.9213" y="56.8607">
<point/>
</object>
<object id="17" name="SWORD" x="89.3516" y="89.9972">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="15">
<properties>
<property name="DIRECTIONS" value="RRRRRRRRRRRRRR"/>
<property name="ITEMS" value="DOOR,SQUID_DOWN,DOOR_SWITCHED,DOOR_SWITCHED_OPEN"/>
<property name="NAME" value="Why are they running right at it?"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Tile Layer 1" width="22" height="20">
<data encoding="csv">
0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,14,3221225564,3221225563,0,0,0,0,
0,0,0,0,19,20,21,22,23,24,25,26,27,28,29,32,3221225546,3221225545,0,0,0,0,
0,0,1,2,1073741951,1073741952,43,44,39,40,49,50,41,42,39,40,3221225600,3221225599,6,7,17,18,
0,0,19,20,1073741933,1073741934,61,62,57,58,67,68,59,60,57,58,3221225582,3221225581,24,25,35,36,
0,0,145,146,43,44,49,50,39,40,41,42,39,40,41,42,43,44,39,40,53,54,
0,0,163,164,61,62,67,68,57,58,59,60,57,58,59,60,61,62,57,58,71,72,
0,0,181,182,39,40,43,44,39,40,43,44,41,42,39,40,41,42,39,40,147,148,
0,0,199,200,57,58,61,62,57,58,61,62,59,60,57,58,59,60,57,58,165,166,
0,0,37,38,43,44,41,42,41,42,49,50,39,40,49,50,39,40,39,40,183,184,
0,0,55,56,61,62,59,60,59,60,67,68,57,58,67,68,57,58,57,58,201,202,
0,0,1073741987,1073741988,43,44,39,40,41,42,41,42,39,40,41,42,41,42,49,50,147,148,
0,0,1073741969,1073741970,61,62,57,58,59,60,59,60,57,58,59,60,59,60,67,68,165,166,
0,0,145,146,39,40,39,40,39,40,39,40,39,40,39,40,41,42,39,40,183,184,
0,0,163,164,57,58,57,58,57,58,57,58,57,58,57,58,59,60,57,58,201,202,
0,0,37,38,43,44,41,42,39,40,41,42,41,42,43,44,39,40,39,40,147,148,
0,0,55,56,61,62,59,60,57,58,59,60,59,60,61,62,57,58,57,58,165,166,
0,0,3221225508,3221225507,75,76,77,78,79,80,109,110,39,40,39,40,39,40,2147483758,2147483757,89,90,
0,0,3221225490,3221225489,93,94,95,96,97,98,127,128,57,58,57,58,57,58,2147483776,2147483775,107,108,
0,0,0,0,0,0,0,0,0,0,73,74,83,84,85,86,87,88,3221225492,3221225491,0,0,
0,0,0,0,0,0,0,0,0,0,91,92,101,102,103,104,105,106,3221225474,3221225473,0,0
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="7" name="HERO" x="39.3138" y="75.0303">
<point/>
</object>
<object id="9" name="SPIKES_DOWN" x="103.552" y="74.7733">
<point/>
</object>
<object id="10" name="SQUID_UP" x="103.295" y="141.324">
<point/>
</object>
<object id="11" name="SWORD" x="103.552" y="122.31">
<point/>
</object>
<object id="12" name="SWITCH" x="135.414" y="75.5442">
<point/>
</object>
<object id="13" name="STAIRS" x="153.144" y="72.9747">
<point/>
</object>
<object id="14" name="SLIME" x="121.282" y="72.9747">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="7">
<properties>
<property name="DIRECTIONS" value="RRRRRRRRR"/>
<property name="ITEMS" value="SWORD,SQUID_DOWN,HERO"/>
<property name="NAME" value="Squids keep stealing the treasure. Why did I put them here again?"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Tile Layer 1" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,17,18,0,0,0,0,
0,0,0,0,0,0,0,0,19,20,21,22,23,24,25,26,35,36,0,0,0,0,
0,0,0,0,0,0,0,0,145,146,45,46,47,48,49,50,147,148,0,0,0,0,
0,0,0,0,0,0,0,0,163,164,63,64,65,66,67,68,165,166,0,0,0,0,
0,0,0,0,0,0,0,0,181,182,45,46,47,48,45,46,53,54,0,0,0,0,
0,0,0,0,0,0,0,0,199,200,63,64,65,66,63,64,71,72,0,0,0,0,
0,0,0,0,0,0,0,0,73,74,109,110,43,44,2147483758,2147483757,89,90,0,0,0,0,
0,0,0,0,0,0,0,0,91,92,127,128,61,62,2147483776,2147483775,107,108,0,0,0,0,
0,0,1,2,3,4,5,6,7,8,1073741951,1073741952,45,46,3221225600,3221225599,14,15,17,18,0,0,
0,0,19,20,21,22,23,24,25,26,1073741933,1073741934,63,64,3221225582,3221225581,32,33,35,36,0,0,
0,0,37,38,39,40,39,40,41,42,43,44,45,46,47,48,49,50,53,54,0,0,
0,0,55,56,57,58,57,58,59,60,61,62,63,64,65,66,67,68,71,72,0,0,
0,0,73,74,2147483729,75,76,77,78,79,109,110,47,48,2147483758,2147483757,86,87,89,90,0,0,
0,0,91,92,2147483747,93,94,95,96,97,127,128,65,66,2147483776,2147483775,104,105,107,108,0,0,
0,0,0,0,0,0,0,0,0,0,145,146,43,44,147,148,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,163,164,61,62,165,166,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,181,182,45,46,183,184,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,199,200,63,64,201,202,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,73,74,75,76,3221225492,3221225491,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,91,92,93,94,3221225474,3221225473,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="2" name="DOOR" x="120.114" y="92.7461">
<point/>
</object>
<object id="3" name="SLIME" x="104.552" y="92.7253">
<point/>
</object>
<object id="4" name="STAIRS" x="136.268" y="93.2461">
<point/>
</object>
<object id="6" name="KEY" x="104" y="56">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="6">
<properties>
<property name="DIRECTIONS" value="DDDRRRR"/>
<property name="ITEMS" value="SQUID_DOWN,DOOR_SWITCHED_OPEN,DOOR_SWITCHED,KEY"/>
<property name="NAME" value="I'm sorry squid"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Tile Layer 1" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,1,2,6,7,17,18,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,19,20,24,25,35,36,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,37,38,39,40,147,148,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,55,56,57,58,165,166,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,145,146,41,42,183,184,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,163,164,59,60,201,202,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,181,182,43,44,3221225600,3221225599,13,14,15,16,17,18,0,0,0,0,
0,0,0,0,0,0,199,200,61,62,3221225582,3221225581,31,32,33,34,35,36,0,0,0,0,
0,0,0,0,0,0,1073742023,1073742024,39,40,41,42,43,44,43,44,53,54,0,0,0,0,
0,0,0,0,0,0,1073742005,1073742006,57,58,59,60,61,62,61,62,71,72,0,0,0,0,
0,0,0,0,0,0,1073741987,1073741988,47,48,2147483758,2147483757,75,76,80,81,89,90,0,0,0,0,
0,0,0,0,0,0,1073741969,1073741970,65,66,2147483776,2147483775,93,94,98,99,107,108,0,0,0,0,
0,0,0,0,0,0,37,38,49,50,147,148,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,55,56,67,68,165,166,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,73,74,75,76,89,90,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,94,107,108,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="1" name="HERO" x="73.4886" y="42.9112">
<point/>
</object>
<object id="2" name="SWITCH" x="71.433" y="125.393">
<point/>
</object>
<object id="3" name="SPIKES" x="105.351" y="91.2183">
<point/>
</object>
<object id="4" name="STAIRS" x="120.511" y="89.6766">
<point/>
</object>
<object id="5" name="DOOR" x="88.3919" y="87.8779">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="8">
<properties>
<property name="DIRECTIONS" value="RDRDRDRRRDDDRR"/>
<property name="ITEMS" value="SWITCH,SWORD,DOOR,SLIME,SLIME"/>
<property name="NAME" value="The key is right there!"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Tile Layer 1" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,1,2,5,6,1073741931,1073741932,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,19,20,23,24,1073741913,1073741914,0,0,0,0,0,0,
0,0,3221225580,3221225579,5,6,7,8,9,5,1073741951,1073741952,43,44,3221225600,3221225599,5,6,2147483650,2147483649,0,0,
0,0,3221225562,3221225561,23,24,25,26,27,23,1073741933,1073741934,61,62,3221225582,3221225581,23,24,2147483668,2147483667,0,0,
0,0,37,38,47,48,39,40,41,42,47,48,39,40,45,46,41,42,53,54,0,0,
0,0,55,56,65,66,57,58,59,60,65,66,57,58,63,64,59,60,71,72,0,0,
0,0,1073741987,1073741988,39,40,47,48,47,48,41,42,39,40,47,48,45,46,147,148,0,0,
0,0,1073741969,1073741970,57,58,65,66,65,66,59,60,57,58,65,66,63,64,165,166,0,0,
0,0,37,38,39,40,39,40,41,42,39,40,39,40,47,48,41,42,183,184,0,0,
0,0,55,56,57,58,57,58,59,60,57,58,57,58,65,66,59,60,201,202,0,0,
0,0,145,146,47,48,47,48,47,48,47,48,41,42,45,46,111,112,113,114,0,0,
0,0,163,164,65,66,65,66,65,66,65,66,59,60,63,64,129,130,131,132,0,0,
0,0,37,38,47,48,43,44,47,48,41,42,149,150,39,40,39,40,53,54,0,0,
0,0,55,56,65,66,61,62,65,66,59,60,167,168,57,58,57,58,71,72,0,0,
0,0,73,74,75,76,77,78,79,80,81,82,185,186,85,86,87,88,89,90,0,0,
0,0,91,92,93,94,95,96,97,98,99,100,203,204,103,104,105,106,107,108,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="1" name="HERO" x="40" y="88">
<point/>
</object>
<object id="2" name="STAIRS" x="136" y="120">
<point/>
</object>
<object id="3" name="DOOR" x="120" y="120">
<point/>
</object>
<object id="4" name="KEY" x="104" y="72">
<point/>
</object>
<object id="5" name="SQUID_DOWN" x="104" y="40">
<point/>
</object>
<object id="6" name="DOOR_SWITCHED" x="104" y="56">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="22">
<properties>
<property name="DIRECTIONS" value="DDDRRRRRRRRRRRRU"/>
<property name="ITEMS" value="SQUID_UP,SQUID_DOWN,SWITCH,SPIKES,DOOR,KEY"/>
<property name="NAME" value="We used to have treasure in there"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Backgrounds" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,2147483650,2147483649,0,0,
0,0,0,0,0,0,19,20,21,22,23,24,25,26,27,28,29,30,2147483668,2147483667,0,0,
0,0,0,0,0,0,2147483814,2147483813,45,46,49,50,45,46,51,52,45,46,2147483794,2147483793,0,0,
0,0,0,0,0,0,2147483832,2147483831,63,64,67,68,63,64,69,70,63,64,2147483812,2147483811,0,0,
0,0,0,0,0,0,37,38,51,52,45,46,49,50,43,44,47,48,3221225600,3221225599,3221225564,3221225563,
0,0,0,0,0,0,55,56,69,70,63,64,67,68,61,62,65,66,3221225582,3221225581,3221225546,3221225545,
1073741915,1073741916,5,6,1073742029,1073742030,153,154,45,46,45,46,43,44,47,48,51,52,39,40,147,148,
1073741897,1073741898,23,24,1073742011,1073742012,171,172,63,64,63,64,61,62,65,66,69,70,57,58,165,166,
3221225638,3221225637,39,40,1073741993,1073741994,153,154,43,44,43,44,43,44,51,52,47,48,47,48,183,184,
3221225620,3221225619,57,58,1073741975,1073741976,171,172,61,62,61,62,61,62,69,70,65,66,65,66,201,202,
3221225638,3221225637,39,40,39,40,1073741991,1073741992,45,46,51,52,41,42,47,48,47,48,51,52,147,148,
3221225620,3221225619,57,58,57,58,1073741973,1073741974,63,64,69,70,59,60,65,66,65,66,69,70,165,166,
145,146,45,46,39,40,39,40,39,40,45,46,51,52,41,42,51,52,49,50,147,148,
163,164,63,64,57,58,57,58,57,58,63,64,69,70,59,60,69,70,67,68,165,166,
145,146,47,48,2147483758,2147483757,79,80,81,82,109,110,45,46,47,48,49,50,47,48,183,184,
163,164,65,66,2147483776,2147483775,97,98,99,100,127,128,63,64,65,66,67,68,65,66,201,202,
1073741843,1073741844,79,80,2147483722,2147483721,0,0,0,0,73,74,1073741847,1073741848,1073741849,1073741850,1073741851,1073741852,1073741853,1073741854,2147483722,2147483721,
1073741825,1073741826,97,98,2147483740,2147483739,0,0,0,0,91,92,1073741829,1073741830,1073741831,1073741832,1073741833,1073741834,1073741835,1073741836,2147483740,2147483739,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Objects">
<object id="12" name="HERO" x="24" y="72">
<point/>
</object>
<object id="18" name="DOOR" x="24" y="104">
<point/>
</object>
<object id="20" name="SWORD" x="88" y="56">
<point/>
</object>
<object id="21" name="STAIRS" x="120" y="88">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="12">
<properties>
<property name="DIRECTIONS" value="DDDRRRDDDLLL"/>
<property name="ITEMS" value="SWITCH,DOOR"/>
<property name="NAME" value="Why did I put that door there?"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Backgrounds" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,1,2,15,16,17,18,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,19,20,33,34,35,36,0,0,0,0,0,0,0,0,0,0,
1,2,6,7,8,9,117,118,43,44,3221225600,3221225599,17,18,0,0,0,0,0,0,0,0,
19,20,24,25,26,27,135,136,61,62,3221225582,3221225581,35,36,0,0,0,0,0,0,0,0,
145,146,39,40,45,46,1073741991,1073741992,45,46,51,52,53,54,0,0,0,0,0,0,0,0,
163,164,57,58,63,64,1073741973,1073741974,63,64,69,70,71,72,0,0,0,0,0,0,0,0,
181,182,45,46,45,46,47,48,49,50,39,40,3221225600,3221225599,11,12,13,14,15,16,17,18,
199,200,63,64,63,64,65,66,67,68,57,58,3221225582,3221225581,29,30,31,32,33,34,35,36,
145,146,45,46,47,48,45,46,45,46,39,40,43,44,47,48,39,40,45,46,147,148,
163,164,63,64,65,66,63,64,63,64,57,58,61,62,65,66,57,58,63,64,165,166,
181,182,47,48,39,40,149,150,47,48,47,48,149,150,39,40,45,46,47,48,183,184,
199,200,65,66,57,58,167,168,65,66,65,66,167,168,57,58,63,64,65,66,201,202,
37,38,45,46,151,152,153,154,39,40,111,112,119,120,47,48,51,52,47,48,165,166,
55,56,63,64,169,170,171,172,57,58,129,130,137,138,65,66,69,70,65,66,183,184,
73,74,75,76,187,188,189,190,47,48,39,40,47,48,51,52,51,52,39,40,147,148,
91,92,93,94,205,206,207,208,65,66,57,58,65,66,69,70,69,70,57,58,165,166,
0,0,0,0,0,0,145,146,47,48,39,40,149,150,51,52,2147483758,2147483757,77,78,2147483722,2147483721,
0,0,0,0,0,0,163,164,65,66,57,58,167,168,69,70,2147483776,2147483775,95,96,2147483740,2147483739,
0,0,0,0,0,0,73,74,75,76,77,78,185,186,79,80,2147483722,2147483721,0,0,0,0,
0,0,0,0,0,0,91,92,93,94,95,96,203,204,97,98,2147483740,2147483739,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Objects">
<object id="6" name="DOOR_SWITCHED" x="72" y="56">
<point/>
</object>
<object id="7" name="DOOR_SWITCHED_OPEN" x="104" y="72">
<point/>
</object>
<object id="8" name="HERO" x="72" y="24">
<point/>
</object>
<object id="9" name="SQUID_DOWN" x="24" y="72">
<point/>
</object>
<object id="10" name="SPIKES_DOWN" x="104" y="120">
<point/>
</object>
<object id="11" name="STAIRS" x="88" y="120">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="18">
<properties>
<property name="DIRECTIONS" value="RRDDRRRRRR"/>
<property name="ITEMS" value="KEY,SQUID_DOWN,SQUID_DOWN"/>
<property name="NAME" value="Why does no one look where they're going?"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Backgrounds" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,2147483650,2147483649,0,0,0,0,
0,0,0,0,0,0,0,0,19,20,21,22,23,24,25,26,2147483668,2147483667,0,0,0,0,
0,0,0,0,0,0,0,0,145,146,43,44,47,48,49,50,3221225600,3221225599,17,18,0,0,
0,0,0,0,0,0,0,0,163,164,61,62,65,66,67,68,3221225582,3221225581,35,36,0,0,
0,0,0,0,0,0,0,0,37,38,51,52,39,40,49,50,45,46,53,54,0,0,
0,0,0,0,0,0,0,0,55,56,69,70,57,58,67,68,63,64,71,72,0,0,
0,0,1,2,8,9,10,11,1073741951,1073741952,51,52,43,44,45,46,49,50,147,148,0,0,
0,0,19,20,26,27,28,29,1073741933,1073741934,69,70,61,62,63,64,67,68,165,166,0,0,
0,0,37,38,43,44,39,40,45,46,47,48,47,48,47,48,43,44,183,184,0,0,
0,0,55,56,61,62,57,58,63,64,65,66,65,66,65,66,61,62,201,202,0,0,
0,0,73,74,109,110,49,50,47,48,149,150,43,44,47,48,2147483758,2147483757,89,90,0,0,
0,0,91,92,127,128,67,68,65,66,167,168,61,62,65,66,2147483776,2147483775,107,108,0,0,
0,0,0,0,73,74,84,85,86,87,185,186,83,79,79,80,2147483722,2147483721,0,0,0,0,
0,0,0,0,91,92,102,103,104,105,203,204,101,97,97,98,2147483740,2147483739,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Objects">
<object id="12" name="HERO" x="40" y="88">
<point/>
</object>
<object id="13" name="DOOR" x="72" y="104">
<point/>
</object>
<object id="14" name="DOOR" x="88" y="88">
<point/>
</object>
<object id="15" name="SPIKES" x="88" y="72">
<point/>
</object>
<object id="16" name="SWITCH" x="120" y="56">
<point/>
</object>
<object id="17" name="STAIRS" x="120" y="88">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="10">
<properties>
<property name="DIRECTIONS" value="RRRRRRRRRRRRRRR"/>
<property name="ITEMS" value="SWITCH,SWITCH,SWITCH,DOOR_SWITCHED,DOOR_SWITCHED_OPEN,SQUID_UP,SQUID_UP,SQUID_DOWN,SQUID_DOWN"/>
<property name="NAME" value="DO I HAVE TO DO EVERYTHING?"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Tile Layer 1" width="22" height="20">
<data encoding="csv">
0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,0,0,
0,0,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,0,0,
0,0,37,38,41,42,43,44,45,46,47,48,49,50,51,52,49,50,53,54,0,0,
0,0,55,56,59,60,61,62,63,64,65,66,67,68,69,70,67,68,71,72,0,0,
0,0,37,38,49,50,39,40,41,42,43,44,45,46,47,48,49,50,53,54,0,0,
0,0,55,56,67,68,57,58,59,60,61,62,63,64,65,66,67,68,71,72,0,0,
0,0,145,146,41,42,43,44,45,46,47,48,49,50,51,52,49,50,147,148,0,0,
0,0,163,164,59,60,61,62,63,64,65,66,67,68,69,70,67,68,165,166,0,0,
0,0,37,38,49,50,39,40,41,42,43,44,45,46,47,48,49,50,183,184,0,0,
0,0,55,56,67,68,57,58,59,60,61,62,63,64,65,66,67,68,201,202,0,0,
0,0,37,38,41,42,43,44,45,46,47,48,49,50,51,52,49,50,53,54,0,0,
0,0,55,56,59,60,61,62,63,64,65,66,67,68,69,70,67,68,71,72,0,0,
0,0,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,0,0,
0,0,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,0,0,
1,2,3,4,3,4,5,6,7,8,9,10,11,12,13,14,15,16,15,16,17,18,
19,20,21,22,21,22,23,24,25,26,27,28,29,30,31,32,33,34,33,34,35,36,
37,38,39,40,39,40,41,42,43,44,45,46,47,48,49,50,51,52,51,52,53,54,
55,56,57,58,57,58,59,60,61,62,63,64,65,66,67,68,69,70,69,70,71,72,
73,74,75,76,75,76,77,78,79,80,81,82,83,84,85,86,87,88,87,88,89,90,
91,92,93,94,93,94,95,96,97,98,99,100,101,102,103,104,105,106,105,106,107,108
</data>
</layer>
<objectgroup id="2" name="Object Layer 1">
<object id="1" name="STAIRS" x="151.349" y="138.188">
<point/>
</object>
<object id="2" name="HERO" x="21.7659" y="137.682">
<point/>
</object>
<object id="3" name="SPIKES" x="118.447" y="140.213">
<point/>
</object>
<object id="4" name="SPIKES" x="102.755" y="142.744">
<point/>
</object>
<object id="5" name="DOOR_SWITCHED_OPEN" x="55.6802" y="139.2">
<point/>
</object>
<object id="6" name="SPIKES" x="39.7354" y="137.429">
<point/>
</object>
<object id="7" name="SPIKES_DOWN" x="71.3719" y="141.984">
<point/>
</object>
<object id="8" name="DOOR_SWITCHED" x="88.5821" y="134.645">
<point/>
</object>
<object id="9" name="SPIKES_DOWN" x="135.151" y="141.984">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="14">
<properties>
<property name="DIRECTIONS" value="RLRRRD"/>
<property name="ITEMS" value="DOOR_SWITCHED"/>
<property name="NAME" value="Switches toggle things"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Backgrounds" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,1,2,3,4,5,5,6,12,13,14,15,16,17,18,0,0,0,0,
0,0,0,0,19,20,21,22,23,23,24,30,31,32,33,34,35,36,0,0,0,0,
0,0,0,0,37,38,39,40,41,42,43,44,39,40,43,44,1073741989,1073741990,0,0,0,0,
0,0,0,0,55,56,57,58,59,60,61,62,57,58,61,62,1073741971,1073741972,0,0,0,0,
0,0,0,0,73,74,75,76,77,78,109,110,43,44,111,112,113,114,0,0,0,0,
0,0,0,0,91,92,93,94,95,96,127,128,61,62,129,130,131,132,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,37,38,49,50,51,52,183,184,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,55,56,67,68,69,70,201,202,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,73,74,75,76,81,82,89,90,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,91,92,93,94,99,100,107,108,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Objects">
<object id="1" name="HERO" x="72" y="56">
<point/>
</object>
<object id="9" name="STAIRS" x="104" y="72">
<point/>
</object>
<object id="11" name="SWITCH" x="56" y="56">
<point/>
</object>
<object id="13" name="SPIKES" x="120" y="56">
<point/>
</object>
</objectgroup>
</map>

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="22" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="15">
<properties>
<property name="DIRECTIONS" value="RDDL"/>
<property name="ITEMS" value="DOOR_SWITCHED,DOOR_SWITCHED_OPEN"/>
<property name="NAME" value="Squids, they have a mind of their own"/>
</properties>
<tileset firstgid="1" source="../level.tsx"/>
<layer id="1" name="Backgrounds" width="22" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,1,2,3,4,2147483650,2147483649,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,19,20,21,22,2147483668,2147483667,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,145,146,51,52,147,148,0,0,0,0,1,2,7,8,9,10,2147483650,2147483649,0,0,
0,0,163,164,69,70,165,166,0,0,0,0,19,20,25,26,27,28,2147483668,2147483667,0,0,
0,0,181,182,49,50,183,184,0,0,1,2,1073741951,1073741952,39,40,41,42,147,148,0,0,
0,0,199,200,67,68,201,202,0,0,19,20,1073741933,1073741934,57,58,59,60,165,166,0,0,
0,0,37,38,49,50,2147483794,2147483793,0,0,145,146,39,40,41,42,41,42,183,184,0,0,
0,0,55,56,67,68,2147483812,2147483811,0,0,163,164,57,58,59,60,59,60,201,202,0,0,
0,0,145,146,49,50,2147483830,2147483829,0,0,181,182,49,50,47,48,49,50,183,184,0,0,
0,0,163,164,67,68,2147483848,2147483847,0,0,199,200,67,68,65,66,67,68,201,202,0,0,
0,0,181,182,43,44,147,148,0,0,1073741843,1073741844,109,110,39,40,2147483758,2147483757,2147483722,2147483721,0,0,
0,0,199,200,61,62,165,166,0,0,1073741825,1073741826,127,128,57,58,2147483776,2147483775,2147483740,2147483739,0,0,
0,0,37,38,45,46,183,184,0,0,0,0,1073741843,1073741844,1073741847,1073741848,2147483722,2147483721,0,0,0,0,
0,0,55,56,63,64,201,202,0,0,0,0,1073741825,1073741826,1073741829,1073741830,2147483740,2147483739,0,0,0,0,
0,0,73,74,77,78,3221225492,3221225491,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,91,92,95,96,3221225474,3221225473,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="2" name="Objects">
<object id="8" name="SQUID_DOWN" x="40" y="40">
<point/>
</object>
<object id="9" name="SWITCH" x="40" y="72">
<point/>
</object>
<object id="10" name="HERO" x="120" y="56">
<point/>
</object>
<object id="11" name="STAIRS" x="104" y="72">
<point/>
</object>
<object id="14" name="SPIKES" x="136" y="72">
<point/>
</object>
</objectgroup>
</map>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,3 @@
[toolchain]
channel = "nightly"
components = ["rust-src", "clippy", "rustfmt"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,68 @@
use agb::{
display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager},
include_background_gfx,
};
include_background_gfx!(backgrounds, "1e151b",
ui => "maps/ui_tiles.png",
level => "maps/level.png",
ending => "gfx/ending_page.aseprite",
);
mod tilemaps {
use super::backgrounds;
include!(concat!(env!("OUT_DIR"), "/tilemaps.rs"));
}
pub fn load_palettes(vram_manager: &mut VRamManager) {
vram_manager.set_background_palettes(backgrounds::PALETTES);
}
pub fn load_ui(map: &mut RegularMap, vram_manager: &mut VRamManager) {
let ui_tileset = TileSet::new(backgrounds::ui.tiles, TileFormat::FourBpp);
for y in 0..20u16 {
for x in 0..30u16 {
let tile_pos = y * 30 + x;
let tile_setting = tilemaps::UI_BACKGROUND_MAP[tile_pos as usize];
map.set_tile(vram_manager, (x, y).into(), &ui_tileset, tile_setting);
}
}
}
pub fn load_level_background(
map: &mut RegularMap,
vram_manager: &mut VRamManager,
level_number: usize,
) {
let level_map = &tilemaps::LEVELS_MAP[level_number];
let level_tileset = TileSet::new(backgrounds::level.tiles, TileFormat::FourBpp);
for y in 0..20u16 {
for x in 0..22u16 {
let tile_pos = y * 22 + x;
let tile_setting = level_map[tile_pos as usize];
map.set_tile(vram_manager, (x, y).into(), &level_tileset, tile_setting);
}
}
}
pub fn load_ending_page(map: &mut RegularMap, vram_manager: &mut VRamManager) {
let ending_tileset = TileSet::new(backgrounds::ending.tiles, TileFormat::FourBpp);
for y in 0..20u16 {
for x in 0..30u16 {
let tile_pos = y * 30 + x;
let tile_setting = TileSetting::new(
tile_pos,
false,
false,
backgrounds::ending.palette_assignments[tile_pos as usize],
);
map.set_tile(vram_manager, (x, y).into(), &ending_tileset, tile_setting);
}
}
}

View file

@ -0,0 +1,434 @@
use agb::{
display::{
object::{
OamIterator, ObjectTextRender, ObjectUnmanaged, PaletteVram, Size, SpriteLoader,
SpriteVram, TextAlignment,
},
palette16::Palette16,
tiled::{MapLoan, RegularMap, TiledMap, VRamManager},
HEIGHT,
},
fixnum::Vector2D,
input::{Button, ButtonController, Tri},
};
use crate::{
resources::{ARROW_RIGHT, FONT},
sfx::Sfx,
};
use self::{
game_state::{GameState, PLAY_AREA_HEIGHT, PLAY_AREA_WIDTH},
simulation::Simulation,
};
pub use simulation::Direction;
use core::{cell::RefCell, fmt::Write};
mod game_state;
mod simulation;
mod numbers;
struct Game<'a, 'b> {
phase: GamePhase<'a, 'b>,
}
struct Lament<'a, 'b> {
level: usize,
writer: RefCell<ObjectTextRender<'static>>,
background: &'a mut MapLoan<'b, RegularMap>,
}
fn generate_text_palette() -> PaletteVram {
let mut palette = [0x0; 16];
palette[1] = 0xFF_FF;
let palette = Palette16::new(palette);
PaletteVram::new(&palette).unwrap()
}
impl<'a, 'b> Lament<'a, 'b> {
fn new(level: usize, background: &'a mut MapLoan<'b, RegularMap>) -> Self {
let palette = generate_text_palette();
let mut writer = ObjectTextRender::new(&super::resources::FONT, Size::S16x16, palette);
let _ = writeln!(
writer,
"{}\n\n{}",
numbers::NUMBERS[level],
crate::level::Level::get_level(level).name
);
writer.layout(
Vector2D::new(
PLAY_AREA_WIDTH as i32 * 16 - 32,
PLAY_AREA_HEIGHT as i32 * 16,
),
TextAlignment::Center,
0,
);
Self {
level,
writer: RefCell::new(writer),
background,
}
}
fn update(self, input: &ButtonController, vram_manager: &mut VRamManager) -> GamePhase<'a, 'b> {
{
let mut writer = self.writer.borrow_mut();
writer.next_letter_group();
writer.update(Vector2D::new(16, HEIGHT / 4));
}
if input.is_just_pressed(Button::A) {
GamePhase::Construction(Construction::new(self.level, self.background, vram_manager))
} else {
GamePhase::Lament(self)
}
}
fn render(&self, oam: &mut OamIterator) {
self.writer.borrow_mut().commit(oam);
}
}
struct Construction<'a, 'b> {
game: GameState,
background: &'a mut MapLoan<'b, RegularMap>,
}
impl<'a, 'b> Drop for Construction<'a, 'b> {
fn drop(&mut self) {
self.background.hide();
}
}
impl<'a, 'b> Construction<'a, 'b> {
fn new(
level: usize,
background: &'a mut MapLoan<'b, RegularMap>,
vram_manager: &mut VRamManager,
) -> Self {
let game = GameState::new(level);
game.load_level_background(background, vram_manager);
background.commit(vram_manager);
background.show();
Self { background, game }
}
fn update(
mut self,
input: &ButtonController,
sfx: &mut Sfx,
loader: &mut SpriteLoader,
) -> GamePhase<'a, 'b> {
self.game.step(input, sfx);
if input.is_just_pressed(Button::START) {
self.game.force_place();
GamePhase::Execute(Execute::new(self, sfx, loader))
} else {
GamePhase::Construction(self)
}
}
fn render(&self, oam: &mut OamIterator, loader: &mut SpriteLoader) {
self.game.render(loader, oam);
}
}
impl<'a, 'b> Execute<'a, 'b> {
fn new(construction: Construction<'a, 'b>, sfx: &mut Sfx, loader: &mut SpriteLoader) -> Self {
Self {
simulation: construction.game.create_simulation(sfx, loader),
construction,
}
}
fn update(
mut self,
input: &ButtonController,
sfx: &mut Sfx,
loader: &mut SpriteLoader,
) -> GamePhase<'a, 'b> {
if input.is_just_pressed(Button::START) {
return GamePhase::Construction(self.construction);
}
match self.simulation.update(loader, sfx) {
simulation::Outcome::Continue => GamePhase::Execute(self),
simulation::Outcome::Loss => GamePhase::Construction(self.construction),
simulation::Outcome::Win => GamePhase::NextLevel,
}
}
fn render(&self, loader: &mut SpriteLoader, oam: &mut OamIterator) {
self.simulation.render(oam);
self.construction
.game
.render_arrows(loader, oam, Some(self.simulation.current_turn()));
}
}
struct Execute<'a, 'b> {
simulation: Simulation,
construction: Construction<'a, 'b>,
}
#[derive(Default)]
enum GamePhase<'a, 'b> {
#[default]
Empty,
Lament(Lament<'a, 'b>),
Construction(Construction<'a, 'b>),
Execute(Execute<'a, 'b>),
NextLevel,
}
impl GamePhase<'_, '_> {
fn update(
&mut self,
input: &ButtonController,
sfx: &mut Sfx,
loader: &mut SpriteLoader,
vram_manger: &mut VRamManager,
) {
*self = match core::mem::take(self) {
GamePhase::Lament(lament) => lament.update(input, vram_manger),
GamePhase::Construction(construction) => construction.update(input, sfx, loader),
GamePhase::Execute(execute) => execute.update(input, sfx, loader),
GamePhase::NextLevel => GamePhase::NextLevel,
GamePhase::Empty => panic!("bad state"),
}
}
fn render(&self, loader: &mut SpriteLoader, oam: &mut OamIterator) {
match self {
GamePhase::Empty => panic!("bad state"),
GamePhase::Lament(lament) => lament.render(oam),
GamePhase::Construction(construction) => construction.render(oam, loader),
GamePhase::Execute(execute) => execute.render(loader, oam),
GamePhase::NextLevel => {}
}
}
}
impl<'a, 'b> Game<'a, 'b> {
pub fn new(level: usize, background: &'a mut MapLoan<'b, RegularMap>) -> Self {
Self {
phase: GamePhase::Lament(Lament::new(level, background)),
}
}
pub fn update(
&mut self,
input: &ButtonController,
sfx: &mut Sfx,
loader: &mut SpriteLoader,
vram_manager: &mut VRamManager,
) -> bool {
self.phase.update(input, sfx, loader, vram_manager);
matches!(self.phase, GamePhase::NextLevel)
}
pub fn render(&self, loader: &mut SpriteLoader, oam: &mut OamIterator) {
self.phase.render(loader, oam)
}
pub fn hide_background(&mut self) {
match &mut self.phase {
GamePhase::Construction(construction) => construction.background.hide(),
GamePhase::Execute(execute) => execute.construction.background.hide(),
_ => {}
}
}
pub fn show_background(&mut self) {
match &mut self.phase {
GamePhase::Construction(construction) => construction.background.show(),
GamePhase::Execute(execute) => execute.construction.background.show(),
_ => {}
}
}
}
pub struct Pausable<'a, 'b> {
paused: Paused,
menu: PauseMenu,
game: Game<'a, 'b>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Paused {
Paused,
Playing,
}
impl Paused {
fn change(self) -> Paused {
match self {
Paused::Paused => Paused::Playing,
Paused::Playing => Paused::Paused,
}
}
}
#[derive(Clone, Copy)]
pub enum PauseSelection {
Restart,
LevelSelect(usize),
}
enum PauseSelectionInner {
Restart,
LevelSelect,
}
struct PauseMenu {
option_text: RefCell<[ObjectTextRender<'static>; 2]>,
selection: PauseSelectionInner,
indicator_sprite: SpriteVram,
selected_level: usize,
maximum_level: usize,
}
impl PauseMenu {
fn text_at_position(
text: core::fmt::Arguments,
position: Vector2D<i32>,
) -> ObjectTextRender<'static> {
let mut t = ObjectTextRender::new(&FONT, Size::S32x16, generate_text_palette());
let _ = writeln!(t, "{}", text);
t.layout(Vector2D::new(i32::MAX, i32::MAX), TextAlignment::Left, 0);
t.next_line();
t.update(position);
t
}
fn new(loader: &mut SpriteLoader, maximum_level: usize, current_level: usize) -> Self {
PauseMenu {
option_text: RefCell::new([
Self::text_at_position(format_args!("Restart"), Vector2D::new(32, HEIGHT / 4)),
Self::text_at_position(
format_args!("Go to level: {}", current_level + 1),
Vector2D::new(32, HEIGHT / 4 + 20),
),
]),
selection: PauseSelectionInner::Restart,
indicator_sprite: loader.get_vram_sprite(ARROW_RIGHT.sprite(0)),
selected_level: current_level,
maximum_level,
}
}
fn update(&mut self, input: &ButtonController) -> Option<PauseSelection> {
if input.is_just_pressed(Button::UP) | input.is_just_pressed(Button::DOWN) {
self.selection = match self.selection {
PauseSelectionInner::Restart => PauseSelectionInner::LevelSelect,
PauseSelectionInner::LevelSelect => PauseSelectionInner::Restart,
};
}
let lr = Tri::from((
input.is_just_pressed(Button::LEFT),
input.is_just_pressed(Button::RIGHT),
));
if matches!(self.selection, PauseSelectionInner::LevelSelect) && lr != Tri::Zero {
let selected_level = self.selected_level as i32;
let selected_level =
(selected_level + lr as i32).rem_euclid(self.maximum_level as i32 + 1);
self.selected_level = selected_level as usize;
self.option_text.borrow_mut()[1] = Self::text_at_position(
format_args!("Go to level: {}", selected_level + 1),
Vector2D::new(32, HEIGHT / 4 + 20),
)
}
if input.is_just_pressed(Button::A) | input.is_just_pressed(Button::START) {
Some(match self.selection {
PauseSelectionInner::Restart => PauseSelection::Restart,
PauseSelectionInner::LevelSelect => {
PauseSelection::LevelSelect(self.selected_level)
}
})
} else {
None
}
}
fn render(&self, oam: &mut OamIterator) {
for text in self.option_text.borrow_mut().iter_mut() {
text.commit(oam);
}
let mut indicator = ObjectUnmanaged::new(self.indicator_sprite.clone());
indicator.show();
match self.selection {
PauseSelectionInner::Restart => indicator.set_position(Vector2D::new(16, HEIGHT / 4)),
PauseSelectionInner::LevelSelect => {
indicator.set_position(Vector2D::new(16, HEIGHT / 4 + 20))
}
};
if let Some(slot) = oam.next() {
slot.set(&indicator);
}
}
}
pub enum UpdateResult {
MenuSelection(PauseSelection),
NextLevel,
}
impl<'a, 'b> Pausable<'a, 'b> {
pub fn new(
level: usize,
maximum_level: usize,
background: &'a mut MapLoan<'b, RegularMap>,
loader: &mut SpriteLoader,
) -> Self {
Self {
paused: Paused::Playing,
game: Game::new(level, background),
menu: PauseMenu::new(loader, maximum_level, level),
}
}
pub fn update(
&mut self,
input: &ButtonController,
sfx: &mut Sfx,
loader: &mut SpriteLoader,
vram_manager: &mut VRamManager,
) -> Option<UpdateResult> {
if input.is_just_pressed(Button::SELECT)
|| (matches!(self.paused, Paused::Paused) && input.is_just_pressed(Button::B))
{
self.paused = self.paused.change();
match self.paused {
Paused::Paused => self.game.hide_background(),
Paused::Playing => self.game.show_background(),
}
}
if !matches!(self.paused, Paused::Paused) {
if self.game.update(input, sfx, loader, vram_manager) {
Some(UpdateResult::NextLevel)
} else {
None
}
} else {
self.menu.update(input).map(UpdateResult::MenuSelection)
}
}
pub fn render(&self, loader: &mut SpriteLoader, oam: &mut OamIterator) {
if matches!(self.paused, Paused::Paused) {
self.menu.render(oam);
} else {
self.game.render(loader, oam);
}
}
}

View file

@ -0,0 +1,459 @@
use agb::{
display::{
object::{OamIterator, ObjectUnmanaged, SpriteLoader, Tag},
tiled::{RegularMap, VRamManager},
},
fixnum::Vector2D,
input::{Button, ButtonController, Tri},
};
use alloc::{vec, vec::Vec};
use crate::{
level::{Item, Level},
map::MapElement,
resources,
sfx::Sfx,
};
use super::simulation::{Direction, Simulation};
pub const PLAY_AREA_WIDTH: usize = 11;
pub const PLAY_AREA_HEIGHT: usize = 10;
const ITEM_AREA_WIDTH: usize = 3;
const ITEM_AREA_HEIGHT: usize = 3;
const ITEM_AREA_TOP_LEFT: Vector2D<i32> = Vector2D::new(179, 96);
const CURSOR_OFFSET: Vector2D<i32> = Vector2D::new(14, 14);
const ARROW_TOP_LEFT: Vector2D<i32> = Vector2D::new(175, 15);
pub struct GameState {
level_number: usize,
level: &'static Level,
cursor_state: CursorState,
frame: usize,
item_states: Vec<ItemState>,
}
impl GameState {
pub fn new(level_number: usize) -> Self {
let level = Level::get_level(level_number);
let position = level
.entities
.iter()
.find(|x| x.0 == Item::Hero)
.map(|hero| hero.1.x as usize + PLAY_AREA_WIDTH * hero.1.y as usize)
.unwrap_or(PLAY_AREA_WIDTH * PLAY_AREA_HEIGHT / 2 + PLAY_AREA_WIDTH / 2);
Self {
level_number,
level,
cursor_state: CursorState {
item_position: 0,
board_position: position,
current_place: CursorPlace::Item,
held_item: None,
},
frame: 0,
item_states: vec![ItemState::default(); level.items.len()],
}
}
pub fn level_text(&self) -> &str {
self.level.name
}
pub fn create_simulation(&self, sfx: &mut Sfx, loader: &mut SpriteLoader) -> Simulation {
Simulation::generate(
self.item_states
.iter()
.zip(self.level.items)
.filter_map(|(location, item)| match location {
ItemState::Placed(loc) => Some((*loc, *item)),
ItemState::NotPlaced => None,
})
.map(|(location, item)| {
(
item,
Vector2D::new(
(location % PLAY_AREA_WIDTH) as i32,
(location / PLAY_AREA_WIDTH) as i32,
),
)
})
.chain(self.level.entities.iter().map(|x| (x.0, x.1))),
self.level,
sfx,
loader,
)
}
pub fn load_level_background(&self, map: &mut RegularMap, vram_manager: &mut VRamManager) {
crate::backgrounds::load_level_background(map, vram_manager, self.level_number);
}
pub fn force_place(&mut self) {
if self.cursor_state.current_place == CursorPlace::Board {
let position_x = (self.cursor_state.board_position % PLAY_AREA_WIDTH) as i32;
let position_y = (self.cursor_state.board_position / PLAY_AREA_WIDTH) as i32;
let position: Vector2D<_> = (position_x, position_y).into();
let map_tile = self.level.map[(position_x, position_y)];
let fixed_item_at_location = self
.level
.entities
.iter()
.any(|entity| entity.1 == position);
if map_tile == MapElement::Floor && !fixed_item_at_location {
let placeable_item_at_location =
self.item_states.iter().position(|state| match state {
ItemState::Placed(position) => {
*position == self.cursor_state.board_position
}
ItemState::NotPlaced => false,
});
if placeable_item_at_location.is_none() {
if let Some(held_item) = self.cursor_state.held_item {
self.item_states[held_item] =
ItemState::Placed(self.cursor_state.board_position);
self.cursor_state.held_item = None;
}
}
}
}
}
pub fn step(&mut self, input: &ButtonController, sfx: &mut Sfx) {
self.frame = self.frame.wrapping_add(1);
self.cursor_state.update_position(input);
if input.is_just_pressed(Button::A) {
match self.cursor_state.current_place {
CursorPlace::Board => {
let position_x = (self.cursor_state.board_position % PLAY_AREA_WIDTH) as i32;
let position_y = (self.cursor_state.board_position / PLAY_AREA_WIDTH) as i32;
let position: Vector2D<_> = (position_x, position_y).into();
let map_tile = self.level.map[(position_x, position_y)];
let fixed_item_at_location = self
.level
.entities
.iter()
.any(|entity| entity.1 == position);
if map_tile == MapElement::Floor && !fixed_item_at_location {
let placeable_item_at_location =
self.item_states.iter().position(|state| match state {
ItemState::Placed(position) => {
*position == self.cursor_state.board_position
}
ItemState::NotPlaced => false,
});
let played_sound = if let Some(held_item) = self.cursor_state.held_item {
self.item_states[held_item] =
ItemState::Placed(self.cursor_state.board_position);
self.cursor_state.held_item = None;
sfx.place();
true
} else {
false
};
if let Some(placeable_item_at_location) = placeable_item_at_location {
self.cursor_state.held_item = Some(placeable_item_at_location);
self.item_states[placeable_item_at_location] = ItemState::NotPlaced;
if !played_sound {
sfx.select();
}
}
} else {
sfx.bad_selection();
}
}
CursorPlace::Item => {
let item_position = self.cursor_state.item_position;
if matches!(
self.item_states.get(item_position),
Some(ItemState::NotPlaced)
) {
sfx.select();
self.cursor_state.current_place = CursorPlace::Board;
self.cursor_state.held_item = Some(item_position);
} else {
sfx.bad_selection();
}
}
}
}
if input.is_just_pressed(Button::B) {
match self.cursor_state.current_place {
CursorPlace::Item => {
self.cursor_state.current_place = CursorPlace::Board;
self.cursor_state.held_item = None;
}
CursorPlace::Board => {
self.cursor_state.current_place = CursorPlace::Item;
self.cursor_state.held_item = None;
}
}
}
}
pub fn render_arrows(
&self,
loader: &mut SpriteLoader,
oam: &mut OamIterator,
current_turn: Option<usize>,
) {
let is_odd_frame = if current_turn.is_some() {
true
} else {
let frame_index = self.frame / 32;
frame_index % 2 == 1
};
for ((i, direction), slot) in self.level.directions.iter().enumerate().zip(oam) {
let x = (i % 4) as i32;
let y = (i / 4) as i32;
let arrow_position = ARROW_TOP_LEFT + (x * 15, y * 15).into();
let arrow_position = if is_odd_frame {
arrow_odd_frame_offset(*direction)
} else {
(0, 0).into()
} + arrow_position;
let sprite_idx = if Some(i) == current_turn { 1 } else { 0 };
let mut arrow_obj = ObjectUnmanaged::new(
loader.get_vram_sprite(arrow_for_direction(*direction).sprite(sprite_idx)),
);
arrow_obj.show().set_position(arrow_position);
slot.set(&arrow_obj);
}
}
pub fn render(&self, loader: &mut SpriteLoader, mut oam: &mut OamIterator) {
let frame_index = self.frame / 32;
let is_odd_frame = frame_index % 2 == 1;
let mut cursor_obj =
ObjectUnmanaged::new(loader.get_vram_sprite(resources::CURSOR.sprite(0)));
cursor_obj
.show()
.set_position(self.cursor_state.get_position(is_odd_frame));
if let Some(slot) = oam.next() {
slot.set(&cursor_obj);
}
let level = self.level;
self.render_arrows(loader, oam, None);
fn placed_position(position: usize, item: &Item) -> Vector2D<i32> {
let position_x = (position % PLAY_AREA_WIDTH) as i32;
let position_y = (position / PLAY_AREA_WIDTH) as i32;
let position = Vector2D::new(position_x, position_y);
position * 16 + item.map_entity_offset()
}
if let Some(held) = self.cursor_state.held_item {
let item = &level.items[held];
let item_position = placed_position(self.cursor_state.board_position, item);
let mut item_obj = ObjectUnmanaged::new(
loader.get_vram_sprite(item.tag().animation_sprite(frame_index)),
);
item_obj.show().set_position(item_position);
if let Some(slot) = oam.next() {
slot.set(&item_obj);
}
}
for ((item_position, item), slot) in level
.items
.iter()
.enumerate()
.filter_map(|(i, item)| {
let item_position = match self.item_states[i] {
ItemState::Placed(position) => placed_position(position, item),
ItemState::NotPlaced => {
if self.cursor_state.held_item == Some(i) {
return None;
} else {
let x = (i % ITEM_AREA_WIDTH) as i32;
let y = (i / ITEM_AREA_WIDTH) as i32;
ITEM_AREA_TOP_LEFT + (x * 16, y * 16).into()
}
}
};
Some((item_position, item))
})
.zip(&mut oam)
{
let mut item_obj = ObjectUnmanaged::new(
loader.get_vram_sprite(item.tag().animation_sprite(frame_index)),
);
item_obj.show().set_position(item_position);
slot.set(&item_obj);
}
for (entity, slot) in level.entities.iter().zip(&mut oam) {
let entity_position = entity.1 * 16 + entity.0.map_entity_offset();
let mut entity_obj =
ObjectUnmanaged::new(loader.get_vram_sprite(entity.0.shadow_tag().sprite(0)));
entity_obj.show().set_position(entity_position);
slot.set(&entity_obj);
}
}
}
struct CursorState {
item_position: usize,
board_position: usize,
current_place: CursorPlace,
held_item: Option<usize>,
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum CursorPlace {
Item,
Board,
}
impl CursorState {
fn get_position(&self, is_odd_frame: bool) -> Vector2D<i32> {
let odd_frame_offset = if is_odd_frame {
Vector2D::new(1, 1)
} else {
Vector2D::new(0, 0)
};
let place_position: Vector2D<_> = match self.current_place {
CursorPlace::Board => {
let current_x = (self.board_position % PLAY_AREA_WIDTH) as i32;
let current_y = (self.board_position / PLAY_AREA_WIDTH) as i32;
(current_x * 16, current_y * 16).into()
}
CursorPlace::Item => {
let current_x = (self.item_position % ITEM_AREA_WIDTH) as i32;
let current_y = (self.item_position / ITEM_AREA_WIDTH) as i32;
ITEM_AREA_TOP_LEFT + (current_x * 16, current_y * 16).into()
}
};
place_position + CURSOR_OFFSET + odd_frame_offset
}
fn update_position(&mut self, input: &ButtonController) {
let ud: Tri = (
input.is_just_pressed(Button::UP),
input.is_just_pressed(Button::DOWN),
)
.into();
let lr: Tri = (
input.is_just_pressed(Button::LEFT),
input.is_just_pressed(Button::RIGHT),
)
.into();
if ud == Tri::Zero && lr == Tri::Zero {
return;
}
match self.current_place {
CursorPlace::Board => {
let current_x = self.board_position % PLAY_AREA_WIDTH;
let current_y = self.board_position / PLAY_AREA_WIDTH;
let mut new_x = current_x.saturating_add_signed(lr as isize).max(1);
let new_y = current_y
.saturating_add_signed(ud as isize)
.max(1)
.min(PLAY_AREA_HEIGHT - 2);
if new_x == PLAY_AREA_WIDTH - 1 {
new_x = new_x.min(PLAY_AREA_WIDTH - 2);
if self.held_item.is_none() {
self.current_place = CursorPlace::Item;
self.item_position = new_y.saturating_sub(5).clamp(0, ITEM_AREA_HEIGHT - 1)
* ITEM_AREA_WIDTH;
}
}
self.board_position = new_x + new_y * PLAY_AREA_WIDTH;
}
CursorPlace::Item => {
let current_x = self.item_position % ITEM_AREA_WIDTH;
let current_y = self.item_position / ITEM_AREA_WIDTH;
let mut new_x = current_x.wrapping_add_signed(lr as isize);
let new_y = current_y
.saturating_add_signed(ud as isize)
.min(ITEM_AREA_HEIGHT - 1);
if new_x == usize::MAX {
new_x = 0;
self.current_place = CursorPlace::Board;
self.board_position = (new_y + 5) * PLAY_AREA_WIDTH + PLAY_AREA_WIDTH - 2;
} else {
new_x = new_x.min(ITEM_AREA_WIDTH - 1);
}
self.item_position = new_x + new_y * ITEM_AREA_WIDTH;
}
}
}
}
const fn arrow_for_direction(direction: Direction) -> &'static Tag {
match direction {
Direction::Up => resources::ARROW_UP,
Direction::Down => resources::ARROW_DOWN,
Direction::Left => resources::ARROW_LEFT,
Direction::Right => resources::ARROW_RIGHT,
}
}
const fn arrow_odd_frame_offset(direction: Direction) -> Vector2D<i32> {
match direction {
Direction::Up => Vector2D::new(0, -1),
Direction::Down => Vector2D::new(0, 1),
Direction::Left => Vector2D::new(-1, 0),
Direction::Right => Vector2D::new(1, 0),
}
}
#[derive(Default, Clone)]
enum ItemState {
Placed(usize),
#[default]
NotPlaced,
}

View file

@ -0,0 +1,22 @@
pub const NUMBERS: &[&str] = &[
"One",
"Two",
"Three",
"Four",
"Five",
"Six",
"Seven",
"Eight",
"Nine",
"Ten",
"Eleven",
"Twelve",
"Thirteen",
"Fourteen",
"Fifteen",
"Sixteen",
"Seventeen",
"Eighteen",
"Ninteen",
"Twenty",
];

View file

@ -0,0 +1,109 @@
use agb::{
display::object::{OamIterator, SpriteLoader},
fixnum::{Num, Vector2D},
};
use alloc::vec::Vec;
use crate::{
level::{Item, Level},
sfx::Sfx,
};
use self::{
animation::{Animation, RenderCache},
entity::{Action, EntityMap},
};
mod animation;
mod entity;
pub use entity::Direction;
pub use entity::Outcome;
pub struct Simulation {
entities: EntityMap,
animation: Animation,
level: &'static Level,
move_idx: usize,
outcome: Outcome,
frame: usize,
render_cache: Vec<RenderCache>,
}
impl Simulation {
pub fn generate(
a: impl Iterator<Item = (Item, Vector2D<i32>)>,
level: &'static Level,
sfx: &mut Sfx,
loader: &mut SpriteLoader,
) -> Simulation {
let mut entities = EntityMap::default();
let mut animation = Animation::default();
for (item, location) in a {
animation.populate(entities.add(item, location), sfx);
}
let mut simulation = Simulation {
entities,
animation,
move_idx: 0,
level,
outcome: Outcome::Continue,
frame: 0,
render_cache: Vec::new(),
};
simulation.cache_render(loader);
simulation
}
pub fn current_turn(&self) -> usize {
self.move_idx.saturating_sub(1)
}
pub fn render(&self, oam: &mut OamIterator) {
for item in self.render_cache.iter() {
item.render(oam);
}
}
pub fn cache_render(&mut self, sprite_loader: &mut SpriteLoader) {
self.render_cache = self.animation.cache_render(sprite_loader, self.frame / 16);
self.render_cache
.sort_unstable_by_key(|x| x.sorting_number());
}
pub fn update(&mut self, sprite_loader: &mut SpriteLoader, sfx: &mut Sfx) -> Outcome {
self.animation.increase_progress(Num::new(1) / 16);
self.frame = self.frame.wrapping_add(1);
let animation_result = self.animation.update(sfx);
self.cache_render(sprite_loader);
if animation_result {
if self.outcome != Outcome::Continue {
return self.outcome;
}
let hero_move = self.level.directions.get(self.move_idx);
if let Some(&hero_move) = hero_move {
let (outcome, animation) = self
.entities
.tick(&self.level.map, Action::Direction(hero_move));
self.move_idx += 1;
self.outcome = outcome;
for anim in animation {
self.animation.populate(anim, sfx);
}
} else {
return Outcome::Loss;
}
}
Outcome::Continue
}
}

View file

@ -0,0 +1,406 @@
#![deny(clippy::indexing_slicing)]
#![deny(clippy::panicking_unwrap)]
#![deny(clippy::panic_in_result_fn)]
use core::ops::{Deref, DerefMut};
use agb::{
display::object::{OamIterator, ObjectUnmanaged, SpriteLoader},
fixnum::{Num, Vector2D},
};
use alloc::vec::Vec;
use slotmap::SecondaryMap;
use crate::{
level::Item,
resources::HERO_CARRY,
sfx::{Sfx, SoundEffect},
};
use super::entity::{Direction, EntityKey};
struct AnimationEntity {
entity: Item,
start_position: Vector2D<Num<i32, 10>>,
rendered_position: Vector2D<Num<i32, 10>>,
attached: Option<(Item, Num<i32, 10>)>,
}
#[derive(Default)]
struct ToPlay {
moves: Vec<Move>,
attach_progress: Vec<AttachProgress>,
fakeout: Vec<FakeOutMove>,
detatch: Vec<Detatch>,
attach: Vec<Attach>,
change: Vec<Change>,
die: Vec<Die>,
}
fn convert_to_real_space(p: Vector2D<i32>) -> Vector2D<Num<i32, 10>> {
p.change_base() * 16
}
impl ToPlay {
pub fn populate(
&mut self,
instruction: AnimationInstruction,
map: &mut SecondaryMap<EntityKey, AnimationEntity>,
sfx: &mut Sfx<'_>,
) {
match instruction {
AnimationInstruction::Move(e, p, s) => {
self.moves.push(Move(e, convert_to_real_space(p), s));
}
AnimationInstruction::FakeOutMove(e, d, s) => self.fakeout.push(FakeOutMove(e, d, s)),
AnimationInstruction::Detatch(e, nk, s) => self.detatch.push(Detatch(e, nk, s)),
AnimationInstruction::Attach(e, o, s) => {
if let Some(entity_to_attach) = map.get(o) {
self.attach.push(Attach(e, entity_to_attach.entity, o, s))
}
}
AnimationInstruction::Die(e, s) => self.die.push(Die(e, s)),
AnimationInstruction::Add(e, item, p, s) => {
map.insert(
e,
AnimationEntity {
entity: item,
start_position: convert_to_real_space(p),
rendered_position: convert_to_real_space(p),
attached: None,
},
);
sfx.play_sound_effect(s);
}
AnimationInstruction::Change(e, i, s) => self.change.push(Change(e, i, s)),
AnimationInstruction::PriorityChange(e, i, s) => {
if let Some(entity) = map.get_mut(e) {
entity.entity = i;
sfx.play_sound_effect(s);
}
}
}
}
}
#[derive(Default)]
pub struct Animation {
map: Map,
to_play: ToPlay,
ease: Num<i32, 10>,
time: Num<i32, 10>,
}
#[derive(Default)]
struct Map {
map: SecondaryMap<EntityKey, AnimationEntity>,
}
fn attached_offset() -> Vector2D<Num<i32, 10>> {
Vector2D::new(0, -10).change_base()
}
pub struct RenderCache {
y: i32,
item: Item,
held: bool,
object: ObjectUnmanaged,
}
impl RenderCache {
pub fn render(&self, oam: &mut OamIterator) {
if let Some(slot) = oam.next() {
slot.set(&self.object);
}
}
pub fn sorting_number(&self) -> i32 {
let mut score = 0;
if matches!(
self.item,
Item::Stairs | Item::Switch | Item::SwitchPressed | Item::SpikesDown | Item::SpikesUp
) {
score += 100000;
}
if self.held {
score -= 10000;
}
if matches!(self.item, Item::Hero) {
score -= 1000;
}
score -= self.y;
score
}
}
impl Map {
fn set_entity_start_location(
&mut self,
entity: EntityKey,
destination: Vector2D<Num<i32, 10>>,
) {
if let Some(entity) = self.map.get_mut(entity) {
entity.rendered_position = destination;
entity.start_position = destination;
}
}
fn set_entity_to_start_location(&mut self, entity: EntityKey) {
if let Some(entity) = self.map.get_mut(entity) {
entity.rendered_position = entity.start_position;
}
}
}
impl Deref for Map {
type Target = SecondaryMap<EntityKey, AnimationEntity>;
fn deref(&self) -> &Self::Target {
&self.map
}
}
impl DerefMut for Map {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.map
}
}
impl Animation {
pub fn populate(&mut self, instruction: AnimationInstruction, sfx: &mut Sfx) {
self.to_play.populate(instruction, &mut self.map, sfx);
}
pub fn increase_progress(&mut self, amount_by: Num<i32, 10>) {
self.time += amount_by;
if self.time >= 1.into() {
self.time = 1.into();
}
let ease_in = self.time;
let sub = self.time - 1;
let ease_out = -sub * sub + 1;
self.ease = ease_in * (Num::new(1) - self.time) + ease_out * self.time;
}
pub fn cache_render(
&self,
sprite_loader: &mut SpriteLoader,
animation_frame: usize,
) -> Vec<RenderCache> {
let mut cache = Vec::new();
for (_, entity) in self.map.iter() {
if let Some((attached, attach_progress)) = entity.attached {
let mut object = ObjectUnmanaged::new(
sprite_loader.get_vram_sprite(attached.tag().animation_sprite(animation_frame)),
);
let pos = (entity.rendered_position + attached_offset() * attach_progress).floor()
+ attached.map_entity_offset();
object.show().set_position(pos);
cache.push(RenderCache {
object,
y: pos.y,
held: true,
item: attached,
});
}
let sprite = if entity.entity == Item::Hero && entity.attached.is_some() {
HERO_CARRY.animation_sprite(animation_frame)
} else {
entity.entity.shadow_tag().animation_sprite(animation_frame)
};
let mut object = ObjectUnmanaged::new(sprite_loader.get_vram_sprite(sprite));
let position = entity.rendered_position.floor() + entity.entity.map_entity_offset();
object.show().set_position(position);
cache.push(RenderCache {
object,
y: position.y,
held: false,
item: entity.entity,
});
}
cache
}
pub fn update(&mut self, sfx: &mut Sfx) -> bool {
if !self.to_play.moves.is_empty()
|| !self.to_play.fakeout.is_empty()
|| !self.to_play.attach_progress.is_empty()
{
if self.time >= 1.into() {
// finalise animations
for m in self.to_play.moves.drain(0..) {
let entity = m.0;
let destination = m.1;
self.map.set_entity_start_location(entity, destination);
}
for m in self.to_play.fakeout.drain(0..) {
let entity = m.0;
self.map.set_entity_to_start_location(entity);
}
for m in self.to_play.attach_progress.drain(0..) {
if let Some(ease) = self
.map
.get_mut(m.0)
.and_then(|x| x.attached.as_mut())
.map(|x| &mut x.1)
{
*ease = 1.into();
}
}
} else {
// play moves and fakeouts
for m in self.to_play.moves.iter_mut() {
let entity = m.0;
let destination = m.1;
sfx.play_sound_effect(m.2.take());
if let Some(entity) = self.map.get_mut(entity) {
let location = entity.start_position * (Num::<i32, 10>::new(1) - self.ease)
+ destination * self.ease;
entity.rendered_position = location;
}
}
for m in self.to_play.fakeout.iter_mut() {
let entity = m.0;
let direction = m.1;
let direction = convert_to_real_space(direction.into());
sfx.play_sound_effect(m.2.take());
let go_to = direction / 2;
let start = (self.ease * 2 - 1).abs();
let end_multiplier = -start + 1;
if let Some(entity) = self.map.get_mut(entity) {
let location = entity.start_position + go_to * end_multiplier;
entity.rendered_position = location;
}
}
for m in self.to_play.attach_progress.iter_mut() {
sfx.play_sound_effect(m.1.take());
if let Some(ease) = self
.map
.get_mut(m.0)
.and_then(|x| x.attached.as_mut())
.map(|x| &mut x.1)
{
*ease = self.ease;
}
}
}
} else if !self.to_play.detatch.is_empty() {
self.time = 0.into();
for detatch in self.to_play.detatch.drain(0..) {
let entity = detatch.0;
let new_key = detatch.1;
sfx.play_sound_effect(detatch.2);
if let Some((entity, attached)) = self
.map
.get_mut(entity)
.and_then(|x| x.attached.take().map(|y| (x, y)))
{
let position = entity.start_position + attached_offset();
let destination_position = entity.start_position;
self.map.insert(
new_key,
AnimationEntity {
entity: attached.0,
start_position: position,
rendered_position: position,
attached: None,
},
);
self.to_play
.moves
.push(Move(new_key, destination_position, None));
}
}
} else if !self.to_play.attach.is_empty() {
self.time = 0.into();
for attach in self.to_play.attach.drain(0..) {
let entity_to_attach_to = attach.0;
let other = attach.1;
sfx.play_sound_effect(attach.3);
if let Some(entity) = self.map.get_mut(entity_to_attach_to) {
entity.attached = Some((other, 0.into()));
}
self.map.remove(attach.2);
self.to_play
.attach_progress
.push(AttachProgress(entity_to_attach_to, None));
}
} else if !self.to_play.change.is_empty() {
self.time = 0.into();
for change in self.to_play.change.drain(0..) {
let entity = change.0;
let item = change.1;
sfx.play_sound_effect(change.2);
if let Some(entity) = self.map.get_mut(entity) {
entity.entity = item;
}
}
} else if !self.to_play.die.is_empty() {
self.time = 0.into();
for death in self.to_play.die.drain(0..) {
sfx.play_sound_effect(death.1);
let to_die = death.0;
self.map.remove(to_die);
}
} else {
self.time = 0.into();
return true;
}
false
}
}
struct Move(EntityKey, Vector2D<Num<i32, 10>>, Option<SoundEffect>);
struct FakeOutMove(EntityKey, Direction, Option<SoundEffect>);
struct Detatch(EntityKey, EntityKey, Option<SoundEffect>);
struct Attach(EntityKey, Item, EntityKey, Option<SoundEffect>);
struct AttachProgress(EntityKey, Option<SoundEffect>);
struct Die(EntityKey, Option<SoundEffect>);
struct Change(EntityKey, Item, Option<SoundEffect>);
#[derive(Clone, Debug)]
pub enum AnimationInstruction {
Add(EntityKey, Item, Vector2D<i32>, Option<SoundEffect>),
Move(EntityKey, Vector2D<i32>, Option<SoundEffect>),
FakeOutMove(EntityKey, Direction, Option<SoundEffect>),
Detatch(EntityKey, EntityKey, Option<SoundEffect>),
Attach(EntityKey, EntityKey, Option<SoundEffect>),
Change(EntityKey, Item, Option<SoundEffect>),
PriorityChange(EntityKey, Item, Option<SoundEffect>),
Die(EntityKey, Option<SoundEffect>),
}

View file

@ -0,0 +1,718 @@
#![deny(clippy::indexing_slicing)]
#![deny(clippy::panicking_unwrap)]
#![deny(clippy::panic_in_result_fn)]
use core::ops::Neg;
use agb::fixnum::Vector2D;
use alloc::{boxed::Box, vec::Vec};
use slotmap::{new_key_type, SlotMap};
use crate::{
level::{self},
map::{Map, MapElement},
sfx::SoundEffect,
};
use super::animation::AnimationInstruction;
new_key_type! { pub struct EntityKey; }
#[derive(Default)]
pub struct EntityMap {
map: SlotMap<EntityKey, Entity>,
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
pub enum Outcome {
Continue,
Loss,
Win,
}
impl EntityMap {
fn whats_at(&self, location: Vector2D<i32>) -> impl Iterator<Item = (EntityKey, &Entity)> {
self.map
.iter()
.filter(move |(_, entity)| entity.location == location)
}
// returns whether killing this is a loss
fn kill_entity(
&mut self,
entity: EntityKey,
animations: &mut Vec<AnimationInstruction>,
) -> bool {
if let Some((location, holding)) = self
.map
.get_mut(entity)
.and_then(|x| x.take_holding().map(|y| (x.location, y)))
{
let new_key = self.map.insert(Entity {
location,
entity: holding,
});
animations.push(AnimationInstruction::Detatch(
entity,
new_key,
self.map.get(new_key).and_then(|e| e.drop_effect()),
));
}
animations.push(AnimationInstruction::Die(
entity,
self.map.get(entity).and_then(|e| e.die_effect()),
));
if let Some(entity) = self.map.remove(entity) {
matches!(entity.entity, EntityType::Hero(_))
} else {
false
}
}
pub fn add(
&mut self,
entity: crate::level::Item,
location: Vector2D<i32>,
) -> AnimationInstruction {
let idx = self.map.insert(Entity {
location,
entity: entity.into(),
});
AnimationInstruction::Add(idx, entity, location, None)
}
pub fn tick(&mut self, map: &Map, hero: Action) -> (Outcome, Vec<AnimationInstruction>) {
let mut hero_has_died = false;
let mut win_has_triggered = false;
let mut animations = Vec::new();
let desired_actions: Vec<(EntityKey, Action)> = self
.map
.iter()
.map(|(key, entity)| (key, entity.desired_action(map, self, hero)))
.collect();
for (entity_key, action) in desired_actions {
if !self.map.contains_key(entity_key) {
continue;
}
match action {
Action::Nothing => {
// nothing does nothing and causes nothing to happen
}
Action::Direction(direction) | Action::ChangeDirection(direction) => {
if matches!(action, Action::ChangeDirection(_)) {
if let Some(change) = self
.map
.get_mut(entity_key)
.and_then(|e| e.change_direction())
{
animations.push(AnimationInstruction::PriorityChange(
entity_key,
change,
self.map.get(entity_key).and_then(|e| e.change_effect()),
));
}
}
let Some(entity) = &self.map.get(entity_key) else {
continue;
};
let desired_location = entity.location + direction.into();
let surface = map.get(desired_location);
if surface == MapElement::Wall {
let wall_resolution = resolve_wall_move(&entity.entity);
match wall_resolution {
WallResolution::Die => {
hero_has_died |= self.kill_entity(entity_key, &mut animations);
}
WallResolution::StayPut => {
animations.push(AnimationInstruction::FakeOutMove(
entity_key,
direction,
entity.fake_out_wall_effect(),
));
}
}
} else {
// what is at that location
let resolutions: Vec<_> = self
.whats_at(desired_location)
.filter(|(k, _)| *k != entity_key)
.map(|(key, other_entity)| (key, resolve_move(entity, other_entity)))
.collect();
let mut can_move = true;
let mut explicit_stay_put = false;
for (other, resolution) in resolutions {
match resolution {
MoveAttemptResolution::KillDie => {
hero_has_died |= self.kill_entity(other, &mut animations);
hero_has_died |= self.kill_entity(entity_key, &mut animations);
can_move = false;
}
MoveAttemptResolution::Kill => {
hero_has_died |= self.kill_entity(other, &mut animations);
can_move = false;
}
MoveAttemptResolution::Die => {
hero_has_died |= self.kill_entity(entity_key, &mut animations);
can_move = false;
}
MoveAttemptResolution::CoExist => {}
MoveAttemptResolution::StayPut => {
can_move = false;
explicit_stay_put = true;
}
}
}
if can_move {
if let Some(e) = self.map.get_mut(entity_key) {
e.location = desired_location;
}
let Some(entity) = &self.map.get(entity_key) else {
continue;
};
animations.push(AnimationInstruction::Move(
entity_key,
desired_location,
entity.move_effect(),
));
let overlap_resolutions: Vec<_> = self
.whats_at(desired_location)
.filter(|(k, _)| *k != entity_key)
.map(|(key, other_entity)| {
(key, resolve_overlap(entity, other_entity))
})
.collect();
if overlap_resolutions
.iter()
.filter(|(_, r)| *r == OverlapResolution::Die)
.count()
!= 0
{
hero_has_died |= self.kill_entity(entity_key, &mut animations);
} else {
for (other, resolution) in overlap_resolutions {
match resolution {
OverlapResolution::Pickup => {
animations.push(AnimationInstruction::Attach(
entity_key,
other,
self.map
.get(other)
.and_then(|x| x.pickup_sound_effect()),
));
let other = self.map.remove(other).unwrap();
if let Some((location, dropped)) =
self.map.get_mut(entity_key).and_then(|x| {
x.pickup(other.entity).map(|y| (x.location, y))
})
{
let new_key = self.map.insert(Entity {
location,
entity: dropped,
});
animations.push(AnimationInstruction::Detatch(
entity_key,
new_key,
self.map
.get(new_key)
.and_then(|x| x.drop_effect()),
));
}
}
OverlapResolution::CoExist => {}
OverlapResolution::Win => {
win_has_triggered = true;
}
OverlapResolution::ToggleSystem(system) => {
for (k, e) in self.map.iter_mut() {
if let Some(change) = e.switch(system) {
animations.push(AnimationInstruction::Change(
k,
change,
e.change_effect(),
));
}
}
}
OverlapResolution::Die => {
// already handled
}
}
}
}
} else {
animations.push(AnimationInstruction::FakeOutMove(
entity_key,
direction,
if explicit_stay_put {
self.map
.get(entity_key)
.and_then(|e| e.fake_out_wall_effect())
} else {
self.map.get(entity_key).and_then(|e| e.fake_out_effect())
},
));
}
}
}
}
}
(
if hero_has_died {
Outcome::Loss
} else if win_has_triggered {
Outcome::Win
} else {
Outcome::Continue
},
animations,
)
}
}
enum MoveAttemptResolution {
Kill,
Die,
KillDie,
CoExist,
StayPut,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct SwitchSystem(usize);
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
enum OverlapResolution {
Pickup,
CoExist,
Win,
ToggleSystem(SwitchSystem),
Die,
}
enum WallResolution {
Die,
StayPut,
}
fn resolve_spikes(switable: &Switchable) -> OverlapResolution {
if switable.active {
OverlapResolution::Die
} else {
OverlapResolution::CoExist
}
}
fn resolve_overlap(me: &Entity, other: &Entity) -> OverlapResolution {
match (&me.entity, &other.entity) {
(EntityType::Hero(_), EntityType::Stairs) => OverlapResolution::Win,
(_, EntityType::Item(_)) => OverlapResolution::Pickup,
(_, EntityType::Spikes(switch)) => resolve_spikes(switch),
(_, EntityType::Switch(switch)) => OverlapResolution::ToggleSystem(switch.system),
(_, EntityType::Enemy(_) | EntityType::Hero(_)) => OverlapResolution::Die,
_ => OverlapResolution::CoExist,
}
}
fn resolve_wall_move(entity: &EntityType) -> WallResolution {
WallResolution::StayPut
}
fn holding_attack_resolve(holding: Option<&EntityType>) -> MoveAttemptResolution {
match holding {
Some(&EntityType::Item(Item::Sword)) => MoveAttemptResolution::Kill,
_ => MoveAttemptResolution::CoExist,
}
}
fn squid_holding_attack_resolve(me: &Squid, other: &Entity) -> MoveAttemptResolution {
match (me.holding.as_deref(), &other.entity, other.holding()) {
(
Some(&EntityType::Item(Item::Sword)),
EntityType::Enemy(Enemy::Squid(squid)),
Some(&EntityType::Item(Item::Sword)),
) => {
if squid.direction == -me.direction {
MoveAttemptResolution::KillDie
} else {
MoveAttemptResolution::Kill
}
}
(Some(&EntityType::Item(Item::Sword)), EntityType::Enemy(_), None) => {
MoveAttemptResolution::Kill
}
(_, EntityType::Enemy(Enemy::Squid(squid)), Some(&EntityType::Item(Item::Sword))) => {
if squid.direction == -me.direction {
MoveAttemptResolution::Die
} else {
MoveAttemptResolution::StayPut
}
}
(_, EntityType::Enemy(_), _) => MoveAttemptResolution::StayPut,
(_, EntityType::Hero(_), _) => MoveAttemptResolution::Kill,
_ => MoveAttemptResolution::CoExist,
}
}
fn holding_door_resolve(holding: Option<&EntityType>) -> MoveAttemptResolution {
match holding {
Some(&EntityType::Item(Item::Key)) => MoveAttemptResolution::Kill,
_ => MoveAttemptResolution::StayPut,
}
}
fn switch_door_resolve(door: &Switchable) -> MoveAttemptResolution {
if door.active {
MoveAttemptResolution::CoExist
} else {
MoveAttemptResolution::StayPut
}
}
fn resolve_move(mover: &Entity, into: &Entity) -> MoveAttemptResolution {
match (&mover.entity, &into.entity) {
(EntityType::Hero(hero), EntityType::Hero(_) | EntityType::Enemy(_)) => {
holding_attack_resolve(hero.holding.as_deref())
}
(EntityType::Hero(hero), EntityType::Door) => holding_door_resolve(hero.holding.as_deref()),
(EntityType::Enemy(Enemy::Squid(squid)), EntityType::Hero(_) | EntityType::Enemy(_)) => {
squid_holding_attack_resolve(squid, into)
}
(EntityType::Enemy(_), EntityType::Hero(_) | EntityType::Enemy(_)) => {
MoveAttemptResolution::Kill
}
(_, EntityType::SwitchedDoor(door)) => switch_door_resolve(door),
(EntityType::Enemy(Enemy::Squid(squid)), EntityType::Door) => {
holding_door_resolve(squid.holding.as_deref())
}
(_, EntityType::Door) => MoveAttemptResolution::StayPut,
(_, _) => MoveAttemptResolution::CoExist,
}
}
#[derive(Debug)]
pub struct Hero {
holding: Option<Box<EntityType>>,
}
pub struct Entity {
location: Vector2D<i32>,
entity: EntityType,
}
#[derive(Debug)]
pub struct Switchable {
system: SwitchSystem,
active: bool,
}
#[derive(Debug)]
pub enum EntityType {
Hero(Hero),
Item(Item),
Enemy(Enemy),
Stairs,
Door,
SwitchedDoor(Switchable),
Switch(Switchable),
Spikes(Switchable),
}
#[derive(Debug)]
pub struct Squid {
direction: Direction,
holding: Option<Box<EntityType>>,
}
#[derive(Debug)]
pub enum Enemy {
Slime,
Squid(Squid),
}
#[derive(Debug)]
pub enum Item {
Sword,
Key,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
impl Neg for Direction {
type Output = Direction;
fn neg(self) -> Self::Output {
match self {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
}
}
}
impl From<Direction> for Vector2D<i32> {
fn from(val: Direction) -> Self {
(&val).into()
}
}
impl From<&Direction> for Vector2D<i32> {
fn from(val: &Direction) -> Self {
match val {
Direction::Up => (0, -1),
Direction::Down => (0, 1),
Direction::Left => (-1, 0),
Direction::Right => (1, 0),
}
.into()
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Action {
Nothing,
Direction(Direction),
ChangeDirection(Direction),
}
impl Entity {
fn desired_action(&self, walls: &Map, entities: &EntityMap, hero_action: Action) -> Action {
match &self.entity {
EntityType::Hero(_) => hero_action,
EntityType::Enemy(Enemy::Squid(squid)) => {
let desired_location = self.location + squid.direction.into();
let wall = walls.get(desired_location);
if matches!(wall, MapElement::Wall) {
Action::ChangeDirection(-squid.direction)
} else {
let can_move = entities
.whats_at(desired_location)
.map(|(_, other_entity)| resolve_move(self, other_entity))
.filter(|resolution| matches!(resolution, MoveAttemptResolution::StayPut))
.count()
== 0;
if can_move {
Action::Direction(squid.direction)
} else {
Action::ChangeDirection(-squid.direction)
}
}
}
_ => Action::Nothing,
}
}
fn pickup(&mut self, item: EntityType) -> Option<EntityType> {
let holding = match &mut self.entity {
EntityType::Hero(hero) => &mut hero.holding,
EntityType::Enemy(Enemy::Squid(squid)) => &mut squid.holding,
_ => panic!("this entity can't pick up things"),
};
let existing = core::mem::replace(holding, Some(Box::new(item)));
existing.map(|x| *x)
}
fn take_holding(&mut self) -> Option<EntityType> {
match &mut self.entity {
EntityType::Hero(hero) => hero.holding.take().map(|x| *x),
EntityType::Enemy(Enemy::Squid(squid)) => squid.holding.take().map(|x| *x),
_ => None,
}
}
fn holding(&self) -> Option<&EntityType> {
match &self.entity {
EntityType::Hero(hero) => hero.holding.as_deref(),
EntityType::Enemy(Enemy::Squid(squid)) => squid.holding.as_deref(),
_ => None,
}
}
fn die_effect(&self) -> Option<SoundEffect> {
match &self.entity {
EntityType::Hero(_) => Some(SoundEffect::HeroDie),
EntityType::Door => Some(SoundEffect::DoorOpen),
EntityType::Enemy(Enemy::Slime) => Some(SoundEffect::SlimeDie),
EntityType::Enemy(Enemy::Squid(_)) => Some(SoundEffect::SquidDie),
_ => None,
}
}
fn drop_effect(&self) -> Option<SoundEffect> {
match &self.entity {
EntityType::Item(Item::Key) => Some(SoundEffect::KeyDrop),
EntityType::Item(Item::Sword) => Some(SoundEffect::SwordDrop),
_ => None,
}
}
fn move_effect(&self) -> Option<SoundEffect> {
None
}
fn kill_sound_effect(&self) -> Option<SoundEffect> {
None
}
fn change_effect(&self) -> Option<SoundEffect> {
match &self.entity {
EntityType::Switch(_) => Some(SoundEffect::SwitchToggle),
EntityType::SwitchedDoor(_) => Some(SoundEffect::SwitchedDoorToggle),
EntityType::Spikes(_) => Some(SoundEffect::SpikesToggle),
_ => None,
}
}
fn fake_out_wall_effect(&self) -> Option<SoundEffect> {
match &self.entity {
EntityType::Hero(_) => Some(SoundEffect::WallHit),
_ => None,
}
}
fn pickup_sound_effect(&self) -> Option<SoundEffect> {
match &self.entity {
EntityType::Item(Item::Key) => Some(SoundEffect::KeyPickup),
EntityType::Item(Item::Sword) => Some(SoundEffect::SwordPickup),
_ => None,
}
}
fn fake_out_effect(&self) -> Option<SoundEffect> {
None
}
fn change_direction(&mut self) -> Option<level::Item> {
match &mut self.entity {
EntityType::Enemy(Enemy::Squid(squid)) => {
squid.direction = -squid.direction;
if squid.direction == Direction::Up {
Some(level::Item::SquidUp)
} else {
Some(level::Item::SquidDown)
}
}
_ => None,
}
}
fn switch(&mut self, system: SwitchSystem) -> Option<level::Item> {
if let EntityType::SwitchedDoor(door) = &mut self.entity {
if door.system == system {
door.active = !door.active;
return Some(if door.active {
level::Item::SwitchedOpenDoor
} else {
level::Item::SwitchedClosedDoor
});
}
}
if let EntityType::Switch(switch) = &mut self.entity {
if switch.system == system {
switch.active = !switch.active;
return Some(if switch.active {
level::Item::SwitchPressed
} else {
level::Item::Switch
});
}
}
if let EntityType::Spikes(switch) = &mut self.entity {
if switch.system == system {
switch.active = !switch.active;
return Some(if switch.active {
level::Item::SpikesUp
} else {
level::Item::SpikesDown
});
}
}
None
}
}
impl From<level::Entity> for Entity {
fn from(value: level::Entity) -> Self {
Entity {
location: value.1,
entity: value.0.into(),
}
}
}
impl From<level::Item> for EntityType {
fn from(value: level::Item) -> Self {
match value {
level::Item::Hero => EntityType::Hero(Hero { holding: None }),
level::Item::Slime => EntityType::Enemy(Enemy::Slime),
level::Item::Stairs => EntityType::Stairs,
level::Item::Sword => EntityType::Item(Item::Sword),
level::Item::Door => EntityType::Door,
level::Item::Key => EntityType::Item(Item::Key),
level::Item::SwitchedOpenDoor => EntityType::SwitchedDoor(Switchable {
system: SwitchSystem(0),
active: true,
}),
level::Item::SwitchedClosedDoor => EntityType::SwitchedDoor(Switchable {
system: SwitchSystem(0),
active: false,
}),
level::Item::Switch => EntityType::Switch(Switchable {
system: SwitchSystem(0),
active: false,
}),
level::Item::SwitchPressed => EntityType::Switch(Switchable {
system: SwitchSystem(0),
active: true,
}),
level::Item::SpikesUp => EntityType::Spikes(Switchable {
system: SwitchSystem(0),
active: true,
}),
level::Item::SpikesDown => EntityType::Spikes(Switchable {
system: SwitchSystem(0),
active: false,
}),
level::Item::SquidUp => EntityType::Enemy(Enemy::Squid(Squid {
direction: Direction::Up,
holding: None,
})),
level::Item::SquidDown => EntityType::Enemy(Enemy::Squid(Squid {
direction: Direction::Down,
holding: None,
})),
}
}
}

View file

@ -0,0 +1,126 @@
use agb::{display::object::Tag, fixnum::Vector2D};
use crate::{game::Direction, map::Map, resources};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Item {
Sword,
Slime,
Hero,
Stairs,
Door,
Key,
SwitchedOpenDoor,
SwitchedClosedDoor,
Switch,
SwitchPressed,
SpikesUp,
SpikesDown,
SquidUp,
SquidDown,
}
impl Item {
pub fn shadow_tag(&self) -> &'static Tag {
match self {
Item::Sword => resources::SWORD_SHADOW,
Item::Slime => resources::SLIME_SHADOW,
Item::Hero => resources::HERO,
Item::Stairs => resources::STAIRS,
Item::Door => resources::DOOR,
Item::Key => resources::KEY_SHADOW,
Item::SwitchedOpenDoor => resources::SWITCHED_DOOR_OPEN,
Item::SwitchedClosedDoor => resources::SWITCHED_DOOR_CLOSED,
Item::Switch => resources::BUTTON_OFF,
Item::SwitchPressed => resources::BUTTON_ON,
Item::SpikesUp => resources::SPIKES_ON,
Item::SpikesDown => resources::SPIKES_OFF,
Item::SquidUp => resources::SQUID_UP_SHADOW,
Item::SquidDown => resources::SQUID_DOWN_SHADOW,
}
}
pub fn tag(&self) -> &'static Tag {
match self {
Item::Sword => resources::SWORD,
Item::Slime => resources::SLIME,
Item::Hero => resources::HERO,
Item::Stairs => resources::STAIRS,
Item::Door => resources::DOOR,
Item::Key => resources::KEY,
Item::SwitchedOpenDoor => resources::SWITCHED_DOOR_OPEN,
Item::SwitchedClosedDoor => resources::SWITCHED_DOOR_CLOSED,
Item::Switch => resources::BUTTON_OFF,
Item::SwitchPressed => resources::BUTTON_ON,
Item::SpikesUp => resources::SPIKES_ON,
Item::SpikesDown => resources::SPIKES_OFF,
Item::SquidUp => resources::SQUID_UP,
Item::SquidDown => resources::SQUID_DOWN,
}
}
pub fn map_entity_offset(&self) -> Vector2D<i32> {
const STANDARD: Vector2D<i32> = Vector2D::new(0, -3);
const ZERO: Vector2D<i32> = Vector2D::new(0, 0);
match self {
Item::Sword => STANDARD,
Item::Slime => STANDARD,
Item::Hero => STANDARD,
Item::Stairs => ZERO,
Item::Door => ZERO,
Item::Key => STANDARD,
Item::SwitchedOpenDoor => ZERO,
Item::SwitchedClosedDoor => ZERO,
Item::Switch => ZERO,
Item::SwitchPressed => ZERO,
Item::SpikesUp => ZERO,
Item::SpikesDown => ZERO,
Item::SquidUp => STANDARD,
Item::SquidDown => STANDARD,
}
}
}
pub struct Entity(pub Item, pub Vector2D<i32>);
pub struct Level {
pub map: Map<'static>,
pub entities: &'static [Entity],
pub directions: &'static [Direction],
pub items: &'static [Item],
pub name: &'static str,
}
impl Level {
const fn new(
map: Map<'static>,
entities: &'static [Entity],
directions: &'static [Direction],
items: &'static [Item],
name: &'static str,
) -> Self {
Self {
map,
entities,
directions,
items,
name,
}
}
pub const fn get_level(level_number: usize) -> &'static Level {
&levels::LEVELS[level_number]
}
pub const fn num_levels() -> usize {
levels::LEVELS.len()
}
}
mod levels {
use super::*;
use agb::fixnum::Vector2D;
include!(concat!(env!("OUT_DIR"), "/levels.rs"));
}

View file

@ -0,0 +1,186 @@
#![no_std]
#![no_main]
#![cfg_attr(test, feature(custom_test_frameworks))]
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
use agb::{
display::{
object::{OamIterator, OamUnmanaged, SpriteLoader},
tiled::{RegularBackgroundSize, TileFormat, TiledMap, VRamManager},
Priority,
},
input::{Button, ButtonController},
interrupt::VBlank,
sound::mixer::Frequency,
};
use game::{Pausable, PauseSelection};
use sfx::Sfx;
extern crate alloc;
mod backgrounds;
mod level;
mod map;
mod resources;
mod sfx;
mod game;
mod save;
struct Agb<'gba> {
vblank: VBlank,
input: ButtonController,
loader: SpriteLoader,
sfx: Sfx<'gba>,
vram: VRamManager,
oam: OamUnmanaged<'gba>,
}
impl<'gba> Agb<'gba> {
fn frame<D, U, T, F>(&mut self, data: &mut D, update: U, render: F) -> T
where
U: FnOnce(
&mut D,
&ButtonController,
&mut SpriteLoader,
&mut Sfx<'gba>,
&mut VRamManager,
) -> T,
F: FnOnce(&D, &mut OamIterator, &mut SpriteLoader),
{
self.vblank.wait_for_vblank();
self.input.update();
{
render(data, &mut self.oam.iter(), &mut self.loader);
}
self.sfx.frame();
update(
data,
&self.input,
&mut self.loader,
&mut self.sfx,
&mut self.vram,
)
}
}
pub fn entry(mut gba: agb::Gba) -> ! {
let vblank = VBlank::get();
let _ = save::init_save(&mut gba);
let (tiled, mut vram) = gba.display.video.tiled0();
let mut ui_bg = tiled.background(
Priority::P0,
RegularBackgroundSize::Background32x32,
TileFormat::FourBpp,
);
let mut level_bg = tiled.background(
Priority::P1,
RegularBackgroundSize::Background32x32,
TileFormat::FourBpp,
);
let mut ending_bg = tiled.background(
Priority::P0,
RegularBackgroundSize::Background32x32,
TileFormat::FourBpp,
);
backgrounds::load_ending_page(&mut ending_bg, &mut vram);
ending_bg.commit(&mut vram);
backgrounds::load_palettes(&mut vram);
backgrounds::load_ui(&mut ui_bg, &mut vram);
ui_bg.commit(&mut vram);
ui_bg.show();
let (unmanaged, sprite_loader) = gba.display.object.get_unmanaged();
let mut input = agb::input::ButtonController::new();
input.update();
if input.is_pressed(Button::START | Button::SELECT | Button::L | Button::R) {
let _ = save::save_max_level(&mut gba.save, 0);
}
let mut mixer = gba.mixer.mixer(Frequency::Hz18157);
let sfx = Sfx::new(&mut mixer);
let mut g = Agb {
vblank,
input,
loader: sprite_loader,
sfx,
vram,
oam: unmanaged,
};
let mut current_level = 0;
let mut maximum_level = save::load_max_level() as usize;
loop {
if current_level >= level::Level::num_levels() {
current_level = 0;
ui_bg.hide();
level_bg.hide();
ending_bg.show();
loop {
if g.frame(
&mut (),
|_, input, _, _, _| input.is_just_pressed(Button::SELECT),
|_, _, _| {},
) {
break;
}
}
ui_bg.show();
ending_bg.hide();
} else {
if current_level > maximum_level {
maximum_level = current_level;
let _ = save::save_max_level(&mut gba.save, maximum_level as u32);
}
let mut game = g.frame(
&mut (),
|_, _, loader, _, _| {
Pausable::new(current_level, maximum_level, &mut level_bg, loader)
},
|_, _, _| {},
);
loop {
if let Some(option) = g.frame(
&mut game,
|game, input, loader, sfx, vram| game.update(input, sfx, loader, vram),
|game, oam, loader| game.render(loader, oam),
) {
match option {
game::UpdateResult::MenuSelection(PauseSelection::Restart) => break,
game::UpdateResult::MenuSelection(PauseSelection::LevelSelect(level)) => {
current_level = level;
break;
}
game::UpdateResult::NextLevel => {
current_level += 1;
break;
}
}
}
}
}
}
}
#[cfg(test)]
#[agb::entry]
fn agb_test_main(gba: agb::Gba) -> ! {
loop {
// full implementation provided by the #[entry]
agb::syscall::halt();
}
}

View file

@ -0,0 +1,10 @@
#![no_std]
#![no_main]
#![cfg_attr(test, feature(custom_test_frameworks))]
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
#[agb::entry]
fn main(gba: agb::Gba) -> ! {
the_dungeon_keepers_lament::entry(gba);
}

View file

@ -0,0 +1,83 @@
use core::ops::Index;
use agb::fixnum::Vector2D;
#[derive(Debug)]
pub struct Map<'map> {
width: usize,
height: usize,
data: &'map [u8],
}
impl<'map> Map<'map> {
pub const fn new(width: usize, height: usize, data: &'map [u8]) -> Self {
assert!((width * height + 7) / 8 == data.len());
Self {
width,
height,
data,
}
}
pub const fn get(&self, index: Vector2D<i32>) -> MapElement {
let (x, y) = (index.x, index.y);
if x > self.width as i32 || y > self.height as i32 {
MapElement::Wall
} else {
let position = x as usize + y as usize * self.width;
let index = position / 8;
let bit = position % 8;
if (self.data[index] & (1 << bit)) != 0 {
MapElement::Wall
} else {
MapElement::Floor
}
}
}
}
impl Index<(i32, i32)> for Map<'_> {
type Output = MapElement;
fn index(&self, index: (i32, i32)) -> &Self::Output {
&self[Into::<Vector2D<i32>>::into(index)]
}
}
impl Index<Vector2D<i32>> for Map<'_> {
type Output = MapElement;
fn index(&self, index: Vector2D<i32>) -> &Self::Output {
const WALL: MapElement = MapElement::Wall;
const FLOOR: MapElement = MapElement::Floor;
match self.get(index) {
MapElement::Wall => &WALL,
MapElement::Floor => &FLOOR,
}
}
}
#[derive(Default, PartialEq, Eq, Debug, Clone, Copy)]
pub enum MapElement {
#[default]
Wall,
Floor,
}
#[cfg(test)]
mod tests {
use super::*;
#[test_case]
fn check_default_is_correct(_: &mut agb::Gba) {
let map = Map {
width: 0,
height: 0,
data: &[],
};
assert_eq!(map[(-1, -1)], MapElement::Wall);
}
}

View file

@ -0,0 +1,55 @@
use agb::{
display::{object::Graphics, Font},
include_aseprite, include_font,
};
const SPRITES: &Graphics = include_aseprite!(
"gfx/sprites16x16.aseprite",
"gfx/sprites8x8.aseprite",
"gfx/countdown.aseprite"
);
macro_rules! named_tag {
(
$sprites:ident, [
$($name:tt),+ $(,)?
] $(,)?
) => {
$(
pub const $name: &agb::display::object::Tag = $sprites.tags().get(stringify!($name));
)+
};
}
named_tag!(
SPRITES,
[
SWORD,
SWORD_SHADOW,
SLIME,
SLIME_SHADOW,
STAIRS,
HERO,
HERO_CARRY,
ARROW_LEFT,
ARROW_RIGHT,
ARROW_UP,
ARROW_DOWN,
CURSOR,
KEY,
KEY_SHADOW,
DOOR,
SWITCHED_DOOR_CLOSED,
SWITCHED_DOOR_OPEN,
SPIKES_ON,
SPIKES_OFF,
BUTTON_ON,
BUTTON_OFF,
SQUID_UP,
SQUID_DOWN,
SQUID_UP_SHADOW,
SQUID_DOWN_SHADOW,
]
);
pub const FONT: Font = include_font!("fnt/yoster.ttf", 12);

View file

@ -0,0 +1,46 @@
use agb::{
save::{Error, SaveManager},
sync::Static,
Gba,
};
static MAXIMUM_LEVEL: Static<u32> = Static::new(0);
pub fn init_save(gba: &mut Gba) -> Result<(), Error> {
gba.save.init_sram();
let mut access = gba.save.access()?;
let mut buffer = [0; 1];
access.read(0, &mut buffer)?;
if buffer[0] != 0 {
access.prepare_write(0..1)?.write(0, &[0])?;
core::mem::drop(access);
save_max_level(&mut gba.save, 0)?;
} else {
let mut buffer = [0; 4];
access.read(1, &mut buffer)?;
let max_level = u32::from_le_bytes(buffer);
if max_level > 100 {
MAXIMUM_LEVEL.write(0)
} else {
MAXIMUM_LEVEL.write(max_level)
}
}
Ok(())
}
pub fn load_max_level() -> u32 {
MAXIMUM_LEVEL.read()
}
pub fn save_max_level(save: &mut SaveManager, level: u32) -> Result<(), Error> {
save.access()?
.prepare_write(1..5)?
.write(1, &level.to_le_bytes())?;
MAXIMUM_LEVEL.write(level);
Ok(())
}

View file

@ -0,0 +1,104 @@
use agb::{
include_wav,
sound::mixer::{Mixer, SoundChannel},
};
const BGM: &[u8] = include_wav!("sfx/bgm.wav");
const BAD_SELECTION: &[u8] = include_wav!("sfx/bad.wav");
const SELECT: &[u8] = include_wav!("sfx/select.wav");
const PLACE: &[u8] = include_wav!("sfx/place.wav");
const BANG: &[u8] = include_wav!("sfx/bang.wav");
const SWORD_HIT: &[u8] = include_wav!("sfx/sword_hit.wav");
const SLIME_DEATH: &[u8] = include_wav!("sfx/slime_death.wav");
const SWORD_PICKUP: &[u8] = include_wav!("sfx/sword_pickup.wav");
const WALL_HIT: &[u8] = include_wav!("sfx/wall_hit.wav");
const DOOR_OPEN: &[u8] = include_wav!("sfx/door_open.wav");
const SWICTH_TOGGLES: &[&[u8]] = &[include_wav!("sfx/switch_toggle1.wav")];
pub struct Sfx<'a> {
mixer: &'a mut Mixer<'a>,
}
impl<'a> Sfx<'a> {
pub fn new(mixer: &'a mut Mixer<'a>) -> Self {
let mut bgm_channel = SoundChannel::new_high_priority(BGM);
bgm_channel.stereo().should_loop();
mixer.play_sound(bgm_channel);
mixer.enable();
Self { mixer }
}
pub fn frame(&mut self) {
self.mixer.frame();
}
pub fn bad_selection(&mut self) {
self.mixer.play_sound(SoundChannel::new(BAD_SELECTION));
}
pub fn select(&mut self) {
self.mixer.play_sound(SoundChannel::new(SELECT));
}
pub fn place(&mut self) {
self.mixer.play_sound(SoundChannel::new(PLACE));
}
pub fn play_sound_effect(&mut self, effect: Option<SoundEffect>) {
if let Some(effect) = effect {
match effect {
SoundEffect::DoorFail | SoundEffect::WallHit => {
self.mixer.play_sound(SoundChannel::new(WALL_HIT));
}
SoundEffect::SlimeDie => {
self.mixer.play_sound(SoundChannel::new(SLIME_DEATH));
}
SoundEffect::HeroDie => {}
SoundEffect::SquidDie => {}
SoundEffect::SwordPickup => {
self.mixer.play_sound(SoundChannel::new(SWORD_PICKUP));
}
SoundEffect::SwordKill => {
self.mixer.play_sound(SoundChannel::new(SWORD_HIT));
}
SoundEffect::SwordFail => {
self.mixer.play_sound(SoundChannel::new(BANG));
}
SoundEffect::KeyPickup => {}
SoundEffect::DoorOpen => {
self.mixer.play_sound(SoundChannel::new(DOOR_OPEN));
}
SoundEffect::SwitchToggle => {
self.mixer.play_sound(SoundChannel::new(SWICTH_TOGGLES[0]));
}
SoundEffect::KeyDrop => {}
SoundEffect::SwordDrop => {}
SoundEffect::SwitchedDoorToggle => {}
SoundEffect::SpikesToggle => {}
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum SoundEffect {
SlimeDie,
HeroDie,
SquidDie,
SwordPickup,
SwordKill,
SwordFail,
KeyPickup,
DoorFail,
DoorOpen,
SwitchToggle,
KeyDrop,
SwordDrop,
SwitchedDoorToggle,
SpikesToggle,
WallHit,
}