mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 16:21:33 +11:00
Combo game rom (#355)
Adds a rom that is all the examples compiled together! - [ ] Changelog updated / no changelog update needed
This commit is contained in:
commit
d65ca26bd8
14
examples/combo/.cargo/config.toml
Normal file
14
examples/combo/.cargo/config.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[unstable]
|
||||
build-std = ["core", "alloc"]
|
||||
build-std-features = ["compiler-builtins-mem"]
|
||||
|
||||
[build]
|
||||
target = "thumbv4t-none-eabi"
|
||||
|
||||
[target.thumbv4t-none-eabi]
|
||||
rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"]
|
||||
runner = "mgba-qt"
|
||||
|
||||
[target.armv4t-none-eabi]
|
||||
rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"]
|
||||
runner = "mgba-qt"
|
554
examples/combo/Cargo.lock
generated
Normal file
554
examples/combo/Cargo.lock
generated
Normal file
|
@ -0,0 +1,554 @@
|
|||
# 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.12.2"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
"bare-metal",
|
||||
"bitflags",
|
||||
"modular-bitfield",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_fixnum"
|
||||
version = "0.12.2"
|
||||
dependencies = [
|
||||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.12.2"
|
||||
dependencies = [
|
||||
"asefile",
|
||||
"fontdue",
|
||||
"image",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_macros"
|
||||
version = "0.12.2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_sound_converter"
|
||||
version = "0.12.2"
|
||||
dependencies = [
|
||||
"hound",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asefile"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a71de7aecd2d0a76ec90fde2c443d12667c737d92de76bd187f101eca37891"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"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.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[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 = "combo"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agb",
|
||||
"hyperspace-roll",
|
||||
"the-hat-chooses-the-wizard",
|
||||
"the-purple-night",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[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.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.6.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontdue"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generational-arena"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hound"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1"
|
||||
|
||||
[[package]]
|
||||
name = "hyperspace-roll"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agb",
|
||||
"bare-metal",
|
||||
]
|
||||
|
||||
[[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 = "itoa"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
|
||||
[[package]]
|
||||
name = "libflate"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"crc32fast",
|
||||
"rle-decode-fast",
|
||||
"take_mut",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[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.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "modular-bitfield"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74"
|
||||
dependencies = [
|
||||
"modular-bitfield-impl",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "modular-bitfield-impl"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[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.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.16.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"deflate",
|
||||
"miniz_oxide 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
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 = "ryu"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.148"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.148"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "take_mut"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
|
||||
|
||||
[[package]]
|
||||
name = "the-hat-chooses-the-wizard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agb",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "the-purple-night"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agb",
|
||||
"generational-arena",
|
||||
"quote",
|
||||
"tiled",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiled"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d2c30aeea9d8159cb461a17dba23ad28980a2a9c217a6784a14e931e4979d6f"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"libflate",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[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.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
22
examples/combo/Cargo.toml
Normal file
22
examples/combo/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "combo"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
agb = { version = "0.12.2", path = "../../agb" }
|
||||
the-purple-night = { path = "../the-purple-night" }
|
||||
the-hat-chooses-the-wizard = { path = "../the-hat-chooses-the-wizard" }
|
||||
hyperspace-roll = { path = "../hyperspace-roll" }
|
||||
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 2
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
debug = true
|
113
examples/combo/gba.ld
Normal file
113
examples/combo/gba.ld
Normal file
|
@ -0,0 +1,113 @@
|
|||
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
|
||||
OUTPUT_ARCH(arm)
|
||||
|
||||
ENTRY(__start)
|
||||
EXTERN(__RUST_INTERRUPT_HANDLER)
|
||||
|
||||
EXTERN(__agbabi_memset)
|
||||
EXTERN(__agbabi_memcpy)
|
||||
|
||||
MEMORY {
|
||||
ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K
|
||||
iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K
|
||||
rom (rx) : ORIGIN = 0x08000000, LENGTH = 32M
|
||||
}
|
||||
|
||||
__text_start = ORIGIN(rom);
|
||||
|
||||
INPUT (agb.a)
|
||||
|
||||
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;
|
||||
|
||||
/* 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/ : { *(*) }
|
||||
}
|
111
examples/combo/gba_mb.ld
Normal file
111
examples/combo/gba_mb.ld
Normal file
|
@ -0,0 +1,111 @@
|
|||
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);
|
||||
|
||||
INPUT (agb.a)
|
||||
|
||||
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;
|
||||
|
||||
/* 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/ : { *(*) }
|
||||
}
|
BIN
examples/combo/gfx/games.aseprite
Normal file
BIN
examples/combo/gfx/games.aseprite
Normal file
Binary file not shown.
16
examples/combo/gfx/games.toml
Normal file
16
examples/combo/gfx/games.toml
Normal file
|
@ -0,0 +1,16 @@
|
|||
version = "1.0"
|
||||
|
||||
[image.hat]
|
||||
filename = "hat.png"
|
||||
tile_size = "8x8"
|
||||
transparent_colour = "121105"
|
||||
|
||||
[image.purple]
|
||||
filename = "purple.png"
|
||||
tile_size = "8x8"
|
||||
transparent_colour = "121105"
|
||||
|
||||
[image.hyperspace]
|
||||
filename = "hyperspace.png"
|
||||
tile_size = "8x8"
|
||||
transparent_colour = "121105"
|
BIN
examples/combo/gfx/hat.png
Normal file
BIN
examples/combo/gfx/hat.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
BIN
examples/combo/gfx/hyperspace.png
Normal file
BIN
examples/combo/gfx/hyperspace.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
BIN
examples/combo/gfx/purple.png
Normal file
BIN
examples/combo/gfx/purple.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
3
examples/combo/rust-toolchain.toml
Normal file
3
examples/combo/rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rust-src", "clippy"]
|
131
examples/combo/src/lib.rs
Normal file
131
examples/combo/src/lib.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
#![no_std]
|
||||
#![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))]
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::boxed::Box;
|
||||
|
||||
use agb::{
|
||||
display::{
|
||||
tiled::{InfiniteScrolledMap, RegularBackgroundSize, TileFormat, TileSet, TileSetting},
|
||||
Priority,
|
||||
},
|
||||
fixnum::{Num, Vector2D},
|
||||
include_gfx,
|
||||
input::Button,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Game {
|
||||
TheHatChoosesTheWizard,
|
||||
ThePurpleNight,
|
||||
HyperspaceRoll,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
fn launch_game(self, gba: agb::Gba) -> ! {
|
||||
match self {
|
||||
Game::TheHatChoosesTheWizard => the_hat_chooses_the_wizard::main(gba),
|
||||
Game::ThePurpleNight => the_purple_night::main(gba),
|
||||
Game::HyperspaceRoll => hyperspace_roll::main(gba),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_index(index: i32) -> Game {
|
||||
match index.rem_euclid(3) {
|
||||
0 => Game::TheHatChoosesTheWizard,
|
||||
1 => Game::ThePurpleNight,
|
||||
2 => Game::HyperspaceRoll,
|
||||
_ => unreachable!("game out of index in an unreachable manner"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include_gfx!("gfx/games.toml");
|
||||
|
||||
fn get_game(gba: &mut agb::Gba) -> Game {
|
||||
let mut input = agb::input::ButtonController::new();
|
||||
let vblank = agb::interrupt::VBlank::get();
|
||||
|
||||
let (tile, mut vram) = gba.display.video.tiled0();
|
||||
|
||||
let hat = TileSet::new(games::hat.tiles, TileFormat::FourBpp);
|
||||
let purple = TileSet::new(games::purple.tiles, TileFormat::FourBpp);
|
||||
let hyperspace = TileSet::new(games::hyperspace.tiles, TileFormat::FourBpp);
|
||||
|
||||
let tiles = [hat, purple, hyperspace];
|
||||
|
||||
let palette_assignments = &[
|
||||
games::hat.palette_assignments,
|
||||
games::purple.palette_assignments,
|
||||
games::hyperspace.palette_assignments,
|
||||
];
|
||||
|
||||
vram.set_background_palettes(games::PALETTES);
|
||||
|
||||
let mut bg = InfiniteScrolledMap::new(
|
||||
tile.background(Priority::P0, RegularBackgroundSize::Background32x32),
|
||||
Box::new(|pos| {
|
||||
let y = pos.y.rem_euclid(20);
|
||||
let x = pos.x.rem_euclid(30);
|
||||
|
||||
let game = (pos.x).rem_euclid(90) as usize / 30;
|
||||
let tile_id = (y * 30 + x) as usize;
|
||||
(
|
||||
&tiles[game],
|
||||
TileSetting::new(
|
||||
tile_id as u16,
|
||||
false,
|
||||
false,
|
||||
palette_assignments[game][tile_id],
|
||||
),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
bg.init(&mut vram, (0, 0).into(), &mut || {});
|
||||
|
||||
bg.set_pos(&mut vram, (0, 0).into());
|
||||
bg.commit(&mut vram);
|
||||
bg.show();
|
||||
|
||||
let mut position: Vector2D<Num<i32, 8>> = (0, 0).into();
|
||||
let mut game_idx = 0;
|
||||
let game = loop {
|
||||
let lr: agb::input::Tri = (
|
||||
input.is_just_pressed(Button::LEFT),
|
||||
input.is_just_pressed(Button::RIGHT),
|
||||
)
|
||||
.into();
|
||||
|
||||
game_idx += lr as i32;
|
||||
|
||||
if (position.x - game_idx * 30 * 8).abs() < Num::new(1) / 2 {
|
||||
position.x = Num::new(game_idx * 30 * 8);
|
||||
}
|
||||
|
||||
position.x +=
|
||||
((Num::new(game_idx * 30 * 8) - position.x) / 8).clamp(-Num::new(8), Num::new(8));
|
||||
|
||||
bg.set_pos(&mut vram, position.floor());
|
||||
|
||||
vblank.wait_for_vblank();
|
||||
bg.commit(&mut vram);
|
||||
input.update();
|
||||
|
||||
if input.is_just_pressed(Button::A) {
|
||||
break Game::from_index(game_idx);
|
||||
}
|
||||
};
|
||||
|
||||
bg.hide();
|
||||
bg.clear(&mut vram);
|
||||
bg.commit(&mut vram);
|
||||
|
||||
game
|
||||
}
|
||||
|
||||
pub fn main(mut gba: agb::Gba) -> ! {
|
||||
get_game(&mut gba).launch_game(gba)
|
||||
}
|
10
examples/combo/src/main.rs
Normal file
10
examples/combo/src/main.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
#![cfg_attr(test, feature(custom_test_frameworks))]
|
||||
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
|
||||
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
|
||||
|
||||
#[agb::entry]
|
||||
fn entry(gba: agb::Gba) -> ! {
|
||||
combo::main(gba);
|
||||
}
|
|
@ -64,7 +64,7 @@ pub(crate) fn load_description(
|
|||
|
||||
for y in 0..11 {
|
||||
for x in 0..8 {
|
||||
let tile_id = y * 8 + x + 8 * 11 * (face_id as u16 - 10);
|
||||
let tile_id = y * 8 + x + 8 * 11 * (face_id as u16 % 10);
|
||||
descriptions_map.set_tile(
|
||||
vram,
|
||||
(x, y).into(),
|
||||
|
|
226
examples/hyperspace-roll/src/lib.rs
Normal file
226
examples/hyperspace-roll/src/lib.rs
Normal file
|
@ -0,0 +1,226 @@
|
|||
// Games made using `agb` are no_std which means you don't have access to the standard
|
||||
// rust library. This is because the game boy advance doesn't really have an operating
|
||||
// system, so most of the content of the standard library doesn't apply.
|
||||
//
|
||||
// Provided you haven't disabled it, agb does provide an allocator, so it is possible
|
||||
// to use both the `core` and the `alloc` built in crates.
|
||||
#![no_std]
|
||||
// `agb` defines its own `main` function, so you must declare your game's main function
|
||||
// using the #[agb::entry] proc macro. Failing to do so will cause failure in linking
|
||||
// which won't be a particularly clear error message.
|
||||
#![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::ObjectController;
|
||||
use agb::display::tiled::{TiledMap, VRamManager};
|
||||
use agb::display::Priority;
|
||||
use agb::interrupt::VBlank;
|
||||
use agb::{display, sound::mixer::Frequency};
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
mod background;
|
||||
mod battle;
|
||||
mod customise;
|
||||
mod graphics;
|
||||
mod level_generation;
|
||||
mod save;
|
||||
mod sfx;
|
||||
|
||||
use background::{show_title_screen, StarBackground};
|
||||
use battle::BattleResult;
|
||||
use graphics::NumberDisplay;
|
||||
use sfx::Sfx;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Face {
|
||||
Shoot,
|
||||
Shield,
|
||||
Malfunction,
|
||||
Heal,
|
||||
Bypass,
|
||||
DoubleShot,
|
||||
TripleShot,
|
||||
Blank,
|
||||
Disrupt,
|
||||
MalfunctionShot,
|
||||
DoubleShield,
|
||||
TripleShield,
|
||||
DoubleShieldValue,
|
||||
DoubleShotValue,
|
||||
TripleShotValue,
|
||||
BurstShield,
|
||||
Invert,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Ship {
|
||||
Player,
|
||||
Drone,
|
||||
PilotedShip,
|
||||
Shield,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum EnemyAttackType {
|
||||
Attack,
|
||||
Shield,
|
||||
Heal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Die {
|
||||
faces: [Face; 6],
|
||||
}
|
||||
|
||||
impl Die {
|
||||
/// roll this die (potentially using the custom probabilities, should we implement that) and return which face index is showing
|
||||
fn roll(&self) -> Face {
|
||||
let n = agb::rng::gen().rem_euclid(6);
|
||||
self.faces[n as usize]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlayerDice {
|
||||
dice: Vec<Die>,
|
||||
}
|
||||
|
||||
struct Agb<'a> {
|
||||
obj: ObjectController,
|
||||
vblank: VBlank,
|
||||
star_background: StarBackground<'a>,
|
||||
vram: VRamManager,
|
||||
sfx: Sfx<'a>,
|
||||
}
|
||||
|
||||
pub fn main(mut gba: agb::Gba) -> ! {
|
||||
save::init_save(&mut gba).expect("Could not initialize save game");
|
||||
|
||||
if save::load_high_score() > 1000 {
|
||||
save::save_high_score(&mut gba, 0).expect("Could not reset high score");
|
||||
}
|
||||
|
||||
let gfx = gba.display.object.get();
|
||||
let vblank = agb::interrupt::VBlank::get();
|
||||
|
||||
let (tiled, mut vram) = gba.display.video.tiled0();
|
||||
let mut background0 = tiled.background(
|
||||
Priority::P0,
|
||||
display::tiled::RegularBackgroundSize::Background64x32,
|
||||
);
|
||||
let mut background1 = tiled.background(
|
||||
Priority::P0,
|
||||
display::tiled::RegularBackgroundSize::Background64x32,
|
||||
);
|
||||
let mut card_descriptions = tiled.background(
|
||||
Priority::P1,
|
||||
display::tiled::RegularBackgroundSize::Background32x32,
|
||||
);
|
||||
|
||||
let mut help_background = tiled.background(
|
||||
Priority::P1,
|
||||
display::tiled::RegularBackgroundSize::Background32x32,
|
||||
);
|
||||
|
||||
let basic_die = Die {
|
||||
faces: [
|
||||
Face::Shoot,
|
||||
Face::Shield,
|
||||
Face::Blank,
|
||||
Face::Malfunction,
|
||||
Face::Blank,
|
||||
Face::Blank,
|
||||
],
|
||||
};
|
||||
|
||||
let mut star_background = StarBackground::new(&mut background0, &mut background1, &mut vram);
|
||||
star_background.commit(&mut vram);
|
||||
|
||||
let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
|
||||
mixer.enable();
|
||||
let _interrupt_handler = mixer.setup_interrupt_handler();
|
||||
|
||||
let sfx = Sfx::new(&mut mixer);
|
||||
|
||||
let mut agb = Agb {
|
||||
obj: gfx,
|
||||
vblank,
|
||||
star_background,
|
||||
vram,
|
||||
sfx,
|
||||
};
|
||||
|
||||
loop {
|
||||
let mut dice = PlayerDice {
|
||||
dice: vec![basic_die.clone(); 2],
|
||||
};
|
||||
|
||||
let mut current_level = 1;
|
||||
|
||||
agb.sfx.title_screen();
|
||||
|
||||
{
|
||||
show_title_screen(&mut help_background, &mut agb.vram, &mut agb.sfx);
|
||||
let mut score_display = NumberDisplay::new((216, 9).into());
|
||||
score_display.set_value(Some(save::load_high_score()), &agb.obj);
|
||||
agb.obj.commit();
|
||||
agb.star_background.hide();
|
||||
|
||||
let mut input = agb::input::ButtonController::new();
|
||||
loop {
|
||||
let _ = agb::rng::gen();
|
||||
input.update();
|
||||
if input.is_just_pressed(agb::input::Button::all()) {
|
||||
break;
|
||||
}
|
||||
agb.vblank.wait_for_vblank();
|
||||
agb.sfx.frame();
|
||||
}
|
||||
}
|
||||
|
||||
agb.obj.commit();
|
||||
|
||||
help_background.hide();
|
||||
help_background.clear(&mut agb.vram);
|
||||
help_background.commit(&mut agb.vram);
|
||||
agb.sfx.frame();
|
||||
|
||||
background::load_palettes(&mut agb.vram);
|
||||
agb.star_background.show();
|
||||
|
||||
loop {
|
||||
dice = customise::customise_screen(
|
||||
&mut agb,
|
||||
dice.clone(),
|
||||
&mut card_descriptions,
|
||||
&mut help_background,
|
||||
current_level,
|
||||
);
|
||||
|
||||
let result =
|
||||
battle::battle_screen(&mut agb, dice.clone(), current_level, &mut help_background);
|
||||
match result {
|
||||
BattleResult::Win => {}
|
||||
BattleResult::Loss => {
|
||||
agb.obj.commit();
|
||||
agb.sfx.customise();
|
||||
if save::load_high_score() < current_level {
|
||||
save::save_high_score(&mut gba, current_level)
|
||||
.expect("Could not save high score");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
current_level += 1;
|
||||
|
||||
if current_level % 5 == 0 && dice.dice.len() < 5 {
|
||||
dice.dice.push(basic_die.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,232 +1,10 @@
|
|||
// Games made using `agb` are no_std which means you don't have access to the standard
|
||||
// rust library. This is because the game boy advance doesn't really have an operating
|
||||
// system, so most of the content of the standard library doesn't apply.
|
||||
//
|
||||
// Provided you haven't disabled it, agb does provide an allocator, so it is possible
|
||||
// to use both the `core` and the `alloc` built in crates.
|
||||
#![no_std]
|
||||
// `agb` defines its own `main` function, so you must declare your game's main function
|
||||
// using the #[agb::entry] proc macro. Failing to do so will cause failure in linking
|
||||
// which won't be a particularly clear error message.
|
||||
#![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::ObjectController;
|
||||
use agb::display::tiled::{TiledMap, VRamManager};
|
||||
use agb::display::Priority;
|
||||
use agb::interrupt::VBlank;
|
||||
use agb::{display, sound::mixer::Frequency};
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
mod background;
|
||||
mod battle;
|
||||
mod customise;
|
||||
mod graphics;
|
||||
mod level_generation;
|
||||
mod save;
|
||||
mod sfx;
|
||||
|
||||
use background::{show_title_screen, StarBackground};
|
||||
use battle::BattleResult;
|
||||
use graphics::NumberDisplay;
|
||||
use sfx::Sfx;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Face {
|
||||
Shoot,
|
||||
Shield,
|
||||
Malfunction,
|
||||
Heal,
|
||||
Bypass,
|
||||
DoubleShot,
|
||||
TripleShot,
|
||||
Blank,
|
||||
Disrupt,
|
||||
MalfunctionShot,
|
||||
DoubleShield,
|
||||
TripleShield,
|
||||
DoubleShieldValue,
|
||||
DoubleShotValue,
|
||||
TripleShotValue,
|
||||
BurstShield,
|
||||
Invert,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Ship {
|
||||
Player,
|
||||
Drone,
|
||||
PilotedShip,
|
||||
Shield,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum EnemyAttackType {
|
||||
Attack,
|
||||
Shield,
|
||||
Heal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Die {
|
||||
faces: [Face; 6],
|
||||
}
|
||||
|
||||
impl Die {
|
||||
/// roll this die (potentially using the custom probabilities, should we implement that) and return which face index is showing
|
||||
fn roll(&self) -> Face {
|
||||
let n = agb::rng::gen().rem_euclid(6);
|
||||
self.faces[n as usize]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlayerDice {
|
||||
dice: Vec<Die>,
|
||||
}
|
||||
|
||||
struct Agb<'a> {
|
||||
obj: ObjectController,
|
||||
vblank: VBlank,
|
||||
star_background: StarBackground<'a>,
|
||||
vram: VRamManager,
|
||||
sfx: Sfx<'a>,
|
||||
}
|
||||
|
||||
fn main(mut gba: agb::Gba) -> ! {
|
||||
save::init_save(&mut gba).expect("Could not initialize save game");
|
||||
|
||||
if save::load_high_score() > 1000 {
|
||||
save::save_high_score(&mut gba, 0).expect("Could not reset high score");
|
||||
}
|
||||
|
||||
let gfx = gba.display.object.get();
|
||||
let vblank = agb::interrupt::VBlank::get();
|
||||
|
||||
let (tiled, mut vram) = gba.display.video.tiled0();
|
||||
let mut background0 = tiled.background(
|
||||
Priority::P0,
|
||||
display::tiled::RegularBackgroundSize::Background64x32,
|
||||
);
|
||||
let mut background1 = tiled.background(
|
||||
Priority::P0,
|
||||
display::tiled::RegularBackgroundSize::Background64x32,
|
||||
);
|
||||
let mut card_descriptions = tiled.background(
|
||||
Priority::P1,
|
||||
display::tiled::RegularBackgroundSize::Background32x32,
|
||||
);
|
||||
|
||||
let mut help_background = tiled.background(
|
||||
Priority::P1,
|
||||
display::tiled::RegularBackgroundSize::Background32x32,
|
||||
);
|
||||
|
||||
let basic_die = Die {
|
||||
faces: [
|
||||
Face::Shoot,
|
||||
Face::Shield,
|
||||
Face::Blank,
|
||||
Face::Malfunction,
|
||||
Face::Blank,
|
||||
Face::Blank,
|
||||
],
|
||||
};
|
||||
|
||||
let mut star_background = StarBackground::new(&mut background0, &mut background1, &mut vram);
|
||||
star_background.commit(&mut vram);
|
||||
|
||||
let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
|
||||
mixer.enable();
|
||||
let _interrupt_handler = mixer.setup_interrupt_handler();
|
||||
|
||||
let sfx = Sfx::new(&mut mixer);
|
||||
|
||||
let mut agb = Agb {
|
||||
obj: gfx,
|
||||
vblank,
|
||||
star_background,
|
||||
vram,
|
||||
sfx,
|
||||
};
|
||||
|
||||
loop {
|
||||
let mut dice = PlayerDice {
|
||||
dice: vec![basic_die.clone(); 2],
|
||||
};
|
||||
|
||||
let mut current_level = 1;
|
||||
|
||||
agb.sfx.title_screen();
|
||||
|
||||
{
|
||||
show_title_screen(&mut help_background, &mut agb.vram, &mut agb.sfx);
|
||||
let mut score_display = NumberDisplay::new((216, 9).into());
|
||||
score_display.set_value(Some(save::load_high_score()), &agb.obj);
|
||||
agb.obj.commit();
|
||||
agb.star_background.hide();
|
||||
|
||||
let mut input = agb::input::ButtonController::new();
|
||||
loop {
|
||||
let _ = agb::rng::gen();
|
||||
input.update();
|
||||
if input.is_just_pressed(agb::input::Button::all()) {
|
||||
break;
|
||||
}
|
||||
agb.vblank.wait_for_vblank();
|
||||
agb.sfx.frame();
|
||||
}
|
||||
}
|
||||
|
||||
agb.obj.commit();
|
||||
|
||||
help_background.hide();
|
||||
help_background.clear(&mut agb.vram);
|
||||
help_background.commit(&mut agb.vram);
|
||||
agb.sfx.frame();
|
||||
|
||||
background::load_palettes(&mut agb.vram);
|
||||
agb.star_background.show();
|
||||
|
||||
loop {
|
||||
dice = customise::customise_screen(
|
||||
&mut agb,
|
||||
dice.clone(),
|
||||
&mut card_descriptions,
|
||||
&mut help_background,
|
||||
current_level,
|
||||
);
|
||||
|
||||
let result =
|
||||
battle::battle_screen(&mut agb, dice.clone(), current_level, &mut help_background);
|
||||
match result {
|
||||
BattleResult::Win => {}
|
||||
BattleResult::Loss => {
|
||||
agb.obj.commit();
|
||||
agb.sfx.customise();
|
||||
if save::load_high_score() < current_level {
|
||||
save::save_high_score(&mut gba, current_level)
|
||||
.expect("Could not save high score");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
current_level += 1;
|
||||
|
||||
if current_level % 5 == 0 && dice.dice.len() < 5 {
|
||||
dice.dice.push(basic_die.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[agb::entry]
|
||||
fn entry(mut gba: agb::Gba) -> ! {
|
||||
main(gba)
|
||||
hyperspace_roll::main(gba)
|
||||
}
|
||||
|
|
994
examples/the-hat-chooses-the-wizard/src/lib.rs
Normal file
994
examples/the-hat-chooses-the-wizard/src/lib.rs
Normal file
|
@ -0,0 +1,994 @@
|
|||
#![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))]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use agb::{
|
||||
display::{
|
||||
object::{Graphics, Object, ObjectController, Tag, TagMap},
|
||||
tiled::{
|
||||
InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet,
|
||||
TileSetting, TiledMap, VRamManager,
|
||||
},
|
||||
Priority, HEIGHT, WIDTH,
|
||||
},
|
||||
fixnum::{FixedNum, Vector2D},
|
||||
input::{self, Button, ButtonController},
|
||||
sound::mixer::Frequency,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
|
||||
mod enemies;
|
||||
mod level_display;
|
||||
mod sfx;
|
||||
mod splash_screen;
|
||||
|
||||
pub struct Level {
|
||||
background: &'static [u16],
|
||||
foreground: &'static [u16],
|
||||
dimensions: Vector2D<u32>,
|
||||
collision: &'static [u32],
|
||||
|
||||
slimes: &'static [(i32, i32)],
|
||||
snails: &'static [(i32, i32)],
|
||||
enemy_stops: &'static [(i32, i32)],
|
||||
start_pos: (i32, i32),
|
||||
}
|
||||
|
||||
mod map_tiles {
|
||||
|
||||
use super::Level;
|
||||
pub const LEVELS: &[Level] = &[
|
||||
l1_1::get_level(),
|
||||
l1_2::get_level(),
|
||||
l1_3::get_level(),
|
||||
l1_4::get_level(),
|
||||
l1_5::get_level(),
|
||||
l1_7::get_level(), // these are intentionally this way round
|
||||
l1_6::get_level(),
|
||||
l1_8::get_level(),
|
||||
l2_3::get_level(), // goes 2-3, 2-1 then 2-2
|
||||
l2_1::get_level(),
|
||||
l2_2::get_level(),
|
||||
l2_4::get_level(),
|
||||
];
|
||||
|
||||
pub mod l1_1 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-1.json.rs"));
|
||||
}
|
||||
pub mod l1_2 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-2.json.rs"));
|
||||
}
|
||||
pub mod l1_3 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-3.json.rs"));
|
||||
}
|
||||
pub mod l1_4 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-4.json.rs"));
|
||||
}
|
||||
pub mod l1_5 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-5.json.rs"));
|
||||
}
|
||||
pub mod l1_6 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-6.json.rs"));
|
||||
}
|
||||
pub mod l1_7 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-7.json.rs"));
|
||||
}
|
||||
pub mod l2_1 {
|
||||
include!(concat!(env!("OUT_DIR"), "/2-1.json.rs"));
|
||||
}
|
||||
|
||||
pub mod l1_8 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-8.json.rs"));
|
||||
}
|
||||
pub mod l2_2 {
|
||||
include!(concat!(env!("OUT_DIR"), "/2-2.json.rs"));
|
||||
}
|
||||
pub mod l2_3 {
|
||||
include!(concat!(env!("OUT_DIR"), "/2-3.json.rs"));
|
||||
}
|
||||
|
||||
pub mod l2_4 {
|
||||
include!(concat!(env!("OUT_DIR"), "/2-4.json.rs"));
|
||||
}
|
||||
|
||||
pub mod tilemap {
|
||||
include!(concat!(env!("OUT_DIR"), "/tilemap.rs"));
|
||||
}
|
||||
}
|
||||
|
||||
agb::include_gfx!("gfx/tile_sheet.toml");
|
||||
|
||||
const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite");
|
||||
const TAG_MAP: &TagMap = GRAPHICS.tags();
|
||||
|
||||
const WALKING: &Tag = TAG_MAP.get("Walking");
|
||||
const JUMPING: &Tag = TAG_MAP.get("Jumping");
|
||||
const FALLING: &Tag = TAG_MAP.get("Falling");
|
||||
const PLAYER_DEATH: &Tag = TAG_MAP.get("Player Death");
|
||||
const HAT_SPIN_1: &Tag = TAG_MAP.get("HatSpin");
|
||||
const HAT_SPIN_2: &Tag = TAG_MAP.get("HatSpin2");
|
||||
const HAT_SPIN_3: &Tag = TAG_MAP.get("HatSpin3");
|
||||
|
||||
type FixedNumberType = FixedNum<10>;
|
||||
|
||||
pub struct Entity<'a> {
|
||||
sprite: Object<'a>,
|
||||
position: Vector2D<FixedNumberType>,
|
||||
velocity: Vector2D<FixedNumberType>,
|
||||
collision_mask: Vector2D<u16>,
|
||||
}
|
||||
|
||||
impl<'a> Entity<'a> {
|
||||
pub fn new(object: &'a ObjectController, collision_mask: Vector2D<u16>) -> Self {
|
||||
let dummy_sprite = object.sprite(WALKING.sprite(0));
|
||||
let mut sprite = object.object(dummy_sprite);
|
||||
sprite.set_priority(Priority::P1);
|
||||
Entity {
|
||||
sprite,
|
||||
collision_mask,
|
||||
position: (0, 0).into(),
|
||||
velocity: (0, 0).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn something_at_point<T: Fn(i32, i32) -> bool>(
|
||||
&self,
|
||||
position: Vector2D<FixedNumberType>,
|
||||
something_fn: T,
|
||||
) -> bool {
|
||||
let left = (position.x - self.collision_mask.x as i32 / 2).floor() / 8;
|
||||
let right = (position.x + self.collision_mask.x as i32 / 2 - 1).floor() / 8;
|
||||
let top = (position.y - self.collision_mask.y as i32 / 2).floor() / 8;
|
||||
let bottom = (position.y + self.collision_mask.y as i32 / 2 - 1).floor() / 8;
|
||||
|
||||
for x in left..=right {
|
||||
for y in top..=bottom {
|
||||
if something_fn(x, y) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn collision_at_point(&self, level: &Level, position: Vector2D<FixedNumberType>) -> bool {
|
||||
self.something_at_point(position, |x, y| level.collides(x, y))
|
||||
}
|
||||
|
||||
fn killision_at_point(&self, level: &Level, position: Vector2D<FixedNumberType>) -> bool {
|
||||
self.something_at_point(position, |x, y| level.kills(x, y))
|
||||
}
|
||||
|
||||
fn completion_at_point(&self, level: &Level, position: Vector2D<FixedNumberType>) -> bool {
|
||||
self.something_at_point(position, |x, y| level.wins(x, y))
|
||||
}
|
||||
|
||||
fn enemy_collision_at_point(
|
||||
&self,
|
||||
enemies: &[enemies::Enemy],
|
||||
position: Vector2D<FixedNumberType>,
|
||||
) -> bool {
|
||||
for enemy in enemies {
|
||||
if enemy.collides_with_hat(position) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// returns the distance actually moved
|
||||
fn update_position(&mut self, level: &Level) -> Vector2D<FixedNumberType> {
|
||||
let old_position = self.position;
|
||||
let x_velocity = (self.velocity.x, 0.into()).into();
|
||||
if !self.collision_at_point(level, self.position + x_velocity) {
|
||||
self.position += x_velocity;
|
||||
} else {
|
||||
self.position += self.binary_search_collision(level, (1, 0).into(), self.velocity.x);
|
||||
}
|
||||
|
||||
let y_velocity = (0.into(), self.velocity.y).into();
|
||||
if !self.collision_at_point(level, self.position + y_velocity) {
|
||||
self.position += y_velocity;
|
||||
} else {
|
||||
self.position += self.binary_search_collision(level, (0, 1).into(), self.velocity.y);
|
||||
}
|
||||
|
||||
self.position - old_position
|
||||
}
|
||||
|
||||
fn update_position_with_enemy(
|
||||
&mut self,
|
||||
level: &Level,
|
||||
enemies: &[enemies::Enemy],
|
||||
) -> (Vector2D<FixedNumberType>, bool) {
|
||||
let mut was_enemy_collision = false;
|
||||
let old_position = self.position;
|
||||
let x_velocity = (self.velocity.x, 0.into()).into();
|
||||
|
||||
if !(self.collision_at_point(level, self.position + x_velocity)
|
||||
|| self.enemy_collision_at_point(enemies, self.position + x_velocity))
|
||||
{
|
||||
self.position += x_velocity;
|
||||
} else if self.enemy_collision_at_point(enemies, self.position + x_velocity) {
|
||||
self.position -= x_velocity;
|
||||
was_enemy_collision = true;
|
||||
}
|
||||
|
||||
let y_velocity = (0.into(), self.velocity.y).into();
|
||||
if !(self.collision_at_point(level, self.position + y_velocity)
|
||||
|| self.enemy_collision_at_point(enemies, self.position + y_velocity))
|
||||
{
|
||||
self.position += y_velocity;
|
||||
} else if self.enemy_collision_at_point(enemies, self.position + y_velocity) {
|
||||
self.position -= y_velocity;
|
||||
was_enemy_collision = true;
|
||||
}
|
||||
|
||||
(self.position - old_position, was_enemy_collision)
|
||||
}
|
||||
|
||||
fn binary_search_collision(
|
||||
&self,
|
||||
level: &Level,
|
||||
unit_vector: Vector2D<FixedNumberType>,
|
||||
initial: FixedNumberType,
|
||||
) -> Vector2D<FixedNumberType> {
|
||||
let mut low: FixedNumberType = 0.into();
|
||||
let mut high = initial;
|
||||
|
||||
let one: FixedNumberType = 1.into();
|
||||
while (high - low).abs() > one / 8 {
|
||||
let mid = (low + high) / 2;
|
||||
let new_vel: Vector2D<FixedNumberType> = unit_vector * mid;
|
||||
|
||||
if self.collision_at_point(level, self.position + new_vel) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid;
|
||||
}
|
||||
}
|
||||
|
||||
unit_vector * low
|
||||
}
|
||||
|
||||
fn commit_position(&mut self, offset: Vector2D<FixedNumberType>) {
|
||||
let position = (self.position - offset).floor();
|
||||
self.sprite.set_position(position - (8, 8).into());
|
||||
if position.x < -8 || position.x > WIDTH + 8 || position.y < -8 || position.y > HEIGHT + 8 {
|
||||
self.sprite.hide();
|
||||
} else {
|
||||
self.sprite.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Map<'a, 'b> {
|
||||
background: &'a mut InfiniteScrolledMap<'b>,
|
||||
foreground: &'a mut InfiniteScrolledMap<'b>,
|
||||
position: Vector2D<FixedNumberType>,
|
||||
level: &'a Level,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Map<'a, 'b> {
|
||||
pub fn commit_position(&mut self, vram: &mut VRamManager) {
|
||||
self.background.set_pos(vram, self.position.floor());
|
||||
self.foreground.set_pos(vram, self.position.floor());
|
||||
|
||||
self.background.commit(vram);
|
||||
self.foreground.commit(vram);
|
||||
}
|
||||
|
||||
pub fn init_background(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus {
|
||||
self.background.init_partial(vram, self.position.floor())
|
||||
}
|
||||
|
||||
pub fn init_foreground(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus {
|
||||
self.foreground.init_partial(vram, self.position.floor())
|
||||
}
|
||||
}
|
||||
|
||||
impl Level {
|
||||
fn collides(&self, x: i32, y: i32) -> bool {
|
||||
self.at_point(x, y, map_tiles::tilemap::COLLISION_TILE as u32)
|
||||
}
|
||||
|
||||
fn kills(&self, x: i32, y: i32) -> bool {
|
||||
self.at_point(x, y, map_tiles::tilemap::KILL_TILE as u32)
|
||||
}
|
||||
|
||||
fn at_point(&self, x: i32, y: i32, tile: u32) -> bool {
|
||||
if (x < 0 || x >= self.dimensions.x as i32) || (y < 0 || y >= self.dimensions.y as i32) {
|
||||
return true;
|
||||
}
|
||||
let pos = (self.dimensions.x as i32 * y + x) as usize;
|
||||
let tile_foreground = self.foreground[pos];
|
||||
let tile_background = self.background[pos];
|
||||
let foreground_tile_property = self.collision[tile_foreground as usize];
|
||||
let background_tile_property = self.collision[tile_background as usize];
|
||||
foreground_tile_property == tile || background_tile_property == tile
|
||||
}
|
||||
|
||||
fn wins(&self, x: i32, y: i32) -> bool {
|
||||
self.at_point(x, y, map_tiles::tilemap::WIN_TILE as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum HatState {
|
||||
OnHead,
|
||||
Thrown,
|
||||
WizardTowards,
|
||||
}
|
||||
|
||||
struct Player<'a> {
|
||||
wizard: Entity<'a>,
|
||||
hat: Entity<'a>,
|
||||
hat_state: HatState,
|
||||
hat_left_range: bool,
|
||||
hat_slow_counter: i32,
|
||||
wizard_frame: u8,
|
||||
num_recalls: i8,
|
||||
is_on_ground: bool,
|
||||
facing: input::Tri,
|
||||
}
|
||||
|
||||
fn ping_pong(i: i32, n: i32) -> i32 {
|
||||
let cycle = 2 * (n - 1);
|
||||
let i = i % cycle;
|
||||
if i >= n {
|
||||
cycle - i
|
||||
} else {
|
||||
i
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Player<'a> {
|
||||
fn new(controller: &'a ObjectController, start_position: Vector2D<FixedNumberType>) -> Self {
|
||||
let mut wizard = Entity::new(controller, (6_u16, 14_u16).into());
|
||||
let mut hat = Entity::new(controller, (6_u16, 6_u16).into());
|
||||
|
||||
wizard
|
||||
.sprite
|
||||
.set_sprite(controller.sprite(HAT_SPIN_1.sprite(0)));
|
||||
hat.sprite
|
||||
.set_sprite(controller.sprite(HAT_SPIN_1.sprite(0)));
|
||||
|
||||
wizard.sprite.show();
|
||||
hat.sprite.show();
|
||||
|
||||
hat.sprite.set_z(-1);
|
||||
|
||||
wizard.position = start_position;
|
||||
hat.position = start_position - (0, 10).into();
|
||||
|
||||
Player {
|
||||
wizard,
|
||||
hat,
|
||||
hat_slow_counter: 0,
|
||||
hat_state: HatState::OnHead,
|
||||
hat_left_range: false,
|
||||
wizard_frame: 0,
|
||||
num_recalls: 0,
|
||||
is_on_ground: true,
|
||||
facing: input::Tri::Zero,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_frame(
|
||||
&mut self,
|
||||
input: &ButtonController,
|
||||
controller: &'a ObjectController,
|
||||
timer: i32,
|
||||
level: &Level,
|
||||
enemies: &[enemies::Enemy],
|
||||
sfx_player: &mut sfx::SfxPlayer,
|
||||
) {
|
||||
// throw or recall
|
||||
if input.is_just_pressed(Button::A) {
|
||||
if self.hat_state == HatState::OnHead {
|
||||
let direction: Vector2D<FixedNumberType> = {
|
||||
let up_down = input.y_tri() as i32;
|
||||
let left_right = if up_down == 0 {
|
||||
self.facing as i32
|
||||
} else {
|
||||
input.x_tri() as i32
|
||||
};
|
||||
(left_right, up_down).into()
|
||||
};
|
||||
|
||||
if direction != (0, 0).into() {
|
||||
let mut velocity = direction.normalise() * 5;
|
||||
if velocity.y > 0.into() {
|
||||
velocity.y *= FixedNumberType::new(4) / 3;
|
||||
}
|
||||
self.hat.velocity = velocity;
|
||||
self.hat_state = HatState::Thrown;
|
||||
|
||||
sfx_player.throw();
|
||||
}
|
||||
} else if self.hat_state == HatState::Thrown {
|
||||
self.num_recalls += 1;
|
||||
if self.num_recalls < 3 {
|
||||
self.hat.velocity = (0, 0).into();
|
||||
self.wizard.velocity = (0, 0).into();
|
||||
self.hat_state = HatState::WizardTowards;
|
||||
}
|
||||
} else if self.hat_state == HatState::WizardTowards {
|
||||
self.hat_state = HatState::Thrown;
|
||||
self.wizard.velocity /= 8;
|
||||
}
|
||||
}
|
||||
|
||||
let was_on_ground = self.is_on_ground;
|
||||
let is_on_ground = self
|
||||
.wizard
|
||||
.collision_at_point(level, self.wizard.position + (0, 1).into());
|
||||
|
||||
if is_on_ground && !was_on_ground && self.wizard.velocity.y > 1.into() {
|
||||
sfx_player.land();
|
||||
}
|
||||
self.is_on_ground = is_on_ground;
|
||||
|
||||
if self.hat_state != HatState::WizardTowards {
|
||||
if is_on_ground {
|
||||
self.num_recalls = 0;
|
||||
}
|
||||
|
||||
if is_on_ground {
|
||||
self.wizard.velocity.x += FixedNumberType::new(input.x_tri() as i32) / 16;
|
||||
self.wizard.velocity = self.wizard.velocity * 54 / 64;
|
||||
if input.is_just_pressed(Button::B) {
|
||||
self.wizard.velocity.y = -FixedNumberType::new(3) / 2;
|
||||
sfx_player.jump();
|
||||
}
|
||||
} else {
|
||||
self.wizard.velocity.x += FixedNumberType::new(input.x_tri() as i32) / 64;
|
||||
self.wizard.velocity = self.wizard.velocity * 63 / 64;
|
||||
let gravity: Vector2D<FixedNumberType> = (0, 1).into();
|
||||
let gravity = gravity / 16;
|
||||
self.wizard.velocity += gravity;
|
||||
}
|
||||
|
||||
self.wizard.velocity = self.wizard.update_position(level);
|
||||
|
||||
if self.wizard.velocity.x.abs() > 0.into() {
|
||||
let offset = (ping_pong(timer / 16, 4)) as usize;
|
||||
self.wizard_frame = offset as u8;
|
||||
|
||||
let frame = WALKING.animation_sprite(offset);
|
||||
let sprite = controller.sprite(frame);
|
||||
|
||||
self.wizard.sprite.set_sprite(sprite);
|
||||
}
|
||||
|
||||
if self.wizard.velocity.y < -FixedNumberType::new(1) / 16 {
|
||||
// going up
|
||||
self.wizard_frame = 5;
|
||||
|
||||
let frame = JUMPING.animation_sprite(0);
|
||||
let sprite = controller.sprite(frame);
|
||||
|
||||
self.wizard.sprite.set_sprite(sprite);
|
||||
} else if self.wizard.velocity.y > FixedNumberType::new(1) / 16 {
|
||||
// going down
|
||||
let offset = if self.wizard.velocity.y * 2 > 3.into() {
|
||||
(timer / 4) as usize
|
||||
} else {
|
||||
// Don't flap beard unless going quickly
|
||||
0
|
||||
};
|
||||
|
||||
self.wizard_frame = 0;
|
||||
|
||||
let frame = FALLING.animation_sprite(offset);
|
||||
let sprite = controller.sprite(frame);
|
||||
|
||||
self.wizard.sprite.set_sprite(sprite);
|
||||
}
|
||||
|
||||
if input.x_tri() != agb::input::Tri::Zero {
|
||||
self.facing = input.x_tri();
|
||||
}
|
||||
}
|
||||
|
||||
let hat_base_tile = match self.num_recalls {
|
||||
0 => HAT_SPIN_1,
|
||||
1 => HAT_SPIN_2,
|
||||
_ => HAT_SPIN_3,
|
||||
};
|
||||
|
||||
let hat_resting_position = match self.wizard_frame {
|
||||
1 | 2 => (0, 9).into(),
|
||||
5 => (0, 10).into(),
|
||||
_ => (0, 8).into(),
|
||||
};
|
||||
|
||||
match self.facing {
|
||||
agb::input::Tri::Negative => {
|
||||
self.wizard.sprite.set_hflip(true);
|
||||
self.hat
|
||||
.sprite
|
||||
.set_sprite(controller.sprite(hat_base_tile.sprite(5)));
|
||||
}
|
||||
agb::input::Tri::Positive => {
|
||||
self.wizard.sprite.set_hflip(false);
|
||||
self.hat
|
||||
.sprite
|
||||
.set_sprite(controller.sprite(hat_base_tile.sprite(0)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.hat_state {
|
||||
HatState::Thrown => {
|
||||
// hat is thrown, make hat move towards wizard
|
||||
let distance_vector =
|
||||
self.wizard.position - self.hat.position - hat_resting_position;
|
||||
let distance = distance_vector.magnitude();
|
||||
let direction = if distance == 0.into() {
|
||||
(0, 0).into()
|
||||
} else {
|
||||
distance_vector / distance
|
||||
};
|
||||
|
||||
let hat_sprite_divider = match self.num_recalls {
|
||||
0 => 1,
|
||||
1 => 2,
|
||||
_ => 4,
|
||||
};
|
||||
|
||||
let hat_sprite_offset = (timer / hat_sprite_divider) as usize;
|
||||
|
||||
self.hat.sprite.set_sprite(
|
||||
controller.sprite(hat_base_tile.animation_sprite(hat_sprite_offset)),
|
||||
);
|
||||
|
||||
if self.hat_slow_counter < 30 && self.hat.velocity.magnitude() < 2.into() {
|
||||
self.hat.velocity = (0, 0).into();
|
||||
self.hat_slow_counter += 1;
|
||||
} else {
|
||||
self.hat.velocity += direction / 4;
|
||||
}
|
||||
let (new_velocity, enemy_collision) =
|
||||
self.hat.update_position_with_enemy(level, enemies);
|
||||
self.hat.velocity = new_velocity;
|
||||
|
||||
if enemy_collision {
|
||||
sfx_player.snail_hat_bounce();
|
||||
}
|
||||
|
||||
if distance > 16.into() {
|
||||
self.hat_left_range = true;
|
||||
}
|
||||
if self.hat_left_range && distance < 16.into() {
|
||||
sfx_player.catch();
|
||||
self.hat_state = HatState::OnHead;
|
||||
}
|
||||
}
|
||||
HatState::OnHead => {
|
||||
// hat is on head, place hat on head
|
||||
self.hat_slow_counter = 0;
|
||||
self.hat_left_range = false;
|
||||
self.hat.position = self.wizard.position - hat_resting_position;
|
||||
}
|
||||
HatState::WizardTowards => {
|
||||
self.hat.sprite.set_sprite(
|
||||
controller.sprite(hat_base_tile.animation_sprite(timer as usize / 2)),
|
||||
);
|
||||
let distance_vector =
|
||||
self.hat.position - self.wizard.position + hat_resting_position;
|
||||
let distance = distance_vector.magnitude();
|
||||
if distance != 0.into() {
|
||||
let v = self.wizard.velocity.magnitude() + 1;
|
||||
self.wizard.velocity = distance_vector / distance * v;
|
||||
}
|
||||
self.wizard.velocity = self.wizard.update_position(level);
|
||||
if distance < 16.into() {
|
||||
self.wizard.velocity /= 8;
|
||||
self.hat_state = HatState::OnHead;
|
||||
sfx_player.catch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PlayingLevel<'a, 'b> {
|
||||
timer: i32,
|
||||
background: Map<'a, 'b>,
|
||||
input: ButtonController,
|
||||
player: Player<'a>,
|
||||
|
||||
enemies: [enemies::Enemy<'a>; 16],
|
||||
}
|
||||
|
||||
enum UpdateState {
|
||||
Normal,
|
||||
Dead,
|
||||
Complete,
|
||||
}
|
||||
|
||||
impl<'a, 'b> PlayingLevel<'a, 'b> {
|
||||
fn open_level(
|
||||
level: &'a Level,
|
||||
object_control: &'a ObjectController,
|
||||
background: &'a mut InfiniteScrolledMap<'b>,
|
||||
foreground: &'a mut InfiniteScrolledMap<'b>,
|
||||
input: ButtonController,
|
||||
) -> Self {
|
||||
let mut e: [enemies::Enemy<'a>; 16] = Default::default();
|
||||
let mut enemy_count = 0;
|
||||
for &slime in level.slimes {
|
||||
e[enemy_count] = enemies::Enemy::new_slime(object_control, slime.into());
|
||||
enemy_count += 1;
|
||||
}
|
||||
|
||||
for &snail in level.snails {
|
||||
e[enemy_count] = enemies::Enemy::new_snail(object_control, snail.into());
|
||||
enemy_count += 1;
|
||||
}
|
||||
|
||||
let start_pos: Vector2D<FixedNumberType> = level.start_pos.into();
|
||||
|
||||
let background_position = (
|
||||
(start_pos.x - WIDTH / 2)
|
||||
.clamp(0.into(), ((level.dimensions.x * 8) as i32 - WIDTH).into()),
|
||||
(start_pos.y - HEIGHT / 2)
|
||||
.clamp(0.into(), ((level.dimensions.y * 8) as i32 - HEIGHT).into()),
|
||||
)
|
||||
.into();
|
||||
|
||||
PlayingLevel {
|
||||
timer: 0,
|
||||
background: Map {
|
||||
background,
|
||||
foreground,
|
||||
level,
|
||||
position: background_position,
|
||||
},
|
||||
player: Player::new(object_control, start_pos),
|
||||
input,
|
||||
enemies: e,
|
||||
}
|
||||
}
|
||||
|
||||
fn show_backgrounds(&mut self) {
|
||||
self.background.background.show();
|
||||
self.background.foreground.show();
|
||||
}
|
||||
|
||||
fn hide_backgrounds(&mut self) {
|
||||
self.background.background.hide();
|
||||
self.background.foreground.hide();
|
||||
}
|
||||
|
||||
fn clear_backgrounds(&mut self, vram: &mut VRamManager) {
|
||||
self.background.background.clear(vram);
|
||||
self.background.foreground.clear(vram);
|
||||
}
|
||||
|
||||
fn dead_start(&mut self) {
|
||||
self.player.wizard.velocity = (0, -1).into();
|
||||
self.player.wizard.sprite.set_priority(Priority::P0);
|
||||
}
|
||||
|
||||
fn dead_update(&mut self, controller: &'a ObjectController) -> bool {
|
||||
self.timer += 1;
|
||||
|
||||
let frame = PLAYER_DEATH.animation_sprite(self.timer as usize / 8);
|
||||
let sprite = controller.sprite(frame);
|
||||
|
||||
self.player.wizard.velocity += (0.into(), FixedNumberType::new(1) / 32).into();
|
||||
self.player.wizard.position += self.player.wizard.velocity;
|
||||
self.player.wizard.sprite.set_sprite(sprite);
|
||||
|
||||
self.player.wizard.commit_position(self.background.position);
|
||||
|
||||
self.player.wizard.position.y - self.background.position.y < (HEIGHT + 8).into()
|
||||
}
|
||||
|
||||
fn update_frame(
|
||||
&mut self,
|
||||
sfx_player: &mut sfx::SfxPlayer,
|
||||
vram: &mut VRamManager,
|
||||
controller: &'a ObjectController,
|
||||
) -> UpdateState {
|
||||
self.timer += 1;
|
||||
self.input.update();
|
||||
|
||||
let mut player_dead = false;
|
||||
|
||||
self.player.update_frame(
|
||||
&self.input,
|
||||
controller,
|
||||
self.timer,
|
||||
self.background.level,
|
||||
&self.enemies,
|
||||
sfx_player,
|
||||
);
|
||||
|
||||
for enemy in self.enemies.iter_mut() {
|
||||
match enemy.update(
|
||||
controller,
|
||||
self.background.level,
|
||||
self.player.wizard.position,
|
||||
self.player.hat_state,
|
||||
self.timer,
|
||||
sfx_player,
|
||||
) {
|
||||
enemies::EnemyUpdateState::KillPlayer => player_dead = true,
|
||||
enemies::EnemyUpdateState::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.background.position = self.get_next_map_position();
|
||||
self.background.commit_position(vram);
|
||||
|
||||
self.player.wizard.commit_position(self.background.position);
|
||||
self.player.hat.commit_position(self.background.position);
|
||||
|
||||
for enemy in self.enemies.iter_mut() {
|
||||
enemy.commit(self.background.position);
|
||||
}
|
||||
|
||||
player_dead |= self
|
||||
.player
|
||||
.wizard
|
||||
.killision_at_point(self.background.level, self.player.wizard.position);
|
||||
if player_dead {
|
||||
UpdateState::Dead
|
||||
} else if self
|
||||
.player
|
||||
.wizard
|
||||
.completion_at_point(self.background.level, self.player.wizard.position)
|
||||
{
|
||||
UpdateState::Complete
|
||||
} else {
|
||||
UpdateState::Normal
|
||||
}
|
||||
}
|
||||
|
||||
fn get_next_map_position(&self) -> Vector2D<FixedNumberType> {
|
||||
// want to ensure the player and the hat are visible if possible, so try to position the map
|
||||
// so the centre is at the average position. But give the player some extra priority
|
||||
let hat_pos = self.player.hat.position.floor();
|
||||
let player_pos = self.player.wizard.position.floor();
|
||||
|
||||
let new_target_position = (hat_pos + player_pos * 3) / 4;
|
||||
|
||||
let screen: Vector2D<i32> = (WIDTH, HEIGHT).into();
|
||||
let half_screen = screen / 2;
|
||||
let current_centre = self.background.position.floor() + half_screen;
|
||||
|
||||
let mut target_position = ((current_centre * 3 + new_target_position) / 4) - half_screen;
|
||||
|
||||
target_position.x = target_position.x.clamp(
|
||||
0,
|
||||
(self.background.level.dimensions.x * 8 - (WIDTH as u32)) as i32,
|
||||
);
|
||||
target_position.y = target_position.y.clamp(
|
||||
0,
|
||||
(self.background.level.dimensions.y * 8 - (HEIGHT as u32)) as i32,
|
||||
);
|
||||
|
||||
target_position.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main(mut agb: agb::Gba) -> ! {
|
||||
let (tiled, mut vram) = agb.display.video.tiled0();
|
||||
vram.set_background_palettes(tile_sheet::PALETTES);
|
||||
let mut splash_screen = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
||||
let mut world_display = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
||||
|
||||
let tileset = TileSet::new(tile_sheet::background.tiles, TileFormat::FourBpp);
|
||||
|
||||
for y in 0..32u16 {
|
||||
for x in 0..32u16 {
|
||||
world_display.set_tile(
|
||||
&mut vram,
|
||||
(x, y).into(),
|
||||
&tileset,
|
||||
TileSetting::from_raw(level_display::BLANK),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
world_display.commit(&mut vram);
|
||||
world_display.show();
|
||||
|
||||
splash_screen::show_splash_screen(
|
||||
splash_screen::SplashScreen::Start,
|
||||
None,
|
||||
None,
|
||||
&mut splash_screen,
|
||||
&mut vram,
|
||||
);
|
||||
|
||||
loop {
|
||||
world_display.commit(&mut vram);
|
||||
world_display.show();
|
||||
|
||||
vram.set_background_palettes(tile_sheet::PALETTES);
|
||||
|
||||
let object = agb.display.object.get();
|
||||
let mut mixer = agb.mixer.mixer(Frequency::Hz10512);
|
||||
|
||||
mixer.enable();
|
||||
let mut music_box = sfx::MusicBox::new();
|
||||
|
||||
let vblank = agb::interrupt::VBlank::get();
|
||||
let mut current_level = 0;
|
||||
|
||||
loop {
|
||||
if current_level == map_tiles::LEVELS.len() as u32 {
|
||||
break;
|
||||
}
|
||||
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
|
||||
level_display::write_level(
|
||||
&mut world_display,
|
||||
current_level / 8 + 1,
|
||||
current_level % 8 + 1,
|
||||
&tileset,
|
||||
&mut vram,
|
||||
);
|
||||
|
||||
world_display.commit(&mut vram);
|
||||
world_display.show();
|
||||
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
|
||||
let map_current_level = current_level;
|
||||
let mut background = InfiniteScrolledMap::new(
|
||||
tiled.background(Priority::P2, RegularBackgroundSize::Background32x64),
|
||||
Box::new(|pos: Vector2D<i32>| {
|
||||
let level = &map_tiles::LEVELS[map_current_level as usize];
|
||||
(
|
||||
&tileset,
|
||||
TileSetting::from_raw(
|
||||
*level
|
||||
.background
|
||||
.get((pos.y * level.dimensions.x as i32 + pos.x) as usize)
|
||||
.unwrap_or(&0),
|
||||
),
|
||||
)
|
||||
}),
|
||||
);
|
||||
let mut foreground = InfiniteScrolledMap::new(
|
||||
tiled.background(Priority::P0, RegularBackgroundSize::Background64x32),
|
||||
Box::new(|pos: Vector2D<i32>| {
|
||||
let level = &map_tiles::LEVELS[map_current_level as usize];
|
||||
(
|
||||
&tileset,
|
||||
TileSetting::from_raw(
|
||||
*level
|
||||
.foreground
|
||||
.get((pos.y * level.dimensions.x as i32 + pos.x) as usize)
|
||||
.unwrap_or(&0),
|
||||
),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
let mut level = PlayingLevel::open_level(
|
||||
&map_tiles::LEVELS[current_level as usize],
|
||||
&object,
|
||||
&mut background,
|
||||
&mut foreground,
|
||||
agb::input::ButtonController::new(),
|
||||
);
|
||||
|
||||
while level.background.init_background(&mut vram) != PartialUpdateStatus::Done {
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
}
|
||||
|
||||
while level.background.init_foreground(&mut vram) != PartialUpdateStatus::Done {
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
}
|
||||
|
||||
for _ in 0..20 {
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
}
|
||||
|
||||
object.commit();
|
||||
|
||||
level.show_backgrounds();
|
||||
|
||||
world_display.hide();
|
||||
|
||||
loop {
|
||||
match level.update_frame(
|
||||
&mut sfx::SfxPlayer::new(&mut mixer, &music_box),
|
||||
&mut vram,
|
||||
&object,
|
||||
) {
|
||||
UpdateState::Normal => {}
|
||||
UpdateState::Dead => {
|
||||
level.dead_start();
|
||||
while level.dead_update(&object) {
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
object.commit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
UpdateState::Complete => {
|
||||
current_level += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
object.commit();
|
||||
}
|
||||
|
||||
level.hide_backgrounds();
|
||||
level.clear_backgrounds(&mut vram);
|
||||
}
|
||||
|
||||
object.commit();
|
||||
|
||||
splash_screen::show_splash_screen(
|
||||
splash_screen::SplashScreen::End,
|
||||
Some(&mut mixer),
|
||||
Some(&mut music_box),
|
||||
&mut splash_screen,
|
||||
&mut vram,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use agb::Gba;
|
||||
|
||||
#[test_case]
|
||||
fn test_ping_pong(_gba: &mut Gba) {
|
||||
let test_cases = [
|
||||
[0, 2, 0],
|
||||
[0, 7, 0],
|
||||
[1, 2, 1],
|
||||
[2, 2, 0],
|
||||
[3, 2, 1],
|
||||
[4, 2, 0],
|
||||
];
|
||||
|
||||
for test_case in test_cases {
|
||||
assert_eq!(
|
||||
ping_pong(test_case[0], test_case[1]),
|
||||
test_case[2],
|
||||
"Expected ping_pong({}, {}) to equal {}",
|
||||
test_case[0],
|
||||
test_case[1],
|
||||
test_case[2],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,996 +4,7 @@
|
|||
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
|
||||
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use agb::{
|
||||
display::{
|
||||
object::{Graphics, Object, ObjectController, Tag, TagMap},
|
||||
tiled::{
|
||||
InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet,
|
||||
TileSetting, TiledMap, VRamManager,
|
||||
},
|
||||
Priority, HEIGHT, WIDTH,
|
||||
},
|
||||
fixnum::{FixedNum, Vector2D},
|
||||
input::{self, Button, ButtonController},
|
||||
sound::mixer::Frequency,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
|
||||
mod enemies;
|
||||
mod level_display;
|
||||
mod sfx;
|
||||
mod splash_screen;
|
||||
|
||||
pub struct Level {
|
||||
background: &'static [u16],
|
||||
foreground: &'static [u16],
|
||||
dimensions: Vector2D<u32>,
|
||||
collision: &'static [u32],
|
||||
|
||||
slimes: &'static [(i32, i32)],
|
||||
snails: &'static [(i32, i32)],
|
||||
enemy_stops: &'static [(i32, i32)],
|
||||
start_pos: (i32, i32),
|
||||
}
|
||||
|
||||
mod map_tiles {
|
||||
|
||||
use super::Level;
|
||||
pub const LEVELS: &[Level] = &[
|
||||
l1_1::get_level(),
|
||||
l1_2::get_level(),
|
||||
l1_3::get_level(),
|
||||
l1_4::get_level(),
|
||||
l1_5::get_level(),
|
||||
l1_7::get_level(), // these are intentionally this way round
|
||||
l1_6::get_level(),
|
||||
l1_8::get_level(),
|
||||
l2_3::get_level(), // goes 2-3, 2-1 then 2-2
|
||||
l2_1::get_level(),
|
||||
l2_2::get_level(),
|
||||
l2_4::get_level(),
|
||||
];
|
||||
|
||||
pub mod l1_1 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-1.json.rs"));
|
||||
}
|
||||
pub mod l1_2 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-2.json.rs"));
|
||||
}
|
||||
pub mod l1_3 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-3.json.rs"));
|
||||
}
|
||||
pub mod l1_4 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-4.json.rs"));
|
||||
}
|
||||
pub mod l1_5 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-5.json.rs"));
|
||||
}
|
||||
pub mod l1_6 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-6.json.rs"));
|
||||
}
|
||||
pub mod l1_7 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-7.json.rs"));
|
||||
}
|
||||
pub mod l2_1 {
|
||||
include!(concat!(env!("OUT_DIR"), "/2-1.json.rs"));
|
||||
}
|
||||
|
||||
pub mod l1_8 {
|
||||
include!(concat!(env!("OUT_DIR"), "/1-8.json.rs"));
|
||||
}
|
||||
pub mod l2_2 {
|
||||
include!(concat!(env!("OUT_DIR"), "/2-2.json.rs"));
|
||||
}
|
||||
pub mod l2_3 {
|
||||
include!(concat!(env!("OUT_DIR"), "/2-3.json.rs"));
|
||||
}
|
||||
|
||||
pub mod l2_4 {
|
||||
include!(concat!(env!("OUT_DIR"), "/2-4.json.rs"));
|
||||
}
|
||||
|
||||
pub mod tilemap {
|
||||
include!(concat!(env!("OUT_DIR"), "/tilemap.rs"));
|
||||
}
|
||||
}
|
||||
|
||||
agb::include_gfx!("gfx/tile_sheet.toml");
|
||||
|
||||
const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite");
|
||||
const TAG_MAP: &TagMap = GRAPHICS.tags();
|
||||
|
||||
const WALKING: &Tag = TAG_MAP.get("Walking");
|
||||
const JUMPING: &Tag = TAG_MAP.get("Jumping");
|
||||
const FALLING: &Tag = TAG_MAP.get("Falling");
|
||||
const PLAYER_DEATH: &Tag = TAG_MAP.get("Player Death");
|
||||
const HAT_SPIN_1: &Tag = TAG_MAP.get("HatSpin");
|
||||
const HAT_SPIN_2: &Tag = TAG_MAP.get("HatSpin2");
|
||||
const HAT_SPIN_3: &Tag = TAG_MAP.get("HatSpin3");
|
||||
|
||||
type FixedNumberType = FixedNum<10>;
|
||||
|
||||
pub struct Entity<'a> {
|
||||
sprite: Object<'a>,
|
||||
position: Vector2D<FixedNumberType>,
|
||||
velocity: Vector2D<FixedNumberType>,
|
||||
collision_mask: Vector2D<u16>,
|
||||
}
|
||||
|
||||
impl<'a> Entity<'a> {
|
||||
pub fn new(object: &'a ObjectController, collision_mask: Vector2D<u16>) -> Self {
|
||||
let dummy_sprite = object.sprite(WALKING.sprite(0));
|
||||
let mut sprite = object.object(dummy_sprite);
|
||||
sprite.set_priority(Priority::P1);
|
||||
Entity {
|
||||
sprite,
|
||||
collision_mask,
|
||||
position: (0, 0).into(),
|
||||
velocity: (0, 0).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn something_at_point<T: Fn(i32, i32) -> bool>(
|
||||
&self,
|
||||
position: Vector2D<FixedNumberType>,
|
||||
something_fn: T,
|
||||
) -> bool {
|
||||
let left = (position.x - self.collision_mask.x as i32 / 2).floor() / 8;
|
||||
let right = (position.x + self.collision_mask.x as i32 / 2 - 1).floor() / 8;
|
||||
let top = (position.y - self.collision_mask.y as i32 / 2).floor() / 8;
|
||||
let bottom = (position.y + self.collision_mask.y as i32 / 2 - 1).floor() / 8;
|
||||
|
||||
for x in left..=right {
|
||||
for y in top..=bottom {
|
||||
if something_fn(x, y) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn collision_at_point(&self, level: &Level, position: Vector2D<FixedNumberType>) -> bool {
|
||||
self.something_at_point(position, |x, y| level.collides(x, y))
|
||||
}
|
||||
|
||||
fn killision_at_point(&self, level: &Level, position: Vector2D<FixedNumberType>) -> bool {
|
||||
self.something_at_point(position, |x, y| level.kills(x, y))
|
||||
}
|
||||
|
||||
fn completion_at_point(&self, level: &Level, position: Vector2D<FixedNumberType>) -> bool {
|
||||
self.something_at_point(position, |x, y| level.wins(x, y))
|
||||
}
|
||||
|
||||
fn enemy_collision_at_point(
|
||||
&self,
|
||||
enemies: &[enemies::Enemy],
|
||||
position: Vector2D<FixedNumberType>,
|
||||
) -> bool {
|
||||
for enemy in enemies {
|
||||
if enemy.collides_with_hat(position) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// returns the distance actually moved
|
||||
fn update_position(&mut self, level: &Level) -> Vector2D<FixedNumberType> {
|
||||
let old_position = self.position;
|
||||
let x_velocity = (self.velocity.x, 0.into()).into();
|
||||
if !self.collision_at_point(level, self.position + x_velocity) {
|
||||
self.position += x_velocity;
|
||||
} else {
|
||||
self.position += self.binary_search_collision(level, (1, 0).into(), self.velocity.x);
|
||||
}
|
||||
|
||||
let y_velocity = (0.into(), self.velocity.y).into();
|
||||
if !self.collision_at_point(level, self.position + y_velocity) {
|
||||
self.position += y_velocity;
|
||||
} else {
|
||||
self.position += self.binary_search_collision(level, (0, 1).into(), self.velocity.y);
|
||||
}
|
||||
|
||||
self.position - old_position
|
||||
}
|
||||
|
||||
fn update_position_with_enemy(
|
||||
&mut self,
|
||||
level: &Level,
|
||||
enemies: &[enemies::Enemy],
|
||||
) -> (Vector2D<FixedNumberType>, bool) {
|
||||
let mut was_enemy_collision = false;
|
||||
let old_position = self.position;
|
||||
let x_velocity = (self.velocity.x, 0.into()).into();
|
||||
|
||||
if !(self.collision_at_point(level, self.position + x_velocity)
|
||||
|| self.enemy_collision_at_point(enemies, self.position + x_velocity))
|
||||
{
|
||||
self.position += x_velocity;
|
||||
} else if self.enemy_collision_at_point(enemies, self.position + x_velocity) {
|
||||
self.position -= x_velocity;
|
||||
was_enemy_collision = true;
|
||||
}
|
||||
|
||||
let y_velocity = (0.into(), self.velocity.y).into();
|
||||
if !(self.collision_at_point(level, self.position + y_velocity)
|
||||
|| self.enemy_collision_at_point(enemies, self.position + y_velocity))
|
||||
{
|
||||
self.position += y_velocity;
|
||||
} else if self.enemy_collision_at_point(enemies, self.position + y_velocity) {
|
||||
self.position -= y_velocity;
|
||||
was_enemy_collision = true;
|
||||
}
|
||||
|
||||
(self.position - old_position, was_enemy_collision)
|
||||
}
|
||||
|
||||
fn binary_search_collision(
|
||||
&self,
|
||||
level: &Level,
|
||||
unit_vector: Vector2D<FixedNumberType>,
|
||||
initial: FixedNumberType,
|
||||
) -> Vector2D<FixedNumberType> {
|
||||
let mut low: FixedNumberType = 0.into();
|
||||
let mut high = initial;
|
||||
|
||||
let one: FixedNumberType = 1.into();
|
||||
while (high - low).abs() > one / 8 {
|
||||
let mid = (low + high) / 2;
|
||||
let new_vel: Vector2D<FixedNumberType> = unit_vector * mid;
|
||||
|
||||
if self.collision_at_point(level, self.position + new_vel) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid;
|
||||
}
|
||||
}
|
||||
|
||||
unit_vector * low
|
||||
}
|
||||
|
||||
fn commit_position(&mut self, offset: Vector2D<FixedNumberType>) {
|
||||
let position = (self.position - offset).floor();
|
||||
self.sprite.set_position(position - (8, 8).into());
|
||||
if position.x < -8 || position.x > WIDTH + 8 || position.y < -8 || position.y > HEIGHT + 8 {
|
||||
self.sprite.hide();
|
||||
} else {
|
||||
self.sprite.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Map<'a, 'b> {
|
||||
background: &'a mut InfiniteScrolledMap<'b>,
|
||||
foreground: &'a mut InfiniteScrolledMap<'b>,
|
||||
position: Vector2D<FixedNumberType>,
|
||||
level: &'a Level,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Map<'a, 'b> {
|
||||
pub fn commit_position(&mut self, vram: &mut VRamManager) {
|
||||
self.background.set_pos(vram, self.position.floor());
|
||||
self.foreground.set_pos(vram, self.position.floor());
|
||||
|
||||
self.background.commit(vram);
|
||||
self.foreground.commit(vram);
|
||||
}
|
||||
|
||||
pub fn init_background(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus {
|
||||
self.background.init_partial(vram, self.position.floor())
|
||||
}
|
||||
|
||||
pub fn init_foreground(&mut self, vram: &mut VRamManager) -> PartialUpdateStatus {
|
||||
self.foreground.init_partial(vram, self.position.floor())
|
||||
}
|
||||
}
|
||||
|
||||
impl Level {
|
||||
fn collides(&self, x: i32, y: i32) -> bool {
|
||||
self.at_point(x, y, map_tiles::tilemap::COLLISION_TILE as u32)
|
||||
}
|
||||
|
||||
fn kills(&self, x: i32, y: i32) -> bool {
|
||||
self.at_point(x, y, map_tiles::tilemap::KILL_TILE as u32)
|
||||
}
|
||||
|
||||
fn at_point(&self, x: i32, y: i32, tile: u32) -> bool {
|
||||
if (x < 0 || x >= self.dimensions.x as i32) || (y < 0 || y >= self.dimensions.y as i32) {
|
||||
return true;
|
||||
}
|
||||
let pos = (self.dimensions.x as i32 * y + x) as usize;
|
||||
let tile_foreground = self.foreground[pos];
|
||||
let tile_background = self.background[pos];
|
||||
let foreground_tile_property = self.collision[tile_foreground as usize];
|
||||
let background_tile_property = self.collision[tile_background as usize];
|
||||
foreground_tile_property == tile || background_tile_property == tile
|
||||
}
|
||||
|
||||
fn wins(&self, x: i32, y: i32) -> bool {
|
||||
self.at_point(x, y, map_tiles::tilemap::WIN_TILE as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum HatState {
|
||||
OnHead,
|
||||
Thrown,
|
||||
WizardTowards,
|
||||
}
|
||||
|
||||
struct Player<'a> {
|
||||
wizard: Entity<'a>,
|
||||
hat: Entity<'a>,
|
||||
hat_state: HatState,
|
||||
hat_left_range: bool,
|
||||
hat_slow_counter: i32,
|
||||
wizard_frame: u8,
|
||||
num_recalls: i8,
|
||||
is_on_ground: bool,
|
||||
facing: input::Tri,
|
||||
}
|
||||
|
||||
fn ping_pong(i: i32, n: i32) -> i32 {
|
||||
let cycle = 2 * (n - 1);
|
||||
let i = i % cycle;
|
||||
if i >= n {
|
||||
cycle - i
|
||||
} else {
|
||||
i
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Player<'a> {
|
||||
fn new(controller: &'a ObjectController, start_position: Vector2D<FixedNumberType>) -> Self {
|
||||
let mut wizard = Entity::new(controller, (6_u16, 14_u16).into());
|
||||
let mut hat = Entity::new(controller, (6_u16, 6_u16).into());
|
||||
|
||||
wizard
|
||||
.sprite
|
||||
.set_sprite(controller.sprite(HAT_SPIN_1.sprite(0)));
|
||||
hat.sprite
|
||||
.set_sprite(controller.sprite(HAT_SPIN_1.sprite(0)));
|
||||
|
||||
wizard.sprite.show();
|
||||
hat.sprite.show();
|
||||
|
||||
hat.sprite.set_z(-1);
|
||||
|
||||
wizard.position = start_position;
|
||||
hat.position = start_position - (0, 10).into();
|
||||
|
||||
Player {
|
||||
wizard,
|
||||
hat,
|
||||
hat_slow_counter: 0,
|
||||
hat_state: HatState::OnHead,
|
||||
hat_left_range: false,
|
||||
wizard_frame: 0,
|
||||
num_recalls: 0,
|
||||
is_on_ground: true,
|
||||
facing: input::Tri::Zero,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_frame(
|
||||
&mut self,
|
||||
input: &ButtonController,
|
||||
controller: &'a ObjectController,
|
||||
timer: i32,
|
||||
level: &Level,
|
||||
enemies: &[enemies::Enemy],
|
||||
sfx_player: &mut sfx::SfxPlayer,
|
||||
) {
|
||||
// throw or recall
|
||||
if input.is_just_pressed(Button::A) {
|
||||
if self.hat_state == HatState::OnHead {
|
||||
let direction: Vector2D<FixedNumberType> = {
|
||||
let up_down = input.y_tri() as i32;
|
||||
let left_right = if up_down == 0 {
|
||||
self.facing as i32
|
||||
} else {
|
||||
input.x_tri() as i32
|
||||
};
|
||||
(left_right, up_down).into()
|
||||
};
|
||||
|
||||
if direction != (0, 0).into() {
|
||||
let mut velocity = direction.normalise() * 5;
|
||||
if velocity.y > 0.into() {
|
||||
velocity.y *= FixedNumberType::new(4) / 3;
|
||||
}
|
||||
self.hat.velocity = velocity;
|
||||
self.hat_state = HatState::Thrown;
|
||||
|
||||
sfx_player.throw();
|
||||
}
|
||||
} else if self.hat_state == HatState::Thrown {
|
||||
self.num_recalls += 1;
|
||||
if self.num_recalls < 3 {
|
||||
self.hat.velocity = (0, 0).into();
|
||||
self.wizard.velocity = (0, 0).into();
|
||||
self.hat_state = HatState::WizardTowards;
|
||||
}
|
||||
} else if self.hat_state == HatState::WizardTowards {
|
||||
self.hat_state = HatState::Thrown;
|
||||
self.wizard.velocity /= 8;
|
||||
}
|
||||
}
|
||||
|
||||
let was_on_ground = self.is_on_ground;
|
||||
let is_on_ground = self
|
||||
.wizard
|
||||
.collision_at_point(level, self.wizard.position + (0, 1).into());
|
||||
|
||||
if is_on_ground && !was_on_ground && self.wizard.velocity.y > 1.into() {
|
||||
sfx_player.land();
|
||||
}
|
||||
self.is_on_ground = is_on_ground;
|
||||
|
||||
if self.hat_state != HatState::WizardTowards {
|
||||
if is_on_ground {
|
||||
self.num_recalls = 0;
|
||||
}
|
||||
|
||||
if is_on_ground {
|
||||
self.wizard.velocity.x += FixedNumberType::new(input.x_tri() as i32) / 16;
|
||||
self.wizard.velocity = self.wizard.velocity * 54 / 64;
|
||||
if input.is_just_pressed(Button::B) {
|
||||
self.wizard.velocity.y = -FixedNumberType::new(3) / 2;
|
||||
sfx_player.jump();
|
||||
}
|
||||
} else {
|
||||
self.wizard.velocity.x += FixedNumberType::new(input.x_tri() as i32) / 64;
|
||||
self.wizard.velocity = self.wizard.velocity * 63 / 64;
|
||||
let gravity: Vector2D<FixedNumberType> = (0, 1).into();
|
||||
let gravity = gravity / 16;
|
||||
self.wizard.velocity += gravity;
|
||||
}
|
||||
|
||||
self.wizard.velocity = self.wizard.update_position(level);
|
||||
|
||||
if self.wizard.velocity.x.abs() > 0.into() {
|
||||
let offset = (ping_pong(timer / 16, 4)) as usize;
|
||||
self.wizard_frame = offset as u8;
|
||||
|
||||
let frame = WALKING.animation_sprite(offset);
|
||||
let sprite = controller.sprite(frame);
|
||||
|
||||
self.wizard.sprite.set_sprite(sprite);
|
||||
}
|
||||
|
||||
if self.wizard.velocity.y < -FixedNumberType::new(1) / 16 {
|
||||
// going up
|
||||
self.wizard_frame = 5;
|
||||
|
||||
let frame = JUMPING.animation_sprite(0);
|
||||
let sprite = controller.sprite(frame);
|
||||
|
||||
self.wizard.sprite.set_sprite(sprite);
|
||||
} else if self.wizard.velocity.y > FixedNumberType::new(1) / 16 {
|
||||
// going down
|
||||
let offset = if self.wizard.velocity.y * 2 > 3.into() {
|
||||
(timer / 4) as usize
|
||||
} else {
|
||||
// Don't flap beard unless going quickly
|
||||
0
|
||||
};
|
||||
|
||||
self.wizard_frame = 0;
|
||||
|
||||
let frame = FALLING.animation_sprite(offset);
|
||||
let sprite = controller.sprite(frame);
|
||||
|
||||
self.wizard.sprite.set_sprite(sprite);
|
||||
}
|
||||
|
||||
if input.x_tri() != agb::input::Tri::Zero {
|
||||
self.facing = input.x_tri();
|
||||
}
|
||||
}
|
||||
|
||||
let hat_base_tile = match self.num_recalls {
|
||||
0 => HAT_SPIN_1,
|
||||
1 => HAT_SPIN_2,
|
||||
_ => HAT_SPIN_3,
|
||||
};
|
||||
|
||||
let hat_resting_position = match self.wizard_frame {
|
||||
1 | 2 => (0, 9).into(),
|
||||
5 => (0, 10).into(),
|
||||
_ => (0, 8).into(),
|
||||
};
|
||||
|
||||
match self.facing {
|
||||
agb::input::Tri::Negative => {
|
||||
self.wizard.sprite.set_hflip(true);
|
||||
self.hat
|
||||
.sprite
|
||||
.set_sprite(controller.sprite(hat_base_tile.sprite(5)));
|
||||
}
|
||||
agb::input::Tri::Positive => {
|
||||
self.wizard.sprite.set_hflip(false);
|
||||
self.hat
|
||||
.sprite
|
||||
.set_sprite(controller.sprite(hat_base_tile.sprite(0)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.hat_state {
|
||||
HatState::Thrown => {
|
||||
// hat is thrown, make hat move towards wizard
|
||||
let distance_vector =
|
||||
self.wizard.position - self.hat.position - hat_resting_position;
|
||||
let distance = distance_vector.magnitude();
|
||||
let direction = if distance == 0.into() {
|
||||
(0, 0).into()
|
||||
} else {
|
||||
distance_vector / distance
|
||||
};
|
||||
|
||||
let hat_sprite_divider = match self.num_recalls {
|
||||
0 => 1,
|
||||
1 => 2,
|
||||
_ => 4,
|
||||
};
|
||||
|
||||
let hat_sprite_offset = (timer / hat_sprite_divider) as usize;
|
||||
|
||||
self.hat.sprite.set_sprite(
|
||||
controller.sprite(hat_base_tile.animation_sprite(hat_sprite_offset)),
|
||||
);
|
||||
|
||||
if self.hat_slow_counter < 30 && self.hat.velocity.magnitude() < 2.into() {
|
||||
self.hat.velocity = (0, 0).into();
|
||||
self.hat_slow_counter += 1;
|
||||
} else {
|
||||
self.hat.velocity += direction / 4;
|
||||
}
|
||||
let (new_velocity, enemy_collision) =
|
||||
self.hat.update_position_with_enemy(level, enemies);
|
||||
self.hat.velocity = new_velocity;
|
||||
|
||||
if enemy_collision {
|
||||
sfx_player.snail_hat_bounce();
|
||||
}
|
||||
|
||||
if distance > 16.into() {
|
||||
self.hat_left_range = true;
|
||||
}
|
||||
if self.hat_left_range && distance < 16.into() {
|
||||
sfx_player.catch();
|
||||
self.hat_state = HatState::OnHead;
|
||||
}
|
||||
}
|
||||
HatState::OnHead => {
|
||||
// hat is on head, place hat on head
|
||||
self.hat_slow_counter = 0;
|
||||
self.hat_left_range = false;
|
||||
self.hat.position = self.wizard.position - hat_resting_position;
|
||||
}
|
||||
HatState::WizardTowards => {
|
||||
self.hat.sprite.set_sprite(
|
||||
controller.sprite(hat_base_tile.animation_sprite(timer as usize / 2)),
|
||||
);
|
||||
let distance_vector =
|
||||
self.hat.position - self.wizard.position + hat_resting_position;
|
||||
let distance = distance_vector.magnitude();
|
||||
if distance != 0.into() {
|
||||
let v = self.wizard.velocity.magnitude() + 1;
|
||||
self.wizard.velocity = distance_vector / distance * v;
|
||||
}
|
||||
self.wizard.velocity = self.wizard.update_position(level);
|
||||
if distance < 16.into() {
|
||||
self.wizard.velocity /= 8;
|
||||
self.hat_state = HatState::OnHead;
|
||||
sfx_player.catch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PlayingLevel<'a, 'b> {
|
||||
timer: i32,
|
||||
background: Map<'a, 'b>,
|
||||
input: ButtonController,
|
||||
player: Player<'a>,
|
||||
|
||||
enemies: [enemies::Enemy<'a>; 16],
|
||||
}
|
||||
|
||||
enum UpdateState {
|
||||
Normal,
|
||||
Dead,
|
||||
Complete,
|
||||
}
|
||||
|
||||
impl<'a, 'b> PlayingLevel<'a, 'b> {
|
||||
fn open_level(
|
||||
level: &'a Level,
|
||||
object_control: &'a ObjectController,
|
||||
background: &'a mut InfiniteScrolledMap<'b>,
|
||||
foreground: &'a mut InfiniteScrolledMap<'b>,
|
||||
input: ButtonController,
|
||||
) -> Self {
|
||||
let mut e: [enemies::Enemy<'a>; 16] = Default::default();
|
||||
let mut enemy_count = 0;
|
||||
for &slime in level.slimes {
|
||||
e[enemy_count] = enemies::Enemy::new_slime(object_control, slime.into());
|
||||
enemy_count += 1;
|
||||
}
|
||||
|
||||
for &snail in level.snails {
|
||||
e[enemy_count] = enemies::Enemy::new_snail(object_control, snail.into());
|
||||
enemy_count += 1;
|
||||
}
|
||||
|
||||
let start_pos: Vector2D<FixedNumberType> = level.start_pos.into();
|
||||
|
||||
let background_position = (
|
||||
(start_pos.x - WIDTH / 2)
|
||||
.clamp(0.into(), ((level.dimensions.x * 8) as i32 - WIDTH).into()),
|
||||
(start_pos.y - HEIGHT / 2)
|
||||
.clamp(0.into(), ((level.dimensions.y * 8) as i32 - HEIGHT).into()),
|
||||
)
|
||||
.into();
|
||||
|
||||
PlayingLevel {
|
||||
timer: 0,
|
||||
background: Map {
|
||||
background,
|
||||
foreground,
|
||||
level,
|
||||
position: background_position,
|
||||
},
|
||||
player: Player::new(object_control, start_pos),
|
||||
input,
|
||||
enemies: e,
|
||||
}
|
||||
}
|
||||
|
||||
fn show_backgrounds(&mut self) {
|
||||
self.background.background.show();
|
||||
self.background.foreground.show();
|
||||
}
|
||||
|
||||
fn hide_backgrounds(&mut self) {
|
||||
self.background.background.hide();
|
||||
self.background.foreground.hide();
|
||||
}
|
||||
|
||||
fn clear_backgrounds(&mut self, vram: &mut VRamManager) {
|
||||
self.background.background.clear(vram);
|
||||
self.background.foreground.clear(vram);
|
||||
}
|
||||
|
||||
fn dead_start(&mut self) {
|
||||
self.player.wizard.velocity = (0, -1).into();
|
||||
self.player.wizard.sprite.set_priority(Priority::P0);
|
||||
}
|
||||
|
||||
fn dead_update(&mut self, controller: &'a ObjectController) -> bool {
|
||||
self.timer += 1;
|
||||
|
||||
let frame = PLAYER_DEATH.animation_sprite(self.timer as usize / 8);
|
||||
let sprite = controller.sprite(frame);
|
||||
|
||||
self.player.wizard.velocity += (0.into(), FixedNumberType::new(1) / 32).into();
|
||||
self.player.wizard.position += self.player.wizard.velocity;
|
||||
self.player.wizard.sprite.set_sprite(sprite);
|
||||
|
||||
self.player.wizard.commit_position(self.background.position);
|
||||
|
||||
self.player.wizard.position.y - self.background.position.y < (HEIGHT + 8).into()
|
||||
}
|
||||
|
||||
fn update_frame(
|
||||
&mut self,
|
||||
sfx_player: &mut sfx::SfxPlayer,
|
||||
vram: &mut VRamManager,
|
||||
controller: &'a ObjectController,
|
||||
) -> UpdateState {
|
||||
self.timer += 1;
|
||||
self.input.update();
|
||||
|
||||
let mut player_dead = false;
|
||||
|
||||
self.player.update_frame(
|
||||
&self.input,
|
||||
controller,
|
||||
self.timer,
|
||||
self.background.level,
|
||||
&self.enemies,
|
||||
sfx_player,
|
||||
);
|
||||
|
||||
for enemy in self.enemies.iter_mut() {
|
||||
match enemy.update(
|
||||
controller,
|
||||
self.background.level,
|
||||
self.player.wizard.position,
|
||||
self.player.hat_state,
|
||||
self.timer,
|
||||
sfx_player,
|
||||
) {
|
||||
enemies::EnemyUpdateState::KillPlayer => player_dead = true,
|
||||
enemies::EnemyUpdateState::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.background.position = self.get_next_map_position();
|
||||
self.background.commit_position(vram);
|
||||
|
||||
self.player.wizard.commit_position(self.background.position);
|
||||
self.player.hat.commit_position(self.background.position);
|
||||
|
||||
for enemy in self.enemies.iter_mut() {
|
||||
enemy.commit(self.background.position);
|
||||
}
|
||||
|
||||
player_dead |= self
|
||||
.player
|
||||
.wizard
|
||||
.killision_at_point(self.background.level, self.player.wizard.position);
|
||||
if player_dead {
|
||||
UpdateState::Dead
|
||||
} else if self
|
||||
.player
|
||||
.wizard
|
||||
.completion_at_point(self.background.level, self.player.wizard.position)
|
||||
{
|
||||
UpdateState::Complete
|
||||
} else {
|
||||
UpdateState::Normal
|
||||
}
|
||||
}
|
||||
|
||||
fn get_next_map_position(&self) -> Vector2D<FixedNumberType> {
|
||||
// want to ensure the player and the hat are visible if possible, so try to position the map
|
||||
// so the centre is at the average position. But give the player some extra priority
|
||||
let hat_pos = self.player.hat.position.floor();
|
||||
let player_pos = self.player.wizard.position.floor();
|
||||
|
||||
let new_target_position = (hat_pos + player_pos * 3) / 4;
|
||||
|
||||
let screen: Vector2D<i32> = (WIDTH, HEIGHT).into();
|
||||
let half_screen = screen / 2;
|
||||
let current_centre = self.background.position.floor() + half_screen;
|
||||
|
||||
let mut target_position = ((current_centre * 3 + new_target_position) / 4) - half_screen;
|
||||
|
||||
target_position.x = target_position.x.clamp(
|
||||
0,
|
||||
(self.background.level.dimensions.x * 8 - (WIDTH as u32)) as i32,
|
||||
);
|
||||
target_position.y = target_position.y.clamp(
|
||||
0,
|
||||
(self.background.level.dimensions.y * 8 - (HEIGHT as u32)) as i32,
|
||||
);
|
||||
|
||||
target_position.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[agb::entry]
|
||||
fn agb_main(mut gba: agb::Gba) -> ! {
|
||||
main(gba);
|
||||
}
|
||||
|
||||
pub fn main(mut agb: agb::Gba) -> ! {
|
||||
let (tiled, mut vram) = agb.display.video.tiled0();
|
||||
vram.set_background_palettes(tile_sheet::PALETTES);
|
||||
let mut splash_screen = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
||||
let mut world_display = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
||||
|
||||
let tileset = TileSet::new(tile_sheet::background.tiles, TileFormat::FourBpp);
|
||||
|
||||
for y in 0..32u16 {
|
||||
for x in 0..32u16 {
|
||||
world_display.set_tile(
|
||||
&mut vram,
|
||||
(x, y).into(),
|
||||
&tileset,
|
||||
TileSetting::from_raw(level_display::BLANK),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
world_display.commit(&mut vram);
|
||||
world_display.show();
|
||||
|
||||
splash_screen::show_splash_screen(
|
||||
splash_screen::SplashScreen::Start,
|
||||
None,
|
||||
None,
|
||||
&mut splash_screen,
|
||||
&mut vram,
|
||||
);
|
||||
|
||||
loop {
|
||||
world_display.commit(&mut vram);
|
||||
world_display.show();
|
||||
|
||||
vram.set_background_palettes(tile_sheet::PALETTES);
|
||||
|
||||
let object = agb.display.object.get();
|
||||
let mut mixer = agb.mixer.mixer(Frequency::Hz10512);
|
||||
|
||||
mixer.enable();
|
||||
let mut music_box = sfx::MusicBox::new();
|
||||
|
||||
let vblank = agb::interrupt::VBlank::get();
|
||||
let mut current_level = 0;
|
||||
|
||||
loop {
|
||||
if current_level == map_tiles::LEVELS.len() as u32 {
|
||||
break;
|
||||
}
|
||||
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
|
||||
level_display::write_level(
|
||||
&mut world_display,
|
||||
current_level / 8 + 1,
|
||||
current_level % 8 + 1,
|
||||
&tileset,
|
||||
&mut vram,
|
||||
);
|
||||
|
||||
world_display.commit(&mut vram);
|
||||
world_display.show();
|
||||
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
|
||||
let map_current_level = current_level;
|
||||
let mut background = InfiniteScrolledMap::new(
|
||||
tiled.background(Priority::P2, RegularBackgroundSize::Background32x64),
|
||||
Box::new(|pos: Vector2D<i32>| {
|
||||
let level = &map_tiles::LEVELS[map_current_level as usize];
|
||||
(
|
||||
&tileset,
|
||||
TileSetting::from_raw(
|
||||
*level
|
||||
.background
|
||||
.get((pos.y * level.dimensions.x as i32 + pos.x) as usize)
|
||||
.unwrap_or(&0),
|
||||
),
|
||||
)
|
||||
}),
|
||||
);
|
||||
let mut foreground = InfiniteScrolledMap::new(
|
||||
tiled.background(Priority::P0, RegularBackgroundSize::Background64x32),
|
||||
Box::new(|pos: Vector2D<i32>| {
|
||||
let level = &map_tiles::LEVELS[map_current_level as usize];
|
||||
(
|
||||
&tileset,
|
||||
TileSetting::from_raw(
|
||||
*level
|
||||
.foreground
|
||||
.get((pos.y * level.dimensions.x as i32 + pos.x) as usize)
|
||||
.unwrap_or(&0),
|
||||
),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
let mut level = PlayingLevel::open_level(
|
||||
&map_tiles::LEVELS[current_level as usize],
|
||||
&object,
|
||||
&mut background,
|
||||
&mut foreground,
|
||||
agb::input::ButtonController::new(),
|
||||
);
|
||||
|
||||
while level.background.init_background(&mut vram) != PartialUpdateStatus::Done {
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
}
|
||||
|
||||
while level.background.init_foreground(&mut vram) != PartialUpdateStatus::Done {
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
}
|
||||
|
||||
for _ in 0..20 {
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
}
|
||||
|
||||
object.commit();
|
||||
|
||||
level.show_backgrounds();
|
||||
|
||||
world_display.hide();
|
||||
|
||||
loop {
|
||||
match level.update_frame(
|
||||
&mut sfx::SfxPlayer::new(&mut mixer, &music_box),
|
||||
&mut vram,
|
||||
&object,
|
||||
) {
|
||||
UpdateState::Normal => {}
|
||||
UpdateState::Dead => {
|
||||
level.dead_start();
|
||||
while level.dead_update(&object) {
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
object.commit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
UpdateState::Complete => {
|
||||
current_level += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
music_box.before_frame(&mut mixer);
|
||||
mixer.frame();
|
||||
vblank.wait_for_vblank();
|
||||
mixer.after_vblank();
|
||||
object.commit();
|
||||
}
|
||||
|
||||
level.hide_backgrounds();
|
||||
level.clear_backgrounds(&mut vram);
|
||||
}
|
||||
|
||||
object.commit();
|
||||
|
||||
splash_screen::show_splash_screen(
|
||||
splash_screen::SplashScreen::End,
|
||||
Some(&mut mixer),
|
||||
Some(&mut music_box),
|
||||
&mut splash_screen,
|
||||
&mut vram,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use agb::Gba;
|
||||
|
||||
#[test_case]
|
||||
fn test_ping_pong(_gba: &mut Gba) {
|
||||
let test_cases = [
|
||||
[0, 2, 0],
|
||||
[0, 7, 0],
|
||||
[1, 2, 1],
|
||||
[2, 2, 0],
|
||||
[3, 2, 1],
|
||||
[4, 2, 0],
|
||||
];
|
||||
|
||||
for test_case in test_cases {
|
||||
assert_eq!(
|
||||
ping_pong(test_case[0], test_case[1]),
|
||||
test_case[2],
|
||||
"Expected ping_pong({}, {}) to equal {}",
|
||||
test_case[0],
|
||||
test_case[1],
|
||||
test_case[2],
|
||||
);
|
||||
}
|
||||
}
|
||||
fn entry(mut gba: agb::Gba) -> ! {
|
||||
the_hat_chooses_the_wizard::main(gba);
|
||||
}
|
||||
|
|
2344
examples/the-purple-night/src/lib.rs
Normal file
2344
examples/the-purple-night/src/lib.rs
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue