diff --git a/Cargo.lock b/Cargo.lock index 5b56ad3..e85e554 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,15 +28,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" - -[[package]] -name = "ahash" -version = "0.6.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "796540673305a66d127804eef19ad696f1f204b8c1025aaca4958c17eab32877" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ "getrandom 0.2.6", "once_cell", @@ -178,6 +172,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "async-recursion" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-std" version = "1.11.0" @@ -309,15 +314,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - [[package]] name = "block-buffer" version = "0.10.2" @@ -370,12 +366,6 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - [[package]] name = "bytes" version = "1.1.0" @@ -388,28 +378,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" -[[package]] -name = "cargo-platform" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714a157da7991e23d90686b9524b9e12e0407a108647f52e9328f4b3d51ac7f" -dependencies = [ - "cargo-platform", - "semver 0.11.0", - "semver-parser", - "serde", - "serde_json", -] - [[package]] name = "cc" version = "1.0.73" @@ -514,15 +482,20 @@ dependencies = [ ] [[package]] -name = "crossbeam-channel" -version = "0.5.4" +name = "crc" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" dependencies = [ - "cfg-if", - "crossbeam-utils", + "crc-catalog", ] +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + [[package]] name = "crossbeam-queue" version = "0.3.5" @@ -553,16 +526,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "ctor" version = "0.1.22" @@ -588,22 +551,33 @@ dependencies = [ [[package]] name = "digest" -version = "0.9.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "generic-array", + "block-buffer", + "crypto-common", + "subtle", ] [[package]] -name = "digest" -version = "0.10.3" +name = "dirs" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "block-buffer 0.10.2", - "crypto-common", - "subtle", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", ] [[package]] @@ -624,16 +598,18 @@ version = "0.1.0" dependencies = [ "ages-prs", "anyhow", + "async-recursion", "async-std", "async-trait", "barrel", "bcrypt", "byteorder", "chrono", - "crc", + "crc 1.8.1", "derive_more", "enum-utils", "fern", + "fix-hidden-lifetime-bug", "futures", "lazy_static", "libpso", @@ -716,6 +692,26 @@ dependencies = [ "log", ] +[[package]] +name = "fix-hidden-lifetime-bug" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ae9c2016a663983d4e40a9ff967d6dcac59819672f0b47f2b17574e99c33c8" +dependencies = [ + "fix-hidden-lifetime-bug-proc_macros", +] + +[[package]] +name = "fix-hidden-lifetime-bug-proc_macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c81935e123ab0741c4c4f0d9b8377e5fb21d3de7e062fa4b1263b1fbcba1ea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -789,6 +785,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.21" @@ -901,28 +908,22 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "hashbrown" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -dependencies = [ - "ahash 0.4.7", -] - [[package]] name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] [[package]] name = "hashlink" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" dependencies = [ - "hashbrown 0.9.1", + "hashbrown", ] [[package]] @@ -934,6 +935,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -950,13 +960,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "hmac" -version = "0.10.1" +name = "hkdf" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" dependencies = [ - "crypto-mac", - "digest 0.9.0", + "hmac", ] [[package]] @@ -965,7 +974,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest", ] [[package]] @@ -986,7 +995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" dependencies = [ "autocfg 1.1.0", - "hashbrown 0.11.2", + "hashbrown", ] [[package]] @@ -1007,12 +1016,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.1" @@ -1091,24 +1094,13 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" -[[package]] -name = "md-5" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "md-5" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" dependencies = [ - "digest 0.10.3", + "digest", ] [[package]] @@ -1307,19 +1299,16 @@ dependencies = [ ] [[package]] -name = "percent-encoding" -version = "2.1.0" +name = "paste" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" [[package]] -name = "pest" -version = "2.1.3" +name = "percent-encoding" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" -dependencies = [ - "ucd-trie", -] +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "phf" @@ -1376,7 +1365,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb76d6535496f633fa799bb872ffb4790e9cbdedda9d35564ca0252f930c0dd5" dependencies = [ - "bytes 1.1.0", + "bytes", "fallible-iterator", "futures", "log", @@ -1392,13 +1381,13 @@ checksum = "79ec03bce71f18b4a27c4c64c6ba2ddf74686d69b91d8714fb32ead3adaed713" dependencies = [ "base64", "byteorder", - "bytes 1.1.0", + "bytes", "fallible-iterator", - "hmac 0.12.1", - "md-5 0.10.1", + "hmac", + "md-5", "memchr", "rand 0.8.5", - "sha2 0.10.2", + "sha2", "stringprep", ] @@ -1408,7 +1397,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04619f94ba0cc80999f4fc7073607cb825bc739a883cb6d20900fc5e009d6b0d" dependencies = [ - "bytes 1.1.0", + "bytes", "fallible-iterator", "postgres-protocol", ] @@ -1642,6 +1631,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.6", + "redox_syscall", + "thiserror", +] + [[package]] name = "refinery" version = "0.5.0" @@ -1735,7 +1735,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.7", + "semver", ] [[package]] @@ -1792,31 +1792,12 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", - "serde", -] - [[package]] name = "semver" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - [[package]] name = "serde" version = "1.0.136" @@ -1854,36 +1835,20 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ - "indexmap", - "itoa 1.0.1", + "itoa", "ryu", "serde", ] [[package]] name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha2" -version = "0.9.9" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ - "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "digest", ] [[package]] @@ -1894,7 +1859,7 @@ checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest", ] [[package]] @@ -1957,9 +1922,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.4.2" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1a98f9bf17b690f026b6fec565293a995b46dfbd6293debcb654dcffd2d1b34" +checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b" dependencies = [ "sqlx-core", "sqlx-macros", @@ -1967,41 +1932,44 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.4.2" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36bb6a2ca3345a86493bc3b71eabc2c6c16a8bb1aa476cf5303bee27f67627d7" +checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" dependencies = [ - "ahash 0.6.3", + "ahash", "atoi", "base64", "bitflags", "byteorder", - "bytes 0.5.6", + "bytes", "chrono", - "crc", - "crossbeam-channel", + "crc 2.1.0", "crossbeam-queue", - "crossbeam-utils", + "dirs", "either", + "event-listener", "futures-channel", "futures-core", + "futures-intrusive", "futures-util", "hashlink", "hex", - "hmac 0.10.1", - "itoa 0.4.8", + "hkdf", + "hmac", + "indexmap", + "itoa", "libc", "log", - "md-5 0.9.1", + "md-5", "memchr", "once_cell", - "parking_lot", + "paste", "percent-encoding", - "rand 0.7.3", + "rand 0.8.5", "serde", "serde_json", "sha-1", - "sha2 0.9.9", + "sha2", "smallvec", "sqlformat", "sqlx-rt", @@ -2013,20 +1981,18 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.4.2" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5ada8b3b565331275ce913368565a273a74faf2a34da58c4dc010ce3286844" +checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1" dependencies = [ - "cargo_metadata", "dotenv", "either", - "futures", - "heck", - "lazy_static", + "heck 0.4.0", + "once_cell", "proc-macro2", "quote", "serde_json", - "sha2 0.9.9", + "sha2", "sqlx-core", "sqlx-rt", "syn", @@ -2035,9 +2001,9 @@ dependencies = [ [[package]] name = "sqlx-rt" -version = "0.2.0" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63fc5454c9dd7aaea3a0eeeb65ca40d06d0d8e7413a8184f7c3a3ffa5056190b" +checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae" dependencies = [ "async-native-tls", "async-std", @@ -2066,7 +2032,7 @@ version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e61bb0be289045cb80bfce000512e32d09f8337e54c186725da381377ad1f8d5" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro2", "quote", "syn", @@ -2154,7 +2120,7 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f48b6d60512a392e34dbf7fd456249fd2de3c83669ab642e021903f4015185b" dependencies = [ - "bytes 1.1.0", + "bytes", "libc", "memchr", "mio", @@ -2172,7 +2138,7 @@ checksum = "4b6c8b33df661b548dcd8f9bf87debb8c56c05657ed291122e1188698c2ece95" dependencies = [ "async-trait", "byteorder", - "bytes 1.1.0", + "bytes", "fallible-iterator", "futures", "log", @@ -2193,7 +2159,7 @@ version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-core", "futures-sink", "log", @@ -2216,12 +2182,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" -[[package]] -name = "ucd-trie" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" - [[package]] name = "unicode-bidi" version = "0.3.8" diff --git a/Cargo.toml b/Cargo.toml index 582634f..fb618d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,11 +25,12 @@ derive_more = { version = "0.99.3", features = ["display"]} thiserror = "1.0.15" ages-prs = "0.1" async-trait = "0.1.51" +async-recursion= "1.0.0" lazy_static = "1.4.0" barrel = { version = "0.6.5", features = ["pg"] } refinery = { version = "0.5.0", features = ["postgres"] } -sqlx = { version = "0.4.0", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] } +sqlx = { version = "0.5.10", features = ["runtime-async-std-native-tls", "postgres", "json", "chrono"] } strum = "0.19.5" strum_macros = "0.19" anyhow = { version = "1.0.47", features = ["backtrace"] } - +fix-hidden-lifetime-bug = "0.2.4" diff --git a/src/bin/main.rs b/src/bin/main.rs index d493ec2..76c63a6 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -69,14 +69,14 @@ fn main() { character.name = format!("Test Char {}", i*2); let character = entity_gateway.create_character(character).await.unwrap(); entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap(); - entity_gateway.set_bank_meseta(&character.id, item::BankName("".into()), item::Meseta(999999)).await.unwrap(); + entity_gateway.set_bank_meseta(&character.id, &item::BankName("".into()), item::Meseta(999999)).await.unwrap(); let mut character = NewCharacterEntity::new(fake_user.id, 1); character.slot = 2; character.name = "ItemRefactor".into(); character.exp = 80000000; let character = entity_gateway.create_character(character).await.unwrap(); entity_gateway.set_character_meseta(&character.id, item::Meseta(999999)).await.unwrap(); - entity_gateway.set_bank_meseta(&character.id, item::BankName("".into()), item::Meseta(999999)).await.unwrap(); + entity_gateway.set_bank_meseta(&character.id, &item::BankName("".into()), item::Meseta(999999)).await.unwrap(); for _ in 0..3 { entity_gateway.create_item( @@ -327,7 +327,7 @@ fn main() { let inventory = item::InventoryEntity::new(vec![InventoryItemEntity::from(item0), item1.into(), item2_w.into(), item3.into(), item4.into(), item5_m.into(), item6.into(), item6_1.into(), item7_a.into(), item8_s.into(), item9_u0.into(), item10_u1.into(), item11_u2.into(), item12_u3.into(), item13.into(), item14.into(), monomates.into()]); entity_gateway.set_character_inventory(&character.id, &inventory).await.unwrap(); - entity_gateway.set_character_bank(&character.id, &item::BankEntity::default(), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&character.id, &item::BankEntity::default(), &item::BankName("".into())).await.unwrap(); } info!("[patch] starting server"); diff --git a/src/common/leveltable.rs b/src/common/leveltable.rs index 94ad8ae..1ca6cd1 100644 --- a/src/common/leveltable.rs +++ b/src/common/leveltable.rs @@ -3,7 +3,7 @@ use std::fs::File; use serde_json::Value; use crate::entity::character::CharacterClass; -#[derive(Default, Copy, Clone, Debug, PartialEq)] +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] pub struct CharacterStats { pub hp: u16, pub atp: u16, diff --git a/src/entity/gateway/entitygateway.rs b/src/entity/gateway/entitygateway.rs index fd8b1aa..53b6fc4 100644 --- a/src/entity/gateway/entitygateway.rs +++ b/src/entity/gateway/entitygateway.rs @@ -1,4 +1,6 @@ +use std::convert::From; use thiserror::Error; +use futures::Future; use crate::entity::account::*; use crate::entity::character::*; @@ -14,17 +16,34 @@ pub enum GatewayError { PgError(#[from] sqlx::Error) } + #[async_trait::async_trait] -pub trait EntityGateway: Send + Sync + Clone { +pub trait EntityGateway: Send + Sync { + async fn transaction<'a>(&'a mut self) -> Result, GatewayError> + { + unimplemented!(); + } + + async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, _func: F) -> Result + where + Fut: Future, R), E>> + Send + 'a, + F: FnOnce(Box) -> Fut + Send, + R: Send, + E: From, + Self: Sized + { + unimplemented!(); + } + async fn create_user(&mut self, _user: NewUserAccountEntity) -> Result { unimplemented!() } - async fn get_user_by_id(&self, _id: UserAccountId) -> Result { + async fn get_user_by_id(&mut self, _id: UserAccountId) -> Result { unimplemented!(); } - async fn get_user_by_name(&self, _username: String) -> Result { + async fn get_user_by_name(&mut self, _username: String) -> Result { unimplemented!(); } @@ -36,7 +55,7 @@ pub trait EntityGateway: Send + Sync + Clone { unimplemented!(); } - async fn get_user_settings_by_user(&self, _user: &UserAccountEntity) -> Result { + async fn get_user_settings_by_user(&mut self, _user: &UserAccountEntity) -> Result { unimplemented!(); } @@ -49,7 +68,7 @@ pub trait EntityGateway: Send + Sync + Clone { } // TODO: just make this a vec sorted by slot order? - async fn get_characters_by_user(&self, _user: &UserAccountEntity) -> Result<[Option; 4], GatewayError> { + async fn get_characters_by_user(&mut self, _user: &UserAccountEntity) -> Result<[Option; 4], GatewayError> { unimplemented!(); } @@ -57,7 +76,7 @@ pub trait EntityGateway: Send + Sync + Clone { unimplemented!(); } - async fn get_guild_card_data_by_user(&self, _user: &UserAccountEntity) -> Result { + async fn get_guild_card_data_by_user(&mut self, _user: &UserAccountEntity) -> Result { unimplemented!(); } @@ -85,18 +104,11 @@ pub trait EntityGateway: Send + Sync + Clone { unimplemented!(); } - - /* - async fn get_items_by_character(&self, _char_id: &CharacterEntityId) -> Result, GatewayError> { - unimplemented!(); - } - */ - async fn get_character_inventory(&mut self, _char_id: &CharacterEntityId) -> Result { unimplemented!(); } - async fn get_character_bank(&mut self, _char_id: &CharacterEntityId, _bank_name: BankName) -> Result { + async fn get_character_bank(&mut self, _char_id: &CharacterEntityId, _bank_name: &BankName) -> Result { unimplemented!(); } @@ -104,7 +116,7 @@ pub trait EntityGateway: Send + Sync + Clone { unimplemented!(); } - async fn set_character_bank(&mut self, _char_id: &CharacterEntityId, _inventory: &BankEntity, _bank_name: BankName) -> Result<(), GatewayError> { + async fn set_character_bank(&mut self, _char_id: &CharacterEntityId, _inventory: &BankEntity, _bank_name: &BankName) -> Result<(), GatewayError> { unimplemented!(); } @@ -124,11 +136,27 @@ pub trait EntityGateway: Send + Sync + Clone { unimplemented!(); } - async fn get_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: BankName) -> Result { + async fn get_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: &BankName) -> Result { unimplemented!(); } - async fn set_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: BankName, _amount: Meseta) -> Result<(), GatewayError> { + async fn set_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: &BankName, _amount: Meseta) -> Result<(), GatewayError> { unimplemented!(); } + + async fn create_trade(&mut self, _char_id1: &CharacterEntityId, _char_id2: &CharacterEntityId) -> Result { + unimplemented!(); + } +} + + +#[async_trait::async_trait] +pub trait EntityGatewayTransaction: Send + Sync { + fn gateway(&mut self) -> &mut dyn EntityGateway { + unimplemented!() + } + + async fn commit(self: Box) -> Result<(), GatewayError> { + unimplemented!() + } } diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index 5c7afb4..f8b3c96 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -1,13 +1,68 @@ use std::collections::BTreeMap; use std::convert::TryInto; +use futures::Future; use crate::entity::account::*; use crate::entity::character::*; -use crate::entity::gateway::{EntityGateway, GatewayError}; +use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError}; use crate::entity::item::*; use std::sync::{Arc, Mutex}; +// TODO: implement multiple banks + +pub struct InMemoryGatewayTransaction<'a> { + working_gateway: InMemoryGateway, + original_gateway: &'a mut InMemoryGateway, +} + +#[async_trait::async_trait] +impl<'a> EntityGatewayTransaction for InMemoryGatewayTransaction<'a> { + fn gateway(&mut self) -> &mut dyn EntityGateway { + &mut self.working_gateway + } + + async fn commit(mut self: Box) -> Result<(), GatewayError> { + self.original_gateway.users.lock().unwrap().clear(); + self.original_gateway.users.lock().unwrap().extend(self.working_gateway.users.lock().unwrap().clone()); + + self.original_gateway.user_settings.lock().unwrap().clear(); + self.original_gateway.user_settings.lock().unwrap().extend(self.working_gateway.user_settings.lock().unwrap().clone()); + + self.original_gateway.characters.lock().unwrap().clear(); + self.original_gateway.characters.lock().unwrap().extend(self.working_gateway.characters.lock().unwrap().clone()); + + self.original_gateway.character_meseta.lock().unwrap().clear(); + self.original_gateway.character_meseta.lock().unwrap().extend(self.working_gateway.character_meseta.lock().unwrap().clone()); + + self.original_gateway.bank_meseta.lock().unwrap().clear(); + self.original_gateway.bank_meseta.lock().unwrap().extend(self.working_gateway.bank_meseta.lock().unwrap().clone()); + + self.original_gateway.items.lock().unwrap().clear(); + self.original_gateway.items.lock().unwrap().extend(self.working_gateway.items.lock().unwrap().clone()); + + self.original_gateway.inventories.lock().unwrap().clear(); + self.original_gateway.inventories.lock().unwrap().extend(self.working_gateway.inventories.lock().unwrap().clone()); + + self.original_gateway.banks.lock().unwrap().clear(); + self.original_gateway.banks.lock().unwrap().extend(self.working_gateway.banks.lock().unwrap().clone()); + + self.original_gateway.equips.lock().unwrap().clear(); + self.original_gateway.equips.lock().unwrap().extend(self.working_gateway.equips.lock().unwrap().clone()); + + self.original_gateway.mag_modifiers.lock().unwrap().clear(); + self.original_gateway.mag_modifiers.lock().unwrap().extend(self.working_gateway.mag_modifiers.lock().unwrap().clone()); + + self.original_gateway.weapon_modifiers.lock().unwrap().clear(); + self.original_gateway.weapon_modifiers.lock().unwrap().extend(self.working_gateway.weapon_modifiers.lock().unwrap().clone()); + + self.original_gateway.trades.lock().unwrap().clear(); + self.original_gateway.trades.lock().unwrap().extend(self.working_gateway.trades.lock().unwrap().clone()); + + Ok(()) + } +} + #[derive(Clone)] pub struct InMemoryGateway { users: Arc>>, @@ -21,6 +76,7 @@ pub struct InMemoryGateway { equips: Arc>>, mag_modifiers: Arc>>>, weapon_modifiers: Arc>>>, + trades: Arc>>, } impl Default for InMemoryGateway { @@ -37,6 +93,7 @@ impl Default for InMemoryGateway { equips: Arc::new(Mutex::new(BTreeMap::new())), mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())), weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())), + trades: Arc::new(Mutex::new(Vec::new())), } } } @@ -74,7 +131,7 @@ impl InMemoryGateway { mag::MagModifier::MagCell(mag_cell_id) => { if let Some(mag_cell) = items.get(mag_cell_id) { if let ItemDetail::Tool(mag_cell) = mag_cell.item { - mag.apply_mag_cell(mag_cell.tool.try_into().unwrap()) + mag.apply_mag_cell(mag_cell.tool.try_into().unwrap()).unwrap() } } }, @@ -99,6 +156,91 @@ impl InMemoryGateway { #[async_trait::async_trait] impl EntityGateway for InMemoryGateway { + async fn transaction<'a>(&'a mut self) -> Result, GatewayError> + { + let working_gateway = { + let users = self.users.lock().unwrap().clone(); + let user_settings = self.user_settings.lock().unwrap().clone(); + let characters = self.characters.lock().unwrap().clone(); + let character_meseta = self.character_meseta.lock().unwrap().clone(); + let bank_meseta = self.bank_meseta.lock().unwrap().clone(); + let items = self.items.lock().unwrap().clone(); + let inventories = self.inventories.lock().unwrap().clone(); + let banks = self.banks.lock().unwrap().clone(); + let equips = self.equips.lock().unwrap().clone(); + let mag_modifiers = self.mag_modifiers.lock().unwrap().clone(); + let weapon_modifiers = self.weapon_modifiers.lock().unwrap().clone(); + let trades = self.trades.lock().unwrap().clone(); + + InMemoryGateway { + users: Arc::new(Mutex::new(users)), + user_settings: Arc::new(Mutex::new(user_settings)), + characters: Arc::new(Mutex::new(characters)), + character_meseta: Arc::new(Mutex::new(character_meseta)), + bank_meseta: Arc::new(Mutex::new(bank_meseta)), + items: Arc::new(Mutex::new(items)), + inventories: Arc::new(Mutex::new(inventories)), + banks: Arc::new(Mutex::new(banks)), + equips: Arc::new(Mutex::new(equips)), + mag_modifiers: Arc::new(Mutex::new(mag_modifiers)), + weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)), + trades: Arc::new(Mutex::new(trades)), + } + }; + + Ok(Box::new(InMemoryGatewayTransaction { + working_gateway, + original_gateway: self, + })) + } + + + async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result + where + Fut: Future, R), E>> + Send + 'a, + F: FnOnce(Box) -> Fut + Send, + R: Send, + E: From, + { + let users = self.users.lock().unwrap().clone(); + let user_settings = self.user_settings.lock().unwrap().clone(); + let characters = self.characters.lock().unwrap().clone(); + let character_meseta = self.character_meseta.lock().unwrap().clone(); + let bank_meseta = self.bank_meseta.lock().unwrap().clone(); + let items = self.items.lock().unwrap().clone(); + let inventories = self.inventories.lock().unwrap().clone(); + let banks = self.banks.lock().unwrap().clone(); + let equips = self.equips.lock().unwrap().clone(); + let mag_modifiers = self.mag_modifiers.lock().unwrap().clone(); + let weapon_modifiers = self.weapon_modifiers.lock().unwrap().clone(); + let trades = self.trades.lock().unwrap().clone(); + + let working_gateway = InMemoryGateway { + users: Arc::new(Mutex::new(users)), + user_settings: Arc::new(Mutex::new(user_settings)), + characters: Arc::new(Mutex::new(characters)), + character_meseta: Arc::new(Mutex::new(character_meseta)), + bank_meseta: Arc::new(Mutex::new(bank_meseta)), + items: Arc::new(Mutex::new(items)), + inventories: Arc::new(Mutex::new(inventories)), + banks: Arc::new(Mutex::new(banks)), + equips: Arc::new(Mutex::new(equips)), + mag_modifiers: Arc::new(Mutex::new(mag_modifiers)), + weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)), + trades: Arc::new(Mutex::new(trades)), + }; + + let transaction = Box::new(InMemoryGatewayTransaction { + working_gateway, + original_gateway: self, + }); + + let (transaction, result) = func(transaction).await?; + + transaction.commit().await?; + Ok(result) + } + async fn create_user(&mut self, user: NewUserAccountEntity) -> Result { let mut users = self.users.lock().unwrap(); let id = users @@ -124,12 +266,12 @@ impl EntityGateway for InMemoryGateway { Ok(user) } - async fn get_user_by_id(&self, id: UserAccountId) -> Result { + async fn get_user_by_id(&mut self, id: UserAccountId) -> Result { let users = self.users.lock().unwrap(); users.get(&id).cloned().ok_or(GatewayError::Error) } - async fn get_user_by_name(&self, username: String) -> Result { + async fn get_user_by_name(&mut self, username: String) -> Result { let users = self.users.lock().unwrap(); users .iter() @@ -159,7 +301,7 @@ impl EntityGateway for InMemoryGateway { Ok(new_settings) } - async fn get_user_settings_by_user(&self, user: &UserAccountEntity) -> Result { + async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result { let user_settings = self.user_settings.lock().unwrap(); user_settings .iter() @@ -168,7 +310,7 @@ impl EntityGateway for InMemoryGateway { .ok_or(GatewayError::Error) } - async fn get_characters_by_user(&self, user: &UserAccountEntity) -> Result<[Option; 4], GatewayError> { + async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option; 4], GatewayError> { let characters = self.characters.lock().unwrap(); const NONE: Option = None; let mut chars = [NONE; 4]; @@ -215,7 +357,7 @@ impl EntityGateway for InMemoryGateway { Ok(()) } - async fn get_guild_card_data_by_user(&self, user: &UserAccountEntity) -> Result { + async fn get_guild_card_data_by_user(&mut self, user: &UserAccountEntity) -> Result { Ok(GuildCardDataEntity::new(user.id)) } @@ -281,7 +423,7 @@ impl EntityGateway for InMemoryGateway { .unwrap_or_default()) } - async fn get_character_bank(&mut self, char_id: &CharacterEntityId, _bank_name: BankName) -> Result { + async fn get_character_bank(&mut self, char_id: &CharacterEntityId, _bank_name: &BankName) -> Result { let banks = self.banks.lock().unwrap(); Ok(banks .iter() @@ -297,7 +439,7 @@ impl EntityGateway for InMemoryGateway { } // TOOD: impl bank name - async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, _bank_name: BankName) -> Result<(), GatewayError> { + async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, _bank_name: &BankName) -> Result<(), GatewayError> { let mut banks = self.banks.lock().unwrap(); banks.insert(*char_id, bank.clone()); Ok(()) @@ -334,19 +476,31 @@ impl EntityGateway for InMemoryGateway { } } - async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: BankName, meseta: Meseta) -> Result<(), GatewayError> { + async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> { let mut bank_meseta = self.bank_meseta.lock().unwrap(); - bank_meseta.insert((*char_id, bank), meseta); + bank_meseta.insert((*char_id, bank.clone()), meseta); Ok(()) } - async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: BankName) -> Result { + async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result { let mut bank_meseta = self.bank_meseta.lock().unwrap(); - if let Some(meseta) = bank_meseta.get_mut(&(*char_id, bank)) { + if let Some(meseta) = bank_meseta.get_mut(&(*char_id, bank.clone())) { Ok(*meseta) } else { Err(GatewayError::Error) } } + + async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result { + let mut trades = self.trades.lock().unwrap(); + let id = trades.len() as u32; + let new_trade = TradeEntity { + id: TradeId(id), + character1: *char_id1, + character2: *char_id2, + }; + trades.push(new_trade.clone()); + Ok(new_trade) + } } diff --git a/src/entity/gateway/mod.rs b/src/entity/gateway/mod.rs index d819ef5..4ce029d 100644 --- a/src/entity/gateway/mod.rs +++ b/src/entity/gateway/mod.rs @@ -2,6 +2,6 @@ pub mod entitygateway; pub mod inmemory; pub mod postgres; -pub use entitygateway::{EntityGateway, GatewayError}; +pub use entitygateway::{EntityGateway, EntityGatewayTransaction, GatewayError}; pub use inmemory::InMemoryGateway; pub use self::postgres::PostgresGateway; diff --git a/src/entity/gateway/postgres/migrations/V0005__trade.sql b/src/entity/gateway/postgres/migrations/V0005__trade.sql new file mode 100644 index 0000000..16d3b84 --- /dev/null +++ b/src/entity/gateway/postgres/migrations/V0005__trade.sql @@ -0,0 +1,5 @@ +create table trades ( + id serial primary key not null, + character1 integer references character (id) not null, + character2 integer references character (id) not null, +); diff --git a/src/entity/gateway/postgres/models.rs b/src/entity/gateway/postgres/models.rs index f2b8c94..c1257ee 100644 --- a/src/entity/gateway/postgres/models.rs +++ b/src/entity/gateway/postgres/models.rs @@ -591,6 +591,7 @@ pub enum PgItemNoteDetail { character_id: u32, }, PlayerDrop { + character_id: u32, map_area: MapArea, x: f32, y: f32, @@ -605,10 +606,18 @@ pub enum PgItemNoteDetail { }, SoldToShop, Trade { - id: u32, + trade_id: u32, character_to: u32, character_from: u32, }, + Withdraw { + character_id: u32, + bank: String, + }, + Deposit { + character_id: u32, + bank: String, + } } impl From for PgItemNoteDetail { @@ -625,7 +634,8 @@ impl From for PgItemNoteDetail { ItemNote::Pickup{character_id} => PgItemNoteDetail::Pickup { character_id: character_id.0, }, - ItemNote::PlayerDrop{map_area, x, y, z} => PgItemNoteDetail::PlayerDrop { + ItemNote::PlayerDrop{character_id, map_area, x, y, z} => PgItemNoteDetail::PlayerDrop { + character_id: character_id.0, map_area, x,y,z, }, @@ -637,10 +647,22 @@ impl From for PgItemNoteDetail { character_id: character_id.0, }, ItemNote::SoldToShop => PgItemNoteDetail::SoldToShop, - ItemNote::Trade{id, character_to, character_from} => PgItemNoteDetail::Trade { - id: id.0, + ItemNote::Trade{trade_id, character_to, character_from} => PgItemNoteDetail::Trade { + trade_id: trade_id.0, character_to: character_to.0, character_from: character_from.0, + }, + ItemNote::Withdraw{character_id, bank} => { + PgItemNoteDetail::Withdraw { + character_id: character_id.0, + bank: bank.0, + } + }, + ItemNote::Deposit{character_id, bank} => { + PgItemNoteDetail::Deposit { + character_id: character_id.0, + bank: bank.0, + } } } } @@ -660,7 +682,8 @@ impl From for ItemNote { PgItemNoteDetail::Pickup{character_id} => ItemNote::Pickup { character_id: CharacterEntityId(character_id as u32), }, - PgItemNoteDetail::PlayerDrop{map_area, x, y, z} => ItemNote::PlayerDrop { + PgItemNoteDetail::PlayerDrop{character_id, map_area, x, y, z} => ItemNote::PlayerDrop { + character_id: CharacterEntityId(character_id as u32), map_area, x,y,z, }, @@ -672,11 +695,19 @@ impl From for ItemNote { character_id: CharacterEntityId(character_id), }, PgItemNoteDetail::SoldToShop => ItemNote::SoldToShop, - PgItemNoteDetail::Trade {id, character_to, character_from} => ItemNote::Trade { - id: TradeId(id as u32), + PgItemNoteDetail::Trade {trade_id, character_to, character_from} => ItemNote::Trade { + trade_id: TradeId(trade_id as u32), character_to: CharacterEntityId(character_to as u32), character_from: CharacterEntityId(character_from as u32), - } + }, + PgItemNoteDetail::Withdraw{character_id, bank} => ItemNote::Withdraw { + character_id: CharacterEntityId(character_id as u32), + bank: BankName(bank), + }, + PgItemNoteDetail::Deposit{character_id, bank} => ItemNote::Deposit { + character_id: CharacterEntityId(character_id as u32), + bank: BankName(bank), + }, } } } @@ -827,3 +858,19 @@ impl From<(CharacterEntityId, EquippedEntity)> for PgEquipped { } } +#[derive(Debug, sqlx::FromRow)] +pub struct PgTradeEntity { + id: i32, + character1: i32, + character2: i32, +} + +impl From for TradeEntity { + fn from(other: PgTradeEntity) -> TradeEntity { + TradeEntity { + id: TradeId(other.id as u32), + character1: CharacterEntityId(other.character1 as u32), + character2: CharacterEntityId(other.character2 as u32), + } + } +} diff --git a/src/entity/gateway/postgres/postgres.rs b/src/entity/gateway/postgres/postgres.rs index 39be9ac..8987ab1 100644 --- a/src/entity/gateway/postgres/postgres.rs +++ b/src/entity/gateway/postgres/postgres.rs @@ -1,14 +1,16 @@ use std::convert::{From, TryFrom, Into}; -use futures::TryStreamExt; +use futures::{Future, TryStreamExt}; use async_std::stream::StreamExt; use libpso::character::guildcard; use crate::entity::account::*; use crate::entity::character::*; -use crate::entity::gateway::{EntityGateway, GatewayError}; +use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError}; use crate::entity::item::*; use super::models::*; use sqlx::postgres::PgPoolOptions; +use sqlx::Connection; + mod embedded { use refinery::embed_migrations; @@ -16,6 +18,24 @@ mod embedded { } +pub struct PostgresTransaction<'t> { + pgtransaction: sqlx::Transaction<'t, sqlx::Postgres>, +} + + +#[async_trait::async_trait] +impl<'t> EntityGatewayTransaction for PostgresTransaction<'t> { + fn gateway(&mut self) -> &mut dyn EntityGateway { + self + } + + async fn commit(self: Box) -> Result<(), GatewayError> { + self.pgtransaction.commit().await?; + Ok(()) + } +} + + #[derive(Clone)] pub struct PostgresGateway { pool: sqlx::Pool, @@ -31,252 +51,586 @@ impl PostgresGateway { embedded::migrations::runner().run(&mut conn).unwrap(); let pool = async_std::task::block_on(async move { - let pool = PgPoolOptions::new() + PgPoolOptions::new() .max_connections(5) - .connect(&format!("postgresql://{}:{}@{}:5432/{}", username, password, host, dbname)).await.unwrap(); - - pool + .connect(&format!("postgresql://{}:{}@{}:5432/{}", username, password, host, dbname)).await.unwrap() }); PostgresGateway { pool, } } +} - async fn apply_item_modifications(&self, item: ItemEntity) -> ItemEntity { - let ItemEntity {id, item} = item; - let item = match item { - ItemDetail::Weapon(mut weapon) => { - let q = r#"select weapon, modifier +// TODO: remove unwraps, return Result +async fn apply_item_modifications(conn: &mut sqlx::PgConnection, item: ItemEntity) -> ItemEntity +{ + let ItemEntity {id, item} = item; + + let item = match item { + ItemDetail::Weapon(mut weapon) => { + let q = r#"select weapon, modifier from weapon_modifier where weapon = $1 order by created_at"#; - let weapon_modifiers = sqlx::query_as::<_, PgWeaponModifier>(q) - .bind(id.0 as i32) - .fetch(&self.pool); + let weapon_modifiers = sqlx::query_as::<_, PgWeaponModifier>(q) + .bind(id.0 as i32) + .fetch(conn); - weapon_modifiers.for_each(|modifier| { - if let Ok(modifier) = modifier { - weapon.apply_modifier(&modifier.modifier); - } - }).await; + weapon_modifiers.for_each(|modifier| { + if let Ok(modifier) = modifier { + weapon.apply_modifier(&modifier.modifier); + } + }).await; - ItemDetail::Weapon(weapon) - }, - ItemDetail::Mag(mut mag) => { - let q = r#"select mag, modifier, item.item -> 'Tool' as feed, item2.item -> 'Tool' as cell + ItemDetail::Weapon(weapon) + }, + ItemDetail::Mag(mut mag) => { + let q = r#"select mag, modifier, item.item -> 'Tool' as feed, item2.item -> 'Tool' as cell from mag_modifier left join item on item.id = cast (modifier ->> 'FeedMag' as integer) left join item as item2 on item2.id = cast (modifier ->> 'MagCell' as integer) where mag = $1 order by created_at"#; - let mag_modifiers = sqlx::query_as::<_, PgMagModifierWithParameters>(q) - .bind(id.0 as i32) - .fetch(&self.pool); - - mag_modifiers.for_each(|modifier| { - let PgMagModifierWithParameters {modifier, feed, cell, ..} = modifier.unwrap(); - let modifier: mag::MagModifier = modifier.0.into(); - match modifier { - mag::MagModifier::FeedMag{..} => { - mag.feed(feed.unwrap().tool) - }, - mag::MagModifier::BankMag => { - mag.bank() - }, - mag::MagModifier::MagCell(_) => { - mag.apply_mag_cell(mag::MagCell::try_from(Into::::into(cell.unwrap().0).tool).unwrap()) - }, - mag::MagModifier::OwnerChange(class, section_id) => { - mag.change_owner(class, section_id) - }, - } - }).await; - - ItemDetail::Mag(mag) + let mag_modifiers = sqlx::query_as::<_, PgMagModifierWithParameters>(q) + .bind(id.0 as i32) + .fetch(conn); + + mag_modifiers.for_each(|modifier| { + let PgMagModifierWithParameters {modifier, feed, cell, ..} = modifier.unwrap(); + let modifier: mag::MagModifier = modifier.0.into(); + match modifier { + mag::MagModifier::FeedMag{..} => { + mag.feed(feed.unwrap().tool) + }, + mag::MagModifier::BankMag => { + mag.bank() + }, + mag::MagModifier::MagCell(_) => { + mag.apply_mag_cell(mag::MagCell::try_from(Into::::into(cell.unwrap().0).tool).unwrap()).unwrap() + }, + mag::MagModifier::OwnerChange(class, section_id) => { + mag.change_owner(class, section_id) + }, + } + }).await; + + ItemDetail::Mag(mag) + }, + item => item + }; + + ItemEntity { + id, + item, + } +} + +async fn create_user(conn: &mut sqlx::PgConnection, user: NewUserAccountEntity) -> Result +{ + let new_user = sqlx::query_as::<_, PgUserAccount>("insert into user_accounts (email, username, password, activated) values ($1, $2, $3, $4) returning *;") + .bind(user.email) + .bind(user.username) + .bind(user.password) + .bind(user.activated) + .fetch_one(conn).await?; + Ok(new_user.into()) +} + +async fn get_user_by_id(conn: &mut sqlx::PgConnection, id: UserAccountId) -> Result +{ + let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where id = $1") + .bind(id.0) + .fetch_one(conn).await?; + Ok(user.into()) +} + +async fn get_user_by_name(conn: &mut sqlx::PgConnection, username: String) -> Result +{ + let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where username = $1") + .bind(username) + .fetch_one(conn).await?; + Ok(user.into()) +} + + +async fn save_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<(), GatewayError> +{ + sqlx::query("UPDATE user_accounts set username=$1, password=$2, banned=$3, muted=$4, flags=$5 where id=$6") + .bind(&user.username) + .bind(&user.password) + .bind(&user.banned_until) + .bind(&user.muted_until) + .bind(&user.flags) + .bind(&user.id.0) + .execute(conn).await?; + Ok(()) +} + +async fn create_user_settings(conn: &mut sqlx::PgConnection, settings: NewUserSettingsEntity) -> Result +{ + let new_settings = sqlx::query_as::<_, PgUserSettings>("insert into user_settings (user_account, blocked_users, key_config, joystick_config, option_flags, shortcuts, symbol_chats, team_name) + values ($1, $2, $3, $4, $5, $6, $7, $8) returning *;") + .bind(settings.user_id.0) + .bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::>()) + .bind(settings.settings.keyboard_config.to_vec()) + .bind(settings.settings.gamepad_config.to_vec()) + .bind(settings.settings.option_flags as i32) + .bind(settings.settings.shortcuts.to_vec()) + .bind(settings.settings.symbol_chats.to_vec()) + .bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::>()) + .fetch_one(conn).await?; + Ok(new_settings.into()) +} + +async fn get_user_settings_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result +{ + let settings = sqlx::query_as::<_, PgUserSettings>("select * from user_settings where user_account = $1") + .bind(user.id.0) + .fetch_one(conn).await?; + Ok(settings.into()) +} + +async fn save_user_settings(conn: &mut sqlx::PgConnection, settings: &UserSettingsEntity) -> Result<(), GatewayError> +{ + sqlx::query("update user_settings set blocked_users=$1, key_config=$2, joystick_config=$3, option_flags=$4, shortcuts=$5, symbol_chats=$6, team_name=$7 where id=$8") + .bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::>()) + .bind(&settings.settings.keyboard_config.to_vec()) + .bind(&settings.settings.gamepad_config.to_vec()) + .bind(&settings.settings.option_flags) + .bind(&settings.settings.shortcuts.to_vec()) + .bind(&settings.settings.symbol_chats.to_vec()) + .bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::>()) + .bind(&settings.id.0) + .fetch_one(conn).await?; + Ok(()) +} + +async fn create_character(conn: &mut sqlx::PgConnection, char: NewCharacterEntity) -> Result +{ + let q = r#"insert into player_character + (user_account, slot, name, exp, class, section_id, costume, skin, face, head, hair, hair_r, hair_g, hair_b, prop_x, prop_y, techs, + config, infoboard, guildcard, power, mind, def, evade, luck, hp, tp, tech_menu, option_flags) + values + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31) + returning *;"#; + let character = sqlx::query_as::<_, PgCharacter>(q) + .bind(char.user_id.0) + .bind(char.slot as i16) + .bind(char.name) + .bind(char.exp as i32) + .bind(char.char_class.to_string()) + .bind(char.section_id.to_string()) + .bind(char.appearance.costume as i16) + .bind(char.appearance.skin as i16) + .bind(char.appearance.face as i16) + .bind(char.appearance.head as i16) + .bind(char.appearance.hair as i16) + .bind(char.appearance.hair_r as i16) + .bind(char.appearance.hair_g as i16) + .bind(char.appearance.hair_b as i16) + .bind(char.appearance.prop_x) + .bind(char.appearance.prop_y) + .bind(&char.techs.as_bytes().to_vec()) + .bind(&char.config.as_bytes().to_vec()) + .bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0))) + .bind(char.guildcard.description) + .bind(char.materials.power as i16) + .bind(char.materials.mind as i16) + .bind(char.materials.def as i16) + .bind(char.materials.evade as i16) + .bind(char.materials.luck as i16) + .bind(char.materials.hp as i16) + .bind(char.materials.tp as i16) + .bind(char.tech_menu.tech_menu.to_vec()) + .bind(char.option_flags as i32) + .fetch_one(conn).await?; + + Ok(character.into()) +} + +async fn get_characters_by_user(conn: &mut sqlx::PgConnection, user: &UserAccountEntity) -> Result<[Option; 4], GatewayError> +{ + let mut stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by slot") + .bind(user.id.0) + .fetch(conn); + const NONE: Option = None; + let mut result = [NONE; 4]; + while let Some(character) = stream.try_next().await? { + let index = character.slot as usize; + result[index] = Some(character.into()) + } + + Ok(result) +} + +async fn save_character(conn: &mut sqlx::PgConnection, char: &CharacterEntity) -> Result<(), GatewayError> +{ + let q = r#"update player_character set + user_account=$1, slot=$2, name=$3, exp=$4, class=$5, section_id=$6, costume=$7, skin=$8, face=$9, head=$10, hair=$11, hair_r=$12, + hair_g=$13, hair_b=$14, prop_x=$15, prop_y=$16, techs=$17, config=$18, infoboard=$19, guildcard=$20, power=$21, mind=$22, def=$23, + evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, option_flags=$29 + where id=$32;"#; + sqlx::query(q) + .bind(char.user_id.0) + .bind(char.slot as i16) + .bind(&char.name) + .bind(char.exp as i32) + .bind(char.char_class.to_string()) + .bind(char.section_id.to_string()) + .bind(char.appearance.costume as i16) + .bind(char.appearance.skin as i16) + .bind(char.appearance.face as i16) + .bind(char.appearance.head as i16) + .bind(char.appearance.hair as i16) + .bind(char.appearance.hair_r as i16) + .bind(char.appearance.hair_g as i16) + .bind(char.appearance.hair_b as i16) + .bind(char.appearance.prop_x) + .bind(char.appearance.prop_y) + .bind(&char.techs.as_bytes().to_vec()) + .bind(&char.config.as_bytes().to_vec()) + .bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0))) + .bind(&char.guildcard.description) + .bind(char.materials.power as i16) + .bind(char.materials.mind as i16) + .bind(char.materials.def as i16) + .bind(char.materials.evade as i16) + .bind(char.materials.luck as i16) + .bind(char.materials.hp as i16) + .bind(char.materials.tp as i16) + .bind(char.tech_menu.tech_menu.to_vec()) + .bind(char.option_flags as i32) + .bind(char.id.0 as i32) + .execute(conn).await?; + Ok(()) +} + +async fn create_item(conn: &mut sqlx::PgConnection, item: NewItemEntity) -> Result +{ + let new_item = sqlx::query_as::<_, PgItem>("insert into item (item) values ($1) returning *;") + .bind(sqlx::types::Json(PgItemDetail::from(item.item))) + .fetch_one(conn).await?; + + Ok(ItemEntity { + id: ItemEntityId(new_item.id as u32), + item: new_item.item.0.into(), + }) +} + +async fn add_item_note(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError> +{ + sqlx::query("insert into item_note(item, note) values ($1, $2)") + .bind(item_id.0) + .bind(sqlx::types::Json(PgItemNoteDetail::from(item_note))) + .execute(conn).await?; + Ok(()) +} + +async fn feed_mag(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> +{ + sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") + .bind(mag_item_id.0) + .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::FeedMag{food: *tool_item_id}))) + .execute(conn).await?; + Ok(()) +} + +async fn change_mag_owner(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> +{ + sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") + .bind(mag_item_id.0) + .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::OwnerChange(character.char_class, character.section_id)))) + .execute(conn).await?; + Ok(()) +} + +async fn use_mag_cell(conn: &mut sqlx::PgConnection, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> +{ + sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") + .bind(mag_item_id.0) + .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::MagCell(*mag_cell_id)))) + .execute(conn).await?; + Ok(()) +} + +async fn add_weapon_modifier(conn: &mut sqlx::PgConnection, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> +{ + sqlx::query("insert into weapon_modifier (weapon, modifier) values ($1, $2);") + .bind(item_id.0) + .bind(sqlx::types::Json(modifier)) + .execute(conn).await?; + Ok(()) +} + +async fn get_character_inventory(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId) -> Result +{ + let mut t = conn.begin().await?; + let inventory = sqlx::query_as::<_, PgInventoryEntity>("select * from inventory where pchar = $1") + .bind(char_id.0) + .fetch_one(&mut t).await?; + + // TODO: inefficient + let mut real_inventory = Vec::new(); + for inv_item in inventory.items.0.into_iter() { + match inv_item { + PgInventoryItemEntity::Individual(item) => { + let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1") + .bind(item) + .fetch_one(&mut t).await + .map(|item| item.into()) + .map(|item| apply_item_modifications(&mut t, item))? + .await; + real_inventory.push(InventoryItemEntity::Individual(entity)); }, - item => item - }; + PgInventoryItemEntity::Stacked(items) => { + let mut stacked_item = Vec::new(); + for s_item in items { + stacked_item.push(sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1") + .bind(s_item) + .fetch_one(&mut t).await + .map(|item| item.into())?) + } + real_inventory.push(InventoryItemEntity::Stacked(stacked_item)); + } + } + } + + Ok(InventoryEntity::new(real_inventory)) +} - ItemEntity { - id, - item, +async fn get_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank_name: &BankName) -> Result +{ + let mut t = conn.begin().await?; + let bank = sqlx::query_as::<_, PgInventoryEntity>("select * from bank where pchar = $1 and name = $2") + .bind(char_id.0) + .bind(bank_name.0.clone()) + .fetch_one(&mut t).await?; + // TODO: inefficient + let mut real_bank = Vec::new(); + for bank_item in bank.items.0.into_iter() { + match bank_item { + PgInventoryItemEntity::Individual(item) => { + let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1") + .bind(item) + .fetch_one(&mut t).await + .map(|item| item.into()) + .map(|item| apply_item_modifications(&mut t, item))? + .await; + real_bank.push(BankItemEntity::Individual(entity)); + }, + PgInventoryItemEntity::Stacked(items) => { + let mut stacked_item = Vec::new(); + for s_item in items { + stacked_item.push(sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1") + .bind(s_item) + .fetch_one(&mut t).await + .map(|item| item.into()) + .map(|item| apply_item_modifications(&mut t, item))? + .await) + } + real_bank.push(BankItemEntity::Stacked(stacked_item)); + } } } + + Ok(BankEntity::new(real_bank)) +} + +async fn set_character_inventory(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> +{ + let inventory = inventory.items.iter() + .map(|item| { + match item { + InventoryItemEntity::Individual(item) => { + PgInventoryItemEntity::Individual(item.id.0 as i32) + }, + InventoryItemEntity::Stacked(items) => { + PgInventoryItemEntity::Stacked(items.iter().map(|i| i.id.0 as i32).collect()) + }, + } + }) + .collect::>(); + + sqlx::query("insert into inventory (pchar, items) values ($1, $2) on conflict (pchar) do update set items = $2") + .bind(char_id.0) + .bind(sqlx::types::Json(inventory)) + .execute(conn) + .await?; + Ok(()) +} + +async fn set_character_bank(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankEntity, bank_name: &BankName) -> Result<(), GatewayError> +{ + let bank = bank.items.iter() + .map(|item| { + match item { + BankItemEntity::Individual(item) => { + PgInventoryItemEntity::Individual(item.id.0 as i32) + }, + BankItemEntity::Stacked(items) => { + PgInventoryItemEntity::Stacked(items.iter().map(|i| i.id.0 as i32).collect()) + }, + } + }) + .collect::>(); + + sqlx::query("insert into bank (pchar, items, name) values ($1, $2, $3) on conflict (pchar, name) do update set items = $2") + .bind(char_id.0) + .bind(sqlx::types::Json(bank)) + .bind(bank_name.0.clone()) + .execute(conn) + .await?; + Ok(()) +} + +async fn get_character_equips(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId) -> Result +{ + let equips = sqlx::query_as::<_, PgEquipped>("select * from equipped where pchar = $1") + .bind(char_id.0) + .fetch_one(conn) + .await?; + + Ok(equips.into()) +} + +async fn set_character_equips(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, equips: &EquippedEntity) -> Result<(), GatewayError> +{ + sqlx::query(r#"insert into equipped (pchar, weapon, armor, shield, unit0, unit1, unit2, unit3, mag) values ($1, $2, $3, $4, $5, $6, $7, $8, $9) + on conflict (pchar) do update set weapon=$2, armor=$3, shield=$4, unit0=$5, unit1=$6, unit2=$7, unit3=$8, mag=$9"#) + .bind(char_id.0) + .bind(equips.weapon.map(|i| i.0 as i32)) + .bind(equips.armor.map(|i| i.0 as i32)) + .bind(equips.shield.map(|i| i.0 as i32)) + .bind(equips.unit[0].map(|i| i.0 as i32)) + .bind(equips.unit[1].map(|i| i.0 as i32)) + .bind(equips.unit[2].map(|i| i.0 as i32)) + .bind(equips.unit[3].map(|i| i.0 as i32)) + .bind(equips.mag.map(|i| i.0 as i32)) + .execute(conn) + .await?; + Ok(()) +} + +async fn set_character_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> +{ + sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set items = $2") + .bind(char_id.0) + .bind(meseta.0 as i32) + .execute(conn) + .await?; + Ok(()) +} + +async fn get_character_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId) -> Result +{ + #[derive(sqlx::FromRow)] + struct PgMeseta(i32); + let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1"#) + .bind(char_id.0) + .fetch_one(conn) + .await?; + Ok(Meseta(meseta.0 as u32)) +} + +async fn set_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> +{ + sqlx::query("insert into bank_meseta values ($1, $2, $3) on conflict (pchar, bank) do update set items = $2") + .bind(char_id.0) + .bind(meseta.0 as i32) + .bind(bank.0.clone()) + .execute(conn) + .await?; + Ok(()) +} + +async fn get_bank_meseta(conn: &mut sqlx::PgConnection, char_id: &CharacterEntityId, bank: &BankName) -> Result +{ + #[derive(sqlx::FromRow)] + struct PgMeseta(i32); + let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1 and bank = $2"#) + .bind(char_id.0) + .bind(bank.0.clone()) + .fetch_one(conn) + .await?; + Ok(Meseta(meseta.0 as u32)) +} + +async fn create_trade(conn: &mut sqlx::PgConnection, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result +{ + let trade = sqlx::query_as::<_, PgTradeEntity>(r#"insert into trades (character1, character2) values ($1, $2) returning *;"#) + .bind(char_id1.0) + .bind(char_id2.0) + .fetch_one(conn) + .await?; + Ok(trade.into()) } #[async_trait::async_trait] impl EntityGateway for PostgresGateway { + async fn transaction<'a>(&'a mut self) -> Result, GatewayError> + { + Ok(Box::new(PostgresTransaction { + pgtransaction: self.pool.begin().await?, + })) + } + + async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result + where + Fut: Future, R), E>> + Send + 'a, + F: FnOnce(Box) -> Fut + Send, + R: Send, + E: From, + { + let transaction = Box::new(PostgresTransaction { + pgtransaction: self.pool.begin().await.map_err(|_| ()).unwrap() + }); + let (transaction, result) = func(transaction).await.map_err(|_| ()).unwrap(); + transaction.commit().await.map_err(|_| ()).unwrap(); + Ok(result) + } + async fn create_user(&mut self, user: NewUserAccountEntity) -> Result { - let new_user = sqlx::query_as::<_, PgUserAccount>("insert into user_accounts (email, username, password, activated) values ($1, $2, $3, $4) returning *;") - .bind(user.email) - .bind(user.username) - .bind(user.password) - .bind(user.activated) - .fetch_one(&self.pool).await?; - Ok(new_user.into()) + create_user(&mut *self.pool.acquire().await?, user).await } - async fn get_user_by_id(&self, id: UserAccountId) -> Result { - let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where id = $1") - .bind(id.0) - .fetch_one(&self.pool).await?; - Ok(user.into()) + async fn get_user_by_id(&mut self, id: UserAccountId) -> Result { + get_user_by_id(&mut *self.pool.acquire().await?, id).await } - async fn get_user_by_name(&self, username: String) -> Result { - let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where username = $1") - .bind(username) - .fetch_one(&self.pool).await?; - Ok(user.into()) + async fn get_user_by_name(&mut self, username: String) -> Result { + get_user_by_name(&mut *self.pool.acquire().await?, username).await } async fn save_user(&mut self, user: &UserAccountEntity) -> Result<(), GatewayError> { - sqlx::query("UPDATE user_accounts set username=$1, password=$2, banned=$3, muted=$4, flags=$5 where id=$6") - .bind(&user.username) - .bind(&user.password) - .bind(&user.banned_until) - .bind(&user.muted_until) - .bind(&user.flags) - .bind(&user.id.0) - .execute(&self.pool).await?; - Ok(()) + save_user(&mut *self.pool.acquire().await?, user).await } async fn create_user_settings(&mut self, settings: NewUserSettingsEntity) -> Result { - let new_settings = sqlx::query_as::<_, PgUserSettings>("insert into user_settings (user_account, blocked_users, keyboard_config, gamepad_config, option_flags, shortcuts, symbol_chats, team_name) - values ($1, $2, $3, $4, $5, $6, $7, $8) returning *;") - .bind(settings.user_id.0) - .bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::>()) - .bind(settings.settings.keyboard_config.to_vec()) - .bind(settings.settings.gamepad_config.to_vec()) - .bind(settings.settings.option_flags as i32) - .bind(settings.settings.shortcuts.to_vec()) - .bind(settings.settings.symbol_chats.to_vec()) - .bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::>()) - .fetch_one(&self.pool).await?; - Ok(new_settings.into()) - } - - async fn get_user_settings_by_user(&self, user: &UserAccountEntity) -> Result { - let settings = sqlx::query_as::<_, PgUserSettings>("select * from user_settings where user_account = $1") - .bind(user.id.0) - .fetch_one(&self.pool).await?; - Ok(settings.into()) + create_user_settings(&mut *self.pool.acquire().await?, settings).await + } + + async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result { + get_user_settings_by_user(&mut *self.pool.acquire().await?, user).await } async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> { - sqlx::query("update user_settings set blocked_users=$1, keyboard_config=$2, gamepad_config=$3, option_flags=$4, shortcuts=$5, symbol_chats=$6, team_name=$7 where id=$8") - .bind(settings.settings.blocked_users.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::>()) - .bind(&settings.settings.keyboard_config.to_vec()) - .bind(&settings.settings.gamepad_config.to_vec()) - .bind(&settings.settings.option_flags) - .bind(&settings.settings.shortcuts.to_vec()) - .bind(&settings.settings.symbol_chats.to_vec()) - .bind(settings.settings.team_name.iter().copied().flat_map(|i| i.to_le_bytes().to_vec()).collect::>()) - .bind(&settings.id.0) - .fetch_one(&self.pool).await?; - Ok(()) + save_user_settings(&mut *self.pool.acquire().await?, settings).await } async fn create_character(&mut self, char: NewCharacterEntity) -> Result { - let q = r#"insert into player_character - (user_account, slot, name, exp, class, section_id, costume, skin, face, head, hair, hair_r, hair_g, hair_b, prop_x, prop_y, techs, - config, infoboard, guildcard, power, mind, def, evade, luck, hp, tp, tech_menu, option_flags) - values - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31) - returning *;"#; - let character = sqlx::query_as::<_, PgCharacter>(q) - .bind(char.user_id.0) - .bind(char.slot as i16) - .bind(char.name) - .bind(char.exp as i32) - .bind(char.char_class.to_string()) - .bind(char.section_id.to_string()) - .bind(char.appearance.costume as i16) - .bind(char.appearance.skin as i16) - .bind(char.appearance.face as i16) - .bind(char.appearance.head as i16) - .bind(char.appearance.hair as i16) - .bind(char.appearance.hair_r as i16) - .bind(char.appearance.hair_g as i16) - .bind(char.appearance.hair_b as i16) - .bind(char.appearance.prop_x) - .bind(char.appearance.prop_y) - .bind(&char.techs.as_bytes().to_vec()) - .bind(&char.config.as_bytes().to_vec()) - .bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0))) - .bind(char.guildcard.description) - .bind(char.materials.power as i16) - .bind(char.materials.mind as i16) - .bind(char.materials.def as i16) - .bind(char.materials.evade as i16) - .bind(char.materials.luck as i16) - .bind(char.materials.hp as i16) - .bind(char.materials.tp as i16) - .bind(char.tech_menu.tech_menu.to_vec()) - .bind(char.option_flags as i32) - .fetch_one(&self.pool).await?; - - Ok(character.into()) - } - - async fn get_characters_by_user(&self, user: &UserAccountEntity) -> Result<[Option; 4], GatewayError> { - let mut stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by slot") - .bind(user.id.0) - .fetch(&self.pool); - const NONE: Option = None; - let mut result = [NONE; 4]; - while let Some(character) = stream.try_next().await? { - let index = character.slot as usize; - result[index] = Some(character.into()) - } + create_character(&mut *self.pool.acquire().await?, char).await + } - Ok(result) + async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option; 4], GatewayError> { + get_characters_by_user(&mut *self.pool.acquire().await?, user).await } async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> { - let q = r#"update player_character set - user_account=$1, slot=$2, name=$3, exp=$4, class=$5, section_id=$6, costume=$7, skin=$8, face=$9, head=$10, hair=$11, hair_r=$12, - hair_g=$13, hair_b=$14, prop_x=$15, prop_y=$16, techs=$17, config=$18, infoboard=$19, guildcard=$20, power=$21, mind=$22, def=$23, - evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, option_flags=$29 - where id=$32;"#; - sqlx::query(q) - .bind(char.user_id.0) - .bind(char.slot as i16) - .bind(&char.name) - .bind(char.exp as i32) - .bind(char.char_class.to_string()) - .bind(char.section_id.to_string()) - .bind(char.appearance.costume as i16) - .bind(char.appearance.skin as i16) - .bind(char.appearance.face as i16) - .bind(char.appearance.head as i16) - .bind(char.appearance.hair as i16) - .bind(char.appearance.hair_r as i16) - .bind(char.appearance.hair_g as i16) - .bind(char.appearance.hair_b as i16) - .bind(char.appearance.prop_x) - .bind(char.appearance.prop_y) - .bind(&char.techs.as_bytes().to_vec()) - .bind(&char.config.as_bytes().to_vec()) - .bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0))) - .bind(&char.guildcard.description) - .bind(char.materials.power as i16) - .bind(char.materials.mind as i16) - .bind(char.materials.def as i16) - .bind(char.materials.evade as i16) - .bind(char.materials.luck as i16) - .bind(char.materials.hp as i16) - .bind(char.materials.tp as i16) - .bind(char.tech_menu.tech_menu.to_vec()) - .bind(char.option_flags as i32) - .bind(char.id.0 as i32) - .execute(&self.pool).await?; - Ok(()) + save_character(&mut *self.pool.acquire().await?, char).await } - async fn get_guild_card_data_by_user(&self, user: &UserAccountEntity) -> Result { + async fn get_guild_card_data_by_user(&mut self, user: &UserAccountEntity) -> Result { Ok(GuildCardDataEntity { id: GuildCardDataId(0), user_id: user.id, @@ -285,305 +639,191 @@ impl EntityGateway for PostgresGateway { } async fn create_item(&mut self, item: NewItemEntity) -> Result { - let mut tx = self.pool.begin().await?; - let new_item = sqlx::query_as::<_, PgItem>("insert into item (item) values ($1) returning *;") - .bind(sqlx::types::Json(PgItemDetail::from(item.item))) - .fetch_one(&mut tx).await?; - - tx.commit().await?; - Ok(ItemEntity { - id: ItemEntityId(new_item.id as u32), - item: new_item.item.0.into(), - }) + create_item(&mut *self.pool.acquire().await?, item).await } async fn add_item_note(&mut self, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError> { - sqlx::query("insert into item_note(item, note) values ($1, $2)") - .bind(item_id.0) - .bind(sqlx::types::Json(PgItemNoteDetail::from(item_note))) - .execute(&self.pool).await?; - Ok(()) - - /* - let mut tx = self.pool.begin().await?; - if let ItemLocation::Inventory{slot, ..} = &item_location { - sqlx::query("delete from inventory_slot where item = $1") - .bind(item_id.0 as i32) - .execute(&mut tx).await?; - sqlx::query("insert into inventory_slot (item, slot) values ($1, $2)") - .bind(item_id.0) - .bind(*slot as i32) - .execute(&mut tx).await?; - sqlx::query(r#"insert into item_location (item, location) - select $1, $2 - where (select jsonb_object_keys(location) from item_location where item=$1 - order by created_at desc limit 1) != 'Inventory'"#) - .bind(item_id.0) - .bind(sqlx::types::Json(PgItemLocationDetail::from(item_location))) - .execute(&mut tx).await?; - } - else { - sqlx::query("insert into item_location (item, location) values ($1, $2)") - .bind(item_id.0) - .bind(sqlx::types::Json(PgItemLocationDetail::from(item_location))) - .execute(&mut tx).await?; - } - tx.commit().await?; - Ok(()) - */ + add_item_note(&mut *self.pool.acquire().await?, item_id, item_note).await } async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> { - sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") - .bind(mag_item_id.0) - .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::FeedMag{food: *tool_item_id}))) - .execute(&self.pool).await?; - Ok(()) + feed_mag(&mut *self.pool.acquire().await?, mag_item_id, tool_item_id).await } async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> { - sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") - .bind(mag_item_id.0) - .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::OwnerChange(character.char_class, character.section_id)))) - .execute(&self.pool).await?; - Ok(()) + change_mag_owner(&mut *self.pool.acquire().await?, mag_item_id, character).await } async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> { - sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);") - .bind(mag_item_id.0) - .bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::MagCell(*mag_cell_id)))) - .execute(&self.pool).await?; - Ok(()) + use_mag_cell(&mut *self.pool.acquire().await?, mag_item_id, mag_cell_id).await } async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> { - sqlx::query("insert into weapon_modifier (weapon, modifier) values ($1, $2);") - .bind(item_id.0) - .bind(sqlx::types::Json(modifier)) - .execute(&self.pool).await?; - Ok(()) + add_weapon_modifier(&mut *self.pool.acquire().await?, item_id, modifier).await } -/* - async fn get_items_by_character(&self, char_id: &CharacterEntityId) -> Result, GatewayError> { - let q = r#"select * from ( - select distinct on (item_location.item) - item.id, - case - when item_location.location -> 'Inventory' is not null then - jsonb_set(item_location.location, '{Inventory,slot}', inventory_slot.slot::text::jsonb) - else - item_location.location - end, - item.item - from item_location - join item on item.id = item_location.item - join inventory_slot on inventory_slot.item = item.id - order by item_location.item, item_location.created_at desc - ) as i - where cast (location -> 'Inventory' ->> 'character_id' as integer) = $1 - or cast (location -> 'Bank' ->> 'character_id' as integer) = $1 - "#; - let items = sqlx::query_as::<_, PgItemWithLocation>(q) - .bind(char_id.0) - .fetch(&self.pool); - Ok(join_all(items - .filter_map(|item: Result| { - let item = item.ok()?; - Some(ItemEntity { - id: ItemEntityId(item.id as u32), - item: item.item.0.into(), - location: item.location.0.into() - }) - }) - .map(|item: ItemEntity| { - self.apply_item_modifications(item) - }) - .collect::>() - .await - ).await) - } -*/ async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result { - let inventory = sqlx::query_as::<_, PgInventoryEntity>("select * from inventory where pchar = $1") - .bind(char_id.0) - .fetch_one(&self.pool).await?; - // TODO: inefficient - let mut real_inventory = Vec::new(); - for inv_item in inventory.items.0.into_iter() { - match inv_item { - PgInventoryItemEntity::Individual(item) => { - let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1") - .bind(item) - .fetch_one(&self.pool).await - .map(|item| item.into()) - .map(|item| self.apply_item_modifications(item))? - .await; - real_inventory.push(InventoryItemEntity::Individual(entity)); - }, - PgInventoryItemEntity::Stacked(items) => { - let mut stacked_item = Vec::new(); - for s_item in items { - stacked_item.push(sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1") - .bind(s_item) - .fetch_one(&self.pool).await - .map(|item| item.into()) - .map(|item| self.apply_item_modifications(item))? - .await) - } - real_inventory.push(InventoryItemEntity::Stacked(stacked_item)); - } - } - } - - Ok(InventoryEntity::new(real_inventory)) - } - - async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_name: BankName) -> Result { - let bank = sqlx::query_as::<_, PgInventoryEntity>("select * from bank where pchar = $1 and name = $2") - .bind(char_id.0) - .bind(bank_name.0) - .fetch_one(&self.pool).await?; - // TODO: inefficient - let mut real_bank = Vec::new(); - for bank_item in bank.items.0.into_iter() { - match bank_item { - PgInventoryItemEntity::Individual(item) => { - let entity = sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1") - .bind(item) - .fetch_one(&self.pool).await - .map(|item| item.into()) - .map(|item| self.apply_item_modifications(item))? - .await; - real_bank.push(BankItemEntity::Individual(entity)); - }, - PgInventoryItemEntity::Stacked(items) => { - let mut stacked_item = Vec::new(); - for s_item in items { - stacked_item.push(sqlx::query_as::<_, PgItemEntity>("select item.id, item.item from item where id = $1") - .bind(s_item) - .fetch_one(&self.pool).await - .map(|item| item.into()) - .map(|item| self.apply_item_modifications(item))? - .await) - } - real_bank.push(BankItemEntity::Stacked(stacked_item)); - } - } - } - - Ok(BankEntity::new(real_bank)) + get_character_inventory(&mut *self.pool.acquire().await?, char_id).await + } + + async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_name: &BankName) -> Result { + get_character_bank(&mut *self.pool.acquire().await?, char_id, bank_name).await } async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> { - let inventory = inventory.items.iter() - .map(|item| { - match item { - InventoryItemEntity::Individual(item) => { - PgInventoryItemEntity::Individual(item.id.0 as i32) - }, - InventoryItemEntity::Stacked(items) => { - PgInventoryItemEntity::Stacked(items.iter().map(|i| i.id.0 as i32).collect()) - }, - } - }) - .collect::>(); - - sqlx::query("insert into inventory (pchar, items) values ($1, $2) on conflict (pchar) do update set items = $2") - .bind(char_id.0) - .bind(sqlx::types::Json(inventory)) - .execute(&self.pool) - .await?; - Ok(()) + set_character_inventory(&mut *self.pool.acquire().await?, char_id, inventory).await } - async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_name: BankName) -> Result<(), GatewayError> { - let bank = bank.items.iter() - .map(|item| { - match item { - BankItemEntity::Individual(item) => { - PgInventoryItemEntity::Individual(item.id.0 as i32) - }, - BankItemEntity::Stacked(items) => { - PgInventoryItemEntity::Stacked(items.iter().map(|i| i.id.0 as i32).collect()) - }, - } - }) - .collect::>(); - - sqlx::query("insert into bank (pchar, items, name) values ($1, $2, $3) on conflict (pchar, name) do update set items = $2") - .bind(char_id.0) - .bind(sqlx::types::Json(bank)) - .bind(bank_name.0) - .execute(&self.pool) - .await?; - Ok(()) + async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_name: &BankName) -> Result<(), GatewayError> { + set_character_bank(&mut *self.pool.acquire().await?, char_id, bank, bank_name).await } async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result { - let equips = sqlx::query_as::<_, PgEquipped>("select * from equipped where pchar = $1") - .bind(char_id.0) - .fetch_one(&self.pool) - .await?; + get_character_equips(&mut *self.pool.acquire().await?, char_id).await + } + + async fn set_character_equips(&mut self, char_id: &CharacterEntityId, equips: &EquippedEntity) -> Result<(), GatewayError> { + set_character_equips(&mut *self.pool.acquire().await?, char_id, equips).await + } + + async fn set_character_meseta(&mut self, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> { + set_character_meseta(&mut *self.pool.acquire().await?, char_id, meseta).await + } + + async fn get_character_meseta(&mut self, char_id: &CharacterEntityId) -> Result { + get_character_meseta(&mut *self.pool.acquire().await?, char_id).await + } + + async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> { + set_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank, meseta).await + } + + async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result { + get_bank_meseta(&mut *self.pool.acquire().await?, char_id, bank).await + } + + async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result { + create_trade(&mut *self.pool.acquire().await?, char_id1, char_id2).await + } +} + + +#[async_trait::async_trait] +impl<'c> EntityGateway for PostgresTransaction<'c> { + async fn create_user(&mut self, user: NewUserAccountEntity) -> Result { + create_user(&mut *self.pgtransaction, user).await + } + + async fn get_user_by_id(&mut self, id: UserAccountId) -> Result { + get_user_by_id(&mut *self.pgtransaction, id).await + } + + async fn get_user_by_name(&mut self, username: String) -> Result { + get_user_by_name(&mut *self.pgtransaction, username).await + } + + async fn save_user(&mut self, user: &UserAccountEntity) -> Result<(), GatewayError> { + save_user(&mut *self.pgtransaction, user).await + } + + async fn create_user_settings(&mut self, settings: NewUserSettingsEntity) -> Result { + create_user_settings(&mut *self.pgtransaction, settings).await + } + + async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result { + get_user_settings_by_user(&mut *self.pgtransaction, user).await + } + + async fn save_user_settings(&mut self, settings: &UserSettingsEntity) -> Result<(), GatewayError> { + save_user_settings(&mut *self.pgtransaction, settings).await + } + + async fn create_character(&mut self, char: NewCharacterEntity) -> Result { + create_character(&mut *self.pgtransaction, char).await + } + + async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option; 4], GatewayError> { + get_characters_by_user(&mut *self.pgtransaction, user).await + } + + async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> { + save_character(&mut *self.pgtransaction, char).await + } + + async fn get_guild_card_data_by_user(&mut self, user: &UserAccountEntity) -> Result { + Ok(GuildCardDataEntity { + id: GuildCardDataId(0), + user_id: user.id, + guildcard: guildcard::GuildCardData::default(), + }) + } + + async fn create_item(&mut self, item: NewItemEntity) -> Result { + create_item(&mut *self.pgtransaction, item).await + } + + async fn add_item_note(&mut self, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError> { + add_item_note(&mut *self.pgtransaction, item_id, item_note).await + } + + async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> { + feed_mag(&mut *self.pgtransaction, mag_item_id, tool_item_id).await + } + + async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> { + change_mag_owner(&mut *self.pgtransaction, mag_item_id, character).await + } + + async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> { + use_mag_cell(&mut *self.pgtransaction, mag_item_id, mag_cell_id).await + } + + async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> { + add_weapon_modifier(&mut *self.pgtransaction, item_id, modifier).await + } + + async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result { + get_character_inventory(&mut *self.pgtransaction, char_id).await + } + + async fn get_character_bank(&mut self, char_id: &CharacterEntityId, bank_name: &BankName) -> Result { + get_character_bank(&mut *self.pgtransaction, char_id, bank_name).await + } - Ok(equips.into()) + async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> { + set_character_inventory(&mut *self.pgtransaction, char_id, inventory).await + } + + async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, bank_name: &BankName) -> Result<(), GatewayError> { + set_character_bank(&mut *self.pgtransaction, char_id, bank, bank_name).await + } + + async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result { + get_character_equips(&mut *self.pgtransaction, char_id).await } async fn set_character_equips(&mut self, char_id: &CharacterEntityId, equips: &EquippedEntity) -> Result<(), GatewayError> { - sqlx::query(r#"insert into equipped (pchar, weapon, armor, shield, unit0, unit1, unit2, unit3, mag) values ($1, $2, $3, $4, $5, $6, $7, $8, $9) - on conflict (pchar) do update set weapon=$2, armor=$3, shield=$4, unit0=$5, unit1=$6, unit2=$7, unit3=$8, mag=$9"#) - .bind(char_id.0) - .bind(equips.weapon.map(|i| i.0 as i32)) - .bind(equips.armor.map(|i| i.0 as i32)) - .bind(equips.shield.map(|i| i.0 as i32)) - .bind(equips.unit[0].map(|i| i.0 as i32)) - .bind(equips.unit[1].map(|i| i.0 as i32)) - .bind(equips.unit[2].map(|i| i.0 as i32)) - .bind(equips.unit[3].map(|i| i.0 as i32)) - .bind(equips.mag.map(|i| i.0 as i32)) - .execute(&self.pool) - .await?; - Ok(()) + set_character_equips(&mut *self.pgtransaction, char_id, equips).await } async fn set_character_meseta(&mut self, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> { - sqlx::query("insert into character_meseta values ($1, $2) on conflict (pchar) do update set items = $2") - .bind(char_id.0) - .bind(meseta.0 as i32) - .execute(&self.pool) - .await?; - Ok(()) + set_character_meseta(&mut *self.pgtransaction, char_id, meseta).await } async fn get_character_meseta(&mut self, char_id: &CharacterEntityId) -> Result { - #[derive(sqlx::FromRow)] - struct PgMeseta(i32); - let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1"#) - .bind(char_id.0) - .fetch_one(&self.pool) - .await?; - Ok(Meseta(meseta.0 as u32)) - } - - async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: BankName, meseta: Meseta) -> Result<(), GatewayError> { - sqlx::query("insert into bank_meseta values ($1, $2, $3) on conflict (pchar, bank) do update set items = $2") - .bind(char_id.0) - .bind(meseta.0 as i32) - .bind(bank.0) - .execute(&self.pool) - .await?; - Ok(()) + get_character_meseta(&mut *self.pgtransaction, char_id).await + } + + async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> { + set_bank_meseta(&mut *self.pgtransaction, char_id, bank, meseta).await } - async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: BankName) -> Result { - #[derive(sqlx::FromRow)] - struct PgMeseta(i32); - let meseta = sqlx::query_as::<_, PgMeseta>(r#"select meseta from character_meseta where id = $1 and bank = $2"#) - .bind(char_id.0) - .bind(bank.0) - .fetch_one(&self.pool) - .await?; - Ok(Meseta(meseta.0 as u32)) + async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result { + get_bank_meseta(&mut *self.pgtransaction, char_id, bank).await + } + + async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result { + create_trade(&mut *self.pgtransaction, char_id1, char_id2).await } } + diff --git a/src/entity/item/armor.rs b/src/entity/item/armor.rs index 451852b..45e9ed4 100644 --- a/src/entity/item/armor.rs +++ b/src/entity/item/armor.rs @@ -289,7 +289,7 @@ impl ArmorType { } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ArmorModifier { AddSlot { addslot: ItemEntityId, @@ -297,7 +297,7 @@ pub enum ArmorModifier { } -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Armor { pub armor: ArmorType, pub dfp: u8, diff --git a/src/entity/item/esweapon.rs b/src/entity/item/esweapon.rs index 3a88ccd..2997780 100644 --- a/src/entity/item/esweapon.rs +++ b/src/entity/item/esweapon.rs @@ -121,7 +121,7 @@ impl ESWeaponType { } } -#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, strum_macros::EnumIter)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, strum_macros::EnumIter)] pub enum ESWeaponSpecial { Jellen = 1, Zalure, @@ -169,7 +169,7 @@ impl ESWeaponSpecial { } } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ESWeapon { pub esweapon: ESWeaponType, pub special: Option, diff --git a/src/entity/item/mag.rs b/src/entity/item/mag.rs index 64efc2e..dbdb4a7 100644 --- a/src/entity/item/mag.rs +++ b/src/entity/item/mag.rs @@ -1,3 +1,4 @@ +use thiserror::Error; use std::collections::HashMap; use serde::{Serialize, Deserialize}; use crate::entity::item::tool::ToolType; @@ -419,9 +420,9 @@ pub enum MagCell { } impl std::convert::TryFrom for MagCell { - type Error = (); + type Error = MagCellError; - fn try_from(tool: ToolType) -> Result { + fn try_from(tool: ToolType) -> Result { match tool { ToolType::CellOfMag502 => Ok(MagCell::CellOfMag502), ToolType::CellOfMag213 => Ok(MagCell::CellOfMag213), @@ -448,7 +449,7 @@ impl std::convert::TryFrom for MagCell { ToolType::YahoosEngine => Ok(MagCell::YahoosEngine), ToolType::DPhotonCore => Ok(MagCell::DPhotonCore), ToolType::LibertaKit => Ok(MagCell::LibertaKit), - _ => Err(()), + _ => Err(MagCellError::IsNotMagCell), } } } @@ -509,7 +510,16 @@ impl MagAttributeOrdering { } } -#[derive(Debug, Clone, PartialEq)] + +#[derive(Error, Debug)] +pub enum MagCellError { + #[error("not a mag cell")] + IsNotMagCell, + #[error("mag is rare")] + IsRareMag, +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub enum MagModifier { FeedMag{ food: ItemEntityId, @@ -519,7 +529,7 @@ pub enum MagModifier { OwnerChange(CharacterClass, SectionID) } -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize, enum_utils::FromStr)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, enum_utils::FromStr)] pub enum PhotonBlast { Farlla, Estlla, @@ -529,7 +539,7 @@ pub enum PhotonBlast { MyllaYoulla, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Mag { pub mag: MagType, def: u16, @@ -1047,7 +1057,10 @@ impl Mag { } // TODO: this needs more checks on validity - pub fn apply_mag_cell(&mut self, mag_cell: MagCell) { + pub fn apply_mag_cell(&mut self, mag_cell: MagCell) -> Result<(), MagCellError> { + if self.is_rare_item() { + return Err(MagCellError::IsRareMag) + } self.mag = match mag_cell { MagCell::CellOfMag502 => { match self.id { @@ -1097,11 +1110,11 @@ impl Mag { MagCell::YahoosEngine => MagType::Yahoo, MagCell::DPhotonCore => MagType::GaelGiel, MagCell::LibertaKit => MagType::Agastya, - } + }; + Ok(()) } - // TODO: is this even needed? mags are not shop sellable...yet - pub fn is_rare_item(self) -> bool { + pub fn is_rare_item(&self) -> bool { matches!( self.mag, MagType::Pitri diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index c840034..a1f78bd 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -13,13 +13,13 @@ use crate::entity::character::CharacterEntityId; use crate::ship::map::MapArea; use crate::ship::drops::ItemDropType; -#[derive(PartialEq, Copy, Clone, Debug, Hash, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord, Serialize, Deserialize)] pub struct ItemEntityId(pub u32); #[derive(Hash, PartialEq, Eq, Debug, Clone)] pub struct ItemId(u32); #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, derive_more::Display)] pub struct BankName(pub String); -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] pub struct TradeId(pub u32); #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -40,13 +40,15 @@ pub enum ItemNote { character_id: CharacterEntityId, }, PlayerDrop { + character_id: CharacterEntityId, map_area: MapArea, x: f32, y: f32, z: f32, }, - Consumed, + Consumed, // TODO: character_id FedToMag { + //character_id: CharacterEntityId, mag: ItemEntityId, }, BoughtAtShop { @@ -54,13 +56,21 @@ pub enum ItemNote { }, SoldToShop, Trade { - id: TradeId, + trade_id: TradeId, character_to: CharacterEntityId, character_from: CharacterEntityId, }, + Withdraw { + character_id: CharacterEntityId, + bank: BankName, + }, + Deposit { + character_id: CharacterEntityId, + bank: BankName, + }, } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Meseta(pub u32); impl Meseta { @@ -85,12 +95,12 @@ pub enum ItemType { ESWeapon(esweapon::ESWeaponType), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum ItemParseError { InvalidBytes } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum ItemDetail { Weapon(weapon::Weapon), Armor(armor::Armor), @@ -176,7 +186,7 @@ pub struct NewItemEntity { pub item: ItemDetail, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ItemEntity { pub id: ItemEntityId, pub item: ItemDetail, @@ -317,3 +327,15 @@ impl BankEntity { } } } + +#[derive(Clone, Debug)] +pub struct TradeEntity { + pub id: TradeId, + pub character1: CharacterEntityId, + pub character2: CharacterEntityId, +} + +#[derive(Clone, Debug)] +pub enum ItemModifier { + WeaponModifier(weapon::WeaponModifier), +} diff --git a/src/entity/item/shield.rs b/src/entity/item/shield.rs index 5c42598..4161c19 100644 --- a/src/entity/item/shield.rs +++ b/src/entity/item/shield.rs @@ -519,7 +519,7 @@ impl ShieldType { } -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Shield { pub shield: ShieldType, pub dfp: u8, diff --git a/src/entity/item/tool.rs b/src/entity/item/tool.rs index f24f596..3826bbd 100644 --- a/src/entity/item/tool.rs +++ b/src/entity/item/tool.rs @@ -642,7 +642,7 @@ impl ToolType { } -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Tool { pub tool: ToolType, } diff --git a/src/entity/item/unit.rs b/src/entity/item/unit.rs index 371f450..5dc73b6 100644 --- a/src/entity/item/unit.rs +++ b/src/entity/item/unit.rs @@ -323,7 +323,7 @@ impl UnitType { } } -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum UnitModifier { PlusPlus, Plus, @@ -331,7 +331,7 @@ pub enum UnitModifier { MinusMinus, } -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Unit { pub unit: UnitType, pub modifier: Option, diff --git a/src/entity/item/weapon.rs b/src/entity/item/weapon.rs index 11a8709..8e9db4b 100644 --- a/src/entity/item/weapon.rs +++ b/src/entity/item/weapon.rs @@ -10,7 +10,7 @@ pub enum ItemParseError { InvalidWeaponAttribute, } -#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, Ord, PartialOrd, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] pub enum Attribute { Native = 1, ABeast, @@ -32,7 +32,7 @@ impl Attribute { } } -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct WeaponAttribute { pub attr: Attribute, pub value: i8, @@ -45,7 +45,7 @@ impl WeaponAttribute { } -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize, strum_macros::EnumIter)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, strum_macros::EnumIter)] pub enum WeaponSpecial { Draw = 1, Drain, @@ -1424,14 +1424,14 @@ impl WeaponType { } -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum TekSpecialModifier { Plus, Neutral, Minus, } -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum TekPercentModifier { PlusPlus, Plus, @@ -1440,7 +1440,7 @@ pub enum TekPercentModifier { MinusMinus, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum WeaponModifier { AddPercents { attr: WeaponAttribute, @@ -1457,7 +1457,7 @@ pub enum WeaponModifier { }, } -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Weapon { pub weapon: WeaponType, pub special: Option, diff --git a/src/lib.rs b/src/lib.rs index b8ceb02..d908e0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ #![feature(drain_filter)] #![feature(try_blocks)] +extern crate fix_hidden_lifetime_bug; pub mod common; diff --git a/src/login/character.rs b/src/login/character.rs index f1192eb..ed2045a 100644 --- a/src/login/character.rs +++ b/src/login/character.rs @@ -257,41 +257,44 @@ async fn new_character(entity_gateway: &mut EG, user: &UserAc character_id: character.id, }).await?; - let (monomates, monofluids) = futures::future::join_all((0..4usize).map(|_| { - let mut eg = entity_gateway.clone(); - let character_id = character.id; - async move { - let monomate = eg.create_item( - NewItemEntity { - item: ItemDetail::Tool ( - Tool { - tool: item::tool::ToolType::Monomate, - })}).await?; - - eg.add_item_note(&monomate.id, ItemNote::CharacterCreation { - character_id - }).await?; - - let monofluid = eg.create_item( - NewItemEntity { - item: ItemDetail::Tool ( - Tool { - tool: item::tool::ToolType::Monofluid, - })}).await?; - - eg.add_item_note(&monofluid.id, ItemNote::CharacterCreation { - character_id - }).await?; - - Ok((monomate, monofluid)) - }})).await.into_iter().collect::, GatewayError>>()?.into_iter().unzip(); + let mut monomates = Vec::new(); + for _ in 0..4usize { + let monomate = entity_gateway.create_item( + NewItemEntity { + item: ItemDetail::Tool ( + Tool { + tool: item::tool::ToolType::Monomate, + })}).await?; + + entity_gateway.add_item_note(&monomate.id, ItemNote::CharacterCreation { + character_id: character.id + }).await?; + + monomates.push(monomate); + } + + let mut monofluids = Vec::new(); + for _ in 0..4usize { + let monofluid = entity_gateway.create_item( + NewItemEntity { + item: ItemDetail::Tool ( + Tool { + tool: item::tool::ToolType::Monofluid, + })}).await?; + + entity_gateway.add_item_note(&monofluid.id, ItemNote::CharacterCreation { + character_id: character.id + }).await?; + + monofluids.push(monofluid); + } let inventory = InventoryEntity { items: vec![InventoryItemEntity::Individual(weapon.clone()), InventoryItemEntity::Individual(armor.clone()), InventoryItemEntity::Individual(mag.clone()), InventoryItemEntity::Stacked(monomates), InventoryItemEntity::Stacked(monofluids)], }; entity_gateway.set_character_inventory(&character.id, &inventory).await?; - entity_gateway.set_character_bank(&character.id, &BankEntity::default(), BankName("".into())).await?; + entity_gateway.set_character_bank(&character.id, &BankEntity::default(), &BankName("".into())).await?; let equipped = EquippedEntity { weapon: Some(weapon.id), armor: Some(armor.id), @@ -326,7 +329,7 @@ impl CharacterServerState { } async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result, anyhow::Error> { - match get_login_status(&self.entity_gateway, pkt).await { + match get_login_status(&mut self.entity_gateway, pkt).await { Ok(user) => { if let Some(connected_client) = self.connected_clients.get(&user.id) { if let Some(expires) = connected_client.expires { @@ -809,7 +812,7 @@ mod test { #[async_trait::async_trait] impl EntityGateway for TestData { - async fn get_user_settings_by_user(&self, user: &UserAccountEntity) -> Result { + async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result { Ok(UserSettingsEntity { id: UserSettingsId(0), user_id: user.id, @@ -865,7 +868,7 @@ mod test { #[async_std::test] async fn test_character_create() { - let test_data = InMemoryGateway::default(); + let mut test_data = InMemoryGateway::default(); let mut fake_user = ClientState::new(); fake_user.user = Some(UserAccountEntity { id: UserAccountId(3), diff --git a/src/login/login.rs b/src/login/login.rs index 4181136..6d73817 100644 --- a/src/login/login.rs +++ b/src/login/login.rs @@ -59,7 +59,8 @@ impl SendServerPacket for SendLoginPacket { } } -pub async fn get_login_status(entity_gateway: &impl EntityGateway, pkt: &Login) -> Result { +// TODO: MORE impl EntityGateway? +pub async fn get_login_status(entity_gateway: &mut impl EntityGateway, pkt: &Login) -> Result { let username = array_to_utf8(pkt.username).map_err(|_err| AccountStatus::Error)?; let password = array_to_utf8(pkt.password).map_err(|_err| AccountStatus::Error)?; let user = entity_gateway.get_user_by_name(username).await.map_err(|_| AccountStatus::InvalidUser)?; @@ -108,7 +109,7 @@ impl LoginServerState { } async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result, anyhow::Error> { - match get_login_status(&self.entity_gateway, pkt).await.and_then(check_if_already_online) { + match get_login_status(&mut self.entity_gateway, pkt).await.and_then(check_if_already_online) { Ok(mut user) => { user.at_login = true; self.entity_gateway.save_user(&user).await.map_err(|_| LoginError::DbError)?; @@ -210,7 +211,7 @@ mod test { #[async_trait::async_trait] impl EntityGateway for TestData { - async fn get_user_by_name(&self, name: String) -> Result { + async fn get_user_by_name(&mut self, name: String) -> Result { assert!(name == "testuser"); Ok(UserAccountEntity { id: UserAccountId(1), @@ -268,7 +269,7 @@ mod test { #[async_trait::async_trait] impl EntityGateway for TestData { - async fn get_user_by_name(&self, _name: String) -> Result { + async fn get_user_by_name(&mut self, _name: String) -> Result { Err(GatewayError::Error) } } @@ -302,7 +303,7 @@ mod test { #[async_trait::async_trait] impl EntityGateway for TestData { - async fn get_user_by_name(&self, name: String) -> Result { + async fn get_user_by_name(&mut self, name: String) -> Result { assert!(name == "testuser"); Ok(UserAccountEntity { id: UserAccountId(1), @@ -351,7 +352,7 @@ mod test { #[async_trait::async_trait] impl EntityGateway for TestData { - async fn get_user_by_name(&self, name: String) -> Result { + async fn get_user_by_name(&mut self, name: String) -> Result { assert!(name == "testuser"); Ok(UserAccountEntity { id: UserAccountId(1), diff --git a/src/ship/character.rs b/src/ship/character.rs index 39064aa..454b2c8 100644 --- a/src/ship/character.rs +++ b/src/ship/character.rs @@ -1,7 +1,9 @@ use libpso::character::character; use crate::common::leveltable::CharacterStats; use crate::entity::character::CharacterEntity; -use crate::ship::items::{CharacterInventory, CharacterBank}; +//use crate::ship::items::{CharacterInventory, CharacterBank}; +use crate::ship::items::bank::BankState; +use crate::ship::items::inventory::InventoryState; use crate::entity::item::Meseta; @@ -88,8 +90,8 @@ pub struct FullCharacterBytesBuilder<'a> { stats: Option<&'a CharacterStats>, level: Option, meseta: Option, - inventory: Option<&'a CharacterInventory>, - bank: Option<&'a CharacterBank>, + inventory: Option<&'a InventoryState>, + bank: Option<&'a BankState>, keyboard_config: Option<&'a [u8; 0x16C]>, gamepad_config: Option<&'a [u8; 0x38]>, symbol_chat: Option<&'a [u8; 1248]>, @@ -131,7 +133,7 @@ impl<'a> FullCharacterBytesBuilder<'a> { } #[must_use] - pub fn inventory(self, inventory: &'a CharacterInventory) -> FullCharacterBytesBuilder<'a> { + pub fn inventory(self, inventory: &'a InventoryState) -> FullCharacterBytesBuilder<'a> { FullCharacterBytesBuilder { inventory: Some(inventory), ..self @@ -139,7 +141,7 @@ impl<'a> FullCharacterBytesBuilder<'a> { } #[must_use] - pub fn bank(self, bank: &'a CharacterBank) -> FullCharacterBytesBuilder<'a> { + pub fn bank(self, bank: &'a BankState) -> FullCharacterBytesBuilder<'a> { FullCharacterBytesBuilder { bank: Some(bank), ..self diff --git a/src/ship/drops/mod.rs b/src/ship/drops/mod.rs index e5037af..5e9747f 100644 --- a/src/ship/drops/mod.rs +++ b/src/ship/drops/mod.rs @@ -89,7 +89,7 @@ pub struct MonsterDropStats { pub max_meseta: u32, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum ItemDropType { Weapon(weapon::Weapon), Armor(armor::Armor), diff --git a/src/ship/items/actions.rs b/src/ship/items/actions.rs new file mode 100644 index 0000000..c2213b5 --- /dev/null +++ b/src/ship/items/actions.rs @@ -0,0 +1,852 @@ +// TODO: replace various u32s and usizes denoting item amounts for ItemAmount(u32) for consistency +use crate::ship::items::ClientItemId; +use crate::entity::item::{Meseta, ItemNote}; +use std::future::Future; +use std::pin::Pin; + +use crate::ship::map::MapArea; +use crate::entity::character::{CharacterEntity, CharacterEntityId}; +use crate::entity::gateway::EntityGatewayTransaction; +use crate::ship::items::state::{ItemStateProxy, ItemStateError, AddItemResult, StackedItemDetail, IndividualItemDetail}; +use crate::ship::items::bank::{BankItem, BankItemDetail}; +use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail}; +use crate::ship::items::floor::{FloorItem, FloorItemDetail}; +use crate::ship::items::apply_item::apply_item; +use crate::entity::item::{ItemDetail, NewItemEntity, TradeId}; +use crate::entity::item::tool::Tool; +use crate::entity::item::ItemModifier; +use crate::ship::shops::ShopItem; +use crate::ship::drops::{ItemDrop, ItemDropType}; + +pub enum TriggerCreateItem { + Yes, + No +} + +pub(super) fn take_item_from_floor(character_id: CharacterEntityId, item_id: ClientItemId) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), FloorItem), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, transaction): (ItemStateProxy<'_>, Box) , _| { + Box::pin(async move { + let mut floor = item_state.floor(&character_id)?; + let item = floor.take_item(&item_id).ok_or(ItemStateError::NoFloorItem(item_id))?; + item_state.set_floor(floor); + + Ok(((item_state, transaction), item)) + }) + } +} + +pub(super) fn add_floor_item_to_inventory(character: &CharacterEntity) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), FloorItem) + -> Pin, Box), TriggerCreateItem), ItemStateError>> + Send + 'a>> +{ + let character = character.clone(); + move |(mut item_state, transaction), floor_item| { + let character = character.clone(); + Box::pin(async move { + let mut inventory = item_state.inventory(&character.id)?; + + let character_id = character.id; + let transaction = floor_item.with_entity_id(transaction, |mut transaction, entity_id| { + async move { + transaction.gateway().add_item_note(&entity_id, ItemNote::Pickup { + character_id + }).await?; + Ok(transaction) + }}).await?; + + let mut transaction = floor_item.with_mag(transaction, |mut transaction, entity_id, _mag| { + let character = character.clone(); + async move { + transaction.gateway().change_mag_owner(&entity_id, &character).await?; + Ok(transaction) + }}).await?; + + let add_result = inventory.add_floor_item(floor_item)?; + transaction.gateway().set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; + transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), + match add_result { + AddItemResult::NewItem => TriggerCreateItem::Yes, + AddItemResult::AddToStack => TriggerCreateItem::No, + AddItemResult::Meseta => TriggerCreateItem::No, + })) + }) + } +} + + + +pub(super) fn take_item_from_inventory(character_id: CharacterEntityId, item_id: ClientItemId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), InventoryItem), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + let item = inventory.take_item(&item_id, amount).ok_or (ItemStateError::NoFloorItem(item_id))?; + + transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), item)) + }) + } +} + + +pub(super) fn add_inventory_item_to_shared_floor(character_id: CharacterEntityId, map_area: MapArea, drop_position: (f32, f32, f32)) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), FloorItem), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, transaction), inventory_item| { + Box::pin(async move { + let transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| { + async move { + transaction.gateway().add_item_note(&entity_id, ItemNote::PlayerDrop { + character_id, + map_area, + x: drop_position.0, + y: drop_position.1, + z: drop_position.2, + }).await?; + Ok(transaction) + }}).await?; + + let mut floor = item_state.floor(&character_id)?; + let floor_item = floor.add_inventory_item(inventory_item, map_area, drop_position).clone(); + item_state.set_floor(floor); + + Ok(((item_state, transaction), floor_item)) + }) + } +} + + +pub(super) fn take_meseta_from_inventory(character_id: CharacterEntityId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + inventory.remove_meseta(amount)?; + transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), ())) + }) + } +} + +pub(super) fn add_meseta_to_inventory(character_id: CharacterEntityId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + inventory.add_meseta(amount)?; + transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), ())) + }) + } +} + +pub(super) fn add_meseta_to_shared_floor(character_id: CharacterEntityId, amount: u32, map_area: MapArea, drop_position: (f32, f32)) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), FloorItem), ItemStateError>> + Send + 'a>> +{ + + move |(mut item_state, transaction), _| { + Box::pin(async move { + let floor_item = FloorItem { + item_id: item_state.new_item_id()?, + item: FloorItemDetail::Meseta(Meseta(amount)), + map_area, + x: drop_position.0, + y: 0.0, + z: drop_position.1, + }; + + let mut floor = item_state.floor(&character_id)?; + let floor_item = floor.add_shared_item(floor_item).clone(); + item_state.set_floor(floor); + + Ok(((item_state, transaction), floor_item)) + }) + } +} + +pub(super) fn take_meseta_from_bank(character_id: CharacterEntityId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut bank = item_state.bank(&character_id)?; + bank.remove_meseta(amount)?; + transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?; + + Ok(((item_state, transaction), ())) + }) + } +} + +pub(super) fn add_meseta_from_bank_to_inventory(character_id: CharacterEntityId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + inventory.add_meseta_no_overflow(amount)?; + transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; + + Ok(((item_state, transaction), ())) + }) + } +} + + +pub(super) fn add_meseta_to_bank(character_id: CharacterEntityId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut bank = item_state.bank(&character_id)?; + bank.add_meseta(amount)?; + transaction.gateway().set_bank_meseta(&character_id, &bank.name, bank.meseta).await?; + + Ok(((item_state, transaction), ())) + }) + } +} + + +pub(super) fn take_item_from_bank(character_id: CharacterEntityId, item_id: ClientItemId, amount: u32) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), BankItem), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut bank = item_state.bank(&character_id)?; + let item = bank.take_item(&item_id, amount).ok_or(ItemStateError::NoBankItem(item_id))?; + transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.name).await?; + item_state.set_bank(bank); + + Ok(((item_state, transaction), item)) + }) + } +} + +pub(super) fn add_bank_item_to_inventory(character: &CharacterEntity) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), BankItem) + -> Pin, Box), InventoryItem), ItemStateError>> + Send + 'a>> +{ + let character = character.clone(); + move |(mut item_state, transaction), bank_item| { + let character = character.clone(); + Box::pin(async move { + let bank_name = item_state.bank(&character.id)?.name; + let mut inventory = item_state.inventory(&character.id)?; + + let character_id = character.id; + let transaction = bank_item.with_entity_id(transaction, |mut transaction, entity_id| { + let bank_name = bank_name.clone(); + async move { + transaction.gateway().add_item_note(&entity_id, ItemNote::Withdraw { + character_id, + bank: bank_name, + }).await?; + Ok(transaction) + }}).await?; + + let inventory_item = InventoryItem { + item_id: bank_item.item_id, + item: match bank_item.item { + BankItemDetail::Individual(individual_item) => InventoryItemDetail::Individual(individual_item), + BankItemDetail::Stacked(stacked_item) => InventoryItemDetail::Stacked(stacked_item), + }, + }; + + let mut transaction = inventory_item.with_mag(transaction, |mut transaction, entity_id, _mag| { + let character = character.clone(); + async move { + transaction.gateway().change_mag_owner(&entity_id, &character).await?; + Ok(transaction) + }}).await?; + + inventory.add_item(inventory_item.clone())?; + transaction.gateway().set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), inventory_item)) + }) + } +} + + +pub(super) fn add_inventory_item_to_bank(character_id: CharacterEntityId) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, transaction), inventory_item| { + Box::pin(async move { + let mut bank = item_state.bank(&character_id)?; + let bank_name = bank.name.clone(); + let mut transaction = inventory_item.with_entity_id(transaction, move |mut transaction, entity_id| { + let bank_name = bank_name.clone(); + async move { + transaction.gateway().add_item_note(&entity_id, ItemNote::Deposit { + character_id, + bank: bank_name, + }).await?; + Ok(transaction) + }}).await?; + + bank.add_inventory_item(inventory_item)?; + + transaction.gateway().set_character_bank(&character_id, &bank.as_bank_entity(), &bank.name).await?; + item_state.set_bank(bank); + + + Ok(((item_state, transaction), ())) + }) + } +} + + +pub(super) fn equip_inventory_item(character_id: CharacterEntityId, item_id: ClientItemId, equip_slot: u8) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + inventory.equip(&item_id, equip_slot); + transaction.gateway().set_character_equips(&character_id, &inventory.as_equipped_entity()).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), ())) + }) + } +} + + +pub(super) fn unequip_inventory_item(character_id: CharacterEntityId, item_id: ClientItemId) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + inventory.unequip(&item_id); + transaction.gateway().set_character_equips(&character_id, &inventory.as_equipped_entity()).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), ())) + }) + } +} + + + +pub(super) fn sort_inventory_items(character_id: CharacterEntityId, item_ids: Vec) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), ()), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + let item_ids = item_ids.clone(); + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + inventory.sort(&item_ids); + transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), ())) + }) + } +} + + +pub(super) fn use_consumed_item(character: CharacterEntity) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), CharacterEntity), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, transaction), inventory_item| { + let mut character = character.clone(); + Box::pin(async move { + let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| { + async move { + transaction.gateway().add_item_note(&entity_id, ItemNote::Consumed).await?; + Ok(transaction) + }}).await?; + + apply_item(&mut item_state, transaction.gateway(), &mut character, inventory_item).await?; + + Ok(((item_state, transaction), character)) + }) + } +} + + +pub(super) fn feed_mag_item(character: CharacterEntity, mag_item_id: ClientItemId) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), CharacterEntity), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, transaction), tool| { + let character = character.clone(); + Box::pin(async move { + let mut inventory = item_state.inventory(&character.id)?; + let mag_entity = inventory.get_by_client_id_mut(&mag_item_id) + .ok_or(ItemStateError::InvalidItemId(mag_item_id))? + .item + .as_individual_mut() + .ok_or(ItemStateError::NotAMag(mag_item_id))?; + let mag_entity_id = mag_entity.entity_id; + + let mut transaction = tool.with_entity_id(transaction, |mut transaction, entity_id| { + async move { + transaction.gateway().add_item_note(&entity_id, ItemNote::FedToMag { + //character_id: character.id, + mag: mag_entity_id, + }).await?; + transaction.gateway().feed_mag(&mag_entity_id, &entity_id).await?; + Ok(transaction) + }}).await?; + + let food_tool = tool + .item + .stacked() + .ok_or(ItemStateError::NotMagFood(tool.item_id))? + .tool + .tool; + + let mag_entity = mag_entity + .as_mag_mut() + .ok_or(ItemStateError::NotAMag(mag_item_id))?; + + mag_entity.feed(food_tool); + + transaction.gateway().set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), character)) + }) + } +} + + +pub(super) fn add_bought_item_to_inventory<'a>(character_id: CharacterEntityId, + shop_item: &'a (dyn ShopItem + Send + Sync), + item_id: ClientItemId, + amount: u32) + -> impl Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), InventoryItem), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), _| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + let bought_item = shop_item.as_item(); + + let inventory_item = match bought_item { + ItemDetail::Tool(tool) if tool.is_stackable() => { + let mut item_entities = Vec::new(); + for _ in 0..amount { + let item_entity = transaction.gateway().create_item(NewItemEntity { + item: ItemDetail::Tool(tool), + }).await?; + transaction.gateway().add_item_note(&item_entity.id, ItemNote::BoughtAtShop { + character_id, + }).await?; + item_entities.push(item_entity); + } + + let inventory_item = InventoryItem { + item_id, + item: InventoryItemDetail::Stacked(StackedItemDetail { + entity_ids: item_entities.into_iter().map(|i| i.id).collect(), + tool, + }) + }; + inventory.add_item(inventory_item)?.1 + }, + item_detail => { + let item_entity = transaction.gateway().create_item(NewItemEntity { + item: item_detail.clone(), + }).await?; + transaction.gateway().add_item_note(&item_entity.id, ItemNote::BoughtAtShop { + character_id, + }).await?; + + let inventory_item = InventoryItem { + item_id, + item: InventoryItemDetail::Individual(IndividualItemDetail { + entity_id: item_entity.id, + item: item_detail, + }) + }; + inventory.add_item(inventory_item)?.1 + }, + }; + + transaction.gateway().set_character_inventory(&character_id, &inventory.as_inventory_entity(&character_id)).await?; + item_state.set_inventory(inventory); + Ok(((item_state, transaction), inventory_item)) + }) + } +} + + +pub(super) fn sell_inventory_item<'a>(character_id: CharacterEntityId) + -> impl Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), InventoryItem), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, transaction), inventory_item| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + let price = inventory_item.item.sell_price()?; + inventory.add_meseta_no_overflow(price)?; + + let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| { + async move { + transaction.gateway().add_item_note(&entity_id, ItemNote::SoldToShop).await?; + Ok(transaction) + }}).await?; + transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; + item_state.set_inventory(inventory); + Ok(((item_state, transaction), inventory_item)) + }) + } +} + + +#[async_recursion::async_recursion] +async fn iterate_inner<'a, I, O, T, F, FR>(state: (ItemStateProxy<'a>, Box), + mut input: Vec, + func: F, + arg: T) + -> Result<((ItemStateProxy<'a>, Box), Vec), ItemStateError> +where + 'a: 'async_recursion, + I: Send, + O: Send, + T: Clone + Send + Sync, + F: Fn(I) -> FR + Send + Sync + Clone + 'static, + FR: Fn((ItemStateProxy<'a>, Box), T) + -> Pin, Box), O), ItemStateError>> + Send + 'a>> + Send + Sync, +{ + let item = match input.pop() { + Some(item) => item, + None => return Ok((state, Vec::new())) + }; + + let (state, mut output) = iterate_inner(state, input, func.clone(), arg.clone()).await.unwrap(); + let rfunc = func(item); + let (state, result) = rfunc(state, arg.clone()).await.unwrap(); + + output.push(result); + + Ok((state, output)) +} + +pub(super) fn iterate<'k, I, O, T, F, FR>( + input: Vec, + func: F) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), T) + -> Pin, Box), Vec), ItemStateError>> + Send + 'a>> +where + O: Send, + I: Send + Clone + 'static + std::fmt::Debug, + T: Send + Clone + 'static + std::fmt::Debug, + F: Fn(I) -> FR + Send + Sync + Clone + 'static, + FR: for<'a> Fn((ItemStateProxy<'a>, Box), T) + -> Pin, Box), O), ItemStateError>> + Send + 'a>> + Send + Sync, + T: Clone + Send + Sync, +{ + move |(item_state, transaction), arg| { + let input = input.clone(); + let func = func.clone(); + println!("i {:?} {:?}", input, arg); + Box::pin(async move { + let (state, result) = iterate_inner((item_state, transaction), input, func, arg.clone()).await?; + Ok((state, result)) + }) + } +} + + +#[async_recursion::async_recursion] +async fn foreach_inner<'a, O, T, F>(state: (ItemStateProxy<'a>, Box), + mut input: Vec, + func: F) + -> Result<((ItemStateProxy<'a>, Box), Vec), ItemStateError> +where + 'a: 'async_recursion, + O: Send, + T: Clone + Send, + F: Fn((ItemStateProxy<'a>, Box), T) + -> Pin, Box), O), ItemStateError>> + Send + 'a>> + Send + Sync, + F: Clone, +{ + let item = match input.pop() { + Some(item) => item, + None => return Ok((state, Vec::new())) + }; + + let (state, mut output) = foreach_inner(state, input, func.clone()).await?; + let (state, result) = func(state, item).await?; + + output.push(result); + + Ok((state, output)) +} + +pub(super) fn foreach<'k, O, T, F>(func: F) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), Vec) + -> Pin, Box), Vec), ItemStateError>> + Send + 'a>> +where + O: Send, + T: Send + Clone + 'static + std::fmt::Debug, + F: for<'a> Fn((ItemStateProxy<'a>, Box), T) + -> Pin, Box), O), ItemStateError>> + Send + 'a>> + Send + Sync + 'static, + F: Clone, + T: Clone + Send + Sync, +{ + move |(item_state, transaction), items| { + println!("fe {:?}", items); + let func = func.clone(); + Box::pin(async move { + let (state, result) = foreach_inner((item_state, transaction), items, func).await?; + Ok((state, result)) + }) + } +} + +pub(super) fn insert<'a, T: Send + Clone + 'a>(element: T) + -> impl Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), T), ItemStateError>> + Send + 'a>> +{ + move |state, _| { + let element = element.clone(); + Box::pin(async move { + Ok((state, element)) + }) + } +} + +pub(super) fn add_item_to_inventory(character: CharacterEntity) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), InventoryItem), ItemStateError>> + Send + 'a>> + Clone +{ + move |(mut item_state, transaction), inventory_item| { + let character = character.clone(); + Box::pin(async move { + let mut inventory = item_state.inventory(&character.id)?; + let mut transaction = inventory_item.with_mag(transaction, |mut transaction, entity_id, _mag| { + let character = character.clone(); + async move { + transaction.gateway().change_mag_owner(&entity_id, &character).await?; + Ok(transaction) + }}).await?; + + inventory.add_item(inventory_item.clone())?; + transaction.gateway().set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; + item_state.set_inventory(inventory); + + Ok(((item_state, transaction), inventory_item)) + }) + } +} + +pub(super) fn record_trade(trade_id: TradeId, character_to: CharacterEntityId, character_from: CharacterEntityId) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), Vec) + -> Pin, Box), Vec), ItemStateError>> + Send + 'a>> + Clone +{ + move |(item_state, mut transaction), traded_items| { + Box::pin(async move { + for item in &traded_items { + transaction = item.with_entity_id(transaction, |mut transaction, entity_id| { + async move { + transaction.gateway().add_item_note(&entity_id, ItemNote::Trade { + trade_id, + character_to, + character_from, + }).await?; + Ok(transaction) + }}).await?; + } + Ok(((item_state, transaction), traded_items)) + }) + } +} + + +pub(super) fn assign_new_item_id() + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), InventoryItem), ItemStateError>> + Send + 'a>> + Clone +{ + move |(mut item_state, transaction), mut inventory_item| { + Box::pin(async move { + inventory_item.item_id = item_state.new_item_id()?; + Ok(((item_state, transaction), inventory_item)) + }) + } +} + + +pub(super) fn convert_item_drop_to_floor_item(character_id: CharacterEntityId, item_drop: ItemDrop) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), ()) + -> Pin, Box), FloorItem), ItemStateError>> + Send + 'a>> + Clone +{ + move |(mut item_state, mut transaction), _| { + let item_drop = item_drop.clone(); + Box::pin(async move { + enum ItemOrMeseta { + Individual(ItemDetail), + Stacked(Tool), + Meseta(Meseta) + } + + let item = match item_drop.item { + ItemDropType::Weapon(w) => ItemOrMeseta::Individual(ItemDetail::Weapon(w)), + ItemDropType::Armor(w) => ItemOrMeseta::Individual(ItemDetail::Armor(w)), + ItemDropType::Shield(w) => ItemOrMeseta::Individual(ItemDetail::Shield(w)), + ItemDropType::Unit(w) => ItemOrMeseta::Individual(ItemDetail::Unit(w)), + ItemDropType::TechniqueDisk(w) => ItemOrMeseta::Individual(ItemDetail::TechniqueDisk(w)), + ItemDropType::Mag(w) => ItemOrMeseta::Individual(ItemDetail::Mag(w)), + ItemDropType::Tool(t) => { + if t.tool.is_stackable() { + ItemOrMeseta::Stacked(t) + } + else { + ItemOrMeseta::Individual(ItemDetail::Tool(t)) + } + }, + ItemDropType::Meseta(m) => ItemOrMeseta::Meseta(Meseta(m)), + }; + + let item_id = item_state.new_item_id()?; + + let floor_item = match item { + ItemOrMeseta::Individual(item_detail) => { + let entity = transaction.gateway().create_item(NewItemEntity { + item: item_detail.clone(), + }).await?; + transaction.gateway().add_item_note(&entity.id, ItemNote::EnemyDrop { + character_id, + map_area: item_drop.map_area, + x: item_drop.x, + y: item_drop.y, + z: item_drop.z, + }).await?; + FloorItem { + item_id, + item: FloorItemDetail::Individual(IndividualItemDetail { + entity_id: entity.id, + item: item_detail, + }), + map_area: item_drop.map_area, + x: item_drop.x, + y: item_drop.y, + z: item_drop.z, + } + }, + ItemOrMeseta::Stacked(tool) => { + let entity = transaction.gateway().create_item(NewItemEntity { + item: ItemDetail::Tool(tool), + }).await?; + transaction.gateway().add_item_note(&entity.id, ItemNote::EnemyDrop { + character_id, + map_area: item_drop.map_area, + x: item_drop.x, + y: item_drop.y, + z: item_drop.z, + }).await?; + FloorItem { + item_id, + item: FloorItemDetail::Stacked(StackedItemDetail{ + entity_ids: vec![entity.id], + tool, + }), + map_area: item_drop.map_area, + x: item_drop.x, + y: item_drop.y, + z: item_drop.z, + } + }, + ItemOrMeseta::Meseta(meseta) => { + FloorItem { + item_id, + item: FloorItemDetail::Meseta(meseta), + map_area: item_drop.map_area, + x: item_drop.x, + y: item_drop.y, + z: item_drop.z, + } + }, + }; + + Ok(((item_state, transaction), floor_item)) + }) + } +} + +pub(super) fn add_item_to_local_floor(character_id: CharacterEntityId) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), FloorItem) + -> Pin, Box), FloorItem), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, transaction) , floor_item| { + Box::pin(async move { + let mut floor = item_state.floor(&character_id)?; + let item = floor.add_local_item(floor_item).clone(); + item_state.set_floor(floor); + + Ok(((item_state, transaction), item)) + }) + } +} + +pub(super) fn apply_modifier_to_inventory_item(modifier: ItemModifier) + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), InventoryItem), ItemStateError>> + Send + 'a>> +{ + move |(item_state, mut transaction), mut inventory_item| { + let modifier = modifier.clone(); + Box::pin(async move { + match (&mut inventory_item.item, modifier) { + (InventoryItemDetail::Individual(IndividualItemDetail{entity_id, item: ItemDetail::Weapon(ref mut weapon), ..}), ItemModifier::WeaponModifier(modifier)) => { + weapon.apply_modifier(&modifier); + transaction.gateway().add_weapon_modifier(entity_id, modifier).await?; + }, + _ => return Err(ItemStateError::InvalidModifier) + } + + Ok(((item_state, transaction), inventory_item)) + }) + } +} + +pub(super) fn as_individual_item() + -> impl for<'a> Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), IndividualItemDetail), ItemStateError>> + Send + 'a>> +{ + move |(item_state, transaction), inventory_item| { + Box::pin(async move { + let item = match inventory_item.item { + InventoryItemDetail::Individual(individual_item) => individual_item, + _ => return Err(ItemStateError::WrongItemType(inventory_item.item_id)) + }; + + Ok(((item_state, transaction), item)) + }) + } +} diff --git a/src/ship/items/apply_item.rs b/src/ship/items/apply_item.rs new file mode 100644 index 0000000..d43f55d --- /dev/null +++ b/src/ship/items/apply_item.rs @@ -0,0 +1,293 @@ +use thiserror::Error; +use std::convert::TryInto; +use crate::entity::gateway::{EntityGateway, GatewayError}; +use crate::entity::character::CharacterEntity; +use crate::entity::item::mag::{MagCell, MagCellError}; +use crate::entity::item::tool::ToolType; +use crate::entity::item::{ItemDetail, ItemEntityId}; +use crate::ship::items::state::{ItemStateProxy, ItemStateError}; +use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail}; + + +#[derive(Error, Debug)] +pub enum ApplyItemError { + #[error("no character")] + NoCharacter, + #[error("item not equipped")] + ItemNotEquipped, + #[error("invalid item")] + InvalidItem, + #[error("gateway error {0}")] + GatewayError(#[from] GatewayError), + + #[error("itemstate error {0}")] + ItemStateError(Box), + + #[error("magcell error {0}")] + MagCellError(#[from] MagCellError), +} + +impl From for ApplyItemError { + fn from(other: ItemStateError) -> ApplyItemError { + ApplyItemError::ItemStateError(Box::new(other)) + } +} + +// TODO: make all these functions not-pub +pub async fn power_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> { + character.materials.power += 1; + entity_gateway.save_character(character).await?; + Ok(()) +} + +pub async fn mind_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> { + character.materials.mind += 1; + entity_gateway.save_character(character).await.unwrap(); + Ok(()) +} + +pub async fn evade_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> { + character.materials.evade += 1; + entity_gateway.save_character(character).await.unwrap(); + Ok(()) +} + +pub async fn def_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> { + character.materials.def += 1; + entity_gateway.save_character(character).await.unwrap(); + Ok(()) +} + +pub async fn luck_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> { + character.materials.luck += 1; + entity_gateway.save_character(character).await.unwrap(); + Ok(()) +} + +pub async fn hp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> { + character.materials.hp += 1; + entity_gateway.save_character(character).await.unwrap(); + Ok(()) +} + +pub async fn tp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) -> Result<(), ApplyItemError> { + character.materials.tp += 1; + entity_gateway.save_character(character).await.unwrap(); + Ok(()) +} + +/* +async fn mag_cell(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory, mag_cell_type: MagCell) -> Result<(), ApplyItemError> { + let mut mag_handle = inventory.get_equipped_mag_handle().ok_or(ApplyItemError::ItemNotEquipped)?; + let mag_item = mag_handle.item_mut() + .ok_or(ApplyItemError::InvalidItem)?; + let actual_mag = mag_item + .individual_mut() + .ok_or(ApplyItemError::InvalidItem)? + .mag_mut() + .ok_or(ApplyItemError::InvalidItem)?; + actual_mag.apply_mag_cell(mag_cell_type); + for mag_entity_id in mag_item.entity_ids() { + for cell_entity_id in used_cell.entity_ids() { + entity_gateway.use_mag_cell(&mag_entity_id, &cell_entity_id).await.unwrap(); + } + } + + Ok(()) +} + */ + + +async fn mag_cell<'a, EG>(item_state: &mut ItemStateProxy<'a>, + entity_gateway: &mut EG, + character: &CharacterEntity, + cell_entity_id: ItemEntityId, + mag_cell_type: MagCell) + -> Result<(), ApplyItemError> +where + EG: EntityGateway + ?Sized, +{ + let mut inventory = item_state.inventory(&character.id)?; + + let (mag_entity_id, mag) = inventory.equipped_mag_mut() + .ok_or(ApplyItemError::ItemNotEquipped)?; + mag.apply_mag_cell(mag_cell_type)?; + + entity_gateway.use_mag_cell(&mag_entity_id, &cell_entity_id).await?; + entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; + item_state.set_inventory(inventory); + + Ok(()) +} + +/* +pub async fn cell_of_mag_502(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, inventory, MagCell::CellOfMag502).await +} + +pub async fn cell_of_mag_213(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::CellOfMag213).await +} + +pub async fn parts_of_robochao(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::PartsOfRobochao).await +} + +pub async fn heart_of_opaopa(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfOpaOpa).await +} + +pub async fn heart_of_pian(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfPian).await +} + +pub async fn heart_of_chao(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfChao).await +} + +pub async fn heart_of_angel(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfAngel).await +} + +pub async fn kit_of_hamburger(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfHamburger).await +} + +pub async fn panthers_spirit(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::PanthersSpirit).await +} + +pub async fn kit_of_mark3(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfMark3).await +} + +pub async fn kit_of_master_system(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfMasterSystem).await +} + +pub async fn kit_of_genesis(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfGenesis).await +} + +pub async fn kit_of_sega_saturn(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfSegaSaturn).await +} + +pub async fn kit_of_dreamcast(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfDreamcast).await +} + +pub async fn tablet(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::Tablet).await +} + +pub async fn dragon_scale(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::DragonScale).await +} + +pub async fn heaven_striker_coat(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeavenStrikerCoat).await +} + +pub async fn pioneer_parts(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::PioneerParts).await +} + +pub async fn amities_memo(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::AmitiesMemo).await +} + +pub async fn heart_of_morolian(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfMorolian).await +} + +pub async fn rappys_beak(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::RappysBeak).await +} + +pub async fn yahoos_engine(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::YahoosEngine).await +} + +pub async fn d_photon_core(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::DPhotonCore).await +} + +pub async fn liberta_kit(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), ApplyItemError> { + mag_cell(entity_gateway, used_cell, inventory, MagCell::LibertaKit).await +} +*/ + +async fn apply_tool<'a, EG>(item_state: &mut ItemStateProxy<'a>, + entity_gateway: &mut EG, + character: &mut CharacterEntity, + entity_id: ItemEntityId, + tool: ToolType) + -> Result<(), ApplyItemError> +where + EG: EntityGateway + ?Sized, +{ + match tool { + ToolType::PowerMaterial => power_material(entity_gateway, character).await, + ToolType::MindMaterial => mind_material(entity_gateway, character).await, + ToolType::EvadeMaterial => evade_material(entity_gateway, character).await, + ToolType::DefMaterial => def_material(entity_gateway, character).await, + ToolType::LuckMaterial => luck_material(entity_gateway, character).await, + ToolType::HpMaterial => hp_material(entity_gateway, character).await, + ToolType::TpMaterial => tp_material(entity_gateway, character).await, + ToolType::Monomate => Ok(()), + ToolType::Dimate => Ok(()), + ToolType::Trimate => Ok(()), + ToolType::Monofluid => Ok(()), + ToolType::Difluid => Ok(()), + ToolType::Trifluid => Ok(()), + ToolType::HuntersReport => Ok(()), + ToolType::CellOfMag502 + | ToolType::CellOfMag213 + | ToolType::PartsOfRobochao + | ToolType::HeartOfOpaOpa + | ToolType::HeartOfPian + | ToolType::HeartOfChao + | ToolType::HeartOfAngel + | ToolType::KitOfHamburger + | ToolType::PanthersSpirit + | ToolType::KitOfMark3 + | ToolType::KitOfMasterSystem + | ToolType::KitOfGenesis + | ToolType::KitOfSegaSaturn + | ToolType::KitOfDreamcast + | ToolType::Tablet + | ToolType::DragonScale + | ToolType::HeavenStrikerCoat + | ToolType::PioneerParts + | ToolType::AmitiesMemo + | ToolType::HeartOfMorolian + | ToolType::RappysBeak + | ToolType::YahoosEngine + | ToolType::DPhotonCore + | ToolType::LibertaKit => { + mag_cell(item_state, entity_gateway, character, entity_id, tool.try_into()?).await + } + // TODO: rest of these + _ => Err(ApplyItemError::InvalidItem) + } + +} + + +pub async fn apply_item<'a, EG: EntityGateway + ?Sized>(item_state: &mut ItemStateProxy<'a>, entity_gateway: &mut EG, character: &mut CharacterEntity, item: InventoryItem) -> Result<(), ApplyItemError> { + match item.item { + InventoryItemDetail::Individual(individual_item) => { + match individual_item.item { + ItemDetail::Tool(tool) => apply_tool(item_state, entity_gateway, character, individual_item.entity_id, tool.tool).await, + _ => Err(ApplyItemError::InvalidItem) + } + }, + InventoryItemDetail::Stacked(stacked_item) => { + for entity_id in stacked_item.entity_ids { + apply_tool(item_state, entity_gateway, character, entity_id, stacked_item.tool.tool).await? + } + Ok(()) + }, + } +} diff --git a/src/ship/items/bank.rs b/src/ship/items/bank.rs index dab026f..5abef95 100644 --- a/src/ship/items/bank.rs +++ b/src/ship/items/bank.rs @@ -1,124 +1,43 @@ +use std::cmp::Ordering; +use libpso::character::character; use crate::ship::items::ClientItemId; -use libpso::character::character;//::InventoryItem; -use crate::entity::item::{ItemEntityId, ItemEntity, ItemDetail, BankEntity, BankItemEntity, BankName}; -use crate::entity::character::CharacterEntityId; -use crate::entity::item::tool::Tool; -use crate::ship::items::inventory::{InventoryItemHandle, InventoryItem}; - -const BANK_CAPACITY: usize = 200; - -#[derive(Debug, Clone)] -pub struct IndividualBankItem { - pub entity_id: ItemEntityId, - pub item_id: ClientItemId, - pub item: ItemDetail, -} - -#[derive(Debug, Clone)] -pub struct StackedBankItem { - pub entity_ids: Vec, - pub item_id: ClientItemId, - pub tool: Tool, -} - -impl StackedBankItem { - pub fn count(&self) -> usize { - self.entity_ids.len() - } - - pub fn take_entity_ids(&mut self, amount: usize) -> Option> { - if amount <= self.count() { - Some(self.entity_ids.drain(..amount).collect()) - } - else { - None - } - } -} - -#[derive(Debug, Clone)] -pub enum BankItem { - Individual(IndividualBankItem), - Stacked(StackedBankItem), -} - - -impl std::cmp::PartialEq for BankItem { - fn eq(&self, other: &BankItem) -> bool { - let mut self_bytes = [0u8; 4]; - let mut other_bytes = [0u8; 4]; - self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]); - other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]); - - let self_value = u32::from_be_bytes(self_bytes); - let other_value = u32::from_be_bytes(other_bytes); - - self_value.eq(&other_value) - } -} - -impl std::cmp::Eq for BankItem {} - -impl std::cmp::PartialOrd for BankItem { - fn partial_cmp(&self, other: &BankItem) -> Option { - //let self_bytes = self.as_client_bytes(); - //let other_bytes = other.as_client_bytes(); - let mut self_bytes = [0u8; 4]; - let mut other_bytes = [0u8; 4]; - self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]); - other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]); - - - let self_value = u32::from_be_bytes(self_bytes); - let other_value = u32::from_be_bytes(other_bytes); +use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, BankEntity, BankItemEntity, BankName}; +use std::future::Future; - self_value.partial_cmp(&other_value) - } +use crate::entity::character::CharacterEntityId; +use crate::ship::items::state::ItemStateError; +use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail, AddItemResult}; +use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail}; + + +#[derive(thiserror::Error, Debug)] +pub enum BankError { + #[error("bank full")] + BankFull, + #[error("stack full")] + StackFull, + #[error("meseta full")] + MesetaFull, } -impl std::cmp::Ord for BankItem { - fn cmp(&self, other: &BankItem) -> std::cmp::Ordering { - //let self_bytes = self.as_client_bytes(); - //let other_bytes = other.as_client_bytes(); - let mut self_bytes = [0u8; 4]; - let mut other_bytes = [0u8; 4]; - self_bytes.copy_from_slice(&self.as_client_bytes()[0..4]); - other_bytes.copy_from_slice(&other.as_client_bytes()[0..4]); - - - let self_value = u32::from_le_bytes(self_bytes); - let other_value = u32::from_le_bytes(other_bytes); - self_value.cmp(&other_value) - } +#[derive(Clone, Debug)] +pub enum BankItemDetail { + Individual(IndividualItemDetail), + Stacked(StackedItemDetail), } -impl BankItem { - pub fn set_item_id(&mut self, item_id: ClientItemId) { - match self { - BankItem::Individual(individual_bank_item) => { - individual_bank_item.item_id = item_id - }, - BankItem::Stacked(stacked_bank_item) => { - stacked_bank_item.item_id = item_id - } - } - } - - pub fn item_id(&self) -> ClientItemId { +impl BankItemDetail { + fn stacked_mut(&mut self) -> Option<&mut StackedItemDetail> { match self { - BankItem::Individual(individual_bank_item) => { - individual_bank_item.item_id - }, - BankItem::Stacked(stacked_bank_item) => { - stacked_bank_item.item_id - } + BankItemDetail::Stacked(sitem) => Some(sitem), + _ => None, } } pub fn as_client_bytes(&self) -> [u8; 16] { match self { - BankItem::Individual(item) => { + BankItemDetail::Individual(item) => { match &item.item { ItemDetail::Weapon(w) => w.as_bytes(), ItemDetail::Armor(a) => a.as_bytes(), @@ -130,7 +49,7 @@ impl BankItem { ItemDetail::ESWeapon(e) => e.as_bytes(), } }, - BankItem::Stacked(item) => { + BankItemDetail::Stacked(item) => { item.tool.as_stacked_bytes(item.entity_ids.len()) }, } @@ -138,92 +57,206 @@ impl BankItem { } -pub struct BankItemHandle<'a> { - bank: &'a mut CharacterBank, - index: usize +#[derive(Clone, Debug)] +pub struct BankItem { + pub item_id: ClientItemId, + pub item: BankItemDetail, } -impl<'a> BankItemHandle<'a> { - pub fn item(&'a self) -> Option<&'a BankItem> { - self.bank.items.get(self.index) +impl BankItem { + pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result + where + F: FnMut(T, ItemEntityId) -> Fut, + Fut: Future>, + { + match &self.item { + BankItemDetail::Individual(individual_item) => { + param = func(param, individual_item.entity_id).await?; + }, + BankItemDetail::Stacked(stacked_item) => { + for entity_id in &stacked_item.entity_ids { + param = func(param, *entity_id).await?; + } + } + } + Ok(param) } +} - pub fn item_mut(&mut self) -> Option<&mut BankItem> { - self.bank.items.get_mut(self.index) - } - pub fn remove_from_bank(self) { - self.bank.items.remove(self.index); +#[derive(Clone, Debug)] +pub struct Bank(Vec); + +impl Bank { + pub fn new(items: Vec) -> Bank { + Bank(items) } } -pub struct CharacterBank { - item_id_counter: u32, - items: Vec +#[derive(Clone, Debug)] +pub struct BankState { + pub character_id: CharacterEntityId, + pub item_id_counter: u32, + pub name: BankName, + pub bank: Bank, + pub meseta: Meseta, } -impl CharacterBank { - pub fn new(mut items: Vec) -> CharacterBank { - items.sort(); - CharacterBank { +impl BankState { + pub fn new(character_id: CharacterEntityId, name: BankName, mut bank: Bank, meseta: Meseta) -> BankState { + bank.0.sort(); + BankState { + character_id, item_id_counter: 0, - items, + name, + bank, + meseta, } } + pub fn count(&self) -> usize { + self.bank.0.len() + } + pub fn initialize_item_ids(&mut self, base_item_id: u32) { - for (i, item) in self.items.iter_mut().enumerate() { - item.set_item_id(ClientItemId(base_item_id + i as u32)); + for (i, item) in self.bank.0.iter_mut().enumerate() { + item.item_id = ClientItemId(base_item_id + i as u32); } - self.item_id_counter = base_item_id + self.items.len() as u32 + 1; + self.item_id_counter = base_item_id + self.bank.0.len() as u32; } - pub fn get_item_handle_by_id(&mut self, item_id: ClientItemId) -> Option { - let (index, _) = self.items.iter() - .enumerate() - .find(|(_, item)| { - item.item_id() == item_id - })?; - Some(BankItemHandle { - bank: self, - index, - }) + pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { + if self.meseta.0 + amount > 999999 { + return Err(ItemStateError::FullOfMeseta) + } + self.meseta.0 += amount; + Ok(()) + } + + pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { + if amount > self.meseta.0 { + return Err(ItemStateError::InvalidMesetaRemoval(amount)) + } + self.meseta.0 -= amount; + Ok(()) + } + + pub fn add_inventory_item(&mut self, item: InventoryItem) -> Result { + match item.item { + InventoryItemDetail::Individual(iitem) => { + if self.bank.0.len() >= 30 { + Err(BankError::BankFull) + } + else { + self.bank.0.push(BankItem { + item_id: item.item_id, + item: BankItemDetail::Individual(iitem) + }); + self.bank.0.sort(); + Ok(AddItemResult::NewItem) + } + }, + InventoryItemDetail::Stacked(sitem) => { + let existing_stack = self.bank.0 + .iter_mut() + .filter_map(|item| item.item.stacked_mut()) + .find(|item| { + item.tool == sitem.tool + }); + match existing_stack { + Some(existing_stack) => { + if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { + Err(BankError::StackFull) + } + else { + existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); + Ok(AddItemResult::AddToStack) + } + }, + None => { + if self.bank.0.len() >= 30 { + Err(BankError::BankFull) + } + else { + self.bank.0.push(BankItem { + item_id: item.item_id, + item: BankItemDetail::Stacked(sitem) + }); + self.bank.0.sort(); + Ok(AddItemResult::NewItem) + } + } + } + } + } + } + + pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { + let idx = self.bank.0 + .iter() + .position(|i| i.item_id == *item_id)?; + match &mut self.bank.0[idx].item { + BankItemDetail::Individual(_individual_item) => { + Some(self.bank.0.remove(idx)) + }, + BankItemDetail::Stacked(stacked_item) => { + let remove_all = match stacked_item.entity_ids.len().cmp(&(amount as usize)) { + Ordering::Equal => true, + Ordering::Greater => false, + Ordering::Less => return None, + }; + + if remove_all { + Some(self.bank.0.remove(idx)) + } + else { + let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect(); + self.item_id_counter += 1; + Some(BankItem { + item_id: ClientItemId(self.item_id_counter), + item: BankItemDetail::Stacked(StackedItemDetail { + entity_ids, + tool: stacked_item.tool, + })}) + } + } + } } pub fn as_client_bank_items(&self) -> character::Bank { - self.items.iter() + self.bank.0.iter() .enumerate() .fold(character::Bank::default(), |mut bank, (slot, item)| { bank.item_count = (slot + 1) as u32; - let bytes = item.as_client_bytes(); + let bytes = item.item.as_client_bytes(); bank.items[slot].data1.copy_from_slice(&bytes[0..12]); bank.items[slot].data2.copy_from_slice(&bytes[12..16]); - bank.items[slot].item_id = item.item_id().0; + bank.items[slot].item_id = item.item_id.0; bank }) } pub fn as_client_bank_request(&self) -> Vec { - self.items.iter() + self.bank.0.iter() .map(|item| { - let bytes = item.as_client_bytes(); + let bytes = item.item.as_client_bytes(); let mut data1 = [0; 12]; let mut data2 = [0; 4]; data1.copy_from_slice(&bytes[0..12]); data2.copy_from_slice(&bytes[12..16]); - let amount = match item { - BankItem::Individual(_individual_bank_item) => { + let amount = match &item.item { + BankItemDetail::Individual(_individual_bank_item) => { 1 }, - BankItem::Stacked(stacked_bank_item) => { + BankItemDetail::Stacked(stacked_bank_item) => { stacked_bank_item.count() }, }; character::BankItem { data1, data2, - item_id: item.item_id().0, + item_id: item.item_id.0, amount: amount as u16, flags: 1, } @@ -231,80 +264,18 @@ impl CharacterBank { .collect() } - pub fn count(&self) -> usize { - self.items.len() - } - - pub fn deposit_item(&mut self, mut inventory_item: InventoryItemHandle, amount: usize) -> Option<&BankItem> { - let remove = match inventory_item.item_mut()? { - InventoryItem::Individual(individual_inventory_item) => { - if self.items.len() >= BANK_CAPACITY { - return None - } - self.items.push(BankItem::Individual(IndividualBankItem { - entity_id: individual_inventory_item.entity_id, - item_id: individual_inventory_item.item_id, - item: individual_inventory_item.item.clone(), - })); - true - }, - InventoryItem::Stacked(stacked_inventory_item) => { - let existing_bank_item = self.items.iter_mut() - .find_map(|item| { - if let BankItem::Stacked(stacked_bank_item) = item { - if stacked_bank_item.tool == stacked_inventory_item.tool { - return Some(stacked_bank_item) - } - } - None - }); - - match existing_bank_item { - Some(stacked_bank_item) => { - if stacked_bank_item.count() + stacked_inventory_item.count() > stacked_inventory_item.tool.max_stack() { - return None - } - - let mut deposited_entity_ids = stacked_inventory_item.take_entity_ids(amount)?; - stacked_bank_item.entity_ids.append(&mut deposited_entity_ids); - } - None => { - if self.items.len() >= BANK_CAPACITY { - return None - } - let deposited_entity_ids = stacked_inventory_item.take_entity_ids(amount)?; - - self.item_id_counter += 1; - self.items.push(BankItem::Stacked(StackedBankItem { - entity_ids: deposited_entity_ids, - item_id: ClientItemId(self.item_id_counter), - tool: stacked_inventory_item.tool, - })) - } - } - stacked_inventory_item.count() == 0 - } - }; - - if remove { - inventory_item.remove_from_inventory(); - } - - self.items.last() - } - - pub fn as_bank_entity(&self, _character_id: &CharacterEntityId, _bank_name: &BankName) -> BankEntity { + pub fn as_bank_entity(&self) -> BankEntity { BankEntity { - items: self.items.iter() + items: self.bank.0.iter() .map(|item| { - match item { - BankItem::Individual(item) => { + match &item.item { + BankItemDetail::Individual(item) => { BankItemEntity::Individual(ItemEntity { id: item.entity_id, item: item.item.clone(), }) }, - BankItem::Stacked(items) => { + BankItemDetail::Stacked(items) => { BankItemEntity::Stacked(items.entity_ids.iter() .map(|id| { ItemEntity { @@ -321,4 +292,49 @@ impl CharacterBank { } } +impl std::cmp::PartialEq for BankItem { + fn eq(&self, other: &BankItem) -> bool { + let mut self_bytes = [0u8; 4]; + let mut other_bytes = [0u8; 4]; + self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]); + other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]); + + let self_value = u32::from_be_bytes(self_bytes); + let other_value = u32::from_be_bytes(other_bytes); + + self_value.eq(&other_value) + } +} + +impl std::cmp::Eq for BankItem {} + +impl std::cmp::PartialOrd for BankItem { + fn partial_cmp(&self, other: &BankItem) -> Option { + let mut self_bytes = [0u8; 4]; + let mut other_bytes = [0u8; 4]; + self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]); + other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]); + + + let self_value = u32::from_be_bytes(self_bytes); + let other_value = u32::from_be_bytes(other_bytes); + + self_value.partial_cmp(&other_value) + } +} + +impl std::cmp::Ord for BankItem { + fn cmp(&self, other: &BankItem) -> std::cmp::Ordering { + let mut self_bytes = [0u8; 4]; + let mut other_bytes = [0u8; 4]; + self_bytes.copy_from_slice(&self.item.as_client_bytes()[0..4]); + other_bytes.copy_from_slice(&other.item.as_client_bytes()[0..4]); + + + let self_value = u32::from_le_bytes(self_bytes); + let other_value = u32::from_le_bytes(other_bytes); + + self_value.cmp(&other_value) + } +} diff --git a/src/ship/items/floor.rs b/src/ship/items/floor.rs index f2720c0..2aab742 100644 --- a/src/ship/items/floor.rs +++ b/src/ship/items/floor.rs @@ -1,253 +1,139 @@ use crate::ship::items::ClientItemId; -use crate::entity::item::{ItemEntityId, ItemDetail}; -use crate::entity::item::Meseta; -use crate::entity::item::tool::Tool; +use crate::entity::item::{Meseta, ItemEntityId, ItemDetail}; +use std::future::Future; + use crate::ship::map::MapArea; -use crate::ship::items::inventory::{IndividualInventoryItem, StackedInventoryItem, InventoryItemHandle}; +use crate::entity::character::CharacterEntityId; +use crate::entity::item::mag::Mag; +use crate::ship::items::state::ItemStateError; +use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail}; +use crate::ship::items::inventory::{InventoryItem, InventoryItemDetail}; -#[derive(Debug, Clone)] -pub struct IndividualFloorItem { - pub entity_id: ItemEntityId, - pub item_id: ClientItemId, - pub item: ItemDetail, - pub map_area: MapArea, - pub x: f32, - pub y: f32, - pub z: f32, +pub enum FloorType { + Local, + Shared, } #[derive(Debug, Clone)] -pub struct StackedFloorItem { - pub entity_ids: Vec, - pub item_id: ClientItemId, - pub tool: Tool, - pub map_area: MapArea, - pub x: f32, - pub y: f32, - pub z: f32, -} - -impl StackedFloorItem { - pub fn count(&self) -> usize { - self.entity_ids.len() - } - - pub fn as_client_bytes(&self) -> [u8; 16] { - self.tool.as_stacked_bytes(self.count()) - } +pub enum FloorItemDetail { + Individual(IndividualItemDetail), + Stacked(StackedItemDetail), + Meseta(Meseta), } #[derive(Debug, Clone)] -pub struct MesetaFloorItem { +pub struct FloorItem { pub item_id: ClientItemId, - pub meseta: Meseta, + pub item: FloorItemDetail, pub map_area: MapArea, pub x: f32, pub y: f32, pub z: f32, } -#[derive(Debug, Clone)] -pub enum FloorItem { - Individual(IndividualFloorItem), - Stacked(StackedFloorItem), - Meseta(MesetaFloorItem), -} - impl FloorItem { - pub fn item_id(&self) -> ClientItemId { - match self { - FloorItem::Individual(individual_floor_item) => { - individual_floor_item.item_id - }, - FloorItem::Stacked(stacked_floor_item) => { - stacked_floor_item.item_id - }, - FloorItem::Meseta(meseta_floor_item) => { - meseta_floor_item.item_id - } - } - } - - pub fn x(&self) -> f32 { - match self { - FloorItem::Individual(individual_floor_item) => { - individual_floor_item.x - }, - FloorItem::Stacked(stacked_floor_item) => { - stacked_floor_item.x - }, - FloorItem::Meseta(meseta_floor_item) => { - meseta_floor_item.x - } - } - } - - pub fn y(&self) -> f32 { - match self { - FloorItem::Individual(individual_floor_item) => { - individual_floor_item.y + pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result + where + F: FnMut(T, ItemEntityId) -> Fut, + Fut: Future>, + { + match &self.item { + FloorItemDetail::Individual(individual_item) => { + param = func(param, individual_item.entity_id).await?; }, - FloorItem::Stacked(stacked_floor_item) => { - stacked_floor_item.y + FloorItemDetail::Stacked(stacked_item) => { + for entity_id in &stacked_item.entity_ids { + param = func(param, *entity_id).await?; + } }, - FloorItem::Meseta(meseta_floor_item) => { - meseta_floor_item.y - } + FloorItemDetail::Meseta(_meseta) => {}, } - } - pub fn z(&self) -> f32 { - match self { - FloorItem::Individual(individual_floor_item) => { - individual_floor_item.z - }, - FloorItem::Stacked(stacked_floor_item) => { - stacked_floor_item.z - }, - FloorItem::Meseta(meseta_floor_item) => { - meseta_floor_item.z - } - } + Ok(param) } - pub fn map_area(&self) -> MapArea { - match self { - FloorItem::Individual(individual_floor_item) => { - individual_floor_item.map_area - }, - FloorItem::Stacked(stacked_floor_item) => { - stacked_floor_item.map_area - }, - FloorItem::Meseta(meseta_floor_item) => { - meseta_floor_item.map_area + pub async fn with_mag(&self, mut param: T, mut func: F) -> Result + where + F: FnMut(T, ItemEntityId, Mag) -> Fut, + Fut: Future>, + { + if let FloorItemDetail::Individual(individual_item) = &self.item { + if let ItemDetail::Mag(mag) = &individual_item.item { + param = func(param, individual_item.entity_id, mag.clone()).await?; } } + Ok(param) } pub fn as_client_bytes(&self) -> [u8; 16] { - match self { - FloorItem::Individual(individual_floor_item) => { + match &self.item { + FloorItemDetail::Individual(individual_floor_item) => { individual_floor_item.item.as_client_bytes() }, - FloorItem::Stacked(stacked_floor_item) => { - stacked_floor_item.as_client_bytes() + FloorItemDetail::Stacked(stacked_floor_item) => { + stacked_floor_item.tool.as_stacked_bytes(stacked_floor_item.entity_ids.len()) }, - FloorItem::Meseta(meseta_floor_item) => { - meseta_floor_item.meseta.as_bytes() + FloorItemDetail::Meseta(meseta_floor_item) => { + meseta_floor_item.as_bytes() } } } } +#[derive(Debug, Clone, Default)] +pub struct LocalFloor(pub Vec); +#[derive(Debug, Clone, Default)] +pub struct SharedFloor(pub Vec); - -pub struct FloorItemHandle<'a> { - floor: &'a mut RoomFloorItems, - index: usize, -} - -impl<'a> FloorItemHandle<'a> { - pub fn item(&'a self) -> Option<&'a FloorItem> { - self.floor.0.get(self.index) - } - - pub fn remove_from_floor(self) { - self.floor.0.remove(self.index); - } +#[derive(Debug)] +pub struct FloorState { + pub character_id: CharacterEntityId, + pub local: LocalFloor, + pub shared: SharedFloor, } -// TODO: floors should keep track of their own item_ids -#[derive(Debug, Default)] -pub struct RoomFloorItems(Vec); - -impl RoomFloorItems { - pub fn add_item(&mut self, item: FloorItem) { - self.0.push(item); - } - - pub fn remove_item(&mut self, item_id: &ClientItemId) { - self.0.retain(|item| item.item_id() != *item_id); - } - - // TODO: &ClientItemId - pub fn get_item_by_id(&self, item_id: ClientItemId) -> Option<&FloorItem> { - self.0.iter().find(|item| item.item_id() == item_id) - } - - // TODO: &ClientItemId - pub fn get_item_handle_by_id(&mut self, item_id: ClientItemId) -> Option { - let index = self.0.iter().position(|item| item.item_id() == item_id)?; - Some(FloorItemHandle { - floor: self, - index, +impl FloorState { + pub fn take_item(&mut self, item_id: &ClientItemId) -> Option { + let item = self.local.0 + .drain_filter(|item| { + item.item_id == *item_id + }) + .next(); + item.or_else(|| { + self.shared.0 + .drain_filter(|item| { + item.item_id == *item_id + }) + .next() }) } - - pub fn take_item_by_id(&mut self, item_id: ClientItemId) -> Option { - self.0 - .drain_filter(|i| i.item_id() == item_id) - .next() - } - pub fn drop_individual_inventory_item(&mut self, individual_inventory_item: IndividualInventoryItem, item_drop_location: (MapArea, f32, f32, f32)) -> &IndividualFloorItem { - self.0.push(FloorItem::Individual(IndividualFloorItem { - entity_id: individual_inventory_item.entity_id, - item_id: individual_inventory_item.item_id, - item: individual_inventory_item.item, - map_area: item_drop_location.0, - x: item_drop_location.1, - y: item_drop_location.2, - z: item_drop_location.3, - })); - - match self.0.last().unwrap() { - FloorItem::Individual(item) => item, - _ => unreachable!(), - } - } + pub fn add_inventory_item(&mut self, inventory_item: InventoryItem, map_area: MapArea, position: (f32, f32, f32)) -> &FloorItem { + let floor_item = FloorItem { + item_id: inventory_item.item_id, + item: match inventory_item.item { + InventoryItemDetail::Individual(individual_item) => FloorItemDetail::Individual(individual_item), + InventoryItemDetail::Stacked(stacked_item) => FloorItemDetail::Stacked(stacked_item), + }, + map_area, + x: position.0, + y: position.1, + z: position.2, + }; - pub fn drop_stacked_inventory_item(&mut self, stacked_inventory_item: StackedInventoryItem, item_drop_location: (MapArea, f32, f32, f32)) -> &StackedFloorItem { - self.0.push(FloorItem::Stacked(StackedFloorItem { - entity_ids: stacked_inventory_item.entity_ids, - item_id: stacked_inventory_item.item_id, - tool: stacked_inventory_item.tool, - map_area: item_drop_location.0, - x: item_drop_location.1, - y: item_drop_location.2, - z: item_drop_location.3, - })); - - match self.0.last().unwrap() { - FloorItem::Stacked(item) => item, - _ => unreachable!(), - } + self.shared.0.push(floor_item); + &self.shared.0[self.shared.0.len()-1] } - // TODO: Result - // TODO: if consumed_item is not a tool items do not get placed back into inventory (should I care?) - pub fn drop_partial_stacked_inventory_item(&mut self, inventory_item: InventoryItemHandle, amount: usize, new_item_id: ClientItemId, item_drop_location: (MapArea, f32, f32, f32)) -> Option<&StackedFloorItem> { - let consumed_item = inventory_item.consume(amount).ok()?; - - if let ItemDetail::Tool(tool) = consumed_item.item() { - self.0.push(FloorItem::Stacked(StackedFloorItem { - entity_ids: consumed_item.entity_ids(), - item_id: new_item_id, - tool, - map_area: item_drop_location.0, - x: item_drop_location.1, - y: item_drop_location.2, - z: item_drop_location.3, - })) - } - else { - return None - } + pub fn add_shared_item(&mut self, floor_item: FloorItem) -> &FloorItem { + self.shared.0.push(floor_item); + &self.shared.0[self.shared.0.len()-1] + } - match self.0.last().unwrap() { - FloorItem::Stacked(item) => Some(item), - _ => unreachable!(), - } + pub fn add_local_item(&mut self, floor_item: FloorItem) -> &FloorItem { + self.local.0.push(floor_item); + &self.local.0[self.local.0.len()-1] } } + diff --git a/src/ship/items/inventory.rs b/src/ship/items/inventory.rs index da4c4b0..4024fc8 100644 --- a/src/ship/items/inventory.rs +++ b/src/ship/items/inventory.rs @@ -1,248 +1,68 @@ use std::cmp::Ordering; -use thiserror::Error; use libpso::character::character; +use crate::ship::items::ClientItemId; +use crate::entity::item::{Meseta, ItemEntityId, ItemDetail, ItemEntity, InventoryEntity, InventoryItemEntity, EquippedEntity}; +use std::future::Future; + use crate::entity::character::CharacterEntityId; -use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, ItemType, InventoryEntity, InventoryItemEntity, EquippedEntity}; -use crate::entity::item::tool::{Tool, ToolType}; +use crate::entity::item::tool::ToolType; use crate::entity::item::mag::Mag; -use crate::entity::item::weapon::Weapon; -use crate::ship::items::{ClientItemId, BankItem, BankItemHandle, ItemManagerError}; -use crate::ship::items::floor::{IndividualFloorItem, StackedFloorItem}; use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem}; - -const INVENTORY_CAPACITY: usize = 30; - - -#[derive(Debug, Clone)] -pub struct InventorySlot(pub usize); - - -#[derive(Debug, Clone)] -pub struct IndividualInventoryItem { - pub entity_id: ItemEntityId, - pub item_id: ClientItemId, - pub item: ItemDetail, -} - -impl IndividualInventoryItem { - pub fn mag(&self) -> Option<&Mag> { - match self.item { - ItemDetail::Mag(ref mag) => Some(mag), - _ => None - } - } - - pub fn weapon(&self) -> Option<&Weapon> { - match self.item { - ItemDetail::Weapon(ref weapon) => Some(weapon), - _ => None - } - } - - pub fn mag_mut(&mut self) -> Option<&mut Mag> { - match self.item { - ItemDetail::Mag(ref mut mag) => Some(mag), - _ => None - } - } -} - -#[derive(Debug, Clone)] -pub struct StackedInventoryItem { - pub entity_ids: Vec, - pub item_id: ClientItemId, - pub tool: Tool, -} - -impl StackedInventoryItem { - pub fn count(&self) -> usize { - self.entity_ids.len() - } - - pub fn take_entity_ids(&mut self, amount: usize) -> Option> { - if amount <= self.count() { - Some(self.entity_ids.drain(..amount).collect()) - } - else { - None - } - } -} - -#[derive(Debug, Clone)] -pub enum InventoryItem { - Individual(IndividualInventoryItem), - Stacked(StackedInventoryItem), -} - -#[derive(Error, Debug, Clone)] -#[error("")] -pub enum InventoryItemAddToError { - BothAreNotStacked, - DifferentTool, - ExceedsCapacity, +use crate::ship::items::state::ItemStateError; +use crate::ship::items::state::{IndividualItemDetail, StackedItemDetail, AddItemResult}; +use crate::ship::items::floor::{FloorItem, FloorItemDetail}; + +#[derive(Clone, Debug)] +pub enum InventoryItemDetail { + Individual(IndividualItemDetail), + Stacked(StackedItemDetail), } -#[derive(Error, Debug, Clone)] -#[error("")] -pub enum InventoryAddError { -} - -#[derive(Debug, Clone)] -pub enum YesThereIsSpace { - NewStack, - ExistingStack, -} - -#[derive(Debug, Clone)] -pub enum NoThereIsNotSpace { - FullStack, - FullInventory, -} - -#[derive(Debug, Clone)] -pub enum SpaceForStack { - Yes(YesThereIsSpace), - No(NoThereIsNotSpace), -} - -impl InventoryItem { - pub fn entity_ids(&self) -> Vec { +impl InventoryItemDetail { + // TODO: rename as_stacked for consistency + pub fn stacked(&self) -> Option<&StackedItemDetail> { match self { - InventoryItem::Individual(individual_inventory_item) => { - vec![individual_inventory_item.entity_id] - }, - InventoryItem::Stacked(stacked_inventory_item) => { - stacked_inventory_item.entity_ids.clone() - } + InventoryItemDetail::Stacked(sitem) => Some(sitem), + _ => None, } } - - pub fn item_id(&self) -> ClientItemId { + // TODO: rename as_stacked_mut for consistency + pub fn stacked_mut(&mut self) -> Option<&mut StackedItemDetail> { match self { - InventoryItem::Individual(individual_inventory_item) => { - individual_inventory_item.item_id - }, - InventoryItem::Stacked(stacked_inventory_item) => { - stacked_inventory_item.item_id - } + InventoryItemDetail::Stacked(sitem) => Some(sitem), + _ => None, } } - pub fn set_item_id(&mut self, item_id: ClientItemId) { + pub fn as_individual(&self) -> Option<&IndividualItemDetail> { match self { - InventoryItem::Individual(individual_inventory_item) => { - individual_inventory_item.item_id = item_id - }, - InventoryItem::Stacked(stacked_inventory_item) => { - stacked_inventory_item.item_id = item_id - } + InventoryItemDetail::Individual(iitem) => Some(iitem), + _ => None, } } - pub fn item_type(&self) -> ItemType { + pub fn as_individual_mut(&mut self) -> Option<&mut IndividualItemDetail> { match self { - InventoryItem::Individual(individual_inventory_item) => { - individual_inventory_item.item.item_type() - }, - InventoryItem::Stacked(stacked_inventory_item) => { - ItemType::Tool(stacked_inventory_item.tool.tool) - } - } - } - - // TOOD: delete? - pub fn are_same_stackable_tool(&self, other_stacked_item: &StackedFloorItem) -> bool { - match self { - InventoryItem::Stacked(self_stacked_item) => { - self_stacked_item.tool == other_stacked_item.tool - && self_stacked_item.tool.is_stackable() && other_stacked_item.tool.is_stackable() - }, - _ => false - } - } - - // TOOD: delete? - pub fn can_combine_stacks(&self, other_stacked_item: &StackedFloorItem) -> bool { - match self { - InventoryItem::Stacked(self_stacked_item) => { - self_stacked_item.tool == other_stacked_item.tool - && self_stacked_item.tool.is_stackable() && other_stacked_item.tool.is_stackable() - && self_stacked_item.count() + other_stacked_item.count() <= self_stacked_item.tool.max_stack() - }, - _ => false - } - } - - // TODO: result - // TOOD: delete? - pub fn combine_stacks(&mut self, other_stacked_item: &mut StackedFloorItem) { - if let InventoryItem::Stacked(self_stacked_item) = self { - self_stacked_item.entity_ids.append(&mut other_stacked_item.entity_ids); + InventoryItemDetail::Individual(iitem) => Some(iitem), + _ => None, } } pub fn as_client_bytes(&self) -> [u8; 16] { match self { - InventoryItem::Individual(item) => { - match &item.item { - ItemDetail::Weapon(w) => w.as_bytes(), - ItemDetail::Armor(a) => a.as_bytes(), - ItemDetail::Shield(s) => s.as_bytes(), - ItemDetail::Unit(u) => u.as_bytes(), - ItemDetail::Tool(t) => t.as_individual_bytes(), - ItemDetail::TechniqueDisk(d) => d.as_bytes(), - ItemDetail::Mag(m) => m.as_bytes(), - ItemDetail::ESWeapon(e) => e.as_bytes(), - } + InventoryItemDetail::Individual(item) => { + item.as_client_bytes() }, - InventoryItem::Stacked(item) => { + InventoryItemDetail::Stacked(item) => { item.tool.as_stacked_bytes(item.entity_ids.len()) }, } } - pub fn can_add_to(&mut self, stacked_floor_item: &StackedFloorItem) -> Result<(), InventoryItemAddToError> { - if let InventoryItem::Stacked(stacked_inventory_item) = self { - if stacked_floor_item.tool != stacked_inventory_item.tool { - return Err(InventoryItemAddToError::DifferentTool) - } - - if stacked_floor_item.tool.tool.max_stack() < (stacked_floor_item.count() + stacked_inventory_item.count()) { - return Err(InventoryItemAddToError::ExceedsCapacity) - } - Ok(()) - } - else { - Err(InventoryItemAddToError::BothAreNotStacked) - } - } - - pub fn add_to(&mut self, mut stacked_floor_item: StackedFloorItem) -> Result<(), InventoryItemAddToError> { - self.can_add_to(&stacked_floor_item)?; - if let InventoryItem::Stacked(stacked_inventory_item) = self { - stacked_inventory_item.entity_ids.append(&mut stacked_floor_item.entity_ids); - } - Ok(()) - } - - pub fn individual(&self) -> Option<&IndividualInventoryItem> { - match self { - InventoryItem::Individual(ref individual_inventory_item) => Some(individual_inventory_item), - _ => None - } - } - - pub fn individual_mut(&mut self) -> Option<&mut IndividualInventoryItem> { - match self { - InventoryItem::Individual(ref mut individual_inventory_item) => Some(individual_inventory_item), - _ => None - } - } - - pub fn get_sell_price(&self) -> Result { + // TODO: this should probably go somewhere a bit more fundamental like ItemDetail + pub fn sell_price(&self) -> Result { match self { - InventoryItem::Individual(individual_item) => { + InventoryItemDetail::Individual(individual_item) => { match &individual_item.item { // TODO: can wrapped items be sold? ItemDetail::Weapon(w) => { @@ -282,7 +102,7 @@ impl InventoryItem { Ok((ToolShopItem::from(d).price() / 8) as u32) }, ItemDetail::Mag(_m) => { - Err(ItemManagerError::ItemNotSellable(self.clone())) + Err(ItemStateError::ItemNotSellable) }, ItemDetail::ESWeapon(_e) => { Ok(10u32) @@ -290,627 +110,294 @@ impl InventoryItem { } }, // the number of stacked items sold is handled by the caller. this is just the price of 1 - InventoryItem::Stacked(stacked_item) => { - Ok((ToolShopItem::from(&stacked_item.tool).price() / 8) as u32) + InventoryItemDetail::Stacked(stacked_item) => { + Ok(((ToolShopItem::from(&stacked_item.tool).price() / 8) as u32) * stacked_item.count() as u32) }, } } - pub fn stacked(&self) -> Option<&StackedInventoryItem> { - match self { - InventoryItem::Stacked(ref stacked_inventory_item) => Some(stacked_inventory_item), - _ => None - } - } - - pub fn stacked_mut(&mut self) -> Option<&mut StackedInventoryItem> { - match self { - InventoryItem::Stacked(ref mut stacked_inventory_item) => Some(stacked_inventory_item), - _ => None - } - } - - pub fn mag(&self) -> Option<&Mag> { - match self { - InventoryItem::Individual(individual_inventory_item) => individual_inventory_item.mag(), - _ => None - } - } } - - -#[derive(Error, Debug, Clone)] -#[error("")] -pub enum InventoryItemConsumeError { - InconsistentState, - InvalidAmount, -} - -pub struct IndividualConsumedItem { - pub entity_id: ItemEntityId, - pub item: ItemDetail, -} - -pub struct StackedConsumedItem { - pub entity_ids: Vec, - pub tool: Tool -} - -pub enum ConsumedItem { - Individual(IndividualConsumedItem), - Stacked(StackedConsumedItem), +#[derive(Clone, Debug)] +pub struct InventoryItem { + pub item_id: ClientItemId, + pub item: InventoryItemDetail, } -impl ConsumedItem { - pub fn entity_ids(&self) -> Vec { - match self { - ConsumedItem::Individual(individual_consumed_item) => { - vec![individual_consumed_item.entity_id] +impl InventoryItem { + pub async fn with_entity_id(&self, mut param: T, mut func: F) -> Result + where + F: FnMut(T, ItemEntityId) -> Fut, + Fut: Future>, + { + match &self.item { + InventoryItemDetail::Individual(individual_item) => { + param = func(param, individual_item.entity_id).await?; }, - ConsumedItem::Stacked(stacked_consumed_item) => { - stacked_consumed_item.entity_ids.clone() + InventoryItemDetail::Stacked(stacked_item) => { + for entity_id in &stacked_item.entity_ids { + param = func(param, *entity_id).await?; + } } } + + Ok(param) } - pub fn item(&self) -> ItemDetail { - match self { - ConsumedItem::Individual(individual_consumed_item) => { - individual_consumed_item.item.clone() - }, - ConsumedItem::Stacked(stacked_consumed_item) => { - ItemDetail::Tool(stacked_consumed_item.tool) + pub async fn with_mag(&self, mut param: T, mut func: F) -> Result + where + F: FnMut(T, ItemEntityId, Mag) -> Fut, + Fut: Future>, + { + if let InventoryItemDetail::Individual(individual_item) = &self.item { + if let ItemDetail::Mag(mag) = &individual_item.item { + param = func(param, individual_item.entity_id, mag.clone()).await?; } } + Ok(param) } } +#[derive(Clone, Debug)] +pub struct Inventory(Vec); -pub struct InventoryItemHandle<'a> { - inventory: &'a mut CharacterInventory, - slot: usize, -} - -impl<'a> InventoryItemHandle<'a> { - pub fn item(&'a self) -> Option<&'a InventoryItem> { - self.inventory.items.get(self.slot) - } - - pub fn item_mut(&mut self) -> Option<&mut InventoryItem> { - self.inventory.items.get_mut(self.slot) - } - - pub fn remove_from_inventory(self) { - self.inventory.items.remove(self.slot); - } - - pub fn consume(self, amount: usize) -> Result { - enum RemoveMethod { - EntireThing(ConsumedItem), - Partial(Tool), - } - - let inventory_item = self.inventory.items.get(self.slot).ok_or(InventoryItemConsumeError::InconsistentState)?; - let remove_method = match inventory_item { - InventoryItem::Individual(individual_inventory_item) => { - RemoveMethod::EntireThing(ConsumedItem::Individual(IndividualConsumedItem { - entity_id: individual_inventory_item.entity_id, - item: individual_inventory_item.item.clone() - })) - }, - InventoryItem::Stacked(stacked_inventory_item) => { - match stacked_inventory_item.count().cmp(&amount) { - Ordering::Equal => { - RemoveMethod::EntireThing(ConsumedItem::Stacked(StackedConsumedItem { - entity_ids: stacked_inventory_item.entity_ids.clone(), - tool: stacked_inventory_item.tool, - })) - }, - Ordering::Greater => { - RemoveMethod::Partial(stacked_inventory_item.tool) - }, - Ordering::Less => { - return Err(InventoryItemConsumeError::InvalidAmount) - } - } - }, - }; - - match remove_method { - RemoveMethod::EntireThing(consumed_item) => { - self.inventory.items.remove(self.slot); - Ok(consumed_item) - }, - RemoveMethod::Partial(tool) => { - let entity_ids = self.inventory.items.get_mut(self.slot) - .and_then(|item| { - if let InventoryItem::Stacked(stacked_inventory_item) = item { - Some(stacked_inventory_item.entity_ids.drain(..amount).collect::>()) - } - else { - None - } - }) - .ok_or(InventoryItemConsumeError::InvalidAmount)?; - Ok(ConsumedItem::Stacked(StackedConsumedItem { - entity_ids, - tool, - })) - } - } - } - - pub fn get_slot(&self) -> usize { - self.slot +impl Inventory { + pub fn new(items: Vec) -> Inventory { + Inventory(items) } } - - -#[derive(Debug)] -pub struct CharacterInventory { - item_id_counter: u32, - items: Vec, - equipped: EquippedEntity, +#[derive(thiserror::Error, Debug)] +pub enum InventoryError { + #[error("inventory full")] + InventoryFull, + #[error("stack full")] + StackFull, + #[error("meseta full")] + MesetaFull, } -impl CharacterInventory { - pub fn new(items: Vec, equipped: &EquippedEntity) -> CharacterInventory { - CharacterInventory{ - item_id_counter: 0, - items, - equipped: equipped.clone(), - } - } +#[derive(Clone)] +pub struct InventoryState { + pub character_id: CharacterEntityId, + pub item_id_counter: u32, + pub inventory: Inventory, + pub equipped: EquippedEntity, + pub meseta: Meseta, +} +impl InventoryState { pub fn initialize_item_ids(&mut self, base_item_id: u32) { - for (i, item) in self.items.iter_mut().enumerate() { - item.set_item_id(ClientItemId(base_item_id + i as u32)); + for (i, item) in self.inventory.0.iter_mut().enumerate() { + item.item_id = ClientItemId(base_item_id + i as u32); } - self.item_id_counter = base_item_id + self.items.len() as u32 + 1; + self.item_id_counter = base_item_id + self.inventory.0.len() as u32 + 1; } - pub fn as_client_inventory_items(&self) -> [character::InventoryItem; 30] { - self.items.iter() - .enumerate() - .fold([character::InventoryItem::default(); 30], |mut inventory, (slot, item)| { - let bytes = item.as_client_bytes(); - inventory[slot].data1.copy_from_slice(&bytes[0..12]); - inventory[slot].data2.copy_from_slice(&bytes[12..16]); - inventory[slot].item_id = item.item_id().0; - inventory[slot].equipped = 0; - inventory[slot].flags = 0; - - if let InventoryItem::Individual(individual_item) = item { - if self.equipped.is_equipped(&individual_item.entity_id) { - if let ItemDetail::Unit(_) = individual_item.item { - inventory[slot].data1[4] = self.equipped.unit.iter() - .enumerate() - .find(|(_, u_id)| **u_id == Some(individual_item.entity_id)) - .map(|(a, _)| a) - .unwrap_or(0) as u8 - } - inventory[slot].equipped = 1; - inventory[slot].flags |= 8; - } - } - inventory - }) - } - - pub fn slot(&self, slot: usize) -> Option<&InventoryItem> { - self.items.get(slot) + pub fn new_item_id(&mut self) -> ClientItemId { + self.item_id_counter += 1; + ClientItemId(self.item_id_counter) } pub fn count(&self) -> usize { - self.items.len() - } - - pub fn space_for_individual_item(&self) -> bool { - self.count() < INVENTORY_CAPACITY + self.inventory.0.len() } - pub fn space_for_stacked_item(&self, tool: &Tool, amount: usize) -> SpaceForStack { - let existing_item = self.items.iter() - .filter_map(|item| { - match item { - InventoryItem::Stacked(s_item) => { - Some(s_item) - }, - _ => None - } - }) - .find(|s_item| { - s_item.tool == *tool - }); - - match existing_item { - Some(item) => { - if item.count() + amount <= tool.tool.max_stack() { - SpaceForStack::Yes(YesThereIsSpace::ExistingStack) + pub fn add_floor_item(&mut self, item: FloorItem) -> Result { + match item.item { + FloorItemDetail::Individual(iitem) => { + if self.inventory.0.len() >= 30 { + Err(InventoryError::InventoryFull) } else { - SpaceForStack::No(NoThereIsNotSpace::FullStack) - } - } - None => { - if self.count() < INVENTORY_CAPACITY { - SpaceForStack::Yes(YesThereIsSpace::NewStack) - } - else { - SpaceForStack::No(NoThereIsNotSpace::FullInventory) + self.inventory.0.push(InventoryItem { + item_id: item.item_id, + item: InventoryItemDetail::Individual(iitem) + }); + Ok(AddItemResult::NewItem) } - } - } - } - - pub fn stack_item_id(&self, tool: &Tool) -> Option { - self.items.iter() - .filter_map(|item| { - match item { - InventoryItem::Stacked(s_item) => { - Some(s_item) + }, + FloorItemDetail::Stacked(sitem) => { + let existing_stack = self.inventory.0 + .iter_mut() + .filter_map(|item| item.item.stacked_mut()) + .find(|item| { + item.tool == sitem.tool + }); + match existing_stack { + Some(existing_stack) => { + if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { + Err(InventoryError::StackFull) + } + else { + existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); + Ok(AddItemResult::AddToStack) + } }, - _ => None - } - }) - .find(|s_item| { - s_item.tool == *tool - }) - .map(|item| { - item.item_id - }) - } - - pub fn get_item_handle_by_id(&mut self, item_id: ClientItemId) -> Option { - let (slot, _) = self.items.iter() - .enumerate() - .find(|(_, item)| { - item.item_id() == item_id - })?; - Some(InventoryItemHandle { - inventory: self, - slot, - }) - } - - pub fn get_equipped_mag_handle(&mut self) -> Option { - let (slot, _) = self.items.iter() - .enumerate() - .find(|(_, item)| { - if let InventoryItem::Individual(individual_inventory_item) = item { - if let ItemDetail::Mag(_) = &individual_inventory_item.item { - return self.equipped.is_equipped(&individual_inventory_item.entity_id) - } - } - false - })?; - Some(InventoryItemHandle { - inventory: self, - slot, - }) - } - - pub fn get_equipped_armor_handle(&mut self) -> Option { - let (slot, _) = self.items.iter() - .enumerate() - .find(|(_, item)| { - if let InventoryItem::Individual(individual_inventory_item) = item { - if let ItemDetail::Armor(_) = &individual_inventory_item.item { - return self.equipped.is_equipped(&individual_inventory_item.entity_id) + None => { + if self.inventory.0.len() >= 30 { + Err(InventoryError::InventoryFull) + } + else { + self.inventory.0.push(InventoryItem { + item_id: item.item_id, + item: InventoryItemDetail::Stacked(sitem) + }); + Ok(AddItemResult::NewItem) + } } } - false - })?; - Some(InventoryItemHandle { - inventory: self, - slot, - }) - } - pub fn get_equipped_shield_handle(&mut self) -> Option { - let (slot, _) = self.items.iter() - .enumerate() - .find(|(_, item)| { - if let InventoryItem::Individual(individual_inventory_item) = item { - if let ItemDetail::Shield(_) = &individual_inventory_item.item { - return self.equipped.is_equipped(&individual_inventory_item.entity_id) - } + }, + FloorItemDetail::Meseta(meseta) => { + if self.meseta == Meseta(999999) { + Err(InventoryError::MesetaFull) } - false - })?; - Some(InventoryItemHandle { - inventory: self, - slot, - }) - } - - pub fn get_equipped_weapon_handle(&mut self) -> Option { - let (slot, _) = self.items.iter() - .enumerate() - .find(|(_, item)| { - if let InventoryItem::Individual(individual_inventory_item) = item { - if let ItemDetail::Weapon(_) = &individual_inventory_item.item { - return self.equipped.is_equipped(&individual_inventory_item.entity_id) - } + else { + self.meseta.0 = std::cmp::min(self.meseta.0 + meseta.0, 999999); + Ok(AddItemResult::Meseta) } - false - })?; - Some(InventoryItemHandle { - inventory: self, - slot, - }) - } - - pub fn get_item_by_id(&self, item_id: ClientItemId) -> Option<&InventoryItem> { - self.items.iter() - .find(|item| { - item.item_id() == item_id - }) - } - - pub fn take_item_by_id(&mut self, item_id: ClientItemId) -> Option { - self.items - .drain_filter(|i| i.item_id() == item_id) - .next() - } - - pub fn take_stacked_item_by_id(&mut self, item_id: ClientItemId, amount: usize) -> Option { - let idx = self.items - .iter_mut() - .position(|i| i.item_id() == item_id)?; - let item: &mut StackedInventoryItem = self.items.get_mut(idx)?.stacked_mut()?; - match item.entity_ids.len().cmp(&amount) { - Ordering::Equal => { - let item = self.items.remove(idx); - item.stacked().cloned() }, - Ordering::Greater => { - let entity_ids = item.entity_ids.drain(..amount).collect(); - Some(StackedInventoryItem { - entity_ids, - tool: item.tool, - item_id: item.item_id, - }) - }, - Ordering::Less => { - None - } } } - pub fn add_item(&mut self, item: InventoryItem) { - self.items.push(item); - } - - pub fn add_stacked_item(&mut self, mut item: StackedInventoryItem) { - let existing_item = self.items - .iter_mut() - .filter_map(|i| { - match i { - InventoryItem::Stacked(stacked) => { - Some(stacked) - }, - _ => None + pub fn add_item(&mut self, item: InventoryItem) -> Result<(AddItemResult, InventoryItem), InventoryError> { + match &item.item { + InventoryItemDetail::Individual(_) => { + if self.inventory.0.len() >= 30 { + Err(InventoryError::InventoryFull) + } + else { + self.inventory.0.push(item); + Ok(( + AddItemResult::NewItem, + self.inventory.0 + .last() + .unwrap() + .clone() + )) } - }) - .find(|i| { - i.tool == item.tool - }); - - match existing_item { - Some(existing_item) => { - existing_item.entity_ids.append(&mut item.entity_ids) }, - None => { - self.items.push(InventoryItem::Stacked(item)) + InventoryItemDetail::Stacked(sitem) => { + let existing_stack = self.inventory.0 + .iter_mut() + .filter_map(|item| item.item.stacked_mut()) + .find(|item| { + item.tool == sitem.tool + }); + match existing_stack { + Some(existing_stack) => { + if existing_stack.entity_ids.len() + sitem.entity_ids.len() > sitem.tool.max_stack() { + Err(InventoryError::StackFull) + } + else { + existing_stack.entity_ids.append(&mut sitem.entity_ids.clone()); + Ok(( + AddItemResult::AddToStack, + self.inventory.0[self.inventory.0 + .iter() + .filter_map(|item| item.item.stacked()) + .position(|item| item.tool == sitem.tool) + .unwrap()] + .clone() + )) + } + }, + None => { + if self.inventory.0.len() >= 30 { + Err(InventoryError::InventoryFull) + } + else { + self.inventory.0.push(item); + Ok(( + AddItemResult::NewItem, + self.inventory.0 + .last() + .unwrap() + .clone() + )) + } + } + } } } } - pub fn add_item_with_new_item_id(&mut self, item: InventoryItem, item_id: ClientItemId) { - match item { - InventoryItem::Individual(mut individual_inventory_item) => { - individual_inventory_item.item_id = item_id; - self.add_item(InventoryItem::Individual(individual_inventory_item)); + pub fn take_item(&mut self, item_id: &ClientItemId, amount: u32) -> Option { + let idx = self.inventory.0 + .iter() + .position(|i| i.item_id == *item_id)?; + match &mut self.inventory.0[idx].item { + InventoryItemDetail::Individual(_individual_item) => { + Some(self.inventory.0.remove(idx)) }, - InventoryItem::Stacked(mut stacked_inventory_item) => { - stacked_inventory_item.item_id = item_id; - self.add_stacked_item(stacked_inventory_item) - } - } - } - - pub fn add_individual_floor_item(&mut self, floor_item: &IndividualFloorItem) -> &InventoryItem { - self.items.push(InventoryItem::Individual(IndividualInventoryItem { - entity_id: floor_item.entity_id, - item_id: floor_item.item_id, - item: floor_item.item.clone(), - })); - - self.items.last().unwrap() - } - - // TODO: should these pick up functions take floor_item as mut and remove the ids? - pub fn pick_up_individual_floor_item(&mut self, floor_item: &IndividualFloorItem) -> Option<(&IndividualInventoryItem, InventorySlot)> { - if self.count() >= 30 { - return None; - } - - self.items.push(InventoryItem::Individual(IndividualInventoryItem { - entity_id: floor_item.entity_id, - item_id: floor_item.item_id, - item: floor_item.item.clone(), - })); - - if let Some(InventoryItem::Individual(new_item)) = self.items.last() { - Some((new_item, InventorySlot(self.count()-1))) - } - else { - None - } - } - - pub fn add_stacked_floor_item(&mut self, floor_item: &StackedFloorItem) { - let existing_item = self.items.iter_mut() - .filter_map(|item| { - match item { - InventoryItem::Stacked(s_item) => Some(s_item), - _ => None, - } - - }) - .find(|item| { - item.tool == floor_item.tool - }); - - match existing_item { - Some(item) => { - item.entity_ids.append(&mut floor_item.entity_ids.clone()) - }, - None => { - self.items.push(InventoryItem::Stacked(StackedInventoryItem { - entity_ids: floor_item.entity_ids.clone(), - item_id: floor_item.item_id, - tool: floor_item.tool, - })); - } - } - } + InventoryItemDetail::Stacked(stacked_item) => { + let remove_all = (amount == 0) || match stacked_item.entity_ids.len().cmp(&(amount as usize)) { + Ordering::Equal => true, + Ordering::Greater => false, + Ordering::Less => return None, + }; - // TODO: can be simplified using find instead of position - pub fn pick_up_stacked_floor_item(&mut self, floor_item: &StackedFloorItem) -> Option<(&StackedInventoryItem, InventorySlot)> { - let existing_stack_position = self.items.iter() - .position(|inventory_item| { - if let InventoryItem::Stacked(stacked_inventory_item) = inventory_item { - if stacked_inventory_item.tool == floor_item.tool { - return true - } - } - false - }); - - if let Some(existing_stack_position) = existing_stack_position { - if let Some(InventoryItem::Stacked(stacked_item)) = self.items.get_mut(existing_stack_position) { - if stacked_item.count() + floor_item.count() <= stacked_item.tool.max_stack() { - stacked_item.entity_ids.append(&mut floor_item.entity_ids.clone()); - Some((stacked_item, InventorySlot(existing_stack_position))) + if remove_all { + Some(self.inventory.0.remove(idx)) } else { - None + let entity_ids = stacked_item.entity_ids.drain(..(amount as usize)).collect(); + self.item_id_counter += 1; + Some(InventoryItem { + item_id: ClientItemId(self.item_id_counter), + item: InventoryItemDetail::Stacked(StackedItemDetail { + entity_ids, + tool: stacked_item.tool, + })}) } } - else { - None - } - } - else { - let new_stacked_item = InventoryItem::Stacked(StackedInventoryItem { - entity_ids: floor_item.entity_ids.clone(), - item_id: floor_item.item_id, - tool: floor_item.tool, - }); - - self.items.push(new_stacked_item); - if let Some(InventoryItem::Stacked(new_item)) = self.items.last() { - Some((new_item, InventorySlot(self.count()-1))) - } - else { - None - } } } - pub fn withdraw_item(&mut self, mut bank_item: BankItemHandle, amount: usize) -> Option<(&InventoryItem, usize)> { - let (remove, slot) = match bank_item.item_mut()? { - BankItem::Individual(individual_bank_item) => { - if self.items.len() >= INVENTORY_CAPACITY { - return None - } - self.items.push(InventoryItem::Individual(IndividualInventoryItem { - entity_id: individual_bank_item.entity_id, - item_id: individual_bank_item.item_id, - item: individual_bank_item.item.clone(), - })); - (true, self.count()-1) - }, - BankItem::Stacked(stacked_bank_item) => { - let existing_inventory_item = self.items.iter_mut() - .enumerate() - .find_map(|(index, item)| { - if let InventoryItem::Stacked(stacked_inventory_item) = item { - if stacked_bank_item.tool == stacked_inventory_item.tool { - return Some((index, stacked_inventory_item)) - } - } - None - }); - - let slot = match existing_inventory_item { - Some((slot, stacked_inventory_item)) => { - if stacked_inventory_item.count() + stacked_bank_item.count() > stacked_bank_item.tool.max_stack() { - return None - } - - let mut withdrawn_entity_ids = stacked_bank_item.take_entity_ids(amount)?; - stacked_inventory_item.entity_ids.append(&mut withdrawn_entity_ids); - slot - } - None => { - if self.items.len() >= INVENTORY_CAPACITY { - return None - } - let withdrawn_entity_ids = stacked_bank_item.take_entity_ids(amount)?; - - self.item_id_counter += 1; // oh no - self.items.push(InventoryItem::Stacked(StackedInventoryItem { - entity_ids: withdrawn_entity_ids, - item_id: ClientItemId(self.item_id_counter), - tool: stacked_bank_item.tool, - })); - self.count()-1 - } - }; - (stacked_bank_item.count() == 0, slot) - } - }; - - if remove { - bank_item.remove_from_bank(); - } - - self.items.last().map(|item| { - (item, slot) - }) + // TODO: rename get_item_by_client_id + pub fn get_by_client_id(&self, item_id: &ClientItemId) -> Option<&InventoryItem> { + self.inventory.0 + .iter() + .find(|i| i.item_id == *item_id) } - pub fn iter(&self) -> impl Iterator { - self.items.iter() + pub fn get_by_client_id_mut(&mut self, item_id: &ClientItemId) -> Option<&mut InventoryItem> { + self.inventory.0 + .iter_mut() + .find(|i| i.item_id == *item_id) } - pub fn items(&self) -> &Vec { - &self.items + pub fn add_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { + if self.meseta.0 == 999999 { + return Err(ItemStateError::FullOfMeseta) + } + self.meseta.0 = std::cmp::min(self.meseta.0 + amount, 999999); + Ok(()) } - pub fn set_items(&mut self, sorted_items: Vec) { - self.items = sorted_items; + pub fn add_meseta_no_overflow(&mut self, amount: u32) -> Result<(), ItemStateError> { + if self.meseta.0 + amount > 999999 { + return Err(ItemStateError::FullOfMeseta) + } + self.meseta.0 += amount; + Ok(()) } - pub fn remove_by_id(&mut self, id: ClientItemId) -> Option { - self.items.iter() - .position(|i| i.item_id() == id) - .map(|position| { - self.items.remove(position) - }) + pub fn remove_meseta(&mut self, amount: u32) -> Result<(), ItemStateError> { + if amount > self.meseta.0 { + return Err(ItemStateError::InvalidMesetaRemoval(amount)) + } + self.meseta.0 -= amount; + Ok(()) } - pub fn equip(&mut self, id: &ClientItemId, equip_slot: u8) { - for item in &self.items { - if let InventoryItem::Individual(inventory_item) = item { - if inventory_item.item_id == *id { + pub fn equip(&mut self, item_id: &ClientItemId, equip_slot: u8) { + for item in &self.inventory.0 { + if let InventoryItemDetail::Individual(inventory_item) = &item.item { + if item.item_id == *item_id { match inventory_item.item { ItemDetail::Weapon(_) => self.equipped.weapon = Some(inventory_item.entity_id), ItemDetail::Armor(_) => self.equipped.armor = Some(inventory_item.entity_id), @@ -928,10 +415,10 @@ impl CharacterInventory { } } - pub fn unequip(&mut self, id: &ClientItemId) { - for item in &self.items { - if let InventoryItem::Individual(inventory_item) = item { - if inventory_item.item_id == *id { + pub fn unequip(&mut self, item_id: &ClientItemId) { + for item in &self.inventory.0 { + if let InventoryItemDetail::Individual(inventory_item) = &item.item { + if item.item_id == *item_id { match inventory_item.item { ItemDetail::Weapon(_) => self.equipped.weapon = None, ItemDetail::Armor(_) => { @@ -954,18 +441,44 @@ impl CharacterInventory { } } + pub fn equipped_mag_mut(&mut self) -> Option<(ItemEntityId, &mut Mag)> { + let mag_id = self.equipped.mag?; + self.inventory.0 + .iter_mut() + .filter_map(|i| { + let individual = i.item.as_individual_mut()?; + let entity_id = individual.entity_id; + Some((entity_id, individual.as_mag_mut()?)) + }) + .find(|(entity_id, _)| *entity_id == mag_id) + } + + pub fn sort(&mut self, item_ids: &[ClientItemId]) { + self.inventory.0.sort_by(|a, b| { + let a_index = item_ids.iter().position(|item_id| *item_id == a.item_id); + let b_index = item_ids.iter().position(|item_id| *item_id == b.item_id); + + match (a_index, b_index) { + (Some(a_index), Some(b_index)) => { + a_index.cmp(&b_index) + }, + _ => Ordering::Equal + } + }); + } + pub fn as_inventory_entity(&self, _character_id: &CharacterEntityId) -> InventoryEntity { InventoryEntity { - items: self.items.iter() + items: self.inventory.0.iter() .map(|item| { - match item { - InventoryItem::Individual(item) => { + match &item.item { + InventoryItemDetail::Individual(item) => { InventoryItemEntity::Individual(ItemEntity { id: item.entity_id, item: item.item.clone(), }) }, - InventoryItem::Stacked(items) => { + InventoryItemDetail::Stacked(items) => { InventoryItemEntity::Stacked(items.entity_ids.iter() .map(|id| { ItemEntity { @@ -984,5 +497,33 @@ impl CharacterInventory { pub fn as_equipped_entity(&self) -> EquippedEntity { self.equipped.clone() } -} + + pub fn as_client_inventory_items(&self) -> [character::InventoryItem; 30] { + self.inventory.0.iter() + .enumerate() + .fold([character::InventoryItem::default(); 30], |mut inventory, (slot, item)| { + let bytes = item.item.as_client_bytes(); + inventory[slot].data1.copy_from_slice(&bytes[0..12]); + inventory[slot].data2.copy_from_slice(&bytes[12..16]); + inventory[slot].item_id = item.item_id.0; + inventory[slot].equipped = 0; + inventory[slot].flags = 0; + + if let InventoryItemDetail::Individual(individual_item) = &item.item { + if self.equipped.is_equipped(&individual_item.entity_id) { + if let ItemDetail::Unit(_) = individual_item.item { + inventory[slot].data1[4] = self.equipped.unit.iter() + .enumerate() + .find(|(_, u_id)| **u_id == Some(individual_item.entity_id)) + .map(|(a, _)| a) + .unwrap_or(0) as u8 + } + inventory[slot].equipped = 1; + inventory[slot].flags |= 8; + } + } + inventory + }) + } +} diff --git a/src/ship/items/itemstateaction.rs b/src/ship/items/itemstateaction.rs new file mode 100644 index 0000000..3b60548 --- /dev/null +++ b/src/ship/items/itemstateaction.rs @@ -0,0 +1,137 @@ +use std::future::Future; + +#[async_trait::async_trait] +pub trait ItemAction { + type Input; + type Output; + type Start; + type Error; + + async fn action(&self, s: Self::Start, i: Self::Input) -> Result<(Self::Start, Self::Output), Self::Error>; + async fn commit(&self, v: Self::Start) -> Result<(Self::Start, Self::Output), Self::Error>; +} + + +pub struct ItemStateAction { + _t: std::marker::PhantomData, + _s: std::marker::PhantomData, + _e: std::marker::PhantomData, +} + +impl Default for ItemStateAction { + fn default() -> ItemStateAction { + ItemStateAction { + _t: std::marker::PhantomData, + _s: std::marker::PhantomData, + _e: std::marker::PhantomData, + } + } +} + +impl ItemStateAction +where + T: Send + Sync, + S: Send + Sync, + E: Send + Sync, +{ + pub fn act(self, f: F) -> ItemActionStage, F, Fut, S, E> + where + F: Fn(S, ()) -> Fut + Send + Sync, + Fut: Future> + Send + { + ItemActionStage { + _s: Default::default(), + _e: std::marker::PhantomData, + prev: self, + actionf: f, + } + } +} + +pub struct ItemActionStage +where + P: ItemAction, + F: Fn(S, P::Output) -> Fut + Send + Sync, + Fut: Future> + Send, +{ + _s: std::marker::PhantomData, + _e: std::marker::PhantomData, + prev: P, + actionf: F, +} + +#[async_trait::async_trait] +impl ItemAction for ItemActionStage +where + P: ItemAction + ItemAction + Send + Sync, + F: Fn(S, P::Output) -> Fut + Send + Sync, + Fut: Future> + Send, + S: Send + Sync, + P::Output: Send + Sync, + E: Send + Sync, + O: Send + Sync, + P::Error: Send + Sync, +{ + type Input = P::Output; + type Output = O; + type Start = S; + type Error = P::Error; + + async fn action(&self, s: Self::Start, i: Self::Input) -> Result<(Self::Start, Self::Output), Self::Error> { + (self.actionf)(s, i).await + } + + async fn commit(&self, i: Self::Start) -> Result<(Self::Start, Self::Output), Self::Error> { + let (i, prev) = self.prev.commit(i).await?; + self.action(i, prev).await + } +} + +impl ItemActionStage +where + P: ItemAction + Send + Sync, + F: Fn(S, P::Output) -> Fut + Send + Sync, + Fut: Future> + Send, + S: Send + Sync, + P::Output: Send + Sync, + E: Send + Sync, + O: Send + Sync, + P::Error: Send + Sync, +{ + #[allow(clippy::type_complexity)] + pub fn act(self, g: G) -> ItemActionStage, G, GFut, S, E> + where + S: Send + Sync, + G: Fn(S, as ItemAction>::Output) -> GFut + Send + Sync, + GFut: Future> + Send, + O2: Send + Sync, + { + ItemActionStage { + _s: Default::default(), + _e: Default::default(), + prev: self, + actionf: g, + } + } +} + +#[async_trait::async_trait] +impl ItemAction for ItemStateAction +where + T: Send + Sync, + S: Send + Sync, + E: Send + Sync, +{ + type Input = T; + type Output = (); + type Start = T; + type Error = E; + + async fn action(&self, s: Self::Start, _i: Self::Input) -> Result<(Self::Start, Self::Output), Self::Error> { + Ok((s, ())) + } + + async fn commit(&self, i: Self::Start) -> Result<(Self::Start, Self::Output), Self::Error> { + Ok((i, ())) + } +} diff --git a/src/ship/items/manager.rs b/src/ship/items/manager.rs index 950e088..e9251c4 100644 --- a/src/ship/items/manager.rs +++ b/src/ship/items/manager.rs @@ -20,7 +20,6 @@ use crate::ship::packet::handler::trade::{TradeError, OTHER_MESETA_ITEM_ID}; use crate::ship::items::bank::*; use crate::ship::items::floor::*; use crate::ship::items::inventory::*; -use crate::ship::items::use_tool; use crate::ship::items::transaction::{ItemTransaction, ItemAction, TransactionError, TransactionCommitError}; #[derive(PartialEq, Eq)] @@ -147,7 +146,7 @@ impl ItemManager { pub async fn load_character(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), anyhow::Error> { let inventory = entity_gateway.get_character_inventory(&character.id).await?; - let bank = entity_gateway.get_character_bank(&character.id, BankName("".into())).await?; + let bank = entity_gateway.get_character_bank(&character.id, &BankName("".into())).await?; let equipped = entity_gateway.get_character_equips(&character.id).await?; let inventory_items = inventory.items.into_iter() @@ -205,7 +204,7 @@ impl ItemManager { let character_bank = CharacterBank::new(bank_items); let character_meseta = entity_gateway.get_character_meseta(&character.id).await?; - let bank_meseta = entity_gateway.get_bank_meseta(&character.id, BankName("".into())).await?; + let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?; self.character_inventory.insert(character.id, character_inventory); self.character_bank.insert(character.id, character_bank); @@ -388,7 +387,7 @@ impl ItemManager { .map_err(|err| err.into()) } - pub async fn enemy_drop_item_on_local_floor(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, item_drop: ItemDrop) -> Result<&FloorItem, anyhow::Error> { + pub async fn enemy_drop_item_on_local_floor<'a, EG: EntityGateway>(&'a mut self, entity_gateway: &'a mut EG, character: &'a CharacterEntity, item_drop: ItemDrop) -> Result<&'a FloorItem, anyhow::Error> { let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; enum ItemOrMeseta { @@ -492,6 +491,7 @@ impl ItemManager { entity_gateway.add_item_note( &individual_floor_item.entity_id, ItemNote::PlayerDrop { + character_id: character.id, map_area: item_drop_location.0, x: item_drop_location.1, y: item_drop_location.2, @@ -505,6 +505,7 @@ impl ItemManager { entity_gateway.add_item_note( entity_id, ItemNote::PlayerDrop { + character_id: character.id, map_area: item_drop_location.0, x: item_drop_location.1, y: item_drop_location.2, @@ -548,14 +549,14 @@ impl ItemManager { Ok(floor_item) } - pub async fn player_drops_partial_stack_on_shared_floor(&mut self, - entity_gateway: &mut EG, - character: &CharacterEntity, - //inventory_item: InventoryItem, - item_id: ClientItemId, - drop_location: ItemDropLocation, - amount: usize) - -> Result<&StackedFloorItem, anyhow::Error> { + pub async fn player_drops_partial_stack_on_shared_floor<'a, EG: EntityGateway>(&'a mut self, + entity_gateway: &'a mut EG, + character: &'a CharacterEntity, + //inventory_item: InventoryItem, + item_id: ClientItemId, + drop_location: ItemDropLocation, + amount: usize) + -> Result<&'a StackedFloorItem, anyhow::Error> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let room_id = self.character_room.get(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let shared_floor = self.room_floor.get_mut(room_id).ok_or(ItemManagerError::NoCharacter(character.id))?; @@ -570,6 +571,7 @@ impl ItemManager { entity_gateway.add_item_note( entity_id, ItemNote::PlayerDrop { + character_id: character.id, map_area: drop_location.map_area, x: drop_location.x, y: 0.0, @@ -622,16 +624,16 @@ impl ItemManager { let _bank_item = bank.deposit_item(item_to_deposit, amount).ok_or(ItemManagerError::Idunnoman)?; entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; - entity_gateway.set_character_bank(&character.id, &bank.as_bank_entity(&character.id, &BankName("".into())), BankName("".into())).await?; + entity_gateway.set_character_bank(&character.id, &bank.as_bank_entity(&character.id, &BankName("".into())), &BankName("".into())).await?; Ok(()) } - pub async fn player_withdraws_item(&mut self, - entity_gateway: &mut EG, - character: &CharacterEntity, - item_id: ClientItemId, - amount: usize) - -> Result<&InventoryItem, anyhow::Error> { + pub async fn player_withdraws_item<'a, EG: EntityGateway>(&'a mut self, + entity_gateway: &'a mut EG, + character: &'a CharacterEntity, + item_id: ClientItemId, + amount: usize) + -> Result<&'a InventoryItem, anyhow::Error> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let bank = self.character_bank @@ -645,7 +647,7 @@ impl ItemManager { }; entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?; - entity_gateway.set_character_bank(&character.id, &bank.as_bank_entity(&character.id, &BankName("".into())), BankName("".into())).await?; + entity_gateway.set_character_bank(&character.id, &bank.as_bank_entity(&character.id, &BankName("".into())), &BankName("".into())).await?; inventory.slot(inventory_item_slot).ok_or_else(|| ItemManagerError::Idunnoman.into()) } @@ -801,13 +803,13 @@ impl ItemManager { Ok(()) } - pub async fn player_buys_item(&mut self, - entity_gateway: &mut EG, - character: &CharacterEntity, - shop_item: &(dyn ShopItem + Send + Sync), - item_id: ClientItemId, - amount: usize) - -> Result<&InventoryItem, anyhow::Error> { + pub async fn player_buys_item<'a, EG: EntityGateway>(&'a mut self, + entity_gateway: &'a mut EG, + character: &'a CharacterEntity, + shop_item: &'a (dyn ShopItem + Send + Sync), + item_id: ClientItemId, + amount: usize) + -> Result<&'a InventoryItem, anyhow::Error> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let item_detail = shop_item.as_item(); diff --git a/src/ship/items/mod.rs b/src/ship/items/mod.rs index 8a917a8..549ee51 100644 --- a/src/ship/items/mod.rs +++ b/src/ship/items/mod.rs @@ -1,18 +1,11 @@ -mod bank; -mod floor; +pub mod state; +pub mod actions; +pub mod apply_item; +pub mod itemstateaction; pub mod inventory; -pub mod manager; -pub mod transaction; -pub mod use_tool; -use serde::{Serialize, Deserialize}; +pub mod floor; +pub mod bank; +pub mod tasks; -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, derive_more::Display)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::Display)] pub struct ClientItemId(pub u32); - -// TODO: remove these and fix use statements in the rest of the codebase -pub use inventory::*; -pub use floor::*; -pub use bank::*; -pub use manager::*; - - diff --git a/src/ship/items/state.rs b/src/ship/items/state.rs new file mode 100644 index 0000000..8ce0d0d --- /dev/null +++ b/src/ship/items/state.rs @@ -0,0 +1,391 @@ +use std::collections::HashMap; +use crate::ship::items::ClientItemId; +use crate::entity::item::{ItemEntityId, ItemDetail, ItemEntity, InventoryItemEntity, BankItemEntity, BankName}; + +use crate::ship::location::{AreaClient, RoomId}; +use crate::entity::character::{CharacterEntity, CharacterEntityId}; +use crate::entity::gateway::{EntityGateway, GatewayError}; +use crate::entity::item::tool::Tool; +use crate::entity::item::weapon::Weapon; +use crate::entity::item::mag::Mag; +use crate::ship::drops::ItemDrop; + +use crate::ship::items::inventory::{Inventory, InventoryItem, InventoryItemDetail, InventoryError, InventoryState}; +use crate::ship::items::floor::{FloorState, FloorItem, LocalFloor, SharedFloor, FloorType}; +use crate::ship::items::bank::{Bank, BankState, BankItem, BankItemDetail, BankError}; + +#[derive(thiserror::Error, Debug)] +pub enum ItemStateError { + #[error("character {0} not found")] + NoCharacter(CharacterEntityId), + #[error("room {0} not found")] + NoRoom(RoomId), + #[error("floor item {0} not found")] + NoFloorItem(ClientItemId), + + #[error("expected {0} to be a tool")] + NotATool(ClientItemId), + + #[error("bank item {0} not found")] + NoBankItem(ClientItemId), + + #[error("inventory error {0}")] + InventoryError(#[from] InventoryError), + + #[error("bank error {0}")] + BankError(#[from] BankError), + + #[error("invalid item id {0}")] + InvalidItemId(ClientItemId), + + #[error("invalid drop? {0:?} (this shouldn't occur)")] + BadItemDrop(ItemDrop), + + #[error("idk")] + Dummy, + + #[error("gateway")] + GatewayError(#[from] GatewayError), + + #[error("tried to remove more meseta than exists: {0}")] + InvalidMesetaRemoval(u32), + + #[error("tried to add meseta when there is no more room")] + FullOfMeseta, + + #[error("stacked item")] + StackedItemError(Vec), + + #[error("apply item {0}")] + ApplyItemError(#[from] crate::ship::items::apply_item::ApplyItemError), + + #[error("item is not a mag {0}")] + NotAMag(ClientItemId), + + #[error("item is not mag food {0}")] + NotMagFood(ClientItemId), + + #[error("item is not sellable")] + ItemNotSellable, + + #[error("could not modify item")] + InvalidModifier, + + #[error("wrong item type ")] + WrongItemType(ClientItemId), +} + + +#[derive(Clone, Debug)] +pub struct IndividualItemDetail { + pub entity_id: ItemEntityId, + pub item: ItemDetail, +} + +impl IndividualItemDetail { + pub fn as_weapon(&self) -> Option<&Weapon> { + match &self.item { + ItemDetail::Weapon(weapon) => Some(weapon), + _ => None + } + } + + pub fn as_mag(&self) -> Option<&Mag> { + match &self.item { + ItemDetail::Mag(mag) => Some(mag), + _ => None + } + } + + pub fn as_mag_mut(&mut self) -> Option<&mut Mag> { + match &mut self.item { + ItemDetail::Mag(mag) => Some(mag), + _ => None + } + } + + pub fn as_client_bytes(&self) -> [u8; 16] { + match &self.item { + ItemDetail::Weapon(w) => w.as_bytes(), + ItemDetail::Armor(a) => a.as_bytes(), + ItemDetail::Shield(s) => s.as_bytes(), + ItemDetail::Unit(u) => u.as_bytes(), + ItemDetail::Tool(t) => t.as_individual_bytes(), + ItemDetail::TechniqueDisk(d) => d.as_bytes(), + ItemDetail::Mag(m) => m.as_bytes(), + ItemDetail::ESWeapon(e) => e.as_bytes(), + } + } + +} + +#[derive(Clone, Debug)] +pub struct StackedItemDetail { + pub entity_ids: Vec, + pub tool: Tool, +} + +impl StackedItemDetail { + pub fn count(&self) -> usize { + self.entity_ids.len() + } +} + + + +#[derive(Clone)] +pub enum AddItemResult { + NewItem, + AddToStack, + Meseta, +} + + +pub struct ItemState { + character_inventory: HashMap, + character_bank: HashMap, + + character_room: HashMap, + character_floor: HashMap, + room_floor: HashMap, + + room_item_id_counter: u32, +} + +impl Default for ItemState { + fn default() -> ItemState { + ItemState { + character_inventory: HashMap::new(), + character_bank: HashMap::new(), + character_room: HashMap::new(), + character_floor: HashMap::new(), + room_floor: HashMap::new(), + room_item_id_counter: 0x00810000, + } + } +} + +impl ItemState { + pub fn get_character_inventory(&self, character: &CharacterEntity) -> Result<&InventoryState, ItemStateError> { + self.character_inventory.get(&character.id) + .ok_or(ItemStateError::NoCharacter(character.id)) + } + + pub fn get_character_bank(&self, character: &CharacterEntity) -> Result<&BankState, ItemStateError> { + self.character_bank.get(&character.id) + .ok_or(ItemStateError::NoCharacter(character.id)) + } +} + +impl ItemState { + fn new_item_id(&mut self) -> Result { + self.room_item_id_counter += 1; + Ok(ClientItemId(self.room_item_id_counter)) + } + + pub async fn load_character(&mut self, entity_gateway: &mut EG, character: &CharacterEntity) -> Result<(), ItemStateError> { + let inventory = entity_gateway.get_character_inventory(&character.id).await?; + let bank = entity_gateway.get_character_bank(&character.id, &BankName("".into())).await?; + let equipped = entity_gateway.get_character_equips(&character.id).await?; + + let inventory_items = inventory.items.into_iter() + .map(|item| -> Result { + Ok(match item { + InventoryItemEntity::Individual(item) => { + InventoryItem { + item_id: ClientItemId(0), + item: InventoryItemDetail::Individual(IndividualItemDetail { + entity_id: item.id, + item: item.item, + }), + } + }, + InventoryItemEntity::Stacked(items) => { + InventoryItem { + item_id: ClientItemId(0), + item: InventoryItemDetail::Stacked(StackedItemDetail { + entity_ids: items.iter().map(|i| i.id).collect(), + tool: items.get(0) + .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? + .item + .clone() + .as_tool() + .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? + }) + } + }, + }) + }) + .collect::, _>>()?; + + let character_meseta = entity_gateway.get_character_meseta(&character.id).await?; + let inventory_state = InventoryState { + character_id: character.id, + item_id_counter: 0, + inventory: Inventory::new(inventory_items), + equipped, + meseta: character_meseta, + }; + + let bank_items = bank.items.into_iter() + .map(|item| -> Result { + Ok(match item { + BankItemEntity::Individual(item) => { + BankItem { + item_id: self.new_item_id()?, + item: BankItemDetail::Individual(IndividualItemDetail { + entity_id: item.id, + item: item.item, + }) + } + }, + BankItemEntity::Stacked(items) => { + BankItem { + item_id: self.new_item_id()?, + item: BankItemDetail::Stacked(StackedItemDetail { + entity_ids: items.iter().map(|i| i.id).collect(), + tool: items.get(0) + .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? + .item + .clone() + .as_tool() + .ok_or_else(|| ItemStateError::StackedItemError(items.clone()))? + }) + } + }, + }) + }) + .collect::, _>>()?; + + let bank_meseta = entity_gateway.get_bank_meseta(&character.id, &BankName("".into())).await?; + let bank_state = BankState::new(character.id, BankName("".into()), Bank::new(bank_items), bank_meseta); + + self.character_inventory.insert(character.id, inventory_state); + self.character_bank.insert(character.id, bank_state); + Ok(()) + } + + pub fn add_character_to_room(&mut self, room_id: RoomId, character: &CharacterEntity, area_client: AreaClient) { + let base_inventory_id = ((area_client.local_client.id() as u32) << 21) | 0x10000; + let inventory = self.character_inventory.get_mut(&character.id).unwrap(); + inventory.initialize_item_ids(base_inventory_id); + let base_bank_id = ((area_client.local_client.id() as u32) << 21) | 0x20000; + let default_bank = self.character_bank.get_mut(&character.id); + if let Some(default_bank ) = default_bank { + default_bank.initialize_item_ids(base_bank_id); + } + self.character_room.insert(character.id, room_id); + self.character_floor.insert(character.id, LocalFloor::default()); + self.room_floor.entry(room_id).or_insert_with(SharedFloor::default); + } + + pub fn remove_character_from_room(&mut self, character: &CharacterEntity) { + self.character_inventory.remove(&character.id); + self.character_floor.remove(&character.id); + if let Some(room) = self.character_room.remove(&character.id).as_ref() { + if self.character_room.iter().any(|(_, r)| r == room) { + self.room_floor.remove(room); + } + } + } + + pub fn get_floor_item(&self, character_id: &CharacterEntityId, item_id: &ClientItemId) -> Result<(&FloorItem, FloorType), ItemStateError> { + let local_floor = self.character_floor.get(character_id).ok_or(ItemStateError::NoCharacter(*character_id))?; + let room = self.character_room.get(character_id).ok_or(ItemStateError::NoCharacter(*character_id))?; + let shared_floor = self.room_floor.get(room).ok_or(ItemStateError::NoCharacter(*character_id))?; + + local_floor.0 + .iter() + .find(|item| item.item_id == *item_id) + .map(|item| (item, FloorType::Local)) + .or_else(|| { + shared_floor.0 + .iter() + .find(|item| item.item_id == *item_id) + .map(|item| (item, FloorType::Shared)) + }) + .ok_or(ItemStateError::NoFloorItem(*item_id)) + } +} + + +#[derive(Default)] +struct ProxiedItemState { + character_inventory: HashMap, + character_bank: HashMap, + + character_room: HashMap, + character_floor: HashMap, + room_floor: HashMap, +} + +pub struct ItemStateProxy<'a> { + item_state: &'a mut ItemState, + proxied_state: ProxiedItemState, +} + +impl<'a> ItemStateProxy<'a> { + pub fn commit(self) { + self.item_state.character_inventory.extend(self.proxied_state.character_inventory.clone()); + self.item_state.character_bank.extend(self.proxied_state.character_bank.clone()); + self.item_state.character_room.extend(self.proxied_state.character_room.clone()); + self.item_state.character_floor.extend(self.proxied_state.character_floor.clone()); + self.item_state.room_floor.extend(self.proxied_state.room_floor.clone()); + } +} + + +fn get_or_clone(master: &HashMap, proxy: &mut HashMap, key: K, err: fn(K) -> ItemStateError) -> Result +where + K: Eq + std::hash::Hash + Copy, + V: Clone +{ + let existing_element = master.get(&key).ok_or_else(|| err(key))?; + Ok(proxy.entry(key) + .or_insert_with(|| existing_element.clone()).clone()) + +} + +impl<'a> ItemStateProxy<'a> { + pub fn new(item_state: &'a mut ItemState) -> Self { + ItemStateProxy { + item_state, + proxied_state: Default::default(), + } + } + + pub fn inventory(&mut self, character_id: &CharacterEntityId) -> Result { + get_or_clone(&self.item_state.character_inventory, &mut self.proxied_state.character_inventory, *character_id, ItemStateError::NoCharacter) + } + + pub fn set_inventory(&mut self, inventory: InventoryState) { + self.proxied_state.character_inventory.insert(inventory.character_id, inventory); + } + + pub fn bank(&mut self, character_id: &CharacterEntityId) -> Result { + get_or_clone(&self.item_state.character_bank, &mut self.proxied_state.character_bank, *character_id, ItemStateError::NoCharacter) + } + + pub fn set_bank(&mut self, bank: BankState) { + self.proxied_state.character_bank.insert(bank.character_id, bank); + } + + pub fn floor(&mut self, character_id: &CharacterEntityId) -> Result { + let room_id = get_or_clone(&self.item_state.character_room, &mut self.proxied_state.character_room, *character_id, ItemStateError::NoCharacter)?; + Ok(FloorState { + character_id: *character_id, + local: get_or_clone(&self.item_state.character_floor, &mut self.proxied_state.character_floor, *character_id, ItemStateError::NoCharacter)?, + shared: get_or_clone(&self.item_state.room_floor, &mut self.proxied_state.room_floor, room_id, ItemStateError::NoRoom)?, + }) + } + + pub fn set_floor(&mut self, floor: FloorState) { + let room_id = get_or_clone(&self.item_state.character_room, &mut self.proxied_state.character_room, floor.character_id, ItemStateError::NoCharacter).unwrap(); + self.proxied_state.character_floor.insert(floor.character_id, floor.local); + self.proxied_state.room_floor.insert(room_id, floor.shared); + } + + pub fn new_item_id(&mut self) -> Result { + self.item_state.new_item_id() + } +} diff --git a/src/ship/items/tasks.rs b/src/ship/items/tasks.rs new file mode 100644 index 0000000..4264420 --- /dev/null +++ b/src/ship/items/tasks.rs @@ -0,0 +1,500 @@ +use crate::ship::items::ClientItemId; +use crate::entity::item::Meseta; + +use crate::ship::map::MapArea; +use crate::entity::character::{CharacterEntity, CharacterEntityId}; +use crate::entity::gateway::EntityGateway; +use crate::ship::items::state::{ItemState, ItemStateProxy, ItemStateError, IndividualItemDetail}; +use crate::ship::items::itemstateaction::{ItemStateAction, ItemAction}; +use crate::ship::items::inventory::InventoryItem; +use crate::ship::items::floor::FloorItem; +use crate::entity::item::ItemModifier; +use crate::ship::shops::ShopItem; +use crate::ship::trade::TradeItem; +use crate::ship::location::AreaClient; +use crate::ship::drops::ItemDrop; + +use crate::ship::items::actions; + +pub async fn pick_up_item( + item_state: &mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: &ClientItemId) + -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::take_item_from_floor(character.id, *item_id)) + .act(actions::add_floor_item_to_inventory(character)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + +pub async fn drop_item( + item_state: &mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: &ClientItemId, + map_area: MapArea, + drop_position: (f32, f32, f32)) + -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::take_item_from_inventory(character.id, *item_id, 0)) + .act(actions::add_inventory_item_to_shared_floor(character.id, map_area, drop_position)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + +pub async fn drop_partial_item<'a, EG>( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: &ClientItemId, + map_area: MapArea, + drop_position: (f32, f32), + amount: u32) + -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::take_item_from_inventory(character.id, *item_id, amount)) + .act(actions::add_inventory_item_to_shared_floor(character.id, map_area, (drop_position.0, 0.0, drop_position.1))) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + + + +pub async fn drop_meseta<'a, EG>( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + map_area: MapArea, + drop_position: (f32, f32), + amount: u32) + -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::take_meseta_from_inventory(character.id, amount)) + .act(actions::add_meseta_to_shared_floor(character.id, amount, map_area, drop_position)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + + +pub async fn withdraw_meseta<'a, EG>( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + amount: u32) + -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::take_meseta_from_bank(character.id, amount)) + .act(actions::add_meseta_from_bank_to_inventory(character.id, amount)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + + +pub async fn deposit_meseta<'a, EG>( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + amount: u32) + -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), _) = ItemStateAction::default() + .act(actions::take_meseta_from_inventory(character.id, amount)) + .act(actions::add_meseta_to_bank(character.id, amount)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, ())) + }).await +} + + +pub async fn withdraw_item<'a, EG>( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: &ClientItemId, + amount: u32) + -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::take_item_from_bank(character.id, *item_id, amount)) + //.act(bank_item_to_inventory_item) + //.act(add_item_to_inventory) + .act(actions::add_bank_item_to_inventory(character)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + + +pub async fn deposit_item<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: &ClientItemId, + amount: u32) + -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::take_item_from_inventory(character.id, *item_id, amount)) + .act(actions::add_inventory_item_to_bank(character.id)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + +pub async fn equip_item<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: &ClientItemId, + equip_slot: u8, +) -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::equip_inventory_item(character.id, *item_id, equip_slot)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + + +pub async fn unequip_item<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: &ClientItemId, +) -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::unequip_inventory_item(character.id, *item_id)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + + +pub async fn sort_inventory<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_ids: Vec, +) -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::sort_inventory_items(character.id, item_ids)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + + +pub async fn use_item<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &mut CharacterEntity, + item_id: &ClientItemId, + amount: u32, +) -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), new_character) = ItemStateAction::default() + .act(actions::take_item_from_inventory(character.id, *item_id, amount)) + .act(actions::use_consumed_item(character.clone())) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + *character = new_character; + Ok((transaction, ())) + }).await +} + + +pub async fn feed_mag<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + mag_item_id: &ClientItemId, + tool_item_id: &ClientItemId, +) -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), _) = ItemStateAction::default() + .act(actions::take_item_from_inventory(character.id, *tool_item_id, 1)) + .act(actions::feed_mag_item(character.clone(), *mag_item_id)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, ())) + }).await +} + + +pub async fn buy_shop_item<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + shop_item: &'a (dyn ShopItem + Send + Sync), + item_id: ClientItemId, + amount: u32, +) -> Result +where + EG: EntityGateway, +{ + let item_price = shop_item.price() as u32 * amount; + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::take_meseta_from_inventory(character.id, item_price)) + //.act(bought_item_to_inventory_item) + //.act(add_item_to_inventory) + .act(actions::add_bought_item_to_inventory(character.id, shop_item, item_id, amount)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} + + +pub async fn sell_item<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: ClientItemId, + amount: u32, +) -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(actions::take_item_from_inventory(character.id, item_id, amount)) + .act(actions::sell_inventory_item(character.id)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} +pub async fn trade_items<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + p1: (&AreaClient, &CharacterEntity, &Vec, Meseta), + p2: (&AreaClient, &CharacterEntity, &Vec, Meseta)) + -> Result<(Vec, Vec), ItemStateError> +where + EG: EntityGateway, +{ + let p1_trade_items = p1.2 + .iter() + .map(|item| { + match item { + TradeItem::Individual(item_id) => (*item_id, 1), + TradeItem::Stacked(item_id, amount) => (*item_id, *amount as u32), + } + }) + .collect(); + let p2_trade_items = p2.2 + .iter() + .map(|item| { + match item { + TradeItem::Individual(item_id) => (*item_id, 1), + TradeItem::Stacked(item_id, amount) => (*item_id, *amount as u32), + } + }) + .collect(); + entity_gateway.with_transaction(|mut transaction| async move { + let p1_id = p1.1.id; + let p2_id = p2.1.id; + let trade = transaction.gateway().create_trade(&p1_id, &p2_id).await?; + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), p1_removed_items) = ItemStateAction::default() + .act(actions::iterate(p1_trade_items, move |p1_trade_item| actions::take_item_from_inventory(p1_id, p1_trade_item.0, p1_trade_item.1) )) + .act(actions::foreach(actions::assign_new_item_id())) + .commit((item_state_proxy, transaction)) + .await?; + let ((item_state_proxy, transaction), p2_removed_items) = ItemStateAction::default() + .act(actions::iterate(p2_trade_items, move |p2_trade_item| actions::take_item_from_inventory(p2_id, p2_trade_item.0, p2_trade_item.1) )) + .act(actions::foreach(actions::assign_new_item_id())) + .commit((item_state_proxy, transaction)) + .await?; + + let ((item_state_proxy, transaction), p2_new_items) = ItemStateAction::default() + .act(actions::insert(p1_removed_items)) + .act(actions::foreach(actions::add_item_to_inventory(p2.1.clone()))) + .act(actions::record_trade(trade.id, p1_id, p2_id)) + .commit((item_state_proxy, transaction)) + .await?; + let ((item_state_proxy, transaction), p1_new_items) = ItemStateAction::default() + .act(actions::insert(p2_removed_items)) + .act(actions::foreach(actions::add_item_to_inventory(p1.1.clone()))) + .act(actions::record_trade(trade.id, p2_id, p1_id)) + .commit((item_state_proxy, transaction)) + .await?; + + let ((item_state_proxy, transaction), _) = ItemStateAction::default() + .act(actions::take_meseta_from_inventory(p1_id, p1.3.0)) + .act(actions::take_meseta_from_inventory(p2_id, p2.3.0)) + .act(actions::add_meseta_to_inventory(p1_id, p2.3.0)) + .act(actions::add_meseta_to_inventory(p2_id, p1.3.0)) + .commit((item_state_proxy, transaction)) + .await?; + + item_state_proxy.commit(); + Ok((transaction, (p1_new_items, p2_new_items))) + }).await +} + + +pub async fn take_meseta<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character_id: &CharacterEntityId, + meseta: Meseta) + -> Result<(), ItemStateError> +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), _) = ItemStateAction::default() + .act(actions::take_meseta_from_inventory(*character_id, meseta.0)) + .commit((item_state_proxy, transaction)) + .await?; + + item_state_proxy.commit(); + Ok((transaction, ())) + }).await +} + +pub async fn enemy_drops_item<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character_id: CharacterEntityId, + item_drop: ItemDrop) + -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), floor_item) = ItemStateAction::default() + .act(actions::convert_item_drop_to_floor_item(character_id, item_drop)) + .act(actions::add_item_to_local_floor(character_id)) + .commit((item_state_proxy, transaction)) + .await?; + + item_state_proxy.commit(); + Ok((transaction, floor_item)) + }).await +} + + +pub async fn apply_modifier<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: ClientItemId, + modifier: ItemModifier) + -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), item) = ItemStateAction::default() + .act(actions::take_item_from_inventory(character.id, item_id, 1)) + .act(actions::apply_modifier_to_inventory_item(modifier)) + .act(actions::add_item_to_inventory(character.clone())) + .act(actions::as_individual_item()) + .commit((item_state_proxy, transaction)) + .await?; + + item_state_proxy.commit(); + Ok((transaction, item)) + }).await +} diff --git a/src/ship/items/transaction.rs b/src/ship/items/transaction.rs deleted file mode 100644 index bc0d0a9..0000000 --- a/src/ship/items/transaction.rs +++ /dev/null @@ -1,337 +0,0 @@ -use crate::entity::gateway::EntityGateway; -use thiserror::Error; -use crate::ship::items::manager::{ItemManager, ItemManagerError}; -use crate::entity::gateway::GatewayError; - -#[derive(Error, Debug)] -pub enum TransactionCommitError { - #[error("transaction commit gateway error {0}")] - Gateway(#[from] GatewayError), - #[error("transaction commit itemmanager error {0}")] - ItemManager(#[from] ItemManagerError), -} - -#[async_trait::async_trait] -pub trait ItemAction: std::marker::Send + std::marker::Sync + std::fmt::Debug { - async fn commit(&self, manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError>; -} - -pub struct ItemTransactionActions<'a, EG: EntityGateway> { - action_queue: Vec>>, - pub manager: &'a ItemManager, -} - - -impl<'a, EG: EntityGateway> ItemTransactionActions<'a, EG> { - fn new(manager: &'a ItemManager) -> ItemTransactionActions<'a, EG> { - ItemTransactionActions { - action_queue: Vec::new(), - manager - } - } - - pub fn action(&mut self, action: Box>) { - self.action_queue.push(action) - } -} - - -pub struct ItemTransaction<'a, T, EG: EntityGateway> { - data: T, - actions: ItemTransactionActions<'a, EG>, -} - -impl<'a, T, EG: EntityGateway> ItemTransaction<'a, T, EG> { - pub fn new(manager: &'a ItemManager, arg: T) -> ItemTransaction<'a, T, EG> { - ItemTransaction { - data: arg, - actions: ItemTransactionActions::new(manager), - } - } - - pub fn act(mut self, action: fn(&mut ItemTransactionActions, &T) -> Result) -> FinalizedItemTransaction { - match action(&mut self.actions, &self.data) { - Ok(k) => { - FinalizedItemTransaction { - value: Ok(k), - action_queue: self.actions.action_queue, - } - }, - Err(err) => { - FinalizedItemTransaction { - value: Err(err), - action_queue: Vec::new(), - } - } - } - } -} - - -#[derive(Error, Debug)] -pub enum TransactionError { - #[error("transaction action error {0:?}")] - Action(E), - #[error("transaction commit error {0}")] - Commit(#[from] TransactionCommitError), - -} - -// this only exists to drop the ItemManager borrow of ItemTransaction so a mutable ItemTransaction can be passed in later -pub struct FinalizedItemTransaction { - value: Result, - action_queue: Vec>>, -} - -impl FinalizedItemTransaction { - pub async fn commit(self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result> { - match self.value { - Ok(value) => { - for action in self.action_queue.into_iter() { - // TODO: better handle rolling back if this ever errors out - action.commit(item_manager, entity_gateway).await.map_err(|err| TransactionError::Commit(err))?; - } - Ok(value) - }, - Err(err) => Err(TransactionError::Action(err)), - } - } -} - - -#[cfg(test)] -mod test { - use super::*; - use crate::entity::account::{UserAccountId, NewUserAccountEntity, UserAccountEntity}; - use crate::entity::character::{NewCharacterEntity, CharacterEntity}; - use crate::entity::gateway::GatewayError; - use thiserror::Error; - - #[async_std::test] - async fn test_item_transaction() { - #[derive(Debug)] - struct DummyAction1 { - name: String, - } - #[derive(Debug)] - struct DummyAction2 { - value: u32, - } - - #[derive(Error, Debug)] - #[error("")] - enum DummyError { - Error - } - - #[derive(Default, Clone)] - struct DummyGateway { - d1_set: String, - d2_inc: u32, - } - - #[async_trait::async_trait] - impl EntityGateway for DummyGateway { - async fn create_user(&mut self, user: NewUserAccountEntity) -> Result { - self.d1_set = user.username; - Ok(UserAccountEntity::default()) - } - - async fn create_character(&mut self, char: NewCharacterEntity) -> Result { - self.d2_inc += char.slot; - Ok(CharacterEntity::default()) - } - } - - - #[async_trait::async_trait] - impl ItemAction for DummyAction1 { - async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { - item_manager.id_counter = 55555; - entity_gateway.create_user(NewUserAccountEntity { - username: self.name.clone(), - ..NewUserAccountEntity::default() - }) - .await?; - Ok(()) - } - } - - #[async_trait::async_trait] - impl ItemAction for DummyAction2 { - async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { - item_manager.id_counter += self.value; - entity_gateway.create_character(NewCharacterEntity { - slot: self.value, - ..NewCharacterEntity::new(UserAccountId(0), 1) // TODO: handle different keyboard_config_presets - }) - .await?; - Ok(()) - } - } - - let mut item_manager = ItemManager::default(); - let mut entity_gateway = DummyGateway::default(); - - let result = ItemTransaction::new(&item_manager, 12) - .act(|it, k| { - it.action(Box::new(DummyAction1 {name: "asdf".into()})); - it.action(Box::new(DummyAction2 {value: 11})); - it.action(Box::new(DummyAction2 {value: *k})); - if *k == 99 { - return Err(DummyError::Error) - } - Ok(String::from("hello")) - }) - .commit(&mut item_manager, &mut entity_gateway) - .await; - - assert!(entity_gateway.d1_set == "asdf"); - assert!(entity_gateway.d2_inc == 23); - assert!(item_manager.id_counter == 55578); - assert!(result.unwrap() == "hello"); - } - - #[async_std::test] - async fn test_item_transaction_with_action_error() { - #[derive(Debug)] - struct DummyAction1 { - } - #[derive(Debug)] - struct DummyAction2 { - } - - #[derive(Error, Debug, PartialEq, Eq)] - #[error("")] - enum DummyError { - Error - } - - #[derive(Default, Clone)] - struct DummyGateway { - _d1_set: String, - d2_inc: u32, - } - - #[async_trait::async_trait] - impl EntityGateway for DummyGateway { - async fn create_character(&mut self, char: NewCharacterEntity) -> Result { - self.d2_inc += char.slot; - Ok(CharacterEntity::default()) - } - } - - - #[async_trait::async_trait] - impl ItemAction for DummyAction1 { - async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { - entity_gateway.create_character(NewCharacterEntity { - slot: 1, - ..NewCharacterEntity::new(UserAccountId(0), 1) // TODO: handle different keyboard_config_presets - }) - .await?; - Ok(()) - } - } - - #[async_trait::async_trait] - impl ItemAction for DummyAction2 { - async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { - entity_gateway.create_character(NewCharacterEntity { - slot: 1, - ..NewCharacterEntity::new(UserAccountId(0), 1) // TODO: handle different keyboard_config_presets - }) - .await?; - Ok(()) - } - } - - let mut item_manager = ItemManager::default(); - let mut entity_gateway = DummyGateway::default(); - - let result = ItemTransaction::new(&item_manager, 12) - .act(|it, _| -> Result<(), _> { - it.action(Box::new(DummyAction1 {})); - it.action(Box::new(DummyAction2 {})); - it.action(Box::new(DummyAction2 {})); - Err(DummyError::Error) - }) - .commit(&mut item_manager, &mut entity_gateway) - .await; - - assert!(entity_gateway.d2_inc == 0); - assert!(matches!(result, Err(TransactionError::Action(DummyError::Error)))); - } - - #[async_std::test] - async fn test_item_transaction_with_commit_error() { - #[derive(Debug)] - struct DummyAction1 { - } - #[derive(Debug)] - struct DummyAction2 { - } - - #[derive(Error, Debug, PartialEq, Eq)] - #[error("")] - enum DummyError { - } - - #[derive(Default, Clone)] - struct DummyGateway { - _d1_set: String, - d2_inc: u32, - } - - #[async_trait::async_trait] - impl EntityGateway for DummyGateway { - async fn create_character(&mut self, char: NewCharacterEntity) -> Result { - self.d2_inc += char.slot; - Ok(CharacterEntity::default()) - } - } - - - #[async_trait::async_trait] - impl ItemAction for DummyAction1 { - async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { - entity_gateway.create_character(NewCharacterEntity { - slot: 1, - ..NewCharacterEntity::new(UserAccountId(0), 1) // TODO: handle different keyboard_config_presets - }) - .await?; - Err(GatewayError::Error.into()) - } - } - - #[async_trait::async_trait] - impl ItemAction for DummyAction2 { - async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { - entity_gateway.create_character(NewCharacterEntity { - slot: 1, - ..NewCharacterEntity::new(UserAccountId(0), 1) // TODO: handle different keyboard_config_presets - }) - .await?; - Ok(()) - } - } - - let mut item_manager = ItemManager::default(); - let mut entity_gateway = DummyGateway::default(); - - let result = ItemTransaction::new(&item_manager, 12) - .act(|it, _| -> Result<_, DummyError> { - it.action(Box::new(DummyAction1 {})); - it.action(Box::new(DummyAction2 {})); - it.action(Box::new(DummyAction2 {})); - Ok(()) - }) - .commit(&mut item_manager, &mut entity_gateway) - .await; - - // in an ideal world this would be 0 as rollbacks would occur - assert!(entity_gateway.d2_inc == 1); - assert!(matches!(result, Err(TransactionError::Commit(TransactionCommitError::Gateway(GatewayError::Error))))); - } -} - diff --git a/src/ship/items/use_tool.rs b/src/ship/items/use_tool.rs deleted file mode 100644 index 6440b84..0000000 --- a/src/ship/items/use_tool.rs +++ /dev/null @@ -1,163 +0,0 @@ -use thiserror::Error; -use crate::entity::gateway::EntityGateway; -use crate::entity::character::CharacterEntity; -use crate::entity::item::mag::MagCell; -use crate::ship::items::{CharacterInventory, ConsumedItem}; - -#[derive(Error, Debug)] -#[error("")] -pub enum UseItemError { - NoCharacter, - ItemNotEquipped, - InvalidItem, -} - -pub async fn power_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { - character.materials.power += 1; - entity_gateway.save_character(character).await.unwrap(); -} - -pub async fn mind_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { - character.materials.mind += 1; - entity_gateway.save_character(character).await.unwrap(); -} - -pub async fn evade_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { - character.materials.evade += 1; - entity_gateway.save_character(character).await.unwrap(); -} - -pub async fn def_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { - character.materials.def += 1; - entity_gateway.save_character(character).await.unwrap(); -} - -pub async fn luck_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { - character.materials.luck += 1; - entity_gateway.save_character(character).await.unwrap(); -} - -pub async fn hp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { - character.materials.hp += 1; - entity_gateway.save_character(character).await.unwrap(); -} - -pub async fn tp_material(entity_gateway: &mut EG, character: &mut CharacterEntity) { - character.materials.tp += 1; - entity_gateway.save_character(character).await.unwrap(); -} - -async fn mag_cell(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory, mag_cell_type: MagCell) -> Result<(), UseItemError> { - let mut mag_handle = inventory.get_equipped_mag_handle().ok_or(UseItemError::ItemNotEquipped)?; - let mag_item = mag_handle.item_mut() - .ok_or(UseItemError::InvalidItem)?; - let actual_mag = mag_item - .individual_mut() - .ok_or(UseItemError::InvalidItem)? - .mag_mut() - .ok_or(UseItemError::InvalidItem)?; - actual_mag.apply_mag_cell(mag_cell_type); - for mag_entity_id in mag_item.entity_ids() { - for cell_entity_id in used_cell.entity_ids() { - entity_gateway.use_mag_cell(&mag_entity_id, &cell_entity_id).await.unwrap(); - } - } - - Ok(()) -} - -pub async fn cell_of_mag_502(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::CellOfMag502).await -} - -pub async fn cell_of_mag_213(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::CellOfMag213).await -} - -pub async fn parts_of_robochao(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::PartsOfRobochao).await -} - -pub async fn heart_of_opaopa(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfOpaOpa).await -} - -pub async fn heart_of_pian(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfPian).await -} - -pub async fn heart_of_chao(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfChao).await -} - -pub async fn heart_of_angel(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfAngel).await -} - -pub async fn kit_of_hamburger(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfHamburger).await -} - -pub async fn panthers_spirit(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::PanthersSpirit).await -} - -pub async fn kit_of_mark3(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfMark3).await -} - -pub async fn kit_of_master_system(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfMasterSystem).await -} - -pub async fn kit_of_genesis(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfGenesis).await -} - -pub async fn kit_of_sega_saturn(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfSegaSaturn).await -} - -pub async fn kit_of_dreamcast(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::KitOfDreamcast).await -} - -pub async fn tablet(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::Tablet).await -} - -pub async fn dragon_scale(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::DragonScale).await -} - -pub async fn heaven_striker_coat(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::HeavenStrikerCoat).await -} - -pub async fn pioneer_parts(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::PioneerParts).await -} - -pub async fn amities_memo(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::AmitiesMemo).await -} - -pub async fn heart_of_morolian(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::HeartOfMorolian).await -} - -pub async fn rappys_beak(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::RappysBeak).await -} - -pub async fn yahoos_engine(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::YahoosEngine).await -} - -pub async fn d_photon_core(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::DPhotonCore).await -} - -pub async fn liberta_kit(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> { - mag_cell(entity_gateway, used_cell, inventory, MagCell::LibertaKit).await -} diff --git a/src/ship/location.rs b/src/ship/location.rs index efba55f..f4a1433 100644 --- a/src/ship/location.rs +++ b/src/ship/location.rs @@ -12,7 +12,7 @@ pub enum AreaType { } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct LobbyId(pub usize); #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, derive_more::Display)] @@ -25,7 +25,7 @@ impl LobbyId { } -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] #[error("create room")] pub enum CreateRoomError { NoOpenSlots, @@ -33,7 +33,7 @@ pub enum CreateRoomError { JoinError, } -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] #[error("join room")] pub enum JoinRoomError { RoomDoesNotExist, @@ -41,7 +41,7 @@ pub enum JoinRoomError { ClientInAreaAlready, } -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] #[error("join lobby")] pub enum JoinLobbyError { LobbyDoesNotExist, @@ -49,7 +49,7 @@ pub enum JoinLobbyError { ClientInAreaAlready, } -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] #[error("get area")] pub enum GetAreaError { NotInRoom, @@ -57,28 +57,28 @@ pub enum GetAreaError { InvalidClient, } -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] #[error("client removal")] pub enum ClientRemovalError { ClientNotInArea, InvalidArea, } -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] #[error("get clients")] pub enum GetClientsError { InvalidClient, InvalidArea, } -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] #[error("get neighbor")] pub enum GetNeighborError { InvalidClient, InvalidArea, } -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] #[error("get leader")] pub enum GetLeaderError { InvalidClient, @@ -86,7 +86,7 @@ pub enum GetLeaderError { NoClientInArea, } -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] #[error("clientlocation")] pub enum ClientLocationError { CreateRoomError(#[from] CreateRoomError), @@ -100,7 +100,7 @@ pub enum ClientLocationError { } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct LocalClientId(usize); impl LocalClientId { @@ -115,19 +115,19 @@ impl PartialEq for LocalClientId { } } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct AreaClient { pub client: ClientId, pub local_client: LocalClientId, time_join: SystemTime, } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] struct Lobby([Option; 12]); -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] struct Room([Option; 4]); -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum RoomLobby { Room(RoomId), Lobby(LobbyId), diff --git a/src/ship/map/area.rs b/src/ship/map/area.rs index 33b51aa..7989cba 100644 --- a/src/ship/map/area.rs +++ b/src/ship/map/area.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use thiserror::Error; use crate::ship::room::Episode; -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum MapArea { Pioneer2Ep1, Forest1, diff --git a/src/ship/map/variant.rs b/src/ship/map/variant.rs index 819bcdb..0fa7c65 100644 --- a/src/ship/map/variant.rs +++ b/src/ship/map/variant.rs @@ -5,7 +5,7 @@ use rand::Rng; // TODO: don't use * use crate::ship::map::*; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum MapVariantMode { Online, Offline, diff --git a/src/ship/packet/builder/lobby.rs b/src/ship/packet/builder/lobby.rs index 56213dd..13c9dd5 100644 --- a/src/ship/packet/builder/lobby.rs +++ b/src/ship/packet/builder/lobby.rs @@ -4,21 +4,21 @@ use crate::common::leveltable::CharacterLevelTable; use crate::ship::ship::{ShipError, Clients}; use crate::ship::location::{ClientLocation, LobbyId, ClientLocationError}; use crate::ship::packet::builder::{player_info}; -use crate::ship::items::ItemManager; +use crate::ship::items::state::ItemState; pub fn join_lobby(id: ClientId, lobby: LobbyId, client_location: &ClientLocation, clients: &Clients, - item_manager: &ItemManager, + item_state: &ItemState, level_table: &CharacterLevelTable) -> Result { let lobby_clients = client_location.get_clients_in_lobby(lobby).map_err(|err| -> ClientLocationError { err.into() })?; let playerinfo = lobby_clients.iter() .map(|area_client| { let client = clients.get(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client)).unwrap(); - player_info(0x100, client, area_client, item_manager, level_table) + player_info(0x100, client, area_client, item_state, level_table) }); let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id)).unwrap(); @@ -40,7 +40,7 @@ pub fn add_to_lobby(id: ClientId, lobby: LobbyId, client_location: &ClientLocation, clients: &Clients, - item_manager: &ItemManager, + item_state: &ItemState, level_table: &CharacterLevelTable) -> Result { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id)).unwrap(); @@ -55,7 +55,7 @@ pub fn add_to_lobby(id: ClientId, block: client.block as u16, event: 0, padding: 0, - playerinfo: player_info(0x100, client, &area_client, item_manager, level_table), + playerinfo: player_info(0x100, client, &area_client, item_state, level_table), }) } diff --git a/src/ship/packet/builder/message.rs b/src/ship/packet/builder/message.rs index 186e637..ac60484 100644 --- a/src/ship/packet/builder/message.rs +++ b/src/ship/packet/builder/message.rs @@ -3,7 +3,11 @@ use libpso::packet::ship::*; use crate::entity::item; use crate::common::leveltable::CharacterStats; use crate::ship::ship::{ShipError}; -use crate::ship::items::{ClientItemId, InventoryItem, StackedFloorItem, FloorItem, CharacterBank}; +use crate::ship::items::ClientItemId; +use crate::ship::items::inventory::InventoryItem; +use crate::ship::items::state::IndividualItemDetail; +use crate::ship::items::bank::BankState; +use crate::ship::items::floor::FloorItem; use crate::ship::location::AreaClient; use std::convert::TryInto; use crate::ship::shops::ShopItem; @@ -14,21 +18,21 @@ pub fn item_drop(client: u8, target: u8, item_drop: &FloorItem) -> Result Result { +pub fn create_individual_item(area_client: AreaClient, item_id: ClientItemId, item: &IndividualItemDetail) -> Result { let bytes = item.as_client_bytes(); Ok(CreateItem { client: area_client.local_client.id(), @@ -66,12 +70,24 @@ pub fn create_meseta(area_client: AreaClient, amount: usize) -> CreateItem { } pub fn create_withdrawn_inventory_item(area_client: AreaClient, item: &InventoryItem) -> Result { - let bytes = item.as_client_bytes(); + let bytes = item.item.as_client_bytes(); + Ok(CreateItem { + client: area_client.local_client.id(), + target: 0, + item_data: bytes[0..12].try_into()?, + item_id: item.item_id.0, + item_data2: bytes[12..16].try_into()?, + unknown: 0, + }) +} + +pub fn create_withdrawn_inventory_item2(area_client: AreaClient, item: &InventoryItem) -> Result { + let bytes = item.item.as_client_bytes(); Ok(CreateItem { client: area_client.local_client.id(), target: 0, item_data: bytes[0..12].try_into()?, - item_id: item.item_id().0, + item_id: item.item_id.0, item_data2: bytes[12..16].try_into()?, unknown: 0, }) @@ -83,13 +99,13 @@ pub fn remove_item_from_floor(area_client: AreaClient, item: &FloorItem) -> Resu target: 0, client_id: area_client.local_client.id(), unknown: 0, - map_area: item.map_area().area_value(), + map_area: item.map_area.area_value(), unknown2: 0, - item_id: item.item_id().0, + item_id: item.item_id.0, }) } -pub fn drop_split_stack(area_client: AreaClient, item: &StackedFloorItem) -> Result { +pub fn drop_split_stack(area_client: AreaClient, item: &FloorItem) -> Result { let item_bytes = item.as_client_bytes(); Ok(DropSplitStack { client: area_client.local_client.id(), @@ -113,11 +129,11 @@ pub fn drop_split_meseta_stack(area_client: AreaClient, item: &FloorItem) -> Res target: 0, variety: 0, unknown1: 0, - map_area: item.map_area().area_value(), - x: item.x(), - z: item.z(), + map_area: item.map_area.area_value(), + x: item.x, + z: item.z, item_bytes: item_bytes[0..12].try_into()?, - item_id: item.item_id().0, + item_id: item.item_id.0, item_bytes2: item_bytes[12..16].try_into()?, unknown2: 0, }) @@ -146,7 +162,7 @@ pub fn character_leveled_up(area_client: AreaClient, level: u32, before_stats: C } // TOOD: checksum? -pub fn bank_item_list(bank: &CharacterBank, char_bank_meseta: u32) -> BankItemList { +pub fn bank_item_list(bank: &BankState) -> BankItemList { BankItemList { aflag: 0, cmd: 0xBC, @@ -154,7 +170,7 @@ pub fn bank_item_list(bank: &CharacterBank, char_bank_meseta: u32) -> BankItemLi size: bank.count() as u32 * 0x18 + 0x14, checksum: 0x123434, item_count: bank.count() as u32, - meseta: char_bank_meseta, + meseta: bank.meseta.0, items: bank.as_client_bank_request() } } diff --git a/src/ship/packet/builder/mod.rs b/src/ship/packet/builder/mod.rs index f5d9f3c..80d9506 100644 --- a/src/ship/packet/builder/mod.rs +++ b/src/ship/packet/builder/mod.rs @@ -10,7 +10,7 @@ use crate::common::leveltable::CharacterLevelTable; use crate::ship::character::CharacterBytesBuilder; use crate::ship::ship::ClientState; use crate::ship::location::AreaClient; -use crate::ship::items::ItemManager; +use crate::ship::items::state::ItemState; pub fn player_header(tag: u32, client: &ClientState, area_client: &AreaClient) -> PlayerHeader { PlayerHeader { @@ -23,15 +23,14 @@ pub fn player_header(tag: u32, client: &ClientState, area_client: &AreaClient) - } } -pub fn player_info(tag: u32, client: &ClientState, area_client: &AreaClient, item_manager: &ItemManager, level_table: &CharacterLevelTable) -> PlayerInfo { +pub fn player_info(tag: u32, client: &ClientState, area_client: &AreaClient, item_state: &ItemState, level_table: &CharacterLevelTable) -> PlayerInfo { let (level, stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp); - let inventory = item_manager.get_character_inventory(&client.character).unwrap(); - let meseta = item_manager.get_character_meseta(&client.character.id).unwrap(); + let inventory = item_state.get_character_inventory(&client.character).unwrap(); let character = CharacterBytesBuilder::default() .character(&client.character) .stats(&stats) .level(level - 1) - .meseta(*meseta) + .meseta(inventory.meseta) .build(); PlayerInfo { header: player_header(tag, client, area_client), diff --git a/src/ship/packet/builder/room.rs b/src/ship/packet/builder/room.rs index c5bd11c..29dc0f2 100644 --- a/src/ship/packet/builder/room.rs +++ b/src/ship/packet/builder/room.rs @@ -4,7 +4,7 @@ use crate::common::leveltable::CharacterLevelTable; use crate::ship::ship::{ShipError, ClientState, Clients}; use crate::ship::location::{ClientLocation, RoomId, AreaClient, ClientLocationError}; use crate::ship::room::RoomState; -use crate::ship::items::ItemManager; +use crate::ship::items::state::ItemState; use crate::ship::packet::builder::{player_header, player_info}; use std::convert::TryInto; @@ -53,7 +53,7 @@ pub fn add_to_room(_id: ClientId, client: &ClientState, area_client: &AreaClient, leader: &AreaClient, - item_manager: &ItemManager, + item_state: &ItemState, level_table: &CharacterLevelTable, _room_id: RoomId, ) @@ -68,7 +68,7 @@ pub fn add_to_room(_id: ClientId, block: 0, event: 0, padding: 0, - playerinfo: player_info(0x10000, client, area_client, item_manager, level_table), + playerinfo: player_info(0x10000, client, area_client, item_state, level_table), }) } @@ -76,4 +76,4 @@ pub fn build_rare_monster_list(rare_monster_vec: Vec) -> RareMonsterList { RareMonsterList { ids: rare_monster_vec.try_into().unwrap_or([0xFFFFu16; 16]), } -} \ No newline at end of file +} diff --git a/src/ship/packet/handler/auth.rs b/src/ship/packet/handler/auth.rs index c9e6987..50be751 100644 --- a/src/ship/packet/handler/auth.rs +++ b/src/ship/packet/handler/auth.rs @@ -4,7 +4,7 @@ use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, ClientState, Clients}; use crate::login::login::get_login_status; use crate::entity::gateway::EntityGateway; -use crate::ship::items::ItemManager; +use crate::ship::items::state::ItemState; use crate::common::interserver::ShipMessage; #[allow(clippy::too_many_arguments)] @@ -12,7 +12,7 @@ pub async fn validate_login(id: ClientId, pkt: &Login, entity_gateway: &mut EG, clients: &mut Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, shipgate_sender: &Option>, ship_name: &str, num_blocks: usize) @@ -30,7 +30,7 @@ pub async fn validate_login(id: ClientId, .clone(); let settings = entity_gateway.get_user_settings_by_user(&user).await?; - item_manager.load_character(entity_gateway, &character).await?; + item_state.load_character(entity_gateway, &character).await?; if let Some(shipgate_sender) = shipgate_sender.as_ref() { shipgate_sender(ShipMessage::AddUser(user.id)); diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index a417c6f..4861b7b 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -8,12 +8,16 @@ use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms, ItemShops}; use crate::ship::location::{ClientLocation, ClientLocationError}; use crate::ship::drops::ItemDrop; -use crate::ship::items::{ItemManager, ItemManagerError, ClientItemId, TriggerCreateItem, FloorItem, FloorType}; +use crate::ship::items::ClientItemId; use crate::entity::gateway::EntityGateway; use crate::entity::item; use libpso::utf8_to_utf16_array; use crate::ship::packet::builder; use crate::ship::shops::{ShopItem, ToolShopItem, ArmorShopItem}; +use crate::ship::items::state::{ItemState, ItemStateError}; +use crate::ship::items::floor::{FloorType, FloorItemDetail}; +use crate::ship::items::actions::TriggerCreateItem; +use crate::ship::items::tasks::{pick_up_item, withdraw_meseta, deposit_meseta, withdraw_item, deposit_item, buy_shop_item, enemy_drops_item, take_meseta, apply_modifier}; const BANK_ACTION_DEPOSIT: u8 = 0; const BANK_ACTION_WITHDRAW: u8 = 1; @@ -22,15 +26,11 @@ const SHOP_OPTION_TOOL: u8 = 0; const SHOP_OPTION_WEAPON: u8 = 1; const SHOP_OPTION_ARMOR: u8 = 2; -const INVENTORY_MESETA_CAPACITY: u32 = 999999; -const BANK_MESETA_CAPACITY: u32 = 999999; - -//const BANK_ACTION_: u8 = 1; - #[derive(thiserror::Error, Debug)] -#[error("")] pub enum MessageError { + #[error("invalid tek {0}")] InvalidTek(ClientItemId), + #[error("mismatched tek {0} {1}")] MismatchedTekIds(ClientItemId, ClientItemId), } @@ -74,7 +74,7 @@ pub async fn request_item(id: ClientId, client_location: &ClientLocation, clients: &mut Clients, rooms: &mut Rooms, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -109,8 +109,8 @@ where item: item_drop, }; let client = clients.get_mut(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client))?; - let floor_item = item_manager.enemy_drop_item_on_local_floor(entity_gateway, &client.character, item_drop).await?; - let item_drop_msg = builder::message::item_drop(request_item.client, request_item.target, floor_item)?; + let floor_item = enemy_drops_item(item_state, entity_gateway, client.character.id, item_drop).await?; + let item_drop_msg = builder::message::item_drop(request_item.client, request_item.target, &floor_item)?; item_drop_packets.push((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg))))); } @@ -123,7 +123,7 @@ pub async fn pickup_item(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -134,7 +134,9 @@ where let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; // TODO: should not need to fetch the item here to construct this packet - let (item, floor_type) = item_manager.get_floor_item_by_id(&client.character, ClientItemId(pickup_item.item_id))?; + //let (item, floor_type) = item_manager.get_floor_item_by_id(&client.character, ClientItemId(pickup_item.item_id))?; + /* + let (item, floor_type) = item_state.get_floor_item(&client.character, &ClientItemId(pickup_item.item_id))?; let remove_item = builder::message::remove_item_from_floor(area_client, item)?; let create_item = match item { FloorItem::Individual(individual_floor_item) => Some(builder::message::create_individual_item(area_client, item.item_id(), &individual_floor_item.item)?), @@ -142,8 +144,19 @@ where FloorItem::Meseta(_) => None, //_ => Some(builder::message::create_item(area_client, &item)?), }; + */ + + let (item, floor_type) = item_state.get_floor_item(&client.character.id, &ClientItemId(pickup_item.item_id))?; + let remove_item = builder::message::remove_item_from_floor(area_client, item)?; + let create_item = match &item.item { + FloorItemDetail::Individual(individual_floor_item) => Some(builder::message::create_individual_item(area_client, item.item_id, individual_floor_item)?), + FloorItemDetail::Stacked(stacked_floor_item) => Some(builder::message::create_stacked_item(area_client, item.item_id, &stacked_floor_item.tool, stacked_floor_item.count())?), + FloorItemDetail::Meseta(_) => None, + //_ => Some(builder::message::create_item(area_client, &item)?), + }; - match item_manager.character_picks_up_item(entity_gateway, &mut client.character, ClientItemId(pickup_item.item_id)).await { + //match item_manager.character_picks_up_item(entity_gateway, &mut client.character, ClientItemId(pickup_item.item_id)).await { + match pick_up_item(item_state, entity_gateway, &client.character, &ClientItemId(pickup_item.item_id)).await { Ok(trigger_create_item) => { let remove_packets: Box + Send> = match floor_type { FloorType::Local => { @@ -180,7 +193,7 @@ pub async fn request_box_item(id: ClientId, client_location: &ClientLocation, clients: &mut Clients, rooms: &mut Rooms, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -215,8 +228,8 @@ EG: EntityGateway item: item_drop, }; let client = clients.get_mut(&area_client.client).ok_or(ShipError::ClientNotFound(area_client.client))?; - let floor_item = item_manager.enemy_drop_item_on_local_floor(entity_gateway, &client.character, item_drop).await?; // TODO: unwrap - let item_drop_msg = builder::message::item_drop(box_drop_request.client, box_drop_request.target, floor_item)?; + let floor_item = enemy_drops_item(item_state, entity_gateway, client.character.id, item_drop).await?; + let item_drop_msg = builder::message::item_drop(box_drop_request.client, box_drop_request.target, &floor_item)?; item_drop_packets.push((area_client.client, SendShipPacket::Message(Message::new(GameMessage::ItemDrop(item_drop_msg))))) } @@ -227,13 +240,12 @@ EG: EntityGateway // item_manager is not mutable in this, but for reasons I don't quite understand it requires the unique access of it to compile here pub async fn send_bank_list(id: ClientId, clients: &Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; - let bank_items = item_manager.get_character_bank(&client.character)?; - let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?; - let bank_items_pkt = builder::message::bank_item_list(bank_items, bank_meseta.0); + let bank = item_state.get_character_bank(&client.character)?; + let bank_items_pkt = builder::message::bank_item_list(bank); Ok(Box::new(vec![(id, SendShipPacket::BankItemList(bank_items_pkt))].into_iter())) } @@ -242,7 +254,7 @@ pub async fn bank_interaction(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -252,42 +264,24 @@ where let other_clients_in_area = client_location.get_all_clients_by_client(id).map_err(|err| -> ClientLocationError { err.into() })?; let bank_action_pkts = match bank_interaction.action { BANK_ACTION_DEPOSIT => { - let character_meseta = item_manager.get_character_meseta(&client.character.id)?; - let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?; if bank_interaction.item_id == 0xFFFFFFFF { - if character_meseta.0 >= bank_interaction.meseta_amount && (bank_interaction.meseta_amount + bank_meseta.0) <= BANK_MESETA_CAPACITY { - let (character_meseta, bank_meseta) = item_manager.get_character_and_bank_meseta_mut(&client.character.id)?; - character_meseta.0 -= bank_interaction.meseta_amount; - bank_meseta.0 += bank_interaction.meseta_amount; - entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?; - // TODO: BankName - entity_gateway.set_bank_meseta(&client.character.id, item::BankName("".into()), *bank_meseta).await?; - } + deposit_meseta(item_state, entity_gateway, &client.character, bank_interaction.meseta_amount).await?; Vec::new() } else { - item_manager.player_deposits_item(entity_gateway, &client.character, ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as usize).await?; + deposit_item(item_state, entity_gateway, &client.character, &ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as u32).await?; let player_no_longer_has_item = builder::message::player_no_longer_has_item(area_client, ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as u32); vec![SendShipPacket::Message(Message::new(GameMessage::PlayerNoLongerHasItem(player_no_longer_has_item)))] } }, BANK_ACTION_WITHDRAW => { - let character_meseta = item_manager.get_character_meseta(&client.character.id)?; - let bank_meseta = item_manager.get_bank_meseta(&client.character.id)?; if bank_interaction.item_id == 0xFFFFFFFF { - if (bank_meseta.0 >= bank_interaction.meseta_amount) && (character_meseta.0 + bank_interaction.meseta_amount <= INVENTORY_MESETA_CAPACITY) { - let (character_meseta, bank_meseta) = item_manager.get_character_and_bank_meseta_mut(&client.character.id)?; - character_meseta.0 += bank_interaction.meseta_amount; - bank_meseta.0 -= bank_interaction.meseta_amount; - entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?; - // TODO: BankName - entity_gateway.set_bank_meseta(&client.character.id, item::BankName("".into()), *bank_meseta).await?; - } + withdraw_meseta(item_state, entity_gateway, &client.character, bank_interaction.meseta_amount).await?; Vec::new() } else { - let item_added_to_inventory = item_manager.player_withdraws_item(entity_gateway, &client.character, ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as usize).await?; - let item_created = builder::message::create_withdrawn_inventory_item(area_client, item_added_to_inventory)?; + let item_added_to_inventory = withdraw_item(item_state, entity_gateway, &client.character, &ClientItemId(bank_interaction.item_id), bank_interaction.item_amount as u32).await?; + let item_created = builder::message::create_withdrawn_inventory_item2(area_client, &item_added_to_inventory)?; vec![SendShipPacket::Message(Message::new(GameMessage::CreateItem(item_created)))] } }, @@ -351,7 +345,7 @@ pub async fn buy_item(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -359,7 +353,6 @@ where let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?; - let (item, remove): (&(dyn ShopItem + Send + Sync), bool) = match buy_item.shop_type { SHOP_OPTION_WEAPON => { (client.weapon_shop.get(buy_item.shop_index as usize).ok_or(ShipError::ShopError)?, false) @@ -379,16 +372,8 @@ where } }; - let character_meseta = item_manager.get_character_meseta_mut(&client.character.id)?; - if character_meseta.0 < (item.price() * buy_item.amount as usize) as u32 { - return Err(ShipError::ShopError.into()) - } - - character_meseta.0 -= (item.price() * buy_item.amount as usize) as u32; - entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?; - - let inventory_item = item_manager.player_buys_item(entity_gateway, &client.character, item, ClientItemId(buy_item.item_id), buy_item.amount as usize).await?; - let create = builder::message::create_withdrawn_inventory_item(area_client, inventory_item)?; + let inventory_item = buy_shop_item(item_state, entity_gateway, &client.character, item, ClientItemId(buy_item.item_id), buy_item.amount as u32).await?; + let create = builder::message::create_withdrawn_inventory_item(area_client, &inventory_item)?; if remove { match buy_item.shop_type { @@ -424,7 +409,7 @@ pub async fn request_tek_item(id: ClientId, tek_request: &TekRequest, entity_gateway: &mut EG, clients: &mut Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -442,13 +427,13 @@ where client.tek = Some((ClientItemId(tek_request.item_id), special_mod, percent_mod, grind_mod)); - let inventory = item_manager.get_character_inventory(&client.character)?; - let item = inventory.get_item_by_id(ClientItemId(tek_request.item_id)) - .ok_or(ItemManagerError::WrongItemType(ClientItemId(tek_request.item_id)))?; - let mut weapon = *item.individual() - .ok_or(ItemManagerError::WrongItemType(ClientItemId(tek_request.item_id)))? - .weapon() - .ok_or(ItemManagerError::WrongItemType(ClientItemId(tek_request.item_id)))?; + let inventory = item_state.get_character_inventory(&client.character)?; + let item = inventory.get_by_client_id(&ClientItemId(tek_request.item_id)) + .ok_or(ItemStateError::WrongItemType(ClientItemId(tek_request.item_id)))?; + let mut weapon = *item.item.as_individual() + .ok_or(ItemStateError::WrongItemType(ClientItemId(tek_request.item_id)))? + .as_weapon() + .ok_or(ItemStateError::WrongItemType(ClientItemId(tek_request.item_id)))?; weapon.apply_modifier(&item::weapon::WeaponModifier::Tekked { special: special_mod, @@ -456,9 +441,7 @@ where grind: grind_mod, }); - let character_meseta = item_manager.get_character_meseta_mut(&client.character.id)?; - character_meseta.0 -= 100; - entity_gateway.set_character_meseta(&client.character.id, *character_meseta).await?; + take_meseta(item_state, entity_gateway, &client.character.id, item::Meseta(100)).await?; let preview_pkt = builder::message::tek_preview(ClientItemId(tek_request.item_id), &weapon)?; @@ -470,7 +453,7 @@ pub async fn accept_tek_item(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -488,9 +471,9 @@ where percent: percent_mod, grind: grind_mod, }; - let weapon = item_manager.replace_item_with_tekked(entity_gateway, &client.character, item_id, modifier).await?; + let weapon = apply_modifier(item_state, entity_gateway, &client.character, item_id, item::ItemModifier::WeaponModifier(modifier)).await?; - let create_item_pkt = builder::message::create_individual_item(area_client, item_id, &item::ItemDetail::Weapon(weapon))?; + let create_item_pkt = builder::message::create_individual_item(area_client, item_id, &weapon)?; let neighbors = client_location.get_client_neighbors(id).map_err(|err| -> ClientLocationError { err.into() })?; Ok(Box::new(neighbors.into_iter() diff --git a/src/ship/packet/handler/lobby.rs b/src/ship/packet/handler/lobby.rs index 37f18da..50839fd 100644 --- a/src/ship/packet/handler/lobby.rs +++ b/src/ship/packet/handler/lobby.rs @@ -6,14 +6,14 @@ use crate::ship::character::{FullCharacterBytesBuilder}; use crate::ship::location::{ClientLocation, LobbyId, RoomLobby, ClientLocationError}; //use crate::ship::items::; use crate::ship::packet; -use crate::ship::items::ItemManager; +use crate::ship::items::state::ItemState; use crate::entity::gateway::EntityGateway; // this function needs a better home pub fn block_selected(id: ClientId, pkt: &MenuSelect, clients: &mut Clients, - item_manager: &ItemManager, + item_state: &ItemState, level_table: &CharacterLevelTable) -> Result, anyhow::Error> { let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; @@ -21,15 +21,14 @@ pub fn block_selected(id: ClientId, let (level, stats) = level_table.get_stats_from_exp(client.character.char_class, client.character.exp); - let inventory = item_manager.get_character_inventory(&client.character).unwrap(); - let meseta = item_manager.get_character_meseta(&client.character.id).unwrap(); - let bank = item_manager.get_character_bank(&client.character).unwrap(); + let inventory = item_state.get_character_inventory(&client.character).unwrap(); + let bank = item_state.get_character_bank(&client.character).unwrap(); let fc = FullCharacterBytesBuilder::default() .character(&client.character) .stats(&stats) .level(level) - .meseta(*meseta) + .meseta(inventory.meseta) .inventory(inventory) .bank(bank) .keyboard_config(&client.character.keyboard_config.as_bytes()) @@ -52,12 +51,12 @@ pub fn send_player_to_lobby(id: ClientId, _pkt: &CharData, client_location: &mut ClientLocation, clients: &Clients, - item_manager: &ItemManager, + item_state: &ItemState, level_table: &CharacterLevelTable) -> Result, anyhow::Error> { let lobby = client_location.add_client_to_next_available_lobby(id, LobbyId(0)).map_err(|_| ShipError::TooManyClients)?; - let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_manager, level_table)?; - let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_manager, level_table)?; + let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state, level_table)?; + let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state, level_table)?; let neighbors = client_location.get_client_neighbors(id).unwrap(); Ok(vec![(id, SendShipPacket::JoinLobby(join_lobby))] .into_iter() @@ -70,7 +69,7 @@ pub async fn change_lobby(id: ClientId, requested_lobby: u32, client_location: &mut ClientLocation, clients: &Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, level_table: &CharacterLevelTable, ship_rooms: &mut Rooms, entity_gateway: &mut EG) @@ -87,7 +86,7 @@ pub async fn change_lobby(id: ClientId, if client_location.get_client_neighbors(id)?.is_empty() { ship_rooms[old_room.0] = None; } - item_manager.remove_character_from_room(&client.character); + item_state.remove_character_from_room(&client.character); }, } let leave_lobby = packet::builder::lobby::remove_from_lobby(id, client_location)?; @@ -104,9 +103,9 @@ pub async fn change_lobby(id: ClientId, } } } - item_manager.load_character(entity_gateway, &client.character).await?; - let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_manager, level_table)?; - let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_manager, level_table)?; + item_state.load_character(entity_gateway, &client.character).await?; + let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state, level_table)?; + let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state, level_table)?; let neighbors = client_location.get_client_neighbors(id).unwrap(); Ok(vec![(id, SendShipPacket::JoinLobby(join_lobby))] .into_iter() diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index c39ba73..d0d4f44 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -1,12 +1,15 @@ use libpso::packet::ship::*; use libpso::packet::messages::*; use crate::entity::gateway::EntityGateway; +use crate::entity::item::Meseta; use crate::common::serverstate::ClientId; use crate::common::leveltable::CharacterLevelTable; use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients, ItemDropLocation}; use crate::ship::location::{ClientLocation, ClientLocationError}; -use crate::ship::items::{ItemManager, ClientItemId}; +use crate::ship::items::ClientItemId; use crate::ship::packet::builder; +use crate::ship::items::state::ItemState; +use crate::ship::items::tasks::{drop_item, drop_partial_item, drop_meseta, equip_item, unequip_item, sort_inventory, use_item, feed_mag, sell_item, take_meseta}; pub async fn request_exp(id: ClientId, request_exp: &RequestExp, @@ -63,12 +66,12 @@ pub async fn request_exp(id: ClientId, } pub async fn player_drop_item(id: ClientId, - player_drop_item: &PlayerDropItem, - entity_gateway: &mut EG, - client_location: &ClientLocation, - clients: &mut Clients, - rooms: &mut Rooms, - item_manager: &mut ItemManager) + player_drop_item: &PlayerDropItem, + entity_gateway: &mut EG, + client_location: &ClientLocation, + clients: &mut Clients, + rooms: &mut Rooms, + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -80,7 +83,7 @@ where .as_mut() .ok_or(ShipError::InvalidRoom(room_id.0 as u32))?; let area = room.map_areas.get_area_map(player_drop_item.map_area)?; - item_manager.player_drop_item_on_shared_floor(entity_gateway, &client.character, ClientItemId(player_drop_item.item_id), (*area, player_drop_item.x, player_drop_item.y, player_drop_item.z)).await?; + drop_item(item_state, entity_gateway, &client.character, &ClientItemId(player_drop_item.item_id), *area, (player_drop_item.x, player_drop_item.y, player_drop_item.z)).await?; let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; let pdi = player_drop_item.clone(); Ok(Box::new(clients_in_area.into_iter() @@ -118,7 +121,7 @@ pub async fn no_longer_has_item(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -132,7 +135,7 @@ where } if no_longer_has_item.item_id == 0xFFFFFFFF { - let dropped_meseta = item_manager.player_drops_meseta_on_shared_floor(entity_gateway, &mut client.character, drop_location, no_longer_has_item.amount as u32).await?; + let dropped_meseta = drop_meseta(item_state, entity_gateway, &client.character, drop_location.map_area, (drop_location.x, drop_location.z), no_longer_has_item.amount).await?; let dropped_meseta_pkt = builder::message::drop_split_meseta_stack(area_client, &dropped_meseta)?; let no_longer_has_meseta_pkt = builder::message::player_no_longer_has_meseta(area_client, no_longer_has_item.amount as u32); @@ -156,9 +159,9 @@ where )) } else { - let dropped_item = item_manager.player_drops_partial_stack_on_shared_floor(entity_gateway, &client.character, drop_location.item_id, drop_location, no_longer_has_item.amount as usize).await?; + let dropped_item = drop_partial_item(item_state, entity_gateway, &client.character, &drop_location.item_id, drop_location.map_area, (drop_location.x, drop_location.z), no_longer_has_item.amount).await?; - let dropped_item_pkt = builder::message::drop_split_stack(area_client, dropped_item)?; + let dropped_item_pkt = builder::message::drop_split_stack(area_client, &dropped_item)?; client.item_drop_location = None; let clients_in_area = client_location.get_clients_in_room(room_id).map_err(|err| -> ClientLocationError { err.into() })?; @@ -255,63 +258,61 @@ pub fn update_player_position(id: ClientId, } pub async fn charge_attack(id: ClientId, - charge: &ChargeAttack, - clients: &mut Clients, - entity_gateway: &mut EG, - item_manager: &mut ItemManager) - -> Result + Send>, anyhow::Error> + charge: &ChargeAttack, + entity_gateway: &mut EG, + client_location: &ClientLocation, + clients: &mut Clients, + item_state: &mut ItemState) + -> Result + Send>, anyhow::Error> where EG: EntityGateway { let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; - let meseta = item_manager.get_character_meseta_mut(&client.character.id)?; - - if meseta.0 >= charge.meseta { - meseta.0 -= charge.meseta; - entity_gateway.set_character_meseta(&client.character.id, *meseta).await?; - // TODO: this should probably echo the packet - Ok(Box::new(None.into_iter())) - } else { - Err(ShipError::NotEnoughMeseta(id, meseta.0).into()) - } + + // TODO: should probably validate this to be a legit number, I'd just hardcode 200 but vjaya + take_meseta(item_state, entity_gateway, &client.character.id, Meseta(charge.meseta)).await?; + + let charge = charge.clone(); + Ok(Box::new(client_location.get_client_neighbors(id).unwrap().into_iter() + .map(move |client| { + (client.client, SendShipPacket::Message(Message::new(GameMessage::ChargeAttack(charge.clone())))) + }))) } -pub async fn use_item(id: ClientId, +pub async fn player_uses_item(id: ClientId, player_use_tool: &PlayerUseItem, entity_gateway: &mut EG, _client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway { let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; - let item_used_type = item_manager.player_consumes_tool(entity_gateway, &mut client.character, ClientItemId(player_use_tool.item_id), 1).await?; - - item_manager.use_item(item_used_type, entity_gateway, &mut client.character).await?; + use_item(item_state, entity_gateway, &mut client.character, &ClientItemId(player_use_tool.item_id), 1).await?; Ok(Box::new(None.into_iter())) // TODO: should probably tell other players we used an item } pub async fn player_used_medical_center(id: ClientId, - _pumc: &PlayerUsedMedicalCenter, // not needed? + pumc: &PlayerUsedMedicalCenter, entity_gateway: &mut EG, + client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway { let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; - let meseta = item_manager.get_character_meseta_mut(&client.character.id)?; - if meseta.0 >= 10 { - meseta.0 -= 10; - entity_gateway.set_character_meseta(&client.character.id, *meseta).await?; - // TODO: this should probably echo the packet - Ok(Box::new(None.into_iter())) - } else { - Err(ShipError::NotEnoughMeseta(id, meseta.0).into()) - } + + take_meseta(item_state, entity_gateway, &client.character.id, Meseta(10)).await?; + + let pumc = pumc.clone(); + Ok(Box::new(client_location.get_client_neighbors(id).unwrap().into_iter() + .map(move |client| { + (client.client, SendShipPacket::Message(Message::new(GameMessage::PlayerUsedMedicalCenter(pumc.clone())))) + }))) } @@ -320,13 +321,13 @@ pub async fn player_feed_mag(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; - item_manager.player_feeds_mag_item(entity_gateway, &client.character, ClientItemId(mag_feed.mag_id), ClientItemId(mag_feed.item_id)).await?; + feed_mag(item_state, entity_gateway, &client.character, &ClientItemId(mag_feed.mag_id), &ClientItemId(mag_feed.item_id)).await?; let mag_feed = mag_feed.clone(); Ok(Box::new(client_location.get_client_neighbors(id).unwrap().into_iter() @@ -339,7 +340,7 @@ pub async fn player_equips_item(id: ClientId, pkt: &PlayerEquipItem, entity_gateway: &mut EG, clients: &Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway @@ -351,7 +352,7 @@ where else { 0 }; - item_manager.player_equips_item(entity_gateway, &client.character, ClientItemId(pkt.item_id), equip_slot).await?; + equip_item(item_state, entity_gateway, &client.character, &ClientItemId(pkt.item_id), equip_slot).await?; Ok(Box::new(None.into_iter())) // TODO: tell other players you equipped an item } @@ -359,13 +360,13 @@ pub async fn player_unequips_item(id: ClientId, pkt: &PlayerUnequipItem, entity_gateway: &mut EG, clients: &Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; - item_manager.player_unequips_item(entity_gateway, &client.character, ClientItemId(pkt.item_id)).await?; + unequip_item(item_state, entity_gateway, &client.character, &ClientItemId(pkt.item_id)).await?; Ok(Box::new(None.into_iter())) // TODO: tell other players if you unequip an item } @@ -373,28 +374,38 @@ pub async fn player_sorts_items(id: ClientId, pkt: &SortItems, entity_gateway: &mut EG, clients: &Clients, - item_manager: &mut ItemManager) + item_state: &mut ItemState) -> Result + Send>, anyhow::Error> where EG: EntityGateway { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; - item_manager.player_sorts_items(entity_gateway, &client.character, pkt.item_ids).await?; + let item_ids = pkt.item_ids + .iter() + .filter_map(|item_id| { + if *item_id != 0 { + Some(ClientItemId(*item_id)) + } + else { + None + } + }) + .collect(); + sort_inventory(item_state, entity_gateway, &client.character, item_ids).await?; Ok(Box::new(None.into_iter())) // TODO: clients probably care about each others item orders } pub async fn player_sells_item (id: ClientId, - sold_item: &PlayerSoldItem, - entity_gateway: &mut EG, - // client_location: &ClientLocation, - clients: &mut Clients, - item_manager: &mut ItemManager) - -> Result + Send>, anyhow::Error> + sold_item: &PlayerSoldItem, + entity_gateway: &mut EG, + clients: &mut Clients, + item_state: &mut ItemState) + -> Result + Send>, anyhow::Error> where EG: EntityGateway { let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; - item_manager.player_sells_item(entity_gateway, &mut client.character, ClientItemId(sold_item.item_id), sold_item.amount as usize).await?; + sell_item(item_state, entity_gateway, &client.character, ClientItemId(sold_item.item_id), sold_item.amount as u32).await?; // TODO: send the packet to other clients Ok(Box::new(None.into_iter())) } diff --git a/src/ship/packet/handler/room.rs b/src/ship/packet/handler/room.rs index ba82b2b..bb04c3b 100644 --- a/src/ship/packet/handler/room.rs +++ b/src/ship/packet/handler/room.rs @@ -6,14 +6,14 @@ use crate::ship::ship::{SendShipPacket, ShipError, Rooms, Clients}; use crate::ship::location::{ClientLocation, RoomId, RoomLobby, ClientLocationError}; use crate::ship::packet::builder; use crate::ship::room; -use crate::ship::items::ItemManager; +use crate::ship::items::state::ItemState; use std::convert::{TryFrom}; pub fn create_room(id: ClientId, create_room: &CreateRoom, client_location: &mut ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, level_table: &CharacterLevelTable, rooms: &mut Rooms) -> Result + Send>, anyhow::Error> { @@ -45,7 +45,7 @@ pub fn create_room(id: ClientId, let mut room = room::RoomState::from_create_room(create_room, client.character.section_id).unwrap(); room.bursting = true; - item_manager.add_character_to_room(room_id, &client.character, area_client); + item_state.add_character_to_room(room_id, &client.character, area_client); let join_room = builder::room::join_room(id, clients, client_location, room_id, &room)?; rooms[room_id.0] = Some(room); @@ -80,7 +80,7 @@ pub fn join_room(id: ClientId, pkt: &MenuSelect, client_location: &mut ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, level_table: &CharacterLevelTable, rooms: &mut Rooms) -> Result + Send>, ShipError> { @@ -119,11 +119,11 @@ pub fn join_room(id: ClientId, let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?; - item_manager.add_character_to_room(room_id, &client.character, area_client); + item_state.add_character_to_room(room_id, &client.character, area_client); let leader = client_location.get_room_leader(room_id).map_err(|err| -> ClientLocationError { err.into() })?; let join_room = builder::room::join_room(id, clients, client_location, room_id, room)?; - let add_to = builder::room::add_to_room(id, client, &area_client, &leader, item_manager, level_table, room_id)?; + let add_to = builder::room::add_to_room(id, client, &area_client, &leader, item_state, level_table, room_id)?; let room = rooms.get_mut(room_id.0).unwrap().as_mut().unwrap(); room.bursting = true; diff --git a/src/ship/packet/handler/trade.rs b/src/ship/packet/handler/trade.rs index 040bc29..7b01ed6 100644 --- a/src/ship/packet/handler/trade.rs +++ b/src/ship/packet/handler/trade.rs @@ -4,16 +4,19 @@ use libpso::packet::messages::*; use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, Clients}; use crate::ship::location::{ClientLocation, ClientLocationError}; -use crate::ship::items::{ItemManager, ItemManagerError, ClientItemId, ItemToTradeDetail}; -use crate::ship::items::inventory::InventoryItem; +use crate::ship::items::ClientItemId; +use crate::ship::items::state::{ItemState, ItemStateError}; +use crate::ship::items::inventory::InventoryItemDetail; use crate::ship::trade::{TradeItem, TradeState, TradeStatus}; use crate::entity::gateway::EntityGateway; use crate::ship::packet::builder; +use crate::ship::items::tasks::trade_items; +use crate::ship::location::{AreaClient, RoomId}; +use crate::entity::item::Meseta; pub const MESETA_ITEM_ID: ClientItemId = ClientItemId(0xFFFFFF01); pub const OTHER_MESETA_ITEM_ID: ClientItemId = ClientItemId(0xFFFFFFFF); - #[derive(thiserror::Error, Debug, PartialEq, Eq)] pub enum TradeError { #[error("no partner")] @@ -51,9 +54,9 @@ pub async fn trade_request(id: ClientId, target: u32, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, trades: &mut TradeState) - -> Result + Send>, anyhow::Error> + -> Result + Send>, ShipError> { let trade_request = trade_request.clone(); // TODO: make this function take ownership of packet match trade_request.trade { @@ -114,18 +117,18 @@ pub async fn trade_request(id: ClientId, .with(&id, |this, other| -> Result + Send>, anyhow::Error> { if this.status == TradeStatus::Trading && other.status == TradeStatus::Trading { let client = clients.get(&this.client()).ok_or_else(|| ShipError::ClientNotFound(this.client()))?; - let inventory = item_manager.get_character_inventory(&client.character)?; + let inventory = item_state.get_character_inventory(&client.character)?; if ClientItemId(item_id) == MESETA_ITEM_ID { this.meseta += amount as usize; } else { - let item = inventory.get_item_by_id(ClientItemId(item_id)).ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item_id)))?; + let item = inventory.get_by_client_id(&ClientItemId(item_id)).ok_or(ItemStateError::InvalidItemId(ClientItemId(item_id)))?; - match item { - InventoryItem::Individual(_) => { + match &item.item { + InventoryItemDetail::Individual(_) => { this.items.push(TradeItem::Individual(ClientItemId(item_id))); }, - InventoryItem::Stacked(stacked_item) => { + InventoryItemDetail::Stacked(stacked_item) => { if stacked_item.count() < amount as usize { return Err(TradeError::InvalidStackAmount(ClientItemId(item_id), amount as usize).into()); } @@ -160,20 +163,20 @@ pub async fn trade_request(id: ClientId, .with(&id, |this, other| -> Option + Send>> { if this.status == TradeStatus::Trading && other.status == TradeStatus::Trading { let client = clients.get(&this.client())?; //.ok_or(ShipError::ClientNotFound(id)).ok()?; - let inventory = item_manager.get_character_inventory(&client.character).ok()?; + let inventory = item_state.get_character_inventory(&client.character).ok()?; if ClientItemId(item_id) == MESETA_ITEM_ID { this.meseta -= amount as usize; } else { - let item = inventory.get_item_by_id(ClientItemId(item_id))?; + let item = inventory.get_by_client_id(&ClientItemId(item_id))?; - match item { - InventoryItem::Individual(_) => { + match &item.item { + InventoryItemDetail::Individual(_) => { this.items.retain(|item| { item.item_id() != ClientItemId(item_id) }) }, - InventoryItem::Stacked(_stacked_item) => { + InventoryItemDetail::Stacked(_stacked_item) => { let trade_item_index = this.items.iter() .position(|item| { item.item_id() == ClientItemId(item_id) @@ -293,7 +296,7 @@ pub async fn inner_items_to_trade(id: ClientId, items_to_trade: &ItemsToTrade, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, trades: &mut TradeState) -> Result + Send>, anyhow::Error> { @@ -305,7 +308,7 @@ pub async fn inner_items_to_trade(id: ClientId, let client = clients.get(&this.client()).ok_or_else(|| ShipError::ClientNotFound(this.client()))?; let other_client = clients.get(&other.client()).ok_or_else(|| ShipError::ClientNotFound(other.client()))?; - let inventory = item_manager.get_character_inventory(&client.character)?; + let inventory = item_state.get_character_inventory(&client.character)?; if items_to_trade.count as usize != (this.items.len() + (if this.meseta != 0 { 1 } else { 0 })) { return Err(TradeError::MismatchedTradeItems.into()) @@ -320,8 +323,8 @@ pub async fn inner_items_to_trade(id: ClientId, return Err(TradeError::InvalidItemId(ClientItemId(item.item_id)).into()) } let amount = u32::from_le_bytes(item.item_data2); - let character_meseta = item_manager.get_character_meseta(&client.character.id).map_err(|_| TradeError::InvalidMeseta)?; - let other_character_meseta = item_manager.get_character_meseta(&other_client.character.id).map_err(|_| TradeError::InvalidMeseta)?; + let character_meseta = item_state.get_character_inventory(&client.character).map_err(|_| TradeError::InvalidMeseta)?.meseta; + let other_character_meseta = item_state.get_character_inventory(&other_client.character).map_err(|_| TradeError::InvalidMeseta)?.meseta; if amount > character_meseta.0 { return Err(TradeError::InvalidMeseta.into()) } @@ -334,8 +337,8 @@ pub async fn inner_items_to_trade(id: ClientId, Ok(()) } else { - let real_item = inventory.get_item_by_id(ClientItemId(item.item_id)) - .ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?; + let real_item = inventory.get_by_client_id(&ClientItemId(item.item_id)) + .ok_or(ItemStateError::InvalidItemId(ClientItemId(item.item_id)))?; let real_trade_item = this.items .iter() .find(|i| i.item_id() == ClientItemId(item.item_id)) @@ -345,28 +348,28 @@ pub async fn inner_items_to_trade(id: ClientId, .cloned().collect::>() .try_into() .unwrap(); - match real_item { - InventoryItem::Individual(_individual_inventory_item) => { - if real_item.as_client_bytes() == trade_item_bytes { + match &real_item.item { + InventoryItemDetail::Individual(_individual_inventory_item) => { + if real_item.item.as_client_bytes() == trade_item_bytes { Ok(()) } else { Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into()) } }, - InventoryItem::Stacked(stacked_inventory_item) => { - if real_item.as_client_bytes()[0..4] == trade_item_bytes[0..4] { + InventoryItemDetail::Stacked(stacked_inventory_item) => { + if real_item.item.as_client_bytes()[0..4] == trade_item_bytes[0..4] { let amount = trade_item_bytes[5] as usize; if amount <= stacked_inventory_item.entity_ids.len() { if real_trade_item.stacked().ok_or(TradeError::SketchyTrade)?.1 == amount { Ok(()) } else { - Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into()) + Err(TradeError::InvalidStackAmount(real_item.item_id, amount).into()) } } else { - Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into()) + Err(TradeError::InvalidStackAmount(real_item.item_id, amount).into()) } } else { @@ -405,11 +408,11 @@ pub async fn items_to_trade(id: ClientId, items_to_trade_pkt: &ItemsToTrade, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, trades: &mut TradeState) -> Result + Send>, anyhow::Error> { - let t = inner_items_to_trade(id, items_to_trade_pkt, client_location, clients, item_manager, trades).await; + let t = inner_items_to_trade(id, items_to_trade_pkt, client_location, clients, item_state, trades).await; match t { Ok(p) => Ok(p), Err(err) => { @@ -429,7 +432,7 @@ pub async fn trade_confirmed(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager, + item_state: &mut ItemState, trades: &mut TradeState) -> Result + Send>, anyhow::Error> where @@ -437,9 +440,9 @@ where { enum TradeReady<'a> { OnePlayer, - BothPlayers(crate::ship::location::RoomId, - (crate::ship::location::AreaClient, &'a crate::ship::ship::ClientState, crate::ship::trade::ClientTradeState), - (crate::ship::location::AreaClient, &'a crate::ship::ship::ClientState, crate::ship::trade::ClientTradeState)), + BothPlayers(RoomId, + (AreaClient, &'a crate::ship::ship::ClientState, crate::ship::trade::ClientTradeState), + (AreaClient, &'a crate::ship::ship::ClientState, crate::ship::trade::ClientTradeState)), } let trade_instructions = trades @@ -472,37 +475,63 @@ where TradeReady::OnePlayer => { Ok(Box::new(None.into_iter()) as Box + Send>) }, - TradeReady::BothPlayers(room_id, (this_local_client, this_client, this), (other_local_client, other_client, other)) => { - let traded_items = item_manager.trade_items(entity_gateway, - room_id, - (&this_local_client, &this_client.character, &this.items, this.meseta), - (&other_local_client, &other_client.character, &other.items, other.meseta)).await?; + TradeReady::BothPlayers(_room_id, (this_local_client, this_client, this), (other_local_client, other_client, other)) => { + let remove_item_packets = this.items + .clone() + .into_iter() + .map(move |item| { + (this_local_client, item) + }) + .chain(other.items + .clone() + .into_iter() + .map(move |item| { + (other_local_client, item) + })) + .map(|(client, item)| { + GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(client, item.item_id(), item.amount() as u32)) + }); - let clients_in_room = client_location.get_all_clients_by_client(id)?; - let traded_item_packets = traded_items + let (this_new_items, other_new_items) = trade_items(item_state, + entity_gateway, + (&this_local_client, &this_client.character, &this.items, Meseta(this.meseta as u32)), + (&other_local_client, &other_client.character, &other.items, Meseta(other.meseta as u32))).await?; + + let create_item_packets = this_new_items .into_iter() - .flat_map(|item| { - match item.item_detail { - ItemToTradeDetail::Individual(item_detail) => { - [ - GameMessage::CreateItem(builder::message::create_individual_item(item.add_to, item.new_item_id, &item_detail).unwrap()), - GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.current_item_id, 1)) // TODO: amount = ? - ] - }, - ItemToTradeDetail::Stacked(tool, amount) => { - [ - GameMessage::CreateItem(builder::message::create_stacked_item(item.add_to, item.new_item_id, &tool, amount).unwrap()), - GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.current_item_id, amount as u32)) - ] - }, - ItemToTradeDetail::Meseta(amount) => { - [ - GameMessage::CreateItem(builder::message::create_meseta(item.add_to, amount)), - GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.current_item_id, amount as u32)) - ] + .map(move |item| { + (this_local_client, item) + }) + .chain(other_new_items + .into_iter() + .map(move |item| { + (other_local_client, item) + })) + .map(|(client, item)| { + match item.item { + InventoryItemDetail::Individual(individual_item) => { + GameMessage::CreateItem(builder::message::create_individual_item(client, item.item_id, &individual_item).unwrap()) }, + InventoryItemDetail::Stacked(stacked_item) => { + GameMessage::CreateItem(builder::message::create_stacked_item(client, item.item_id, &stacked_item.tool, stacked_item.count()).unwrap()) + } } - }) + }); + + let meseta_packets = vec![(this_local_client, other_local_client, this.meseta), (other_local_client, this_local_client, other.meseta)] + .into_iter() + .filter(|(_, _, meseta)| *meseta != 0) + .flat_map(|(this, other, meseta)| { + [ + GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(this, MESETA_ITEM_ID, meseta as u32)), + GameMessage::CreateItem(builder::message::create_meseta(other, meseta)), + ] + }); + + let clients_in_room = client_location.get_all_clients_by_client(id)?; + let traded_item_packets = remove_item_packets + .chain(create_item_packets) + .chain(meseta_packets) .flat_map(move |packet| { clients_in_room .clone() @@ -521,6 +550,7 @@ where } }) }); + let close_trade = vec![ (this.client(), SendShipPacket::TradeSuccessful(TradeSuccessful::default())), (other.client(), SendShipPacket::TradeSuccessful(TradeSuccessful::default())) diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 4cf5e2a..c1ec3d8 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -41,44 +41,71 @@ pub type Rooms = [Option; MAX_ROOMS]; pub type Clients = HashMap; #[derive(Error, Debug)] -#[error("shiperror {0:?}")] pub enum ShipError { + #[error("client not found {0}")] ClientNotFound(ClientId), + #[error("no character in slot {0} {1}")] NoCharacterInSlot(ClientId, u32), + #[error("invalid slot {0} {1}")] InvalidSlot(ClientId, u32), - #[error("")] + #[error("too many clients")] TooManyClients, + #[error("client error location {0}")] ClientLocationError(#[from] ClientLocationError), + #[error("get neighbor error {0}")] GetNeighborError(#[from] GetNeighborError), + #[error("get clients error {0}")] GetClientsError(#[from] GetClientsError), + #[error("get area error {0}")] GetAreaError(#[from] GetAreaError), + #[error("maps error {0}")] MapsError(#[from] MapsError), + #[error("map area error {0}")] MapAreaError(#[from] MapAreaError), + #[error("invalid room {0}")] InvalidRoom(u32), + #[error("monster already droppped item {0} {1}")] MonsterAlreadyDroppedItem(ClientId, u16), + #[error("slice error {0}")] SliceError(#[from] std::array::TryFromSliceError), - #[error("")] + #[error("item error")] ItemError, // TODO: refine this + #[error("pick up invalid item id {0}")] PickUpInvalidItemId(u32), + #[error("drop invalid item id {0}")] DropInvalidItemId(u32), - ItemManagerError(#[from] items::ItemManagerError), - #[error("")] + #[error("item state error {0}")] + ItemStateError(#[from] items::state::ItemStateError), + #[error("item drop location not set")] ItemDropLocationNotSet, + #[error("box already dropped item {0} {1}")] BoxAlreadyDroppedItem(ClientId, u16), + #[error("invalid quest category {0}")] InvalidQuestCategory(u32), + #[error("invalid quest {0}")] InvalidQuest(u32), + #[error("invalid quest filename {0}")] InvalidQuestFilename(String), + #[error("io error {0}")] IoError(#[from] std::io::Error), + #[error("not enough meseta {0} {1}")] NotEnoughMeseta(ClientId, u32), - #[error("")] + #[error("shop error")] ShopError, + #[error("gateway error {0}")] GatewayError(#[from] GatewayError), + #[error("unknown monster {0}")] UnknownMonster(crate::ship::monster::MonsterType), + #[error("invalid ship {0}")] InvalidShip(usize), + #[error("invalid block {0}")] InvalidBlock(usize), + #[error("invalid item {0}")] InvalidItem(items::ClientItemId), - #[error("tradeerror {0}")] + #[error("trade error {0}")] TradeError(#[from] crate::ship::packet::handler::trade::TradeError), + #[error("trade state error {0}")] + TradeStateError(#[from] crate::ship::trade::TradeStateError), } #[derive(Debug)] @@ -403,7 +430,7 @@ impl ShipServerStateBuilder { clients: HashMap::new(), level_table: CharacterLevelTable::default(), name: self.name.unwrap_or_else(|| "NAMENOTSET".into()), - item_manager: items::ItemManager::default(), + item_state: items::state::ItemState::default(), ip: self.ip.unwrap_or_else(|| Ipv4Addr::new(127,0,0,1)), port: self.port.unwrap_or(SHIP_PORT), shops: Box::new(ItemShops::default()), @@ -447,7 +474,7 @@ pub struct ShipServerState { pub clients: Clients, level_table: CharacterLevelTable, name: String, - item_manager: items::ItemManager, + item_state: items::state::ItemState, shops: Box, pub blocks: Blocks, @@ -477,7 +504,7 @@ impl ShipServerState { }, GameMessage::PlayerDropItem(player_drop_item) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::message::player_drop_item(id, player_drop_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_manager).await? + handler::message::player_drop_item(id, player_drop_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_state).await? }, GameMessage::DropCoordinates(drop_coordinates) => { let block = self.blocks.with_client(id, &self.clients)?; @@ -485,7 +512,7 @@ impl ShipServerState { }, GameMessage::PlayerNoLongerHasItem(no_longer_has_item) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::message::no_longer_has_item(id, no_longer_has_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + handler::message::no_longer_has_item(id, no_longer_has_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state).await? }, GameMessage::PlayerChangedMap(_) | GameMessage::PlayerChangedMap2(_) | GameMessage::TellOtherPlayerMyLocation(_) | GameMessage::PlayerWarpingToFloor(_) | GameMessage::PlayerTeleported(_) | GameMessage::PlayerStopped(_) | @@ -495,30 +522,32 @@ impl ShipServerState { handler::message::update_player_position(id, msg, &mut self.clients, &block.client_location, &block.rooms)? }, GameMessage::ChargeAttack(charge_attack) => { - handler::message::charge_attack(id, charge_attack, &mut self.clients, &mut self.entity_gateway, &mut self.item_manager).await? + let block = self.blocks.with_client(id, &self.clients)?; + handler::message::charge_attack(id, charge_attack, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state).await? }, GameMessage::PlayerUseItem(player_use_item) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::message::use_item(id, player_use_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + handler::message::player_uses_item(id, player_use_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state).await? }, GameMessage::PlayerUsedMedicalCenter(player_used_medical_center) => { - handler::message::player_used_medical_center(id, player_used_medical_center, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager).await? + let block = self.blocks.with_client(id, &self.clients)?; + handler::message::player_used_medical_center(id, player_used_medical_center, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state).await? }, GameMessage::PlayerFeedMag(player_feed_mag) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::message::player_feed_mag(id, player_feed_mag, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_manager).await? + handler::message::player_feed_mag(id, player_feed_mag, &mut self.entity_gateway, &block.client_location, &self.clients, &mut self.item_state).await? }, GameMessage::PlayerEquipItem(player_equip_item) => { - handler::message::player_equips_item(id, player_equip_item, &mut self.entity_gateway, &self.clients, &mut self.item_manager).await? + handler::message::player_equips_item(id, player_equip_item, &mut self.entity_gateway, &self.clients, &mut self.item_state).await? }, GameMessage::PlayerUnequipItem(player_unequip_item) => { - handler::message::player_unequips_item(id, player_unequip_item, &mut self.entity_gateway, &self.clients, &mut self.item_manager).await? + handler::message::player_unequips_item(id, player_unequip_item, &mut self.entity_gateway, &self.clients, &mut self.item_state).await? }, GameMessage::SortItems(sort_items) => { - handler::message::player_sorts_items(id, sort_items, &mut self.entity_gateway, &self.clients, &mut self.item_manager).await? + handler::message::player_sorts_items(id, sort_items, &mut self.entity_gateway, &self.clients, &mut self.item_state).await? }, GameMessage::PlayerSoldItem(player_sold_item) => { - handler::message::player_sells_item(id, player_sold_item, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager).await? + handler::message::player_sells_item(id, player_sold_item, &mut self.entity_gateway, &mut self.clients, &mut self.item_state).await? }, _ => { let cmsg = msg.clone(); @@ -539,34 +568,34 @@ impl ShipServerState { handler::direct_message::guildcard_send(id, guildcard_send, target, &block.client_location, &self.clients) }, GameMessage::RequestItem(request_item) => { - handler::direct_message::request_item(id, request_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_manager).await? + handler::direct_message::request_item(id, request_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_state).await? }, GameMessage::PickupItem(pickup_item) => { - handler::direct_message::pickup_item(id, pickup_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + handler::direct_message::pickup_item(id, pickup_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state).await? }, GameMessage::BoxDropRequest(box_drop_request) => { - handler::direct_message::request_box_item(id, box_drop_request, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_manager).await? + handler::direct_message::request_box_item(id, box_drop_request, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut block.rooms, &mut self.item_state).await? }, GameMessage::BankRequest(_bank_request) => { - handler::direct_message::send_bank_list(id, &self.clients, &mut self.item_manager).await? + handler::direct_message::send_bank_list(id, &self.clients, &mut self.item_state).await? }, GameMessage::BankInteraction(bank_interaction) => { - handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + handler::direct_message::bank_interaction(id, bank_interaction, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state).await? }, GameMessage::ShopRequest(shop_request) => { handler::direct_message::shop_request(id, shop_request, &block.client_location, &mut self.clients, &block.rooms, &self.level_table, &mut self.shops).await? }, GameMessage::BuyItem(buy_item) => { - handler::direct_message::buy_item(id, buy_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + handler::direct_message::buy_item(id, buy_item, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state).await? }, GameMessage::TekRequest(tek_request) => { - handler::direct_message::request_tek_item(id, tek_request, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager).await? + handler::direct_message::request_tek_item(id, tek_request, &mut self.entity_gateway, &mut self.clients, &mut self.item_state).await? }, GameMessage::TekAccept(tek_accept) => { - handler::direct_message::accept_tek_item(id, tek_accept, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + handler::direct_message::accept_tek_item(id, tek_accept, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state).await? }, GameMessage::TradeRequest(trade_request) => { - handler::trade::trade_request(id, trade_request, target, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await? + handler::trade::trade_request(id, trade_request, target, &block.client_location, &mut self.clients, &mut self.item_state, &mut self.trades).await? }, _ => { let cmsg = msg.clone(); @@ -604,7 +633,7 @@ impl ServerState for ShipServerState { -> Result + Send>, anyhow::Error> { Ok(match pkt { RecvShipPacket::Login(login) => { - Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_manager, &self.shipgate_sender, &self.name, self.blocks.0.len()) + Box::new(handler::auth::validate_login(id, login, &mut self.entity_gateway, &mut self.clients, &mut self.item_state, &self.shipgate_sender, &self.name, self.blocks.0.len()) .await?.into_iter().map(move |pkt| (id, pkt))) }, RecvShipPacket::QuestDetailRequest(questdetailrequest) => { @@ -624,10 +653,10 @@ impl ServerState for ShipServerState { } BLOCK_MENU_ID => { let leave_lobby = handler::lobby::remove_from_lobby(id, &mut block.client_location).into_iter().into_iter().flatten(); - let select_block = handler::lobby::block_selected(id, menuselect, &mut self.clients, &self.item_manager, &self.level_table)?.into_iter(); + let select_block = handler::lobby::block_selected(id, menuselect, &mut self.clients, &self.item_state, &self.level_table)?.into_iter(); Box::new(leave_lobby.chain(select_block)) } - ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)?, + ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &mut self.clients, &mut self.item_state, &self.level_table, &mut block.rooms)?, QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &block.client_location, &mut block.rooms)?, _ => unreachable!(), } @@ -649,7 +678,7 @@ impl ServerState for ShipServerState { menu: room_password_req.menu, item: room_password_req.item, }; - handler::room::join_room(id, &menuselect, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)? + handler::room::join_room(id, &menuselect, &mut block.client_location, &mut self.clients, &mut self.item_state, &self.level_table, &mut block.rooms)? } else { Box::new(vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Incorrect password".into())))].into_iter()) @@ -657,7 +686,7 @@ impl ServerState for ShipServerState { }, RecvShipPacket::CharData(chardata) => { let block = self.blocks.with_client(id, &self.clients)?; - Box::new(handler::lobby::send_player_to_lobby(id, chardata, &mut block.client_location, &self.clients, &self.item_manager, &self.level_table)?.into_iter()) + Box::new(handler::lobby::send_player_to_lobby(id, chardata, &mut block.client_location, &self.clients, &self.item_state, &self.level_table)?.into_iter()) }, RecvShipPacket::Message(msg) => { self.message(id, msg).await? @@ -671,7 +700,7 @@ impl ServerState for ShipServerState { }, RecvShipPacket::CreateRoom(create_room) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::room::create_room(id, create_room, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)? + handler::room::create_room(id, create_room, &mut block.client_location, &mut self.clients, &mut self.item_state, &self.level_table, &mut block.rooms)? }, RecvShipPacket::RoomNameRequest(_req) => { let block = self.blocks.with_client(id, &self.clients)?; @@ -709,7 +738,7 @@ impl ServerState for ShipServerState { }, RecvShipPacket::LobbySelect(pkt) => { let block = self.blocks.with_client(id, &self.clients)?; - Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms, &mut self.entity_gateway).await?.into_iter()) + Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_state, &self.level_table, &mut block.rooms, &mut self.entity_gateway).await?.into_iter()) }, RecvShipPacket::RequestQuestList(rql) => { let block = self.blocks.with_client(id, &self.clients)?; @@ -741,11 +770,11 @@ impl ServerState for ShipServerState { }, RecvShipPacket::ItemsToTrade(items_to_trade) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::trade::items_to_trade(id, items_to_trade, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await? + handler::trade::items_to_trade(id, items_to_trade, &block.client_location, &mut self.clients, &mut self.item_state, &mut self.trades).await? }, RecvShipPacket::TradeConfirmed(_) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::trade::trade_confirmed(id, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await? + handler::trade::trade_confirmed(id, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_state, &mut self.trades).await? }, RecvShipPacket::KeyboardConfig(keyboard_config) => { handler::settings::keyboard_config(id, keyboard_config, &mut self.clients, &mut self.entity_gateway).await @@ -781,7 +810,7 @@ impl ServerState for ShipServerState { } block.client_location.remove_client_from_area(id); - self.item_manager.remove_character_from_room(&client.character); + self.item_state.remove_character_from_room(&client.character); if let Some(mut client) = self.clients.remove(&id) { client.user.at_ship = false; diff --git a/src/ship/trade.rs b/src/ship/trade.rs index 39d7d0e..6871179 100644 --- a/src/ship/trade.rs +++ b/src/ship/trade.rs @@ -31,6 +31,13 @@ impl TradeItem { TradeItem::Stacked(item_id, _) => *item_id, } } + + pub fn amount(&self) -> usize { + match self { + TradeItem::Individual(_) => 1, + TradeItem::Stacked(_, amount) => *amount, + } + } } @@ -67,9 +74,10 @@ impl ClientTradeState { } #[derive(thiserror::Error, Debug)] -#[error("")] pub enum TradeStateError { + #[error("client not in trade {0}")] ClientNotInTrade(ClientId), + #[error("mismatched trade {0} {1}")] MismatchedTrade(ClientId, ClientId), } diff --git a/tests/common.rs b/tests/common.rs index a44679f..cf958c8 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -29,7 +29,7 @@ pub async fn new_user_character(entity_gateway: &mut EG, user let new_character = NewCharacterEntity::new(user.id, kb_conf_preset); let character = entity_gateway.create_character(new_character).await.unwrap(); entity_gateway.set_character_meseta(&character.id, Meseta(0)).await.unwrap(); - entity_gateway.set_bank_meseta(&character.id, BankName("".into()), Meseta(0)).await.unwrap(); + entity_gateway.set_bank_meseta(&character.id, &BankName("".into()), Meseta(0)).await.unwrap(); (user, character) } diff --git a/tests/test_bank.rs b/tests/test_bank.rs index 75777ab..43a4364 100644 --- a/tests/test_bank.rs +++ b/tests/test_bank.rs @@ -31,7 +31,7 @@ async fn test_bank_items_sent_in_character_login() { ), }).await.unwrap(); - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![item]), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![item]), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -69,7 +69,7 @@ async fn test_request_bank_items() { }).await.unwrap()); } - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(bank), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(bank), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -113,7 +113,7 @@ async fn test_request_stacked_bank_items() { }).await.unwrap()); } - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![monomates]), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![monomates]), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -178,7 +178,7 @@ async fn test_request_bank_items_sorted() { }).await.unwrap(); let bank = vec![item::BankItemEntity::Individual(item1), vec![monomate].into(), item2.into()]; - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(bank), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(bank), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -269,7 +269,13 @@ async fn test_deposit_individual_item() { && player_no_longer_has_item.amount == 0 )); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(inventory_items.items.len(), 1); + inventory_items.items[0].with_individual(|item| { + assert_eq!(item.id, item::ItemEntityId(1)); + }).unwrap(); + + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 1); bank_items.items[0].with_individual(|item| { assert_eq!(item.id, item::ItemEntityId(2)); @@ -329,7 +335,7 @@ async fn test_deposit_stacked_item() { && player_no_longer_has_item.amount == 3 )); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 1); bank_items.items[0].with_stacked(|items| { assert_eq!(items.iter().map(|i| i.id).collect::>(), @@ -391,7 +397,7 @@ async fn test_deposit_partial_stacked_item() { )); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 1); bank_items.items[0].with_stacked(|items| { assert_eq!(items.iter().map(|i| i.id).collect::>(), @@ -437,7 +443,7 @@ async fn test_deposit_stacked_item_with_stack_already_in_bank() { } entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![inventory_monomates])).await.unwrap(); - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![bank_monomates]), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![bank_monomates]), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -471,7 +477,7 @@ async fn test_deposit_stacked_item_with_stack_already_in_bank() { && player_no_longer_has_item.amount == 2 )); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 1); bank_items.items[0].with_stacked(|items| { assert_eq!(items.iter().map(|i| i.id).collect::>(), @@ -510,7 +516,7 @@ async fn test_deposit_stacked_item_with_full_stack_in_bank() { } entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![inventory_monomates])).await.unwrap(); - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![bank_monomates]), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![bank_monomates]), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -537,7 +543,7 @@ async fn test_deposit_stacked_item_with_full_stack_in_bank() { assert!(packets.is_err()); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 1); bank_items.items[0].with_stacked(|items| { assert_eq!(items.len(), 10); @@ -588,7 +594,7 @@ async fn test_deposit_individual_item_in_full_bank() { } entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(inventory)).await.unwrap(); - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(bank), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(bank), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -615,7 +621,7 @@ async fn test_deposit_individual_item_in_full_bank() { assert!(packets.is_err()); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 200); let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); @@ -660,7 +666,7 @@ async fn test_deposit_stacked_item_in_full_bank() { } entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![monomates])).await.unwrap(); - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(full_bank), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(full_bank), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -687,7 +693,7 @@ async fn test_deposit_stacked_item_in_full_bank() { assert!(packets.is_err()); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 200); let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); @@ -746,7 +752,7 @@ async fn test_deposit_stacked_item_in_full_bank_with_partial_stack() { almost_full_bank.push(bank_monomates.into()); entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![monomates])).await.unwrap(); - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(almost_full_bank), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(almost_full_bank), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -771,7 +777,7 @@ async fn test_deposit_stacked_item_in_full_bank_with_partial_stack() { unknown: 0, })))).await.unwrap().for_each(drop); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 200); bank_items.items[199].with_stacked(|items| { assert_eq!(items.len(), 4); @@ -812,7 +818,7 @@ async fn test_deposit_meseta() { })))).await.unwrap().for_each(drop); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); - let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap(); + let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap(); assert!(c1_meseta.0 == 277); assert!(c1_bank_meseta.0 == 23); } @@ -823,7 +829,7 @@ async fn test_deposit_too_much_meseta() { let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; entity_gateway.set_character_meseta(&char1.id, item::Meseta(300)).await.unwrap(); - entity_gateway.set_bank_meseta(&char1.id, item::BankName("".into()), item::Meseta(999980)).await.unwrap(); + entity_gateway.set_bank_meseta(&char1.id, &item::BankName("".into()), item::Meseta(999980)).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -838,7 +844,7 @@ async fn test_deposit_too_much_meseta() { unknown: 0, })))).await.unwrap().for_each(drop); - ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { + let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { client: 0, target: 0, item_id: 0xFFFFFFFF, @@ -846,10 +852,12 @@ async fn test_deposit_too_much_meseta() { item_amount: 0, meseta_amount: 23, unknown: 0, - })))).await.unwrap().for_each(drop); + })))).await; + + assert!(packets.is_err()); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); - let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap(); + let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap(); assert!(c1_meseta.0 == 300); assert!(c1_bank_meseta.0 == 999980); } @@ -860,7 +868,7 @@ async fn test_deposit_meseta_when_bank_is_maxed() { let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; entity_gateway.set_character_meseta(&char1.id, item::Meseta(300)).await.unwrap(); - entity_gateway.set_bank_meseta(&char1.id, item::BankName("".into()), item::Meseta(999999)).await.unwrap(); + entity_gateway.set_bank_meseta(&char1.id, &item::BankName("".into()), item::Meseta(999999)).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -875,7 +883,7 @@ async fn test_deposit_meseta_when_bank_is_maxed() { unknown: 0, })))).await.unwrap().for_each(drop); - ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { + let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { client: 0, target: 0, item_id: 0xFFFFFFFF, @@ -883,10 +891,12 @@ async fn test_deposit_meseta_when_bank_is_maxed() { item_amount: 0, meseta_amount: 23, unknown: 0, - })))).await.unwrap().for_each(drop); + })))).await; + + assert!(packets.is_err()); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); - let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap(); + let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap(); assert!(c1_meseta.0 == 300); assert!(c1_bank_meseta.0 == 999999); } @@ -913,7 +923,7 @@ async fn test_withdraw_individual_item() { ), }).await.unwrap()); - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(bank), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(bank), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -973,7 +983,7 @@ async fn test_withdraw_stacked_item() { }).await.unwrap()); } - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![monomates]), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![monomates]), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -1003,7 +1013,7 @@ async fn test_withdraw_stacked_item() { assert!(packets.len() == 2); assert!(matches!(&packets[1], (ClientId(2), SendShipPacket::Message(Message {msg: GameMessage::CreateItem(create_item)})) - if create_item.item_id == 0x10002 + if create_item.item_id == 0x20000 )); let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); @@ -1032,7 +1042,7 @@ async fn test_withdraw_partial_stacked_item() { ), }).await.unwrap()); } - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![monomates]), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![monomates]), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -1062,10 +1072,10 @@ async fn test_withdraw_partial_stacked_item() { assert!(packets.len() == 2); assert!(matches!(&packets[1], (ClientId(2), SendShipPacket::Message(Message {msg: GameMessage::CreateItem(create_item)})) - if create_item.item_id == 0x10002 + if create_item.item_id == 0x20002 )); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 1); bank_items.items[0].with_stacked(|items| { assert_eq!(items.iter().map(|i| i.id).collect::>(), @@ -1110,7 +1120,7 @@ async fn test_withdraw_stacked_item_with_stack_already_in_inventory() { } entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![inventory_monomates])).await.unwrap(); - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![bank_monomates]), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![bank_monomates]), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -1140,10 +1150,10 @@ async fn test_withdraw_stacked_item_with_stack_already_in_inventory() { assert!(packets.len() == 2); assert!(matches!(&packets[1], (ClientId(2), SendShipPacket::Message(Message {msg: GameMessage::CreateItem(create_item)})) - if create_item.item_id == 0x10000 + if create_item.item_id == 0x20000 )); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 0); let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); @@ -1185,7 +1195,7 @@ async fn test_withdraw_stacked_item_with_full_stack_in_inventory() { } entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![inventory_monomates])).await.unwrap(); - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![bank_monomates]), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![bank_monomates]), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -1212,7 +1222,7 @@ async fn test_withdraw_stacked_item_with_full_stack_in_inventory() { assert!(packets.is_err()); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 1); bank_items.items[0].with_stacked(|items| { assert_eq!(items.iter().map(|i| i.id).collect::>(), @@ -1263,7 +1273,7 @@ async fn test_withdraw_individual_item_in_full_inventory() { } entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(inventory)).await.unwrap(); - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(bank), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(bank), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -1289,7 +1299,7 @@ async fn test_withdraw_individual_item_in_full_inventory() { })))).await; assert!(packets.is_err()); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 1); let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); @@ -1331,7 +1341,7 @@ async fn test_withdraw_stacked_item_in_full_inventory() { } entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(inventory)).await.unwrap(); - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![monomates]), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![monomates]), &item::BankName("".into())).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -1359,7 +1369,7 @@ async fn test_withdraw_stacked_item_in_full_inventory() { assert!(packets.is_err()); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert_eq!(bank_items.items.len(), 1); bank_items.items[0].with_stacked(|items| { assert_eq!(items.iter().map(|i| i.id).collect::>(), @@ -1387,7 +1397,7 @@ async fn test_withdraw_stacked_item_in_full_inventory_with_partial_stack() { ), }).await.unwrap()); } - entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![bank_item]), item::BankName("".into())).await.unwrap(); + entity_gateway.set_character_bank(&char1.id, &item::BankEntity::new(vec![bank_item]), &item::BankName("".into())).await.unwrap(); let mut items = Vec::new(); for _i in 0..29usize { @@ -1443,7 +1453,7 @@ async fn test_withdraw_stacked_item_in_full_inventory_with_partial_stack() { unknown: 0, })))).await.unwrap().for_each(drop); - let bank_items = entity_gateway.get_character_bank(&char1.id, item::BankName("".into())).await.unwrap(); + let bank_items = entity_gateway.get_character_bank(&char1.id, &item::BankName("".into())).await.unwrap(); assert!(bank_items.items.len() == 0); let inventory_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); @@ -1461,7 +1471,7 @@ async fn test_withdraw_meseta() { let mut entity_gateway = InMemoryGateway::default(); let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; - entity_gateway.set_bank_meseta(&char1.id, item::BankName("".into()), item::Meseta(300)).await.unwrap(); + entity_gateway.set_bank_meseta(&char1.id, &item::BankName("".into()), item::Meseta(300)).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -1487,7 +1497,7 @@ async fn test_withdraw_meseta() { })))).await.unwrap().for_each(drop); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); - let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap(); + let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap(); assert!(c1_meseta.0 == 23); assert!(c1_bank_meseta.0 == 277); } @@ -1498,7 +1508,7 @@ async fn test_withdraw_too_much_meseta() { let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; entity_gateway.set_character_meseta(&char1.id, item::Meseta(999980)).await.unwrap(); - entity_gateway.set_bank_meseta(&char1.id, item::BankName("".into()), item::Meseta(300)).await.unwrap(); + entity_gateway.set_bank_meseta(&char1.id, &item::BankName("".into()), item::Meseta(300)).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -1513,7 +1523,7 @@ async fn test_withdraw_too_much_meseta() { unknown: 0, })))).await.unwrap().for_each(drop); - ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { + let packet = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { client: 0, target: 0, item_id: 0xFFFFFFFF, @@ -1521,10 +1531,12 @@ async fn test_withdraw_too_much_meseta() { item_amount: 0, meseta_amount: 23, unknown: 0, - })))).await.unwrap().for_each(drop); + })))).await; + + assert!(packet.is_err()); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); - let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap(); + let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap(); assert!(c1_meseta.0 == 999980); assert!(c1_bank_meseta.0 == 300); } @@ -1535,7 +1547,7 @@ async fn test_withdraw_meseta_inventory_is_maxed() { let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await; entity_gateway.set_character_meseta(&char1.id, item::Meseta(999999)).await.unwrap(); - entity_gateway.set_bank_meseta(&char1.id, item::BankName("".into()), item::Meseta(300)).await.unwrap(); + entity_gateway.set_bank_meseta(&char1.id, &item::BankName("".into()), item::Meseta(300)).await.unwrap(); let mut ship = Box::new(ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -1550,7 +1562,7 @@ async fn test_withdraw_meseta_inventory_is_maxed() { unknown: 0, })))).await.unwrap().for_each(drop); - ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { + let packet = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BankInteraction(BankInteraction { client: 0, target: 0, item_id: 0xFFFFFFFF, @@ -1558,10 +1570,12 @@ async fn test_withdraw_meseta_inventory_is_maxed() { item_amount: 0, meseta_amount: 23, unknown: 0, - })))).await.unwrap().for_each(drop); + })))).await; + + assert!(packet.is_err()); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); - let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, item::BankName("".into())).await.unwrap(); + let c1_bank_meseta = entity_gateway.get_bank_meseta(&char1.id, &item::BankName("".into())).await.unwrap(); assert!(c1_meseta.0 == 999999); assert!(c1_bank_meseta.0 == 300); } diff --git a/tests/test_item_actions.rs b/tests/test_item_actions.rs index ab91b2f..ec216da 100644 --- a/tests/test_item_actions.rs +++ b/tests/test_item_actions.rs @@ -47,8 +47,8 @@ async fn test_equip_unit_from_equip_menu() { }).await.unwrap()); let equipped = item::EquippedEntity { - weapon: Some(p1_inv[0].id), - armor: None, + weapon: None, + armor: Some(p1_inv[0].id), shield: None, unit: [None; 4], mag: None, diff --git a/tests/test_item_pickup.rs b/tests/test_item_pickup.rs index 9520527..b2d2ce3 100644 --- a/tests/test_item_pickup.rs +++ b/tests/test_item_pickup.rs @@ -734,7 +734,7 @@ async fn test_player_drops_partial_stack_and_other_player_picks_it_up() { ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::PickupItem(PickupItem { client: 0, target: 0, - item_id: 0x00810001, + item_id: 0x10003, map_area: 0, unknown: [0; 3] })))).await.unwrap().for_each(drop); diff --git a/tests/test_item_use.rs b/tests/test_item_use.rs index 9151a0c..199dce7 100644 --- a/tests/test_item_use.rs +++ b/tests/test_item_use.rs @@ -164,7 +164,7 @@ async fn test_use_nonstackable_tool() { item::NewItemEntity { item: item::ItemDetail::Tool( item::tool::Tool { - tool: item::tool::ToolType::MagicStoneIritista, + tool: item::tool::ToolType::HuntersReport, } ), }).await.unwrap()); @@ -251,6 +251,9 @@ async fn test_use_materials() { assert!(char.materials.luck == 2); } +// TODO: tests for ALL ITEMS WOW + +/* #[async_std::test] pub async fn test_learn_new_tech() {} @@ -268,3 +271,4 @@ pub async fn test_char_cannot_learn_high_level_tech() {} #[async_std::test] pub async fn test_android_cannot_learn_tech() {} +*/ diff --git a/tests/test_shops.rs b/tests/test_shops.rs index c4c4655..c127c09 100644 --- a/tests/test_shops.rs +++ b/tests/test_shops.rs @@ -3,7 +3,7 @@ use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; use elseware::entity::item; use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket}; use elseware::ship::room::Difficulty; -use elseware::ship::items::manager::ItemManagerError; +use elseware::ship::items::state::ItemStateError; use libpso::packet::ship::*; use libpso::packet::messages::*; @@ -1142,7 +1142,7 @@ async fn test_player_cant_sell_if_meseta_would_go_over_max() { item_id: 0x10000, amount: 1, })))).await.err().unwrap(); - assert!(matches!(ack.downcast::().unwrap(), ItemManagerError::WalletFull)); + assert!(matches!(ack.downcast::().unwrap(), ItemStateError::FullOfMeseta)); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); assert_eq!(c1_meseta.0, 999995); diff --git a/tests/test_trade.rs b/tests/test_trade.rs index 65505b3..fd2c1b7 100644 --- a/tests/test_trade.rs +++ b/tests/test_trade.rs @@ -2,10 +2,11 @@ use std::convert::TryInto; use elseware::common::serverstate::{ClientId, ServerState}; use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; use elseware::entity::item; -use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket}; +use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket, ShipError}; use elseware::entity::item::{Meseta, ItemEntity}; -use elseware::ship::items::transaction::TransactionError; use elseware::ship::packet::handler::trade::TradeError; +use elseware::ship::items::state::ItemStateError; +use elseware::ship::items::inventory::InventoryError; use libpso::packet::ship::*; use libpso::packet::messages::*; @@ -190,16 +191,16 @@ async fn test_trade_one_individual_item() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 5); - assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem {..}), + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); @@ -292,15 +293,15 @@ async fn test_trade_player2_to_player1() { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 5); assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem {..}), + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem {..}), .. })))); - assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); @@ -392,16 +393,16 @@ async fn test_reverse_trade_ack_order() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 5); - assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem {..}), + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); @@ -496,16 +497,16 @@ async fn test_trade_one_stacked_item() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 5); - assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem {..}), + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); @@ -601,16 +602,16 @@ async fn test_trade_partial_stacked_item() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 5); - assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem {..}), + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); @@ -727,32 +728,31 @@ async fn test_trade_individual_both() { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 8); assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { - client: 0, - item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun - item_id: 0x810001, + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, .. }), .. })))); assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { client: 0, - item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun - item_id: 0x810001, + item_id: 0x10000, .. }), .. })))); assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 1, - item_id: 0x210000, + item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber + item_id: 0x810002, .. }), .. })))); - assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { client: 1, item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber @@ -761,19 +761,20 @@ async fn test_trade_individual_both() { }), .. })))); - assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { - client: 1, - item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber - item_id: 0x810002, + client: 0, + item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810001, .. }), .. })))); assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 0, - item_id: 0x10000, + item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810001, .. }), .. @@ -899,30 +900,30 @@ async fn test_trade_stacked_both() { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 8); assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { - client: 0, - item_id: 0x810001, + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, .. }), .. })))); assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { client: 0, - item_id: 0x810001, + item_id: 0x10000, .. }), .. })))); assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 1, - item_id: 0x210000, + item_id: 0x810002, .. }), .. })))); - assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { client: 1, item_id: 0x810002, @@ -930,18 +931,18 @@ async fn test_trade_stacked_both() { }), .. })))); - assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { - client: 1, - item_id: 0x810002, + client: 0, + item_id: 0x810001, .. }), .. })))); assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 0, - item_id: 0x10000, + item_id: 0x810001, .. }), .. @@ -1069,31 +1070,32 @@ async fn test_trade_partial_stack_both() { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 8); assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { - client: 0, - item_id: 0x810001, + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, + amount: 2, .. }), .. })))); assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { client: 0, - item_id: 0x810001, + item_id: 0x10000, + amount: 1, .. }), .. })))); assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 1, - item_id: 0x210000, - amount: 2, + item_id: 0x810002, .. }), .. })))); - assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { client: 1, item_id: 0x810002, @@ -1101,19 +1103,18 @@ async fn test_trade_partial_stack_both() { }), .. })))); - assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { - client: 1, - item_id: 0x810002, + client: 0, + item_id: 0x810001, .. }), .. })))); assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 0, - item_id: 0x10000, - amount: 1, + item_id: 0x810001, .. }), .. @@ -1245,31 +1246,32 @@ async fn test_trade_same_stacked_item_to_eachother() { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 8); assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { - client: 0, - item_id: 0x810001, + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, + amount: 3, .. }), .. })))); assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { client: 0, - item_id: 0x810001, + item_id: 0x10000, + amount: 1, .. }), .. })))); assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 1, - item_id: 0x210000, - amount: 3, + item_id: 0x810002, .. }), .. })))); - assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { client: 1, item_id: 0x810002, @@ -1277,19 +1279,18 @@ async fn test_trade_same_stacked_item_to_eachother() { }), .. })))); - assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { - client: 1, - item_id: 0x810002, + client: 0, + item_id: 0x810001, .. }), .. })))); assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 0, - item_id: 0x10000, - amount: 1, + item_id: 0x810001, .. }), .. @@ -1407,16 +1408,16 @@ async fn test_trade_stacked_when_already_have_partial_stack() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 5); - assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { - client: 1, - item_id: 0x810001, - item_data: [3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 0, + item_id: 0x10000, + amount: 2, .. }), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { client: 1, item_id: 0x810001, @@ -1426,10 +1427,10 @@ async fn test_trade_stacked_when_already_have_partial_stack() { .. })))); assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { - client: 0, - item_id: 0x10000, - amount: 2, + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810001, + item_data: [3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], .. }), .. @@ -1554,32 +1555,31 @@ async fn test_trade_individual_for_stacked() { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 8); assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { - client: 0, - item_data: [3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], - item_id: 0x810001, + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, .. }), .. })))); assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { client: 0, - item_data: [3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], - item_id: 0x810001, + item_id: 0x10000, .. }), .. })))); assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 1, - item_id: 0x210000, + item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + item_id: 0x810002, .. }), .. })))); - assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { client: 1, item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], @@ -1588,19 +1588,20 @@ async fn test_trade_individual_for_stacked() { }), .. })))); - assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { - client: 1, - item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - item_id: 0x810002, + client: 0, + item_data: [3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], + item_id: 0x810001, .. }), .. })))); assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 0, - item_id: 0x10000, + item_data: [3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], + item_id: 0x810001, .. }), .. @@ -1758,53 +1759,51 @@ async fn test_trade_multiple_individual() { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 14); assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { - client: 0, - item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun - item_id: 0x810001, + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, .. }), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { - client: 0, - item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun - item_id: 0x810001, + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210001, .. }), .. })))); - assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { - client: 1, - item_id: 0x210000, + client: 0, + item_id: 0x10000, .. }), .. })))); - assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { client: 0, - item_data: [0, 6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun - item_id: 0x810002, + item_id: 0x10001, .. }), .. })))); - assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { - client: 0, - item_data: [0, 6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun - item_id: 0x810002, + client: 1, + item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber + item_id: 0x810003, .. }), .. })))); - assert!(matches!(ack[5], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { client: 1, - item_id: 0x210001, + item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber + item_id: 0x810003, .. }), .. @@ -1812,8 +1811,8 @@ async fn test_trade_multiple_individual() { assert!(matches!(ack[6], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { client: 1, - item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber - item_id: 0x810003, + item_data: [0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber + item_id: 0x810004, .. }), .. @@ -1821,42 +1820,44 @@ async fn test_trade_multiple_individual() { assert!(matches!(ack[7], (ClientId(2), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { client: 1, - item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber - item_id: 0x810003, + item_data: [0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber + item_id: 0x810004, .. }), .. })))); - assert!(matches!(ack[8], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + assert!(matches!(ack[8], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { client: 0, - item_id: 0x10000, + item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810001, .. }), .. })))); - assert!(matches!(ack[9], (ClientId(1), SendShipPacket::Message(Message { + assert!(matches!(ack[9], (ClientId(2), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { - client: 1, - item_data: [0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber - item_id: 0x810004, + client: 0, + item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810001, .. }), .. })))); - assert!(matches!(ack[10], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[10], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { - client: 1, - item_data: [0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber - item_id: 0x810004, + client: 0, + item_data: [0, 6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810002, .. }), .. })))); assert!(matches!(ack[11], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 0, - item_id: 0x10001, + item_data: [0, 6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810002, .. }), .. @@ -2030,49 +2031,49 @@ async fn test_trade_multiple_stacked() { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 14); assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { - client: 0, - item_id: 0x810001, + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, .. }), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { - client: 0, - item_id: 0x810001, + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210001, .. }), .. })))); - assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { - client: 1, - item_id: 0x210000, + client: 0, + item_id: 0x10000, .. }), .. })))); - assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem { + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { client: 0, - item_id: 0x810002, + item_id: 0x10001, .. }), .. })))); - assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { - client: 0, - item_id: 0x810002, + client: 1, + item_id: 0x810003, .. }), .. })))); - assert!(matches!(ack[5], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { client: 1, - item_id: 0x210001, + item_id: 0x810003, .. }), .. @@ -2080,7 +2081,7 @@ async fn test_trade_multiple_stacked() { assert!(matches!(ack[6], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { client: 1, - item_id: 0x810003, + item_id: 0x810004, .. }), .. @@ -2088,39 +2089,39 @@ async fn test_trade_multiple_stacked() { assert!(matches!(ack[7], (ClientId(2), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { client: 1, - item_id: 0x810003, + item_id: 0x810004, .. }), .. })))); - assert!(matches!(ack[8], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + assert!(matches!(ack[8], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { client: 0, - item_id: 0x10000, + item_id: 0x810001, .. }), .. })))); - assert!(matches!(ack[9], (ClientId(1), SendShipPacket::Message(Message { + assert!(matches!(ack[9], (ClientId(2), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { - client: 1, - item_id: 0x810004, + client: 0, + item_id: 0x810001, .. }), .. })))); - assert!(matches!(ack[10], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[10], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem { - client: 1, - item_id: 0x810004, + client: 0, + item_id: 0x810002, .. }), .. })))); assert!(matches!(ack[11], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + msg: GameMessage::CreateItem(CreateItem { client: 0, - item_id: 0x10001, + item_id: 0x810002, .. }), .. @@ -2251,12 +2252,7 @@ async fn test_trade_not_enough_inventory_space_individual() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.err().unwrap(); - match ack.downcast::>().unwrap() { - TransactionError::Action(a) => { - assert_eq!(a.downcast::().unwrap(), TradeError::NoInventorySpace); - }, - _ => panic!() - } + assert!(matches!(ack.downcast::().unwrap(), ItemStateError::InventoryError(InventoryError::InventoryFull))); let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); assert_eq!(p1_items.items.len(), 2); @@ -2368,12 +2364,7 @@ async fn test_trade_not_enough_inventory_space_stacked() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.err().unwrap(); - match ack.downcast::>().unwrap() { - TransactionError::Action(a) => { - assert_eq!(a.downcast::().unwrap(), TradeError::NoInventorySpace); - }, - _ => panic!() - } + assert!(matches!(ack.downcast::().unwrap(), ItemStateError::InventoryError(InventoryError::InventoryFull))); let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); assert_eq!(p1_items.items.len(), 1); @@ -2482,12 +2473,7 @@ async fn test_trade_stack_too_big() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.err().unwrap(); - match ack.downcast::>().unwrap() { - TransactionError::Action(a) => { - assert_eq!(a.downcast::().unwrap(), TradeError::NoStackSpace); - }, - _ => panic!() - } + assert!(matches!(ack.downcast::().unwrap(), ItemStateError::InventoryError(InventoryError::StackFull))); let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); assert_eq!(p1_items.items.len(), 1); @@ -2557,16 +2543,16 @@ async fn test_trade_meseta() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 5); - assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem {..}), + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); @@ -3109,12 +3095,7 @@ async fn test_invalid_trade_when_both_inventories_are_full() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.err().unwrap(); - match ack.downcast::>().unwrap() { - TransactionError::Action(a) => { - assert_eq!(a.downcast::().unwrap(), TradeError::NoInventorySpace); - }, - _ => panic!() - } + assert!(matches!(ack.downcast::().unwrap(), ItemStateError::InventoryError(InventoryError::InventoryFull))); let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); assert_eq!(p1_items.items.len(), 30); @@ -3124,6 +3105,7 @@ async fn test_invalid_trade_when_both_inventories_are_full() { assert_eq!(p2_items.items.iter().filter(|i| matches!(i.individual().unwrap().item, item::ItemDetail::Weapon(item::weapon::Weapon { weapon: item::weapon::WeaponType::Handgun, ..}, ..))).count(), 30); } + #[async_std::test] async fn test_client_tries_to_start_two_trades() { let mut entity_gateway = InMemoryGateway::default(); @@ -3153,7 +3135,8 @@ async fn test_client_tries_to_start_two_trades() { target: 0, trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0) })))).await.err().unwrap(); - assert_eq!(ack.downcast::().unwrap(), TradeError::ClientAlreadyInTrade); + + assert!(matches!(ack.downcast::().unwrap(), ShipError::TradeError(TradeError::ClientAlreadyInTrade))); } #[async_std::test] @@ -3185,14 +3168,14 @@ async fn test_client_tries_trading_with_client_already_trading() { target: 0, trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0) })))).await.err().unwrap(); - assert_eq!(ack.downcast::().unwrap(), TradeError::OtherAlreadyInTrade); + assert!(matches!(ack.downcast::().unwrap(), ShipError::TradeError(TradeError::OtherAlreadyInTrade))); let ack = ship.handle(ClientId(3), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { client: 2, target: 0, trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 1) })))).await.err().unwrap(); - assert_eq!(ack.downcast::().unwrap(), TradeError::OtherAlreadyInTrade); + assert!(matches!(ack.downcast::().unwrap(), ShipError::TradeError(TradeError::OtherAlreadyInTrade))); } #[async_std::test] @@ -3287,16 +3270,16 @@ async fn test_add_then_remove_individual_item() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 5); - assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem {..}), + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); @@ -3418,16 +3401,16 @@ async fn test_add_then_remove_stacked_item() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 5); - assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem {..}), + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); @@ -3632,16 +3615,16 @@ async fn test_add_then_remove_meseta() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 5); - assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem {..}), + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); @@ -4349,16 +4332,16 @@ async fn test_dropping_item_after_trade() { let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { })).await.unwrap().collect::>(); assert_eq!(ack.len(), 5); - assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { - msg: GameMessage::CreateItem(CreateItem {..}), + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), .. })))); - assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::Message(Message { msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { - msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + msg: GameMessage::CreateItem(CreateItem {..}), .. })))); assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..})));