kill_counters #109

Closed
andy wants to merge 80 commits from kill_counters into master
  1. 354
      Cargo.lock
  2. 5
      Cargo.toml
  3. 48
      src/bin/main.rs
  4. 2
      src/common/leveltable.rs
  5. 58
      src/entity/gateway/entitygateway.rs
  6. 223
      src/entity/gateway/inmemory.rs
  7. 2
      src/entity/gateway/mod.rs
  8. 5
      src/entity/gateway/postgres/migrations/V0005__trade.sql
  9. 69
      src/entity/gateway/postgres/models.rs
  10. 1162
      src/entity/gateway/postgres/postgres.rs
  11. 4
      src/entity/item/armor.rs
  12. 4
      src/entity/item/esweapon.rs
  13. 33
      src/entity/item/mag.rs
  14. 52
      src/entity/item/mod.rs
  15. 2
      src/entity/item/shield.rs
  16. 2
      src/entity/item/tool.rs
  17. 36
      src/entity/item/unit.rs
  18. 129
      src/entity/item/weapon.rs
  19. 1
      src/lib.rs
  20. 68
      src/login/character.rs
  21. 13
      src/login/login.rs
  22. 12
      src/ship/character.rs
  23. 2
      src/ship/drops/generic_unit.rs
  24. 5
      src/ship/drops/generic_weapon.rs
  25. 16
      src/ship/drops/mod.rs
  26. 28
      src/ship/drops/rare_drop_table.rs
  27. 852
      src/ship/items/actions.rs
  28. 293
      src/ship/items/apply_item.rs
  29. 450
      src/ship/items/bank.rs
  30. 294
      src/ship/items/floor.rs
  31. 1080
      src/ship/items/inventory.rs
  32. 137
      src/ship/items/itemstateaction.rs
  33. 145
      src/ship/items/manager.rs
  34. 23
      src/ship/items/mod.rs
  35. 391
      src/ship/items/state.rs
  36. 500
      src/ship/items/tasks.rs
  37. 337
      src/ship/items/transaction.rs
  38. 163
      src/ship/items/use_tool.rs
  39. 30
      src/ship/location.rs
  40. 2
      src/ship/map/area.rs
  41. 10
      src/ship/map/maps.rs
  42. 2
      src/ship/map/variant.rs
  43. 10
      src/ship/packet/builder/lobby.rs
  44. 52
      src/ship/packet/builder/message.rs
  45. 9
      src/ship/packet/builder/mod.rs
  46. 8
      src/ship/packet/builder/room.rs
  47. 6
      src/ship/packet/handler/auth.rs
  48. 135
      src/ship/packet/handler/direct_message.rs
  49. 27
      src/ship/packet/handler/lobby.rs
  50. 153
      src/ship/packet/handler/message.rs
  51. 12
      src/ship/packet/handler/room.rs
  52. 152
      src/ship/packet/handler/trade.rs
  53. 6
      src/ship/room.rs
  54. 109
      src/ship/ship.rs
  55. 2
      src/ship/shops/armor.rs
  56. 1
      src/ship/shops/weapon.rs
  57. 10
      src/ship/trade.rs
  58. 40
      tests/common.rs
  59. 135
      tests/test_bank.rs
  60. 10
      tests/test_item_actions.rs
  61. 7
      tests/test_item_pickup.rs
  62. 6
      tests/test_item_use.rs
  63. 2
      tests/test_rooms.rs
  64. 11
      tests/test_shops.rs
  65. 467
      tests/test_trade.rs
  66. 272
      tests/test_unseal_items.rs

354
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"

5
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"

48
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(
@ -88,6 +88,7 @@ fn main() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap();
@ -108,13 +109,14 @@ fn main() {
NewItemEntity {
item: ItemDetail::Weapon(
item::weapon::Weapon {
weapon: item::weapon::WeaponType::Raygun,
weapon: item::weapon::WeaponType::SealedJSword,
grind: 5,
special: Some(item::weapon::WeaponSpecial::Hell),
special: None,
attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 40}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 30}),
None,],
tekked: false,
tekked: true,
kills: Some(22998),
}
),
}).await.unwrap();
@ -122,13 +124,14 @@ fn main() {
NewItemEntity {
item: ItemDetail::Weapon(
item::weapon::Weapon {
weapon: item::weapon::WeaponType::Handgun,
grind: 5,
weapon: item::weapon::WeaponType::Club,
grind: 10,
special: Some(item::weapon::WeaponSpecial::Charge),
attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 40}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 30}),
None,],
tekked: true,
kills: None,
}
),
}).await.unwrap();
@ -143,6 +146,7 @@ fn main() {
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
None,],
tekked: true,
kills: None,
}
),
}).await.unwrap();
@ -157,6 +161,7 @@ fn main() {
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
None,],
tekked: true,
kills: None,
}
),
}).await.unwrap();
@ -171,6 +176,7 @@ fn main() {
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Native, value: 100}),],
tekked: true,
kills: None,
}
),
}).await.unwrap();
@ -197,7 +203,7 @@ fn main() {
item::NewItemEntity {
item: ItemDetail::Tool (
item::tool::Tool {
tool: item::tool::ToolType::CellOfMag502,
tool: item::tool::ToolType::MagicRockMoola,
}
),
}).await.unwrap();
@ -213,8 +219,17 @@ fn main() {
let item6_1 = entity_gateway.create_item(
NewItemEntity {
item: ItemDetail::ESWeapon(
item::esweapon::ESWeapon::new(item::esweapon::ESWeaponType::Saber)
item: ItemDetail::Weapon(
item::weapon::Weapon {
weapon: item::weapon::WeaponType::Autogun,
grind: 5,
special: Some(item::weapon::WeaponSpecial::Hell),
attrs: [Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Hit, value: 70}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 80}),
None,],
tekked: false,
kills: None,
}
),
}).await.unwrap();
let item7_a = entity_gateway.create_item(
@ -244,8 +259,9 @@ fn main() {
NewItemEntity {
item: ItemDetail::Unit(
item::unit::Unit {
unit: item::unit::UnitType::PriestMind,
modifier: Some(item::unit::UnitModifier::PlusPlus),
unit: item::unit::UnitType::Limiter,
modifier: None,
kills: Some(19999),
}
),
}
@ -255,7 +271,8 @@ fn main() {
item: ItemDetail::Unit(
item::unit::Unit {
unit: item::unit::UnitType::PriestMind,
modifier: Some(item::unit::UnitModifier::Plus),
modifier: Some(item::unit::UnitModifier::Minus),
kills: None,
}
),
}
@ -266,6 +283,7 @@ fn main() {
item::unit::Unit {
unit: item::unit::UnitType::PriestMind,
modifier: Some(item::unit::UnitModifier::Minus),
kills: None,
}
),
}
@ -276,6 +294,7 @@ fn main() {
item::unit::Unit {
unit: item::unit::UnitType::PriestMind,
modifier: Some(item::unit::UnitModifier::MinusMinus),
kills: None,
}
),
}
@ -298,6 +317,7 @@ fn main() {
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Native, value: 100}),],
tekked: true,
kills: None,
}
),
}).await.unwrap();
@ -327,7 +347,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");

2
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,

58
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<Box<dyn EntityGatewayTransaction + 'a>, GatewayError>
{
unimplemented!();
}
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, _func: F) -> Result<R, E>
where
Fut: Future<Output = Result<(Box<dyn EntityGatewayTransaction + 'a>, R), E>> + Send + 'a,
F: FnOnce(Box<dyn EntityGatewayTransaction + 'a>) -> Fut + Send,
R: Send,
E: From<GatewayError>,
Self: Sized
{
unimplemented!();
}
async fn create_user(&mut self, _user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
unimplemented!()
}
async fn get_user_by_id(&self, _id: UserAccountId) -> Result<UserAccountEntity, GatewayError> {
async fn get_user_by_id(&mut self, _id: UserAccountId) -> Result<UserAccountEntity, GatewayError> {
unimplemented!();
}
async fn get_user_by_name(&self, _username: String) -> Result<UserAccountEntity, GatewayError> {
async fn get_user_by_name(&mut self, _username: String) -> Result<UserAccountEntity, GatewayError> {
unimplemented!();
}
@ -36,7 +55,7 @@ pub trait EntityGateway: Send + Sync + Clone {
unimplemented!();
}
async fn get_user_settings_by_user(&self, _user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
async fn get_user_settings_by_user(&mut self, _user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
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<CharacterEntity>; 4], GatewayError> {
async fn get_characters_by_user(&mut self, _user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 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<GuildCardDataEntity, GatewayError> {
async fn get_guild_card_data_by_user(&mut self, _user: &UserAccountEntity) -> Result<GuildCardDataEntity, GatewayError> {
unimplemented!();
}
@ -85,6 +104,9 @@ pub trait EntityGateway: Send + Sync + Clone {
unimplemented!();
}
async fn add_unit_modifier(&mut self, _item_id: &ItemEntityId, _modifier: unit::UnitModifier) -> Result<(), GatewayError> {
unimplemented!();
}
/*
async fn get_items_by_character(&self, _char_id: &CharacterEntityId) -> Result<Vec<ItemEntity>, GatewayError> {
@ -96,7 +118,7 @@ pub trait EntityGateway: Send + Sync + Clone {
unimplemented!();
}
async fn get_character_bank(&mut self, _char_id: &CharacterEntityId, _bank_name: BankName) -> Result<BankEntity, GatewayError> {
async fn get_character_bank(&mut self, _char_id: &CharacterEntityId, _bank_name: &BankName) -> Result<BankEntity, GatewayError> {
unimplemented!();
}
@ -104,7 +126,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 +146,27 @@ pub trait EntityGateway: Send + Sync + Clone {
unimplemented!();
}
async fn get_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: BankName) -> Result<Meseta, GatewayError> {
async fn get_bank_meseta(&mut self, _char_id: &CharacterEntityId, _bank: &BankName) -> Result<Meseta, GatewayError> {
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<TradeEntity, GatewayError> {
unimplemented!();
}
}
#[async_trait::async_trait]
pub trait EntityGatewayTransaction: Send + Sync {
fn gateway(&mut self) -> &mut dyn EntityGateway {
unimplemented!()
}
async fn commit(self: Box<Self>) -> Result<(), GatewayError> {
unimplemented!()
}
}

223
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<Self>) -> 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<Mutex<BTreeMap<UserAccountId, UserAccountEntity>>>,
@ -21,6 +76,9 @@ pub struct InMemoryGateway {
equips: Arc<Mutex<BTreeMap<CharacterEntityId, EquippedEntity>>>,
mag_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<mag::MagModifier>>>>,
weapon_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<weapon::WeaponModifier>>>>,
unit_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<unit::UnitModifier>>>>,
trades: Arc<Mutex<Vec<TradeEntity>>>,
unit_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<unit::UnitModifier>>>>,
}
impl Default for InMemoryGateway {
@ -37,6 +95,9 @@ 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())),
unit_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
trades: Arc::new(Mutex::new(Vec::new())),
unit_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
}
}
}
@ -50,6 +111,9 @@ impl InMemoryGateway {
item.item = match item.item {
ItemDetail::Weapon(mut weapon) => {
if let Some(weapon_modifiers) = self.weapon_modifiers.lock().unwrap().get(&item.id) {
if weapon.weapon.has_counter() {
weapon.kills = Some(0);
}
for weapon_modifier in weapon_modifiers.iter() {
weapon.apply_modifier(weapon_modifier);
}
@ -74,7 +138,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()
}
}
},
@ -83,7 +147,18 @@ impl InMemoryGateway {
}
}
ItemDetail::Mag(mag)
}
},
ItemDetail::Unit(mut unit) => {
if let Some(unit_modifiers) = self.unit_modifiers.lock().unwrap().get(&item.id) {
if unit.unit.has_counter() {
unit.kills = Some(0);
}
for unit_modifier in unit_modifiers.iter() {
unit.apply_modifier(unit_modifier);
}
}
ItemDetail::Unit(unit)
},
_ => {
item.item
}
@ -99,6 +174,91 @@ impl InMemoryGateway {
#[async_trait::async_trait]
impl EntityGateway for InMemoryGateway {
async fn transaction<'a>(&'a mut self) -> Result<Box<dyn EntityGatewayTransaction + 'a>, 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<R, E>
where
Fut: Future<Output = Result<(Box<dyn EntityGatewayTransaction + 'a>, R), E>> + Send + 'a,
F: FnOnce(Box<dyn EntityGatewayTransaction + 'a>) -> Fut + Send,
R: Send,
E: From<GatewayError>,
{
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<UserAccountEntity, GatewayError> {
let mut users = self.users.lock().unwrap();
let id = users
@ -124,12 +284,12 @@ impl EntityGateway for InMemoryGateway {
Ok(user)
}
async fn get_user_by_id(&self, id: UserAccountId) -> Result<UserAccountEntity, GatewayError> {
async fn get_user_by_id(&mut self, id: UserAccountId) -> Result<UserAccountEntity, GatewayError> {
let users = self.users.lock().unwrap();
users.get(&id).cloned().ok_or(GatewayError::Error)
}
async fn get_user_by_name(&self, username: String) -> Result<UserAccountEntity, GatewayError> {
async fn get_user_by_name(&mut self, username: String) -> Result<UserAccountEntity, GatewayError> {
let users = self.users.lock().unwrap();
users
.iter()
@ -159,7 +319,7 @@ impl EntityGateway for InMemoryGateway {
Ok(new_settings)
}
async fn get_user_settings_by_user(&self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
let user_settings = self.user_settings.lock().unwrap();
user_settings
.iter()
@ -168,7 +328,7 @@ impl EntityGateway for InMemoryGateway {
.ok_or(GatewayError::Error)
}
async fn get_characters_by_user(&self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> {
async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> {
let characters = self.characters.lock().unwrap();
const NONE: Option<CharacterEntity> = None;
let mut chars = [NONE; 4];
@ -215,7 +375,7 @@ impl EntityGateway for InMemoryGateway {
Ok(())
}
async fn get_guild_card_data_by_user(&self, user: &UserAccountEntity) -> Result<GuildCardDataEntity, GatewayError> {
async fn get_guild_card_data_by_user(&mut self, user: &UserAccountEntity) -> Result<GuildCardDataEntity, GatewayError> {
Ok(GuildCardDataEntity::new(user.id))
}
@ -271,6 +431,14 @@ impl EntityGateway for InMemoryGateway {
Ok(())
}
async fn add_unit_modifier(&mut self, item_id: &ItemEntityId, modifier: unit::UnitModifier) -> Result<(), GatewayError> {
self.unit_modifiers.lock().unwrap()
.entry(*item_id)
.or_insert_with(Vec::new)
.push(modifier);
Ok(())
}
async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> {
let inventories = self.inventories.lock().unwrap();
Ok(inventories
@ -281,7 +449,7 @@ impl EntityGateway for InMemoryGateway {
.unwrap_or_default())
}
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, _bank_name: BankName) -> Result<BankEntity, GatewayError> {
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, _bank_name: &BankName) -> Result<BankEntity, GatewayError> {
let banks = self.banks.lock().unwrap();
Ok(banks
.iter()
@ -297,7 +465,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 +502,46 @@ 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<Meseta, GatewayError> {
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
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<TradeEntity, GatewayError> {
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)
}
async fn increment_kill_counter(&mut self, item_id: &ItemEntityId) -> Result<(), GatewayError> {
if let Some(item_entity) = self.items.lock().unwrap().get_mut(item_id) {
item_entity.increase_kill_counter();
}
Ok(())
}
async fn get_kill_counter() {
println!("src/entity/gateway/inmemory.rs::get_kill_counter() - unimplemented!");
}
async fn set_kill_counter() {
println!("src/entity/gateway/inmemory.rs::set_kill_counter() - unimplemented!");
}
}

2
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;

5
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,
);

69
src/entity/gateway/postgres/models.rs

@ -291,6 +291,7 @@ pub struct PgWeapon {
grind: u8,
attrs: HashMap<weapon::Attribute, i8>,
tekked: bool,
kills: Option<u16>,
}
impl From<weapon::Weapon> for PgWeapon {
@ -301,6 +302,7 @@ impl From<weapon::Weapon> for PgWeapon {
grind: other.grind,
attrs: other.attrs.iter().flatten().map(|attr| (attr.attr, attr.value)).collect(),
tekked: other.tekked,
kills: other.kills,
}
}
}
@ -321,6 +323,7 @@ impl From<PgWeapon> for weapon::Weapon {
grind: other.grind,
attrs,
tekked: other.tekked,
kills: other.kills,
}
}
}
@ -392,6 +395,7 @@ impl From<PgShield> for shield::Shield {
pub struct PgUnit {
unit: unit::UnitType,
modifier: Option<unit::UnitModifier>,
kills: Option<u16>,
}
impl From<unit::Unit> for PgUnit {
@ -399,6 +403,7 @@ impl From<unit::Unit> for PgUnit {
PgUnit {
unit: other.unit,
modifier: other.modifier,
kills: other.kills,
}
}
}
@ -408,6 +413,7 @@ impl From<PgUnit> for unit::Unit {
unit::Unit {
unit: other.unit,
modifier: other.modifier,
kills: other.kills,
}
}
}
@ -591,6 +597,7 @@ pub enum PgItemNoteDetail {
character_id: u32,
},
PlayerDrop {
character_id: u32,
map_area: MapArea,
x: f32,
y: f32,
@ -605,10 +612,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<ItemNote> for PgItemNoteDetail {
@ -625,7 +640,8 @@ impl From<ItemNote> 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 +653,22 @@ impl From<ItemNote> 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 +688,8 @@ impl From<PgItemNoteDetail> 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 +701,19 @@ impl From<PgItemNoteDetail> 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 +864,19 @@ impl From<(CharacterEntityId, EquippedEntity)> for PgEquipped {
}
}
#[derive(Debug, sqlx::FromRow)]
pub struct PgTradeEntity {
id: i32,
character1: i32,
character2: i32,
}
impl From<PgTradeEntity> 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),
}
}
}

1162
src/entity/gateway/postgres/postgres.rs
File diff suppressed because it is too large
View File

4
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,

4
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<ESWeaponSpecial>,

33
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<ToolType> for MagCell {
type Error = ();
type Error = MagCellError;
fn try_from(tool: ToolType) -> Result<MagCell, ()> {
fn try_from(tool: ToolType) -> Result<MagCell, MagCellError> {
match tool {
ToolType::CellOfMag502 => Ok(MagCell::CellOfMag502),
ToolType::CellOfMag213 => Ok(MagCell::CellOfMag213),
@ -448,7 +449,7 @@ impl std::convert::TryFrom<ToolType> 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

52
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),
@ -169,6 +179,19 @@ impl ItemDetail {
_ => None,
}
}
pub fn has_kill_counter(self) -> bool {
match self {
ItemDetail::Weapon(w) => w.kills.is_some(),
ItemDetail::Armor(_a) => false,
ItemDetail::Shield(_s) => false,
ItemDetail::Unit(u) => u.kills.is_some(),
ItemDetail::Tool(_t) => false,
ItemDetail::TechniqueDisk(_d) => false,
ItemDetail::Mag(_m) => false,
ItemDetail::ESWeapon(_e) => false,
}
}
}
#[derive(Clone, Debug)]
@ -176,13 +199,12 @@ 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,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InventoryItemEntity {
Individual(ItemEntity),
@ -317,3 +339,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),
}

2
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,

2
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,
}

36
src/entity/item/unit.rs

@ -1,4 +1,6 @@
use serde::{Serialize, Deserialize};
use crate::ship::monster::MonsterType;
#[derive(Debug, Copy, Clone)]
pub enum ItemParseError {
@ -321,20 +323,29 @@ impl UnitType {
_ => Err(ItemParseError::InvalidUnitType),
}
}
pub fn has_counter(&self) -> bool {
matches!(self, UnitType::Limiter)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnitModifier {
PlusPlus,
Plus,
Minus,
MinusMinus,
AddKill {
enemy: MonsterType,
// attack: u32, // maybe one day for TURBO logging?
},
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Unit {
pub unit: UnitType,
pub modifier: Option<UnitModifier>,
pub kills: Option<u16>,
}
@ -359,13 +370,19 @@ impl Unit {
result[6] = 0xFE;
result[7] = 0xFF;
},
_ => {},
}
}
if self.unit.has_counter() {
result[10..12].copy_from_slice(&self.kills.unwrap_or(0u16).to_be_bytes());
result[10] += 0x80;
}
result
}
pub fn from_bytes(data: [u8; 16]) -> Result<Unit, ItemParseError> {
let u = UnitType::parse_type([data[0], data[1], data[2]]);
let u = UnitType::parse_type([data[0], data[1], data[2]]);
let mut k = None;
if let Ok(u) = u {
let m = match u16::from_le_bytes([data[6], data[7]]) {
0x02 => Some(UnitModifier::PlusPlus),
@ -375,9 +392,14 @@ impl Unit {
_ => None,
};
if data[10] & 0x80 == 0x80 {
k = Some(u16::from_be_bytes([data[10] - 0x80, data[11]]));
}
Ok(Unit{
unit: u,
modifier: m,
kills: k,
})
}
else {
@ -456,4 +478,12 @@ impl Unit {
_ => 0,
}
}
pub fn apply_modifier(&mut self, modifier: &UnitModifier) {
if let UnitModifier::AddKill{enemy: _} = modifier {
if let Some(kills) = self.kills {
self.kills = Some(kills + 1);
}
};
}
}

129
src/entity/item/weapon.rs

@ -1,4 +1,5 @@
use crate::entity::item::ItemEntityId;
use crate::ship::monster::MonsterType;
use serde::{Serialize, Deserialize};
#[derive(Debug, Copy, Clone)]
@ -10,7 +11,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 +33,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 +46,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,
@ -1421,17 +1422,21 @@ impl WeaponType {
_ => Err(ItemParseError::InvalidWeaponType),
}
}
pub fn has_counter(&self) -> bool {
matches!(self, WeaponType::SealedJSword | WeaponType::LameDArgent)
}
}
#[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 +1445,7 @@ pub enum TekPercentModifier {
MinusMinus,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum WeaponModifier {
AddPercents {
attr: WeaponAttribute,
@ -1455,15 +1460,20 @@ pub enum WeaponModifier {
percent: TekPercentModifier,
grind: i32,
},
AddKill {
enemy: MonsterType,
// attack: u32, // maybe one day for TURBO logging?
},
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Weapon {
pub weapon: WeaponType,
pub special: Option<WeaponSpecial>,
pub grind: u8,
pub attrs: [Option<WeaponAttribute>; 3],
pub tekked: bool,
pub kills: Option<u16>,
}
@ -1475,49 +1485,60 @@ impl Weapon {
grind: 0,
attrs: [None; 3],
tekked: true,
kills: None,
}
}
// TODO: apply other modifiers
pub fn apply_modifier(&mut self, modifier: &WeaponModifier) {
if let WeaponModifier::Tekked{special, percent, grind} = modifier {
match special {
TekSpecialModifier::Plus => {
self.special = self.special.map(|special| {
special.rank_up()
});
},
TekSpecialModifier::Minus => {
self.special = self.special.map(|special| {
special.rank_down()
});
},
TekSpecialModifier::Neutral => {
},
}
for i in 0..3 {
self.attrs[i] = self.attrs[i].map(|mut attr| {
match percent {
TekPercentModifier::PlusPlus => {
attr.value += 10;
},
TekPercentModifier::Plus => {
attr.value += 5;
},
TekPercentModifier::MinusMinus => {
attr.value -= 10;
},
TekPercentModifier::Minus => {
attr.value -= 5;
},
TekPercentModifier::Neutral => {
match modifier {
WeaponModifier::AddPercents{attr: _, pds: _} => {},
WeaponModifier::AddGrind{amount: _, grinder: _} => {},
WeaponModifier::Tekked{special, percent, grind} => {
match special {
TekSpecialModifier::Plus => {
self.special = self.special.map(|special| {
special.rank_up()
});
},
TekSpecialModifier::Minus => {
self.special = self.special.map(|special| {
special.rank_down()
});
},
TekSpecialModifier::Neutral => {
},
}
for i in 0..3 {
self.attrs[i] = self.attrs[i].map(|mut attr| {
match percent {
TekPercentModifier::PlusPlus => {
attr.value += 10;
},
TekPercentModifier::Plus => {
attr.value += 5;
},
TekPercentModifier::MinusMinus => {
attr.value -= 10;
},
TekPercentModifier::Minus => {
attr.value -= 5;
},
TekPercentModifier::Neutral => {
}
}
}
attr
});
}
self.grind = std::cmp::max(self.grind as i32 + grind, 0) as u8;
self.tekked = true;
}
attr
});
}
self.grind = std::cmp::max(self.grind as i32 + grind, 0) as u8;
self.tekked = true;
},
WeaponModifier::AddKill{enemy: _} => {
if let Some(kills) = self.kills {
self.kills = Some(kills + 1);
}
},
};
}
pub fn as_bytes(&self) -> [u8; 16] {
@ -1530,9 +1551,14 @@ impl Weapon {
result[4] += 0x80
};
result[6..8].copy_from_slice(&self.attrs[0].map(|s| s.value()).unwrap_or([0,0]));
result[8..10].copy_from_slice(&self.attrs[1].map(|s| s.value()).unwrap_or([0,0]));
result[10..12].copy_from_slice(&self.attrs[2].map(|s| s.value()).unwrap_or([0,0]));
result[6..8].copy_from_slice(&self.attrs[0].map(|s| s.value()).unwrap_or([0,0]));
result[8..10].copy_from_slice(&self.attrs[1].map(|s| s.value()).unwrap_or([0,0]));
if self.weapon.has_counter() {
result[10..12].copy_from_slice(&self.kills.unwrap_or(0u16).to_be_bytes());
result[10] += 0x80;
} else {
result[10..12].copy_from_slice(&self.attrs[2].map(|s| s.value()).unwrap_or([0,0]));
}
result
}
@ -1543,6 +1569,7 @@ impl Weapon {
if let Ok(weapon) = wep {
let mut special = None;
let mut tekked = true;
let mut kills = None;
let grind = data[3];
if data[4] >= 0x81 && data[4] <= 0xA8 {
@ -1575,12 +1602,18 @@ impl Weapon {
}
}
if data[10] & 0x80 == 0x80 {
attrs[2] = None;
kills = Some(u16::from_be_bytes([data[10] - 0x80, data[11]]));
}
Ok(Weapon {
weapon,
special,
grind,
attrs,
tekked,
kills,
})
}
else {

1
src/lib.rs

@ -3,6 +3,7 @@
#![feature(drain_filter)]
#![feature(try_blocks)]
extern crate fix_hidden_lifetime_bug;
pub mod common;

68
src/login/character.rs

@ -220,6 +220,7 @@ async fn new_character<EG: EntityGateway>(entity_gateway: &mut EG, user: &UserAc
special: None,
attrs: [None; 3],
tekked: true,
kills: None,
})}).await?;
entity_gateway.add_item_note(&weapon.id, ItemNote::CharacterCreation {
@ -257,41 +258,44 @@ async fn new_character<EG: EntityGateway>(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::<Result<Vec<_>, 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 +330,7 @@ impl<EG: EntityGateway> CharacterServerState<EG> {
}
async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result<Vec<SendCharacterPacket>, 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 +813,7 @@ mod test {
#[async_trait::async_trait]
impl EntityGateway for TestData {
async fn get_user_settings_by_user(&self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
Ok(UserSettingsEntity {
id: UserSettingsId(0),
user_id: user.id,
@ -865,7 +869,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),

13
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<UserAccountEntity, AccountStatus> {
// TODO: MORE impl EntityGateway?
pub async fn get_login_status(entity_gateway: &mut impl EntityGateway, pkt: &Login) -> Result<UserAccountEntity, AccountStatus> {
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<EG: EntityGateway> LoginServerState<EG> {
}
async fn validate_login(&mut self, id: ClientId, pkt: &Login) -> Result<Vec<SendLoginPacket>, 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<UserAccountEntity, GatewayError> {
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
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<UserAccountEntity, GatewayError> {
async fn get_user_by_name(&mut self, _name: String) -> Result<UserAccountEntity, GatewayError> {
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<UserAccountEntity, GatewayError> {
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
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<UserAccountEntity, GatewayError> {
async fn get_user_by_name(&mut self, name: String) -> Result<UserAccountEntity, GatewayError> {
assert!(name == "testuser");
Ok(UserAccountEntity {
id: UserAccountId(1),

12
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<u32>,
meseta: Option<Meseta>,
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

2
src/ship/drops/generic_unit.rs

@ -89,6 +89,7 @@ impl GenericUnitTable {
ItemDropType::Unit(Unit {
unit: unit_type,
modifier: unit_modifier,
kills: None,
})
})
}
@ -116,6 +117,7 @@ mod test {
assert!(gut.get_drop(&area, &mut rng) == Some(ItemDropType::Unit(Unit {
unit: unit,
modifier: umod,
kills: None,
})));
}
}

5
src/ship/drops/generic_weapon.rs

@ -497,6 +497,7 @@ impl GenericWeaponTable {
grind: weapon_grind as u8,
attrs: weapon_attributes,
tekked: weapon_special.is_none(),
kills: None,
}))
}
}
@ -518,6 +519,7 @@ mod test {
grind: 0,
attrs: [None, None, None],
tekked: true,
kills: None,
})));
let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Hard, SectionID::Skyly);
@ -527,6 +529,7 @@ mod test {
grind: 2,
attrs: [None, None, None],
tekked: true,
kills: None,
})));
let gwt = GenericWeaponTable::new(Episode::One, Difficulty::VeryHard, SectionID::Skyly);
@ -536,6 +539,7 @@ mod test {
grind: 0,
attrs: [None, None, None],
tekked: false,
kills: None,
})));
let gwt = GenericWeaponTable::new(Episode::One, Difficulty::Ultimate, SectionID::Skyly);
@ -545,6 +549,7 @@ mod test {
grind: 0,
attrs: [Some(WeaponAttribute {attr: Attribute::ABeast, value: 30}), Some(WeaponAttribute {attr: Attribute::Dark, value: 30}), None],
tekked: true,
kills: None,
})));
}
}

16
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),
@ -186,6 +186,10 @@ impl<R: Rng + SeedableRng> DropTable<R> {
pub fn get_box_drop(&mut self, map_area: &MapArea, object: &MapObject) -> Option<ItemDropType> {
self.box_table.get_drop(map_area, object, &mut self.rng)
}
pub fn get_rare_drop(&mut self, map_area: &MapArea, monster: &MonsterType) -> Option<ItemDropType> {
self.rare_table.get_rare_drop(map_area, monster, &mut self.rng)
}
}
@ -205,4 +209,14 @@ mod test {
.into_iter().choose(&mut rng).unwrap();
DropTable::<rand_chacha::ChaCha20Rng>::new(episode, difficulty, section_id);
}
#[test]
fn test_sjs_drop() {
let mut drop_table = DropTable::<rand_chacha::ChaCha20Rng>::new(Episode::Two, Difficulty::Ultimate, SectionID::Skyly);
let drop = drop_table.get_rare_drop(&MapArea::Seaside, &MonsterType::GiGue).unwrap();
if let ItemDropType::Weapon(weapon) = drop {
assert!(weapon.weapon == weapon::WeaponType::SealedJSword);
assert!(weapon.kills == Some(0));
}
}
}

28
src/ship/drops/rare_drop_table.rs

@ -97,14 +97,19 @@ impl RareDropTable {
pub fn apply_item_stats<R: Rng>(&self, map_area: &MapArea, item: RareDropItem, rng: &mut R) -> ItemDropType {
match item {
RareDropItem::Weapon(weapon) => {
ItemDropType::Weapon(Weapon {
let mut dropped_weapon = Weapon {
weapon,
special: None,
grind: 0,
attrs: self.attribute_table.generate_rare_attributes(map_area, rng),
tekked: false,
})
kills: None,
};
if dropped_weapon.weapon.has_counter() {
dropped_weapon.attrs[2] = None;
dropped_weapon.kills = Some(0);
};
ItemDropType::Weapon(dropped_weapon)
},
RareDropItem::Armor(armor) => {
ItemDropType::Armor(Armor {
@ -125,6 +130,13 @@ impl RareDropTable {
ItemDropType::Unit(Unit {
unit,
modifier: None,
kills: {
if unit.has_counter() {
Some(0)
} else {
None
}
},
})
},
RareDropItem::Tool(tool) => {
@ -153,4 +165,14 @@ impl RareDropTable {
}).next()
})
}
pub fn get_rare_drop<R: Rng>(&self, map_area: &MapArea, monster: &MonsterType, rng: &mut R) -> Option<ItemDropType> {
self.rates.get(monster)
.and_then(|drop_rates| {
drop_rates.iter()
.map(|drop_rate| {
self.apply_item_stats(map_area, drop_rate.item, rng)
}).next()
})
}
}

852
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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), FloorItem), ItemStateError>> + Send + 'a>>
{
move |(mut item_state, transaction): (ItemStateProxy<'_>, Box<dyn EntityGatewayTransaction + '_>) , _| {
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<dyn EntityGatewayTransaction + 'a>), FloorItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), InventoryItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), BankItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), InventoryItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), 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<ClientItemId>)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), ()), 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<dyn EntityGatewayTransaction + 'a>), InventoryItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), InventoryItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), InventoryItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>),
mut input: Vec<I>,
func: F,
arg: T)
-> Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), Vec<O>), 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<dyn EntityGatewayTransaction + 'a>), T)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<I>,
func: F)
-> impl for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), T)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), Vec<O>), 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<dyn EntityGatewayTransaction + 'a>), T)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>),
mut input: Vec<T>,
func: F)
-> Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), Vec<O>), ItemStateError>
where
'a: 'async_recursion,
O: Send,
T: Clone + Send,
F: Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), T)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), Vec<T>)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), Vec<O>), ItemStateError>> + Send + 'a>>
where
O: Send,
T: Send + Clone + 'static + std::fmt::Debug,
F: for<'a> Fn((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), T)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), InventoryItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), Vec<InventoryItem>)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), Vec<InventoryItem>), 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<dyn EntityGatewayTransaction + 'a>), InventoryItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), ())
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), FloorItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), InventoryItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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<dyn EntityGatewayTransaction + 'a>), InventoryItem)
-> Pin<Box<dyn Future<Output=Result<((ItemStateProxy<'a>, Box<dyn EntityGatewayTransaction + 'a>), 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))
})
}
}

293
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<ItemStateError>),
#[error("magcell error {0}")]
MagCellError(#[from] MagCellError),
}
impl From<ItemStateError> for ApplyItemError {
fn from(other: ItemStateError) -> ApplyItemError {
ApplyItemError::ItemStateError(Box::new(other))
}
}
// TODO: make all these functions not-pub
pub async fn power_material<EG: EntityGateway + ?Sized>(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<EG: EntityGateway + ?Sized>(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<EG: EntityGateway + ?Sized>(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<EG: EntityGateway + ?Sized>(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<EG: EntityGateway + ?Sized>(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<EG: EntityGateway + ?Sized>(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<EG: EntityGateway + ?Sized>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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(())
},
}
}

450
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<ItemEntityId>,
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<Vec<ItemEntityId>> {
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<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_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<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
where
F: FnMut(T, ItemEntityId) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
{
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<BankItem>);
impl Bank {
pub fn new(items: Vec<BankItem>) -> Bank {
Bank(items)
}
}
pub struct CharacterBank {
item_id_counter: u32,
items: Vec<BankItem>
#[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<BankItem>) -> 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<BankItemHandle> {
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<AddItemResult, BankError> {
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<BankItem> {
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<character::BankItem> {
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<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_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)
}
}

294
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<ItemEntityId>,
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<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
where
F: FnMut(T, ItemEntityId) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
{
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<F, Fut, T>(&self, mut param: T, mut func: F) -> Result<T, ItemStateError>
where
F: FnMut(T, ItemEntityId, Mag) -> Fut,
Fut: Future<Output=Result<T, ItemStateError>>,
{
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<FloorItem>);
#[derive(Debug, Clone, Default)]
pub struct SharedFloor(pub Vec<FloorItem>);
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<FloorItem>);
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<FloorItemHandle> {
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<FloorItem> {
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<FloorItem> {
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]
}
}

1080
src/ship/items/inventory.rs
File diff suppressed because it is too large
View File

137
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, S, E> {
_t: std::marker::PhantomData<T>,
_s: std::marker::PhantomData<S>,
_e: std::marker::PhantomData<E>,
}
impl<T, S, E> Default for ItemStateAction<T, S, E> {
fn default() -> ItemStateAction<T, S, E> {
ItemStateAction {
_t: std::marker::PhantomData,
_s: std::marker::PhantomData,
_e: std::marker::PhantomData,
}
}
}
impl<T, S, E> ItemStateAction<T, S, E>
where
T: Send + Sync,
S: Send + Sync,
E: Send + Sync,
{
pub fn act<O, F, Fut>(self, f: F) -> ItemActionStage<O, ItemStateAction<T, S, E>, F, Fut, S, E>
where
F: Fn(S, ()) -> Fut + Send + Sync,
Fut: Future<Output=Result<(S, O), E>> + Send
{
ItemActionStage {
_s: Default::default(),
_e: std::marker::PhantomData,
prev: self,
actionf: f,
}
}
}
pub struct ItemActionStage<O, P, F, Fut, S, E>
where
P: ItemAction,
F: Fn(S, P::Output) -> Fut + Send + Sync,
Fut: Future<Output=Result<(S, O) , E>> + Send,
{
_s: std::marker::PhantomData<S>,
_e: std::marker::PhantomData<E>,
prev: P,
actionf: F,
}
#[async_trait::async_trait]
impl<O, P: ItemAction, F, Fut, S, E> ItemAction for ItemActionStage<O, P, F, Fut, S, E>
where
P: ItemAction + ItemAction<Start = S, Error = E> + Send + Sync,
F: Fn(S, P::Output) -> Fut + Send + Sync,
Fut: Future<Output=Result<(S, O), E>> + 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<O, P: ItemAction, F, Fut, S, E> ItemActionStage<O, P, F, Fut, S, E>
where
P: ItemAction<Start = S, Error = E> + Send + Sync,
F: Fn(S, P::Output) -> Fut + Send + Sync,
Fut: Future<Output=Result<(S, O), E>> + 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<O2, G, GFut>(self, g: G) -> ItemActionStage<O2, ItemActionStage<O, P, F, Fut, S, E>, G, GFut, S, E>
where
S: Send + Sync,
G: Fn(S, <ItemActionStage<O, P, F, Fut, S, E> as ItemAction>::Output) -> GFut + Send + Sync,
GFut: Future<Output=Result<(S, O2), E>> + Send,
O2: Send + Sync,
{
ItemActionStage {
_s: Default::default(),
_e: Default::default(),
prev: self,
actionf: g,
}
}
}
#[async_trait::async_trait]
impl<T, S, E> ItemAction for ItemStateAction<T, S, E>
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, ()))
}
}

145
src/ship/items/manager.rs

@ -6,9 +6,10 @@ use thiserror::Error;
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel};
use crate::entity::item::{ItemDetail, ItemNote, BankName};
use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, InventoryItemEntity, BankItemEntity};
use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, InventoryItemEntity, BankItemEntity, EquippedEntity, ItemEntityId};
use crate::entity::item::tool::{Tool, ToolType};
use crate::entity::item::weapon;
use crate::entity::item::weapon::{Weapon, WeaponModifier};
use crate::entity::item::unit::UnitModifier;
use crate::ship::map::MapArea;
use crate::ship::ship::ItemDropLocation;
use crate::ship::trade::TradeItem;
@ -20,8 +21,8 @@ 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};
use crate::ship::monster::MonsterType;
#[derive(PartialEq, Eq)]
pub enum FloorType {
@ -82,6 +83,9 @@ pub enum ItemManagerError {
ItemTransactionAction(Box<dyn std::error::Error + Send + Sync>),
#[error("invalid trade")]
InvalidTrade,
EntityIdNotInInventory(ItemEntityId),
WeaponCannotCombine,
NotEnoughKills(u16),
}
impl<E> std::convert::From<TransactionError<E>> for ItemManagerError
@ -147,7 +151,7 @@ impl ItemManager {
pub async fn load_character<EG: EntityGateway>(&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 +209,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 +392,7 @@ impl ItemManager {
.map_err(|err| err.into())
}
pub async fn enemy_drop_item_on_local_floor<EG: EntityGateway>(&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 +496,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 +510,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 +554,14 @@ impl ItemManager {
Ok(floor_item)
}
pub async fn player_drops_partial_stack_on_shared_floor<EG: EntityGateway>(&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 +576,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 +629,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<EG: EntityGateway>(&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 +652,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())
}
@ -695,7 +702,6 @@ impl ItemManager {
match &used_item.item() {
ItemDetail::Weapon(_w) => {
// something like when items are used to combine/transform them?
//_ => {}
},
ItemDetail::Tool(t) => {
match t.tool {
@ -792,7 +798,7 @@ impl ItemManager {
ToolType::LibertaKit => {
use_tool::liberta_kit(entity_gateway, &used_item, inventory).await?;
},
_ => {}
_ => {},
}
}
_ => {}
@ -801,13 +807,13 @@ impl ItemManager {
Ok(())
}
pub async fn player_buys_item<EG: EntityGateway>(&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();
@ -982,8 +988,8 @@ impl ItemManager {
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: ClientItemId,
tek: weapon::WeaponModifier)
-> Result<weapon::Weapon, anyhow::Error> {
tek: WeaponModifier)
-> Result<Weapon, anyhow::Error> {
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
let item = inventory.remove_by_id(item_id)
@ -1191,6 +1197,42 @@ impl ItemManager {
.await
.map_err(|err| err.into())
}
pub async fn increase_kill_counters<EG: EntityGateway>(&mut self, entity_gateway: &mut EG, character: &CharacterEntity, equipped_items: &EquippedEntity, monstertype: MonsterType) -> Result<(), anyhow::Error> {
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
if let Some(weapon_entity) = equipped_items.weapon {
let weapon_id = inventory.get_item_by_entity_id(weapon_entity).ok_or(ItemManagerError::EntityIdNotInInventory(weapon_entity))?.item_id();
let mut weapon_handle = inventory.get_item_handle_by_id(weapon_id).ok_or(ItemManagerError::NoSuchItemId(weapon_id))?;
let individual_item_w = weapon_handle.item_mut()
.ok_or(ItemManagerError::NoSuchItemId(weapon_id))?
.individual_mut()
.ok_or(ItemManagerError::WrongItemType(weapon_id))?;
let weapon = individual_item_w
.weapon_mut()
.ok_or(ItemManagerError::WrongItemType(weapon_id))?;
let wmodifier = WeaponModifier::AddKill { enemy: monstertype };
weapon.apply_modifier(&wmodifier);
entity_gateway.add_weapon_modifier(&weapon_entity, wmodifier).await?;
}
for units in equipped_items.unit.iter().flatten() {
let unit_id = inventory.get_item_by_entity_id(*units).ok_or(ItemManagerError::EntityIdNotInInventory(*units))?.item_id();
let mut unit_handle = inventory.get_item_handle_by_id(unit_id).ok_or(ItemManagerError::NoSuchItemId(unit_id))?;
let individual_item_u = unit_handle.item_mut()
.ok_or(ItemManagerError::NoSuchItemId(unit_id))?
.individual_mut()
.ok_or(ItemManagerError::WrongItemType(unit_id))?;
let unit = individual_item_u
.unit_mut()
.ok_or(ItemManagerError::WrongItemType(unit_id))?;
let umodifier = UnitModifier::AddKill { enemy: monstertype };
unit.apply_modifier(&umodifier);
entity_gateway.add_unit_modifier(units, umodifier).await?;
}
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
Ok(())
}
}
#[derive(Debug)]
@ -1373,6 +1415,43 @@ impl<EG: EntityGateway> ItemAction<EG> for TradeMeseta {
dest_meseta.0 += self.amount as u32;
entity_gateway.set_character_meseta(&self.dest_character_id, *dest_meseta).await?;
}
}
pub async fn increase_kill_counters<EG: EntityGateway>( &mut self,
entity_gateway: &mut EG,
character: &CharacterEntity,
equipped_items: &EquippedEntity)
-> Result<(), anyhow::Error> {
let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?;
// weapon
if let Some(weapon_entity) = equipped_items.weapon {
let weapon_id = inventory.get_item_by_entity_id(weapon_entity).ok_or(ItemManagerError::EntityIdNotInInventory(weapon_entity))?.item_id();
let mut weapon_handle = inventory.get_item_handle_by_id(weapon_id).ok_or(ItemManagerError::NoSuchItemId(weapon_id))?;
let individual_item_w = weapon_handle.item_mut()
.ok_or(ItemManagerError::NoSuchItemId(weapon_id))?
.individual_mut()
.ok_or(ItemManagerError::WrongItemType(weapon_id))?;
let weapon = individual_item_w
.weapon_mut()
.ok_or(ItemManagerError::WrongItemType(weapon_id))?;
weapon.increment_kill_counter();
entity_gateway.increment_kill_counter(&weapon_entity).await?;
}
for units in equipped_items.unit.iter().flatten() {
let unit_id = inventory.get_item_by_entity_id(*units).ok_or(ItemManagerError::EntityIdNotInInventory(*units))?.item_id();
let mut unit_handle = inventory.get_item_handle_by_id(unit_id).ok_or(ItemManagerError::NoSuchItemId(unit_id))?;
let individual_item_u = unit_handle.item_mut()
.ok_or(ItemManagerError::NoSuchItemId(unit_id))?
.individual_mut()
.ok_or(ItemManagerError::WrongItemType(unit_id))?;
let unit = individual_item_u
.unit_mut()
.ok_or(ItemManagerError::WrongItemType(unit_id))?;
unit.increment_kill_counter();
}
entity_gateway.set_character_inventory(&character.id, &inventory.as_inventory_entity(&character.id)).await?;
Ok(())
}
}

23
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::*;

391
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<ItemEntity>),
#[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<ItemEntityId>,
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<CharacterEntityId, InventoryState>,
character_bank: HashMap<CharacterEntityId, BankState>,
character_room: HashMap<CharacterEntityId, RoomId>,
character_floor: HashMap<CharacterEntityId, LocalFloor>,
room_floor: HashMap<RoomId, SharedFloor>,
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<ClientItemId, ItemStateError> {
self.room_item_id_counter += 1;
Ok(ClientItemId(self.room_item_id_counter))
}
pub async fn load_character<EG: EntityGateway>(&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<InventoryItem, ItemStateError> {
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::<Result<Vec<_>, _>>()?;
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<BankItem, ItemStateError> {
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::<Result<Vec<_>, _>>()?;
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<CharacterEntityId, InventoryState>,
character_bank: HashMap<CharacterEntityId, BankState>,
character_room: HashMap<CharacterEntityId, RoomId>,
character_floor: HashMap<CharacterEntityId, LocalFloor>,
room_floor: HashMap<RoomId, SharedFloor>,
}
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<K, V>(master: &HashMap<K, V>, proxy: &mut HashMap<K, V>, key: K, err: fn(K) -> ItemStateError) -> Result<V, ItemStateError>
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<InventoryState, ItemStateError> {
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<BankState, ItemStateError> {
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<FloorState, ItemStateError> {
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<ClientItemId, ItemStateError> {
self.item_state.new_item_id()
}
}

500
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<EG>(
item_state: &mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId)
-> Result<actions::TriggerCreateItem, 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_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<EG>(
item_state: &mut ItemState,
entity_gateway: &mut EG,
character: &CharacterEntity,
item_id: &ClientItemId,
map_area: MapArea,
drop_position: (f32, f32, f32))
-> Result<FloorItem, 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, 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<FloorItem, 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_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<FloorItem, 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_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<InventoryItem, 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_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<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::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<InventoryItem, ItemStateError>
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<InventoryItem, 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::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<TradeItem>, Meseta),
p2: (&AreaClient, &CharacterEntity, &Vec<TradeItem>, Meseta))
-> Result<(Vec<InventoryItem>, Vec<InventoryItem>), 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<FloorItem, ItemStateError>
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<IndividualItemDetail, ItemStateError>
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
}

337
src/ship/items/transaction.rs

@ -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<EG: EntityGateway>: 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<Box<dyn ItemAction<EG>>>,
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<dyn ItemAction<EG>>) {
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<E: std::fmt::Debug, U>(mut self, action: fn(&mut ItemTransactionActions<EG>, &T) -> Result<U, E>) -> FinalizedItemTransaction<U, E, EG> {
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<E: std::fmt::Debug> {
#[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<T, E: std::fmt::Debug, EG: EntityGateway> {
value: Result<T, E>,
action_queue: Vec<Box<dyn ItemAction<EG>>>,
}
impl<T, E: std::fmt::Debug, EG: EntityGateway> FinalizedItemTransaction<T, E, EG> {
pub async fn commit(self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<T, TransactionError<E>> {
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<UserAccountEntity, GatewayError> {
self.d1_set = user.username;
Ok(UserAccountEntity::default())
}
async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
self.d2_inc += char.slot;
Ok(CharacterEntity::default())
}
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> 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<EG: EntityGateway> ItemAction<EG> 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<CharacterEntity, GatewayError> {
self.d2_inc += char.slot;
Ok(CharacterEntity::default())
}
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> 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<EG: EntityGateway> ItemAction<EG> 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<CharacterEntity, GatewayError> {
self.d2_inc += char.slot;
Ok(CharacterEntity::default())
}
}
#[async_trait::async_trait]
impl<EG: EntityGateway> ItemAction<EG> 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<EG: EntityGateway> ItemAction<EG> 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)))));
}
}

163
src/ship/items/use_tool.rs

@ -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<EG: EntityGateway>(entity_gateway: &mut EG, character: &mut CharacterEntity) {
character.materials.power += 1;
entity_gateway.save_character(character).await.unwrap();
}
pub async fn mind_material<EG: EntityGateway>(entity_gateway: &mut EG, character: &mut CharacterEntity) {
character.materials.mind += 1;
entity_gateway.save_character(character).await.unwrap();
}
pub async fn evade_material<EG: EntityGateway>(entity_gateway: &mut EG, character: &mut CharacterEntity) {
character.materials.evade += 1;
entity_gateway.save_character(character).await.unwrap();
}
pub async fn def_material<EG: EntityGateway>(entity_gateway: &mut EG, character: &mut CharacterEntity) {
character.materials.def += 1;
entity_gateway.save_character(character).await.unwrap();
}
pub async fn luck_material<EG: EntityGateway>(entity_gateway: &mut EG, character: &mut CharacterEntity) {
character.materials.luck += 1;
entity_gateway.save_character(character).await.unwrap();
}
pub async fn hp_material<EG: EntityGateway>(entity_gateway: &mut EG, character: &mut CharacterEntity) {
character.materials.hp += 1;
entity_gateway.save_character(character).await.unwrap();
}
pub async fn tp_material<EG: EntityGateway>(entity_gateway: &mut EG, character: &mut CharacterEntity) {
character.materials.tp += 1;
entity_gateway.save_character(character).await.unwrap();
}
async fn mag_cell<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(entity_gateway: &mut EG, used_cell: &ConsumedItem, inventory: &mut CharacterInventory) -> Result<(), UseItemError> {
mag_cell(entity_gateway, used_cell, inventory, MagCell::LibertaKit).await
}

30
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<u8> 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<AreaClient>; 12]);
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct Room([Option<AreaClient>; 4]);
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RoomLobby {
Room(RoomId),
Lobby(LobbyId),

2
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,

10
src/ship/map/maps.rs

@ -320,6 +320,16 @@ impl Maps {
self.object_data = objects;
}
pub fn get_enemy_id_by_monster_type(&self, monster: MonsterType) -> Option<u16> {
let (id, _) = self.enemy_data
.iter()
.enumerate()
.filter(|(_i, &m)| m.is_some())
.find(|(_i, &m)| m.unwrap().monster == monster)?;
Some(id as u16)
}
pub fn get_rare_monster_list(&self) -> Vec<u16> {
let mut rare_monsters = vec![0xFFFF; 16];
let shiny: Vec<(usize, &Option<MapEnemy>)> = self.enemy_data.iter()

2
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,

10
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<JoinLobby, anyhow::Error> {
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<AddToLobby, ShipError> {
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),
})
}

52
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<ItemDr
Ok(ItemDrop {
client,
target,
map_area: item_drop.map_area().area_value(),
map_area: item_drop.map_area.area_value(),
variety: 0,
unknown: 0,
x: item_drop.x(),
z: item_drop.z(),
y: item_drop.y(),
x: item_drop.x,
z: item_drop.z,
y: item_drop.y,
item_bytes: item_bytes[0..12].try_into()?,
item_id: item_drop.item_id().0,
item_id: item_drop.item_id.0,
item_bytes2: item_bytes[12..16].try_into()?,
unknown2: 0,
})
}
// TODO: this doesn't need to be a Result, just unwrap try_intos they are guaranteed to succeed
pub fn create_individual_item(area_client: AreaClient, item_id: ClientItemId, item: &item::ItemDetail) -> Result<CreateItem, ShipError> {
pub fn create_individual_item(area_client: AreaClient, item_id: ClientItemId, item: &IndividualItemDetail) -> Result<CreateItem, ShipError> {
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<CreateItem, ShipError> {
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<CreateItem, ShipError> {
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<DropSplitStack, ShipError> {
pub fn drop_split_stack(area_client: AreaClient, item: &FloorItem) -> Result<DropSplitStack, ShipError> {
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()
}
}

9
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),

8
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<u16>) -> RareMonsterList {
RareMonsterList {
ids: rare_monster_vec.try_into().unwrap_or([0xFFFFu16; 16]),
}
}
}

6
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<EG: EntityGateway>(id: ClientId,
pkt: &Login,
entity_gateway: &mut EG,
clients: &mut Clients,
item_manager: &mut ItemManager,
item_state: &mut ItemState,
shipgate_sender: &Option<Box<dyn Fn(ShipMessage) + Send + Sync>>,
ship_name: &str,
num_blocks: usize)
@ -30,7 +30,7 @@ pub async fn validate_login<EG: EntityGateway>(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));

135
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<EG>(id: ClientId,
client_location: &ClientLocation,
clients: &mut Clients,
rooms: &mut Rooms,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
@ -94,9 +94,15 @@ where
let client_and_drop = clients_in_area.into_iter()
.filter_map(|area_client| {
room.drop_table.get_drop(&monster.map_area, &monster.monster).map(|item_drop_type| {
(area_client, item_drop_type)
})
if room.redbox {
room.drop_table.get_rare_drop(&monster.map_area, &monster.monster).map(|item_drop_type| {
(area_client, item_drop_type)
})
} else {
room.drop_table.get_drop(&monster.map_area, &monster.monster).map(|item_drop_type| {
(area_client, item_drop_type)
})
}
});
let mut item_drop_packets = Vec::new();
@ -109,8 +115,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 +129,7 @@ pub async fn pickup_item<EG>(id: ClientId,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
@ -134,7 +140,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 +150,19 @@ where
FloorItem::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 {
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 pick_up_item(item_state, entity_gateway, &client.character, &ClientItemId(pickup_item.item_id)).await {
Ok(trigger_create_item) => {
let remove_packets: Box<dyn Iterator<Item=(ClientId, SendShipPacket)> + Send> = match floor_type {
FloorType::Local => {
@ -180,7 +199,7 @@ pub async fn request_box_item<EG>(id: ClientId,
client_location: &ClientLocation,
clients: &mut Clients,
rooms: &mut Rooms,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
@ -215,8 +234,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 +246,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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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 +260,7 @@ pub async fn bank_interaction<EG>(id: ClientId,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
@ -252,42 +270,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 +351,7 @@ pub async fn buy_item<EG>(id: ClientId,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
@ -359,7 +359,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 +378,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 +415,7 @@ pub async fn request_tek_item<EG>(id: ClientId,
tek_request: &TekRequest,
entity_gateway: &mut EG,
clients: &mut Clients,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
@ -442,13 +433,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 +447,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 +459,7 @@ pub async fn accept_tek_item<EG>(id: ClientId,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
@ -488,9 +477,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()

27
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<Vec<(ClientId, SendShipPacket)>, 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<Vec<(ClientId, SendShipPacket)>, 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<EG: EntityGateway>(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<EG: EntityGateway>(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<EG: EntityGateway>(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()

153
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<EG: EntityGateway>(id: ClientId,
request_exp: &RequestExp,
@ -63,12 +66,12 @@ pub async fn request_exp<EG: EntityGateway>(id: ClientId,
}
pub async fn player_drop_item<EG>(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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG>(id: ClientId,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG>(id: ClientId,
charge: &ChargeAttack,
clients: &mut Clients,
entity_gateway: &mut EG,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
charge: &ChargeAttack,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG>(id: ClientId,
pub async fn player_uses_item<EG>(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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG>(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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG>(id: ClientId,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &Clients,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG>(id: ClientId,
pkt: &PlayerEquipItem,
entity_gateway: &mut EG,
clients: &Clients,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG>(id: ClientId,
pkt: &PlayerUnequipItem,
entity_gateway: &mut EG,
clients: &Clients,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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,62 @@ pub async fn player_sorts_items<EG>(id: ClientId,
pkt: &SortItems,
entity_gateway: &mut EG,
clients: &Clients,
item_manager: &mut ItemManager)
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG> (id: ClientId,
sold_item: &PlayerSoldItem,
entity_gateway: &mut EG,
// client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
sold_item: &PlayerSoldItem,
entity_gateway: &mut EG,
clients: &mut Clients,
item_state: &mut ItemState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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()))
Ok(Box::new(None.into_iter())) // TODO: Do clients care about the order of other clients items?
}
pub async fn player_killed_monster<EG>( id: ClientId,
pkt: &KillMonster,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &Clients,
rooms: &mut Rooms,
item_manager: &mut ItemManager)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
{
let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
let equipped_items = entity_gateway.get_character_equips(&client.character.id).await?;
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let room = rooms.get_mut(room_id.0)
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?
.as_mut()
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
let enemy_id = u16::from_le_bytes([pkt.client, pkt.target]) & 0x0FFF;
let monstertype = room.maps.enemy_by_id(enemy_id as usize)?.monster;
item_manager.increase_kill_counters(entity_gateway, &client.character, &equipped_items, monstertype).await?;
Ok(Box::new(None.into_iter())) // TODO: forward to other clients in the room
}

12
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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<Box<dyn Iterator<Item=(ClientId, SendShipPacket)> + 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;

152
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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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::<Vec<u8>>()
.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<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<EG>(id: ClientId,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
item_manager: &mut ItemManager,
item_state: &mut ItemState,
trades: &mut TradeState)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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()))

6
src/ship/room.rs

@ -203,6 +203,7 @@ pub struct RoomState {
pub rare_monster_table: Box<RareMonsterAppearTable>,
pub quest_group: QuestCategoryType,
pub quests: Vec<quests::QuestList>,
pub redbox: bool,
// items on ground
// enemy info
}
@ -316,6 +317,11 @@ impl RoomState {
map_areas: MapAreaLookup::new(&room_mode.episode()),
quest_group: QuestCategoryType::Standard,
quests: room_quests,
redbox: false,
})
}
pub fn toggle_redbox_mode(&mut self) {
self.redbox = !self.redbox;
}
}

109
src/ship/ship.rs

@ -41,44 +41,71 @@ pub type Rooms = [Option<room::RoomState>; MAX_ROOMS];
pub type Clients = HashMap<ClientId, ClientState>;
#[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<EG: EntityGateway> ShipServerStateBuilder<EG> {
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<EG: EntityGateway> {
pub clients: Clients,
level_table: CharacterLevelTable,
name: String,
item_manager: items::ItemManager,
item_state: items::state::ItemState,
shops: Box<ItemShops>,
pub blocks: Blocks,
@ -477,7 +504,7 @@ impl<EG: EntityGateway> ShipServerState<EG> {
},
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<EG: EntityGateway> ShipServerState<EG> {
},
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,36 @@ impl<EG: EntityGateway> ShipServerState<EG> {
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?
},
GameMessage::KillMonster(kill_monster) => {
let block = self.blocks.with_client(id, &self.clients)?;
handler::message::player_killed_monster(id, kill_monster, &mut self.entity_gateway, &block.client_location, &self.clients, &mut block.rooms, &mut self.item_manager).await?
},
_ => {
let cmsg = msg.clone();
@ -539,34 +572,34 @@ impl<EG: EntityGateway> ShipServerState<EG> {
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 +637,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + 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 +657,10 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
}
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 +682,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
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 +690,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
},
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 +704,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
},
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 +742,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
},
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 +774,11 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
},
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 +814,7 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
}
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;

2
src/ship/shops/armor.rs

@ -92,6 +92,7 @@ impl ShopItem for ArmorShopItem {
ItemDetail::Unit(Unit {
unit: unit.unit,
modifier: None,
kills: None,
})
},
}
@ -333,6 +334,7 @@ impl<R: Rng + SeedableRng> ArmorShop<R> {
ArmorShopItem::Unit(Unit {
unit: unit_detail.item,
modifier: None,
kills: None,
})
})
.collect()

1
src/ship/shops/weapon.rs

@ -148,6 +148,7 @@ impl ShopItem for WeaponShopItem {
grind: self.grind as u8,
attrs: [self.attributes[0], self.attributes[1], None],
tekked: true,
kills: None,
}
)
}

10
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),
}

40
tests/common.rs

@ -3,8 +3,8 @@
use elseware::common::serverstate::{ClientId, ServerState};
use elseware::entity::gateway::EntityGateway;
use elseware::entity::account::{UserAccountEntity, NewUserAccountEntity, NewUserSettingsEntity};
use elseware::entity::character::{CharacterEntity, NewCharacterEntity};
use elseware::entity::item::{Meseta, BankName};
use elseware::entity::character::{CharacterEntity, NewCharacterEntity, SectionID};
use elseware::ship::ship::{ShipServerState, RecvShipPacket};
use elseware::ship::room::Difficulty;
@ -29,8 +29,29 @@ pub async fn new_user_character<EG: EntityGateway>(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)
}
pub async fn new_user_character_with_sid<EG: EntityGateway>(entity_gateway: &mut EG, username: &str, password: &str, sid: SectionID) -> (UserAccountEntity, CharacterEntity) {
let new_user = NewUserAccountEntity {
email: format!("{}@pso.com", username),
username: username.into(),
password: bcrypt::hash(password, 5).unwrap(),
guildcard: 1,
activated: true,
..NewUserAccountEntity::default()
};
let user = entity_gateway.create_user(new_user).await.unwrap();
let new_settings = NewUserSettingsEntity::new(user.id);
let _settings = entity_gateway.create_user_settings(new_settings).await.unwrap();
let mut new_character = NewCharacterEntity::new(user.id, 1);
new_character.section_id = sid;
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();
(user, character)
}
@ -84,6 +105,21 @@ pub async fn create_room_with_difficulty<EG: EntityGateway>(ship: &mut ShipServe
ship.handle(id, &RecvShipPacket::DoneBursting(DoneBursting {})).await.unwrap().for_each(drop);
}
pub async fn create_ep2_room_with_difficulty<EG: EntityGateway>(ship: &mut ShipServerState<EG>, id: ClientId, name: &str, password: &str, difficulty: Difficulty) {
ship.handle(id, &RecvShipPacket::CreateRoom(CreateRoom {
unknown: [0; 2],
name: utf8_to_utf16_array!(name, 16),
password: utf8_to_utf16_array!(password, 16),
difficulty: difficulty.into(),
battle: 0,
challenge: 0,
episode: 2,
single_player: 0,
padding: [0; 3],
})).await.unwrap().for_each(drop);
ship.handle(id, &RecvShipPacket::DoneBursting(DoneBursting {})).await.unwrap().for_each(drop);
}
pub async fn join_room<EG: EntityGateway>(ship: &mut ShipServerState<EG>, id: ClientId, room_id: u32) {
ship.handle(id, &RecvShipPacket::MenuSelect(MenuSelect {
menu: ROOM_MENU_ID,

135
tests/test_bank.rs

@ -27,11 +27,12 @@ async fn test_bank_items_sent_in_character_login() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).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())
@ -64,12 +65,13 @@ async fn test_request_bank_items() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).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 +115,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())
@ -153,6 +155,7 @@ async fn test_request_bank_items_sorted() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap();
@ -173,12 +176,13 @@ async fn test_request_bank_items_sorted() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).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())
@ -219,6 +223,7 @@ async fn test_deposit_individual_item() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap();
@ -231,6 +236,7 @@ async fn test_deposit_individual_item() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap();
@ -269,7 +275,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 +341,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::<Vec<_>>(),
@ -391,7 +403,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::<Vec<_>>(),
@ -437,7 +449,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 +483,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::<BTreeSet<_>>(),
@ -510,7 +522,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 +549,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);
@ -567,6 +579,7 @@ async fn test_deposit_individual_item_in_full_bank() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());
@ -582,13 +595,14 @@ async fn test_deposit_individual_item_in_full_bank() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());
}
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 +629,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();
@ -654,13 +668,14 @@ async fn test_deposit_stacked_item_in_full_bank() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());
}
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 +702,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();
@ -739,6 +754,7 @@ async fn test_deposit_stacked_item_in_full_bank_with_partial_stack() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap().into());
@ -746,7 +762,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 +787,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 +828,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 +839,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 +854,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 +862,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 +878,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 +893,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 +901,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);
}
@ -909,11 +929,12 @@ async fn test_withdraw_individual_item() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).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 +994,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 +1024,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 +1053,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 +1083,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::<Vec<_>>(),
@ -1110,7 +1131,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 +1161,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 +1206,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 +1233,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::<Vec<_>>(),
@ -1242,6 +1263,7 @@ async fn test_withdraw_individual_item_in_full_inventory() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());
@ -1257,13 +1279,14 @@ async fn test_withdraw_individual_item_in_full_inventory() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());
}
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 +1312,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();
@ -1325,13 +1348,14 @@ async fn test_withdraw_stacked_item_in_full_inventory() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());
}
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 +1383,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::<Vec<_>>(),
@ -1387,7 +1411,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 {
@ -1400,6 +1424,7 @@ async fn test_withdraw_stacked_item_in_full_inventory_with_partial_stack() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap().into());
@ -1443,7 +1468,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 +1486,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 +1512,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 +1523,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 +1538,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 +1546,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 +1562,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 +1577,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 +1585,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);
}

10
tests/test_item_actions.rs

@ -34,6 +34,7 @@ async fn test_equip_unit_from_equip_menu() {
item::unit::Unit{
unit: item::unit::UnitType::KnightPower,
modifier: None,
kills: None,
}),
}).await.unwrap());
@ -43,12 +44,13 @@ async fn test_equip_unit_from_equip_menu() {
item::unit::Unit{
unit: item::unit::UnitType::KnightPower,
modifier: Some(item::unit::UnitModifier::Plus),
kills: None,
}),
}).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,
@ -111,6 +113,7 @@ async fn test_unequip_armor_with_units() {
item::unit::Unit{
unit: item::unit::UnitType::KnightPower,
modifier: None,
kills: None,
}),
}).await.unwrap());
@ -120,6 +123,7 @@ async fn test_unequip_armor_with_units() {
item::unit::Unit{
unit: item::unit::UnitType::KnightPower,
modifier: Some(item::unit::UnitModifier::Plus),
kills: None,
}),
}).await.unwrap());
@ -179,6 +183,7 @@ async fn test_sort_items() {
item::unit::Unit{
unit: item::unit::UnitType::KnightPower,
modifier: None,
kills: None,
}),
}).await.unwrap());
@ -188,6 +193,7 @@ async fn test_sort_items() {
item::unit::Unit{
unit: item::unit::UnitType::KnightPower,
modifier: Some(item::unit::UnitModifier::Plus),
kills: None,
}),
}).await.unwrap());

7
tests/test_item_pickup.rs

@ -27,6 +27,7 @@ async fn test_pick_up_individual_item() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());
@ -234,6 +235,7 @@ async fn test_pick_up_meseta_when_inventory_full() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());
@ -306,6 +308,7 @@ async fn test_pick_up_partial_stacked_item_when_inventory_is_otherwise_full() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap().into());
@ -389,6 +392,7 @@ async fn test_can_not_pick_up_item_when_inventory_full() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());
@ -404,6 +408,7 @@ async fn test_can_not_pick_up_item_when_inventory_full() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());
@ -734,7 +739,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);

6
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() {}
*/

2
tests/test_rooms.rs

@ -29,6 +29,7 @@ async fn test_item_ids_reset_when_rejoining_rooms() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());
@ -45,6 +46,7 @@ async fn test_item_ids_reset_when_rejoining_rooms() {
special: None,
attrs: [None, None, None],
tekked: true,
kills: None,
}
),
}).await.unwrap());

11
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::*;
@ -274,6 +274,7 @@ async fn test_player_sells_3_attr_weapon_to_shop() {
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Native, value: 100}),],
tekked: true,
kills: None,
}
),
}).await.unwrap());
@ -635,6 +636,7 @@ async fn test_player_sells_untekked_weapon() {
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Native, value: 100}),],
tekked: false,
kills: None,
}
),
}).await.unwrap());
@ -679,6 +681,7 @@ async fn test_player_sells_rare_item() {
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Dark, value: 100}),
Some(item::weapon::WeaponAttribute{attr: item::weapon::Attribute::Native, value: 100}),],
tekked: true,
kills: None,
}
),
}).await.unwrap());
@ -923,6 +926,7 @@ async fn test_player_sells_1_star_minusminus_unit() {
item::unit::Unit {
unit: item::unit::UnitType::PriestMind,
modifier: Some(item::unit::UnitModifier::MinusMinus),
kills: None,
}
),
}).await.unwrap());
@ -962,6 +966,7 @@ async fn test_player_sells_5_star_plusplus_unit() {
item::unit::Unit {
unit: item::unit::UnitType::GeneralHp,
modifier: Some(item::unit::UnitModifier::PlusPlus),
kills: None,
}
),
}).await.unwrap());
@ -1082,6 +1087,7 @@ async fn test_player_sells_rare_unit() {
item::unit::Unit {
unit: item::unit::UnitType::V101,
modifier: None,
kills: None,
}
),
}).await.unwrap());
@ -1122,6 +1128,7 @@ async fn test_player_cant_sell_if_meseta_would_go_over_max() {
item::unit::Unit {
unit: item::unit::UnitType::V101,
modifier: None,
kills: None,
}
),
}).await.unwrap());
@ -1142,7 +1149,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::<ItemManagerError>().unwrap(), ItemManagerError::WalletFull));
assert!(matches!(ack.downcast::<ItemStateError>().unwrap(), ItemStateError::FullOfMeseta));
let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap();
assert_eq!(c1_meseta.0, 999995);

467
tests/test_trade.rs
File diff suppressed because it is too large
View File

272
tests/test_unseal_items.rs

@ -0,0 +1,272 @@
/* TODO:
4. test unsealing item:
- client item id does not change
- unsealed item no longer has kill counter
5. test reject unsealing item if not enough kills (can this even happen?)
*/
use elseware::common::serverstate::{ClientId, ServerState};
use elseware::entity::gateway::{EntityGateway, InMemoryGateway};
use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket};
use elseware::entity::character::SectionID;
use elseware::ship::room::Difficulty;
use elseware::ship::monster::MonsterType;
use elseware::entity::item;
use libpso::packet::ship::*;
use libpso::packet::messages::*;
#[path = "common.rs"]
mod common;
use common::*;
#[async_std::test]
async fn test_sjs_drops_with_kill_counter() {
let mut entity_gateway = InMemoryGateway::default();
let (_user1, mut char1) = new_user_character_with_sid(&mut entity_gateway, "a1", "a", SectionID::Skyly).await;
char1.exp = 80000000;
entity_gateway.save_character(&char1).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
.build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_ep2_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
let room = ship.blocks.0[0].rooms[0].as_mut().unwrap();
room.toggle_redbox_mode(); // enable redbox mode
let gigue_id = room.maps.get_enemy_id_by_monster_type(MonsterType::GiGue).unwrap();
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::RequestItem(RequestItem {
client: 0,
target: 0,
map_area: 9, // seaside
pt_index: 55, // gigue ? (taken from ingame logs)
enemy_id: gigue_id,
x: 0.0,
y: 0.0,
z: 0.0,
})))).await.unwrap().collect::<Vec<_>>(); // this should return 1 packet (ItemDrop)?
assert!(packets.len() == 1);
match &packets[0].1 {
SendShipPacket::Message(Message {msg: GameMessage::ItemDrop(item_drop)}) => {
assert_eq!(item_drop.item_bytes[10], 0x80)
}
_ => panic!("SJS didn't drop with the expected value! attr[2] should be 0x80 (128) for 0 kills")
}
}
#[async_std::test]
async fn test_other_weapons_drop_without_kill_counter() {
let mut entity_gateway = InMemoryGateway::default();
let (_user1, mut char1) = new_user_character_with_sid(&mut entity_gateway, "a1", "a", SectionID::Skyly).await;
char1.exp = 80000000;
entity_gateway.save_character(&char1).await.unwrap();
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
.build());
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_ep2_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await;
let room = ship.blocks.0[0].rooms[0].as_mut().unwrap();
room.toggle_redbox_mode(); // enable redbox mode
let enemy_id = room.maps.get_enemy_id_by_monster_type(MonsterType::Hildebear).unwrap();
let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::RequestItem(RequestItem {
client: 0,
target: 0,
map_area: 1, // temple alpha
pt_index: 0, // TODO: this is going to break if pt_index ever gets properly used
enemy_id: enemy_id,
x: 0.0,
y: 0.0,
z: 0.0,
})))).await.unwrap().collect::<Vec<_>>();
assert!(packets.len() == 1);
match &packets[0].1 {
SendShipPacket::Message(Message {msg: GameMessage::ItemDrop(item_drop)}) => {
assert_ne!(item_drop.item_bytes[10], 0x80)
}
_ => panic!("Weapon didn't drop with the expected value! attr[2] should be less than 0x80 (128) because it shouldn't have a kill counter!")
}
}
#[async_std::test]
async fn test_all_equipped_kill_counters_increase_per_kill() {
let mut entity_gateway = InMemoryGateway::default();
let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
.build());
let mut p1_inv = Vec::new();
p1_inv.push(entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Weapon(
item::weapon::Weapon {
weapon: item::weapon::WeaponType::SealedJSword,
grind: 0,
special: None,
attrs: [None,
None,
None,],
tekked: true,
kills: Some(0),
}
),
}).await.unwrap());
p1_inv.push(entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Unit(
item::unit::Unit {
unit: item::unit::UnitType::Limiter,
modifier: None,
kills: Some(0),
}
),
}).await.unwrap());
let equipped = item::EquippedEntity {
weapon: Some(p1_inv[0].id),
armor: None,
shield: None,
unit: [Some(p1_inv[1].id), None, None, None],
mag: None,
};
entity_gateway.set_character_equips(&char1.id, &equipped).await.unwrap();
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room(&mut ship, ClientId(1), "room", "").await;
let enemy_id = {
let room = ship.blocks.0[0].rooms[0].as_ref().unwrap();
let enemy_id = (0..).filter_map(|i| {
room.maps.enemy_by_id(i).ok().and_then(|enemy| {
if enemy.monster == MonsterType::Booma {
Some(i)
}
else {
None
}
})
}).next().unwrap();
enemy_id
};
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::KillMonster(KillMonster{
client: enemy_id as u8,
target: 16,
map_area: 1,
data: [8,0],
})))).await.unwrap().for_each(drop);
let equipped_items = entity_gateway.get_character_equips(&char1.id).await.unwrap();
let inventory = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
let w = inventory.items.iter().find(|x| x.individual().unwrap().id == equipped_items.weapon.unwrap()).unwrap().individual().unwrap();
let u = inventory.items.iter().find(|x| x.individual().unwrap().id == equipped_items.unit[0].unwrap()).unwrap().individual().unwrap();
assert!(w.item.as_client_bytes()[11] == 1);
assert!(u.item.as_client_bytes()[11] == 1);
}
#[async_std::test]
async fn test_non_equipped_kill_counter_does_not_increase() {
let mut entity_gateway = InMemoryGateway::default();
let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a", 1).await;
let mut ship = Box::new(ShipServerState::builder()
.gateway(entity_gateway.clone())
.build());
let mut p1_inv = Vec::new();
p1_inv.push(entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Weapon(
item::weapon::Weapon {
weapon: item::weapon::WeaponType::SealedJSword,
grind: 0,
special: None,
attrs: [None,
None,
None,],
tekked: true,
kills: Some(0),
}
),
}).await.unwrap());
p1_inv.push(entity_gateway.create_item(
item::NewItemEntity {
item: item::ItemDetail::Unit(
item::unit::Unit {
unit: item::unit::UnitType::Limiter,
modifier: None,
kills: Some(0),
}
),
}).await.unwrap());
let equipped = item::EquippedEntity {
weapon: Some(p1_inv[0].id),
armor: None,
shield: None,
unit: [None; 4],
mag: None,
};
entity_gateway.set_character_equips(&char1.id, &equipped).await.unwrap();
entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap();
log_in_char(&mut ship, ClientId(1), "a1", "a").await;
join_lobby(&mut ship, ClientId(1)).await;
create_room(&mut ship, ClientId(1), "room", "").await;
let enemy_id = {
let room = ship.blocks.0[0].rooms[0].as_ref().unwrap();
let enemy_id = (0..).filter_map(|i| {
room.maps.enemy_by_id(i).ok().and_then(|enemy| {
if enemy.monster == MonsterType::Booma {
Some(i)
}
else {
None
}
})
}).next().unwrap();
enemy_id
};
ship.handle(ClientId(1), &RecvShipPacket::Message(Message::new(GameMessage::KillMonster(KillMonster{
client: enemy_id as u8,
target: 16,
map_area: 1,
data: [8,0],
})))).await.unwrap().for_each(drop);
let equipped_items = entity_gateway.get_character_equips(&char1.id).await.unwrap();
let inventory = entity_gateway.get_character_inventory(&char1.id).await.unwrap();
let w = inventory.items.iter().find(|x| x.individual().unwrap().id == equipped_items.weapon.unwrap()).unwrap().individual().unwrap();
let u = inventory.items.iter().find(|x| x.individual().unwrap().id == item::ItemEntityId(2)).unwrap().individual().unwrap();
assert!(w.item.as_client_bytes()[11] == 1);
assert!(u.item.as_client_bytes()[11] == 0);
}
Loading…
Cancel
Save