diff --git a/examples/the-dungeon-puzzlers-lament/.cargo/config.toml b/examples/the-dungeon-puzzlers-lament/.cargo/config.toml new file mode 100644 index 00000000..b3276236 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/.cargo/config.toml @@ -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" diff --git a/examples/the-dungeon-puzzlers-lament/Cargo.lock b/examples/the-dungeon-puzzlers-lament/Cargo.lock new file mode 100644 index 00000000..8c5ac17d --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/Cargo.lock @@ -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-puzzlers-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" diff --git a/examples/the-dungeon-puzzlers-lament/Cargo.toml b/examples/the-dungeon-puzzlers-lament/Cargo.toml new file mode 100644 index 00000000..746ab6c6 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "the-dungeon-puzzlers-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" \ No newline at end of file diff --git a/examples/the-dungeon-puzzlers-lament/build.rs b/examples/the-dungeon-puzzlers-lament/build.rs new file mode 100644 index 00000000..60d40a2a --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/build.rs @@ -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::>(); + 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 { + 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 for Direction { + type Error = (); + + fn try_from(c: char) -> Result { + 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, + fixed_positions: Vec, + directions: Vec, + wall_bitmap: Vec, + 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::>()), + 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 { + 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]); +} diff --git a/examples/the-dungeon-puzzlers-lament/fnt/yoster.ttf b/examples/the-dungeon-puzzlers-lament/fnt/yoster.ttf new file mode 100644 index 00000000..bbbeb516 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/fnt/yoster.ttf differ diff --git a/examples/the-dungeon-puzzlers-lament/gba.ld b/examples/the-dungeon-puzzlers-lament/gba.ld new file mode 100644 index 00000000..525260d9 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/gba.ld @@ -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/ : { *(*) } +} \ No newline at end of file diff --git a/examples/the-dungeon-puzzlers-lament/gba_mb.ld b/examples/the-dungeon-puzzlers-lament/gba_mb.ld new file mode 100644 index 00000000..118baca6 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/gba_mb.ld @@ -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/ : { *(*) } +} \ No newline at end of file diff --git a/examples/the-dungeon-puzzlers-lament/gfx/concept.aseprite b/examples/the-dungeon-puzzlers-lament/gfx/concept.aseprite new file mode 100644 index 00000000..69259a09 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/gfx/concept.aseprite differ diff --git a/examples/the-dungeon-puzzlers-lament/gfx/countdown.aseprite b/examples/the-dungeon-puzzlers-lament/gfx/countdown.aseprite new file mode 100644 index 00000000..60e9c9f8 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/gfx/countdown.aseprite differ diff --git a/examples/the-dungeon-puzzlers-lament/gfx/ending_page.aseprite b/examples/the-dungeon-puzzlers-lament/gfx/ending_page.aseprite new file mode 100644 index 00000000..322176b0 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/gfx/ending_page.aseprite differ diff --git a/examples/the-dungeon-puzzlers-lament/gfx/level.aseprite b/examples/the-dungeon-puzzlers-lament/gfx/level.aseprite new file mode 100644 index 00000000..86f5ffbf Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/gfx/level.aseprite differ diff --git a/examples/the-dungeon-puzzlers-lament/gfx/sprites16x16.aseprite b/examples/the-dungeon-puzzlers-lament/gfx/sprites16x16.aseprite new file mode 100644 index 00000000..5c818a91 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/gfx/sprites16x16.aseprite differ diff --git a/examples/the-dungeon-puzzlers-lament/gfx/sprites8x8.aseprite b/examples/the-dungeon-puzzlers-lament/gfx/sprites8x8.aseprite new file mode 100644 index 00000000..0e278f3f Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/gfx/sprites8x8.aseprite differ diff --git a/examples/the-dungeon-puzzlers-lament/gfx/ui_tiles.aseprite b/examples/the-dungeon-puzzlers-lament/gfx/ui_tiles.aseprite new file mode 100644 index 00000000..f4008555 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/gfx/ui_tiles.aseprite differ diff --git a/examples/the-dungeon-puzzlers-lament/maps/UI.tmx b/examples/the-dungeon-puzzlers-lament/maps/UI.tmx new file mode 100644 index 00000000..dd90b414 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/UI.tmx @@ -0,0 +1,28 @@ + + + + + +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 + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/UI.tsx b/examples/the-dungeon-puzzlers-lament/maps/UI.tsx new file mode 100644 index 00000000..661a28ce --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/UI.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/gmtk23.tiled-project b/examples/the-dungeon-puzzlers-lament/maps/gmtk23.tiled-project new file mode 100644 index 00000000..28056787 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/gmtk23.tiled-project @@ -0,0 +1,12 @@ +{ + "automappingRulesFile": "", + "commands": [ + ], + "compatibilityVersion": 1100, + "extensionsPath": "extensions", + "folders": [ + "." + ], + "propertyTypes": [ + ] +} diff --git a/examples/the-dungeon-puzzlers-lament/maps/level.png b/examples/the-dungeon-puzzlers-lament/maps/level.png new file mode 100644 index 00000000..9e016675 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/maps/level.png differ diff --git a/examples/the-dungeon-puzzlers-lament/maps/level.tsx b/examples/the-dungeon-puzzlers-lament/maps/level.tsx new file mode 100644 index 00000000..33df5c03 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/level.tsx @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level1.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level1.tmx new file mode 100644 index 00000000..a8427a5f --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level1.tmx @@ -0,0 +1,44 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +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 + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level2.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level2.tmx new file mode 100644 index 00000000..14bf9401 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level2.tmx @@ -0,0 +1,41 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,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 + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level3.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level3.tmx new file mode 100644 index 00000000..2dbeb348 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level3.tmx @@ -0,0 +1,47 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,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 + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level4.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level4.tmx new file mode 100644 index 00000000..46cd80b4 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level4.tmx @@ -0,0 +1,44 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +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 + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level5.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level5.tmx new file mode 100644 index 00000000..5335cf04 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level5.tmx @@ -0,0 +1,41 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +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 + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level6.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level6.tmx new file mode 100644 index 00000000..e23991d3 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level6.tmx @@ -0,0 +1,50 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,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 + + + + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_around.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_around.tmx new file mode 100644 index 00000000..29df1e74 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_around.tmx @@ -0,0 +1,41 @@ + + + + + + + + + + +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 + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_spikes.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_spikes.tmx new file mode 100644 index 00000000..60c47114 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_spikes.tmx @@ -0,0 +1,50 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +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 + + + + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_spikes2.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_spikes2.tmx new file mode 100644 index 00000000..0cc3e6e6 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_spikes2.tmx @@ -0,0 +1,53 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,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 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_spikes3.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_spikes3.tmx new file mode 100644 index 00000000..038604d5 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_spikes3.tmx @@ -0,0 +1,56 @@ + + + + + + + + + + +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 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid1.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid1.tmx new file mode 100644 index 00000000..0325157c --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid1.tmx @@ -0,0 +1,47 @@ + + + + + + + + + + +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 + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid2.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid2.tmx new file mode 100644 index 00000000..0709dced --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid2.tmx @@ -0,0 +1,50 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,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 + + + + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_button.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_button.tmx new file mode 100644 index 00000000..639c34ec --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_button.tmx @@ -0,0 +1,53 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,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 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_drop.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_drop.tmx new file mode 100644 index 00000000..c2ec4256 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_drop.tmx @@ -0,0 +1,47 @@ + + + + + + + + + + +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 + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_intro.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_intro.tmx new file mode 100644 index 00000000..f7ccd663 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_intro.tmx @@ -0,0 +1,53 @@ + + + + + + + + + + +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 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_item.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_item.tmx new file mode 100644 index 00000000..3f0d2d21 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squid_item.tmx @@ -0,0 +1,53 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,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 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_squidprogramming.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squidprogramming.tmx new file mode 100644 index 00000000..31df08a2 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_squidprogramming.tmx @@ -0,0 +1,62 @@ + + + + + + + + + + +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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/level_switch.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/level_switch.tmx new file mode 100644 index 00000000..067a1acb --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/level_switch.tmx @@ -0,0 +1,47 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,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 + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/levels/squid_force_button.tmx b/examples/the-dungeon-puzzlers-lament/maps/levels/squid_force_button.tmx new file mode 100644 index 00000000..dde34979 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/maps/levels/squid_force_button.tmx @@ -0,0 +1,50 @@ + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +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 + + + + + + + + + + + + + + + + + + + + diff --git a/examples/the-dungeon-puzzlers-lament/maps/ui_tiles.png b/examples/the-dungeon-puzzlers-lament/maps/ui_tiles.png new file mode 100644 index 00000000..3850e27f Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/maps/ui_tiles.png differ diff --git a/examples/the-dungeon-puzzlers-lament/rust-toolchain.toml b/examples/the-dungeon-puzzlers-lament/rust-toolchain.toml new file mode 100644 index 00000000..6e1f5327 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["rust-src", "clippy", "rustfmt"] diff --git a/examples/the-dungeon-puzzlers-lament/sfx/bad.wav b/examples/the-dungeon-puzzlers-lament/sfx/bad.wav new file mode 100644 index 00000000..a86daef4 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/sfx/bad.wav differ diff --git a/examples/the-dungeon-puzzlers-lament/sfx/bgm.wav b/examples/the-dungeon-puzzlers-lament/sfx/bgm.wav new file mode 100644 index 00000000..9489590f Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/sfx/bgm.wav differ diff --git a/examples/the-dungeon-puzzlers-lament/sfx/door_open.wav b/examples/the-dungeon-puzzlers-lament/sfx/door_open.wav new file mode 100644 index 00000000..f23048ae Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/sfx/door_open.wav differ diff --git a/examples/the-dungeon-puzzlers-lament/sfx/place.wav b/examples/the-dungeon-puzzlers-lament/sfx/place.wav new file mode 100644 index 00000000..ce5b5639 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/sfx/place.wav differ diff --git a/examples/the-dungeon-puzzlers-lament/sfx/select.wav b/examples/the-dungeon-puzzlers-lament/sfx/select.wav new file mode 100644 index 00000000..e2324c64 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/sfx/select.wav differ diff --git a/examples/the-dungeon-puzzlers-lament/sfx/slime_death.wav b/examples/the-dungeon-puzzlers-lament/sfx/slime_death.wav new file mode 100644 index 00000000..dbe60e09 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/sfx/slime_death.wav differ diff --git a/examples/the-dungeon-puzzlers-lament/sfx/switch_toggle1.wav b/examples/the-dungeon-puzzlers-lament/sfx/switch_toggle1.wav new file mode 100644 index 00000000..6c629c5b Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/sfx/switch_toggle1.wav differ diff --git a/examples/the-dungeon-puzzlers-lament/sfx/sword_pickup.wav b/examples/the-dungeon-puzzlers-lament/sfx/sword_pickup.wav new file mode 100644 index 00000000..84f8b330 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/sfx/sword_pickup.wav differ diff --git a/examples/the-dungeon-puzzlers-lament/sfx/wall_hit.wav b/examples/the-dungeon-puzzlers-lament/sfx/wall_hit.wav new file mode 100644 index 00000000..6ea52376 Binary files /dev/null and b/examples/the-dungeon-puzzlers-lament/sfx/wall_hit.wav differ diff --git a/examples/the-dungeon-puzzlers-lament/src/backgrounds.rs b/examples/the-dungeon-puzzlers-lament/src/backgrounds.rs new file mode 100644 index 00000000..d2ac283c --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/backgrounds.rs @@ -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); + } + } +} diff --git a/examples/the-dungeon-puzzlers-lament/src/game.rs b/examples/the-dungeon-puzzlers-lament/src/game.rs new file mode 100644 index 00000000..625dbd91 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/game.rs @@ -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>, + 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, + ) -> 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 { + 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 { + 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); + } + } +} diff --git a/examples/the-dungeon-puzzlers-lament/src/game/game_state.rs b/examples/the-dungeon-puzzlers-lament/src/game/game_state.rs new file mode 100644 index 00000000..eeac61bc --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/game/game_state.rs @@ -0,0 +1,455 @@ +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 = Vector2D::new(179, 96); +const CURSOR_OFFSET: Vector2D = Vector2D::new(14, 14); + +const ARROW_TOP_LEFT: Vector2D = Vector2D::new(175, 15); + +pub struct GameState { + level_number: usize, + level: &'static Level, + cursor_state: CursorState, + frame: usize, + + item_states: Vec, +} + +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 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, + ) { + 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 { + 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, +} + +#[derive(PartialEq, Eq, Clone, Copy)] +enum CursorPlace { + Item, + Board, +} + +impl CursorState { + fn get_position(&self, is_odd_frame: bool) -> Vector2D { + 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 { + 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, +} diff --git a/examples/the-dungeon-puzzlers-lament/src/game/numbers.rs b/examples/the-dungeon-puzzlers-lament/src/game/numbers.rs new file mode 100644 index 00000000..346e42bd --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/game/numbers.rs @@ -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", +]; diff --git a/examples/the-dungeon-puzzlers-lament/src/game/simulation.rs b/examples/the-dungeon-puzzlers-lament/src/game/simulation.rs new file mode 100644 index 00000000..ce42c8ec --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/game/simulation.rs @@ -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, +} + +impl Simulation { + pub fn generate( + a: impl Iterator)>, + 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 + } +} diff --git a/examples/the-dungeon-puzzlers-lament/src/game/simulation/animation.rs b/examples/the-dungeon-puzzlers-lament/src/game/simulation/animation.rs new file mode 100644 index 00000000..b4a2b4a0 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/game/simulation/animation.rs @@ -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>, + rendered_position: Vector2D>, + attached: Option<(Item, Num)>, +} + +#[derive(Default)] +struct ToPlay { + moves: Vec, + attach_progress: Vec, + fakeout: Vec, + detatch: Vec, + attach: Vec, + change: Vec, + die: Vec, +} + +fn convert_to_real_space(p: Vector2D) -> Vector2D> { + p.change_base() * 16 +} + +impl ToPlay { + pub fn populate( + &mut self, + instruction: AnimationInstruction, + map: &mut SecondaryMap, + 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, + time: Num, +} + +#[derive(Default)] +struct Map { + map: SecondaryMap, +} + +fn attached_offset() -> Vector2D> { + 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>, + ) { + 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; + + 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) { + 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 { + 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::::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>, Option); +struct FakeOutMove(EntityKey, Direction, Option); +struct Detatch(EntityKey, EntityKey, Option); +struct Attach(EntityKey, Item, EntityKey, Option); +struct AttachProgress(EntityKey, Option); +struct Die(EntityKey, Option); +struct Change(EntityKey, Item, Option); + +#[derive(Clone, Debug)] +pub enum AnimationInstruction { + Add(EntityKey, Item, Vector2D, Option), + Move(EntityKey, Vector2D, Option), + FakeOutMove(EntityKey, Direction, Option), + Detatch(EntityKey, EntityKey, Option), + Attach(EntityKey, EntityKey, Option), + Change(EntityKey, Item, Option), + PriorityChange(EntityKey, Item, Option), + Die(EntityKey, Option), +} diff --git a/examples/the-dungeon-puzzlers-lament/src/game/simulation/entity.rs b/examples/the-dungeon-puzzlers-lament/src/game/simulation/entity.rs new file mode 100644 index 00000000..95a1474b --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/game/simulation/entity.rs @@ -0,0 +1,724 @@ +#![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, +} + +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +pub enum Outcome { + Continue, + Loss, + Win, +} + +impl EntityMap { + fn whats_at(&self, location: Vector2D) -> impl Iterator { + 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, + ) -> 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, + ) -> 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) { + 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::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; + let mut fake_out_effect = None; + + 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); + fake_out_effect = self + .map + .get(entity_key) + .and_then(|x| x.kill_sound_effect()); + 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 { + fake_out_effect.or_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 { + 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>, +} + +pub struct Entity { + location: Vector2D, + 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>, +} + +#[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 for Vector2D { + fn from(val: Direction) -> Self { + (&val).into() + } +} +impl From<&Direction> for Vector2D { + 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 { + 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 { + 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 { + 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 { + match &self.entity { + EntityType::Item(Item::Key) => Some(SoundEffect::KeyDrop), + EntityType::Item(Item::Sword) => Some(SoundEffect::SwordDrop), + _ => None, + } + } + + fn move_effect(&self) -> Option { + None + } + + fn kill_sound_effect(&self) -> Option { + match self.holding() { + Some(EntityType::Item(Item::Sword)) => Some(SoundEffect::SwordKill), + _ => None, + } + } + + fn change_effect(&self) -> Option { + 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 { + match &self.entity { + EntityType::Hero(_) => Some(SoundEffect::WallHit), + _ => None, + } + } + + fn pickup_sound_effect(&self) -> Option { + match &self.entity { + EntityType::Item(Item::Key) => Some(SoundEffect::KeyPickup), + EntityType::Item(Item::Sword) => Some(SoundEffect::SwordPickup), + _ => None, + } + } + + fn fake_out_effect(&self) -> Option { + None + } + + fn change_direction(&mut self) -> Option { + 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 { + 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 for Entity { + fn from(value: level::Entity) -> Self { + Entity { + location: value.1, + entity: value.0.into(), + } + } +} + +impl From 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, + })), + } + } +} diff --git a/examples/the-dungeon-puzzlers-lament/src/level.rs b/examples/the-dungeon-puzzlers-lament/src/level.rs new file mode 100644 index 00000000..3452f30a --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/level.rs @@ -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 { + const STANDARD: Vector2D = Vector2D::new(0, -3); + const ZERO: Vector2D = 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); + +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")); +} diff --git a/examples/the-dungeon-puzzlers-lament/src/lib.rs b/examples/the-dungeon-puzzlers-lament/src/lib.rs new file mode 100644 index 00000000..91a7926a --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/lib.rs @@ -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(&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(); + } +} diff --git a/examples/the-dungeon-puzzlers-lament/src/main.rs b/examples/the-dungeon-puzzlers-lament/src/main.rs new file mode 100644 index 00000000..b186f54e --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/main.rs @@ -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_puzzlers_lament::entry(gba); +} diff --git a/examples/the-dungeon-puzzlers-lament/src/map.rs b/examples/the-dungeon-puzzlers-lament/src/map.rs new file mode 100644 index 00000000..c0d9c6a3 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/map.rs @@ -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) -> 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::>::into(index)] + } +} + +impl Index> for Map<'_> { + type Output = MapElement; + + fn index(&self, index: Vector2D) -> &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); + } +} diff --git a/examples/the-dungeon-puzzlers-lament/src/resources.rs b/examples/the-dungeon-puzzlers-lament/src/resources.rs new file mode 100644 index 00000000..c4af40f7 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/resources.rs @@ -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); diff --git a/examples/the-dungeon-puzzlers-lament/src/save.rs b/examples/the-dungeon-puzzlers-lament/src/save.rs new file mode 100644 index 00000000..a47a510d --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/save.rs @@ -0,0 +1,46 @@ +use agb::{ + save::{Error, SaveManager}, + sync::Static, + Gba, +}; + +static MAXIMUM_LEVEL: Static = 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(()) +} diff --git a/examples/the-dungeon-puzzlers-lament/src/sfx.rs b/examples/the-dungeon-puzzlers-lament/src/sfx.rs new file mode 100644 index 00000000..bc336e13 --- /dev/null +++ b/examples/the-dungeon-puzzlers-lament/src/sfx.rs @@ -0,0 +1,95 @@ +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 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) { + if let Some(effect) = effect { + match effect { + 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 => {} + 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, + KeyPickup, + DoorOpen, + SwitchToggle, + KeyDrop, + SwordDrop, + SwitchedDoorToggle, + SpikesToggle, + WallHit, +} diff --git a/examples/the-hat-chooses-the-wizard/.gitignore b/examples/the-hat-chooses-the-wizard/.gitignore deleted file mode 100644 index 97b0d649..00000000 --- a/examples/the-hat-chooses-the-wizard/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -/joinedtogether.* diff --git a/examples/the-hat-chooses-the-wizard/.vscode/settings.json b/examples/the-hat-chooses-the-wizard/.vscode/settings.json deleted file mode 100644 index e58f9587..00000000 --- a/examples/the-hat-chooses-the-wizard/.vscode/settings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "files.associations": { - "*.tsx": "xml", - "*.tmx": "xml" - }, - "rust-analyzer.checkOnSave.allTargets": false, - "rust-analyzer.checkOnSave.extraArgs": [ - "--target", - "thumbv4t-none-eabi" - ] -} \ No newline at end of file diff --git a/examples/the-hat-chooses-the-wizard/LICENSE b/examples/the-hat-chooses-the-wizard/LICENSE deleted file mode 100644 index a612ad98..00000000 --- a/examples/the-hat-chooses-the-wizard/LICENSE +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/examples/the-hat-chooses-the-wizard/README.md b/examples/the-hat-chooses-the-wizard/README.md deleted file mode 100644 index 171c47b6..00000000 --- a/examples/the-hat-chooses-the-wizard/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# The Hat Chooses The Wizard - -[![the hat chooses the wizard screenshot](screenshot.png)](https://lostimmortal.itch.io/the-hat-chooses-the-wizard) - -A Game Boy Advance game made for the [GMTK Game jam 2021](https://itch.io/jam/gmtk-2021) written in Rust using the [agb library](https://github.com/corwinkuiper/agb). -Play it at [lostimmortal.itch.io/the-hat-chooses-the-wizard](https://lostimmortal.itch.io/the-hat-chooses-the-wizard). - - -## Compiling - -The [repository for agb](https://github.com/corwinkuiper/agb) gives instructions for what tools are required for using agb. -If you have these, then clone this repository and build away. -If mgba-qt is installed, then a `cargo run` will build and run the game. - -## Changes - -This code may have changed since the gamejam submission, the tag `gmtk-submission` contains *exactly* the code at the point of submission. -This may be difficult to compile due to requiring some special directory setup, so the tag `gmtk-submission-agb-versioned` contains a single change that means it is easier to compile. \ No newline at end of file diff --git a/examples/the-hat-chooses-the-wizard/screenshot.png b/examples/the-hat-chooses-the-wizard/screenshot.png deleted file mode 100644 index a8336fec..00000000 Binary files a/examples/the-hat-chooses-the-wizard/screenshot.png and /dev/null differ diff --git a/examples/the-purple-night/.gitignore b/examples/the-purple-night/.gitignore deleted file mode 100644 index eb5a316c..00000000 --- a/examples/the-purple-night/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/examples/the-purple-night/html/LICENSE b/examples/the-purple-night/html/LICENSE deleted file mode 100644 index d159169d..00000000 --- a/examples/the-purple-night/html/LICENSE +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/examples/the-purple-night/html/app.js b/examples/the-purple-night/html/app.js deleted file mode 100644 index afa73517..00000000 --- a/examples/the-purple-night/html/app.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e){var o={};function i(a){if(o[a])return o[a].exports;var r=o[a]={i:a,l:!1,exports:{}};return e[a].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=o,i.d=function(e,o,a){i.o(e,o)||Object.defineProperty(e,o,{configurable:!1,enumerable:!0,get:a})},i.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},i.n=function(e){var o=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(o,"a",o),o},i.o=function(e,o){return Object.prototype.hasOwnProperty.call(e,o)},i.p="",i(i.s=15)}([function(e,o,i){!function(){"use strict";var o=i(8);e.exports=function(e){return o[e]?escapeHtml(o[e][0]):"Unknown Game"}}()},,,,,,function(e,o,i){!function(){"use strict";function o(e){this.el=e,this.currentlyBinding=!1,this.initialHTML=e.innerHTML,this.el.addEventListener("keydown",this.onKeyDown.bind(this)),this.paused=!1}o.prototype=Object.create(Object.prototype),o.prototype.constructor=o,o.prototype.setPausedState=function(e){this.paused=e,this.el.querySelector(".load-rom-section").style.display=e?"none":"",this.el.querySelector(".paused-section").style.display=e?"":"none"},o.prototype.reset=function(){var e;this.el.innerHTML=this.initialHTML,this.currentlyBinding=!1;var o=this.el.querySelector(".saves-list"),a="",r=window.vbaSaves.listSaves();for(e=0;e";r.length||(a+=""),window.isShittyLocalstorage&&(a+=""),a+="
["+r[e].romCode+"] "+i(0)(r[e].romCode)+"ExportDelete
None
Saving will not be possible because the 'LocalStorage'
feature of your browser is disabled.
",o.innerHTML=a;var s=this.el.querySelector(".keyboard-bindings"),n="",t=window.vbaInput.listBindings();function m(e){return e.join(", ").replace(/Key/im,"Key ").replace(/Arrow/im,"Arrow ").replace(/Digit/im,"Digit ").replace(/Numpad/im,"Numpad ").replace(/Left/im," Left").replace(/Right/im," Right")}for(e=0;e","PAUSE"===t[e].name&&(this.el.querySelector(".unpause-key-prompt").innerText=m(t[e].codes));n+="
"+t[e].friendlyName+""+m(t[e].codes)+"Rebind
",s.innerHTML=n,this.setPausedState(this.paused)},o.prototype.export=function(){vbaSaves.exportSave()},o.prototype.onKeyDown=function(e){if(this.currentlyBinding){var o=vbaInput.bindings[this.currentlyBinding].codes.join();vbaInput.setBinding(this.currentlyBinding,e.code,e.keyCode);var i=vbaInput.bindings[this.currentlyBinding].codes.join();gtag("event","rebind_key_1",{event_label:"Change "+this.currentlyBinding+" from "+o+" to "+i}),this.reset()}},o.prototype.startRebinding=function(e,o){this.currentlyBinding=o,this.el.querySelectorAll(".rebind-key-button").forEach(function(e){e.innerText="Rebind"}),e.innerText="Rebinding..."},o.prototype.resetBindings=function(){gtag("event","reset_bindings_1",{}),vbaInput.resetBindings(),this.reset()},o.prototype.exportSave=function(e){vbaSaves.exportSave(e),this.reset(),gtag("event","export_save_1",{event_label:e+" "+i(0)(e)})},o.prototype.deleteSave=function(e){var o=modal("Are you sure you want to delete your save for ["+e+"] "+i(0)(e)+"?",{title:"Confirm Deletion",leftButtonText:"Delete",leftButtonFn:function(){vbaSaves.deleteSave(e),this.reset(),gtag("event","delete_save_1",{event_label:e+" "+i(0)(e)})}.bind(this),rightButtonText:"Cancel",rightButtonFn:function(){o.hideModal()}})},e.exports=o}()},function(e,o){!function(){"use strict";var o={};function i(){this.downCodes={},this.downKeyCodes={},window.addEventListener("keydown",function(e){var o=this.isKeyDown(this.bindings.PERF_STATS),i=this.isKeyDown(this.bindings.PAUSE);this.downCodes[e.code]=1,this.downKeyCodes[e.keyCode]=1;var a=this.isKeyDown(this.bindings.PERF_STATS);!o&&a&&window.doPerfCalc();var r=this.isKeyDown(this.bindings.PAUSE);return!i&&r&&window.togglePause(),!1}.bind(this)),window.addEventListener("keyup",function(e){var o=this.isKeyDown(this.bindings.PERF_STATS);this.downCodes[e.code]=0,this.downKeyCodes[e.keyCode]=0;var i=this.isKeyDown(this.bindings.PERF_STATS);return o&&!i&&window.doPerfCalc(),!1}.bind(this)),this.bindings=null,this.loadBindings(),null===this.bindings&&this.resetBindings()}o.KEY_BUTTON_A={friendlyName:"A",codes:["KeyZ"],keyCodes:[90]},o.KEY_BUTTON_B={friendlyName:"B",codes:["KeyX"],keyCodes:[88]},o.KEY_BUTTON_SELECT={friendlyName:"Select",codes:["Backspace"],keyCodes:[8]},o.KEY_BUTTON_START={friendlyName:"Start",codes:["Enter"],keyCodes:[13]},o.KEY_RIGHT={friendlyName:"Right",codes:["ArrowRight"],keyCodes:[39]},o.KEY_LEFT={friendlyName:"Left",codes:["ArrowLeft"],keyCodes:[37]},o.KEY_UP={friendlyName:"Up",codes:["ArrowUp"],keyCodes:[38]},o.KEY_DOWN={friendlyName:"Down",codes:["ArrowDown"],keyCodes:[40]},o.KEY_BUTTON_R={friendlyName:"R",codes:["Control"],keyCodes:[17]},o.KEY_BUTTON_L={friendlyName:"L",codes:["Shift"],keyCodes:[16]},o.PERF_STATS={friendlyName:"Performance Stats",codes:["Backquote"],keyCodes:[192]},o.PAUSE={friendlyName:"Pause",codes:["Escape"],keyCodes:[27]},i.prototype=Object.create(Object.prototype),i.prototype.constructor=i,i.prototype.listBindings=function(){return Object.keys(this.bindings).map(function(e){return{name:e,friendlyName:this.bindings[e].friendlyName,codes:this.bindings[e].codes}}.bind(this))},i.prototype.setBinding=function(e,o,i){this.bindings[e].codes=[o],this.bindings[e].keyCodes=[i],this.saveBindings()},i.prototype.loadBindings=function(e){if(this.bindings=JSON.parse(localStorage.VBABindings||"null")||o,Object.keys(this.bindings).sort().join()!==Object.keys(o).sort().join()){if(e)return;this.resetBindings(!0)}},i.prototype.saveBindings=function(){localStorage.VBABindings=JSON.stringify(this.bindings)},i.prototype.resetBindings=function(e){this.bindings=o,this.saveBindings(),this.loadBindings(e)},i.prototype.isKeyDown=function(e){var o;for(o=0;o