jam version of hyperspace roll
11
examples/hyperspace-roll/.cargo/config.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[unstable]
|
||||||
|
build-std = ["core", "alloc"]
|
||||||
|
build-std-features = ["compiler-builtins-mem"]
|
||||||
|
sparse-registry = true
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "thumbv4t-none-eabi"
|
||||||
|
|
||||||
|
[target.thumbv4t-none-eabi]
|
||||||
|
rustflags = ["-Clink-arg=-Tgba.ld"]
|
||||||
|
runner = "mgba-qt"
|
439
examples/hyperspace-roll/Cargo.lock
generated
Normal file
|
@ -0,0 +1,439 @@
|
||||||
|
# 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.9.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.2.1"
|
||||||
|
dependencies = [
|
||||||
|
"agb_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "agb_image_converter"
|
||||||
|
version = "0.7.0"
|
||||||
|
dependencies = [
|
||||||
|
"asefile",
|
||||||
|
"fontdue",
|
||||||
|
"image",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"syn",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "agb_macros"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "agb_sound_converter"
|
||||||
|
version = "0.2.0"
|
||||||
|
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 = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deflate"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
|
||||||
|
dependencies = [
|
||||||
|
"adler32",
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crc32fast",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide 0.5.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "getrandom"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gmtk22"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"agb",
|
||||||
|
"bare-metal",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549"
|
||||||
|
|
||||||
|
[[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 = "libc"
|
||||||
|
version = "0.2.124"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
|
||||||
|
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.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-iter"
|
||||||
|
version = "0.1.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||||
|
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.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||||
|
|
||||||
|
[[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.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.136"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.136"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.91"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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-xid"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
|
[[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"
|
21
examples/hyperspace-roll/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "gmtk22"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = [""]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
agb = { version = "0.9.2", path = "../agb/agb", features = ["freq32768"] }
|
||||||
|
bare-metal = "1"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 2
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
lto = true
|
||||||
|
debug = true
|
73
examples/hyperspace-roll/README.md
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
# AGBRS template
|
||||||
|
|
||||||
|
## A basic template example for agb projects
|
||||||
|
|
||||||
|
This makes getting started with a new project for the Game Boy Advance in rust really simple, by providing
|
||||||
|
all the boiler plate files for you.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
You will need the following installed in order to build and run this project:
|
||||||
|
|
||||||
|
* A recent version of `rustup`. See the [rust website](https://www.rust-lang.org/tools/install) for instructions for your operating system
|
||||||
|
* `arm-none-eabi-binutils` for assembling and linking
|
||||||
|
* Windows: [GNU Arm Embedded Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads).
|
||||||
|
Make sure you select "Add path to environment variable" during the install
|
||||||
|
* Debian and derivatives (e.g. Ubuntu, raspberry pi OS, linux mint): `sudo apt install binutils-arm-none-eabi`
|
||||||
|
* Arch linux and derivatives: `sudo pacman -S arm-none-eabi-binutils`
|
||||||
|
|
||||||
|
You will also want to install an emulator. The best support in agb is with [mgba](https://mgba.io), with
|
||||||
|
`println!` support via `agb::println!` but any emulator should work. You'll get the best experience if
|
||||||
|
`mgba-qt` is in your `PATH`.
|
||||||
|
|
||||||
|
If you want to run your game on real hardware, you will also need to install `gbafix` which you can do after installing
|
||||||
|
rust with the following: `cargo install gbafix`. This is not required if you are only running your game in an emulator.
|
||||||
|
|
||||||
|
### Running in an emulator
|
||||||
|
|
||||||
|
Once you have the prerequisites installed, you should be able to build using
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
or in release mode (recommended for the final version to ship to players)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting file will be in `target/thumbv4t-none-eabi/debug/<your game>` or `target/thumbv4t-none-eabi/release/<your game>` depending on
|
||||||
|
whether you did a release or debug build.
|
||||||
|
|
||||||
|
If you have `mgba-qt` in your path, you will be able to run your game with
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
or in release mode
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run --release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Starting development
|
||||||
|
|
||||||
|
You can find the documentation for agb [here](https://docs.rs/agb/latest/agb/).
|
||||||
|
|
||||||
|
You may also want to change the package name and version in `Cargo.toml` before you start.
|
||||||
|
|
||||||
|
## Shipping a .gba file for real hardware
|
||||||
|
|
||||||
|
To make a game run on real hardware, you will need to convert the built file into a file suitable for
|
||||||
|
running on the real thing.
|
||||||
|
|
||||||
|
First build the binary in release mode using the instructions above, then do the following:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
arm-none-eabi-objcopy -O binary target/thumbv4t-none-eabi/release/<your game> <your game>.gba
|
||||||
|
gbafix <your game>.gba
|
||||||
|
```
|
1
examples/hyperspace-roll/build.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
fn main() {}
|
112
examples/hyperspace-roll/gba.ld
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
__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/ : { *(*) }
|
||||||
|
}
|
110
examples/hyperspace-roll/gba_mb.ld
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
__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/hyperspace-roll/gfx/descriptions.aseprite
Normal file
11
examples/hyperspace-roll/gfx/descriptions.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
version = "1.0"
|
||||||
|
|
||||||
|
[image.descriptions1]
|
||||||
|
filename = "descriptions1.png"
|
||||||
|
tile_size = "8x8"
|
||||||
|
transparent_colour = "121105"
|
||||||
|
|
||||||
|
[image.descriptions2]
|
||||||
|
filename = "descriptions2.png"
|
||||||
|
tile_size = "8x8"
|
||||||
|
transparent_colour = "121105"
|
BIN
examples/hyperspace-roll/gfx/descriptions1.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
examples/hyperspace-roll/gfx/descriptions2.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
examples/hyperspace-roll/gfx/dice-faces.aseprite
Normal file
BIN
examples/hyperspace-roll/gfx/fantasy-24-1x.png
Normal file
After Width: | Height: | Size: 194 B |
4
examples/hyperspace-roll/gfx/font-license.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
The FontStruction “Pixelated”
|
||||||
|
(http://fontstruct.com/fontstructions/show/426637) by “Greenma201” is
|
||||||
|
licensed under a Creative Commons Attribution Share Alike license
|
||||||
|
(http://creativecommons.org/licenses/by-sa/3.0/).
|
BIN
examples/hyperspace-roll/gfx/help-text.aseprite
Normal file
BIN
examples/hyperspace-roll/gfx/help-text.png
Normal file
After Width: | Height: | Size: 591 B |
6
examples/hyperspace-roll/gfx/help.toml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
version = "1.0"
|
||||||
|
|
||||||
|
[image.help]
|
||||||
|
filename = "help-text.png"
|
||||||
|
tile_size = "8x8"
|
||||||
|
transparent_colour = "121105"
|
BIN
examples/hyperspace-roll/gfx/pixelated.ttf
Normal file
BIN
examples/hyperspace-roll/gfx/ships.aseprite
Normal file
BIN
examples/hyperspace-roll/gfx/small-sprites.aseprite
Normal file
BIN
examples/hyperspace-roll/gfx/stars.aseprite
Normal file
BIN
examples/hyperspace-roll/gfx/stars.png
Normal file
After Width: | Height: | Size: 550 B |
10
examples/hyperspace-roll/gfx/stars.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
version = "1.0"
|
||||||
|
|
||||||
|
[image.stars]
|
||||||
|
filename = "stars.png"
|
||||||
|
tile_size = "8x8"
|
||||||
|
transparent_colour = "121105"
|
||||||
|
|
||||||
|
[image.title]
|
||||||
|
filename = "title-screen.png"
|
||||||
|
tile_size = "8x8"
|
BIN
examples/hyperspace-roll/gfx/title-screen-for-submission.png
Normal file
After Width: | Height: | Size: 6 KiB |
BIN
examples/hyperspace-roll/gfx/title-screen.aseprite
Normal file
BIN
examples/hyperspace-roll/gfx/title-screen.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
339
examples/hyperspace-roll/html/LICENSE
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License.
|
2
examples/hyperspace-roll/html/app.js
Normal file
1039
examples/hyperspace-roll/html/emu.html
Normal file
1
examples/hyperspace-roll/html/emu.js
Normal file
BIN
examples/hyperspace-roll/html/emu.wasm
Normal file
17
examples/hyperspace-roll/html/index.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<center>
|
||||||
|
<iframe src="./emu.html" width="240" height="160"></iframe>
|
||||||
|
</center>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
iframe {
|
||||||
|
width: min(100%, calc(100vh / (2/3)));
|
||||||
|
height: min(100%, calc(100vw / (2/3)));
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</html>
|
BIN
examples/hyperspace-roll/html/logo.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
17
examples/hyperspace-roll/make_release
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
|
rm -rf target/export
|
||||||
|
mkdir target/export
|
||||||
|
|
||||||
|
cp -r html target/export
|
||||||
|
|
||||||
|
cd target/export || exit
|
||||||
|
|
||||||
|
cargo build --release
|
||||||
|
arm-none-eabi-objcopy -O binary ../thumbv4t-none-eabi/release/gmtk22 "Hyperspace Roll.gba"
|
||||||
|
gbafix -tHYPERSPACE -cHYLL -mGC "Hyperspace Roll.gba"
|
||||||
|
|
||||||
|
cp "Hyperspace Roll.gba" html/game.gba
|
||||||
|
|
||||||
|
(cd html || return && zip -r ../html.zip .)
|
BIN
examples/hyperspace-roll/mockups-upgrade.aseprite
Normal file
BIN
examples/hyperspace-roll/mockups.aseprite
Normal file
3
examples/hyperspace-roll/rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
|
components = ["rust-src", "clippy"]
|
BIN
examples/hyperspace-roll/sfx/BGM_Fight.wav
Normal file
BIN
examples/hyperspace-roll/sfx/BGM_Menu.wav
Normal file
BIN
examples/hyperspace-roll/sfx/BGM_Title.wav
Normal file
BIN
examples/hyperspace-roll/sfx/MultiRoll_1.wav
Normal file
BIN
examples/hyperspace-roll/sfx/MultiRoll_2.wav
Normal file
BIN
examples/hyperspace-roll/sfx/MultiRoll_3.wav
Normal file
BIN
examples/hyperspace-roll/sfx/MultiRoll_4.wav
Normal file
BIN
examples/hyperspace-roll/sfx/MultiRoll_5.wav
Normal file
BIN
examples/hyperspace-roll/sfx/SingleRoll_1.wav
Normal file
BIN
examples/hyperspace-roll/sfx/SingleRoll_2.wav
Normal file
BIN
examples/hyperspace-roll/sfx/SingleRoll_3.wav
Normal file
BIN
examples/hyperspace-roll/sfx/SingleRoll_4.wav
Normal file
BIN
examples/hyperspace-roll/sfx/SingleRoll_5.wav
Normal file
BIN
examples/hyperspace-roll/sfx/accept.wav
Normal file
BIN
examples/hyperspace-roll/sfx/back.wav
Normal file
BIN
examples/hyperspace-roll/sfx/burst_shield_hit.wav
Normal file
BIN
examples/hyperspace-roll/sfx/disrupt.wav
Normal file
BIN
examples/hyperspace-roll/sfx/heal.wav
Normal file
BIN
examples/hyperspace-roll/sfx/move_cursor.wav
Normal file
BIN
examples/hyperspace-roll/sfx/select.wav
Normal file
BIN
examples/hyperspace-roll/sfx/send_burst_shield.wav
Normal file
BIN
examples/hyperspace-roll/sfx/shield_defend.wav
Normal file
BIN
examples/hyperspace-roll/sfx/shield_down.wav
Normal file
BIN
examples/hyperspace-roll/sfx/shield_up.wav
Normal file
BIN
examples/hyperspace-roll/sfx/ship_explode.wav
Normal file
BIN
examples/hyperspace-roll/sfx/shoot.wav
Normal file
BIN
examples/hyperspace-roll/sfx/shot_hit.wav
Normal file
147
examples/hyperspace-roll/src/background.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
use agb::{
|
||||||
|
display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager},
|
||||||
|
include_gfx, rng,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::sfx::Sfx;
|
||||||
|
|
||||||
|
include_gfx!("gfx/stars.toml");
|
||||||
|
|
||||||
|
include_gfx!("gfx/help.toml");
|
||||||
|
|
||||||
|
pub fn load_palettes(vram: &mut VRamManager) {
|
||||||
|
vram.set_background_palettes(&[
|
||||||
|
stars::stars.palettes[0].clone(),
|
||||||
|
crate::customise::DESCRIPTIONS_1_PALETTE.clone(),
|
||||||
|
crate::customise::DESCRIPTIONS_2_PALETTE.clone(),
|
||||||
|
help::help.palettes[0].clone(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_help_text(
|
||||||
|
vram: &mut VRamManager,
|
||||||
|
background: &mut RegularMap,
|
||||||
|
help_text_line: u16,
|
||||||
|
at_tile: (u16, u16),
|
||||||
|
) {
|
||||||
|
let help_tileset = TileSet::new(help::help.tiles, agb::display::tiled::TileFormat::FourBpp);
|
||||||
|
|
||||||
|
for x in 0..16 {
|
||||||
|
background.set_tile(
|
||||||
|
vram,
|
||||||
|
(x + at_tile.0, at_tile.1).into(),
|
||||||
|
&help_tileset,
|
||||||
|
TileSetting::new(help_text_line * 16 + x, false, false, 3),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expects a 64x32 map
|
||||||
|
fn create_background_map(map: &mut RegularMap, vram: &mut VRamManager, stars_tileset: &TileSet) {
|
||||||
|
for x in 0..64u16 {
|
||||||
|
for y in 0..32u16 {
|
||||||
|
let blank = rng::gen().rem_euclid(32) < 30;
|
||||||
|
|
||||||
|
let tile_id = if blank {
|
||||||
|
(1 << 10) - 1
|
||||||
|
} else {
|
||||||
|
rng::gen().rem_euclid(64) as u16
|
||||||
|
};
|
||||||
|
let tile_setting = TileSetting::new(tile_id, false, false, 0);
|
||||||
|
|
||||||
|
map.set_tile(vram, (x, y).into(), stars_tileset, tile_setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.set_scroll_pos((0u16, rng::gen().rem_euclid(8) as u16).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_title_screen(background: &mut RegularMap, vram: &mut VRamManager, sfx: &mut Sfx) {
|
||||||
|
background.set_scroll_pos((0_u16, 0_u16).into());
|
||||||
|
vram.set_background_palettes(stars::title.palettes);
|
||||||
|
let tile_set = TileSet::new(stars::title.tiles, agb::display::tiled::TileFormat::FourBpp);
|
||||||
|
background.hide();
|
||||||
|
|
||||||
|
for x in 0..30u16 {
|
||||||
|
for y in 0..20u16 {
|
||||||
|
let tile_id = y * 30 + x;
|
||||||
|
background.set_tile(
|
||||||
|
vram,
|
||||||
|
(x, y).into(),
|
||||||
|
&tile_set,
|
||||||
|
TileSetting::new(
|
||||||
|
tile_id,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
stars::title.palette_assignments[tile_id as usize],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sfx.frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
background.commit(vram);
|
||||||
|
sfx.frame();
|
||||||
|
background.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StarBackground<'a> {
|
||||||
|
background1: &'a mut RegularMap,
|
||||||
|
background2: &'a mut RegularMap,
|
||||||
|
|
||||||
|
background1_timer: u32,
|
||||||
|
background2_timer: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> StarBackground<'a> {
|
||||||
|
pub fn new(
|
||||||
|
background1: &'a mut RegularMap,
|
||||||
|
background2: &'a mut RegularMap,
|
||||||
|
vram: &'_ mut VRamManager,
|
||||||
|
) -> Self {
|
||||||
|
let stars_tileset = TileSet::new(stars::stars.tiles, TileFormat::FourBpp);
|
||||||
|
create_background_map(background1, vram, &stars_tileset);
|
||||||
|
create_background_map(background2, vram, &stars_tileset);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
background1,
|
||||||
|
background2,
|
||||||
|
|
||||||
|
background1_timer: 0,
|
||||||
|
background2_timer: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self) {
|
||||||
|
if self.background1_timer == 0 {
|
||||||
|
self.background1
|
||||||
|
.set_scroll_pos(self.background1.scroll_pos() + (1u16, 0).into());
|
||||||
|
self.background1_timer = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.background2_timer == 0 {
|
||||||
|
self.background2
|
||||||
|
.set_scroll_pos(self.background2.scroll_pos() + (1u16, 0).into());
|
||||||
|
self.background2_timer = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.background1_timer -= 1;
|
||||||
|
self.background2_timer -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit(&mut self, vram: &mut VRamManager) {
|
||||||
|
self.background1.commit(vram);
|
||||||
|
self.background2.commit(vram);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hide(&mut self) {
|
||||||
|
self.background1.hide();
|
||||||
|
self.background2.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(&mut self) {
|
||||||
|
self.background1.show();
|
||||||
|
self.background2.show();
|
||||||
|
}
|
||||||
|
}
|
615
examples/hyperspace-roll/src/battle.rs
Normal file
|
@ -0,0 +1,615 @@
|
||||||
|
use crate::sfx::Sfx;
|
||||||
|
use crate::{
|
||||||
|
graphics::SELECT_BOX, level_generation::generate_attack, Agb, EnemyAttackType, Face, PlayerDice,
|
||||||
|
};
|
||||||
|
use agb::display::tiled::RegularMap;
|
||||||
|
use agb::{hash_map::HashMap, input::Button};
|
||||||
|
use alloc::vec;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use self::display::BattleScreenDisplay;
|
||||||
|
|
||||||
|
mod display;
|
||||||
|
|
||||||
|
pub(super) const MALFUNCTION_COOLDOWN_FRAMES: u32 = 3 * 60;
|
||||||
|
const ROLL_TIME_FRAMES_ALL: u32 = 2 * 60;
|
||||||
|
const ROLL_TIME_FRAMES_ONE: u32 = 60 / 8;
|
||||||
|
|
||||||
|
/// A face of the rolled die and it's cooldown (should it be a malfunction)
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RolledDie {
|
||||||
|
face: Face,
|
||||||
|
cooldown: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RolledDie {
|
||||||
|
fn new(face: Face) -> Self {
|
||||||
|
let cooldown = if face == Face::Malfunction {
|
||||||
|
MALFUNCTION_COOLDOWN_FRAMES
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { face, cooldown }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self) {
|
||||||
|
self.cooldown = self.cooldown.saturating_sub(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_reroll(&self) -> bool {
|
||||||
|
self.face != Face::Malfunction || self.cooldown == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_reroll_after_accept(&self) -> bool {
|
||||||
|
self.face != Face::Malfunction
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cooldown(&self) -> Option<u32> {
|
||||||
|
if self.face == Face::Malfunction && self.cooldown > 0 {
|
||||||
|
Some(self.cooldown)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DieState {
|
||||||
|
Rolling(u32, Face, Face),
|
||||||
|
Rolled(RolledDie),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Action {
|
||||||
|
PlayerActivateShield { amount: u32 },
|
||||||
|
PlayerShoot { damage: u32, piercing: u32 },
|
||||||
|
PlayerDisrupt { amount: u32 },
|
||||||
|
PlayerHeal { amount: u32 },
|
||||||
|
PlayerBurstShield { multiplier: u32 },
|
||||||
|
PlayerSendBurstShield { damage: u32 },
|
||||||
|
EnemyShoot { damage: u32 },
|
||||||
|
EnemyShield { amount: u32 },
|
||||||
|
EnemyHeal { amount: u32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RolledDice {
|
||||||
|
rolls: Vec<DieState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RolledDice {
|
||||||
|
fn update(&mut self, player_dice: &PlayerDice) {
|
||||||
|
self.rolls
|
||||||
|
.iter_mut()
|
||||||
|
.zip(player_dice.dice.iter())
|
||||||
|
.for_each(|(die_state, player_die)| match die_state {
|
||||||
|
DieState::Rolling(ref mut timeout, ref mut face, previous_face) => {
|
||||||
|
if *timeout == 0 {
|
||||||
|
let mut number_of_rolls = 0;
|
||||||
|
*die_state = DieState::Rolled(RolledDie::new(loop {
|
||||||
|
let next_face = player_die.roll();
|
||||||
|
number_of_rolls += 1;
|
||||||
|
if *previous_face != Face::Malfunction
|
||||||
|
|| next_face != *previous_face
|
||||||
|
|| number_of_rolls > 16
|
||||||
|
{
|
||||||
|
break next_face;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
if *timeout % 2 == 0 {
|
||||||
|
*face = player_die.roll();
|
||||||
|
}
|
||||||
|
*timeout -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DieState::Rolled(ref mut rolled_die) => rolled_die.update(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn faces_for_accepting(&self) -> impl Iterator<Item = Face> + '_ {
|
||||||
|
self.rolls.iter().filter_map(|state| match state {
|
||||||
|
DieState::Rolled(rolled_die) => Some(rolled_die.face),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn faces_to_render(&self) -> impl Iterator<Item = (Face, Option<u32>)> + '_ {
|
||||||
|
self.rolls.iter().map(|rolled_die| match rolled_die {
|
||||||
|
DieState::Rolling(_, face, _previous_face) => (*face, None),
|
||||||
|
DieState::Rolled(rolled_die) => (rolled_die.face, rolled_die.cooldown()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accept_rolls(&mut self, player_dice: &PlayerDice) -> Vec<Action> {
|
||||||
|
let mut actions = vec![];
|
||||||
|
|
||||||
|
let mut face_counts: HashMap<Face, u32> = HashMap::new();
|
||||||
|
let mut shield_multiplier = 1;
|
||||||
|
let mut shoot_multiplier = 1;
|
||||||
|
for face in self.faces_for_accepting() {
|
||||||
|
match face {
|
||||||
|
Face::DoubleShot => *face_counts.entry(Face::Shoot).or_default() += 2,
|
||||||
|
Face::TripleShot => *face_counts.entry(Face::Shoot).or_default() += 3,
|
||||||
|
Face::DoubleShield => *face_counts.entry(Face::Shield).or_default() += 2,
|
||||||
|
Face::TripleShield => *face_counts.entry(Face::Shield).or_default() += 3,
|
||||||
|
Face::DoubleShieldValue => shield_multiplier *= 2,
|
||||||
|
Face::DoubleShotValue => shoot_multiplier *= 2,
|
||||||
|
Face::TripleShotValue => shoot_multiplier *= 3,
|
||||||
|
other => *face_counts.entry(other).or_default() += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let invert = *face_counts.entry(Face::Invert).or_default() % 2 == 1;
|
||||||
|
|
||||||
|
// shield
|
||||||
|
let mut shield_amount = *face_counts.entry(Face::Shield).or_default() * shield_multiplier;
|
||||||
|
|
||||||
|
// shooting
|
||||||
|
let shoot = *face_counts.entry(Face::Shoot).or_default();
|
||||||
|
let shoot_power = (shoot * (shoot + 1)) / 2;
|
||||||
|
|
||||||
|
let malfunction_shots = *face_counts.entry(Face::MalfunctionShot).or_default();
|
||||||
|
let malfunctions = *face_counts.entry(Face::Malfunction).or_default();
|
||||||
|
|
||||||
|
let malfunction_shoot = (malfunction_shots * (malfunction_shots + 1)) / 2
|
||||||
|
* (malfunctions * (malfunctions + 1))
|
||||||
|
/ 2;
|
||||||
|
|
||||||
|
if malfunction_shoot != 0 {
|
||||||
|
for roll in self.rolls.iter_mut().filter_map(|face| match face {
|
||||||
|
DieState::Rolled(rolled_die) if rolled_die.face == Face::Malfunction => {
|
||||||
|
Some(rolled_die)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}) {
|
||||||
|
roll.face = Face::Blank;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut shoot_power = (shoot_power + malfunction_shoot) * shoot_multiplier;
|
||||||
|
|
||||||
|
if invert {
|
||||||
|
(shoot_power, shield_amount) = (shield_amount, shoot_power);
|
||||||
|
}
|
||||||
|
|
||||||
|
if shoot_power > 0 {
|
||||||
|
actions.push(Action::PlayerShoot {
|
||||||
|
damage: shoot_power,
|
||||||
|
piercing: *face_counts.entry(Face::Bypass).or_default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if shield_amount > 0 {
|
||||||
|
actions.push(Action::PlayerActivateShield {
|
||||||
|
amount: shield_amount.min(5),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// burst shield
|
||||||
|
if face_counts.contains_key(&Face::BurstShield) {
|
||||||
|
actions.push(Action::PlayerBurstShield {
|
||||||
|
multiplier: shoot_multiplier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// disrupt
|
||||||
|
let disrupt = *face_counts.entry(Face::Disrupt).or_default();
|
||||||
|
let disrupt_power = (disrupt * (disrupt + 1)) / 2;
|
||||||
|
|
||||||
|
if disrupt_power > 0 {
|
||||||
|
actions.push(Action::PlayerDisrupt {
|
||||||
|
amount: disrupt_power,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let heal = *face_counts.entry(Face::Heal).or_default();
|
||||||
|
if heal != 0 {
|
||||||
|
actions.push(Action::PlayerHeal {
|
||||||
|
amount: ((heal * (heal + 1)) / 2) as u32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut malfunction_all = false;
|
||||||
|
|
||||||
|
for roll in self.rolls.iter_mut().filter_map(|face| match face {
|
||||||
|
DieState::Rolled(rolled_die) => Some(rolled_die),
|
||||||
|
_ => None,
|
||||||
|
}) {
|
||||||
|
if roll.face == Face::DoubleShot
|
||||||
|
|| roll.face == Face::DoubleShield
|
||||||
|
|| roll.face == Face::DoubleShotValue
|
||||||
|
{
|
||||||
|
roll.cooldown = MALFUNCTION_COOLDOWN_FRAMES;
|
||||||
|
roll.face = Face::Malfunction;
|
||||||
|
}
|
||||||
|
if roll.face == Face::TripleShot
|
||||||
|
|| roll.face == Face::TripleShield
|
||||||
|
|| roll.face == Face::TripleShotValue
|
||||||
|
|| roll.face == Face::BurstShield
|
||||||
|
{
|
||||||
|
malfunction_all = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if malfunction_all {
|
||||||
|
for roll in self.rolls.iter_mut().filter_map(|face| match face {
|
||||||
|
DieState::Rolled(rolled_die) => Some(rolled_die),
|
||||||
|
_ => None,
|
||||||
|
}) {
|
||||||
|
roll.cooldown = MALFUNCTION_COOLDOWN_FRAMES;
|
||||||
|
roll.face = Face::Malfunction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reroll non-malfunctions after accepting
|
||||||
|
for i in 0..player_dice.dice.len() {
|
||||||
|
self.roll_die(i, ROLL_TIME_FRAMES_ALL, true, player_dice);
|
||||||
|
}
|
||||||
|
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
|
||||||
|
fn roll_die(
|
||||||
|
&mut self,
|
||||||
|
die_index: usize,
|
||||||
|
time: u32,
|
||||||
|
is_after_accept: bool,
|
||||||
|
player_dice: &PlayerDice,
|
||||||
|
) {
|
||||||
|
if let DieState::Rolled(ref selected_rolled_die) = self.rolls[die_index] {
|
||||||
|
let can_reroll = if is_after_accept {
|
||||||
|
selected_rolled_die.can_reroll_after_accept()
|
||||||
|
} else {
|
||||||
|
selected_rolled_die.can_reroll()
|
||||||
|
};
|
||||||
|
|
||||||
|
if can_reroll {
|
||||||
|
self.rolls[die_index] = DieState::Rolling(
|
||||||
|
time,
|
||||||
|
player_dice.dice[die_index].roll(),
|
||||||
|
selected_rolled_die.face,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PlayerState {
|
||||||
|
shield_count: u32,
|
||||||
|
health: u32,
|
||||||
|
max_health: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EnemyAttack {
|
||||||
|
Shoot(u32),
|
||||||
|
Shield(u32),
|
||||||
|
Heal(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnemyAttack {
|
||||||
|
fn apply_effect(&self) -> Action {
|
||||||
|
match self {
|
||||||
|
EnemyAttack::Shoot(damage) => Action::EnemyShoot { damage: *damage },
|
||||||
|
EnemyAttack::Shield(shield) => Action::EnemyShield { amount: *shield },
|
||||||
|
EnemyAttack::Heal(amount) => Action::EnemyHeal { amount: *amount },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct EnemyAttackState {
|
||||||
|
attack: EnemyAttack,
|
||||||
|
cooldown: u32,
|
||||||
|
max_cooldown: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnemyAttackState {
|
||||||
|
fn attack_type(&self) -> EnemyAttackType {
|
||||||
|
match self.attack {
|
||||||
|
EnemyAttack::Shoot(_) => EnemyAttackType::Attack,
|
||||||
|
EnemyAttack::Shield(_) => EnemyAttackType::Shield,
|
||||||
|
EnemyAttack::Heal(_) => EnemyAttackType::Heal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_to_show(&self) -> Option<u32> {
|
||||||
|
match self.attack {
|
||||||
|
EnemyAttack::Shoot(i) => Some(i),
|
||||||
|
EnemyAttack::Heal(i) => Some(i),
|
||||||
|
EnemyAttack::Shield(i) => Some(i),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn update(&mut self) -> Option<Action> {
|
||||||
|
if self.cooldown == 0 {
|
||||||
|
return Some(self.attack.apply_effect());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cooldown -= 1;
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct EnemyState {
|
||||||
|
shield_count: u32,
|
||||||
|
health: u32,
|
||||||
|
max_health: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CurrentBattleState {
|
||||||
|
player: PlayerState,
|
||||||
|
enemy: EnemyState,
|
||||||
|
rolled_dice: RolledDice,
|
||||||
|
player_dice: PlayerDice,
|
||||||
|
attacks: [Option<EnemyAttackState>; 2],
|
||||||
|
current_level: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CurrentBattleState {
|
||||||
|
fn accept_rolls(&mut self) -> Vec<Action> {
|
||||||
|
self.rolled_dice.accept_rolls(&self.player_dice)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn roll_die(&mut self, die_index: usize, time: u32, is_after_accept: bool) {
|
||||||
|
self.rolled_dice
|
||||||
|
.roll_die(die_index, time, is_after_accept, &self.player_dice);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self) -> Vec<Action> {
|
||||||
|
let mut actions = vec![];
|
||||||
|
|
||||||
|
for attack in self.attacks.iter_mut() {
|
||||||
|
if let Some(attack_state) = attack {
|
||||||
|
if let Some(action) = attack_state.update() {
|
||||||
|
attack.take();
|
||||||
|
actions.push(action);
|
||||||
|
}
|
||||||
|
} else if let Some(generated_attack) = generate_attack(self.current_level) {
|
||||||
|
attack.replace(EnemyAttackState {
|
||||||
|
attack: generated_attack.attack,
|
||||||
|
cooldown: generated_attack.cooldown,
|
||||||
|
max_cooldown: generated_attack.cooldown,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_dice(&mut self) {
|
||||||
|
self.rolled_dice.update(&self.player_dice);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_action(&mut self, action: Action, sfx: &mut Sfx) -> Option<Action> {
|
||||||
|
match action {
|
||||||
|
Action::PlayerActivateShield { amount } => {
|
||||||
|
if amount > self.player.shield_count {
|
||||||
|
sfx.shield_up();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.player.shield_count = self.player.shield_count.max(amount);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Action::PlayerShoot { damage, piercing } => {
|
||||||
|
if self.enemy.shield_count <= piercing {
|
||||||
|
self.enemy.health = self.enemy.health.saturating_sub(damage);
|
||||||
|
sfx.shot_hit();
|
||||||
|
} else if self.enemy.shield_count <= damage {
|
||||||
|
self.enemy.shield_count = 0; // TODO: Dispatch action of drop shield to animate that
|
||||||
|
sfx.shield_down();
|
||||||
|
} else {
|
||||||
|
sfx.shield_defend();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Action::PlayerDisrupt { amount } => {
|
||||||
|
for attack in self.attacks.iter_mut().flatten() {
|
||||||
|
attack.cooldown += amount * 240;
|
||||||
|
attack.max_cooldown = attack.cooldown.max(attack.max_cooldown);
|
||||||
|
}
|
||||||
|
|
||||||
|
sfx.disrupt();
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Action::PlayerHeal { amount } => {
|
||||||
|
self.player.health = self.player.max_health.min(self.player.health + amount);
|
||||||
|
sfx.heal();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Action::EnemyShoot { damage } => {
|
||||||
|
if self.player.shield_count == 0 {
|
||||||
|
self.player.health = self.player.health.saturating_sub(damage);
|
||||||
|
sfx.shot_hit();
|
||||||
|
} else if self.player.shield_count <= damage {
|
||||||
|
self.player.shield_count = 0; // TODO: Dispatch action of drop shield to animate that
|
||||||
|
sfx.shield_down();
|
||||||
|
} else {
|
||||||
|
sfx.shield_defend();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Action::EnemyShield { amount } => {
|
||||||
|
if amount > self.enemy.shield_count {
|
||||||
|
sfx.shield_up();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.enemy.shield_count = self.enemy.shield_count.max(amount);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Action::EnemyHeal { amount } => {
|
||||||
|
self.enemy.health = self.enemy.max_health.min(self.enemy.health + amount);
|
||||||
|
sfx.heal();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Action::PlayerBurstShield { multiplier } => {
|
||||||
|
let damage =
|
||||||
|
self.player.shield_count * (self.player.shield_count + 1) * multiplier / 2;
|
||||||
|
self.player.shield_count = 0;
|
||||||
|
sfx.send_burst_shield();
|
||||||
|
|
||||||
|
Some(Action::PlayerSendBurstShield { damage })
|
||||||
|
}
|
||||||
|
Action::PlayerSendBurstShield { damage } => {
|
||||||
|
self.enemy.shield_count = 0;
|
||||||
|
self.enemy.health = self.enemy.health.saturating_sub(damage);
|
||||||
|
|
||||||
|
sfx.burst_shield_hit();
|
||||||
|
sfx.shield_down();
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub(crate) enum BattleResult {
|
||||||
|
Win,
|
||||||
|
Loss,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn battle_screen(
|
||||||
|
agb: &mut Agb,
|
||||||
|
player_dice: PlayerDice,
|
||||||
|
current_level: u32,
|
||||||
|
help_background: &mut RegularMap,
|
||||||
|
) -> BattleResult {
|
||||||
|
agb.sfx.battle();
|
||||||
|
agb.sfx.frame();
|
||||||
|
|
||||||
|
help_background.set_scroll_pos((u16::MAX - 16, u16::MAX - 97).into());
|
||||||
|
crate::background::load_help_text(&mut agb.vram, help_background, 1, (0, 0));
|
||||||
|
crate::background::load_help_text(&mut agb.vram, help_background, 2, (0, 1));
|
||||||
|
|
||||||
|
let obj = &agb.obj;
|
||||||
|
|
||||||
|
let mut select_box_obj = agb.obj.object(agb.obj.sprite(SELECT_BOX.sprite(0)));
|
||||||
|
select_box_obj.show();
|
||||||
|
|
||||||
|
let num_dice = player_dice.dice.len();
|
||||||
|
|
||||||
|
let enemy_health = 5 + current_level * agb::rng::gen().rem_euclid(4) as u32;
|
||||||
|
|
||||||
|
let mut current_battle_state = CurrentBattleState {
|
||||||
|
player: PlayerState {
|
||||||
|
shield_count: 0,
|
||||||
|
health: 20,
|
||||||
|
max_health: 20,
|
||||||
|
},
|
||||||
|
enemy: EnemyState {
|
||||||
|
shield_count: 0,
|
||||||
|
health: enemy_health,
|
||||||
|
max_health: enemy_health,
|
||||||
|
},
|
||||||
|
rolled_dice: RolledDice {
|
||||||
|
rolls: player_dice
|
||||||
|
.dice
|
||||||
|
.iter()
|
||||||
|
.map(|die| DieState::Rolling(ROLL_TIME_FRAMES_ALL, die.roll(), Face::Blank))
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
player_dice: player_dice.clone(),
|
||||||
|
attacks: [None, None],
|
||||||
|
current_level,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut battle_screen_display = BattleScreenDisplay::new(obj, ¤t_battle_state);
|
||||||
|
agb.sfx.frame();
|
||||||
|
|
||||||
|
let mut selected_die = 0usize;
|
||||||
|
let mut input = agb::input::ButtonController::new();
|
||||||
|
let mut counter = 0usize;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
counter = counter.wrapping_add(1);
|
||||||
|
|
||||||
|
for action_to_apply in battle_screen_display.update(obj, ¤t_battle_state) {
|
||||||
|
if let Some(action_to_return) =
|
||||||
|
current_battle_state.apply_action(action_to_apply, &mut agb.sfx)
|
||||||
|
{
|
||||||
|
battle_screen_display.add_action(action_to_return, obj, &mut agb.sfx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for action in current_battle_state.update() {
|
||||||
|
battle_screen_display.add_action(action, obj, &mut agb.sfx);
|
||||||
|
}
|
||||||
|
|
||||||
|
current_battle_state.update_dice();
|
||||||
|
|
||||||
|
input.update();
|
||||||
|
|
||||||
|
if input.is_just_pressed(Button::LEFT) {
|
||||||
|
if selected_die == 0 {
|
||||||
|
selected_die = num_dice - 1;
|
||||||
|
} else {
|
||||||
|
selected_die -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
agb.sfx.move_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.is_just_pressed(Button::RIGHT) {
|
||||||
|
if selected_die == num_dice - 1 {
|
||||||
|
selected_die = 0;
|
||||||
|
} else {
|
||||||
|
selected_die += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
agb.sfx.move_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.is_just_pressed(Button::A) {
|
||||||
|
current_battle_state.roll_die(selected_die, ROLL_TIME_FRAMES_ONE, false);
|
||||||
|
agb.sfx.roll();
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.is_just_pressed(Button::START) {
|
||||||
|
for action in current_battle_state.accept_rolls() {
|
||||||
|
battle_screen_display.add_action(action, obj, &mut agb.sfx);
|
||||||
|
}
|
||||||
|
agb.sfx.roll_multi();
|
||||||
|
}
|
||||||
|
|
||||||
|
select_box_obj
|
||||||
|
.set_y(120 - 4)
|
||||||
|
.set_x(selected_die as u16 * 40 + 28 - 4)
|
||||||
|
.set_sprite(agb.obj.sprite(SELECT_BOX.animation_sprite(counter / 10)));
|
||||||
|
|
||||||
|
agb.star_background.update();
|
||||||
|
agb.sfx.frame();
|
||||||
|
agb.vblank.wait_for_vblank();
|
||||||
|
help_background.commit(&mut agb.vram);
|
||||||
|
help_background.show();
|
||||||
|
|
||||||
|
if current_battle_state.enemy.health == 0 {
|
||||||
|
agb.sfx.ship_explode();
|
||||||
|
help_background.hide();
|
||||||
|
crate::background::load_help_text(&mut agb.vram, help_background, 3, (0, 0));
|
||||||
|
crate::background::load_help_text(&mut agb.vram, help_background, 3, (0, 1));
|
||||||
|
return BattleResult::Win;
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_battle_state.player.health == 0 {
|
||||||
|
agb.sfx.ship_explode();
|
||||||
|
help_background.hide();
|
||||||
|
crate::background::load_help_text(&mut agb.vram, help_background, 3, (0, 0));
|
||||||
|
crate::background::load_help_text(&mut agb.vram, help_background, 3, (0, 1));
|
||||||
|
return BattleResult::Loss;
|
||||||
|
}
|
||||||
|
|
||||||
|
agb.obj.commit();
|
||||||
|
agb.star_background.commit(&mut agb.vram);
|
||||||
|
}
|
||||||
|
}
|
500
examples/hyperspace-roll/src/battle/display.rs
Normal file
|
@ -0,0 +1,500 @@
|
||||||
|
use agb::display::object::{Object, ObjectController};
|
||||||
|
use agb::rng;
|
||||||
|
use alloc::vec;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::graphics::{BURST_BULLET, DISRUPT_BULLET, SHIELD};
|
||||||
|
use crate::sfx::Sfx;
|
||||||
|
use crate::{
|
||||||
|
graphics::{
|
||||||
|
FractionDisplay, HealthBar, NumberDisplay, BULLET_SPRITE, ENEMY_ATTACK_SPRITES,
|
||||||
|
FACE_SPRITES, SHIP_SPRITES,
|
||||||
|
},
|
||||||
|
EnemyAttackType, Ship,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Action, CurrentBattleState, EnemyAttackState, MALFUNCTION_COOLDOWN_FRAMES};
|
||||||
|
|
||||||
|
struct BattleScreenDisplayObjects<'a> {
|
||||||
|
dice: Vec<Object<'a>>,
|
||||||
|
dice_cooldowns: Vec<HealthBar<'a>>,
|
||||||
|
player_shield: Vec<Object<'a>>,
|
||||||
|
enemy_shield: Vec<Object<'a>>,
|
||||||
|
|
||||||
|
player_healthbar: HealthBar<'a>,
|
||||||
|
enemy_healthbar: HealthBar<'a>,
|
||||||
|
player_health: FractionDisplay<'a>,
|
||||||
|
enemy_health: FractionDisplay<'a>,
|
||||||
|
|
||||||
|
enemy_attack_display: Vec<EnemyAttackDisplay<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BattleScreenDisplay<'a> {
|
||||||
|
objs: BattleScreenDisplayObjects<'a>,
|
||||||
|
animations: Vec<AnimationStateHolder<'a>>,
|
||||||
|
|
||||||
|
_misc_sprites: Vec<Object<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const HEALTH_BAR_WIDTH: usize = 48;
|
||||||
|
|
||||||
|
impl<'a> BattleScreenDisplay<'a> {
|
||||||
|
pub fn new(obj: &'a ObjectController, current_battle_state: &CurrentBattleState) -> Self {
|
||||||
|
let mut misc_sprites = vec![];
|
||||||
|
let player_x = 12;
|
||||||
|
let player_y = 8;
|
||||||
|
let enemy_x = 167;
|
||||||
|
|
||||||
|
let player_sprite = SHIP_SPRITES.sprite_for_ship(Ship::Player);
|
||||||
|
let enemy_sprite = SHIP_SPRITES.sprite_for_ship(if rng::gen() % 2 == 0 {
|
||||||
|
Ship::Drone
|
||||||
|
} else {
|
||||||
|
Ship::PilotedShip
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut player_obj = obj.object(obj.sprite(player_sprite));
|
||||||
|
let mut enemy_obj = obj.object(obj.sprite(enemy_sprite));
|
||||||
|
|
||||||
|
player_obj.set_x(player_x).set_y(player_y).set_z(1).show();
|
||||||
|
enemy_obj.set_x(enemy_x).set_y(player_y).set_z(1).show();
|
||||||
|
|
||||||
|
misc_sprites.push(player_obj);
|
||||||
|
misc_sprites.push(enemy_obj);
|
||||||
|
|
||||||
|
let dice: Vec<_> = current_battle_state
|
||||||
|
.rolled_dice
|
||||||
|
.faces_to_render()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, (face, _))| {
|
||||||
|
let mut die_obj = obj.object(obj.sprite(FACE_SPRITES.sprite_for_face(face)));
|
||||||
|
|
||||||
|
die_obj.set_y(120).set_x(i as u16 * 40 + 28).show();
|
||||||
|
|
||||||
|
die_obj
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let dice_cooldowns: Vec<_> = dice
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, _)| {
|
||||||
|
let mut cooldown_bar =
|
||||||
|
HealthBar::new((i as i32 * 40 + 28, 120 - 8).into(), 24, obj);
|
||||||
|
cooldown_bar.hide();
|
||||||
|
cooldown_bar
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let shield_sprite = SHIP_SPRITES.sprite_for_ship(Ship::Shield);
|
||||||
|
|
||||||
|
let player_shield: Vec<_> = (0..5)
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| {
|
||||||
|
let mut shield_obj = obj.object(obj.sprite(shield_sprite));
|
||||||
|
shield_obj
|
||||||
|
.set_x(player_x + 18 + 11 * i)
|
||||||
|
.set_y(player_y)
|
||||||
|
.hide();
|
||||||
|
|
||||||
|
shield_obj
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let enemy_shield: Vec<_> = (0..5)
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| {
|
||||||
|
let mut shield_obj = obj.object(obj.sprite(shield_sprite));
|
||||||
|
shield_obj
|
||||||
|
.set_x(enemy_x - 16 - 11 * i)
|
||||||
|
.set_y(player_y)
|
||||||
|
.set_hflip(true)
|
||||||
|
.hide();
|
||||||
|
|
||||||
|
shield_obj
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let player_healthbar_x = 18;
|
||||||
|
let enemy_healthbar_x = 180;
|
||||||
|
let player_healthbar = HealthBar::new(
|
||||||
|
(player_healthbar_x, player_y - 8).into(),
|
||||||
|
HEALTH_BAR_WIDTH,
|
||||||
|
obj,
|
||||||
|
);
|
||||||
|
let enemy_healthbar = HealthBar::new(
|
||||||
|
(enemy_healthbar_x, player_y - 8).into(),
|
||||||
|
HEALTH_BAR_WIDTH,
|
||||||
|
obj,
|
||||||
|
);
|
||||||
|
|
||||||
|
let player_health_display = FractionDisplay::new(
|
||||||
|
(
|
||||||
|
player_healthbar_x + HEALTH_BAR_WIDTH as u16 / 2 - 16,
|
||||||
|
player_y,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
3,
|
||||||
|
obj,
|
||||||
|
);
|
||||||
|
let enemy_health_display = FractionDisplay::new(
|
||||||
|
(
|
||||||
|
enemy_healthbar_x + HEALTH_BAR_WIDTH as u16 / 2 - 16,
|
||||||
|
player_y,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
3,
|
||||||
|
obj,
|
||||||
|
);
|
||||||
|
|
||||||
|
let enemy_attack_display = (0..2)
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| {
|
||||||
|
let mut attack_obj = obj.object(
|
||||||
|
obj.sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(EnemyAttackType::Attack)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let attack_obj_position = (120, 56 + 32 * i).into();
|
||||||
|
attack_obj.set_position(attack_obj_position).hide();
|
||||||
|
|
||||||
|
let mut attack_cooldown =
|
||||||
|
HealthBar::new(attack_obj_position + (32, 8).into(), 48, obj);
|
||||||
|
attack_cooldown.hide();
|
||||||
|
|
||||||
|
let attack_number_display =
|
||||||
|
NumberDisplay::new(attack_obj_position - (8, -10).into());
|
||||||
|
|
||||||
|
EnemyAttackDisplay::new(attack_obj, attack_cooldown, attack_number_display)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let objs = BattleScreenDisplayObjects {
|
||||||
|
dice,
|
||||||
|
dice_cooldowns,
|
||||||
|
player_shield,
|
||||||
|
enemy_shield,
|
||||||
|
|
||||||
|
player_healthbar,
|
||||||
|
enemy_healthbar,
|
||||||
|
player_health: player_health_display,
|
||||||
|
enemy_health: enemy_health_display,
|
||||||
|
|
||||||
|
enemy_attack_display,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
objs,
|
||||||
|
|
||||||
|
animations: vec![],
|
||||||
|
|
||||||
|
_misc_sprites: misc_sprites,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(
|
||||||
|
&mut self,
|
||||||
|
obj: &'a ObjectController,
|
||||||
|
current_battle_state: &CurrentBattleState,
|
||||||
|
) -> Vec<Action> {
|
||||||
|
for (i, player_shield) in self.objs.player_shield.iter_mut().enumerate() {
|
||||||
|
if i < current_battle_state.player.shield_count as usize {
|
||||||
|
player_shield
|
||||||
|
.show()
|
||||||
|
.set_sprite(obj.sprite(SHIELD.sprite(0)));
|
||||||
|
} else {
|
||||||
|
player_shield.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, player_shield) in self.objs.enemy_shield.iter_mut().enumerate() {
|
||||||
|
if i < current_battle_state.enemy.shield_count as usize {
|
||||||
|
player_shield
|
||||||
|
.show()
|
||||||
|
.set_sprite(obj.sprite(SHIELD.sprite(0)));
|
||||||
|
} else {
|
||||||
|
player_shield.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.objs.player_healthbar.set_value(
|
||||||
|
((current_battle_state.player.health * HEALTH_BAR_WIDTH as u32)
|
||||||
|
/ current_battle_state.player.max_health) as usize,
|
||||||
|
obj,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.objs.enemy_healthbar.set_value(
|
||||||
|
((current_battle_state.enemy.health * HEALTH_BAR_WIDTH as u32)
|
||||||
|
/ current_battle_state.enemy.max_health) as usize,
|
||||||
|
obj,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.objs.player_health.set_value(
|
||||||
|
current_battle_state.player.health as usize,
|
||||||
|
current_battle_state.player.max_health as usize,
|
||||||
|
obj,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.objs.enemy_health.set_value(
|
||||||
|
current_battle_state.enemy.health as usize,
|
||||||
|
current_battle_state.enemy.max_health as usize,
|
||||||
|
obj,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i, attack) in current_battle_state.attacks.iter().enumerate() {
|
||||||
|
self.objs.enemy_attack_display[i].update(attack, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut actions_to_apply = vec![];
|
||||||
|
|
||||||
|
// update the dice display to display the current values
|
||||||
|
for ((die_obj, (current_face, cooldown)), cooldown_healthbar) in self
|
||||||
|
.objs
|
||||||
|
.dice
|
||||||
|
.iter_mut()
|
||||||
|
.zip(current_battle_state.rolled_dice.faces_to_render())
|
||||||
|
.zip(self.objs.dice_cooldowns.iter_mut())
|
||||||
|
{
|
||||||
|
die_obj.set_sprite(obj.sprite(FACE_SPRITES.sprite_for_face(current_face)));
|
||||||
|
|
||||||
|
if let Some(cooldown) = cooldown {
|
||||||
|
cooldown_healthbar
|
||||||
|
.set_value((cooldown * 24 / MALFUNCTION_COOLDOWN_FRAMES) as usize, obj);
|
||||||
|
cooldown_healthbar.show();
|
||||||
|
} else {
|
||||||
|
cooldown_healthbar.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut animations_to_remove = vec![];
|
||||||
|
for (i, animation) in self.animations.iter_mut().enumerate() {
|
||||||
|
match animation.update(&mut self.objs, obj, current_battle_state) {
|
||||||
|
AnimationUpdateState::RemoveWithAction(a) => {
|
||||||
|
actions_to_apply.push(a);
|
||||||
|
animations_to_remove.push(i);
|
||||||
|
}
|
||||||
|
AnimationUpdateState::Continue => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for &animation_to_remove in animations_to_remove.iter().rev() {
|
||||||
|
self.animations.swap_remove(animation_to_remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
actions_to_apply
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_action(&mut self, action: Action, obj: &'a ObjectController, sfx: &mut Sfx) {
|
||||||
|
play_sound_for_action_start(&action, sfx);
|
||||||
|
|
||||||
|
self.animations
|
||||||
|
.push(AnimationStateHolder::for_action(action, obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn play_sound_for_action_start(action: &Action, sfx: &mut Sfx) {
|
||||||
|
match action {
|
||||||
|
Action::PlayerShoot { .. } | Action::EnemyShoot { .. } => sfx.shoot(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EnemyAttackDisplay<'a> {
|
||||||
|
face: Object<'a>,
|
||||||
|
cooldown: HealthBar<'a>,
|
||||||
|
number: NumberDisplay<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EnemyAttackDisplay<'a> {
|
||||||
|
pub fn new(face: Object<'a>, cooldown: HealthBar<'a>, number: NumberDisplay<'a>) -> Self {
|
||||||
|
Self {
|
||||||
|
face,
|
||||||
|
cooldown,
|
||||||
|
number,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, attack: &Option<EnemyAttackState>, obj: &'a ObjectController) {
|
||||||
|
if let Some(attack) = attack {
|
||||||
|
self.face.show().set_sprite(
|
||||||
|
obj.sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(attack.attack_type())),
|
||||||
|
);
|
||||||
|
self.cooldown
|
||||||
|
.set_value((attack.cooldown * 48 / attack.max_cooldown) as usize, obj);
|
||||||
|
self.cooldown.show();
|
||||||
|
|
||||||
|
self.number.set_value(attack.value_to_show(), obj);
|
||||||
|
} else {
|
||||||
|
self.face.hide();
|
||||||
|
self.cooldown.hide();
|
||||||
|
self.number.set_value(None, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AnimationState<'a> {
|
||||||
|
PlayerShoot { bullet: Object<'a>, x: i32 },
|
||||||
|
PlayerActivateShield { amount: u32, frame: usize },
|
||||||
|
PlayerDisrupt { bullet: Object<'a>, x: i32 },
|
||||||
|
PlayerBurstShield { frame: usize },
|
||||||
|
PlayerSendBurstShield { bullet: Object<'a>, x: i32 },
|
||||||
|
PlayerHeal {},
|
||||||
|
EnemyShoot { bullet: Object<'a>, x: i32 },
|
||||||
|
EnemyShield { amount: u32, frame: usize },
|
||||||
|
EnemyHeal {},
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AnimationStateHolder<'a> {
|
||||||
|
action: Action,
|
||||||
|
state: AnimationState<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AnimationUpdateState {
|
||||||
|
RemoveWithAction(Action),
|
||||||
|
Continue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AnimationStateHolder<'a> {
|
||||||
|
fn for_action(a: Action, obj: &'a ObjectController) -> Self {
|
||||||
|
let state = match a {
|
||||||
|
Action::PlayerActivateShield { amount, .. } => {
|
||||||
|
AnimationState::PlayerActivateShield { amount, frame: 0 }
|
||||||
|
}
|
||||||
|
Action::PlayerShoot { .. } => AnimationState::PlayerShoot {
|
||||||
|
bullet: obj.object(obj.sprite(BULLET_SPRITE)),
|
||||||
|
x: 64,
|
||||||
|
},
|
||||||
|
Action::PlayerDisrupt { .. } => AnimationState::PlayerDisrupt {
|
||||||
|
bullet: obj.object(obj.sprite(DISRUPT_BULLET)),
|
||||||
|
x: 64,
|
||||||
|
},
|
||||||
|
Action::PlayerHeal { .. } => AnimationState::PlayerHeal {},
|
||||||
|
Action::PlayerBurstShield { .. } => AnimationState::PlayerBurstShield { frame: 0 },
|
||||||
|
Action::PlayerSendBurstShield { .. } => AnimationState::PlayerSendBurstShield {
|
||||||
|
bullet: obj.object(obj.sprite(BURST_BULLET)),
|
||||||
|
x: 64,
|
||||||
|
},
|
||||||
|
Action::EnemyShoot { .. } => AnimationState::EnemyShoot {
|
||||||
|
bullet: obj.object(obj.sprite(BULLET_SPRITE)),
|
||||||
|
x: 175,
|
||||||
|
},
|
||||||
|
Action::EnemyShield { amount, .. } => AnimationState::EnemyShield { amount, frame: 0 },
|
||||||
|
Action::EnemyHeal { .. } => AnimationState::EnemyHeal {},
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { action: a, state }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
objs: &mut BattleScreenDisplayObjects<'a>,
|
||||||
|
obj: &'a ObjectController,
|
||||||
|
current_battle_state: &CurrentBattleState,
|
||||||
|
) -> AnimationUpdateState {
|
||||||
|
match &mut self.state {
|
||||||
|
AnimationState::PlayerShoot { bullet, x } => {
|
||||||
|
bullet.show().set_x(*x as u16).set_y(36);
|
||||||
|
*x += 4;
|
||||||
|
|
||||||
|
if *x > 180 {
|
||||||
|
AnimationUpdateState::RemoveWithAction(self.action.clone())
|
||||||
|
} else {
|
||||||
|
AnimationUpdateState::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimationState::PlayerDisrupt { bullet, x } => {
|
||||||
|
bullet.show().set_x(*x as u16).set_y(36);
|
||||||
|
*x += 2;
|
||||||
|
|
||||||
|
if *x > 180 {
|
||||||
|
AnimationUpdateState::RemoveWithAction(self.action.clone())
|
||||||
|
} else {
|
||||||
|
AnimationUpdateState::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimationState::PlayerActivateShield { amount, frame } => {
|
||||||
|
// find all the shields that need animating
|
||||||
|
let current_player_shields = current_battle_state.player.shield_count;
|
||||||
|
if current_player_shields < *amount {
|
||||||
|
for i in current_player_shields..*amount {
|
||||||
|
objs.player_shield[i as usize]
|
||||||
|
.show()
|
||||||
|
.set_sprite(obj.sprite(SHIELD.sprite(3 - *frame / 2)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return AnimationUpdateState::RemoveWithAction(self.action.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
*frame += 1;
|
||||||
|
|
||||||
|
if *frame >= 6 {
|
||||||
|
AnimationUpdateState::RemoveWithAction(self.action.clone())
|
||||||
|
} else {
|
||||||
|
AnimationUpdateState::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimationState::EnemyShoot { bullet, x } => {
|
||||||
|
bullet.show().set_hflip(true).set_x(*x as u16).set_y(36);
|
||||||
|
*x -= 4;
|
||||||
|
|
||||||
|
if *x < 50 {
|
||||||
|
AnimationUpdateState::RemoveWithAction(self.action.clone())
|
||||||
|
} else {
|
||||||
|
AnimationUpdateState::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimationState::EnemyShield { amount, frame } => {
|
||||||
|
// find all the shields that need animating
|
||||||
|
let current_enemy_shields = current_battle_state.enemy.shield_count;
|
||||||
|
if current_enemy_shields < *amount {
|
||||||
|
for i in current_enemy_shields..*amount {
|
||||||
|
objs.enemy_shield[i as usize]
|
||||||
|
.show()
|
||||||
|
.set_sprite(obj.sprite(SHIELD.sprite(3 - *frame / 2)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return AnimationUpdateState::RemoveWithAction(self.action.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
*frame += 1;
|
||||||
|
|
||||||
|
if *frame > 6 {
|
||||||
|
AnimationUpdateState::RemoveWithAction(self.action.clone())
|
||||||
|
} else {
|
||||||
|
AnimationUpdateState::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimationState::EnemyHeal {} => {
|
||||||
|
AnimationUpdateState::RemoveWithAction(self.action.clone()) // TODO: Animation for healing
|
||||||
|
}
|
||||||
|
AnimationState::PlayerHeal {} => {
|
||||||
|
AnimationUpdateState::RemoveWithAction(self.action.clone()) // TODO: Animation for healing
|
||||||
|
}
|
||||||
|
AnimationState::PlayerBurstShield { frame } => {
|
||||||
|
if *frame < 10 {
|
||||||
|
for shield in objs.player_shield.iter_mut() {
|
||||||
|
shield.set_sprite(obj.sprite(SHIELD.sprite(*frame / 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
*frame += 1;
|
||||||
|
|
||||||
|
AnimationUpdateState::Continue
|
||||||
|
} else {
|
||||||
|
for shield in objs.player_shield.iter_mut() {
|
||||||
|
shield.set_sprite(obj.sprite(SHIELD.sprite(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationUpdateState::RemoveWithAction(self.action.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimationState::PlayerSendBurstShield { bullet, x } => {
|
||||||
|
bullet.show().set_x(*x as u16).set_y(36);
|
||||||
|
*x += 1;
|
||||||
|
|
||||||
|
if *x > 180 {
|
||||||
|
AnimationUpdateState::RemoveWithAction(self.action.clone())
|
||||||
|
} else {
|
||||||
|
AnimationUpdateState::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
402
examples/hyperspace-roll/src/customise.rs
Normal file
|
@ -0,0 +1,402 @@
|
||||||
|
use agb::{
|
||||||
|
display::{
|
||||||
|
object::{Object, ObjectController},
|
||||||
|
palette16::Palette16,
|
||||||
|
tiled::{RegularMap, TileSet, TileSetting},
|
||||||
|
HEIGHT, WIDTH,
|
||||||
|
},
|
||||||
|
include_gfx,
|
||||||
|
input::{Button, Tri},
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
graphics::{FACE_SPRITES, MODIFIED_BOX, SELECTED_BOX, SELECT_BOX},
|
||||||
|
Agb, Die, Face, PlayerDice,
|
||||||
|
};
|
||||||
|
|
||||||
|
include_gfx!("gfx/descriptions.toml");
|
||||||
|
|
||||||
|
pub const DESCRIPTIONS_1_PALETTE: &Palette16 = &descriptions::descriptions1.palettes[0];
|
||||||
|
pub const DESCRIPTIONS_2_PALETTE: &Palette16 = &descriptions::descriptions2.palettes[0];
|
||||||
|
|
||||||
|
enum CustomiseState {
|
||||||
|
Dice,
|
||||||
|
Face,
|
||||||
|
Upgrade,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
struct Cursor {
|
||||||
|
dice: usize,
|
||||||
|
face: usize,
|
||||||
|
upgrade: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn net_position_for_index(idx: usize) -> (u32, u32) {
|
||||||
|
if idx == 4 {
|
||||||
|
(1, 0)
|
||||||
|
} else if idx == 5 {
|
||||||
|
(1, 2)
|
||||||
|
} else {
|
||||||
|
(idx as u32, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn screen_position_for_index(idx: usize) -> (u32, u32) {
|
||||||
|
let (x, y) = net_position_for_index(idx);
|
||||||
|
(x * 32 + 20, y * 32 + HEIGHT as u32 - 3 * 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_net_position_lr(idx: usize, direction: Tri) -> usize {
|
||||||
|
match direction {
|
||||||
|
Tri::Zero => idx,
|
||||||
|
Tri::Positive => {
|
||||||
|
if idx >= 4 {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
(idx + 1) % 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tri::Negative => {
|
||||||
|
if idx >= 4 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
idx.checked_sub(1).unwrap_or(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_net_position_ud(idx: usize, direction: Tri) -> usize {
|
||||||
|
match direction {
|
||||||
|
Tri::Zero => idx,
|
||||||
|
Tri::Negative => {
|
||||||
|
if idx < 4 {
|
||||||
|
4
|
||||||
|
} else if idx == 4 {
|
||||||
|
5
|
||||||
|
} else if idx == 5 {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tri::Positive => {
|
||||||
|
if idx < 4 {
|
||||||
|
5
|
||||||
|
} else if idx == 4 {
|
||||||
|
1
|
||||||
|
} else if idx == 5 {
|
||||||
|
4
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_dice_display<'a>(gfx: &'a ObjectController, dice: &'_ PlayerDice) -> Vec<Object<'a>> {
|
||||||
|
let mut objects = Vec::new();
|
||||||
|
for (idx, dice) in dice.dice.iter().enumerate() {
|
||||||
|
let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(dice.faces[1])));
|
||||||
|
obj.set_x((idx as i32 * 32 - 24 / 2 + 20) as u16);
|
||||||
|
obj.set_y(16 - 24 / 2);
|
||||||
|
|
||||||
|
obj.show();
|
||||||
|
|
||||||
|
objects.push(obj);
|
||||||
|
}
|
||||||
|
objects
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_net<'a>(gfx: &'a ObjectController, die: &'_ Die, modified: &[usize]) -> Vec<Object<'a>> {
|
||||||
|
let mut objects = Vec::new();
|
||||||
|
for (idx, &face) in die.faces.iter().enumerate() {
|
||||||
|
let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(face)));
|
||||||
|
let (x, y) = screen_position_for_index(idx);
|
||||||
|
obj.set_x((x - 24 / 2) as u16);
|
||||||
|
obj.set_y((y - 24 / 2) as u16);
|
||||||
|
|
||||||
|
obj.show();
|
||||||
|
|
||||||
|
objects.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
for &m in modified.iter().chain(core::iter::once(&3)) {
|
||||||
|
let mut obj = gfx.object(gfx.sprite(MODIFIED_BOX));
|
||||||
|
let (x, y) = screen_position_for_index(m);
|
||||||
|
obj.set_x((x - 32 / 2) as u16);
|
||||||
|
obj.set_y((y - 32 / 2) as u16);
|
||||||
|
|
||||||
|
obj.show();
|
||||||
|
|
||||||
|
objects.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
objects
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upgrade_position(idx: usize) -> (u32, u32) {
|
||||||
|
(
|
||||||
|
(WIDTH - 80) as u32,
|
||||||
|
(idx * 32 + HEIGHT as usize - 3 * 32) as u32,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_upgrade_objects<'a>(gfx: &'a ObjectController, upgrades: &[Face]) -> Vec<Object<'a>> {
|
||||||
|
let mut objects = Vec::new();
|
||||||
|
for (idx, &upgrade) in upgrades.iter().enumerate() {
|
||||||
|
let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(upgrade)));
|
||||||
|
let (x, y) = upgrade_position(idx);
|
||||||
|
obj.set_x((x - 24 / 2) as u16);
|
||||||
|
obj.set_y((y - 24 / 2) as u16);
|
||||||
|
|
||||||
|
obj.show();
|
||||||
|
|
||||||
|
objects.push(obj);
|
||||||
|
}
|
||||||
|
objects
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn customise_screen(
|
||||||
|
agb: &mut Agb,
|
||||||
|
mut player_dice: PlayerDice,
|
||||||
|
descriptions_map: &mut RegularMap,
|
||||||
|
help_background: &mut RegularMap,
|
||||||
|
level: u32,
|
||||||
|
) -> PlayerDice {
|
||||||
|
agb.sfx.customise();
|
||||||
|
agb.sfx.frame();
|
||||||
|
descriptions_map.set_scroll_pos((u16::MAX - 174, u16::MAX - 52).into());
|
||||||
|
|
||||||
|
help_background.set_scroll_pos((u16::MAX - 148, u16::MAX - 34).into());
|
||||||
|
crate::background::load_help_text(&mut agb.vram, help_background, 0, (0, 0));
|
||||||
|
|
||||||
|
let descriptions_1_tileset = TileSet::new(
|
||||||
|
descriptions::descriptions1.tiles,
|
||||||
|
agb::display::tiled::TileFormat::FourBpp,
|
||||||
|
);
|
||||||
|
let descriptions_2_tileset = TileSet::new(
|
||||||
|
descriptions::descriptions2.tiles,
|
||||||
|
agb::display::tiled::TileFormat::FourBpp,
|
||||||
|
);
|
||||||
|
|
||||||
|
// create the dice
|
||||||
|
|
||||||
|
let mut _net = create_net(&agb.obj, &player_dice.dice[0], &[]);
|
||||||
|
let mut _dice = create_dice_display(&agb.obj, &player_dice);
|
||||||
|
|
||||||
|
agb.sfx.frame();
|
||||||
|
|
||||||
|
let mut upgrades = crate::level_generation::generate_upgrades(level);
|
||||||
|
let mut _upgrade_objects = create_upgrade_objects(&agb.obj, &upgrades);
|
||||||
|
|
||||||
|
let mut input = agb::input::ButtonController::new();
|
||||||
|
|
||||||
|
let mut select_box = agb.obj.object(agb.obj.sprite(SELECT_BOX.sprite(0)));
|
||||||
|
|
||||||
|
select_box.show();
|
||||||
|
|
||||||
|
let mut selected_dice = agb.obj.object(agb.obj.sprite(SELECTED_BOX));
|
||||||
|
selected_dice.hide();
|
||||||
|
let mut selected_face = agb.obj.object(agb.obj.sprite(SELECTED_BOX));
|
||||||
|
selected_face.hide();
|
||||||
|
agb.sfx.frame();
|
||||||
|
|
||||||
|
let mut counter = 0usize;
|
||||||
|
|
||||||
|
let mut state = CustomiseState::Dice;
|
||||||
|
|
||||||
|
let mut cursor = Cursor {
|
||||||
|
dice: 0,
|
||||||
|
face: 1,
|
||||||
|
upgrade: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut modified: Vec<Cursor> = Vec::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
counter = counter.wrapping_add(1);
|
||||||
|
input.update();
|
||||||
|
let ud = (
|
||||||
|
input.is_just_pressed(Button::UP),
|
||||||
|
input.is_just_pressed(Button::DOWN),
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
let lr = (
|
||||||
|
input.is_just_pressed(Button::LEFT),
|
||||||
|
input.is_just_pressed(Button::RIGHT),
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
|
||||||
|
if ud != Tri::Zero || lr != Tri::Zero {
|
||||||
|
agb.sfx.move_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
match &mut state {
|
||||||
|
CustomiseState::Dice => {
|
||||||
|
selected_dice.hide();
|
||||||
|
let new_dice = (cursor.dice as isize + lr as isize)
|
||||||
|
.rem_euclid(player_dice.dice.len() as isize)
|
||||||
|
as usize;
|
||||||
|
if new_dice != cursor.dice {
|
||||||
|
cursor.dice = new_dice;
|
||||||
|
_net = create_net(
|
||||||
|
&agb.obj,
|
||||||
|
&player_dice.dice[cursor.dice],
|
||||||
|
&modified
|
||||||
|
.iter()
|
||||||
|
.filter_map(|x| (x.dice == cursor.dice).then_some(x.face))
|
||||||
|
.collect::<Vec<usize>>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
select_box.set_x((cursor.dice as i32 * 32 - 32 / 2 + 20) as u16);
|
||||||
|
select_box.set_y(0);
|
||||||
|
|
||||||
|
if input.is_just_pressed(Button::A) {
|
||||||
|
selected_dice.set_x((cursor.dice as i32 * 32 - 32 / 2 + 20) as u16);
|
||||||
|
selected_dice.set_y(0);
|
||||||
|
selected_dice.show();
|
||||||
|
state = CustomiseState::Face;
|
||||||
|
agb.sfx.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CustomiseState::Face => {
|
||||||
|
cursor.face = move_net_position_lr(cursor.face, lr);
|
||||||
|
cursor.face = move_net_position_ud(cursor.face, ud);
|
||||||
|
|
||||||
|
let (x, y) = screen_position_for_index(cursor.face);
|
||||||
|
select_box.set_x((x - 32 / 2) as u16);
|
||||||
|
select_box.set_y((y - 32 / 2) as u16);
|
||||||
|
selected_face.hide();
|
||||||
|
|
||||||
|
if input.is_just_pressed(Button::B) {
|
||||||
|
state = CustomiseState::Dice;
|
||||||
|
agb.sfx.back();
|
||||||
|
} else if input.is_just_pressed(Button::A)
|
||||||
|
&& !upgrades.is_empty()
|
||||||
|
&& !modified.contains(&Cursor {
|
||||||
|
dice: cursor.dice,
|
||||||
|
face: cursor.face,
|
||||||
|
upgrade: 0,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
selected_face.set_x((x - 32 / 2) as u16);
|
||||||
|
selected_face.set_y((y - 32 / 2) as u16);
|
||||||
|
selected_face.show();
|
||||||
|
|
||||||
|
cursor.upgrade += upgrades.len();
|
||||||
|
|
||||||
|
state = CustomiseState::Upgrade;
|
||||||
|
agb.sfx.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CustomiseState::Upgrade => {
|
||||||
|
let old_updade = cursor.upgrade;
|
||||||
|
cursor.upgrade = (cursor.upgrade as isize + ud as isize)
|
||||||
|
.rem_euclid(upgrades.len() as isize) as usize;
|
||||||
|
|
||||||
|
if (upgrades[cursor.upgrade] as u32) < 17 {
|
||||||
|
if cursor.upgrade != old_updade {
|
||||||
|
for y in 0..11 {
|
||||||
|
for x in 0..8 {
|
||||||
|
if (upgrades[cursor.upgrade] as usize) < 10 {
|
||||||
|
descriptions_map.set_tile(
|
||||||
|
&mut agb.vram,
|
||||||
|
(x, y).into(),
|
||||||
|
&descriptions_1_tileset,
|
||||||
|
TileSetting::new(
|
||||||
|
y * 8 + x + 8 * 11 * upgrades[cursor.upgrade] as u16,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
descriptions_map.set_tile(
|
||||||
|
&mut agb.vram,
|
||||||
|
(x, y).into(),
|
||||||
|
&descriptions_2_tileset,
|
||||||
|
TileSetting::new(
|
||||||
|
y * 8
|
||||||
|
+ x
|
||||||
|
+ 8 * 11 * (upgrades[cursor.upgrade] as u16 - 10),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descriptions_map.show();
|
||||||
|
} else {
|
||||||
|
descriptions_map.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
let (x, y) = upgrade_position(cursor.upgrade);
|
||||||
|
select_box.set_x((x - 32 / 2) as u16);
|
||||||
|
select_box.set_y((y - 32 / 2) as u16);
|
||||||
|
|
||||||
|
if input.is_just_pressed(Button::B) {
|
||||||
|
state = CustomiseState::Face;
|
||||||
|
agb.sfx.back();
|
||||||
|
} else if input.is_just_pressed(Button::A)
|
||||||
|
&& player_dice.dice[cursor.dice].faces[cursor.face] != upgrades[cursor.upgrade]
|
||||||
|
{
|
||||||
|
descriptions_map.hide();
|
||||||
|
|
||||||
|
modified.push(Cursor {
|
||||||
|
dice: cursor.dice,
|
||||||
|
face: cursor.face,
|
||||||
|
upgrade: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
player_dice.dice[cursor.dice].faces[cursor.face] = upgrades[cursor.upgrade];
|
||||||
|
upgrades.remove(cursor.upgrade);
|
||||||
|
_upgrade_objects = create_upgrade_objects(&agb.obj, &upgrades);
|
||||||
|
|
||||||
|
_net = create_net(
|
||||||
|
&agb.obj,
|
||||||
|
&player_dice.dice[cursor.dice],
|
||||||
|
&modified
|
||||||
|
.iter()
|
||||||
|
.filter_map(|x| (x.dice == cursor.dice).then_some(x.face))
|
||||||
|
.collect::<Vec<usize>>(),
|
||||||
|
);
|
||||||
|
_dice = create_dice_display(&agb.obj, &player_dice);
|
||||||
|
state = CustomiseState::Face;
|
||||||
|
agb.sfx.accept();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if upgrades.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
select_box.set_sprite(agb.obj.sprite(SELECT_BOX.animation_sprite(counter / 10)));
|
||||||
|
|
||||||
|
agb.star_background.update();
|
||||||
|
let _ = agb::rng::gen();
|
||||||
|
agb.sfx.frame();
|
||||||
|
agb.vblank.wait_for_vblank();
|
||||||
|
agb.obj.commit();
|
||||||
|
descriptions_map.commit(&mut agb.vram);
|
||||||
|
help_background.commit(&mut agb.vram);
|
||||||
|
help_background.show();
|
||||||
|
agb.star_background.commit(&mut agb.vram);
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptions_map.hide();
|
||||||
|
help_background.hide();
|
||||||
|
crate::background::load_help_text(&mut agb.vram, help_background, 3, (0, 0));
|
||||||
|
crate::background::load_help_text(&mut agb.vram, help_background, 3, (0, 1));
|
||||||
|
descriptions_map.clear(&mut agb.vram);
|
||||||
|
|
||||||
|
player_dice
|
||||||
|
}
|
300
examples/hyperspace-roll/src/graphics.rs
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
use agb::{
|
||||||
|
display::object::{Object, ObjectController, Sprite, Tag},
|
||||||
|
fixnum::Vector2D,
|
||||||
|
};
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::{EnemyAttackType, Face, Ship};
|
||||||
|
|
||||||
|
const SPRITES: &agb::display::object::Graphics = agb::include_aseprite!(
|
||||||
|
"gfx/dice-faces.aseprite",
|
||||||
|
"gfx/ships.aseprite",
|
||||||
|
"gfx/small-sprites.aseprite"
|
||||||
|
);
|
||||||
|
pub const FACE_SPRITES: &FaceSprites = &FaceSprites::load_face_sprites();
|
||||||
|
pub const ENEMY_ATTACK_SPRITES: &EnemyAttackSprites = &EnemyAttackSprites::new();
|
||||||
|
pub const SELECT_BOX: &Tag = SPRITES.tags().get("selection");
|
||||||
|
pub const SELECTED_BOX: &Sprite = SPRITES.tags().get("selected").sprite(0);
|
||||||
|
pub const MODIFIED_BOX: &Sprite = SPRITES.tags().get("modified").sprite(0);
|
||||||
|
|
||||||
|
pub const BULLET_SPRITE: &Sprite = SPRITES.tags().get("bullet").sprite(0);
|
||||||
|
pub const DISRUPT_BULLET: &Sprite = SPRITES.tags().get("disrupt bullet").sprite(0);
|
||||||
|
pub const BURST_BULLET: &Sprite = SPRITES.tags().get("burst shield bullet").sprite(0);
|
||||||
|
pub const SHIELD: &Tag = SPRITES.tags().get("ship shield");
|
||||||
|
|
||||||
|
pub const SHIP_SPRITES: &ShipSprites = &ShipSprites::load_ship_sprites();
|
||||||
|
|
||||||
|
pub const SMALL_SPRITES: &SmallSprites = &SmallSprites {};
|
||||||
|
|
||||||
|
pub struct FaceSprites {
|
||||||
|
sprites: [&'static Sprite; 17],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FaceSprites {
|
||||||
|
const fn load_face_sprites() -> Self {
|
||||||
|
const S_SHOOT: &Sprite = SPRITES.tags().get("shoot").sprite(0);
|
||||||
|
const S_SHIELD: &Sprite = SPRITES.tags().get("shield").sprite(0);
|
||||||
|
const S_MALFUNCTION: &Sprite = SPRITES.tags().get("malfunction").sprite(0);
|
||||||
|
const S_HEAL: &Sprite = SPRITES.tags().get("player_heal").sprite(0);
|
||||||
|
const S_BYPASS: &Sprite = SPRITES.tags().get("shield bypass").sprite(0);
|
||||||
|
const S_DOUBLESHOT: &Sprite = SPRITES.tags().get("double shoot").sprite(0);
|
||||||
|
const S_TRIPLESHOT: &Sprite = SPRITES.tags().get("triple shoot").sprite(0);
|
||||||
|
const S_BLANK: &Sprite = SPRITES.tags().get("blank").sprite(0);
|
||||||
|
const S_DISRUPT: &Sprite = SPRITES.tags().get("disruption").sprite(0);
|
||||||
|
const S_MALFUNCTION_SHOOT: &Sprite = SPRITES.tags().get("malfunction shot").sprite(0);
|
||||||
|
const S_DOUBLE_SHIELD: &Sprite = SPRITES.tags().get("double shield").sprite(0);
|
||||||
|
const S_TRIPLE_SHIELD: &Sprite = SPRITES.tags().get("triple shield").sprite(0);
|
||||||
|
const S_DOUBLE_SHIELD_VALUE: &Sprite = SPRITES.tags().get("double shield value").sprite(0);
|
||||||
|
const S_DOUBLE_SHOT_VALUE: &Sprite = SPRITES.tags().get("double shoot power").sprite(0);
|
||||||
|
const S_TRIPLE_SHOT_VALUE: &Sprite = SPRITES.tags().get("triple shoot power").sprite(0);
|
||||||
|
const S_BURST_SHIELD: &Sprite = SPRITES.tags().get("burst shield").sprite(0);
|
||||||
|
const S_INVERT: &Sprite = SPRITES.tags().get("swap shield and shoot").sprite(0);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
sprites: [
|
||||||
|
S_SHOOT,
|
||||||
|
S_SHIELD,
|
||||||
|
S_MALFUNCTION,
|
||||||
|
S_HEAL,
|
||||||
|
S_BYPASS,
|
||||||
|
S_DOUBLESHOT,
|
||||||
|
S_TRIPLESHOT,
|
||||||
|
S_BLANK,
|
||||||
|
S_DISRUPT,
|
||||||
|
S_MALFUNCTION_SHOOT,
|
||||||
|
S_DOUBLE_SHIELD,
|
||||||
|
S_TRIPLE_SHIELD,
|
||||||
|
S_DOUBLE_SHIELD_VALUE,
|
||||||
|
S_DOUBLE_SHOT_VALUE,
|
||||||
|
S_TRIPLE_SHOT_VALUE,
|
||||||
|
S_BURST_SHIELD,
|
||||||
|
S_INVERT,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sprite_for_face(&self, face: Face) -> &'static Sprite {
|
||||||
|
self.sprites[face as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ShipSprites {
|
||||||
|
sprites: [&'static Sprite; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShipSprites {
|
||||||
|
const fn load_ship_sprites() -> Self {
|
||||||
|
const S_PLAYER: &Sprite = SPRITES.tags().get("player").sprite(0);
|
||||||
|
const S_DRONE: &Sprite = SPRITES.tags().get("drone").sprite(0);
|
||||||
|
const S_PILOTED_SHIP: &Sprite = SPRITES.tags().get("piloted ship").sprite(0);
|
||||||
|
const S_SHIELD: &Sprite = SPRITES.tags().get("ship shield").sprite(0);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
sprites: [S_PLAYER, S_DRONE, S_PILOTED_SHIP, S_SHIELD],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sprite_for_ship(&self, ship: Ship) -> &'static Sprite {
|
||||||
|
self.sprites[ship as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SmallSprites;
|
||||||
|
|
||||||
|
impl SmallSprites {
|
||||||
|
pub const fn number(&self, i: u32) -> &'static Sprite {
|
||||||
|
SPRITES.tags().get("numbers").sprite(i as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn slash(&self) -> &'static Sprite {
|
||||||
|
SPRITES.tags().get("numbers").sprite(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn red_bar(&self, i: usize) -> &'static Sprite {
|
||||||
|
SPRITES.tags().get("red bar").sprite(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EnemyAttackSprites {
|
||||||
|
sprites: [&'static Sprite; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnemyAttackSprites {
|
||||||
|
const fn new() -> Self {
|
||||||
|
const S_SHOOT: &Sprite = SPRITES.tags().get("enemy shoot").sprite(0);
|
||||||
|
const S_SHIELD: &Sprite = SPRITES.tags().get("enemy shield").sprite(0);
|
||||||
|
const S_HEAL: &Sprite = SPRITES.tags().get("enemy heal").sprite(0);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
sprites: [S_SHOOT, S_SHIELD, S_HEAL],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sprite_for_attack(&self, attack: EnemyAttackType) -> &'static Sprite {
|
||||||
|
self.sprites[attack as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HealthBar<'a> {
|
||||||
|
max: usize,
|
||||||
|
sprites: Vec<Object<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> HealthBar<'a> {
|
||||||
|
pub fn new(pos: Vector2D<i32>, max: usize, obj: &'a ObjectController) -> Self {
|
||||||
|
assert_eq!(max % 8, 0);
|
||||||
|
|
||||||
|
let sprites = (0..(max / 8))
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| {
|
||||||
|
let health_sprite = obj.sprite(SMALL_SPRITES.red_bar(0));
|
||||||
|
|
||||||
|
let mut health_object = obj.object(health_sprite);
|
||||||
|
health_object
|
||||||
|
.set_position(pos + (i as i32 * 8, 0).into())
|
||||||
|
.show();
|
||||||
|
health_object
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self { max, sprites }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_value(&mut self, new_value: usize, obj: &'a ObjectController) {
|
||||||
|
assert!(new_value <= self.max);
|
||||||
|
|
||||||
|
for (i, sprite) in self.sprites.iter_mut().enumerate() {
|
||||||
|
if (i + 1) * 8 < new_value {
|
||||||
|
sprite.set_sprite(obj.sprite(SMALL_SPRITES.red_bar(0)));
|
||||||
|
} else if i * 8 < new_value {
|
||||||
|
sprite.set_sprite(obj.sprite(SMALL_SPRITES.red_bar(8 - (new_value - i * 8))));
|
||||||
|
} else {
|
||||||
|
sprite.set_sprite(obj.sprite(SMALL_SPRITES.red_bar(8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(&mut self) {
|
||||||
|
for obj in self.sprites.iter_mut() {
|
||||||
|
obj.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hide(&mut self) {
|
||||||
|
for obj in self.sprites.iter_mut() {
|
||||||
|
obj.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FractionDisplay<'a> {
|
||||||
|
sprites: Vec<Object<'a>>,
|
||||||
|
digits: usize,
|
||||||
|
|
||||||
|
current_current: usize,
|
||||||
|
current_max: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FractionDisplay<'a> {
|
||||||
|
pub fn new(pos: Vector2D<i32>, digits: usize, obj: &'a ObjectController) -> Self {
|
||||||
|
let mut sprites = Vec::with_capacity(digits * 2 + 1);
|
||||||
|
|
||||||
|
for i in 0..digits {
|
||||||
|
let mut left_digit = obj.object(obj.sprite(SMALL_SPRITES.number(0)));
|
||||||
|
left_digit.set_position(pos + (i as i32 * 4, 0).into());
|
||||||
|
|
||||||
|
sprites.push(left_digit);
|
||||||
|
|
||||||
|
let mut right_digit = obj.object(obj.sprite(SMALL_SPRITES.number(0)));
|
||||||
|
right_digit.set_position(pos + (i as i32 * 4 + digits as i32 * 4 + 7, 0).into());
|
||||||
|
|
||||||
|
sprites.push(right_digit);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut slash = obj.object(obj.sprite(SMALL_SPRITES.slash()));
|
||||||
|
slash.set_position(pos + (digits as i32 * 4 + 1, 0).into());
|
||||||
|
sprites.push(slash);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
sprites,
|
||||||
|
digits,
|
||||||
|
current_current: 0,
|
||||||
|
current_max: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_value(&mut self, current: usize, max: usize, obj: &'a ObjectController) {
|
||||||
|
if self.current_current == current && self.current_max == max {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current = current;
|
||||||
|
let mut max = max;
|
||||||
|
|
||||||
|
for i in 0..self.digits {
|
||||||
|
let current_value_digit = current % 10;
|
||||||
|
current /= 10;
|
||||||
|
let current_value_sprite = &mut self.sprites[(self.digits - i) * 2 - 2];
|
||||||
|
current_value_sprite
|
||||||
|
.set_sprite(obj.sprite(SMALL_SPRITES.number(current_value_digit as u32)));
|
||||||
|
|
||||||
|
let max_value_digit = max % 10;
|
||||||
|
max /= 10;
|
||||||
|
let max_value_sprite = &mut self.sprites[(self.digits - i) * 2 - 1];
|
||||||
|
max_value_sprite.set_sprite(obj.sprite(SMALL_SPRITES.number(max_value_digit as u32)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NumberDisplay<'a> {
|
||||||
|
objects: Vec<Object<'a>>,
|
||||||
|
value: Option<u32>,
|
||||||
|
position: Vector2D<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NumberDisplay<'a> {
|
||||||
|
pub fn new(position: Vector2D<i32>) -> Self {
|
||||||
|
Self {
|
||||||
|
objects: Vec::new(),
|
||||||
|
value: None,
|
||||||
|
position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_value(&mut self, new_value: Option<u32>, obj: &'a ObjectController) {
|
||||||
|
if self.value == new_value {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.value = new_value;
|
||||||
|
|
||||||
|
self.objects.clear();
|
||||||
|
|
||||||
|
if let Some(mut new_value) = new_value {
|
||||||
|
if new_value == 0 {
|
||||||
|
let mut zero_object = obj.object(obj.sprite(SMALL_SPRITES.number(0)));
|
||||||
|
zero_object.show().set_position(self.position);
|
||||||
|
|
||||||
|
self.objects.push(zero_object);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut digit = 0;
|
||||||
|
while new_value != 0 {
|
||||||
|
let current_value_digit = new_value % 10;
|
||||||
|
new_value /= 10;
|
||||||
|
|
||||||
|
let mut current_value_obj =
|
||||||
|
obj.object(obj.sprite(SMALL_SPRITES.number(current_value_digit)));
|
||||||
|
|
||||||
|
current_value_obj
|
||||||
|
.show()
|
||||||
|
.set_position(self.position - (digit * 4, 0).into());
|
||||||
|
|
||||||
|
digit += 1;
|
||||||
|
|
||||||
|
self.objects.push(current_value_obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
examples/hyperspace-roll/src/level_generation.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use agb::{hash_map::HashMap, rng};
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::{battle::EnemyAttack, Face};
|
||||||
|
|
||||||
|
pub struct GeneratedAttack {
|
||||||
|
pub attack: EnemyAttack,
|
||||||
|
pub cooldown: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_attack(current_level: u32) -> Option<GeneratedAttack> {
|
||||||
|
if (rng::gen().rem_euclid(1024) as u32) < current_level * 2 {
|
||||||
|
Some(GeneratedAttack {
|
||||||
|
attack: generate_enemy_attack(current_level),
|
||||||
|
cooldown: generate_cooldown(current_level),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_enemy_attack(current_level: u32) -> EnemyAttack {
|
||||||
|
let attack_id = rng::gen().rem_euclid(10) as u32;
|
||||||
|
|
||||||
|
if attack_id < 7 {
|
||||||
|
EnemyAttack::Shoot(rng::gen().rem_euclid(((current_level + 2) / 3) as i32) as u32 + 1)
|
||||||
|
} else if attack_id < 9 {
|
||||||
|
EnemyAttack::Shield(
|
||||||
|
(rng::gen().rem_euclid(((current_level + 4) / 5) as i32) as u32 + 1).min(5),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
EnemyAttack::Heal(rng::gen().rem_euclid(((current_level + 1) / 2) as i32) as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_cooldown(current_level: u32) -> u32 {
|
||||||
|
rng::gen().rem_euclid((5 * 60 - current_level as i32 * 10).max(1)) as u32 + 2 * 60
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_upgrades(level: u32) -> Vec<Face> {
|
||||||
|
let mut upgrade_values = HashMap::new();
|
||||||
|
|
||||||
|
upgrade_values.insert(Face::Shoot, 5);
|
||||||
|
upgrade_values.insert(Face::DoubleShot, 10);
|
||||||
|
upgrade_values.insert(Face::DoubleShotValue, 15);
|
||||||
|
upgrade_values.insert(Face::TripleShot, 20);
|
||||||
|
upgrade_values.insert(Face::TripleShotValue, 30);
|
||||||
|
upgrade_values.insert(Face::Shield, 5);
|
||||||
|
upgrade_values.insert(Face::DoubleShield, 10);
|
||||||
|
upgrade_values.insert(Face::TripleShield, 20);
|
||||||
|
upgrade_values.insert(Face::DoubleShieldValue, 25);
|
||||||
|
upgrade_values.insert(Face::Malfunction, -2);
|
||||||
|
upgrade_values.insert(Face::Bypass, 7);
|
||||||
|
upgrade_values.insert(Face::Disrupt, 10);
|
||||||
|
upgrade_values.insert(Face::MalfunctionShot, 15);
|
||||||
|
upgrade_values.insert(Face::Heal, 8);
|
||||||
|
upgrade_values.insert(Face::BurstShield, 30);
|
||||||
|
upgrade_values.insert(Face::Invert, 30);
|
||||||
|
|
||||||
|
let potential_upgrades: Vec<Face> = upgrade_values.keys().cloned().collect();
|
||||||
|
|
||||||
|
let mut upgrades = Vec::new();
|
||||||
|
|
||||||
|
let upgrade_value = |upgrades: &[Face], potential_upgrade: Face| -> i32 {
|
||||||
|
upgrades
|
||||||
|
.iter()
|
||||||
|
.map(|x| upgrade_values.get(x).unwrap())
|
||||||
|
.sum::<i32>()
|
||||||
|
+ upgrade_values.get(&potential_upgrade).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let max_upgrade_value = 15 + (rng::gen().rem_euclid(level as i32 * 5));
|
||||||
|
let mut attempts = 0;
|
||||||
|
|
||||||
|
while upgrades.len() != 3 {
|
||||||
|
attempts += 1;
|
||||||
|
let next = potential_upgrades[rng::gen() as usize % potential_upgrades.len()];
|
||||||
|
let number_of_malfunctions = upgrades
|
||||||
|
.iter()
|
||||||
|
.chain(core::iter::once(&next))
|
||||||
|
.filter(|&x| *x == Face::Malfunction)
|
||||||
|
.count();
|
||||||
|
let maximum_number_of_malfunctions = if level < 5 { 0 } else { 1 };
|
||||||
|
if upgrade_value(&upgrades, next) <= max_upgrade_value
|
||||||
|
&& number_of_malfunctions <= maximum_number_of_malfunctions
|
||||||
|
{
|
||||||
|
upgrades.push(next);
|
||||||
|
attempts = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if attempts > 100 {
|
||||||
|
upgrades.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upgrades
|
||||||
|
}
|
228
examples/hyperspace-roll/src/main.rs
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
// 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]
|
||||||
|
|
||||||
|
use agb::display;
|
||||||
|
use agb::display::object::ObjectController;
|
||||||
|
use agb::display::tiled::VRamManager;
|
||||||
|
use agb::display::Priority;
|
||||||
|
use agb::interrupt::VBlank;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
if save::load_high_score() > 1000 {
|
||||||
|
save::save_high_score(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
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(current_level);
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
44
examples/hyperspace-roll/src/save.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use agb::interrupt::free;
|
||||||
|
use bare_metal::Mutex;
|
||||||
|
use core::cell::RefCell;
|
||||||
|
|
||||||
|
const RAM_ADDRESS: *mut u8 = 0x0E00_0000 as *mut u8;
|
||||||
|
const HIGH_SCORE_ADDRESS_START: *mut u8 = RAM_ADDRESS.wrapping_offset(1);
|
||||||
|
|
||||||
|
static HIGHSCORE: Mutex<RefCell<u32>> = Mutex::new(RefCell::new(0));
|
||||||
|
|
||||||
|
pub fn init_save() {
|
||||||
|
if (unsafe { RAM_ADDRESS.read_volatile() } == !0) {
|
||||||
|
save_high_score(0);
|
||||||
|
unsafe { RAM_ADDRESS.write_volatile(0) };
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut a = [0; 4];
|
||||||
|
for (idx, a) in a.iter_mut().enumerate() {
|
||||||
|
*a = unsafe { HIGH_SCORE_ADDRESS_START.add(idx).read_volatile() };
|
||||||
|
}
|
||||||
|
|
||||||
|
let high_score = u32::from_le_bytes(a);
|
||||||
|
|
||||||
|
free(|cs| {
|
||||||
|
if high_score > 100 {
|
||||||
|
HIGHSCORE.borrow(cs).replace(0);
|
||||||
|
} else {
|
||||||
|
HIGHSCORE.borrow(cs).replace(high_score);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_high_score() -> u32 {
|
||||||
|
free(|cs| *HIGHSCORE.borrow(cs).borrow())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_high_score(score: u32) {
|
||||||
|
let a = score.to_le_bytes();
|
||||||
|
|
||||||
|
for (idx, &a) in a.iter().enumerate() {
|
||||||
|
unsafe { HIGH_SCORE_ADDRESS_START.add(idx).write_volatile(a) };
|
||||||
|
}
|
||||||
|
|
||||||
|
free(|cs| HIGHSCORE.borrow(cs).replace(score));
|
||||||
|
}
|
203
examples/hyperspace-roll/src/sfx.rs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
use agb::fixnum::num;
|
||||||
|
use agb::sound::mixer::{ChannelId, Mixer, SoundChannel};
|
||||||
|
use agb::{include_wav, rng};
|
||||||
|
|
||||||
|
const DICE_ROLLS: &[&[u8]] = &[
|
||||||
|
include_wav!("sfx/SingleRoll_1.wav"),
|
||||||
|
include_wav!("sfx/SingleRoll_2.wav"),
|
||||||
|
include_wav!("sfx/SingleRoll_3.wav"),
|
||||||
|
include_wav!("sfx/SingleRoll_4.wav"),
|
||||||
|
include_wav!("sfx/SingleRoll_5.wav"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const MULTI_ROLLS: &[&[u8]] = &[
|
||||||
|
include_wav!("sfx/MultiRoll_1.wav"),
|
||||||
|
include_wav!("sfx/MultiRoll_2.wav"),
|
||||||
|
include_wav!("sfx/MultiRoll_3.wav"),
|
||||||
|
include_wav!("sfx/MultiRoll_4.wav"),
|
||||||
|
include_wav!("sfx/MultiRoll_5.wav"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const MENU_BGM: &[u8] = include_wav!("sfx/BGM_Menu.wav");
|
||||||
|
const BATTLE_BGM: &[u8] = include_wav!("sfx/BGM_Fight.wav");
|
||||||
|
const TITLE_BGM: &[u8] = include_wav!("sfx/BGM_Title.wav");
|
||||||
|
|
||||||
|
const SHOOT: &[u8] = include_wav!("sfx/shoot.wav");
|
||||||
|
const SHOT_HIT: &[u8] = include_wav!("sfx/shot_hit.wav");
|
||||||
|
const SHIP_EXPLODE: &[u8] = include_wav!("sfx/ship_explode.wav");
|
||||||
|
const MOVE_CURSOR: &[u8] = include_wav!("sfx/move_cursor.wav");
|
||||||
|
const SELECT: &[u8] = include_wav!("sfx/select.wav");
|
||||||
|
const BACK: &[u8] = include_wav!("sfx/back.wav");
|
||||||
|
const ACCEPT: &[u8] = include_wav!("sfx/accept.wav");
|
||||||
|
const SHIELD_DOWN: &[u8] = include_wav!("sfx/shield_down.wav");
|
||||||
|
const SHIELD_UP: &[u8] = include_wav!("sfx/shield_up.wav");
|
||||||
|
const SHIELD_DEFEND: &[u8] = include_wav!("sfx/shield_defend.wav");
|
||||||
|
const DISRUPT: &[u8] = include_wav!("sfx/disrupt.wav");
|
||||||
|
const HEAL: &[u8] = include_wav!("sfx/heal.wav");
|
||||||
|
const SEND_BURST_SHIELD: &[u8] = include_wav!("sfx/send_burst_shield.wav");
|
||||||
|
const BURST_SHIELD_HIT: &[u8] = include_wav!("sfx/burst_shield_hit.wav");
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum BattleOrMenu {
|
||||||
|
Battle,
|
||||||
|
Menu,
|
||||||
|
Title,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sfx<'a> {
|
||||||
|
mixer: &'a mut Mixer,
|
||||||
|
state: BattleOrMenu,
|
||||||
|
|
||||||
|
current_bgm: ChannelId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Sfx<'a> {
|
||||||
|
pub fn new(mixer: &'a mut Mixer) -> Self {
|
||||||
|
let mut title_music = SoundChannel::new_high_priority(TITLE_BGM);
|
||||||
|
title_music.should_loop();
|
||||||
|
let title_channel = mixer.play_sound(title_music).unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
mixer,
|
||||||
|
state: BattleOrMenu::Title,
|
||||||
|
|
||||||
|
current_bgm: title_channel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn frame(&mut self) {
|
||||||
|
self.mixer.frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn battle(&mut self) {
|
||||||
|
if self.state == BattleOrMenu::Battle {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = BattleOrMenu::Battle;
|
||||||
|
|
||||||
|
let current_channel = self.mixer.channel(&self.current_bgm).unwrap();
|
||||||
|
let pos = current_channel.pos();
|
||||||
|
current_channel.stop();
|
||||||
|
|
||||||
|
let mut battle_music = SoundChannel::new_high_priority(BATTLE_BGM);
|
||||||
|
battle_music.should_loop().set_pos(pos);
|
||||||
|
self.current_bgm = self.mixer.play_sound(battle_music).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn customise(&mut self) {
|
||||||
|
if self.state == BattleOrMenu::Menu {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let should_restart = self.state == BattleOrMenu::Title;
|
||||||
|
|
||||||
|
self.state = BattleOrMenu::Menu;
|
||||||
|
let current_channel = self.mixer.channel(&self.current_bgm).unwrap();
|
||||||
|
let pos = current_channel.pos();
|
||||||
|
current_channel.stop();
|
||||||
|
|
||||||
|
let mut menu_music = SoundChannel::new_high_priority(MENU_BGM);
|
||||||
|
menu_music
|
||||||
|
.should_loop()
|
||||||
|
.set_pos(if should_restart { 0.into() } else { pos });
|
||||||
|
self.current_bgm = self.mixer.play_sound(menu_music).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title_screen(&mut self) {
|
||||||
|
if self.state == BattleOrMenu::Title {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = BattleOrMenu::Title;
|
||||||
|
self.mixer.channel(&self.current_bgm).unwrap().stop();
|
||||||
|
|
||||||
|
let mut title_music = SoundChannel::new_high_priority(TITLE_BGM);
|
||||||
|
title_music.should_loop();
|
||||||
|
self.current_bgm = self.mixer.play_sound(title_music).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn roll(&mut self) {
|
||||||
|
let roll_sound_to_use = rng::gen().rem_euclid(DICE_ROLLS.len() as i32);
|
||||||
|
let sound_channel = SoundChannel::new(DICE_ROLLS[roll_sound_to_use as usize]);
|
||||||
|
|
||||||
|
self.mixer.play_sound(sound_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn roll_multi(&mut self) {
|
||||||
|
let roll_sound_to_use = rng::gen().rem_euclid(MULTI_ROLLS.len() as i32);
|
||||||
|
let sound_channel = SoundChannel::new(MULTI_ROLLS[roll_sound_to_use as usize]);
|
||||||
|
|
||||||
|
self.mixer.play_sound(sound_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shoot(&mut self) {
|
||||||
|
self.mixer.play_sound(SoundChannel::new(SHOOT));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shot_hit(&mut self) {
|
||||||
|
self.mixer.play_sound(SoundChannel::new(SHOT_HIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ship_explode(&mut self) {
|
||||||
|
self.mixer.play_sound(SoundChannel::new(SHIP_EXPLODE));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_cursor(&mut self) {
|
||||||
|
let mut channel = SoundChannel::new(MOVE_CURSOR);
|
||||||
|
channel.volume(num!(0.5));
|
||||||
|
|
||||||
|
self.mixer.play_sound(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select(&mut self) {
|
||||||
|
let mut channel = SoundChannel::new(SELECT);
|
||||||
|
channel.volume(num!(0.75));
|
||||||
|
|
||||||
|
self.mixer.play_sound(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn back(&mut self) {
|
||||||
|
let mut channel = SoundChannel::new(BACK);
|
||||||
|
channel.volume(num!(0.5));
|
||||||
|
|
||||||
|
self.mixer.play_sound(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accept(&mut self) {
|
||||||
|
let mut channel = SoundChannel::new(ACCEPT);
|
||||||
|
channel.volume(num!(0.5));
|
||||||
|
|
||||||
|
self.mixer.play_sound(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shield_down(&mut self) {
|
||||||
|
self.mixer.play_sound(SoundChannel::new(SHIELD_DOWN));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shield_up(&mut self) {
|
||||||
|
self.mixer.play_sound(SoundChannel::new(SHIELD_UP));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shield_defend(&mut self) {
|
||||||
|
let mut channel = SoundChannel::new(SHIELD_DEFEND);
|
||||||
|
channel.volume(num!(0.5));
|
||||||
|
self.mixer.play_sound(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disrupt(&mut self) {
|
||||||
|
self.mixer.play_sound(SoundChannel::new(DISRUPT));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn heal(&mut self) {
|
||||||
|
self.mixer.play_sound(SoundChannel::new(HEAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_burst_shield(&mut self) {
|
||||||
|
self.mixer.play_sound(SoundChannel::new(SEND_BURST_SHIELD));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn burst_shield_hit(&mut self) {
|
||||||
|
self.mixer.play_sound(SoundChannel::new(BURST_SHIELD_HIT));
|
||||||
|
}
|
||||||
|
}
|