diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb3cb3c368..f2770e1474 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: - { os: macos-latest, target: "aarch64-apple-darwin" } - { os: windows-latest, target: "x86_64-pc-windows-msvc" } - { os: windows-latest, target: "i686-pc-windows-msvc" } - profile: ["", --release] + profile: ["--release"] steps: - name: Checkout diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10be084adc..e20ac8b42e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,7 +78,8 @@ jobs: cp target/${{ matrix.config.target }}/release/fishfight.exe $release_dir/ strip $release_dir/fishfight.exe find assets -name "*.schema.json" -exec rm -f {} \; - cp -R assets/ mods/ $release_dir/ + cp -R assets/ mods/ licenses/ $release_dir/ + cp LICENSE $release_dir/licenses/ 7z a -tzip $artifact_path $release_dir/ - name: Prepare artifacts [Unix] @@ -92,7 +93,8 @@ jobs: cp target/${{ matrix.config.target }}/release/fishfight $release_dir/ strip $release_dir/fishfight || true find assets -name "*.schema.json" -exec rm -f {} \; - cp -R assets mods $release_dir + cp -R assets mods licenses $release_dir + cp LICENSE $release_dir/licenses tar -czvf $artifact_path $release_dir/ - name: Deploy | Upload artifacts diff --git a/Cargo.lock b/Cargo.lock index 53db8a3fe2..9eb9391ac9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # 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" @@ -25,6 +31,24 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "async-trait" version = "0.1.52" @@ -66,12 +90,27 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + [[package]] name = "bumpalo" version = "3.9.1" @@ -90,6 +129,27 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bzip2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cc" version = "1.0.73" @@ -102,12 +162,51 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "const_fn" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" + +[[package]] +name = "cookie" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" +dependencies = [ + "percent-encoding", + "time 0.2.27", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3" +dependencies = [ + "cookie", + "idna", + "log", + "publicsuffix", + "serde", + "serde_json", + "time 0.2.27", + "url", +] + [[package]] name = "core-foundation" version = "0.6.4" @@ -164,6 +263,36 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "erased-serde" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56047058e1ab118075ca22f9ecd737bcc961aa3566a3019cb71388afa280bd8a" +dependencies = [ + "serde", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "ff-particles" version = "0.1.2" @@ -176,30 +305,38 @@ dependencies = [ [[package]] name = "fishfight" -version = "0.4.1" +version = "0.4.2" dependencies = [ "ff-particles", "fishfight-core", "fishsticks", "hecs", + "hv-alchemy", + "hv-cell", + "hv-lua", "macroquad", "macroquad-platformer", "serde", "serde_json", + "tealr", "wasm-bindgen", ] [[package]] name = "fishfight-core" -version = "0.4.1" +version = "0.4.2" dependencies = [ "async-trait", "fishsticks", "getrandom", "hecs", + "hv-alchemy", + "hv-cell", + "hv-lua", "macroquad", "serde", "serde_json", + "tealr", "toml", "wasm-bindgen", ] @@ -213,6 +350,18 @@ dependencies = [ "gilrs", ] +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide 0.4.4", +] + [[package]] name = "fnv" version = "1.0.7" @@ -229,6 +378,16 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "getrandom" version = "0.2.4" @@ -293,21 +452,20 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ "ahash 0.7.6", ] [[package]] name = "hecs" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4e8eb4d5dd2ea100ffbb4eb6814f4294a7362fcafbc5c1c4c015b41d16f424" +version = "0.7.1" +source = "git+https://github.com/sdleffler/hv-dev#1ee2ef316a34e5561ace279ec9ca4b5c00621053" dependencies = [ - "hashbrown 0.12.0", - "spin", + "hashbrown 0.11.2", + "spin 0.9.2", ] [[package]] @@ -316,6 +474,126 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" +[[package]] +name = "hv-alchemy" +version = "0.1.0" +source = "git+https://github.com/sdleffler/hv-dev#1ee2ef316a34e5561ace279ec9ca4b5c00621053" +dependencies = [ + "hashbrown 0.11.2", + "hv-atom", + "hv-cell", + "lazy_static", + "spin 0.9.2", + "static_assertions", +] + +[[package]] +name = "hv-atom" +version = "0.1.0" +source = "git+https://github.com/sdleffler/hv-dev#1ee2ef316a34e5561ace279ec9ca4b5c00621053" + +[[package]] +name = "hv-cell" +version = "0.1.0" +source = "git+https://github.com/sdleffler/hv-dev#1ee2ef316a34e5561ace279ec9ca4b5c00621053" +dependencies = [ + "hv-guarded-borrow", +] + +[[package]] +name = "hv-elastic" +version = "0.4.1" +source = "git+https://github.com/sdleffler/hv-dev#1ee2ef316a34e5561ace279ec9ca4b5c00621053" +dependencies = [ + "hecs", + "hv-cell", + "hv-guarded-borrow", + "hv-stampede", + "static_assertions", + "thiserror", +] + +[[package]] +name = "hv-guarded-borrow" +version = "0.1.1" +source = "git+https://github.com/sdleffler/hv-dev#1ee2ef316a34e5561ace279ec9ca4b5c00621053" +dependencies = [ + "hecs", +] + +[[package]] +name = "hv-lua" +version = "0.6.6" +source = "git+https://github.com/sdleffler/hv-dev#1ee2ef316a34e5561ace279ec9ca4b5c00621053" +dependencies = [ + "bstr", + "cc", + "erased-serde", + "hecs", + "hv-alchemy", + "hv-cell", + "hv-elastic", + "hv-guarded-borrow", + "hv-lua-derive", + "hv-math", + "lua-src", + "luajit-src", + "nalgebra", + "num-traits", + "once_cell", + "parry3d", + "pkg-config", + "rustc-hash", + "serde", + "static_assertions", +] + +[[package]] +name = "hv-lua-derive" +version = "0.6.0" +source = "git+https://github.com/sdleffler/hv-dev#1ee2ef316a34e5561ace279ec9ca4b5c00621053" +dependencies = [ + "itertools", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "hv-math" +version = "0.1.0" +source = "git+https://github.com/sdleffler/hv-dev#1ee2ef316a34e5561ace279ec9ca4b5c00621053" +dependencies = [ + "approx", + "hv-alchemy", + "nalgebra", + "nalgebra-glm", + "serde", +] + +[[package]] +name = "hv-stampede" +version = "0.2.1" +source = "git+https://github.com/sdleffler/hv-dev#1ee2ef316a34e5561ace279ec9ca4b5c00621053" +dependencies = [ + "bumpalo", + "spin 0.9.2", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "image" version = "0.23.14" @@ -326,11 +604,20 @@ dependencies = [ "byteorder", "color_quant", "num-iter", - "num-rational", + "num-rational 0.3.2", "num-traits", "png", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-kit-sys" version = "0.1.0" @@ -341,6 +628,15 @@ dependencies = [ "mach", ] +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.1" @@ -361,6 +657,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "lewton" @@ -370,7 +669,7 @@ checksum = "8d542c1a317036c45c2aa1cf10cc9d403ca91eb2d333ef1a4917e5cb10628bd0" dependencies = [ "byteorder", "ogg", - "smallvec", + "smallvec 0.6.14", ] [[package]] @@ -379,6 +678,12 @@ version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" +[[package]] +name = "libm" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" + [[package]] name = "libudev-sys" version = "0.1.4" @@ -389,6 +694,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "lock_api" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.14" @@ -398,6 +712,24 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "lua-src" +version = "544.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7341ba039a781c4982ca20761c55f44e07bfefd496a45b1e929763d88f5fc68b" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.3.2+resty1085a4d" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27456f513225a9edd22fc0a5f526323f6adb3099c4de87a84ceb842d93ba4" +dependencies = [ + "cc", +] + [[package]] name = "mach" version = "0.2.3" @@ -438,12 +770,33 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5cecfede1e530599c8686f7f2d609489101d3d63741a6dc423afc997ce3fcc8" +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "matrixmultiply" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +dependencies = [ + "rawpointer", +] + [[package]] name = "maybe-uninit" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + [[package]] name = "memoffset" version = "0.6.5" @@ -477,6 +830,58 @@ dependencies = [ "adler32", ] +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "nalgebra" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d506eb7e08d6329505faa8a3a00a5dcc6de9f76e0c77e4b75763ae3c770831ff" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational 0.4.0", + "num-traits", + "rand", + "rand_distr", + "serde", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-glm" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cec137f377549720ff9fbbcd1413a12bbb03b4feea3192cee987e8493158748" +dependencies = [ + "approx", + "nalgebra", + "num-traits", + "simba", +] + +[[package]] +name = "nalgebra-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ndk-sys" version = "0.2.2" @@ -496,6 +901,27 @@ dependencies = [ "memoffset", ] +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -529,16 +955,28 @@ dependencies = [ ] [[package]] -name = "num-traits" -version = "0.2.14" +name = "num-rational" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ "autocfg", + "num-integer", + "num-traits", ] [[package]] -name = "ogg" +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ogg" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e571c3517af9e1729d4c63571a27edd660ade0667973bfc74a67c660c2b651" @@ -552,6 +990,38 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +[[package]] +name = "parry3d" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "067fa44bf7f7cc4022756ef9796c5f8d67c7a63041df71e6eeb09ad08ab1ece8" +dependencies = [ + "approx", + "bitflags", + "downcast-rs", + "either", + "nalgebra", + "num-derive", + "num-traits", + "rustc-hash", + "serde", + "simba", + "slab", + "smallvec 1.8.0", +] + +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + [[package]] name = "pkg-config" version = "0.3.24" @@ -567,9 +1037,45 @@ dependencies = [ "bitflags", "crc32fast", "deflate", - "miniz_oxide", + "miniz_oxide 0.3.7", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", ] +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + [[package]] name = "proc-macro2" version = "1.0.36" @@ -579,6 +1085,25 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "publicsuffix" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b4ce31ff0a27d93c8de1849cf58162283752f065a90d508f1105fa6c9a213f" +dependencies = [ + "idna", + "url", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + [[package]] name = "quad-alsa-sys" version = "0.3.2" @@ -616,6 +1141,108 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -625,6 +1252,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "rusty-xinput" version = "1.2.0" @@ -642,6 +1282,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +[[package]] +name = "safe_arch" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529" +dependencies = [ + "bytemuck", +] + [[package]] name = "sapp-android" version = "0.1.14" @@ -703,6 +1352,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "semver" version = "0.9.0" @@ -764,6 +1429,25 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +[[package]] +name = "simba" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b7840f121a46d63066ee7a99fc81dcabbc6105e437cae43528cea199b5a05f" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + [[package]] name = "smallvec" version = "0.6.14" @@ -773,11 +1457,41 @@ dependencies = [ "maybe-uninit", ] +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +dependencies = [ + "lock_api", +] + +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stdweb" @@ -841,6 +1555,133 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tealr" +version = "0.8.2" +source = "git+https://github.com/lenscas/tealr?branch=temporary/feature/add_support_for_hv_lua#b3a40117c84e3dda58fb5c4eda95f88651953887" +dependencies = [ + "bstr", + "hecs", + "hv-alchemy", + "hv-cell", + "hv-elastic", + "hv-guarded-borrow", + "hv-lua", + "itertools", + "serde", + "tealr_derive", +] + +[[package]] +name = "tealr_derive" +version = "0.8.2" +source = "git+https://github.com/lenscas/tealr?branch=temporary/feature/add_support_for_hv_lua#b3a40117c84e3dda58fb5c4eda95f88651953887" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "tempfile", + "ureq", + "zip", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "time" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "toml" version = "0.5.8" @@ -856,12 +1697,70 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8b063c2d59218ae09f22b53c42eaad0d53516457905f5235ca4bc9e99daa71" +dependencies = [ + "base64", + "chunked_transfer", + "cookie", + "cookie_store", + "log", + "once_cell", + "qstring", + "rustls", + "url", + "webpki", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + [[package]] name = "uuid" version = "0.8.2" @@ -950,6 +1849,35 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + +[[package]] +name = "wide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3aba2d1dac31ac7cae82847ac5b8be822aee8f99a4e100f279605016b185c5f" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.3.9" @@ -971,3 +1899,17 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zip" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +dependencies = [ + "byteorder", + "bzip2", + "crc32fast", + "flate2", + "thiserror", + "time 0.1.43", +] diff --git a/Cargo.toml b/Cargo.toml index 0b26f952e5..6155c6a0cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "fishfight" -version = "0.4.1" -description = "A tactical 2D shooter" authors = ["Fish Fight Contributors"] -license = "MIT OR Apache-2.0" +description = "A tactical 2D shooter" edition = "2021" +license = "MIT OR Apache-2.0" +name = "fishfight" +version = "0.4.2" [target.'cfg(target_arch = "wasm32")'.lib] crate-type = ["cdylib"] @@ -19,16 +19,19 @@ members = ["core"] opt-level = 3 [dependencies] -core = { path = "./core", package = "fishfight-core" } +core = {path = "./core", package = "fishfight-core"} # ultimate = { path = "../FishFight-ultimate", package = "fishfight-ultimate", optional = true } -ff-particles = { version = "0.1", features = ["serde"] } -fishsticks = { git = "https://github.com/fishfight/fishsticks", default-features = false, features = ["gilrs"] } -macroquad = { version = "0.3.10" } +ff-particles = {version = "0.1", features = ["serde"]} +fishsticks = {git = "https://github.com/fishfight/fishsticks", default-features = false, features = ["gilrs"]} +hecs = {git = "https://github.com/sdleffler/hv-dev"} +hv-alchemy = {git = "https://github.com/sdleffler/hv-dev"} +hv-cell = {git = "https://github.com/sdleffler/hv-dev"} +hv-lua = {git = "https://github.com/sdleffler/hv-dev"}#imported so its macro's work +macroquad = {version = "0.3.10"} macroquad-platformer = "0.1" -hecs = "0.7.1" -serde = { version = "1.0", features = ["derive"] } +serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" - +tealr = {git = "https://github.com/lenscas/tealr", branch = "temporary/feature/add_support_for_hv_lua", features = ["mlua", "hv_lua", "mlua_vendored", "mlua_luajit", "mlua_serialize", "mlua_send", "derive"]} +#tealr = {path = "../tealr/tealr", features = ["mlua", "hv_lua", "mlua_vendored", "mlua_luajit", "mlua_serialize", "mlua_send", "derive"]} [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2.74" - diff --git a/LICENSE b/LICENSE index 071b523f47..867afb0be2 100644 --- a/LICENSE +++ b/LICENSE @@ -5,4 +5,4 @@ Fish Fight source code is dual-licensed under either at your option. -Fish Fight media assets are Copyright (c) 2020-2021 The Fish Fight Game Developers and licensed as [CC BY-NC](https://creativecommons.org/licenses/by-nc/4.0/). +Fish Fight media assets are Copyright (c) 2020-2022 The Fish Fight Game Developers and licensed as [CC BY-NC](https://creativecommons.org/licenses/by-nc/4.0/). diff --git a/README.md b/README.md index 31c02809b1..b37d3c60b7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Fish Fight -[![Build Status](https://img.shields.io/github/workflow/status/fishfight/FishFight/Compilation%20check?logo=github&labelColor=1e1c24&color=8bcfcf)](https://github.com/fishfight/FishFight/actions) [![Documentation](https://img.shields.io/badge/documentation-fishfight.github.io-green.svg?labelColor=1e1c24&color=f3ee7a)](https://fishfight.github.io/FishFight/) [![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg?label=license&labelColor=1e1c24&color=34925e)](./LICENSE) [![Discord](https://img.shields.io/badge/chat-on%20discord-green.svg?logo=discord&logoColor=fff&labelColor=1e1c24&color=8d5b3f)](https://discord.gg/4smxjcheE5) +[![Build Status](https://img.shields.io/github/workflow/status/fishfight/FishFight/Continuous%20integration?logo=github&labelColor=1e1c24&color=8bcfcf)](https://github.com/fishfight/FishFight/actions) [![Documentation](https://img.shields.io/badge/documentation-fishfight.github.io-green.svg?labelColor=1e1c24&color=f3ee7a)](https://fishfight.github.io/FishFight/) [![License](https://img.shields.io/badge/License-MIT%20or%20Apache%202-green.svg?label=license&labelColor=1e1c24&color=34925e)](./LICENSE) [![Discord](https://img.shields.io/badge/chat-on%20discord-green.svg?logo=discord&logoColor=fff&labelColor=1e1c24&color=8d5b3f)](https://discord.gg/4smxjcheE5) ![Fish Fight Preview](https://user-images.githubusercontent.com/24392180/151969075-399e9fea-e2de-4340-96a4-0a0e5b79c281.gif) diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000000..13ad090a9d --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,10 @@ +# Creating a Release + +[GitHub releases](https://github.com/fishfight/FishFight/releases) are automated via [GitHub actions](./.github/workflows/release.yml) and triggered by pushing a tag. + +1. Bump the version in [Cargo.toml](Cargo.toml) and [core/Cargo.toml](core/Cargo.toml). +2. Update [Cargo.lock](Cargo.lock) by building the project. (`cargo build`) +3. Commit and push the changes. (i.e. submit a pull request) +4. Either [draft a new release](https://github.com/fishfight/FishFight/releases/new) from the GitHub interface or create a new tag and push it via the command line. +5. While naming the release, do not include the version in the title. (e.g. "Level Editor") +6. Add release notes and highlights. diff --git a/assets/player_characters.json b/assets/player_characters.json index 84f796d3e7..4dfb0412b5 100644 --- a/assets/player_characters.json +++ b/assets/player_characters.json @@ -34,5 +34,23 @@ "x": 0.0, "y": 16.0 } + }, + { + "id": "lionfishy", + "name": "Lionfishy", + "texture": "player_lionfishy", + "offset": { + "x": 0.0, + "y": 16.0 + } + }, + { + "id": "catty", + "name": "Catty", + "texture": "player_catty", + "offset": { + "x": 0.0, + "y": 16.0 + } } -] \ No newline at end of file +] diff --git a/assets/textures.json b/assets/textures.json index c75c9439f6..b5252a1a36 100644 --- a/assets/textures.json +++ b/assets/textures.json @@ -193,6 +193,24 @@ "y": 80 } }, + { + "id": "player_lionfishy", + "path": "textures/player/PlayerLionfishy(96x80).png", + "type": "spritesheet", + "sprite_size": { + "x": 96, + "y": 80 + } + }, + { + "id": "player_catty", + "path": "textures/player/PlayerCatty(96x80).png", + "type": "spritesheet", + "sprite_size": { + "x": 96, + "y": 80 + } + }, { "id": "pirate_hat", "path": "textures/items/hats/pirate hat.png", @@ -487,4 +505,4 @@ "y": 144 } } -] \ No newline at end of file +] diff --git a/assets/textures/player/PlayerCatty(96x80).png b/assets/textures/player/PlayerCatty(96x80).png new file mode 100644 index 0000000000..9e018a741d Binary files /dev/null and b/assets/textures/player/PlayerCatty(96x80).png differ diff --git a/assets/textures/player/PlayerLionfishy(96x80).png b/assets/textures/player/PlayerLionfishy(96x80).png new file mode 100644 index 0000000000..95663ee6c0 Binary files /dev/null and b/assets/textures/player/PlayerLionfishy(96x80).png differ diff --git a/core/Cargo.toml b/core/Cargo.toml index 538ea4a1eb..1d36b55684 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,19 +1,24 @@ [package] -name = "fishfight-core" -version = "0.4.1" authors = ["Fish Fight Contributors"] -license = "MIT OR Apache-2.0" edition = "2021" +license = "MIT OR Apache-2.0" +name = "fishfight-core" +version = "0.4.2" [dependencies] -fishsticks = { git = "https://github.com/fishfight/fishsticks", default-features = false, features = ["gilrs"] } -macroquad = { version = "0.3.10" } -hecs = "0.7.1" -serde = { version = "1.0", package = "serde", features = ["derive"] } -serde_json = { version = "1.0" } -toml = "0.5" async-trait = "0.1.52" +fishsticks = {git = "https://github.com/fishfight/fishsticks", default-features = false, features = ["gilrs"]} +hecs = {git = "https://github.com/sdleffler/hv-dev"} +hv-alchemy = {git = "https://github.com/sdleffler/hv-dev"} +hv-cell = {git = "https://github.com/sdleffler/hv-dev"} +hv-lua = {git = "https://github.com/sdleffler/hv-dev"}#imported so its macro's work +macroquad = {version = "0.3.10"} +serde = {version = "1.0", package = "serde", features = ["derive"]} +serde_json = {version = "1.0"} +tealr = {git = "https://github.com/lenscas/tealr", branch = "temporary/feature/add_support_for_hv_lua", features = ["mlua", "hv_lua", "mlua_macros", "mlua_vendored", "mlua_luajit", "mlua_serialize", "derive"]} +#tealr = {path = "../../tealr/tealr", features = ["mlua", "hv_lua", "mlua_vendored", "mlua_luajit", "mlua_serialize", "mlua_send", "derive"]} +toml = "0.5" [target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.2", features = ["js"] } -wasm-bindgen = "0.2.74" \ No newline at end of file +getrandom = {version = "0.2", features = ["js"]} +wasm-bindgen = "0.2.74" diff --git a/core/src/lib.rs b/core/src/lib.rs index ed8f3cc21a..6a65e05041 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -20,3 +20,6 @@ pub use transform::Transform; pub use async_trait::async_trait; pub use serde; pub use serde_json; + +pub mod lua; +pub mod test; diff --git a/core/src/lua/create_component.rs b/core/src/lua/create_component.rs new file mode 100644 index 0000000000..1b6fbb534d --- /dev/null +++ b/core/src/lua/create_component.rs @@ -0,0 +1,166 @@ +use std::borrow::Cow; + +use hv_lua::{FromLua, ToLua}; +use tealr::{NameContainer, NamePart, TealType, TypeName}; + +#[derive(Debug, Clone, Copy)] +pub struct CopyComponent(T); +impl hv_lua::UserData for CopyComponent +where + T: for<'a> ToLua<'a> + for<'a> FromLua<'a>, +{ + #[allow(clippy::unit_arg)] + fn add_fields<'lua, F: hv_lua::UserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("value", |_, this| Ok(this.0)); + fields.add_field_method_set("value", |_, this, value| Ok(this.0 = value)); + } + fn on_metatable_init(t: hv_alchemy::Type) { + use hv_lua::hv::LuaUserDataTypeExt; + t.add_clone().add_copy().mark_component(); + } + fn add_type_methods<'lua, M: hv_lua::UserDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) { + methods.add_function("new", |_, i: T| Ok(Self(i))); + } + fn on_type_metatable_init(t: hv_alchemy::Type>) { + use hv_lua::hv::LuaUserDataTypeTypeExt; + t.mark_component_type(); + } +} +impl tealr::TypeName for CopyComponent { + fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> { + let name = tealr::NamePart::Type(TealType { + name: Cow::Borrowed("Component"), + type_kind: tealr::KindOfType::External, + generics: None, + }); + let mut type_name = vec![name, NamePart::Symbol(Cow::Borrowed("<"))]; + type_name.append(&mut T::get_type_parts().into_owned()); + type_name.push(NamePart::Symbol(Cow::Borrowed(">"))); + Cow::Owned(type_name) + } +} +impl tealr::TypeBody for CopyComponent { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + get_body_component::(gen); + //dbg!(gen); + } + fn get_type_body_marker(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + get_type_body_component::(gen); + } +} + +#[derive(Debug, Clone, Copy)] +pub struct CloneComponent(T); +impl hv_lua::UserData for CloneComponent +where + T: for<'a> ToLua<'a> + for<'a> FromLua<'a>, +{ + #[allow(clippy::unit_arg)] + fn add_fields<'lua, F: hv_lua::UserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("value", |_, this| Ok(this.0.clone())); + fields.add_field_method_set("value", |_, this, value| Ok(this.0 = value)); + } + fn on_metatable_init(t: hv_alchemy::Type) { + use hv_lua::hv::LuaUserDataTypeExt; + t.add_clone().mark_component(); + } + fn add_type_methods<'lua, M: hv_lua::UserDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) { + methods.add_function("new", |_, i: T| Ok(Self(i))); + } + fn on_type_metatable_init(t: hv_alchemy::Type>) { + use hv_lua::hv::LuaUserDataTypeTypeExt; + t.mark_component_type(); + } +} +impl tealr::TypeName for CloneComponent { + fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> { + let name = tealr::NamePart::Type(TealType { + name: Cow::Borrowed("Component"), + type_kind: tealr::KindOfType::External, + generics: None, + }); + let mut type_name = vec![name, NamePart::Symbol(Cow::Borrowed("<"))]; + type_name.append(&mut T::get_type_parts().into_owned()); + type_name.push(NamePart::Symbol(Cow::Borrowed(">"))); + Cow::Owned(type_name) + } +} +impl tealr::TypeBody for CloneComponent { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + get_body_component::(gen); + } + fn get_type_body_marker(gen: &mut tealr::TypeGenerator) { + get_type_body_component::(gen); + } +} + +fn get_body_component(gen: &mut tealr::TypeGenerator) { + gen.fields.push(( + "value".as_bytes().to_vec().into(), + ::get_type_parts(), + )); +} + +fn get_type_body_component(gen: &mut tealr::TypeGenerator) { + let mut signature = vec![NamePart::Symbol(Cow::Borrowed("function("))]; + signature.append(&mut T::get_type_parts().into_owned()); + signature.push(NamePart::Symbol(Cow::Borrowed("):"))); + signature.append(&mut SelfType::get_type_parts().into_owned()); + gen.methods.push(tealr::ExportedFunction { + name: Cow::Borrowed("new").into(), + signature: Cow::Owned(signature), + is_meta_method: false, + }); +} +#[macro_export] +macro_rules! create_type_component_container { + ($name:ident with $($field_name:ident of $type_name:ty,)+) => { + #[derive(Debug, Clone)] + pub struct $name<'lua>(hv_lua::Table<'lua>); + impl<'lua> hv_lua::ToLua<'lua> for $name<'lua> { + fn to_lua( + self, + lua: &'lua hv_lua::Lua, + ) -> std::result::Result, hv_lua::Error> { + self.0.to_lua(lua) + } + } + impl<'lua> tealr::TypeName for $name<'lua> { + fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> { + use tealr::new_type; + new_type!(Components) + } + } + impl<'lua> tealr::TypeBody for $name<'lua> { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + $( + println!("pushing field: {}",stringify!($type_name)); + gen.fields.push( + ( + + stringify!($field_name).as_bytes().to_vec().into(), + <$type_name as tealr::TypeName>::get_marker_type_parts() + + ) + ); + )* + + } + } + impl<'lua> $name<'lua> { + pub fn new(lua: &'lua hv_lua::Lua) -> Result> { + let table = lua.create_table()?; + $( + table.set(stringify!($field_name),lua.create_userdata_type::<$type_name>()?)?; + )* + Ok(Self(table)) + } + } + }; +} diff --git a/core/src/lua/mod.rs b/core/src/lua/mod.rs new file mode 100644 index 0000000000..c721dc9c0f --- /dev/null +++ b/core/src/lua/mod.rs @@ -0,0 +1,124 @@ +mod create_component; +pub mod wrapped_types; +pub use create_component::{CloneComponent, CopyComponent}; + +use std::{error::Error, os::unix::prelude::OsStrExt, path::Path}; + +use hv_lua::{chunk, hv::types, Function, Lua, Table, Value}; + +const LUA_ENTRY: &str = "main"; + +crate::create_type_component_container!( + TypeContainer with + I32 of CopyComponent, + Bool of CopyComponent, +); + +pub fn init_lua Result>>( + mod_dir: &Path, + register_types: RegisterTypes, +) -> Result> { + let lua = Lua::new(); + let hv = types(&lua)?; + let component_types = register_types(&lua)?; + let dir = mod_dir.as_os_str().as_bytes().to_owned(); + { + let globals = lua.globals(); + //a table containing all the mods + globals.set("mods", lua.create_table()?)?; + //this table is used to have quick access to every event that needs to run. + globals.set("events", lua.create_table()?)?; + + //we don't want users to simply use `require` to load their files as that will either result in conflicts or annoying path names + //to drive this point home, I rename the `require` function here and add 2 other functions to load mod files + let req = globals.get::<_, Function>("require")?; + globals.set("require_lib", req)?; + globals.set("require", hv_lua::Nil)?; + + globals.set("hv", hv)?; + globals.set("type_components", component_types)?; + + //this function allows people to load a file from arbitrary mods + //this allows "library only" mods to exist. + //there will also be a function that loads from the current mod. However, that one needs to be defined when loading the mod + let load_any_mod_file = lua.create_function( + move |lua, (from_mod, path): (hv_lua::String, hv_lua::String)| { + //TODO: a way to go from `mod id` to `mod folder` + let globals = lua.globals(); + let req = globals.get::<_, Function>("require_lib")?; + let mod_folder = globals + .get::<_, Table>("mods")? + .get::<_, Table>(from_mod)? + .get::<_, hv_lua::String>("dir_name")?; + + let mut full_path = Vec::new(); + full_path.extend_from_slice(&dir); + full_path.extend_from_slice(b"."); + full_path.extend_from_slice(mod_folder.as_bytes()); + full_path.extend_from_slice(b"."); + full_path.extend_from_slice(path.as_bytes()); + let full_path = lua.create_string(&full_path)?; + req.call::<_, hv_lua::Value>(full_path) + }, + )?; + globals.set("require_from", load_any_mod_file)?; + } + Ok(lua) +} + +pub fn load_lua>( + mod_id: String, + mod_folder: P, + lua: &Lua, +) -> Result<(), Box> { + let name = mod_id; + let name_to_transfer = name.clone(); + //ideally, we use a lua string but... those don't get transferred properly for some reason.... + let dir = String::from_utf8_lossy(mod_folder.as_ref()); + let entry = LUA_ENTRY.to_string(); + let chunk = chunk! { + local name = $name_to_transfer + local dir = $dir + local entry = $entry + + function require(load_path) + return require_from(name , load_path) + end + local function init_mod() + return require(entry) + end + local mod_config = { + dir_name = dir, + require = require, + mod_id = name + } + mods[name] = mod_config + + local mod_events = init_mod() + mod_config["events"] = mod_events + if type(mod_events) == "table" then + for k, _ in pairs(mod_events) do + local event_list = events[k] or {} + table.insert(event_list, mod_config) + events[k] = event_list + end + end + require = nil + }; + lua.load(chunk) + .set_name(&format!("Load mod: {:?}", name))? + .exec()?; + + Ok(()) +} + +pub fn get_table(value: hv_lua::Value) -> Result { + match value { + hv_lua::Value::Table(x) => Ok(x), + v => Err(hv_lua::Error::FromLuaConversionError { + from: v.type_name(), + to: "table", + message: None, + }), + } +} diff --git a/core/src/lua/wrapped_types.rs b/core/src/lua/wrapped_types.rs new file mode 100644 index 0000000000..5cf48df440 --- /dev/null +++ b/core/src/lua/wrapped_types.rs @@ -0,0 +1,306 @@ +use std::borrow::Cow; + +use hv_lua::{FromLua, ToLua, UserData, Value}; +use macroquad::{ + audio::Sound, + prelude::{Color, Rect, Texture2D, Vec2}, +}; +use tealr::{ + mlu::{MaybeSend, TealData, UserDataWrapper}, + new_type, TypeBody, TypeName, +}; + +#[derive(Clone)] +pub struct Vec2Lua { + pub x: f32, + pub y: f32, +} +impl From for Vec2Lua { + fn from(x: Vec2) -> Self { + Self { x: x.x, y: x.y } + } +} +impl From for Vec2 { + fn from(x: Vec2Lua) -> Self { + Self::new(x.x, x.y) + } +} +impl<'lua> ToLua<'lua> for Vec2Lua { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let position = lua.create_table()?; + position.set("x", self.x)?; + position.set("y", self.y)?; + lua.pack(position) + } +} + +impl<'lua> FromLua<'lua> for Vec2Lua { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let value = match lua_value { + hv_lua::Value::Table(x) => x, + x => { + return Err(hv_lua::Error::FromLuaConversionError { + from: x.type_name(), + to: "Table", + message: None, + }) + } + }; + let x = value.get("x")?; + let y = value.get("y")?; + Ok(Self { x, y }) + } +} + +impl TypeName for Vec2Lua { + fn get_type_parts() -> Cow<'static, [tealr::NamePart]> { + tealr::new_type!(Vec2, External) + } +} +impl TypeBody for Vec2Lua { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("x").into(), f32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("y").into(), f32::get_type_parts())); + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct ColorLua { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, +} + +impl TypeBody for ColorLua { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push(("r".as_bytes().to_vec().into(), f32::get_type_parts())); + gen.fields + .push(("g".as_bytes().to_vec().into(), f32::get_type_parts())); + gen.fields + .push(("b".as_bytes().to_vec().into(), f32::get_type_parts())); + gen.fields + .push(("a".as_bytes().to_vec().into(), f32::get_type_parts())); + } +} + +impl TypeName for ColorLua { + fn get_type_parts() -> Cow<'static, [tealr::NamePart]> { + tealr::new_type!(Color, External) + } +} +impl From for ColorLua { + fn from(color: Color) -> Self { + Self { + r: color.r, + g: color.g, + b: color.b, + a: color.a, + } + } +} + +impl From for Color { + fn from(color: ColorLua) -> Self { + Self { + r: color.r, + g: color.g, + b: color.b, + a: color.a, + } + } +} +impl<'lua> FromLua<'lua> for ColorLua { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let v = match lua_value { + Value::Table(x) => x, + x => { + return Err(hv_lua::Error::FromLuaConversionError { + from: x.type_name(), + to: "table", + message: None, + }) + } + }; + Ok(Self { + r: v.get("r")?, + g: v.get("g")?, + b: v.get("b")?, + a: v.get("a")?, + }) + } +} +impl<'lua> ToLua<'lua> for ColorLua { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("r", self.r)?; + table.set("g", self.g)?; + table.set("b", self.b)?; + table.set("a", self.a)?; + lua.pack(table) + } +} + +#[derive(Clone)] +pub struct RectLua(Rect); +impl From for Rect { + fn from(r: RectLua) -> Self { + r.0 + } +} +impl From for RectLua { + fn from(x: Rect) -> Self { + Self(x) + } +} +impl TypeName for RectLua { + fn get_type_parts() -> Cow<'static, [tealr::NamePart]> { + tealr::new_type!(Rect, External) + } +} +impl UserData for RectLua { + fn add_fields<'lua, F: hv_lua::UserDataFields<'lua, Self>>(fields: &mut F) { + let mut wrapper = UserDataWrapper::from_user_data_fields(fields); + ::add_fields(&mut wrapper) + } + + fn add_methods<'lua, M: hv_lua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut wrapper) + } + fn add_type_methods<'lua, M: hv_lua::UserDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) where + Self: 'static + MaybeSend, + { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_type_methods(&mut wrapper) + } +} +impl TealData for RectLua { + fn add_type_methods<'lua, M: tealr::mlu::TealDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) where + Self: 'static + MaybeSend, + { + methods.add_function("new", |_, (x, y, w, h)| { + Ok(RectLua::from(Rect::new(x, y, w, h))) + }) + } + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("point", |lua, this, ()| Ok(Vec2Lua::from(this.0.point()))); + methods.add_method("size", |lua, this, ()| Ok(Vec2Lua::from(this.0.size()))); + methods.add_method("left", |lua, this, ()| Ok(this.0.left())); + methods.add_method("right", |lua, this, ()| Ok(this.0.right())); + methods.add_method("top", |lua, this, ()| Ok(this.0.top())); + methods.add_method("bottom", |lua, this, ()| Ok(this.0.bottom())); + methods.add_method_mut("move_to", |_, this, vec: Vec2Lua| { + this.0.move_to(vec.into()); + Ok(()) + }); + methods.add_method_mut("scale", |_, this, (sx, sy)| { + this.0.scale(sx, sy); + Ok(()) + }); + methods.add_method("contains", |lua, this, point: Vec2Lua| { + Ok(this.0.contains(point.into())) + }); + methods.add_method("overlaps", |lua, this, other: RectLua| { + Ok(this.0.overlaps(&other.into())) + }); + methods.add_method("combine_with", |lua, this, other: RectLua| { + Ok(RectLua::from(this.0.combine_with(other.into()))) + }); + methods.add_method("intersect", |lua, this, other: RectLua| { + Ok(this.0.intersect(other.into()).map(RectLua::from)) + }); + methods.add_method("offset", |lua, this, offset: Vec2Lua| { + Ok(RectLua::from(this.0.offset(offset.into()))) + }); + } + fn add_fields<'lua, F: tealr::mlu::TealDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("x", |lua, this| Ok(this.0.x)); + fields.add_field_method_get("y", |lua, this| Ok(this.0.y)); + fields.add_field_method_get("w", |lua, this| Ok(this.0.w)); + fields.add_field_method_get("h", |lua, this| Ok(this.0.h)); + fields.add_field_method_set("x", |_, this, value| { + this.0.x = value; + Ok(()) + }); + fields.add_field_method_set("y", |_, this, value| { + this.0.y = value; + Ok(()) + }); + fields.add_field_method_set("w", |_, this, value| { + this.0.w = value; + Ok(()) + }); + fields.add_field_method_set("h", |_, this, value| { + this.0.h = value; + Ok(()) + }); + } +} +impl TypeBody for RectLua { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_fields(gen); + ::add_methods(gen); + } + fn get_type_body_marker(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_type_methods(gen); + } +} + +#[derive(Clone)] +pub struct Texture2DLua(Texture2D); +impl TypeName for Texture2DLua { + fn get_type_parts() -> Cow<'static, [tealr::NamePart]> { + new_type!(Texture2D, External) + } +} +impl UserData for Texture2DLua {} +impl TypeBody for Texture2DLua { + fn get_type_body(_: &mut tealr::TypeGenerator) {} +} + +impl From for Texture2D { + fn from(x: Texture2DLua) -> Self { + x.0 + } +} +impl From for Texture2DLua { + fn from(x: Texture2D) -> Self { + Self(x) + } +} + +#[derive(Clone)] +pub struct SoundLua(Sound); +impl TypeName for SoundLua { + fn get_type_parts() -> Cow<'static, [tealr::NamePart]> { + new_type!(Sound, External) + } +} +impl UserData for SoundLua {} +impl TealData for SoundLua {} +impl TypeBody for SoundLua { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + } +} + +impl From for SoundLua { + fn from(s: Sound) -> Self { + Self(s) + } +} +impl From for Sound { + fn from(x: SoundLua) -> Self { + x.0 + } +} diff --git a/core/src/query_builder.rs b/core/src/query_builder.rs new file mode 100644 index 0000000000..df140e65b7 --- /dev/null +++ b/core/src/query_builder.rs @@ -0,0 +1,312 @@ +use std::{ + fmt::Display, + marker::PhantomData, + sync::{Arc, Mutex, PoisonError}, +}; + +use hecs::{Entity, Query, QueryBorrow, QueryMut, World}; + +#[derive(Clone)] +pub struct BuildStart(); +pub struct Builder1(PhantomData); +impl Clone for Builder1 { + fn clone(&self) -> Self { + Self(PhantomData) + } +} +pub struct Builder2(PhantomData<(A, B)>); +impl Clone for Builder2 { + fn clone(&self) -> Self { + Self(PhantomData) + } +} +pub struct Builder3(PhantomData<(A, B, C)>); +impl Clone for Builder3 { + fn clone(&self) -> Self { + Self(PhantomData) + } +} +pub struct Builder4(PhantomData<(A, B, C, D)>); +pub struct Builder5(PhantomData<(A, B, C, D, E)>); +pub struct Builder6(PhantomData<(A, B, C, D, E, F)>); +pub struct Builder7(PhantomData<(A, B, C, D, E, F, G)>); +pub struct Builder8(PhantomData<(A, B, C, D, E, F, G, H)>); +pub struct Builder9(PhantomData<(A, B, C, D, E, F, G, H, I)>); +pub struct Builder10(PhantomData<(A, B, C, D, E, F, G, H, I, J)>); +pub struct Builder11( + PhantomData<(A, B, C, D, E, F, G, H, I, J, K)>, +); +pub struct Builder12( + PhantomData<(A, B, C, D, E, F, G, H, I, J, K, L)>, +); +pub struct Builder13( + PhantomData<(A, B, C, D, E, F, G, H, I, J, K, L, M)>, +); +pub struct Builder14( + PhantomData<(A, B, C, D, E, F, G, H, I, J, K, L, M, N)>, +); +pub struct Builder15( + PhantomData<(A, B, C, D, E, F, G, H, I, J, K, L, M, N, P)>, +); + +impl Default for BuildStart { + fn default() -> Self { + Self::new() + } +} + +impl BuildStart { + pub fn new() -> Self { + Self() + } + pub fn query(world: &World) -> QueryBorrow<'_, ()> { + world.query() + } + pub fn query_mut(world: &mut World) -> QueryMut<'_, ()> { + world.query_mut() + } + pub fn with(self) -> Builder1 { + Builder1(PhantomData) + } +} + +impl Builder1 { + pub fn query<'a, 'world>(&'a self, world: &'world World) -> QueryBorrow<'world, T> { + world.query() + } + pub fn query_mut<'a, 'world>(&'a self, world: &'world mut World) -> QueryMut<'world, T> { + world.query_mut() + } + pub fn with(self) -> Builder2 { + Builder2(PhantomData) + } +} + +impl Builder2 +where + (A, B): Query, +{ + pub fn query<'a, 'world>(&'a self, world: &'world World) -> QueryBorrow<'world, (A, B)> { + world.query() + } + pub fn query_mut<'a, 'world>(&'a self, world: &'world mut World) -> QueryMut<'world, (A, B)> { + world.query_mut() + } + pub fn with(self) -> Builder3 { + Builder3(PhantomData) + } +} + +impl Builder3 +where + (A, B, C): Query, +{ + pub fn query(self, world: &World) -> QueryBorrow<'_, (A, B, C)> { + world.query() + } + pub fn query_mut(self, world: &mut World) -> QueryMut<'_, (A, B, C)> { + println!("QUERY!"); + world.query_mut() + } + //added for completions sake but.. not actually going to do much + pub fn with(self) -> Builder4 + where + (A, B, C, N): Query, + { + Builder4(PhantomData) + } +} + +use tealr::{ + mlu::{ + mlua::{Error, ToLua, UserData}, + TealData, TypedFunction, + }, + new_type, TypeName, +}; + +#[derive(Clone, TypeName)] +pub struct LuaWorld(Arc>); + +impl LuaWorld { + fn lock(&self) -> Result, QueryError> { + self.0.lock().map_err(QueryError::from) + } +} + +impl UserData for LuaWorld { + fn add_methods<'lua, M: tealr::mlu::mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut x = ::tealr::mlu::UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut x); + } +} +impl TealData for LuaWorld { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_function( + "start_query", + |lua, function: tealr::mlu::mlua::Function| lua.scope(|v| Ok(())), + ) + } +} + +impl TypeName for BuildStart { + fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> { + new_type!(QueryBuilder) + } +} +//for simplicity sake, lets get rid of the types for now. +impl TypeName for Builder1 { + fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> { + new_type!(QueryBuilder1) + } +} +//for simplicity sake, lets get rid of the types for now. +impl TypeName for Builder2 { + fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> { + new_type!(QueryBuilder2) + } +} + +//for simplicity sake, lets get rid of the types for now. +impl TypeName for Builder3 { + fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> { + new_type!(QueryBuilder3) + } +} +impl UserData for BuildStart { + fn add_methods<'lua, M: tealr::mlu::mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut x = ::tealr::mlu::UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut x); + } +} + +#[derive(Debug)] +pub enum QueryError { + PoisonedWorld, +} + +impl Display for QueryError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "The world mutex got poisoned. This shouldn't happen. PANIC!" + ) + } +} +impl From> for QueryError { + fn from(_: PoisonError) -> Self { + QueryError::PoisonedWorld + } +} +impl From for Error { + fn from(x: QueryError) -> Self { + Error::external(x) + } +} + +#[derive(Clone, Copy, TypeName)] +pub struct LuaEntity(Entity); + +impl TealData for LuaEntity {} +impl UserData for LuaEntity { + fn add_methods<'lua, M: tealr::mlu::mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut x = ::tealr::mlu::UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut x); + } +} + +impl std::error::Error for QueryError {} + +impl TealData for BuildStart { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method( + "query", + |_, + _, + (luaworld, func): ( + LuaWorld, + TypedFunction, + )| { + let unlocked = luaworld.0.lock().map_err(QueryError::from)?; + let mut x = BuildStart::query(&unlocked); + let mut res = Vec::new(); + for (v, _) in &mut x { + res.push(func.call(LuaEntity(v))?) + } + //BuildStart::query() + Ok(res) + }, + ); + methods.add_method("with_integer", |_, this, ()| { + Ok(this.to_owned().with::<&mut i64>()) + }) + } +} +impl<'a, T: 'a> UserData for Builder1<&'a mut T> +where + &'a mut T: Query, + T: ToOwned, + ::Owned: ToLua<'static>, +{ + fn add_methods<'lua, M: tealr::mlu::mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut x = ::tealr::mlu::UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut x); + } +} +impl<'a, A: 'a> TealData for Builder1<&'a mut A> +where + &'a mut A: Query, + A: ToOwned, + ::Owned: ToLua<'static>, +{ + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("with_string", |_, this, ()| { + let x = this.to_owned(); + let x = x.with::<&mut String>(); + Ok(x) + }) + } +} + +impl<'a, A: 'a, B: 'a> UserData for Builder2<&mut A, &mut B> +where + (&'a mut A, &'a mut B): Query, + A: ToOwned, + B: ToOwned, + ::Owned: ToLua<'static>, + ::Owned: ToLua<'static>, +{ + fn add_methods<'lua, M: tealr::mlu::mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut x = ::tealr::mlu::UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut x); + } +} +impl TealData for Builder2 +where + (A, B): Query, + A: ToOwned, + B: ToOwned, + ::Owned: ToLua<'static>, + ::Owned: ToLua<'static>, +{ + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method( + "query", + |lua, + this, + (world, func): ( + LuaWorld, + tealr::mlu::TypedFunction, + )| { + let z = world.lock()?; + let mut x = this.query(&z); + let mut res = Vec::new(); + for (v, x) in &mut x { + res.push(func.call(LuaEntity(v))?) + } + //BuildStart::query() + Ok(res) + }, + ) + } +} diff --git a/core/src/test.rs b/core/src/test.rs new file mode 100644 index 0000000000..e59ac3c502 --- /dev/null +++ b/core/src/test.rs @@ -0,0 +1,127 @@ +use std::{error::Error, sync::Arc}; + +use hecs::{Entity, World}; +use hv_alchemy::Type; +use hv_cell::AtomicRefCell; +use tealr::mlu::mlua::{ + chunk, + hv::{types, LuaUserDataTypeExt, LuaUserDataTypeTypeExt}, + Lua, UserData, UserDataFields, UserDataMethods, +}; + +#[derive(Debug, Clone, Copy)] +pub struct I32Component(i32); +impl UserData for I32Component { + #[allow(clippy::unit_arg)] + fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("value", |_, this| Ok(this.0)); + fields.add_field_method_set("value", |_, this, value| Ok(this.0 = value)); + } + fn on_metatable_init(t: Type) { + t.add_clone().add_copy().mark_component(); + } + + // The following methods are a bit like implementing `UserData` on `Type`, the userdata + // type object of `Self`. This one just lets you construct an `I32Component` from Lua given a + // value convertible to an `i32`. + fn add_type_methods<'lua, M: UserDataMethods<'lua, Type>>(methods: &mut M) { + methods.add_function("new", |_, i: i32| Ok(Self(i))); + } + + // We want to generate the necessary vtables for accessing this type as a component in the ECS. + // The `LuaUserDataTypeTypeExt` extension trait provides convenient methods for registering the + // required traits for this (`.mark_component_type()` is shorthand for + // `.add::()`.) + fn on_type_metatable_init(t: Type>) { + t.mark_component_type(); + } +} +#[derive(Debug, Clone, Copy)] +pub struct BoolComponent(bool); +impl UserData for BoolComponent { + #[allow(clippy::unit_arg)] + fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("value", |_, this| Ok(this.0)); + fields.add_field_method_set("value", |_, this, value| Ok(this.0 = value)); + } + fn on_metatable_init(t: Type) { + t.add_clone().add_copy().mark_component(); + } + + // The following methods are a bit like implementing `UserData` on `Type`, the userdata + // type object of `Self`. This one just lets you construct an `BoolComponent` from Lua given a + // value convertible to an `i32`. + fn add_type_methods<'lua, M: UserDataMethods<'lua, Type>>(methods: &mut M) { + methods.add_function("new", |_, i: bool| Ok(Self(i))); + } + + // We want to generate the necessary vtables for accessing this type as a component in the ECS. + // The `LuaUserDataTypeTypeExt` extension trait provides convenient methods for registering the + // required traits for this (`.mark_component_type()` is shorthand for + // `.add::()`.) + fn on_type_metatable_init(t: Type>) { + t.mark_component_type(); + } +} + +pub fn test() -> Result<(), Box> { + let lua = Lua::new(); + let hv = types(&lua)?; + let i32_ty = lua.create_userdata_type::()?; + let bool_ty = lua.create_userdata_type::()?; + + let world = Arc::new(AtomicRefCell::new(World::new())); + // Clone the world so that it doesn't become owned by Lua. We still want a copy! + let world_clone = world.clone(); + + let chunk = chunk! { + print("Hello from lua!") + // Drag in the `hv` table we created above, and also the `I32Component` and `BoolComponent` types, + // presumptuously calling them `I32` and `Bool` just because they're wrappers around the fact we + // can't just slap a primitive in there and call it a day. + local hv = $hv + local Query = hv.ecs.Query + local I32, Bool = $i32_ty, $bool_ty + + local world = $world_clone + // Spawn an entity, dynamically adding components to it taken from userdata! Works with copy, + // clone, *and* non-clone types (non-clone types will be moved out of the userdata and the userdata + // object marked as destructed) + print("make new entity with I32(5) and Bool(true)") + local entity = world:spawn { I32.new(5), Bool.new(true) } + print("Query it") + // Dynamic query functionality, using our fork's `hecs::DynamicQuery`. + local query = Query.new { Query.write(I32), Query.read(Bool) } + // Querying takes a closure in order to enforce scope - the queryitem will panic if used outside that + // scope. + world:query_one(query, entity, function(item) + print("Got item:",item) + // Querying allows us to access components of our item as userdata objects through the same interface + // we defined above! + print("asserting if still true") + assert(item:take(Bool).value == true) + local i = item:take(I32) + print("asserting if still 5") + assert(i.value == 5) + print("time to set it to 6") + i.value = 6 + assert(i.value == 6) + end) + print("Returning it from lua, back to rust") + // Return the entity we spawned back to Rust so we can examine it there. + return entity + }; + let entity: Entity = lua.load(chunk).eval()?; + + // Look! It worked! + let borrowed = world.borrow(); + println!("Querying from rust and asserting_eq"); + let mut q = borrowed + .query_one::<(&I32Component, &BoolComponent)>(entity) + .ok(); + assert_eq!( + q.as_mut().and_then(|q| q.get()).map(|(i, b)| (i.0, b.0)), + Some((6, true)) + ); + Ok(()) +} diff --git a/core/src/transform.rs b/core/src/transform.rs index f52e1babcd..58e67c008a 100644 --- a/core/src/transform.rs +++ b/core/src/transform.rs @@ -1,6 +1,11 @@ +use std::borrow::Cow; + +use hv_lua::{FromLua, ToLua}; use macroquad::prelude::*; +use tealr::{TypeBody, TypeName}; -#[derive(Debug, Default)] +use crate::lua::wrapped_types::Vec2Lua; +#[derive(Debug, Default, tealr::TypeName, Clone)] pub struct Transform { pub position: Vec2, pub rotation: f32, @@ -20,3 +25,42 @@ impl From for Transform { } } } +impl<'lua> ToLua<'lua> for Transform { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let transform = lua.create_table()?; + let position = Vec2Lua::from(self.position).to_lua(lua)?; + transform.set("position", position)?; + transform.set("rotation", self.rotation)?; + lua.pack(transform) + } +} + +impl<'lua> FromLua<'lua> for Transform { + fn from_lua( + value: hv_lua::Value<'lua>, + _: &'lua hv_lua::Lua, + ) -> std::result::Result { + let value = match value { + hv_lua::Value::Table(x) => x, + x => { + return Err(hv_lua::Error::FromLuaConversionError { + from: x.type_name(), + to: "Table", + message: None, + }) + } + }; + let position = value.get::<_, Vec2Lua>("position")?.into(); + let rotation = value.get("rotation")?; + + Ok(Self { position, rotation }) + } +} +impl TypeBody for Transform { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("position").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("rotation").into(), f32::get_type_parts())); + } +} diff --git a/licenses/LICENSE-APACHE b/licenses/LICENSE-APACHE index 5bc9139c95..39429f1e3b 100644 --- a/licenses/LICENSE-APACHE +++ b/licenses/LICENSE-APACHE @@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright (c) 2020-2021 The Fish Fight Game Developers +Copyright (c) 2020-2022 The Fish Fight Game Developers Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/licenses/LICENSE-MIT b/licenses/LICENSE-MIT index bd34cbeaf8..3011f8b12d 100644 --- a/licenses/LICENSE-MIT +++ b/licenses/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2020-2021 The Fish Fight Game Developers +Copyright (c) 2020-2022 The Fish Fight Game Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/mods/active_mods.json b/mods/active_mods.json index dc3adf275f..e28d9734ae 100644 --- a/mods/active_mods.json +++ b/mods/active_mods.json @@ -1,4 +1,5 @@ [ "test_mod_one", - "test_mod_two" + "test_mod_two", + "test_lua_mod_one" ] \ No newline at end of file diff --git a/mods/test_lua_mod_one/fishfight_mod.json b/mods/test_lua_mod_one/fishfight_mod.json new file mode 100644 index 0000000000..79308e437d --- /dev/null +++ b/mods/test_lua_mod_one/fishfight_mod.json @@ -0,0 +1,7 @@ +{ + "id": "test_lua_mod_one_id_is_different", + "name": "Test Lua Mod One", + "description": "This is a mod meant for testing the lua integration", + "version": "0.1.0", + "kind": "full" +} \ No newline at end of file diff --git a/mods/test_lua_mod_one/main.lua b/mods/test_lua_mod_one/main.lua new file mode 100644 index 0000000000..b76b31e48e --- /dev/null +++ b/mods/test_lua_mod_one/main.lua @@ -0,0 +1,33 @@ +print("Hello from lua mod!") +require "second_file" +local entity; +return { + init = function(world) + print "Run init" + local I32 = type_components.I32 + local Bool = type_components.Bool + entity = world:spawn { I32.new(5), Bool.new(true) } + for k,v in pairs(type_components) do + print(k,v) + end + end, + fixed_update_physics_bodies = function(world) + -- local Query = hv.ecs.Query + -- local I32 = type_components.I32 + -- local Bool = type_components.Bool + -- local query = Query.new { Query.write(I32), Query.read(Bool) } + -- world:query_one(query, entity, function(item) + -- print("Got item:",item) + -- -- Querying allows us to access components of our item as userdata objects through the same interface + -- -- we defined above! + -- print("asserting if still true") + -- assert(item:take(Bool).value == true) + -- local i = item:take(I32) + -- print("asserting if still bigger than 5") + -- assert(i.value >= 5) + -- print("time to increment by 1") + -- i.value = i.value + 1 + -- print("Current value:", i.value) + -- end) + end +} \ No newline at end of file diff --git a/mods/test_lua_mod_one/second_file.lua b/mods/test_lua_mod_one/second_file.lua new file mode 100644 index 0000000000..5e22194f1d --- /dev/null +++ b/mods/test_lua_mod_one/second_file.lua @@ -0,0 +1 @@ +print("This is a second lua file that got opened. LETS GO!") \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..5d56faf9ae --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/drawables/animated_sprite.rs b/src/drawables/animated_sprite.rs index 948ae0d44e..af795da1ac 100644 --- a/src/drawables/animated_sprite.rs +++ b/src/drawables/animated_sprite.rs @@ -1,8 +1,13 @@ -use std::borrow::BorrowMut; +use core::lua::get_table; +use core::lua::wrapped_types::{ColorLua, RectLua, Texture2DLua}; +use std::borrow::Cow; use std::collections::HashMap; use std::iter::FromIterator; use std::ops::Mul; +use std::{borrow::BorrowMut, sync::Arc}; +use hv_cell::AtomicRefCell; +use hv_lua::{FromLua, LuaSerdeExt, ToLua, UserData, Value}; use macroquad::color; use macroquad::experimental::animation::Animation as MQAnimation; use macroquad::experimental::collections::storage; @@ -11,12 +16,14 @@ use macroquad::prelude::*; use hecs::World; use serde::{Deserialize, Serialize}; +use tealr::mlu::{MaybeSend, TealData, UserDataWrapper}; +use tealr::{TypeBody, TypeName}; -use core::Transform; +use core::{lua::wrapped_types::Vec2Lua, Transform}; use crate::{Drawable, DrawableKind, Resources}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, TypeName)] pub struct Animation { pub id: String, pub row: u32, @@ -26,6 +33,50 @@ pub struct Animation { pub is_looping: bool, } +impl<'lua> FromLua<'lua> for Animation { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = get_table(lua_value)?; + Ok(Self { + id: table.get("id")?, + row: table.get("row")?, + frames: table.get("frames")?, + fps: table.get("fps")?, + tweens: table.get("tweens")?, + is_looping: table.get("is_looping")?, + }) + } +} +impl<'lua> ToLua<'lua> for Animation { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("id", self.id)?; + table.set("row", self.row)?; + table.set("frames", self.frames)?; + table.set("fps", self.fps)?; + table.set("tweens", self.tweens)?; + table.set("is_looping", self.is_looping)?; + lua.pack(table) + } +} +impl TypeBody for Animation { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("id").into(), String::get_type_parts())); + gen.fields + .push((Cow::Borrowed("row").into(), u32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("frames").into(), u32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("fps").into(), u32::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("tweens").into(), + HashMap::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("is_looping").into(), bool::get_type_parts())); + } +} + impl From for Animation { fn from(meta: AnimationMetadata) -> Self { let tweens = HashMap::from_iter( @@ -45,12 +96,47 @@ impl From for Animation { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, TypeName)] pub struct Tween { pub keyframes: Vec, pub current_translation: Vec2, } +impl<'lua> FromLua<'lua> for Tween { + fn from_lua(lua_value: hv_lua::Value<'lua>, lua: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = get_table(lua_value)?; + Ok(Self { + keyframes: lua.from_value(table.get::<_, Value>("keyframes")?)?, + current_translation: table.get::<_, Vec2Lua>("current_translation")?.into(), + }) + } +} + +impl<'lua> ToLua<'lua> for Tween { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("keyframes", lua.to_value(&self.keyframes)?)?; + table.set( + "current_translation", + Vec2Lua::from(self.current_translation), + )?; + lua.pack(table) + } +} + +impl TypeBody for Tween { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields.push(( + Cow::Borrowed("keyframes").into(), + Vec::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("current_translation").into(), + Vec2Lua::get_type_parts(), + )); + } +} + impl From for Tween { fn from(meta: TweenMetadata) -> Self { let mut keyframes = meta.keyframes; @@ -69,13 +155,24 @@ impl From for Tween { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, tealr::TypeName)] pub struct Keyframe { pub frame: u32, #[serde(with = "core::json::vec2_def")] pub translation: Vec2, } +impl TypeBody for Keyframe { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("frame").into(), u32::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("translation").into(), + Vec2Lua::get_type_parts(), + )); + } +} +#[derive(Clone, TypeName)] pub struct AnimatedSpriteParams { pub frame_size: Option, pub scale: f32, @@ -87,6 +184,66 @@ pub struct AnimatedSpriteParams { pub autoplay_id: Option, } +impl<'lua> FromLua<'lua> for AnimatedSpriteParams { + fn from_lua(lua_value: Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = get_table(lua_value)?; + Ok(Self { + frame_size: table + .get::<_, Option>("frame_size")? + .map(Vec2::from), + scale: table.get("scale")?, + offset: table.get::<_, Vec2Lua>("offset")?.into(), + pivot: table.get::<_, Option>("pivot")?.map(Vec2::from), + tint: table.get::<_, ColorLua>("tint")?.into(), + is_flipped_x: table.get("is_flipped_x")?, + is_flipped_y: table.get("is_flipped_y")?, + autoplay_id: table.get("autoplay_id")?, + }) + } +} + +impl<'lua> ToLua<'lua> for AnimatedSpriteParams { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("frame_size", self.frame_size.map(Vec2Lua::from))?; + table.set("scale", self.scale)?; + table.set("offset", Vec2Lua::from(self.offset))?; + table.set("pivot", self.pivot.map(Vec2Lua::from))?; + table.set("tint", ColorLua::from(self.tint))?; + table.set("is_flipped_x", self.is_flipped_x)?; + table.set("is_flipped_y", self.is_flipped_y)?; + table.set("autoplay_id", self.autoplay_id)?; + lua.pack(table) + } +} + +impl TypeBody for AnimatedSpriteParams { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields.push(( + Cow::Borrowed("frame_size").into(), + Option::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("scale").into(), f32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("offset").into(), Vec2Lua::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("pivot").into(), + Option::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("tint").into(), ColorLua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("is_flipped_x").into(), bool::get_type_parts())); + gen.fields + .push((Cow::Borrowed("is_flipped_y").into(), bool::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("autoplay_id").into(), + Option::::get_type_parts(), + )); + } +} + impl Default for AnimatedSpriteParams { fn default() -> Self { AnimatedSpriteParams { @@ -115,13 +272,17 @@ impl From for AnimatedSpriteParams { } } -#[derive(Clone)] +#[derive(Clone, TypeName)] pub enum QueuedAnimationAction { Play(String), PlayIndex(usize), WaitThen(f32, Box), Deactivate, } +impl UserData for QueuedAnimationAction {} +impl TypeBody for QueuedAnimationAction { + fn get_type_body(_: &mut tealr::TypeGenerator) {} +} impl QueuedAnimationAction { pub fn wait_then(delay: f32, action: QueuedAnimationAction) -> Self { @@ -129,7 +290,7 @@ impl QueuedAnimationAction { } } -#[derive(Clone)] +#[derive(Clone, TypeName)] pub struct AnimatedSprite { pub texture: Texture2D, pub frame_size: Vec2, @@ -149,6 +310,164 @@ pub struct AnimatedSprite { pub wait_timer: f32, } +impl UserData for AnimatedSprite { + fn add_fields<'lua, F: hv_lua::UserDataFields<'lua, Self>>(fields: &mut F) { + let mut wrapper = UserDataWrapper::from_user_data_fields(fields); + ::add_fields(&mut wrapper) + } + + fn add_methods<'lua, M: hv_lua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut wrapper) + } + fn add_type_methods<'lua, M: hv_lua::UserDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) where + Self: 'static + MaybeSend, + { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_type_methods(&mut wrapper) + } +} +impl TealData for AnimatedSprite { + fn add_fields<'lua, F: tealr::mlu::TealDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("texture", |_, this| Ok(Texture2DLua::from(this.texture))); + fields.add_field_method_get("frame_size", |_, this| Ok(Vec2Lua::from(this.frame_size))); + fields.add_field_method_get("scale", |_, this| Ok(this.scale)); + fields.add_field_method_get("offset", |_, this| Ok(Vec2Lua::from(this.offset))); + fields.add_field_method_get("pivot", |_, this| Ok(this.pivot.map(Vec2Lua::from))); + fields.add_field_method_get("tint", |_, this| Ok(ColorLua::from(this.tint))); + fields.add_field_method_get("animations", |_, this| Ok(this.animations.clone())); + fields.add_field_method_get("current_index", |_, this| Ok(this.current_index)); + fields.add_field_method_get("queued_action", |_, this| Ok(this.queued_action.clone())); + fields.add_field_method_get("current_frame", |_, this| Ok(this.current_frame)); + fields.add_field_method_get("frame_timer", |_, this| Ok(this.frame_timer)); + fields.add_field_method_get("is_playing", |_, this| Ok(this.is_playing)); + fields.add_field_method_get("is_flipped_x", |_, this| Ok(this.is_flipped_x)); + fields.add_field_method_get("is_flipped_y", |_, this| Ok(this.is_flipped_y)); + fields.add_field_method_get("is_deactivated", |_, this| Ok(this.is_deactivated)); + fields.add_field_method_get("wait_timer", |_, this| Ok(this.wait_timer)); + fields.add_field_method_set("texture", |_, this, value: Texture2DLua| { + this.texture = value.into(); + Ok(()) + }); + fields.add_field_method_set("frame_size", |_, this, value: Vec2Lua| { + this.frame_size = value.into(); + Ok(()) + }); + fields.add_field_method_set("scale", |_, this, value| { + this.scale = value; + Ok(()) + }); + fields.add_field_method_set("offset", |_, this, value: Vec2Lua| { + this.offset = value.into(); + Ok(()) + }); + fields.add_field_method_set("pivot", |_, this, value: Option| { + this.pivot = value.map(Into::into); + Ok(()) + }); + fields.add_field_method_set("tint", |_, this, value: ColorLua| { + this.tint = value.into(); + Ok(()) + }); + fields.add_field_method_set("animations", |_, this, value| { + this.animations = value; + Ok(()) + }); + fields.add_field_method_set("current_index", |_, this, value| { + this.current_index = value; + Ok(()) + }); + fields.add_field_method_set("queued_action", |_, this, value| { + this.queued_action = value; + Ok(()) + }); + fields.add_field_method_set("current_frame", |_, this, value| { + this.current_frame = value; + Ok(()) + }); + fields.add_field_method_set("frame_timer", |_, this, value| { + this.frame_timer = value; + Ok(()) + }); + fields.add_field_method_set("is_playing", |_, this, value| { + this.is_playing = value; + Ok(()) + }); + fields.add_field_method_set("is_flipped_x", |_, this, value| { + this.is_flipped_x = value; + Ok(()) + }); + fields.add_field_method_set("is_flipped_y", |_, this, value| { + this.is_flipped_y = value; + Ok(()) + }); + fields.add_field_method_set("is_deactivated", |_, this, value| { + this.is_deactivated = value; + Ok(()) + }); + fields.add_field_method_set("wait_timer", |_, this, value| { + this.wait_timer = value; + Ok(()) + }); + } + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("get_animation", |_, this, value: String| { + Ok(this.get_animation(&value).map(ToOwned::to_owned)) + }); + methods.add_method("current_animation", |_, this, ()| { + Ok(this.current_animation().to_owned()) + }); + methods.add_method("size", |_, this, ()| Ok(Vec2Lua::from(this.size()))); + methods.add_method("source_rect", |_, this, ()| { + Ok(RectLua::from(this.source_rect())) + }); + methods.add_method("as_index", |_, this, value: String| { + Ok(this.as_index(&value)) + }); + methods.add_method_mut("set_animation_index", |_, this, (index, should_restart)| { + this.set_animation_index(index, should_restart); + Ok(()) + }); + methods.add_method_mut( + "set_animation", + |_, this, (id, should_restart): (String, _)| { + this.set_animation(&id, should_restart); + Ok(()) + }, + ); + methods.add_method_mut("queue_action", |_, this, value| { + this.queue_action(value); + Ok(()) + }); + methods.add_method_mut("restart", |_, this, ()| { + this.restart(); + Ok(()) + }); + } + fn add_type_methods<'lua, M: tealr::mlu::TealDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) where + Self: 'static + tealr::mlu::MaybeSend, + { + methods.add_function("new",|_,(texture_id, animations,params): (String, Vec, AnimatedSpriteParams)|{ + Ok(AnimatedSprite::new(&texture_id, &animations, params)) + }) + } +} +impl TypeBody for AnimatedSprite { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_fields(gen); + ::add_methods(gen); + } + fn get_type_body_marker(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_type_methods(gen); + } +} + impl AnimatedSprite { pub fn new(texture_id: &str, animations: &[Animation], params: AnimatedSpriteParams) -> Self { let animations = animations.to_vec(); @@ -258,7 +577,8 @@ impl AnimatedSprite { } } -pub fn update_animated_sprites(world: &mut World) { +pub fn update_animated_sprites(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); for (_, drawable) in world.query_mut::<&mut Drawable>() { match drawable.kind.borrow_mut() { DrawableKind::AnimatedSprite(sprite) => { @@ -411,12 +731,112 @@ pub fn debug_draw_one_animated_sprite(position: Vec2, sprite: &AnimatedSprite) { } } -#[derive(Default)] +#[derive(Default, Clone, TypeName)] pub struct AnimatedSpriteSet { pub draw_order: Vec, pub map: HashMap, } +impl UserData for AnimatedSpriteSet { + fn add_fields<'lua, F: hv_lua::UserDataFields<'lua, Self>>(fields: &mut F) { + let mut wrapper = UserDataWrapper::from_user_data_fields(fields); + ::add_fields(&mut wrapper) + } + + fn add_methods<'lua, M: hv_lua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut wrapper) + } +} +impl TealData for AnimatedSpriteSet { + fn add_fields<'lua, F: tealr::mlu::TealDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("draw_order", |_, this| Ok(this.draw_order.clone())); + fields.add_field_method_get("map", |_, this| Ok(this.map.clone())); + } + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("is_empty", |_, this, ()| Ok(this.is_empty())); + methods.add_method("size", |_, this, ()| Ok(Vec2Lua::from(this.size()))); + methods.add_method_mut( + "set_animation", + |_, this, (sprite_id, id, should_restart): (String, String, _)| { + this.set_animation(&sprite_id, &id, should_restart); + Ok(()) + }, + ); + methods.add_method_mut( + "set_animation_index", + |_, this, (sprite_id, index, should_restart): (String, _, _)| { + this.set_animation_index(&sprite_id, index, should_restart); + Ok(()) + }, + ); + methods.add_method_mut( + "set_queued_action", + |_, this, (sprite_id, action): (String, _)| { + this.set_queued_action(&sprite_id, action); + Ok(()) + }, + ); + methods.add_method_mut("set_all", |_, this, (id, should_restart): (String, _)| { + this.set_all(&id, should_restart); + Ok(()) + }); + methods.add_method_mut("set_all_to_index", |_, this, (index, should_restart)| { + this.set_all_to_index(index, should_restart); + Ok(()) + }); + methods.add_method_mut("queue_action_on_all", |_, this, value| { + this.queue_action_on_all(value); + Ok(()) + }); + methods.add_method_mut("restart_all", |_, this, ()| { + this.restart_all(); + Ok(()) + }); + methods.add_method_mut("flip_all_x", |_, this, value| { + this.flip_all_x(value); + Ok(()) + }); + methods.add_method_mut("flip_all_y", |_, this, value| { + this.flip_all_y(value); + Ok(()) + }); + methods.add_method_mut("activate_all", |_, this, ()| { + this.activate_all(); + Ok(()) + }); + methods.add_method_mut("deactivate_all", |_, this, ()| { + this.deactivate_all(); + Ok(()) + }); + methods.add_method_mut("play_all", |_, this, ()| { + this.play_all(); + Ok(()) + }); + methods.add_method_mut("stop_all", |_, this, ()| { + this.stop_all(); + Ok(()) + }); + } + fn add_type_methods<'lua, M: tealr::mlu::TealDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) where + Self: 'static + MaybeSend, + { + methods.add_function("new_default", |_, ()| Ok(Self::default())) + } +} +impl TypeBody for AnimatedSpriteSet { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_fields(gen); + ::add_methods(gen); + } + fn get_type_body_marker(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_type_methods(gen); + } +} impl AnimatedSpriteSet { pub fn is_empty(&self) -> bool { self.draw_order.is_empty() @@ -533,7 +953,7 @@ impl From<&[(&str, AnimatedSprite)]> for AnimatedSpriteSet { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, tealr::TypeName)] pub struct AnimationMetadata { pub id: String, pub row: u32, @@ -545,6 +965,25 @@ pub struct AnimationMetadata { pub is_looping: bool, } +impl TypeBody for AnimationMetadata { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("id").into(), String::get_type_parts())); + gen.fields + .push((Cow::Borrowed("row").into(), u32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("frames").into(), u32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("fps").into(), u32::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("tweens").into(), + Vec::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("is_looping").into(), bool::get_type_parts())); + } +} + impl From for MQAnimation { fn from(a: AnimationMetadata) -> Self { MQAnimation { @@ -556,13 +995,35 @@ impl From for MQAnimation { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, tealr::TypeName)] pub struct TweenMetadata { pub id: String, pub keyframes: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize)] +impl<'lua> FromLua<'lua> for TweenMetadata { + fn from_lua(lua_value: Value<'lua>, lua: &'lua hv_lua::Lua) -> hv_lua::Result { + LuaSerdeExt::from_value(lua, lua_value) + } +} +impl<'lua> ToLua<'lua> for TweenMetadata { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + LuaSerdeExt::to_value(lua, &self) + } +} + +impl TypeBody for TweenMetadata { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("id").into(), String::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("keyframes").into(), + Vec::::get_type_parts(), + )); + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, tealr::TypeName)] pub struct AnimatedSpriteMetadata { #[serde(rename = "texture")] pub texture_id: String, @@ -588,3 +1049,35 @@ pub struct AnimatedSpriteMetadata { #[serde(default)] pub is_deactivated: bool, } +impl TypeBody for AnimatedSpriteMetadata { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("texture_id").into(), String::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("scale").into(), + Option::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("offset").into(), Vec2Lua::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("pivot").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("tint").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("animations").into(), + Vec::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("autoplay_id").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("is_deactivated").into(), + bool::get_type_parts(), + )); + } +} diff --git a/src/drawables/mod.rs b/src/drawables/mod.rs index 4c3388253a..b4dc28da80 100644 --- a/src/drawables/mod.rs +++ b/src/drawables/mod.rs @@ -2,17 +2,27 @@ mod animated_sprite; mod sprite; pub use animated_sprite::*; +use hv_cell::AtomicRefCell; +use hv_lua::{FromLua, ToLua, UserData}; pub use sprite::*; -use std::borrow::{Borrow, BorrowMut}; +use std::{ + borrow::{Borrow, BorrowMut, Cow}, + sync::Arc, +}; +use tealr::{ + mlu::{MaybeSend, TealData, UserDataWrapper}, + TypeBody, TypeName, +}; use macroquad::prelude::*; use hecs::World; -use core::Transform; +use core::{lua::get_table, Transform}; /// This is a wrapper type for all the different types of drawable sprites, used so that we can /// access them all in one query and draw them, ordered, in one pass, according to `draw_order`. +#[derive(Clone, TypeName)] pub struct Drawable { /// This is used to specify draw order on a sprite /// This will be used, primarily, by `Player` to draw equipped items in the right order, relative @@ -23,6 +33,32 @@ pub struct Drawable { pub kind: DrawableKind, } +impl<'lua> FromLua<'lua> for Drawable { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = get_table(lua_value)?; + Ok(Self { + draw_order: table.get("draw_order")?, + kind: table.get("kind")?, + }) + } +} +impl<'lua> ToLua<'lua> for Drawable { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("draw_order", self.draw_order)?; + table.set("kind", self.kind)?; + lua.pack(table) + } +} +impl TypeBody for Drawable { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("draw_order").into(), u32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("kind").into(), DrawableKind::get_type_parts())); + } +} + impl Drawable { pub fn new_sprite(draw_order: u32, texture_id: &str, params: SpriteParams) -> Self { let sprite = Sprite::new(texture_id, params); @@ -122,14 +158,82 @@ impl Drawable { } } +#[derive(Clone, TypeName)] pub enum DrawableKind { Sprite(Sprite), SpriteSet(SpriteSet), AnimatedSprite(AnimatedSprite), AnimatedSpriteSet(AnimatedSpriteSet), } +impl UserData for DrawableKind { + fn add_methods<'lua, M: hv_lua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut wrapper) + } + fn add_type_methods<'lua, M: hv_lua::UserDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) where + Self: 'static + MaybeSend, + { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_type_methods(&mut wrapper) + } +} +impl TealData for DrawableKind { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("try_get_sprite", |_, this, ()| { + if let DrawableKind::Sprite(x) = this { + Ok((true, Some(x.to_owned()))) + } else { + Ok((false, None)) + } + }); + methods.add_method("try_get_sprite_set", |_, this, ()| { + if let DrawableKind::SpriteSet(x) = this { + Ok((true, Some(x.to_owned()))) + } else { + Ok((false, None)) + } + }); + methods.add_method("try_get_animated_sprite", |_, this, ()| { + if let DrawableKind::AnimatedSprite(x) = this { + Ok((true, Some(x.to_owned()))) + } else { + Ok((false, None)) + } + }); + methods.add_method("try_get_animated_sprite_set", |_, this, ()| { + if let DrawableKind::AnimatedSpriteSet(x) = this { + Ok((true, Some(x.to_owned()))) + } else { + Ok((false, None)) + } + }); + } + fn add_type_methods<'lua, M: tealr::mlu::TealDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) where + Self: 'static + tealr::mlu::MaybeSend, + { + methods.add_function("new_sprite", |_, v| Ok(Self::Sprite(v))); + methods.add_function("new_sprite", |_, v| Ok(Self::AnimatedSprite(v))); + methods.add_function("new_sprite", |_, v| Ok(Self::AnimatedSpriteSet(v))); + methods.add_function("new_sprite", |_, v| Ok(Self::SpriteSet(v))); + } +} +impl TypeBody for DrawableKind { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_methods(gen); + } + fn get_type_body_marker(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_type_methods(gen); + } +} -pub fn draw_drawables(world: &mut World) { +pub fn draw_drawables(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); let mut ordered = world .query_mut::<&Drawable>() .into_iter() @@ -165,7 +269,8 @@ pub fn draw_drawables(world: &mut World) { } } -pub fn debug_draw_drawables(world: &mut World) { +pub fn debug_draw_drawables(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); let mut ordered = world .query_mut::<&Drawable>() .into_iter() diff --git a/src/drawables/sprite.rs b/src/drawables/sprite.rs index 222cfc48a9..19cf57fccf 100644 --- a/src/drawables/sprite.rs +++ b/src/drawables/sprite.rs @@ -1,19 +1,24 @@ -use std::collections::HashMap; +use core::lua::get_table; +use core::lua::wrapped_types::{ColorLua, RectLua, Texture2DLua, Vec2Lua}; use std::iter::FromIterator; use std::ops::Div; +use std::{borrow::Cow, collections::HashMap}; +use hv_lua::{FromLua, ToLua, UserData}; use macroquad::color; use macroquad::experimental::collections::storage; use macroquad::prelude::*; use serde::{Deserialize, Serialize}; +use tealr::mlu::{MaybeSend, TealData, UserDataWrapper}; +use tealr::{TypeBody, TypeName}; use core::Transform; use crate::Resources; /// Parameters for `Sprite` component. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, tealr::TypeName)] pub struct SpriteMetadata { /// The id of the texture that will be used #[serde(rename = "texture")] @@ -58,6 +63,37 @@ pub struct SpriteMetadata { pub is_deactivated: bool, } +impl TypeBody for SpriteMetadata { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("texture").into(), String::get_type_parts())); + gen.fields + .push((Cow::Borrowed("index").into(), usize::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("scale").into(), + Option::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("offset").into(), Vec2Lua::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("pivot").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("size").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("tint").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("is_deactivated").into(), + bool::get_type_parts(), + )); + } +} + impl Default for SpriteMetadata { fn default() -> Self { SpriteMetadata { @@ -73,7 +109,7 @@ impl Default for SpriteMetadata { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, TypeName)] pub struct Sprite { pub texture: Texture2D, pub source_rect: Rect, @@ -86,6 +122,67 @@ pub struct Sprite { pub is_deactivated: bool, } +impl UserData for Sprite { + fn add_fields<'lua, F: hv_lua::UserDataFields<'lua, Self>>(fields: &mut F) { + let mut wrapper = UserDataWrapper::from_user_data_fields(fields); + ::add_fields(&mut wrapper) + } + fn add_methods<'lua, M: hv_lua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut wrapper) + } + fn add_type_methods<'lua, M: hv_lua::UserDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) where + Self: 'static + MaybeSend, + { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_type_methods(&mut wrapper) + } +} +impl TealData for Sprite { + fn add_fields<'lua, F: tealr::mlu::TealDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("texture", |_, this| Ok(Texture2DLua::from(this.texture))); + fields.add_field_method_get("source_rect", |_, this| Ok(RectLua::from(this.source_rect))); + fields.add_field_method_get("tint", |_, this| Ok(ColorLua::from(this.tint))); + fields.add_field_method_get("scale", |_, this| Ok(this.scale)); + fields.add_field_method_get("offset", |_, this| Ok(Vec2Lua::from(this.offset))); + fields.add_field_method_get("pivot", |_, this| Ok(this.pivot.map(Vec2Lua::from))); + fields.add_field_method_get("is_flipped_x", |_, this| Ok(this.is_flipped_x)); + fields.add_field_method_get("is_flipped_y", |_, this| Ok(this.is_flipped_y)); + fields.add_field_method_get("is_deactivated", |_, this| Ok(this.is_deactivated)); + } + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("size", |_, this, ()| Ok(Vec2Lua::from(this.size()))); + methods.add_method_mut("set_scale", |_, this, scale| { + this.set_scale(scale); + Ok(()) + }) + } + + fn add_type_methods<'lua, M: tealr::mlu::TealDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) where + Self: 'static + MaybeSend, + { + methods.add_function("new", |_, (texture, params): (String, SpriteParams)| { + Ok(Self::new(&texture, params)) + }) + } +} +impl TypeBody for Sprite { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_fields(gen); + ::add_methods(gen); + } + + fn get_type_body_marker(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_type_methods(gen); + } +} + impl Sprite { pub fn new(texture_id: &str, params: SpriteParams) -> Self { let texture_res = { @@ -146,7 +243,7 @@ impl Sprite { } } -#[derive(Clone)] +#[derive(Clone, TypeName)] pub struct SpriteParams { pub sprite_size: Option, pub index: usize, @@ -160,6 +257,77 @@ pub struct SpriteParams { pub is_deactivated: bool, } +impl<'lua> FromLua<'lua> for SpriteParams { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = get_table(lua_value)?; + Ok(Self { + sprite_size: table + .get::<_, Option>("sprite_size")? + .map(Vec2::from), + index: table.get("index")?, + scale: table.get("scale")?, + offset: table.get::<_, Vec2Lua>("offset")?.into(), + pivot: table.get::<_, Option>("pivot")?.map(Vec2::from), + size: table.get::<_, Option>("size")?.map(Vec2::from), + tint: table.get::<_, Option>("tint")?.map(Color::from), + is_flipped_x: table.get("is_flipped_x")?, + is_flipped_y: table.get("is_flipped_y")?, + is_deactivated: table.get("is_deactivated")?, + }) + } +} +impl<'lua> ToLua<'lua> for SpriteParams { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("sprite_size", self.sprite_size.map(Vec2Lua::from))?; + table.set("index", self.index)?; + table.set("scale", self.scale)?; + table.set("offset", Vec2Lua::from(self.offset))?; + table.set("pivot", self.pivot.map(Vec2Lua::from))?; + table.set("size", self.size.map(Vec2Lua::from))?; + table.set("tint", self.tint.map(ColorLua::from))?; + table.set("is_flipped_x", self.is_flipped_x)?; + table.set("is_flipped_y", self.is_flipped_y)?; + table.set("is_deactivated", self.is_deactivated)?; + lua.pack(table) + } +} + +impl TypeBody for SpriteParams { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields.push(( + Cow::Borrowed("sprite_size").into(), + Option::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("index").into(), usize::get_type_parts())); + gen.fields + .push((Cow::Borrowed("scale").into(), f32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("offset").into(), Vec2Lua::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("pivot").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("size").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("tint").into(), + Option::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("is_flipped_x").into(), bool::get_type_parts())); + gen.fields + .push((Cow::Borrowed("is_flipped_y").into(), bool::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("is_deactivated").into(), + bool::get_type_parts(), + )); + } +} + impl Default for SpriteParams { fn default() -> Self { SpriteParams { @@ -228,7 +396,7 @@ pub fn debug_draw_one_sprite(position: Vec2, sprite: &Sprite) { } } -#[derive(Debug)] +#[derive(Debug, Clone, TypeName)] pub struct SpriteSet { pub draw_order: Vec, pub map: HashMap, @@ -248,6 +416,55 @@ impl From<&[(&str, Sprite)]> for SpriteSet { } } +impl UserData for SpriteSet { + fn add_methods<'lua, M: hv_lua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut wrapper) + } + fn add_type_methods<'lua, M: hv_lua::UserDataMethods<'lua, hv_alchemy::Type>>( + methods: &mut M, + ) where + Self: 'static + MaybeSend, + { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_type_methods(&mut wrapper) + } +} +impl TealData for SpriteSet { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("is_empty", |_, this, ()| Ok(this.is_empty())); + methods.add_method_mut("flip_all_x", |_, this, state| { + this.flip_all_x(state); + Ok(()) + }); + methods.add_method_mut("flip_all_x", |_, this, state| { + this.flip_all_y(state); + Ok(()) + }); + methods.add_method_mut("activate_all", |_, this, ()| { + this.activate_all(); + Ok(()) + }); + methods.add_method_mut("deactivate_all", |_, this, ()| { + this.deactivate_all(); + Ok(()) + }); + } + fn add_type_methods<'lua, M: tealr::mlu::TealDataMethods<'lua, hv_alchemy::Type>>( + _methods: &mut M, + ) where + Self: 'static + MaybeSend, + { + } +} + +impl TypeBody for SpriteSet { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_methods(gen); + } +} + impl SpriteSet { pub fn is_empty(&self) -> bool { self.draw_order.is_empty() diff --git a/src/ecs.rs b/src/ecs.rs index 1be6ae683a..82d688b03f 100644 --- a/src/ecs.rs +++ b/src/ecs.rs @@ -1,10 +1,23 @@ +use std::sync::Arc; + use hecs::{Entity, World}; +use hv_cell::AtomicRefCell; +use hv_lua::UserData; +use tealr::{mlu::TealData, TypeBody, TypeName}; -pub type SystemFn = fn(&mut World); +pub type SystemFn = fn(Arc>); /// This is used as a component to signify ownership +#[derive(Clone, TypeName)] pub struct Owner(pub Entity); +impl UserData for Owner {} +impl TealData for Owner {} +impl TypeBody for Owner { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + } +} /// Placeholder until we implement threading #[derive(Default)] pub struct SchedulerBuilder { @@ -53,9 +66,9 @@ impl Scheduler { SchedulerBuilder::default() } - pub fn execute(&mut self, world: &mut World) { + pub fn execute(&mut self, world: Arc>) { for f in &mut self.steps { - f(world); + f(world.clone()); } } } diff --git a/src/editor/actions.rs b/src/editor/actions.rs index e748d26d8e..62553fb716 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -614,6 +614,7 @@ impl UndoableAction for ImportAction { autotile_mask: tileset.autotile_mask.clone(), tile_attributes: tileset.tile_attributes.clone(), properties: tileset.properties.clone(), + bitmasks: None, }; map.tilesets.insert(tileset.id.clone(), tileset); @@ -776,6 +777,8 @@ impl UndoableAction for UpdateTilesetAction { self.old_autotile_mask = Some(tileset.autotile_mask.clone()); tileset.autotile_mask = self.autotile_mask.clone(); + + tileset.bitmasks = tileset.get_bitmasks(); } else { return Err(Error::new_const( ErrorKind::EditorAction, @@ -804,6 +807,8 @@ impl UndoableAction for UpdateTilesetAction { } else { return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateTilesetAction (Undo): No old autotile mask stored in action. Undo was probably called on an action that was never applied")); } + + tileset.bitmasks = tileset.get_bitmasks(); } else { return Err(Error::new_const( ErrorKind::EditorAction, diff --git a/src/editor/tools/placement.rs b/src/editor/tools/placement.rs index 24cf58ea48..843ea90afa 100644 --- a/src/editor/tools/placement.rs +++ b/src/editor/tools/placement.rs @@ -5,12 +5,14 @@ use super::{EditorAction, EditorContext, EditorTool, EditorToolParams}; use crate::{ editor::EditorCamera, map::{Map, MapLayerKind}, + rand::ChooseRandom, Resources, }; #[derive(Default)] pub struct TilePlacementTool { params: EditorToolParams, + coords: Option, } impl TilePlacementTool { @@ -21,7 +23,10 @@ impl TilePlacementTool { is_continuous: true, }; - TilePlacementTool { params } + TilePlacementTool { + params, + coords: None, + } } } @@ -64,10 +69,58 @@ impl EditorTool for TilePlacementTool { if self.is_available(map, ctx) { if let Some(tileset_id) = &ctx.selected_tileset { - let _tileset = map.tilesets.get(tileset_id).unwrap(); + let cursor_world_position = scene::find_node_by_type::() + .unwrap() + .to_world_space(ctx.cursor_position); + let coords = map.to_coords(cursor_world_position); + + if self.coords != Some(coords) { + let tileset = map.tilesets.get(tileset_id).unwrap(); + + // Do autotile resolution here and set `res` to an `EditorAction::SelectTile` if + // selected tile should be changed according to context. + + //Get self surrounding tiles + let mut surrounding_tiles: Vec = vec![]; + for y in 0..3 { + for x in 0..3 { + if let Some(layer) = &ctx.selected_layer { + let is_some = map + .get_tile(layer, coords.x + x - 1, coords.y + y - 1) + .is_some(); + surrounding_tiles.push(is_some); + } + } + } + + //Get bitmask value from self surrounding tiles + let mut bitmask = 0; + for (i, b) in surrounding_tiles.iter().enumerate() { + if *b && i < 4 { + bitmask += 2_u32.pow(i as u32); + } else if *b && i > 4 { + bitmask += 2_u32.pow(i as u32 - 1); + } + } + + let mut tile_ids = Vec::new(); + if let Some(bitmasks) = &tileset.bitmasks { + for (i, tileset_bitmask) in bitmasks.iter().enumerate() { + if *tileset_bitmask == bitmask && bitmask != 0 { + tile_ids.push(i as u32); + } + } + } + + if let Some(id) = tile_ids.choose() { + res = Some(EditorAction::SelectTile { + tileset_id: tileset_id.to_owned(), + id: *id, + }); + } + } - // Do autotile resolution here and set `res` to an `EditorAction::SelectTile` if - // selected tile should be changed according to context. + self.coords = Some(coords); } } diff --git a/src/effects/active/mod.rs b/src/effects/active/mod.rs index b80d691fe5..c7136be50a 100644 --- a/src/effects/active/mod.rs +++ b/src/effects/active/mod.rs @@ -1,14 +1,19 @@ use hecs::{Entity, World}; +use hv_cell::AtomicRefCell; use macroquad::audio::play_sound_once; use macroquad::color; use macroquad::experimental::collections::storage; use macroquad::prelude::*; +use mlua::{FromLua, ToLua}; use serde::{Deserialize, Serialize}; +use tealr::mlu::TealData; use core::math::{deg_to_rad, rotate_vector, IsZero}; use core::Result; +use std::borrow::Cow; +use std::sync::Arc; use crate::Resources; use crate::{PassiveEffectInstance, PassiveEffectMetadata}; @@ -28,17 +33,25 @@ pub use projectiles::ProjectileKind; const COLLIDER_DEBUG_DRAW_TTL: f32 = 0.5; +use hv_lua as mlua; +use tealr::{MluaTealDerive, TypeBody, TypeName}; +#[derive(Clone, MluaTealDerive)] struct CircleCollider { r: f32, ttl_timer: f32, } +impl TealData for CircleCollider {} + +#[derive(Clone, MluaTealDerive)] struct RectCollider { w: f32, h: f32, ttl_timer: f32, } +impl TealData for RectCollider {} + pub fn spawn_active_effect( world: &mut World, owner: Entity, @@ -197,7 +210,7 @@ pub fn spawn_active_effect( /// This holds all the common parameters, available to all implementations, as well as specialized /// parameters, in the `ActiveEffectKind`. -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize, TypeName)] pub struct ActiveEffectMetadata { /// This holds all the specialized parameters for the effect, dependent on the implementation, /// specified by its variant. It is flattened into this struct in JSON. @@ -218,12 +231,132 @@ pub struct ActiveEffectMetadata { pub delay: f32, } +impl TypeBody for ActiveEffectMetadata { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields.push(( + Cow::Borrowed("kind").into(), + ActiveEffectKind::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("sound_effect_id").into(), + Option::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("delay").into(), f32::get_type_parts())); + } +} + +impl<'lua> FromLua<'lua> for ActiveEffectMetadata { + fn from_lua(lua_value: mlua::Value<'lua>, _: &'lua mlua::Lua) -> mlua::Result { + let table = core::lua::get_table(lua_value)?; + Ok(Self { + kind: Box::new(table.get::<_, ActiveEffectKind>("kind")?), + sound_effect_id: table.get("sound_effect_id")?, + delay: table.get("delay")?, + }) + } +} + +impl<'lua> ToLua<'lua> for ActiveEffectMetadata { + fn to_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { + let table = lua.create_table()?; + table.set("kind", *self.kind)?; + table.set("sound_effect_id", self.sound_effect_id)?; + table.set("delay", self.delay)?; + lua.pack(table) + } +} + +#[derive(Clone, MluaTealDerive)] +pub struct ActiveEffectKindCircleCollider { + radius: f32, + passive_effects: Vec, + is_lethal: bool, + is_explosion: bool, +} +impl TealData for ActiveEffectKindCircleCollider { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("to_active_effect_kind", |_, this, ()| { + Ok(ActiveEffectKind::CircleCollider { + radius: this.radius, + passive_effects: this.passive_effects.to_owned(), + is_lethal: this.is_lethal, + is_explosion: this.is_explosion, + }) + }) + } +} + +#[derive(Clone, MluaTealDerive)] +pub struct ActiveEffectKindRectCollider { + width: f32, + height: f32, + /// If `true` the effect will do damage to any player it hits + is_lethal: bool, + /// This contains any passive effects that will be spawned on collision + passive_effects: Vec, +} + +impl TealData for ActiveEffectKindRectCollider { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("to_active_effect_kind", |_, this, ()| { + Ok(ActiveEffectKind::RectCollider { + width: this.width, + height: this.height, + is_lethal: this.is_lethal, + passive_effects: this.passive_effects.to_owned(), + }) + }) + } +} +#[derive(Clone, MluaTealDerive)] +pub struct ActiveEffectKindTriggeredEffect { + meta: Box, +} +impl TealData for ActiveEffectKindTriggeredEffect { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("to_active_effect_kind", |_, this, ()| { + Ok(ActiveEffectKind::TriggeredEffect { + meta: this.meta.to_owned(), + }) + }) + } +} + +#[derive(Clone, MluaTealDerive)] +pub struct ActiveEffectKindProjectile { + kind: ProjectileKind, + speed: f32, + range: f32, + spread: f32, + /// If `true` the effect will do damage to any player it hits + is_lethal: bool, + /// This contains any passive effects that will be spawned on collision + passive_effects: Vec, + /// Particle effects that will be attached to the projectile + particles: Vec, +} +impl TealData for ActiveEffectKindProjectile { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("to_active_effect_kind", |_, this, ()| { + Ok(ActiveEffectKind::Projectile { + kind: this.kind.to_owned(), + speed: this.speed, + range: this.range, + spread: this.spread, + is_lethal: this.is_lethal, + passive_effects: this.passive_effects.to_owned(), + particles: this.particles.to_owned(), + }) + }) + } +} /// This should hold implementations of the commonly used weapon effects, that see usage spanning /// many different weapon implementations. /// /// The effects that have the `Collider` suffix denote effects that do an immediate collider check, /// upon attack, using the weapons `effect_offset` as origin. -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize, MluaTealDerive)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ActiveEffectKind { /// Check for hits with a `Circle` collider. @@ -284,7 +417,94 @@ pub enum ActiveEffectKind { }, } -pub fn debug_draw_active_effects(world: &mut World) { +impl TealData for ActiveEffectKind { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("try_get_circle_collider", |_, this, ()| { + if let ActiveEffectKind::CircleCollider { + radius, + passive_effects, + is_lethal, + is_explosion, + } = this + { + Ok(( + true, + Some(ActiveEffectKindCircleCollider { + radius: *radius, + passive_effects: passive_effects.to_owned(), + is_lethal: *is_lethal, + is_explosion: *is_explosion, + }), + )) + } else { + Ok((false, None)) + } + }); + methods.add_method("try_get_rect_collider", |_, this, ()| { + if let ActiveEffectKind::RectCollider { + width, + height, + is_lethal, + passive_effects, + } = this + { + Ok(( + true, + Some(ActiveEffectKindRectCollider { + width: *width, + height: *height, + is_lethal: *is_lethal, + passive_effects: passive_effects.to_owned(), + }), + )) + } else { + Ok((false, None)) + } + }); + methods.add_method("try_get_triggered_effect", |_, this, ()| { + if let ActiveEffectKind::TriggeredEffect { meta } = this { + Ok(( + true, + Some(ActiveEffectKindTriggeredEffect { + meta: meta.to_owned(), + }), + )) + } else { + Ok((false, None)) + } + }); + methods.add_method("try_get_triggered_projectile", |_, this, ()| { + if let ActiveEffectKind::Projectile { + kind, + speed, + range, + spread, + is_lethal, + passive_effects, + particles, + } = this + { + Ok(( + true, + Some(ActiveEffectKindProjectile { + kind: kind.to_owned(), + speed: *speed, + range: *range, + spread: *spread, + is_lethal: *is_lethal, + passive_effects: passive_effects.to_owned(), + particles: particles.to_owned(), + }), + )) + } else { + Ok((false, None)) + } + }) + } +} + +pub fn debug_draw_active_effects(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); let mut to_remove = Vec::new(); let dt = get_frame_time(); diff --git a/src/effects/active/projectiles.rs b/src/effects/active/projectiles.rs index dcbd69e46c..6ad734390c 100644 --- a/src/effects/active/projectiles.rs +++ b/src/effects/active/projectiles.rs @@ -1,6 +1,14 @@ +use core::lua::get_table; +use core::lua::wrapped_types::{ColorLua, Vec2Lua}; +use hv_cell::AtomicRefCell; use macroquad::experimental::collections::storage; use macroquad::prelude::*; +use mlua::{FromLua, ToLua}; +use std::borrow::Cow; use std::f32::consts::PI; +use std::sync::Arc; +use tealr::mlu::TealData; +use tealr::{MluaTealDerive, TypeBody, TypeName}; use hecs::{Entity, World}; use macroquad_platformer::Tile; @@ -17,7 +25,58 @@ use core::Transform; const PROJECTILE_DRAW_ORDER: u32 = 1; -#[derive(Debug, Clone, Serialize, Deserialize)] +use hv_lua as mlua; + +#[derive(Clone, MluaTealDerive)] +pub struct Circle { + radius: f32, + color: ColorLua, +} +impl TealData for Circle { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("to_projectile_kind", |_, this, ()| { + Ok(ProjectileKind::Circle { + radius: this.radius, + color: this.color.to_owned().into(), + }) + }) + } +} + +#[derive(Clone, MluaTealDerive)] +pub struct Rectangle { + width: f32, + height: f32, + color: ColorLua, +} +impl TealData for Rectangle { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("to_projectile_kind", |_, this, ()| { + Ok(ProjectileKind::Rect { + width: this.width, + height: this.height, + color: this.color.to_owned().into(), + }) + }) + } +} +#[derive(Clone, MluaTealDerive)] +pub struct SpriteProjectile { + params: SpriteMetadata, + can_rotate: bool, +} +impl TealData for SpriteProjectile { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("to_projectile_kind", |_, this, ()| { + Ok(ProjectileKind::Sprite { + params: this.params.to_owned(), + can_rotate: this.can_rotate, + }) + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, MluaTealDerive)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ProjectileKind { Circle { @@ -40,7 +99,57 @@ pub enum ProjectileKind { can_rotate: bool, }, } +impl TealData for ProjectileKind { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("try_get_circle", |_, this, ()| { + if let ProjectileKind::Circle { radius, color } = this { + Ok(( + true, + Some(Circle { + radius: radius.to_owned(), + color: color.to_owned().into(), + }), + )) + } else { + Ok((false, None)) + } + }); + methods.add_method("try_get_rect", |_, this, ()| { + if let ProjectileKind::Rect { + width, + height, + color, + } = this + { + Ok(( + true, + Some(Rectangle { + width: *width, + height: *height, + color: color.to_owned().into(), + }), + )) + } else { + Ok((false, None)) + } + }); + methods.add_method("try_get_sprite", |_, this, ()| { + if let ProjectileKind::Sprite { can_rotate, params } = this { + Ok(( + true, + Some(SpriteProjectile { + can_rotate: *can_rotate, + params: params.to_owned(), + }), + )) + } else { + Ok((false, None)) + } + }); + } +} +#[derive(Clone, TypeName)] pub struct Projectile { pub kind: ProjectileKind, pub owner: Entity, @@ -50,6 +159,53 @@ pub struct Projectile { pub passive_effects: Vec, } +impl<'lua> FromLua<'lua> for Projectile { + fn from_lua(lua_value: mlua::Value<'lua>, _: &'lua mlua::Lua) -> mlua::Result { + let table = get_table(lua_value)?; + Ok(Self { + kind: table.get("kind")?, + owner: table.get("owner")?, + origin: table.get::<_, Vec2Lua>("origin")?.into(), + range: table.get("range")?, + is_lethal: table.get("is_lethal")?, + passive_effects: table.get("passive_effects")?, + }) + } +} +impl<'lua> ToLua<'lua> for Projectile { + fn to_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { + let table = lua.create_table()?; + table.set("kind", self.kind)?; + table.set("owner", self.owner)?; + table.set("origin", Vec2Lua::from(self.origin))?; + table.set("range", self.range)?; + table.set("is_lethal", self.is_lethal)?; + table.set("passive_effects", self.passive_effects)?; + lua.pack(table) + } +} + +impl TypeBody for Projectile { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields.push(( + Cow::Borrowed("kind").into(), + ProjectileKind::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("owner").into(), Entity::get_type_parts())); + gen.fields + .push((Cow::Borrowed("origin").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("range").into(), f32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("is_lethal").into(), bool::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("passive_effects").into(), + Vec::::get_type_parts(), + )); + } +} + impl Projectile { pub fn new( owner: Entity, @@ -203,7 +359,8 @@ enum ProjectileCollision { Map, } -pub fn fixed_update_projectiles(world: &mut World) { +pub fn fixed_update_projectiles(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); let bodies = world .query::<(&Transform, &PhysicsBody)>() .iter() @@ -272,7 +429,7 @@ pub fn fixed_update_projectiles(world: &mut World) { if let Some(collision_kind) = collision { match collision_kind { ProjectileCollision::Player(damage_to_entity) => { - on_player_damage(world, damage_from_entity, damage_to_entity); + on_player_damage(&mut world, damage_from_entity, damage_to_entity); } ProjectileCollision::Trigger(trigger_entity) => { let mut effect = world.get_mut::(trigger_entity).unwrap(); diff --git a/src/effects/active/triggered.rs b/src/effects/active/triggered.rs index c934e08067..3cb8433dd1 100644 --- a/src/effects/active/triggered.rs +++ b/src/effects/active/triggered.rs @@ -1,12 +1,18 @@ +use hv_cell::AtomicRefCell; +use hv_lua::{FromLua, ToLua}; use macroquad::experimental::collections::storage; use macroquad::prelude::*; use hecs::{Entity, World}; use serde::{Deserialize, Serialize}; +use tealr::{TypeBody, TypeName}; +use core::lua::get_table; use core::math::deg_to_rad; use core::{Result, Transform}; +use std::borrow::Cow; +use std::sync::Arc; use crate::effects::active::spawn_active_effect; use crate::particles::{ParticleEmitter, ParticleEmitterMetadata}; @@ -18,7 +24,7 @@ use crate::{Drawable, PhysicsBodyParams}; const TRIGGERED_EFFECT_DRAW_ORDER: u32 = 5; /// The various collision types that can trigger a `TriggeredEffect`. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, TypeName)] #[serde(rename_all = "snake_case")] pub enum TriggeredEffectTrigger { /// The player that deployed the effect @@ -32,7 +38,21 @@ pub enum TriggeredEffectTrigger { /// Projectile hit Projectile, } +impl<'lua> FromLua<'lua> for TriggeredEffectTrigger { + fn from_lua(lua_value: hv_lua::Value<'lua>, lua: &'lua hv_lua::Lua) -> hv_lua::Result { + hv_lua::LuaSerdeExt::from_value(lua, lua_value) + } +} +impl<'lua> ToLua<'lua> for TriggeredEffectTrigger { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + hv_lua::LuaSerdeExt::to_value(lua, &self) + } +} +impl TypeBody for TriggeredEffectTrigger { + fn get_type_body(_: &mut tealr::TypeGenerator) {} +} +#[derive(Clone, TypeName)] pub struct TriggeredEffect { pub owner: Entity, pub trigger: Vec, @@ -54,6 +74,111 @@ pub struct TriggeredEffect { pub timed_trigger_timer: f32, } +impl<'lua> FromLua<'lua> for TriggeredEffect { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = get_table(lua_value)?; + Ok(Self { + owner: table.get("owner")?, + trigger: table.get("trigger")?, + effects: table.get("effects")?, + activation_delay: table.get("activation_delay")?, + trigger_delay: table.get("trigger_delay")?, + timed_trigger: table.get("timed_trigger")?, + is_kickable: table.get("is_kickable")?, + should_override_delay: table.get("should_override_delay")?, + should_collide_with_platforms: table.get("should_collide_with_platforms")?, + is_triggered: table.get("is_triggered")?, + triggered_by: table.get("triggered_by")?, + kick_delay_timer: table.get("kick_delay_timer")?, + activation_timer: table.get("activation_timer")?, + trigger_delay_timer: table.get("trigger_delay_timer")?, + timed_trigger_timer: table.get("timed_trigger_timer")?, + }) + } +} + +impl<'lua> ToLua<'lua> for TriggeredEffect { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("owner", self.owner)?; + table.set("trigger", self.trigger)?; + table.set("effects", self.effects)?; + table.set("activation_delay", self.activation_delay)?; + table.set("trigger_delay", self.trigger_delay)?; + table.set("timed_trigger", self.timed_trigger)?; + table.set("is_kickable", self.is_kickable)?; + table.set("should_override_delay", self.should_override_delay)?; + table.set( + "should_collide_with_platforms", + self.should_collide_with_platforms, + )?; + table.set("is_triggered", self.is_triggered)?; + table.set("triggered_by", self.triggered_by)?; + table.set("kick_delay_timer", self.kick_delay_timer)?; + table.set("activation_timer", self.activation_timer)?; + table.set("trigger_delay_timer", self.trigger_delay_timer)?; + table.set("timed_trigger_timer", self.timed_trigger_timer)?; + lua.pack(table) + } +} + +impl TypeBody for TriggeredEffect { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("owner").into(), Entity::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("trigger").into(), + Vec::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("effects").into(), + Vec::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("activation_delay").into(), + f32::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("trigger_delay").into(), f32::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("timed_trigger").into(), + Option::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("is_kickable").into(), bool::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("should_override_delay").into(), + bool::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("should_collide_with_platforms").into(), + bool::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("is_triggered").into(), bool::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("triggered_by").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("kick_delay_timer").into(), + f32::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("activation_timer").into(), + f32::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("trigger_delay_timer").into(), + f32::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("timed_trigger_timer").into(), + f32::get_type_parts(), + )); + } +} + impl TriggeredEffect { pub fn new(owner: Entity, meta: TriggeredEffectMetadata) -> Self { TriggeredEffect { @@ -156,7 +281,8 @@ pub fn spawn_triggered_effect( const KICK_FORCE: f32 = 15.0; const KICK_DELAY: f32 = 0.22; -pub fn fixed_update_triggered_effects(world: &mut World) { +pub fn fixed_update_triggered_effects(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); let dt = get_frame_time(); let mut to_trigger = Vec::new(); @@ -266,7 +392,7 @@ pub fn fixed_update_triggered_effects(world: &mut World) { for (e, _, owner, origin, effects) in to_trigger.drain(0..) { for params in effects { - if let Err(err) = spawn_active_effect(world, owner, origin, params) { + if let Err(err) = spawn_active_effect(&mut world, owner, origin, params) { #[cfg(debug_assertions)] println!("WARNING: {}", err); } diff --git a/src/effects/passive/mod.rs b/src/effects/passive/mod.rs index 1efed2b452..7614236928 100644 --- a/src/effects/passive/mod.rs +++ b/src/effects/passive/mod.rs @@ -1,10 +1,16 @@ +use std::borrow::Cow; use std::collections::HashMap; +use std::sync::Arc; +use hv_cell::AtomicRefCell; +use hv_lua::{FromLua, Function, RegistryKey, Table, ToLua, UserData}; use macroquad::prelude::*; use serde::{Deserialize, Serialize}; use hecs::{Entity, World}; +use tealr::mlu::{TealData, TypedFunction, UserDataWrapper}; +use tealr::{TypeBody, TypeName}; mod turtle_shell; @@ -33,6 +39,108 @@ pub fn get_passive_effect(id: &str) -> &PassiveEffectFn { pub type PassiveEffectFn = fn(world: &mut World, player_entity: Entity, item_entity: Option, event: PlayerEvent); +#[derive(Clone)] +pub enum PassiveEffectFnContainer { + SimpleFn(PassiveEffectFn), + ///we are going to keep the functions inside the lua vm. So, this is just a way to find the function back + LuaFnPointer(Arc), +} + +impl From for PassiveEffectFnContainer { + fn from(value: PassiveEffectFn) -> Self { + Self::SimpleFn(value) + } +} + +impl PassiveEffectFnContainer { + pub fn call_get_lua( + &self, + world: Arc>, + player_entity: Entity, + item_entity: Option, + event: PlayerEvent, + ) -> hv_lua::Result<()> { + match self { + PassiveEffectFnContainer::SimpleFn(x) => { + x(&mut world.borrow_mut(), player_entity, item_entity, event); + Ok(()) + } + PassiveEffectFnContainer::LuaFnPointer(_) => { + todo!("Tried calling a PassiveEffectFnContainer::LuaFnPointer. This should not yet be possible") + } + } + } + pub fn call( + &self, + lua: &hv_lua::Lua, + world: Arc>, + player_entity: Entity, + item_entity: Option, + event: PlayerEvent, + ) -> hv_lua::Result<()> { + match self { + PassiveEffectFnContainer::SimpleFn(x) => { + x(&mut world.borrow_mut(), player_entity, item_entity, event); + Ok(()) + } + PassiveEffectFnContainer::LuaFnPointer(key) => { + let data = lua.registry_value::(key)?; + let globals = lua.globals(); + let old_require = globals.get::<_, Option>("require")?; + let new_require = data.get::<_, Function>("require")?; + let function = data.get::<_, TypedFunction< + ( + Arc>, + Entity, + Option, + PlayerEvent, + ), + (), + >>("function")?; + globals.set("require", new_require)?; + let res = function.call((world, player_entity, item_entity, event)); + //if this fails then the `require` function isn't the same one as which we started with + //this could be a problem, however I think it is unlikely to happen as this value comes from the lua instance already. + //Also, if the value of `old_require` is None then this doesn't matter. + globals.set("require", old_require)?; + res + } + } + } +} + +impl TypeName for PassiveEffectFnContainer { + fn get_type_parts() -> Cow<'static, [tealr::NamePart]> { + //this is a lie + //however, I want to make it so the user doesn't need to know about this thing not being a function + //right now, it already acts like one as you can just call it on the lua side + //once https://github.com/sdleffler/hv-lua/pull/2 is merged, it should also be possible that it can be created by a normal function + //this should make it even more similar. + + //after that, the only thing that should be different is how it converts to a string, and the result of the typeof function + //both of those don't have a good solution, but only the `typeof` thing might be relevant. + tealr::mlu::TypedFunction::<(World,Entity,Option,PlayerEvent),()>::get_type_parts() + } +} + +impl UserData for PassiveEffectFnContainer { + fn add_methods<'lua, M: hv_lua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut wrapper) + } +} + +impl TealData for PassiveEffectFnContainer { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_meta_method( + tealr::mlu::mlua::MetaMethod::Call, + |lua, this, (world, player_entity, item_entity, event)| { + this.call(lua, world, player_entity, item_entity, event) + }, + ) + } +} + pub fn init_passive_effects() { let effects = unsafe { get_passive_effects_map() }; @@ -42,9 +150,10 @@ pub fn init_passive_effects() { ); } +#[derive(Clone, TypeName)] pub struct PassiveEffectInstance { pub name: String, - pub function: Option, + pub function: Option, pub activated_on: Vec, pub particle_effect_id: Option, pub event_particle_effect_id: Option, @@ -56,9 +165,98 @@ pub struct PassiveEffectInstance { pub duration_timer: f32, } +impl UserData for PassiveEffectInstance { + fn add_methods<'lua, M: hv_lua::UserDataMethods<'lua, Self>>(methods: &mut M) { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut wrapper) + } + fn add_fields<'lua, F: hv_lua::UserDataFields<'lua, Self>>(fields: &mut F) { + let mut wrapper = UserDataWrapper::from_user_data_fields(fields); + ::add_fields(&mut wrapper) + } +} +impl TealData for PassiveEffectInstance { + fn add_fields<'lua, F: tealr::mlu::TealDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("name", |_, this| Ok(this.name.to_owned())); + fields.add_field_method_get("function", |_, this| Ok(this.function.to_owned())); + fields.add_field_method_get("activated_on", |_, this| Ok(this.activated_on.to_owned())); + fields.add_field_method_get("particle_effect_id", |_, this| { + Ok(this.particle_effect_id.to_owned()) + }); + fields.add_field_method_get("event_particle_effect_id", |_, this| { + Ok(this.event_particle_effect_id.to_owned()) + }); + fields.add_field_method_get("blocks_damage", |_, this| Ok(this.blocks_damage)); + fields.add_field_method_get("uses", |_, this| Ok(this.uses)); + fields.add_field_method_get("item", |_, this| Ok(this.item)); + fields.add_field_method_get("use_cnt", |_, this| Ok(this.use_cnt)); + fields.add_field_method_get("duration", |_, this| Ok(this.duration)); + fields.add_field_method_get("duration_timer", |_, this| Ok(this.duration_timer)); + fields.add_field_method_set("name", |_, this, value| { + this.name = value; + Ok(()) + }); + fields.add_field_method_set("function", |_, this, value| { + this.function = value; + Ok(()) + }); + fields.add_field_method_set("activated_on", |_, this, value| { + this.activated_on = value; + Ok(()) + }); + fields.add_field_method_set("particle_effect_id", |_, this, value| { + this.particle_effect_id = value; + Ok(()) + }); + fields.add_field_method_set("event_particle_effect_id", |_, this, value| { + this.event_particle_effect_id = value; + Ok(()) + }); + fields.add_field_method_set("blocks_damage", |_, this, value| { + this.blocks_damage = value; + Ok(()) + }); + fields.add_field_method_set("uses", |_, this, value| { + this.uses = value; + Ok(()) + }); + fields.add_field_method_set("item", |_, this, value| { + this.item = value; + Ok(()) + }); + fields.add_field_method_set("use_cnt", |_, this, value| { + this.use_cnt = value; + Ok(()) + }); + fields.add_field_method_set("duration", |_, this, value| { + this.duration = value; + Ok(()) + }); + fields.add_field_method_set("duration_timer", |_, this, value| { + this.duration_timer = value; + Ok(()) + }); + } + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method_mut("update", |_, this, dt| { + this.update(dt); + Ok(()) + }); + methods.add_method("is_depleted", |_, this, ()| Ok(this.is_depleted())); + } +} + +impl TypeBody for PassiveEffectInstance { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_fields(gen); + ::add_methods(gen); + } +} + impl PassiveEffectInstance { pub fn new(item: Option, meta: PassiveEffectMetadata) -> Self { - let function = meta.function_id.map(|id| *get_passive_effect(&id)); + let function = meta.function_id.map(|id| (*get_passive_effect(&id)).into()); PassiveEffectInstance { name: meta.name, @@ -95,8 +293,7 @@ impl PassiveEffectInstance { false } } - -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize, TypeName)] pub struct PassiveEffectMetadata { pub name: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -128,3 +325,50 @@ pub struct PassiveEffectMetadata { #[serde(default, skip_serializing_if = "Option::is_none")] pub duration: Option, } + +impl<'lua> FromLua<'lua> for PassiveEffectMetadata { + fn from_lua(lua_value: hv_lua::Value<'lua>, lua: &'lua hv_lua::Lua) -> hv_lua::Result { + hv_lua::LuaSerdeExt::from_value(lua, lua_value) + } +} + +impl<'lua> ToLua<'lua> for PassiveEffectMetadata { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + hv_lua::LuaSerdeExt::to_value(lua, &self) + } +} + +impl TypeBody for PassiveEffectMetadata { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("name").into(), String::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("function_id").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("activated_on").into(), + Vec::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("particle_effect").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("event_particle_effect").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("blocks_damage").into(), + bool::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("uses").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("duration").into(), + Option::::get_type_parts(), + )); + } +} diff --git a/src/game/mod.rs b/src/game/mod.rs index 5d0956d4a0..c819745d81 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -5,6 +5,7 @@ pub use camera::GameCamera; use fishsticks::{Button, GamepadContext}; +use hv_cell::AtomicRefCell; use macroquad::experimental::collections::storage; use macroquad::experimental::scene::{Node, RefMut}; use macroquad::prelude::*; @@ -14,10 +15,12 @@ use hecs::{Entity, World}; use core::input::is_gamepad_btn_pressed; use core::Result; +use std::sync::Arc; use crate::debug; use crate::ecs::Scheduler; use crate::gui::{self, GAME_MENU_RESULT_MAIN_MENU, GAME_MENU_RESULT_QUIT}; +use crate::lua::run_event; use crate::physics::{debug_draw_physics_bodies, fixed_update_physics_bodies}; use crate::player::{ draw_weapons_hud, spawn_player, update_player_animations, update_player_camera_box, @@ -50,7 +53,7 @@ pub enum GameMode { } pub struct Game { - world: World, + world: Arc>, #[allow(dead_code)] players: Vec, updates: Scheduler, @@ -63,7 +66,6 @@ pub struct Game { impl Game { pub fn new(mode: GameMode, map: Map, player_params: &[PlayerParams]) -> Result { let mut world = World::default(); - { let camera = GameCamera::new(map.get_size()); storage::store(camera); @@ -149,7 +151,8 @@ impl Game { .with_thread_local(debug_draw_rigid_bodies) .with_thread_local(debug_draw_active_effects) .build(); - + let world = Arc::new(AtomicRefCell::new(world)); + let _ = run_event("init", world.clone()); let res = Game { world, players, @@ -164,7 +167,7 @@ impl Game { } fn on_update(&mut self) { - self.updates.execute(&mut self.world); + self.updates.execute(self.world.clone()); #[cfg(debug_assertions)] if is_key_pressed(macroquad::prelude::KeyCode::U) { @@ -182,7 +185,7 @@ impl Game { } fn on_fixed_update(&mut self) { - self.fixed_updates.execute(&mut self.world); + self.fixed_updates.execute(self.world.clone()); } fn on_draw(&mut self) { @@ -194,11 +197,11 @@ impl Game { map.draw(None, true); } - self.draws.execute(&mut self.world); + self.draws.execute(self.world.clone()); #[cfg(debug_assertions)] if debug::is_debug_draw_enabled() { - self.debug_draws.execute(&mut self.world); + self.debug_draws.execute(self.world.clone()); } if gui::is_game_menu_open() { diff --git a/src/items.rs b/src/items.rs index 1ec8200205..e2ceffa4ed 100644 --- a/src/items.rs +++ b/src/items.rs @@ -2,18 +2,23 @@ //! Proto-mods, eventually some of the items will move to some sort of a wasm runtime use hecs::{Entity, World}; +use hv_lua::{FromLua, ToLua}; use macroquad::audio::{play_sound_once, Sound}; use macroquad::experimental::collections::storage; use macroquad::prelude::*; use serde::{Deserialize, Serialize}; +use tealr::{TypeBody, TypeName}; use crate::{ ActiveEffectMetadata, AnimatedSprite, AnimatedSpriteMetadata, CollisionWorld, Drawable, PassiveEffectMetadata, PhysicsBody, QueuedAnimationAction, Resources, }; +use core::lua::get_table; +use core::lua::wrapped_types::{SoundLua, Vec2Lua}; use core::{Result, Transform}; +use std::borrow::Cow; use crate::effects::active::spawn_active_effect; use crate::particles::{ParticleEmitter, ParticleEmitterMetadata}; @@ -29,7 +34,7 @@ pub const GROUND_ANIMATION_ID: &str = "ground"; pub const ATTACK_ANIMATION_ID: &str = "attack"; /// This dictates what happens to an item when it is dropped, either manually or on death. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, TypeName)] #[serde(rename_all = "snake_case")] pub enum ItemDropBehavior { /// Clear all state and restore default parameters @@ -40,6 +45,21 @@ pub enum ItemDropBehavior { Destroy, } +impl<'lua> FromLua<'lua> for ItemDropBehavior { + fn from_lua(lua_value: hv_lua::Value<'lua>, lua: &'lua hv_lua::Lua) -> hv_lua::Result { + hv_lua::LuaSerdeExt::from_value(lua, lua_value) + } +} + +impl<'lua> ToLua<'lua> for ItemDropBehavior { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + hv_lua::LuaSerdeExt::to_value(lua, &self) + } +} +impl TypeBody for ItemDropBehavior { + fn get_type_body(_: &mut tealr::TypeGenerator) {} +} + impl Default for ItemDropBehavior { fn default() -> Self { ItemDropBehavior::ClearState @@ -48,7 +68,7 @@ impl Default for ItemDropBehavior { /// This dictates what happens to an item when it is depleted, either by exceeding its duration, /// in the case of `Equipment`, or by depleting `uses`, if specified, in the case of a `Weapon` -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, TypeName)] #[serde(rename_all = "snake_case")] pub enum ItemDepleteBehavior { /// Keep the item on depletion (do nothing) @@ -59,6 +79,21 @@ pub enum ItemDepleteBehavior { Destroy, } +impl<'lua> FromLua<'lua> for ItemDepleteBehavior { + fn from_lua(lua_value: hv_lua::Value<'lua>, lua: &'lua hv_lua::Lua) -> hv_lua::Result { + hv_lua::LuaSerdeExt::from_value(lua, lua_value) + } +} + +impl<'lua> ToLua<'lua> for ItemDepleteBehavior { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + hv_lua::LuaSerdeExt::to_value(lua, &self) + } +} +impl TypeBody for ItemDepleteBehavior { + fn get_type_body(_: &mut tealr::TypeGenerator) {} +} + impl Default for ItemDepleteBehavior { fn default() -> Self { ItemDepleteBehavior::Keep @@ -89,6 +124,7 @@ pub struct ItemParams { pub is_hat: bool, } +#[derive(Clone, TypeName)] pub struct Item { pub id: String, pub name: String, @@ -103,6 +139,84 @@ pub struct Item { pub use_cnt: u32, } +impl<'lua> FromLua<'lua> for Item { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = get_table(lua_value)?; + Ok(Self { + id: table.get("id")?, + name: table.get("name")?, + effects: table.get("effects")?, + uses: table.get("uses")?, + duration: table.get("duration")?, + mount_offset: table.get::<_, Vec2Lua>("mount_offset")?.into(), + drop_behavior: table.get("drop_behavior")?, + deplete_behavior: table.get("deplete_behavior")?, + is_hat: table.get("is_hat")?, + duration_timer: table.get("duration_timer")?, + use_cnt: table.get("use_cnt")?, + }) + } +} + +impl<'lua> ToLua<'lua> for Item { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("id", self.id)?; + table.set("name", self.name)?; + table.set("effects", self.effects)?; + table.set("uses", self.uses)?; + table.set("duration", self.duration)?; + table.set("mount_offset", Vec2Lua::from(self.mount_offset))?; + table.set("drop_behavior", self.drop_behavior)?; + table.set("deplete_behavior", self.deplete_behavior)?; + table.set("is_hat", self.is_hat)?; + table.set("duration_timer", self.duration_timer)?; + table.set("use_cnt", self.use_cnt)?; + lua.pack(table) + } +} + +impl TypeBody for Item { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("id").into(), String::get_type_parts())); + gen.fields + .push((Cow::Borrowed("name").into(), String::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("effects").into(), + Vec::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("uses").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("duration").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("mount_offset").into(), + Vec2Lua::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("drop_behavior").into(), + ItemDropBehavior::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("deplete_behavior").into(), + ItemDepleteBehavior::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("is_hat").into(), bool::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("duration_timer").into(), + f32::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("use_cnt").into(), u32::get_type_parts())); + } +} + impl Item { pub fn new(id: &str, params: ItemParams) -> Self { Item { @@ -122,7 +236,7 @@ impl Item { } /// This holds the parameters used when constructing an `Equipment` -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize, TypeName)] pub struct ItemMetadata { /// The effects that will be instantiated when the item is equipped #[serde(default)] @@ -136,6 +250,32 @@ pub struct ItemMetadata { pub is_hat: bool, } +impl<'lua> FromLua<'lua> for ItemMetadata { + fn from_lua(lua_value: hv_lua::Value<'lua>, lua: &'lua hv_lua::Lua) -> hv_lua::Result { + hv_lua::LuaSerdeExt::from_value(lua, lua_value) + } +} +impl<'lua> ToLua<'lua> for ItemMetadata { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + hv_lua::LuaSerdeExt::to_value(lua, &self) + } +} + +impl TypeBody for ItemMetadata { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields.push(( + Cow::Borrowed("effects").into(), + Vec::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("duration").into(), + Option::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("is_hat").into(), bool::get_type_parts())); + } +} + #[derive(Clone, Serialize, Deserialize)] pub struct MapItemMetadata { pub id: String, @@ -340,6 +480,7 @@ impl Default for WeaponParams { } } +#[derive(Clone, TypeName)] pub struct Weapon { pub id: String, pub name: String, @@ -357,6 +498,102 @@ pub struct Weapon { pub use_cnt: u32, } +impl<'lua> FromLua<'lua> for Weapon { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = get_table(lua_value)?; + Ok(Self { + id: table.get("id")?, + name: table.get("name")?, + effects: table.get("effects")?, + sound_effect: table + .get::<_, Option>("sound_effect")? + .map(From::from), + recoil: table.get("recoil")?, + cooldown: table.get("cooldown")?, + attack_duration: table.get("attack_duration")?, + uses: table.get("uses")?, + mount_offset: table.get::<_, Vec2Lua>("mount_offset")?.into(), + effect_offset: table.get::<_, Vec2Lua>("effect_offset")?.into(), + drop_behavior: table.get("drop_behavior")?, + deplete_behavior: table.get("deplete_behavior")?, + cooldown_timer: table.get("cooldown_timer")?, + use_cnt: table.get("use_cnt")?, + }) + } +} + +impl<'lua> ToLua<'lua> for Weapon { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("id", self.id)?; + table.set("name", self.name)?; + table.set("effects", self.effects)?; + table.set("sound_effect", self.sound_effect.map(SoundLua::from))?; + table.set("recoil", self.recoil)?; + table.set("cooldown", self.cooldown)?; + table.set("attack_duration", self.attack_duration)?; + table.set("uses", self.uses)?; + table.set("mount_offset", Vec2Lua::from(self.mount_offset))?; + table.set("effect_offset", Vec2Lua::from(self.effect_offset))?; + table.set("drop_behavior", self.drop_behavior)?; + table.set("deplete_behavior", self.deplete_behavior)?; + table.set("cooldown_timer", self.cooldown_timer)?; + table.set("use_cnt", self.use_cnt)?; + lua.pack(table) + } +} + +impl TypeBody for Weapon { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("id").into(), String::get_type_parts())); + gen.fields + .push((Cow::Borrowed("name").into(), String::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("effects").into(), + Vec::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("sound_effect").into(), + Option::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("recoil").into(), f32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("cooldown").into(), f32::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("attack_duration").into(), + f32::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("uses").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("mount_offset").into(), + Vec2Lua::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("effect_offset").into(), + Vec2Lua::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("drop_behavior").into(), + ItemDropBehavior::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("deplete_behavior").into(), + ItemDepleteBehavior::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("cooldown_timer").into(), + f32::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("use_cnt").into(), u32::get_type_parts())); + } +} + impl Weapon { pub fn new( id: &str, @@ -540,6 +777,55 @@ pub struct WeaponMetadata { pub effect_sprite: Option, } +impl<'lua> FromLua<'lua> for WeaponMetadata { + fn from_lua(lua_value: hv_lua::Value<'lua>, lua: &'lua hv_lua::Lua) -> hv_lua::Result { + hv_lua::LuaSerdeExt::from_value(lua, lua_value) + } +} + +impl<'lua> ToLua<'lua> for WeaponMetadata { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + hv_lua::LuaSerdeExt::to_value(lua, &self) + } +} + +impl TypeBody for WeaponMetadata { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields.push(( + Cow::Borrowed("effects").into(), + Vec::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("particles").into(), + Vec::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("sound_effect_id").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("effect_offset").into(), + Vec2Lua::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("uses").into(), + Option::::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("cooldown").into(), f32::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("attack_duration").into(), + f32::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("recoil").into(), f32::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("effect_sprite").into(), + Option::::get_type_parts(), + )); + } +} + impl Default for WeaponMetadata { fn default() -> Self { WeaponMetadata { diff --git a/src/json/map/tiled.rs b/src/json/map/tiled.rs index 9ea3561cde..cb6f114718 100644 --- a/src/json/map/tiled.rs +++ b/src/json/map/tiled.rs @@ -192,6 +192,7 @@ impl TiledMap { autotile_mask, tile_attributes, properties, + bitmasks: None, }; tilesets.insert(tiled_tileset.name, tileset); diff --git a/src/lua.rs b/src/lua.rs new file mode 100644 index 0000000000..dd99863809 --- /dev/null +++ b/src/lua.rs @@ -0,0 +1,96 @@ +use core::lua::wrapped_types::RectLua; +use std::{error::Error, sync::Arc}; + +use hecs::World; +use hv_cell::AtomicRefCell; +use hv_lua::{chunk, Lua, Value}; +use macroquad::prelude::collections::storage; +use macroquad_platformer::Actor; +use tealr::mlu::TealData; +use tealr::TypeName; + +use crate::effects::active::projectiles::Projectile; +use crate::effects::active::triggered::TriggeredEffect; +use crate::particles::ParticleEmitter; +use crate::player::{Player, PlayerEventQueue, PlayerInventory}; +use crate::{ + AnimatedSprite, AnimatedSpriteSet, DrawableKind, Item, Owner, PhysicsBody, Resources, + RigidBody, Sprite, +}; + +pub(crate) fn run_event( + event_name: &'static str, + world: Arc>, +) -> Result<(), Box> { + let res = storage::get_mut::(); + let lua = &res.lua; + let thread_name = format!("Event: {}", event_name); + let chunk = chunk! { + local world = $world + local event_name = $event_name + local events_to_run = events[event_name] or {} + for _ , mod_config in ipairs(events_to_run) do + require = mod_config.require + event = mod_config.events[event_name] + if type(event) == "function" then + local isSuccess, err = pcall(event,world) + if not isSuccess then + io.stderr:write("Error while calling: `",event_name, "` from mod: `",mod_config.mod_id,"` Error:\n",err,"\n") + end + end + end + require = nil + }; + lua.load(chunk).set_name(&thread_name)?.exec()?; + Ok(()) +} +use core::lua::{CloneComponent, CopyComponent}; + +use core::{create_type_component_container, Transform}; +create_type_component_container!( + TypeComponentContainer with + I32 of CopyComponent, + Bool of CopyComponent, + Transform of CloneComponent, + PhysicsBody of CloneComponent, + RigidBody of CloneComponent, + Projectile of CloneComponent, + TriggeredEffect of CloneComponent, + Item of CloneComponent, + Owner of Owner, + PlayerInventory of CloneComponent, + PlayerEventQueue of CloneComponent, + Player of CloneComponent, + RectLua of RectLua, + ParticleEmitter of ParticleEmitter, + AnimatedSprite of AnimatedSprite, + AnimatedSpriteSet of AnimatedSpriteSet, + DrawableKind of DrawableKind, + Sprite of Sprite, + +); + +pub(crate) fn register_types(lua: &Lua) -> Result> { + use hv_lua::ToLua; + Ok(TypeComponentContainer::new(lua)?.to_lua(lua)?) +} + +#[derive(Clone, Copy, tealr::MluaUserData)] +pub struct ActorLua(Actor); +impl TypeName for ActorLua { + fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> { + tealr::new_type!(Actor, External) + } +} +impl TealData for ActorLua {} + +impl From for Actor { + fn from(a: ActorLua) -> Self { + a.0 + } +} +impl From for ActorLua { + fn from(a: Actor) -> Self { + Self(a) + } +} diff --git a/src/main.rs b/src/main.rs index c5669aa1b5..42407d50ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,8 @@ use fishsticks::GamepadContext; +use hecs::{DynamicQuery, Entity, World}; +use core::lua::wrapped_types::{ColorLua, RectLua, SoundLua, Texture2DLua, Vec2Lua}; +use core::lua::CloneComponent; use std::env; use std::path::PathBuf; @@ -24,6 +27,8 @@ pub mod resources; pub mod drawables; +mod lua; + pub use drawables::*; pub use physics::*; @@ -31,8 +36,8 @@ use editor::{Editor, EditorCamera, EditorInputScheme}; use map::{Map, MapLayerKind, MapObjectKind}; -use core::network::Api; use core::Result; +use core::{network::Api, Transform}; pub use core::Config; pub use items::Item; @@ -47,9 +52,19 @@ pub use player::PlayerEvent; pub use ecs::Owner; +use crate::effects::active::projectiles::{Projectile, Rectangle}; +use crate::effects::active::triggered::TriggeredEffect; +use crate::effects::active::{ + ActiveEffectKindCircleCollider, ActiveEffectKindProjectile, ActiveEffectKindRectCollider, + ActiveEffectKindTriggeredEffect, ProjectileKind, +}; use crate::effects::passive::init_passive_effects; +use crate::effects::TriggeredEffectTrigger; use crate::game::GameMode; -use crate::particles::Particles; +use crate::items::{ItemDepleteBehavior, ItemDropBehavior, ItemMetadata, Weapon}; +use crate::lua::ActorLua; +use crate::particles::{ParticleEmitter, ParticleEmitterMetadata, Particles}; +use crate::player::{Player, PlayerEventKind, PlayerInventory, PlayerState}; use crate::resources::load_resources; pub use effects::{ ActiveEffectKind, ActiveEffectMetadata, PassiveEffectInstance, PassiveEffectMetadata, @@ -200,6 +215,80 @@ async fn init_game() -> Result { #[macroquad::main(window_conf)] async fn main() -> std::result::Result<(), Box> { + let types = tealr::TypeWalker::new() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::>() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type::() + .process_type_as_marker::>() + .process_type_as_marker::() + .process_type_as_marker::() + .process_type_as_marker::() + .process_type_as_marker::() + .process_type_as_marker::() + .process_type_as_marker::() + .process_type_as_marker::() + .process_type_as_marker::(); + println!("time to generate the json files"); + std::fs::write("./test.json", serde_json::to_string_pretty(&types).unwrap()).unwrap(); + std::fs::write("./test.d.tl", types.generate_global("test").unwrap()).unwrap(); + println!("Wrote all!"); + // println!("Starting embedded lua test"); + // core::test::test()?; + // println!("Ended embedded lua test"); use events::iter_events; let assets_dir = env::var(ASSETS_DIR_ENV_VAR).unwrap_or_else(|_| "./assets".to_string()); diff --git a/src/map/mod.rs b/src/map/mod.rs index f7d7e50e76..a755080a4e 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -651,6 +651,8 @@ pub struct MapTileset { pub tile_attributes: HashMap>, #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub properties: HashMap, + #[serde(skip)] + pub bitmasks: Option>, } impl MapTileset { @@ -687,6 +689,7 @@ impl MapTileset { autotile_mask, tile_attributes: HashMap::new(), properties: HashMap::new(), + bitmasks: None, } } @@ -699,4 +702,40 @@ impl MapTileset { pub fn default_tile_subdivisions() -> UVec2 { uvec2(3, 3) } + + pub fn get_bitmasks(&self) -> Option> { + //Get autotile mask bitmasks + let tsub_x = self.tile_subdivisions.x as usize; + let tsub_y = self.tile_subdivisions.y as usize; + let atmsk_width = self.grid_size.x as usize * tsub_x; + + let mut bitmasks_vec: Vec> = + vec![vec![]; self.autotile_mask.len() / (tsub_x * tsub_y)]; + let mut bitmasks: Vec = vec![0; self.autotile_mask.len() / (tsub_x * tsub_y)]; + + let mut trow_off = 0; + for i in 0..self.autotile_mask.len() / atmsk_width { + if i != 0 && i % tsub_y == 0 { + trow_off += atmsk_width / tsub_x; + } + let row = self.autotile_mask[i * atmsk_width..i * atmsk_width + atmsk_width].to_vec(); + + for x in 0..row.len() / tsub_x { + let tile_row = row[x * tsub_x..x * tsub_x + tsub_x].to_vec(); + + bitmasks_vec[x + trow_off].extend(tile_row); + } + } + + for (n, surrounding_tiles) in bitmasks_vec.iter().enumerate() { + for (i, b) in surrounding_tiles.iter().enumerate() { + if *b && i < 4 { + bitmasks[n] += 2_u32.pow(i as u32); + } else if *b && i > 4 { + bitmasks[n] += 2_u32.pow(i as u32 - 1); + } + } + } + Some(bitmasks) + } } diff --git a/src/map/sproinger.rs b/src/map/sproinger.rs index 6f515bc681..bb5dffed59 100644 --- a/src/map/sproinger.rs +++ b/src/map/sproinger.rs @@ -1,7 +1,9 @@ +use hv_cell::AtomicRefCell; use macroquad::audio::play_sound_once; use macroquad::experimental::collections::storage; use macroquad::prelude::*; use std::collections::HashMap; +use std::sync::Arc; use hecs::{Entity, World}; @@ -82,7 +84,8 @@ pub fn spawn_sproinger(world: &mut World, position: Vec2) -> Result { Ok(entity) } -pub fn fixed_update_sproingers(world: &mut World) { +pub fn fixed_update_sproingers(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); let dt = get_frame_time(); let bodies = world diff --git a/src/network.rs b/src/network.rs index 860b4884ac..78ccc39c22 100644 --- a/src/network.rs +++ b/src/network.rs @@ -1,23 +1,26 @@ //! This module holds the networking core, used +use std::sync::Arc; + use hecs::World; +use hv_cell::AtomicRefCell; -pub fn update_network_client(world: &mut World) { +pub fn update_network_client(world: Arc>) { update_network_common(world); } -pub fn fixed_update_network_client(world: &mut World) { +pub fn fixed_update_network_client(world: Arc>) { fixed_update_network_common(world); } -pub fn update_network_host(world: &mut World) { +pub fn update_network_host(world: Arc>) { update_network_common(world); } -pub fn fixed_update_network_host(world: &mut World) { +pub fn fixed_update_network_host(world: Arc>) { fixed_update_network_common(world); } -fn update_network_common(_world: &mut World) {} +fn update_network_common(_world: Arc>) {} -fn fixed_update_network_common(_world: &mut World) {} +fn fixed_update_network_common(_world: Arc>) {} diff --git a/src/particles.rs b/src/particles.rs index dedd948a30..9309cc6e69 100644 --- a/src/particles.rs +++ b/src/particles.rs @@ -1,6 +1,12 @@ +use hv_alchemy::Type; +use hv_cell::AtomicRefCell; use macroquad::experimental::collections::storage; use macroquad::prelude::*; -use std::collections::HashMap; +use mlua::{FromLua, ToLua, UserData, UserDataMethods}; +use std::{borrow::Cow, collections::HashMap, sync::Arc}; +use tealr::mlu::{MaybeSend, UserDataWrapper}; +use tealr::TypeName; +use tealr::{mlu::TealData, TypeBody}; use ff_particles::EmittersCache; @@ -8,12 +14,12 @@ use hecs::World; use serde::{Deserialize, Serialize}; -use core::math::IsZero; use core::Transform; +use core::{lua::wrapped_types::Vec2Lua, math::IsZero}; use crate::{AnimatedSpriteMetadata, Resources}; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, tealr::TypeName)] pub struct ParticleEmitterMetadata { /// The id of the particle effect. #[serde(rename = "particle_effect")] @@ -42,6 +48,44 @@ pub struct ParticleEmitterMetadata { #[serde(default, skip_serializing_if = "core::json::is_false")] pub should_autostart: bool, } +impl TypeBody for ParticleEmitterMetadata { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields.push(( + Cow::Borrowed("particle_effect").into(), + String::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("offset").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("delay").into(), f32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("interval").into(), f32::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("emissions").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("animations").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("should_autostart").into(), + bool::get_type_parts(), + )); + } +} + +impl<'lua> FromLua<'lua> for ParticleEmitterMetadata { + fn from_lua(lua_value: mlua::Value<'lua>, lua: &'lua mlua::Lua) -> mlua::Result { + mlua::LuaSerdeExt::from_value(lua, lua_value) + } +} + +impl<'lua> ToLua<'lua> for ParticleEmitterMetadata { + fn to_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { + mlua::LuaSerdeExt::to_value(lua, &self) + } +} impl Default for ParticleEmitterMetadata { fn default() -> Self { @@ -56,7 +100,9 @@ impl Default for ParticleEmitterMetadata { } } } +use hv_lua as mlua; +#[derive(Clone, tealr::TypeName)] pub struct ParticleEmitter { pub particle_effect_id: String, pub offset: Vec2, @@ -106,6 +152,104 @@ impl ParticleEmitter { } } +impl UserData for ParticleEmitter { + fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { + let mut wrapper = UserDataWrapper::from_user_data_fields(fields); + ::add_fields(&mut wrapper) + } + + fn add_methods<'lua, T: UserDataMethods<'lua, Self>>(methods: &mut T) { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_methods(&mut wrapper) + } + fn add_type_methods<'lua, M: UserDataMethods<'lua, Type>>(methods: &mut M) + where + Self: 'static + MaybeSend, + { + let mut wrapper = UserDataWrapper::from_user_data_methods(methods); + ::add_type_methods(&mut wrapper) + } +} +impl TypeBody for ParticleEmitter { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_fields(gen); + ::add_methods(gen); + } + fn get_type_body_marker(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + ::add_type_methods(gen); + } +} + +impl TealData for ParticleEmitter { + fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_method("get_offset", |lua, this, (flip_x, flip_y): (bool, bool)| { + Ok(Vec2Lua::from(this.get_offset(flip_x, flip_y))) + }); + methods.add_method_mut("activate", |_, this, ()| { + this.activate(); + Ok(()) + }) + } + fn add_type_methods<'lua, M: tealr::mlu::TealDataMethods<'lua, Type>>(methods: &mut M) + where + Self: 'static + tealr::mlu::MaybeSend, + { + methods.add_function("new", |_, meta| Ok(ParticleEmitter::new(meta))) + } + fn add_fields<'lua, F: tealr::mlu::TealDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("particle_effect_id", |lua, this| { + Ok(this.particle_effect_id.clone()) + }); + fields.add_field_method_get("offset", |lua, this| Ok(Vec2Lua::from(this.offset))); + fields.add_field_method_get("delay", |lua, this| Ok(this.delay)); + fields.add_field_method_get("emissions", |lua, this| Ok(this.emissions)); + fields.add_field_method_get("interval", |lua, this| Ok(this.interval)); + fields.add_field_method_get("emission_cnt", |lua, this| Ok(this.emission_cnt)); + fields.add_field_method_get("delay_timer", |lua, this| Ok(this.delay_timer)); + fields.add_field_method_get("interval_timer", |lua, this| Ok(this.interval_timer)); + fields.add_field_method_get("is_active", |lua, this| Ok(this.is_active)); + + fields.add_field_method_set("particle_effect_id", |_, this, value| { + this.particle_effect_id = value; + Ok(()) + }); + fields.add_field_method_set("offset", |_, this, value: Vec2Lua| { + this.offset = value.into(); + Ok(()) + }); + fields.add_field_method_set("delay", |_, this, value| { + this.delay = value; + Ok(()) + }); + fields.add_field_method_set("emissions", |_, this, value| { + this.emissions = value; + Ok(()) + }); + fields.add_field_method_set("interval", |_, this, value| { + this.interval = value; + Ok(()) + }); + fields.add_field_method_set("emission_cnt", |_, this, value| { + this.emission_cnt = value; + Ok(()) + }); + fields.add_field_method_set("delay_timer", |_, this, value| { + this.delay_timer = value; + Ok(()) + }); + fields.add_field_method_set("interval_timer", |_, this, value| { + this.interval_timer = value; + Ok(()) + }); + fields.add_field_method_set("is_active", |_, this, value| { + this.is_active = value; + Ok(()) + }); + } +} + impl From for ParticleEmitter { fn from(params: ParticleEmitterMetadata) -> Self { ParticleEmitter::new(params) @@ -165,7 +309,8 @@ pub fn update_one_particle_emitter( } } -pub fn update_particle_emitters(world: &mut World) { +pub fn update_particle_emitters(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); for (_, (transform, emitter)) in world.query_mut::<(&Transform, &mut ParticleEmitter)>() { update_one_particle_emitter(transform.position, transform.rotation, emitter); } @@ -177,7 +322,7 @@ pub fn update_particle_emitters(world: &mut World) { } } -pub fn draw_particles(_world: &mut World) { +pub fn draw_particles(_world: Arc>) { let mut particles = storage::get_mut::(); for cache in particles.cache_map.values_mut() { diff --git a/src/physics.rs b/src/physics.rs index a9f0c0e47e..f53e69b86a 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -1,3 +1,5 @@ +use hv_cell::AtomicRefCell; +use hv_lua::{FromLua, ToLua}; use macroquad::color; use macroquad::experimental::collections::storage; use macroquad::prelude::*; @@ -7,9 +9,17 @@ use macroquad_platformer::{Actor, Tile}; use serde::{Deserialize, Serialize}; use hecs::World; - -use crate::{CollisionWorld, Map}; -use core::Transform; +use tealr::{TypeBody, TypeName}; + +use crate::{ + lua::{run_event, ActorLua}, + CollisionWorld, Map, +}; +use core::{ + lua::{get_table, wrapped_types::Vec2Lua}, + Transform, +}; +use std::{borrow::Cow, sync::Arc}; pub const GRAVITY: f32 = 2.5; pub const TERMINAL_VELOCITY: f32 = 10.0; @@ -54,7 +64,7 @@ pub fn create_collision_world(map: &Map) -> CollisionWorld { const FRICTION_LERP: f32 = 0.96; const STOP_THRESHOLD: f32 = 1.0; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, TypeName)] pub struct PhysicsBodyParams { pub size: Vec2, pub offset: Vec2, @@ -79,9 +89,65 @@ impl Default for PhysicsBodyParams { } } +impl<'lua> FromLua<'lua> for PhysicsBodyParams { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = match lua_value { + hv_lua::Value::Table(x) => x, + x => { + return Err(hv_lua::Error::FromLuaConversionError { + from: x.type_name(), + to: "table", + message: None, + }) + } + }; + Ok(Self { + size: table.get::<_, Vec2Lua>("size")?.into(), + offset: table.get::<_, Vec2Lua>("offset")?.into(), + has_mass: table.get("has_mass")?, + has_friction: table.get("has_friction")?, + can_rotate: table.get("can_rotate")?, + bouncyness: table.get("bouncyness")?, + gravity: table.get("gravity")?, + }) + } +} +impl<'lua> ToLua<'lua> for PhysicsBodyParams { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("size", Vec2Lua::from(self.size))?; + table.set("offset", Vec2Lua::from(self.offset))?; + table.set("has_mass", self.has_mass)?; + table.set("has_friction", self.has_friction)?; + table.set("can_rotate", self.can_rotate)?; + table.set("bouncyness", self.bouncyness)?; + table.set("gravity", self.gravity)?; + lua.pack(table) + } +} +impl TypeBody for PhysicsBodyParams { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("size").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("offset").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("has_mass").into(), bool::get_type_parts())); + gen.fields + .push((Cow::Borrowed("has_friction").into(), bool::get_type_parts())); + gen.fields + .push((Cow::Borrowed("can_rotate").into(), bool::get_type_parts())); + gen.fields + .push((Cow::Borrowed("bouncyness").into(), f32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("gravity").into(), f32::get_type_parts())); + } +} + /// Regular simulated physics bodies. /// Note that rotation is abstract, only set on the transform to be used for draws. The colliders /// are axis-aligned and will not be affected by rotation. +#[derive(Clone, tealr::TypeName)] pub struct PhysicsBody { pub actor: Actor, pub offset: Vec2, @@ -99,6 +165,90 @@ pub struct PhysicsBody { pub is_deactivated: bool, pub gravity: f32, } +impl<'lua> FromLua<'lua> for PhysicsBody { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = match lua_value { + hv_lua::Value::Table(x) => x, + x => { + return Err(hv_lua::Error::FromLuaConversionError { + from: x.type_name(), + to: "table", + message: None, + }) + } + }; + Ok(Self { + actor: table.get::<_, ActorLua>("actor")?.into(), + offset: table.get::<_, Vec2Lua>("offset")?.into(), + size: table.get::<_, Vec2Lua>("size")?.into(), + velocity: table.get::<_, Vec2Lua>("velocity")?.into(), + is_on_ground: table.get("is_on_ground")?, + was_on_ground: table.get("was_on_ground")?, + is_on_platform: table.get("is_on_platform")?, + has_mass: table.get("has_mass")?, + has_friction: table.get("has_friction")?, + can_rotate: table.get("can_rotate")?, + bouncyness: table.get("bouncyness")?, + is_deactivated: table.get("is_deactivated")?, + gravity: table.get("gravity")?, + }) + } +} +impl TypeBody for PhysicsBody { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("actor").into(), ActorLua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("offset").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("size").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("velocity").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("is_on_ground").into(), bool::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("was_on_ground").into(), + bool::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("is_on_platform").into(), + bool::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("has_mass").into(), bool::get_type_parts())); + gen.fields + .push((Cow::Borrowed("has_friction").into(), bool::get_type_parts())); + gen.fields + .push((Cow::Borrowed("can_rotate").into(), bool::get_type_parts())); + gen.fields + .push((Cow::Borrowed("bouncyness").into(), f32::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("is_deactivated").into(), + bool::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("gravity").into(), f32::get_type_parts())); + } +} +impl<'lua> ToLua<'lua> for PhysicsBody { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("actor", ActorLua::from(self.actor))?; + table.set("offset", Vec2Lua::from(self.offset))?; + table.set("size", Vec2Lua::from(self.size))?; + table.set("velocity", Vec2Lua::from(self.velocity))?; + table.set("is_on_ground", self.is_on_ground)?; + table.set("was_on_ground", self.was_on_ground)?; + table.set("is_on_platform", self.is_on_platform)?; + table.set("has_mass", self.has_mass)?; + table.set("has_friction", self.has_friction)?; + table.set("can_rotate", self.can_rotate)?; + table.set("bouncyness", self.bouncyness)?; + table.set("is_deactivated", self.is_deactivated)?; + table.set("gravity", self.gravity)?; + lua.pack(table) + } +} impl PhysicsBody { pub fn new>>( @@ -131,65 +281,71 @@ impl PhysicsBody { } } -pub fn fixed_update_physics_bodies(world: &mut World) { - let mut collision_world = storage::get_mut::(); +pub fn fixed_update_physics_bodies(world: Arc>) { + { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); + let mut collision_world = storage::get_mut::(); - for (_, (transform, body)) in world.query_mut::<(&mut Transform, &mut PhysicsBody)>() { - collision_world.set_actor_position(body.actor, transform.position + body.offset); + for (_, (transform, body)) in world.query_mut::<(&mut Transform, &mut PhysicsBody)>() { + collision_world.set_actor_position(body.actor, transform.position + body.offset); - if !body.is_deactivated { - let position = collision_world.actor_pos(body.actor); + if !body.is_deactivated { + let position = collision_world.actor_pos(body.actor); - { - let position = position + vec2(0.0, 1.0); + { + let position = position + vec2(0.0, 1.0); - body.was_on_ground = body.is_on_ground; + body.was_on_ground = body.is_on_ground; - body.is_on_ground = collision_world.collide_check(body.actor, position); + body.is_on_ground = collision_world.collide_check(body.actor, position); - // FIXME: Using this to set `is_on_ground` caused weird glitching behavior when jumping up through platforms - let tile = collision_world.collide_solids( - position, - body.size.x as i32, - body.size.y as i32, - ); + // FIXME: Using this to set `is_on_ground` caused weird glitching behavior when jumping up through platforms + let tile = collision_world.collide_solids( + position, + body.size.x as i32, + body.size.y as i32, + ); - body.is_on_platform = tile == Tile::JumpThrough; - } + body.is_on_platform = tile == Tile::JumpThrough; + } - if !body.is_on_ground && body.has_mass { - body.velocity.y += body.gravity; + if !body.is_on_ground && body.has_mass { + body.velocity.y += body.gravity; - if body.velocity.y > TERMINAL_VELOCITY { - body.velocity.y = TERMINAL_VELOCITY; + if body.velocity.y > TERMINAL_VELOCITY { + body.velocity.y = TERMINAL_VELOCITY; + } } - } - if !collision_world.move_h(body.actor, body.velocity.x) { - body.velocity.x *= -body.bouncyness; - } + if !collision_world.move_h(body.actor, body.velocity.x) { + body.velocity.x *= -body.bouncyness; + } - if !collision_world.move_v(body.actor, body.velocity.y) { - body.velocity.y *= -body.bouncyness; - } + if !collision_world.move_v(body.actor, body.velocity.y) { + body.velocity.y *= -body.bouncyness; + } - if body.can_rotate { - apply_rotation(transform, &mut body.velocity, body.is_on_ground); - } + if body.can_rotate { + apply_rotation(transform, &mut body.velocity, body.is_on_ground); + } - if body.is_on_ground && body.has_friction { - body.velocity.x *= FRICTION_LERP; - if body.velocity.x.abs() <= STOP_THRESHOLD { - body.velocity.x = 0.0; + if body.is_on_ground && body.has_friction { + body.velocity.x *= FRICTION_LERP; + if body.velocity.x.abs() <= STOP_THRESHOLD { + body.velocity.x = 0.0; + } } - } - transform.position = collision_world.actor_pos(body.actor) - body.offset; + transform.position = collision_world.actor_pos(body.actor) - body.offset; + } } } + let _ = run_event("fixed_update_physics_bodies", world) + .map_err(|v| eprintln!("Ran into an error:\n{}", v)); } -pub fn debug_draw_physics_bodies(world: &mut World) { +pub fn debug_draw_physics_bodies(world: Arc>) { + let world = AtomicRefCell::borrow(world.as_ref()); for (_, (transform, body)) in world.query::<(&Transform, &PhysicsBody)>().iter() { if !body.is_deactivated { let rect = body.as_rect(transform.position); @@ -207,7 +363,7 @@ pub fn debug_draw_physics_bodies(world: &mut World) { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TypeName)] pub struct RigidBodyParams { #[serde(with = "core::json::vec2_def")] pub offset: Vec2, @@ -227,9 +383,21 @@ impl Default for RigidBodyParams { } } +impl TypeBody for RigidBodyParams { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("offset").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("size").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("can_rotate").into(), bool::get_type_parts())); + } +} + /// Simple physics bodies that has a velocity and optional rotation. /// Note that rotation is abstract, only set on the transform to be used for draws. The colliders /// are axis-aligned and will not be affected by rotation. +#[derive(Clone, TypeName)] pub struct RigidBody { pub offset: Vec2, pub size: Vec2, @@ -237,6 +405,41 @@ pub struct RigidBody { pub can_rotate: bool, } +impl<'lua> FromLua<'lua> for RigidBody { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = get_table(lua_value)?; + Ok(Self { + offset: table.get::<_, Vec2Lua>("offset")?.into(), + size: table.get::<_, Vec2Lua>("size")?.into(), + velocity: table.get::<_, Vec2Lua>("velocity")?.into(), + can_rotate: table.get("can_rotate")?, + }) + } +} +impl<'lua> ToLua<'lua> for RigidBody { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("offset", Vec2Lua::from(self.offset))?; + table.set("size", Vec2Lua::from(self.size))?; + table.set("velocity", Vec2Lua::from(self.velocity))?; + table.set("can_rotate", self.can_rotate)?; + lua.pack(table) + } +} + +impl TypeBody for RigidBody { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("offset").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("size").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("velocity").into(), Vec2Lua::get_type_parts())); + gen.fields + .push((Cow::Borrowed("can_rotate").into(), bool::get_type_parts())); + } +} + impl RigidBody { pub fn new>>(velocity: V, params: RigidBodyParams) -> Self { let velocity = velocity.into().unwrap_or_default(); @@ -255,7 +458,8 @@ impl RigidBody { } } -pub fn fixed_update_rigid_bodies(world: &mut World) { +pub fn fixed_update_rigid_bodies(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); for (_, (transform, body)) in world.query_mut::<(&mut Transform, &mut RigidBody)>() { transform.position += body.velocity; @@ -265,7 +469,8 @@ pub fn fixed_update_rigid_bodies(world: &mut World) { } } -pub fn debug_draw_rigid_bodies(world: &mut World) { +pub fn debug_draw_rigid_bodies(world: Arc>) { + let world = AtomicRefCell::borrow(world.as_ref()); for (_, (transform, body)) in world.query::<(&Transform, &RigidBody)>().iter() { let rect = body.as_rect(transform.position); diff --git a/src/player/animation.rs b/src/player/animation.rs index 967d609dfb..4cbfbc838c 100644 --- a/src/player/animation.rs +++ b/src/player/animation.rs @@ -1,3 +1,6 @@ +use std::sync::Arc; + +use hv_cell::AtomicRefCell; use macroquad::prelude::*; use hecs::World; @@ -349,7 +352,8 @@ impl PlayerAnimations { } } -pub fn update_player_animations(world: &mut World) { +pub fn update_player_animations(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); for (_, (player, inventory, body, drawable)) in world.query_mut::<(&Player, &mut PlayerInventory, &PhysicsBody, &mut Drawable)>() { diff --git a/src/player/controller.rs b/src/player/controller.rs index 13db86d20e..fdfdbe338c 100644 --- a/src/player/controller.rs +++ b/src/player/controller.rs @@ -1,10 +1,12 @@ use hecs::World; +use hv_cell::AtomicRefCell; use macroquad::prelude::*; use core::network::PlayerId; use core::input::{collect_local_input, GameInputScheme, PlayerInput}; +use std::sync::Arc; #[derive(Debug, Clone)] pub enum PlayerControllerKind { @@ -78,7 +80,8 @@ impl PlayerController { } } -pub fn update_player_controllers(world: &mut World) { +pub fn update_player_controllers(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); for (_, controller) in world.query_mut::<&mut PlayerController>() { let input = match &controller.kind { PlayerControllerKind::LocalInput(input_scheme) => collect_local_input(*input_scheme), diff --git a/src/player/events.rs b/src/player/events.rs index 4913ccc822..bcc0ba4e30 100644 --- a/src/player/events.rs +++ b/src/player/events.rs @@ -1,21 +1,48 @@ +use std::sync::Arc; + use hecs::{Entity, World}; +use hv_cell::AtomicRefCell; use macroquad::time::get_frame_time; +use mlua::{FromLua, ToLua, UserData}; +use tealr::{mlu::TealData, TypeBody, TypeName}; use crate::player::{Player, PlayerState}; use serde::{Deserialize, Serialize}; -#[derive(Default)] +#[derive(Clone, Default)] pub struct PlayerEventQueue { pub queue: Vec, } +impl TypeName for PlayerEventQueue { + fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> { + Vec::::get_type_parts() + } +} + +impl<'lua> FromLua<'lua> for PlayerEventQueue { + fn from_lua(lua_value: mlua::Value<'lua>, lua: &'lua mlua::Lua) -> mlua::Result { + Ok(Self { + queue: <_>::from_lua(lua_value, lua)?, + }) + } +} + +impl<'lua> ToLua<'lua> for PlayerEventQueue { + fn to_lua(self, lua: &'lua mlua::Lua) -> mlua::Result> { + self.queue.to_lua(lua) + } +} impl PlayerEventQueue { pub fn new() -> Self { PlayerEventQueue { queue: Vec::new() } } } +use hv_lua as mlua; + +use tealr::MluaTealDerive; -#[derive(Clone)] +#[derive(Clone, MluaTealDerive)] pub enum PlayerEvent { Update { dt: f32, @@ -38,9 +65,10 @@ pub enum PlayerEvent { collision_with: Entity, }, } +impl TealData for PlayerEvent {} /// This is used in JSON to specify which event types an effect should apply to -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, TypeName)] #[serde(rename_all = "snake_case")] pub enum PlayerEventKind { Update, @@ -51,6 +79,13 @@ pub enum PlayerEventKind { Collision, } +impl UserData for PlayerEventKind {} +impl TealData for PlayerEventKind {} + +impl TypeBody for PlayerEventKind { + fn get_type_body(_: &mut tealr::TypeGenerator) {} +} + impl From<&PlayerEvent> for PlayerEventKind { fn from(params: &PlayerEvent) -> Self { use PlayerEvent::*; @@ -66,7 +101,8 @@ impl From<&PlayerEvent> for PlayerEventKind { } } -pub fn update_player_events(world: &mut World) { +pub fn update_player_events(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); for (_, (player, events)) in world.query_mut::<(&mut Player, &mut PlayerEventQueue)>() { let dt = get_frame_time(); diff --git a/src/player/inventory.rs b/src/player/inventory.rs index ae801413a7..006acb6644 100644 --- a/src/player/inventory.rs +++ b/src/player/inventory.rs @@ -1,8 +1,15 @@ +use hv_cell::AtomicRefCell; +use hv_lua::{FromLua, ToLua}; use macroquad::prelude::*; use hecs::{Entity, With, Without, World}; +use tealr::{TypeBody, TypeName}; +use core::lua::get_table; +use core::lua::wrapped_types::Vec2Lua; use core::Transform; +use std::borrow::Cow; +use std::sync::Arc; use crate::items::{ fire_weapon, ItemDepleteBehavior, ItemDropBehavior, Weapon, EFFECT_ANIMATED_SPRITE_ID, @@ -14,7 +21,7 @@ use crate::{Drawable, Item, Owner, PassiveEffectInstance, PhysicsBody}; const THROW_FORCE: f32 = 5.0; -#[derive(Default)] +#[derive(Clone, TypeName, Default)] pub struct PlayerInventory { pub weapon_mount: Vec2, pub weapon_mount_offset: Vec2, @@ -27,6 +34,80 @@ pub struct PlayerInventory { pub hat: Option, } +impl<'lua> FromLua<'lua> for PlayerInventory { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = get_table(lua_value)?; + Ok(Self { + weapon_mount: table.get::<_, Vec2Lua>("weapon_mount")?.into(), + weapon_mount_offset: table.get::<_, Vec2Lua>("weapon_mount_offset")?.into(), + item_mount: table.get::<_, Vec2Lua>("item_mount")?.into(), + item_mount_offset: table.get::<_, Vec2Lua>("item_mount_offset")?.into(), + hat_mount: table.get::<_, Vec2Lua>("hat_mount")?.into(), + hat_mount_offset: table.get::<_, Vec2Lua>("hat_mount_offset")?.into(), + weapon: table.get("weapon")?, + items: table.get("items")?, + hat: table.get("hat")?, + }) + } +} +impl<'lua> ToLua<'lua> for PlayerInventory { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("weapon_mount", Vec2Lua::from(self.weapon_mount))?; + table.set( + "weapon_mount_offset", + Vec2Lua::from(self.weapon_mount_offset), + )?; + table.set("item_mount", Vec2Lua::from(self.item_mount))?; + table.set("item_mount_offset", Vec2Lua::from(self.item_mount_offset))?; + table.set("hat_mount", Vec2Lua::from(self.hat_mount))?; + table.set("hat_mount_offset", Vec2Lua::from(self.hat_mount_offset))?; + table.set("weapon", self.weapon)?; + table.set("items", self.items)?; + table.set("hat", self.hat)?; + lua.pack(table) + } +} + +impl TypeBody for PlayerInventory { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields.push(( + Cow::Borrowed("weapon_mount").into(), + Vec2Lua::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("weapon_mount_offset").into(), + Vec2Lua::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("item_mount").into(), + Vec2Lua::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("item_mount_offset").into(), + Vec2Lua::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("hat_mount").into(), Vec2Lua::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("hat_mount_offset").into(), + Vec2Lua::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("weapon").into(), + Option::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("items").into(), + Vec::::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("hat").into(), + Option::::get_type_parts(), + )); + } +} + impl PlayerInventory { pub fn new(weapon_mount: Vec2, item_mount: Vec2, hat_mount: Vec2) -> Self { PlayerInventory { @@ -52,7 +133,8 @@ impl PlayerInventory { } } -pub fn update_player_inventory(world: &mut World) { +pub fn update_player_inventory(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); let mut item_colliders = world .query::>>() .iter() @@ -424,7 +506,7 @@ pub fn update_player_inventory(world: &mut World) { } for (entity, owner) in to_fire.drain(0..) { - if let Err(err) = fire_weapon(world, entity, owner) { + if let Err(err) = fire_weapon(&mut world, entity, owner) { #[cfg(debug_assertions)] println!("WARNING: {}", err); } @@ -456,7 +538,8 @@ const HUD_USE_COUNT_COLOR_EMPTY: Color = Color { a: 0.8, }; -pub fn draw_weapons_hud(world: &mut World) { +pub fn draw_weapons_hud(world: Arc>) { + let world = AtomicRefCell::borrow(world.as_ref()); for (_, (transform, inventory)) in world.query::<(&Transform, &PlayerInventory)>().iter() { if let Some(weapon_entity) = inventory.weapon { let weapon = world.get::(weapon_entity).unwrap(); diff --git a/src/player/mod.rs b/src/player/mod.rs index 0eae96af33..cdeb99db5e 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -1,9 +1,13 @@ +use hv_cell::AtomicRefCell; +use hv_lua::{FromLua, ToLua}; use macroquad::experimental::collections::storage; use macroquad::prelude::*; use hecs::{Entity, World}; +use tealr::{TypeBody, TypeName}; -use core::Transform; +use core::{lua::wrapped_types::RectLua, Transform}; +use std::{borrow::Cow, sync::Arc}; use crate::{ AnimatedSprite, AnimatedSpriteMetadata, AnimatedSpriteParams, CollisionWorld, Drawable, @@ -56,6 +60,7 @@ pub struct PlayerParams { pub character: PlayerCharacterMetadata, } +#[derive(Clone, TypeName)] pub struct Player { pub index: u8, pub state: PlayerState, @@ -72,6 +77,94 @@ pub struct Player { pub passive_effects: Vec, } +impl<'lua> FromLua<'lua> for Player { + fn from_lua(lua_value: hv_lua::Value<'lua>, _: &'lua hv_lua::Lua) -> hv_lua::Result { + let table = core::lua::get_table(lua_value)?; + Ok(Self { + index: table.get("index")?, + state: table.get("state")?, + damage_from_left: table.get("damage_from_left")?, + is_facing_left: table.get("is_facing_left")?, + is_upside_down: table.get("is_upside_down")?, + is_attacking: table.get("is_attacking")?, + jump_frame_counter: table.get("jump_frame_counter")?, + pickup_grace_timer: table.get("pickup_grace_timer")?, + incapacitation_timer: table.get("incapacitation_timer")?, + attack_timer: table.get("attack_timer")?, + respawn_timer: table.get("respawn_timer")?, + camera_box: table.get::<_, RectLua>("camera_box")?.into(), + passive_effects: table.get("passive_effects")?, + }) + } +} + +impl<'lua> ToLua<'lua> for Player { + fn to_lua(self, lua: &'lua hv_lua::Lua) -> hv_lua::Result> { + let table = lua.create_table()?; + table.set("index", self.index)?; + table.set("state", self.state)?; + table.set("damage_from_left", self.damage_from_left)?; + table.set("is_facing_left", self.is_facing_left)?; + table.set("is_upside_down", self.is_upside_down)?; + table.set("is_attacking", self.is_attacking)?; + table.set("jump_frame_counter", self.jump_frame_counter)?; + table.set("pickup_grace_timer", self.pickup_grace_timer)?; + table.set("incapacitation_timer", self.incapacitation_timer)?; + table.set("attack_timer", self.attack_timer)?; + table.set("respawn_timer", self.respawn_timer)?; + table.set("camera_box", RectLua::from(self.camera_box))?; + table.set("passive_effects", self.passive_effects)?; + lua.pack(table) + } +} + +impl TypeBody for Player { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.fields + .push((Cow::Borrowed("index").into(), u8::get_type_parts())); + gen.fields + .push((Cow::Borrowed("state").into(), PlayerState::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("damage_from_left").into(), + bool::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("is_facing_left").into(), + bool::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("is_upside_down").into(), + bool::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("is_attacking").into(), bool::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("jump_frame_counter").into(), + u16::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("pickup_grace_timer").into(), + f32::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("incapacitation_timer").into(), + f32::get_type_parts(), + )); + gen.fields + .push((Cow::Borrowed("attack_timer").into(), f32::get_type_parts())); + gen.fields + .push((Cow::Borrowed("respawn_timer").into(), f32::get_type_parts())); + gen.fields.push(( + Cow::Borrowed("camera_box").into(), + RectLua::get_type_parts(), + )); + gen.fields.push(( + Cow::Borrowed("passive_effects").into(), + Vec::::get_type_parts(), + )); + } +} + impl Player { pub fn new(index: u8, position: Vec2) -> Self { let camera_box = Rect::new(position.x - 30.0, position.y - 150.0, 100.0, 210.0); @@ -94,7 +187,8 @@ impl Player { } } -pub fn update_player_camera_box(world: &mut World) { +pub fn update_player_camera_box(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); for (_, (transform, player)) in world.query_mut::<(&Transform, &mut Player)>() { let rect = Rect::new(transform.position.x, transform.position.y, 32.0, 60.0); diff --git a/src/player/state.rs b/src/player/state.rs index efdd436426..516f28ea12 100644 --- a/src/player/state.rs +++ b/src/player/state.rs @@ -1,10 +1,15 @@ +use hv_cell::AtomicRefCell; +use hv_lua::UserData; use macroquad::audio::play_sound_once; use macroquad::experimental::collections::storage; use macroquad::prelude::*; use hecs::{Entity, World}; +use tealr::mlu::TealData; +use tealr::{TypeBody, TypeName}; use core::Transform; +use std::sync::Arc; use crate::player::{ Player, PlayerAttributes, PlayerController, PlayerEventQueue, JUMP_SOUND_ID, LAND_SOUND_ID, @@ -16,7 +21,7 @@ const SLIDE_STOP_THRESHOLD: f32 = 2.0; const JUMP_FRAME_COUNT: u16 = 8; const PLATFORM_JUMP_FORCE_MULTIPLIER: f32 = 0.2; -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, TypeName)] pub enum PlayerState { None, Jumping, @@ -27,13 +32,22 @@ pub enum PlayerState { Dead, } +impl UserData for PlayerState {} +impl TealData for PlayerState {} +impl TypeBody for PlayerState { + fn get_type_body(gen: &mut tealr::TypeGenerator) { + gen.is_user_data = true; + } +} + impl Default for PlayerState { fn default() -> Self { PlayerState::None } } -pub fn update_player_states(world: &mut World) { +pub fn update_player_states(world: Arc>) { + let mut world = AtomicRefCell::borrow_mut(world.as_ref()); let query = world.query_mut::<( &mut Transform, &mut Player, @@ -190,37 +204,41 @@ pub fn update_player_states(world: &mut World) { } } -pub fn update_player_passive_effects(world: &mut World) { +pub fn update_player_passive_effects(world: Arc>) { let mut function_calls = Vec::new(); + { + let world = AtomicRefCell::borrow_mut(world.as_ref()); + for (entity, (player, events)) in + world.query::<(&mut Player, &mut PlayerEventQueue)>().iter() + { + let dt = get_frame_time(); - for (entity, (player, events)) in world.query::<(&mut Player, &mut PlayerEventQueue)>().iter() { - let dt = get_frame_time(); - - for effect in &mut player.passive_effects { - effect.duration_timer += dt; - } + for effect in &mut player.passive_effects { + effect.duration_timer += dt; + } - player - .passive_effects - .retain(|effect| !effect.is_depleted()); + player + .passive_effects + .retain(|effect| !effect.is_depleted()); - events.queue.push(PlayerEvent::Update { dt }); + events.queue.push(PlayerEvent::Update { dt }); - for event in events.queue.iter() { - let kind = event.into(); + for event in events.queue.iter() { + let kind = event.into(); - for effect in &mut player.passive_effects { - if effect.activated_on.contains(&kind) { - effect.use_cnt += 1; + for effect in &mut player.passive_effects { + if effect.activated_on.contains(&kind) { + effect.use_cnt += 1; - if let Some(item_entity) = effect.item { - let mut item = world.get_mut::(item_entity).unwrap(); + if let Some(item_entity) = effect.item { + let mut item = world.get_mut::(item_entity).unwrap(); - item.use_cnt += 1; - } + item.use_cnt += 1; + } - if let Some(f) = &effect.function { - function_calls.push((*f, entity, effect.item, event.clone())); + if let Some(f) = &effect.function { + function_calls.push((f.to_owned(), entity, effect.item, event.clone())); + } } } } @@ -228,7 +246,7 @@ pub fn update_player_passive_effects(world: &mut World) { } for (f, player_entity, item_entity, event) in function_calls.drain(0..) { - f(world, player_entity, item_entity, event); + f.call_get_lua(world.clone(), player_entity, item_entity, event); } } diff --git a/src/resources.rs b/src/resources.rs index ab4ea407c1..01af47f07a 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fs, path::Path}; +use std::{collections::HashMap, fs, os::unix::prelude::OsStrExt, path::Path}; use macroquad::{ audio::{load_sound, Sound}, @@ -10,13 +10,16 @@ use ff_particles::EmitterConfig; use serde::{Deserialize, Serialize}; -use core::data::{deserialize_json_bytes, deserialize_json_file}; -use core::error::ErrorKind; use core::text::ToStringHelper; +use core::{ + data::{deserialize_json_bytes, deserialize_json_file}, + lua::init_lua, +}; +use core::{error::ErrorKind, lua::load_lua}; use core::{formaterr, Result}; -use crate::gui::GuiResources; use crate::map::DecorationMetadata; +use crate::{gui::GuiResources, lua::register_types}; use crate::player::PlayerCharacterMetadata; use crate::{items::MapItemMetadata, map::Map}; @@ -361,13 +364,14 @@ pub struct Resources { pub decoration: HashMap, pub items: HashMap, pub player_characters: HashMap, + pub lua: hv_lua::Lua, } impl Resources { pub async fn new>(assets_dir: P, mods_dir: P) -> Result { let assets_dir = assets_dir.as_ref(); let mods_dir = mods_dir.as_ref(); - + let lua = init_lua(mods_dir, register_types).unwrap(); let mut resources = Resources { assets_dir: assets_dir.to_string_helper(), mods_dir: mods_dir.to_string_helper(), @@ -381,6 +385,7 @@ impl Resources { maps: Vec::new(), items: HashMap::new(), player_characters: HashMap::new(), + lua, }; load_resources_from(assets_dir, &mut resources).await?; @@ -667,8 +672,11 @@ async fn load_mods>(mods_dir: P, resources: &mut Resources) -> Re } if !has_unmet_dependencies { + let path = mod_dir_path.file_name().unwrap().as_bytes().to_owned(); load_resources_from(mod_dir_path, resources).await?; - + if meta.kind == ModKind::Full { + load_lua(meta.id.to_owned(), path, &resources.lua).unwrap(); + } #[cfg(debug_assertions)] println!("Loaded mod {} (v{})", &meta.id, &meta.version);