From b34d6e9e96ea1ba29155e131bc6c33c42f68dc74 Mon Sep 17 00:00:00 2001 From: vE5li Date: Tue, 23 Apr 2024 21:41:26 +0200 Subject: [PATCH] Move networking to a separate thread This rework splits the networking system to a separate crate and moves the processing to a networking thread. The main thread will no longer be blocked by networking operations, which was the main reason for the client freezing. I took this opportunity to also refactor the way that networking routines are handled. Instead of sending a packet and waiting for a response, the request and response are completely decoupled, offering more resilience to packets arriving out of order. --- Cargo.lock | 974 +++++++++-- Cargo.toml | 5 +- korangar/Cargo.toml | 6 +- korangar/src/graphics/particles/mod.rs | 2 +- .../src/graphics/renderers/deferred/mod.rs | 2 +- korangar/src/graphics/renderers/mod.rs | 2 +- .../graphics/renderers/picker/entity/mod.rs | 2 +- korangar/src/graphics/renderers/picker/mod.rs | 2 +- .../src/graphics/renderers/picker/target.rs | 4 +- korangar/src/graphics/renderers/shadow/mod.rs | 2 +- korangar/src/input/event.rs | 4 +- korangar/src/input/mod.rs | 2 +- korangar/src/interface/cursor/mod.rs | 2 +- korangar/src/interface/dialog.rs | 2 +- .../elements/containers/character.rs | 2 +- .../interface/elements/containers/dialog.rs | 2 +- .../elements/containers/equipment.rs | 31 +- .../interface/elements/containers/friends.rs | 22 +- .../src/interface/elements/containers/mod.rs | 2 +- .../interface/elements/containers/packet.rs | 277 ++- .../elements/miscellanious/chat/builder.rs | 3 +- .../elements/miscellanious/chat/mod.rs | 22 +- korangar/src/interface/linked.rs | 45 + korangar/src/interface/mod.rs | 1 + korangar/src/interface/resource.rs | 2 +- korangar/src/interface/theme/mod.rs | 28 + .../src/interface/windows/account/login.rs | 2 +- .../windows/account/select_server.rs | 2 +- .../interface/windows/character/selection.rs | 2 +- korangar/src/interface/windows/debug/maps.rs | 48 +- .../src/interface/windows/debug/packet.rs | 24 +- .../src/interface/windows/friends/list.rs | 9 +- .../src/interface/windows/friends/request.rs | 4 +- .../src/interface/windows/generic/chat.rs | 10 +- .../src/interface/windows/generic/dialog.rs | 2 +- korangar/src/interface/windows/generic/mod.rs | 2 +- korangar/src/inventory/mod.rs | 16 +- korangar/src/inventory/skills.rs | 2 +- korangar/src/loaders/action/mod.rs | 4 +- .../login.rs => loaders/client/mod.rs} | 0 korangar/src/loaders/effect/mod.rs | 2 +- korangar/src/loaders/mod.rs | 1 + korangar/src/loaders/script/mod.rs | 2 +- korangar/src/main.rs | 539 +++--- korangar/src/network/mod.rs | 1503 ----------------- korangar/src/system/timer.rs | 2 +- korangar/src/world/entity/mod.rs | 11 +- korangar/src/world/map/mod.rs | 2 +- korangar/src/world/model/mod.rs | 2 +- korangar/src/world/model/node.rs | 2 +- korangar/src/world/object/mod.rs | 2 +- korangar_debug/src/lib.rs | 3 +- korangar_debug/src/profiling/ring_buffer.rs | 4 +- korangar_interface/README.md | 2 +- korangar_interface/src/layout/resolver.rs | 2 +- korangar_interface/src/lib.rs | 4 +- korangar_interface/src/state.rs | 7 +- korangar_interface/src/theme.rs | 4 + korangar_interface/src/windows/mod.rs | 2 +- korangar_networking/Cargo.toml | 17 + korangar_networking/README.md | 4 + .../examples/ollama-chat-bot.rs | 202 +++ korangar_networking/src/entity.rs | 80 + korangar_networking/src/event.rs | 194 +++ korangar_networking/src/inventory.rs | 9 + korangar_networking/src/lib.rs | 1039 ++++++++++++ korangar_networking/src/message.rs | 8 + korangar_networking/src/server.rs | 85 + .../Cargo.toml | 3 +- .../README.md | 2 +- ragnarok_packets/src/handler.rs | 194 +++ .../src/lib.rs | 517 +++--- ragnarok_procedural/README.md | 2 +- ragnarok_procedural/src/lib.rs | 34 + ragnarok_procedural/src/packet.rs | 20 +- 75 files changed, 3768 insertions(+), 2313 deletions(-) create mode 100644 korangar/src/interface/linked.rs rename korangar/src/{network/login.rs => loaders/client/mod.rs} (100%) delete mode 100644 korangar/src/network/mod.rs create mode 100644 korangar_networking/Cargo.toml create mode 100644 korangar_networking/README.md create mode 100644 korangar_networking/examples/ollama-chat-bot.rs create mode 100644 korangar_networking/src/entity.rs create mode 100644 korangar_networking/src/event.rs create mode 100644 korangar_networking/src/inventory.rs create mode 100644 korangar_networking/src/lib.rs create mode 100644 korangar_networking/src/message.rs create mode 100644 korangar_networking/src/server.rs rename {ragnarok_networking => ragnarok_packets}/Cargo.toml (82%) rename {ragnarok_networking => ragnarok_packets}/README.md (77%) create mode 100644 ragnarok_packets/src/handler.rs rename {ragnarok_networking => ragnarok_packets}/src/lib.rs (86%) diff --git a/Cargo.lock b/Cargo.lock index f2ab890b..8378afe8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.23" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225" +checksum = "6f90148830dac590fac7ccfe78ec4a8ea404c60f75a24e16407a71f0f40de775" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser 0.20.0", @@ -18,6 +18,15 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -31,7 +40,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.2.12", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -117,9 +126,24 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] [[package]] name = "base64" @@ -127,6 +151,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bit-set" version = "0.5.3" @@ -193,9 +223,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" @@ -214,7 +244,7 @@ checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.60", ] [[package]] @@ -223,6 +253,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + [[package]] name = "calloop" version = "0.10.6" @@ -239,12 +275,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.90" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -272,16 +309,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -406,7 +443,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.60", ] [[package]] @@ -417,7 +454,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.60", ] [[package]] @@ -437,15 +474,24 @@ dependencies = [ [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] [[package]] name = "equivalent" @@ -479,6 +525,12 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + [[package]] name = "fdeflate" version = "0.3.4" @@ -496,9 +548,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -513,6 +565,12 @@ dependencies = [ "spin", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.3.2" @@ -528,6 +586,54 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -541,9 +647,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -560,11 +666,36 @@ dependencies = [ "weezl", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "bytemuck", "cfg-if", @@ -579,9 +710,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" @@ -604,6 +735,102 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -627,6 +854,16 @@ dependencies = [ "cc", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "image" version = "0.24.9" @@ -662,7 +899,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -692,9 +929,15 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.60", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "itertools" version = "0.10.5" @@ -718,9 +961,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -756,13 +999,14 @@ dependencies = [ "image", "korangar_debug", "korangar_interface", + "korangar_networking", "lunify", "mlua", "num", "option-ext", "pathfinding", "ragnarok_bytes", - "ragnarok_networking", + "ragnarok_packets", "rand 0.8.5", "random_color", "rayon", @@ -770,6 +1014,7 @@ dependencies = [ "rusttype", "serde", "serde-xml-rs", + "tokio", "vulkano", "vulkano-shaders", "vulkano-win", @@ -801,6 +1046,18 @@ dependencies = [ "serde", ] +[[package]] +name = "korangar_networking" +version = "0.1.0" +dependencies = [ + "korangar_debug", + "ragnarok_bytes", + "ragnarok_packets", + "reqwest", + "serde", + "tokio", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -815,9 +1072,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libloading" @@ -836,7 +1093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -864,9 +1121,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -914,9 +1171,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memmap2" @@ -936,6 +1193,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -974,6 +1237,24 @@ dependencies = [ "rustc-hash", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.7.0" @@ -1030,9 +1311,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41" dependencies = [ "num-bigint", "num-complex", @@ -1073,9 +1354,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -1096,9 +1377,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1152,7 +1433,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.60", ] [[package]] @@ -1190,12 +1471,65 @@ dependencies = [ "objc-sys", ] +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -1231,9 +1565,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -1241,15 +1575,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -1272,6 +1606,38 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.30" @@ -1309,9 +1675,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -1327,9 +1693,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1343,7 +1709,7 @@ dependencies = [ ] [[package]] -name = "ragnarok_networking" +name = "ragnarok_packets" version = "0.1.0" dependencies = [ "derive-new", @@ -1358,7 +1724,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.60", ] [[package]] @@ -1421,7 +1787,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.12", + "getrandom 0.2.15", ] [[package]] @@ -1495,6 +1861,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "regex" version = "1.10.4" @@ -1520,9 +1895,51 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] [[package]] name = "ron" @@ -1530,7 +1947,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64", + "base64 0.21.7", "bitflags 2.5.0", "serde", "serde_derive", @@ -1545,6 +1962,12 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1553,9 +1976,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -1564,6 +1987,22 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" + [[package]] name = "rusttype" version = "0.9.3" @@ -1594,6 +2033,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1619,11 +2067,34 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -1642,21 +2113,33 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.60", ] [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ + "form_urlencoded", "itoa", "ryu", "serde", @@ -1683,12 +2166,30 @@ dependencies = [ "roxmltree", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "slotmap" version = "1.0.7" @@ -1723,6 +2224,16 @@ dependencies = [ "wayland-protocols", ] +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -1751,33 +2262,72 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.55" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.60", ] [[package]] @@ -1826,6 +2376,74 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.6.5" @@ -1843,6 +2461,60 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "ttf-parser" version = "0.15.2" @@ -1855,12 +2527,44 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" @@ -1932,7 +2636,7 @@ dependencies = [ "proc-macro2", "quote", "shaderc", - "syn 2.0.55", + "syn 2.0.60", "vulkano", ] @@ -1958,6 +2662,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -1991,10 +2704,22 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.60", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -2013,7 +2738,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2143,11 +2868,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -2162,7 +2887,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -2189,7 +2914,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -2224,17 +2949,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -2251,9 +2977,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -2269,9 +2995,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -2287,9 +3013,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -2305,9 +3037,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -2323,9 +3055,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -2341,9 +3073,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -2359,9 +3091,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winit" @@ -2407,6 +3139,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -2426,9 +3168,9 @@ checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" [[package]] name = "xml-rs" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" [[package]] name = "xmlparser" @@ -2444,22 +3186,22 @@ checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "087eca3c1eaf8c47b94d02790dd086cd594b912d2043d4de4bfdd466b3befb7c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "6f4b6c273f496d8fd4eaf18853e6b448760225dc030ff2c485a786859aea6393" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.60", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e99d6d06..b751646d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,10 +7,11 @@ bitflags = "2.4.2" cgmath = { version = "0.18", features = ["serde"] } chrono = "0.4" derive-new = "0.6.0" -korangar_interface = { path = "korangar_interface" } korangar_debug = { path = "korangar_debug" } +korangar_interface = { path = "korangar_interface" } +korangar_networking = { path = "korangar_networking" } num = "0.4.1" ragnarok_bytes = { path = "ragnarok_bytes" } -ragnarok_networking = { path = "ragnarok_networking" } +ragnarok_packets = { path = "ragnarok_packets" } ragnarok_procedural = { path = "ragnarok_procedural" } serde = "1.0.137" diff --git a/korangar/Cargo.toml b/korangar/Cargo.toml index 56cc2664..55c1097e 100644 --- a/korangar/Cargo.toml +++ b/korangar/Cargo.toml @@ -12,6 +12,7 @@ collision = { git = "https://github.com/rustgd/collision-rs.git" } derive-new = { workspace = true } image = "0.24.2" korangar_interface = { workspace = true, features = ["serde", "cgmath"] } +korangar_networking = { workspace = true, features = ["debug"] } lunify = "1.1.0" mlua = { version = "0.8", features = ["lua51", "vendored"] } num = { workspace = true } @@ -20,7 +21,7 @@ option-ext = "0.2.0" pathfinding = "2.2.2" korangar_debug = { workspace = true, optional = true } ragnarok_bytes = { workspace = true, features = ["derive", "cgmath"] } -ragnarok_networking = { workspace = true, features = ["derive", "interface"] } +ragnarok_packets = { workspace = true, features = ["derive", "interface", "packet-to-prototype-element"] } rand = "0.8.5" random_color = { version = "0.6.1", optional = true } rayon = "1.5.3" @@ -28,6 +29,7 @@ ron = "0.8.0" rusttype = { version = "0.9.2", features = ["gpu_cache"] } serde = { workspace = true } serde-xml-rs = "0.6.0" +tokio = { version = "1.37.0", features = ["full"] } vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "db3df4e55f80c137ea6187250957eb92c2291627" } vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "db3df4e55f80c137ea6187250957eb92c2291627" } vulkano-win = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "db3df4e55f80c137ea6187250957eb92c2291627" } @@ -38,6 +40,6 @@ yazi = "0.1.4" [features] patched_as_folder = [] -debug = ["korangar_debug", "ragnarok_networking/debug", "random_color"] +debug = ["korangar_debug", "ragnarok_packets/debug", "random_color"] unicode = ["korangar_debug/unicode"] plain = ["debug"] diff --git a/korangar/src/graphics/particles/mod.rs b/korangar/src/graphics/particles/mod.rs index 7e9cea60..910b237e 100644 --- a/korangar/src/graphics/particles/mod.rs +++ b/korangar/src/graphics/particles/mod.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use cgmath::{Vector2, Vector3}; use derive_new::new; -use ragnarok_networking::{EntityId, QuestColor, QuestEffectPacket}; +use ragnarok_packets::{EntityId, QuestColor, QuestEffectPacket}; use rand::{thread_rng, Rng}; use crate::graphics::*; diff --git a/korangar/src/graphics/renderers/deferred/mod.rs b/korangar/src/graphics/renderers/deferred/mod.rs index ca45fd51..80602189 100644 --- a/korangar/src/graphics/renderers/deferred/mod.rs +++ b/korangar/src/graphics/renderers/deferred/mod.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use cgmath::SquareMatrix; use cgmath::{Matrix4, Vector2, Vector3}; use korangar_interface::application::FontSizeTrait; -use ragnarok_networking::EntityId; +use ragnarok_packets::EntityId; use vulkano::device::{DeviceOwned, Queue}; use vulkano::format::Format; use vulkano::image::Image; diff --git a/korangar/src/graphics/renderers/mod.rs b/korangar/src/graphics/renderers/mod.rs index 38634ac3..5db22cac 100644 --- a/korangar/src/graphics/renderers/mod.rs +++ b/korangar/src/graphics/renderers/mod.rs @@ -48,7 +48,7 @@ use korangar_debug::profile_block; #[cfg(feature = "debug")] use korangar_debug::profiling::Profiler; use option_ext::OptionExt; -use ragnarok_networking::EntityId; +use ragnarok_packets::EntityId; use vulkano::buffer::{Buffer, BufferUsage, Subbuffer}; use vulkano::command_buffer::{ AutoCommandBufferBuilder, ClearAttachment, ClearRect, CommandBufferUsage, CopyImageToBufferInfo, PrimaryAutoCommandBuffer, diff --git a/korangar/src/graphics/renderers/picker/entity/mod.rs b/korangar/src/graphics/renderers/picker/entity/mod.rs index 1bca372e..203ccd72 100644 --- a/korangar/src/graphics/renderers/picker/entity/mod.rs +++ b/korangar/src/graphics/renderers/picker/entity/mod.rs @@ -4,7 +4,7 @@ fragment_shader!("src/graphics/renderers/picker/entity/fragment_shader.glsl"); use std::sync::Arc; use cgmath::{Vector2, Vector3}; -use ragnarok_networking::EntityId; +use ragnarok_packets::EntityId; use vulkano::descriptor_set::WriteDescriptorSet; use vulkano::device::{Device, DeviceOwned}; use vulkano::image::sampler::Sampler; diff --git a/korangar/src/graphics/renderers/picker/mod.rs b/korangar/src/graphics/renderers/picker/mod.rs index cc7c337f..bf9d63c7 100644 --- a/korangar/src/graphics/renderers/picker/mod.rs +++ b/korangar/src/graphics/renderers/picker/mod.rs @@ -8,7 +8,7 @@ mod tile; use std::sync::Arc; use cgmath::{Matrix4, Vector2, Vector3}; -use ragnarok_networking::EntityId; +use ragnarok_packets::EntityId; use vulkano::device::{DeviceOwned, Queue}; use vulkano::format::Format; use vulkano::pipeline::graphics::viewport::Viewport; diff --git a/korangar/src/graphics/renderers/picker/target.rs b/korangar/src/graphics/renderers/picker/target.rs index edd2147b..28e188d0 100644 --- a/korangar/src/graphics/renderers/picker/target.rs +++ b/korangar/src/graphics/renderers/picker/target.rs @@ -1,4 +1,4 @@ -use ragnarok_networking::EntityId; +use ragnarok_packets::EntityId; #[cfg(feature = "debug")] use crate::world::MarkerIdentifier; @@ -100,7 +100,7 @@ impl From for u32 { #[cfg(test)] #[allow(clippy::unusual_byte_groupings)] mod encoding { - use ragnarok_networking::EntityId; + use ragnarok_packets::EntityId; use crate::graphics::PickerTarget; #[cfg(feature = "debug")] diff --git a/korangar/src/graphics/renderers/shadow/mod.rs b/korangar/src/graphics/renderers/shadow/mod.rs index 015e0d8c..a40ee170 100644 --- a/korangar/src/graphics/renderers/shadow/mod.rs +++ b/korangar/src/graphics/renderers/shadow/mod.rs @@ -5,7 +5,7 @@ mod indicator; use std::sync::Arc; use cgmath::{Matrix4, Vector2, Vector3}; -use ragnarok_networking::EntityId; +use ragnarok_packets::EntityId; use serde::{Deserialize, Serialize}; use vulkano::device::{DeviceOwned, Queue}; use vulkano::format::{ClearValue, Format}; diff --git a/korangar/src/input/event.rs b/korangar/src/input/event.rs index e9cae82a..b73cb192 100644 --- a/korangar/src/input/event.rs +++ b/korangar/src/input/event.rs @@ -1,7 +1,7 @@ use cgmath::Vector2; use korangar_interface::event::ClickAction; use korangar_interface::ElementEvent; -use ragnarok_networking::{AccountId, CharacterId, CharacterServerInformation, EntityId}; +use ragnarok_packets::{AccountId, CharacterId, CharacterServerInformation, EntityId, TilePosition}; use super::HotbarSlot; use crate::interface::application::{InterfaceSettings, InternalThemeKind}; @@ -50,7 +50,7 @@ pub enum UserEvent { SwitchCharacterSlot(usize), RequestPlayerMove(Vector2), RequestPlayerInteract(EntityId), - RequestWarpToMap(String, Vector2), + RequestWarpToMap(String, TilePosition), SendMessage(String), NextDialog(EntityId), CloseDialog(EntityId), diff --git a/korangar/src/input/mod.rs b/korangar/src/input/mod.rs index 7e183f6e..73f66413 100644 --- a/korangar/src/input/mod.rs +++ b/korangar/src/input/mod.rs @@ -10,7 +10,7 @@ use korangar_interface::elements::{ElementCell, Focus}; use korangar_interface::event::ClickAction; use korangar_interface::state::{PlainTrackedState, TrackedState}; use korangar_interface::Interface; -use ragnarok_networking::ClientTick; +use ragnarok_packets::ClientTick; use winit::dpi::PhysicalPosition; use winit::event::{ElementState, MouseButton, MouseScrollDelta, VirtualKeyCode}; diff --git a/korangar/src/interface/cursor/mod.rs b/korangar/src/interface/cursor/mod.rs index b24ab4ab..fd435a73 100644 --- a/korangar/src/interface/cursor/mod.rs +++ b/korangar/src/interface/cursor/mod.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use ragnarok_networking::ClientTick; +use ragnarok_packets::ClientTick; use super::application::InterfaceSettings; use super::layout::{ScreenClip, ScreenPosition, ScreenSize}; diff --git a/korangar/src/interface/dialog.rs b/korangar/src/interface/dialog.rs index a1b17fb5..a907440c 100644 --- a/korangar/src/interface/dialog.rs +++ b/korangar/src/interface/dialog.rs @@ -1,6 +1,6 @@ use derive_new::new; use korangar_interface::state::{PlainTrackedState, TrackedStateExt, TrackedStateVec}; -use ragnarok_networking::EntityId; +use ragnarok_packets::EntityId; use super::elements::DialogElement; use super::windows::DialogWindow; diff --git a/korangar/src/interface/elements/containers/character.rs b/korangar/src/interface/elements/containers/character.rs index 2348f27b..be938d44 100644 --- a/korangar/src/interface/elements/containers/character.rs +++ b/korangar/src/interface/elements/containers/character.rs @@ -7,7 +7,7 @@ use korangar_interface::event::{ChangeEvent, ClickAction, HoverInformation}; use korangar_interface::layout::PlacementResolver; use korangar_interface::state::{PlainRemote, Remote}; use korangar_interface::{dimension_bound, size_bound}; -use ragnarok_networking::CharacterInformation; +use ragnarok_packets::CharacterInformation; use crate::graphics::{Color, InterfaceRenderer, Renderer}; use crate::input::{MouseInputMode, UserEvent}; diff --git a/korangar/src/interface/elements/containers/dialog.rs b/korangar/src/interface/elements/containers/dialog.rs index f461da98..0b57fbe5 100644 --- a/korangar/src/interface/elements/containers/dialog.rs +++ b/korangar/src/interface/elements/containers/dialog.rs @@ -3,7 +3,7 @@ use korangar_interface::event::{ChangeEvent, HoverInformation}; use korangar_interface::layout::PlacementResolver; use korangar_interface::size_bound; use korangar_interface::state::{PlainRemote, Remote}; -use ragnarok_networking::EntityId; +use ragnarok_packets::EntityId; use crate::graphics::{Color, InterfaceRenderer, Renderer}; use crate::input::{MouseInputMode, UserEvent}; diff --git a/korangar/src/interface/elements/containers/equipment.rs b/korangar/src/interface/elements/containers/equipment.rs index 5bdf0120..fed61a35 100644 --- a/korangar/src/interface/elements/containers/equipment.rs +++ b/korangar/src/interface/elements/containers/equipment.rs @@ -5,7 +5,7 @@ use korangar_interface::event::{ChangeEvent, HoverInformation}; use korangar_interface::layout::PlacementResolver; use korangar_interface::state::{PlainRemote, Remote}; use korangar_interface::{dimension_bound, size_bound}; -use ragnarok_networking::EquipPosition; +use ragnarok_packets::EquipPosition; use crate::graphics::{Color, InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; @@ -41,9 +41,36 @@ impl EquipmentContainer { (0..SLOT_POSITIONS.len()) .map(|index| { let slot = SLOT_POSITIONS[index]; + let display_name = match slot { + EquipPosition::None => panic!(), + EquipPosition::HeadLower => "Head lower", + EquipPosition::HeadMiddle => "Head middle", + EquipPosition::HeadTop => "Head top", + EquipPosition::RightHand => "Right hand", + EquipPosition::LeftHand => "Left hand", + EquipPosition::Armor => "Armor", + EquipPosition::Shoes => "Shoes", + EquipPosition::Garment => "Garment", + EquipPosition::LeftAccessory => "Left accessory", + EquipPosition::RigthAccessory => "Right accessory", + EquipPosition::CostumeHeadTop => "Costume head top", + EquipPosition::CostumeHeadMiddle => "Costume head middle", + EquipPosition::CostumeHeadLower => "Costume head lower", + EquipPosition::CostumeGarment => "Costume garment", + EquipPosition::Ammo => "Ammo", + EquipPosition::ShadowArmor => "Shadow ammo", + EquipPosition::ShadowWeapon => "Shadow weapon", + EquipPosition::ShadowShield => "Shadow shield", + EquipPosition::ShadowShoes => "Shadow shoes", + EquipPosition::ShadowRightAccessory => "Shadow right accessory", + EquipPosition::ShadowLeftAccessory => "Shadow left accessory", + EquipPosition::LeftRightAccessory => "Accessory", + EquipPosition::LeftRightHand => "Two hand weapon", + EquipPosition::ShadowLeftRightAccessory => "Shadow accessory", + }; let text = Text::default() - .with_text(slot.display_name().to_string()) + .with_text(display_name.to_string()) .with_foreground_color(|_| Color::monochrome_u8(200)) .with_width(dimension_bound!(!)) .wrap(); diff --git a/korangar/src/interface/elements/containers/friends.rs b/korangar/src/interface/elements/containers/friends.rs index eee50ecd..14409306 100644 --- a/korangar/src/interface/elements/containers/friends.rs +++ b/korangar/src/interface/elements/containers/friends.rs @@ -1,28 +1,27 @@ -use std::cell::{RefCell, UnsafeCell}; +use std::cell::RefCell; use std::rc::{Rc, Weak}; -use korangar_interface::elements::{ - ButtonBuilder, ContainerState, Element, ElementCell, ElementState, ElementWrap, Expandable, Focus, WeakElementCell, -}; +use korangar_interface::elements::{ButtonBuilder, ContainerState, Element, ElementCell, ElementState, ElementWrap, Expandable, Focus}; use korangar_interface::event::{ChangeEvent, HoverInformation}; use korangar_interface::layout::PlacementResolver; use korangar_interface::size_bound; use korangar_interface::state::{PlainRemote, Remote}; -use ragnarok_networking::Friend; +use ragnarok_packets::Friend; use crate::graphics::{InterfaceRenderer, Renderer}; use crate::input::{MouseInputMode, UserEvent}; use crate::interface::application::InterfaceSettings; use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::linked::LinkedElement; use crate::interface::theme::InterfaceTheme; pub struct FriendView { - friends: PlainRemote>>)>>, + friends: PlainRemote>, state: ContainerState, } impl FriendView { - pub fn new(friends: PlainRemote>>)>>) -> Self { + pub fn new(friends: PlainRemote>) -> Self { let elements = { let friends = friends.get(); @@ -30,7 +29,7 @@ impl FriendView { .iter() .map(|(friend, linked_element)| { let element = Self::friend_to_element(friend); - unsafe { *linked_element.get() = Some(Rc::downgrade(&element)) }; + linked_element.link(&element); element }) .collect() @@ -114,15 +113,16 @@ impl Element for FriendView { // Remove elements of old friends from the start of the list and add new friends // to the list. self.friends.get().iter().enumerate().for_each(|(index, (friend, linked_element))| { - if let Some(linked_element) = unsafe { &(*linked_element.get()) } { - while !std::ptr::addr_eq(linked_element.as_ptr(), Rc::downgrade(&self.state.elements[index]).as_ptr()) { + if linked_element.is_linked() { + while !linked_element.is_linked_to(&self.state.elements[index]) { self.state.elements.remove(index); } } else { let element = Self::friend_to_element(friend); - unsafe { *linked_element.get() = Some(Rc::downgrade(&element)) }; let weak_self = self.state.state.self_element.clone(); + linked_element.link(&element); + element.borrow_mut().link_back(Rc::downgrade(&element), weak_self); self.state.elements.insert(index, element); diff --git a/korangar/src/interface/elements/containers/mod.rs b/korangar/src/interface/elements/containers/mod.rs index 7a01f8e6..a5f2da5e 100644 --- a/korangar/src/interface/elements/containers/mod.rs +++ b/korangar/src/interface/elements/containers/mod.rs @@ -15,5 +15,5 @@ pub use self::friends::FriendView; pub use self::hotbar::HotbarContainer; pub use self::inventory::InventoryContainer; #[cfg(feature = "debug")] -pub use self::packet::{PacketEntry, PacketView}; +pub use self::packet::{PacketHistoryCallback, PacketHistoryRemote, PacketView}; pub use self::skill_tree::SkillTreeContainer; diff --git a/korangar/src/interface/elements/containers/packet.rs b/korangar/src/interface/elements/containers/packet.rs index 9f074140..7b8b1671 100644 --- a/korangar/src/interface/elements/containers/packet.rs +++ b/korangar/src/interface/elements/containers/packet.rs @@ -1,57 +1,110 @@ -use std::cell::{RefCell, UnsafeCell}; +use std::cell::RefCell; use std::fmt::{Display, Formatter, Result}; use std::rc::{Rc, Weak}; +use std::sync::{Mutex, MutexGuard}; use korangar_debug::profiling::RingBuffer; -use korangar_interface::elements::{ - ContainerState, Element, ElementCell, ElementState, ElementWrap, Focus, PrototypeElement, WeakElementCell, -}; +use korangar_interface::application::Application; +use korangar_interface::elements::{ContainerState, Element, ElementCell, ElementState, ElementWrap, Expandable, Focus, PrototypeElement}; use korangar_interface::event::{ChangeEvent, HoverInformation}; use korangar_interface::layout::PlacementResolver; use korangar_interface::size_bound; use korangar_interface::state::{PlainRemote, Remote, RemoteClone}; +use ragnarok_bytes::{ByteStream, ConversionError, ConversionResult, FromBytes}; +use ragnarok_packets::handler::PacketCallback; +use ragnarok_packets::{IncomingPacket, OutgoingPacket, PacketHeader}; use crate::graphics::{InterfaceRenderer, Renderer}; use crate::input::MouseInputMode; use crate::interface::application::InterfaceSettings; use crate::interface::layout::{ScreenClip, ScreenPosition, ScreenSize}; +use crate::interface::linked::LinkedElement; use crate::interface::theme::InterfaceTheme; -struct HiddenElement; +#[derive(Clone)] +struct UnknownPacket { + pub bytes: Vec, +} -impl Element for HiddenElement { - fn get_state(&self) -> &ElementState { +impl IncomingPacket for UnknownPacket { + const HEADER: PacketHeader = PacketHeader(0); + const IS_PING: bool = false; + + fn payload_from_bytes(byte_stream: &mut ByteStream) -> ConversionResult { + let _ = byte_stream; unimplemented!() } - fn get_state_mut(&mut self) -> &mut ElementState { - unimplemented!() + fn to_prototype_element(&self) -> Box + Send> { + Box::new(self.clone()) } +} - fn resolve( - &mut self, - _placement_resolver: &mut PlacementResolver, - _application: &InterfaceSettings, - _theme: &InterfaceTheme, - ) { - unimplemented!() +impl PrototypeElement for UnknownPacket { + fn to_element(&self, display: String) -> ElementCell { + let mut byte_stream = ByteStream::<()>::without_metadata(&self.bytes); + + let elements = match self.bytes.len() >= 2 { + true => { + let signature = PacketHeader::from_bytes(&mut byte_stream).unwrap(); + let header = format!("0x{:0>4x}", signature.0); + let data = &self.bytes[byte_stream.get_offset()..]; + + vec![header.to_element("header".to_owned()), data.to_element("data".to_owned())] + } + false => { + vec![self.bytes.to_element("data".to_owned())] + } + }; + + Expandable::new(display, elements, false).wrap() } +} - fn render( - &self, - _render_target: &mut ::Target, - _render: &InterfaceRenderer, - _application: &InterfaceSettings, - _theme: &InterfaceTheme, - _parent_position: ScreenPosition, - _screen_clip: ScreenClip, - _hovered_element: Option<&dyn Element>, - _focused_element: Option<&dyn Element>, - _mouse_mode: &MouseInputMode, - _second_theme: bool, - ) { +#[derive(Clone)] +struct ErrorPacket { + pub bytes: Vec, + pub error: Box, +} + +impl IncomingPacket for ErrorPacket { + const HEADER: PacketHeader = PacketHeader(0); + const IS_PING: bool = false; + + fn payload_from_bytes(byte_stream: &mut ByteStream) -> ConversionResult { + let _ = byte_stream; unimplemented!() } + + fn to_prototype_element(&self) -> Box + Send> { + Box::new(self.clone()) + } +} + +impl PrototypeElement for ErrorPacket { + fn to_element(&self, display: String) -> ElementCell { + let mut byte_stream = ByteStream::<()>::without_metadata(&self.bytes); + let error = format!("{:?}", self.error); + + let elements = match self.bytes.len() >= 2 { + true => { + let signature = PacketHeader::from_bytes(&mut byte_stream).unwrap(); + let header = format!("0x{:0>4x}", signature.0); + let data = &self.bytes[byte_stream.get_offset()..]; + + vec![ + header.to_element("header".to_owned()), + error.to_element("error".to_owned()), + data.to_element("data".to_owned()), + ] + } + false => { + vec![error.to_element("error".to_owned()), self.bytes.to_element("data".to_owned())] + } + }; + + Expandable::new(display, elements, false).wrap() + } } enum Direction { @@ -68,26 +121,26 @@ impl Display for Direction { } } -pub struct PacketEntry { - element: Box>, +struct PacketEntry { + element: Box + Send>, name: &'static str, is_ping: bool, direction: Direction, } impl PacketEntry { - pub fn new_incoming(element: &(impl PrototypeElement + Clone + 'static), name: &'static str, is_ping: bool) -> Self { + pub fn new_incoming(element: Box + Send>, name: &'static str, is_ping: bool) -> Self { Self { - element: Box::new(element.clone()), + element, name, is_ping, direction: Direction::Incoming, } } - pub fn new_outgoing(element: &(impl PrototypeElement + Clone + 'static), name: &'static str, is_ping: bool) -> Self { + pub fn new_outgoing(element: Box + Send>, name: &'static str, is_ping: bool) -> Self { Self { - element: Box::new(element.clone()), + element, name, is_ping, direction: Direction::Outgoing, @@ -103,24 +156,118 @@ impl PacketEntry { } } -pub struct PacketView { - packets: PlainRemote>>), N>>, +#[derive(Clone)] +pub struct PacketHistoryCallback { + buffer_pointer: &'static Mutex<(RingBuffer<(PacketEntry, LinkedElement), 256>, usize)>, +} + +impl PacketHistoryCallback { + /// SAFETY: This function is unsafe because it leaks memory. It should + /// only be called once during the lifetime of the program. + pub unsafe fn new() -> Self { + let buffer_pointer = Box::leak(Box::new(Mutex::new((RingBuffer::default(), 0)))); + + Self { buffer_pointer } + } + + pub fn remote(&self) -> PacketHistoryRemote { + PacketHistoryRemote { + buffer_pointer: self.buffer_pointer, + version: 0, + } + } +} + +impl PacketCallback for PacketHistoryCallback { + fn incoming_packet(&self, packet: &Packet) + where + Packet: IncomingPacket, + { + let mut lock = self.buffer_pointer.lock().unwrap(); + + let prototype_element = packet.to_prototype_element(); + let entry = PacketEntry::new_incoming(prototype_element, std::any::type_name::(), Packet::IS_PING); + + lock.0.push((entry, LinkedElement::new())); + lock.1 += 1; + } + + fn outgoing_packet(&self, packet: &Packet) + where + Packet: OutgoingPacket, + { + let mut lock = self.buffer_pointer.lock().unwrap(); + + let prototype_element = packet.to_prototype_element(); + let entry = PacketEntry::new_outgoing(prototype_element, std::any::type_name::(), Packet::IS_PING); + + lock.0.push((entry, LinkedElement::new())); + lock.1 += 1; + } + + fn unknown_packet(&self, bytes: Vec) { + let mut lock = self.buffer_pointer.lock().unwrap(); + + let packet = UnknownPacket { bytes }; + let prototype_element = packet.to_prototype_element(); + let entry = PacketEntry::new_incoming(prototype_element, "^FF8810� Unknown �^000000", false); + + lock.0.push((entry, LinkedElement::new())); + lock.1 += 1; + } + + fn failed_packet(&self, bytes: Vec, error: Box) { + let mut lock = self.buffer_pointer.lock().unwrap(); + + let packet = ErrorPacket { bytes, error }; + let prototype_element = packet.to_prototype_element(); + let entry = PacketEntry::new_incoming(prototype_element, "^FF4444✖ Error ✖^000000", false); + + lock.0.push((entry, LinkedElement::new())); + lock.1 += 1; + } +} + +#[derive(Clone)] +pub struct PacketHistoryRemote { + buffer_pointer: &'static Mutex<(RingBuffer<(PacketEntry, LinkedElement), 256>, usize)>, + version: usize, +} + +impl PacketHistoryRemote { + pub fn consume_changed(&mut self) -> bool { + let lock = self.buffer_pointer.lock().unwrap(); + + let version = lock.1; + let changed = version != self.version; + self.version = version; + + changed + } + + fn get(&self) -> MutexGuard<'_, (RingBuffer<(PacketEntry, LinkedElement), 256>, usize)> { + self.buffer_pointer.lock().unwrap() + } + + pub fn is_empty(&self) -> bool { + self.buffer_pointer.lock().unwrap().0.is_empty() + } +} + +pub struct PacketView { + packets: PacketHistoryRemote, show_pings: PlainRemote, - hidden_element: ElementCell, state: ContainerState, } -impl PacketView { - pub fn new( - packets: PlainRemote>>), N>>, - show_pings: PlainRemote, - ) -> Self { - let hidden_element = HiddenElement.wrap(); +impl PacketView { + pub fn new(packets: PacketHistoryRemote, show_pings: PlainRemote) -> Self { let elements = { let packets = packets.get(); let show_pings = show_pings.cloned(); packets + .0 .iter() .filter_map(|(packet, linked_element)| { let show_packet = show_pings || !packet.is_ping(); @@ -128,11 +275,11 @@ impl PacketView { match show_packet { true => { let element = PacketEntry::to_element(packet); - unsafe { *linked_element.get() = Some(Rc::downgrade(&element)) }; + linked_element.link(&element); Some(element) } false => { - unsafe { *linked_element.get() = Some(Rc::downgrade(&hidden_element)) }; + linked_element.link_hidden(); None } } @@ -143,13 +290,12 @@ impl PacketView { Self { packets, show_pings, - hidden_element, state: ContainerState::new(elements), } } } -impl Element for PacketView { +impl Element for PacketView { fn get_state(&self) -> &ElementState { &self.state.state } @@ -202,27 +348,10 @@ impl Element for PacketView { let mut resolve = false; if self.show_pings.consume_changed() | self.packets.consume_changed() { - fn compare( - linked_element: &UnsafeCell>>, - element: &ElementCell, - ) -> bool { - let linked_element = unsafe { &*linked_element.get() }; - let linked_element = linked_element.as_ref().map(|weak| weak.as_ptr()); - linked_element.is_some_and(|pointer| !std::ptr::addr_eq(pointer, Rc::downgrade(element).as_ptr())) - } - // Remove elements of packets that are no longer in the list. - if let Some(first_visible_packet) = self - .packets - .get() - .iter() - .find(|(_, linked_element)| compare(linked_element, &self.hidden_element)) - { - let first_visible_element = unsafe { &*first_visible_packet.1.get() }; - let first_visible_element = first_visible_element.as_ref().unwrap().as_ptr(); - + if let Some(first_visible_packet) = self.packets.get().0.iter().find(|(_, linked_element)| !linked_element.is_hidden()) { for _index in 0..self.state.elements.len() { - if !std::ptr::addr_eq(first_visible_element, Rc::downgrade(&self.state.elements[0]).as_ptr()) { + if !first_visible_packet.1.is_linked_to(&self.state.elements[0]) { self.state.elements.remove(0); resolve = true; } else { @@ -240,17 +369,17 @@ impl Element for PacketView { // Add or remove elements that need to be shown/hidden based on filtering. Also // append new elements for packets that are new. - self.packets.get().iter().for_each(|(packet, linked_element)| { + self.packets.get().0.iter().for_each(|(packet, linked_element)| { // Getting here means thatt the packet was already processed once. let show_packet = show_pings || !packet.is_ping(); - if let Some(linked_element) = unsafe { &mut (*linked_element.get()) } { - let was_hidden = std::ptr::addr_eq(linked_element.as_ptr(), Rc::downgrade(&self.hidden_element).as_ptr()); + if linked_element.is_linked() { + let was_hidden = linked_element.is_hidden(); // Packet was previously hidden but should be visible now. if show_packet && was_hidden { let element = PacketEntry::to_element(packet); - *linked_element = Rc::downgrade(&element); + linked_element.link(&element); element .borrow_mut() .link_back(Rc::downgrade(&element), self.state.state.self_element.clone()); @@ -261,7 +390,7 @@ impl Element for PacketView { // Packet was previously visible but now should be hidden. if !show_packet && !was_hidden { - *linked_element = Rc::downgrade(&self.hidden_element); + linked_element.link_hidden(); self.state.elements.remove(index); resolve = true; @@ -271,7 +400,7 @@ impl Element for PacketView { match show_packet { true => { let element = PacketEntry::to_element(packet); - unsafe { *linked_element.get() = Some(Rc::downgrade(&element)) }; + linked_element.link(&element); element .borrow_mut() .link_back(Rc::downgrade(&element), self.state.state.self_element.clone()); @@ -280,7 +409,7 @@ impl Element for PacketView { resolve = true; } false => { - unsafe { *linked_element.get() = Some(Rc::downgrade(&self.hidden_element)) }; + linked_element.link_hidden(); } } } diff --git a/korangar/src/interface/elements/miscellanious/chat/builder.rs b/korangar/src/interface/elements/miscellanious/chat/builder.rs index bee457f4..717d81c0 100644 --- a/korangar/src/interface/elements/miscellanious/chat/builder.rs +++ b/korangar/src/interface/elements/miscellanious/chat/builder.rs @@ -5,8 +5,8 @@ use korangar_interface::builder::Unset; use korangar_interface::state::PlainRemote; use super::Chat; +use crate::interface::windows::ChatMessage; use crate::loaders::FontLoader; -use crate::network::ChatMessage; /// Type state [`Chat`] builder. This builder utilizes the type system to /// prevent calling the same method multiple times and calling @@ -51,7 +51,6 @@ impl ChatBuilder>, Rc>> { Chat { messages, font_loader, - stamp: true, state: Default::default(), } } diff --git a/korangar/src/interface/elements/miscellanious/chat/mod.rs b/korangar/src/interface/elements/miscellanious/chat/mod.rs index 97db1dc5..e87c76f3 100644 --- a/korangar/src/interface/elements/miscellanious/chat/mod.rs +++ b/korangar/src/interface/elements/miscellanious/chat/mod.rs @@ -16,14 +16,12 @@ use crate::input::MouseInputMode; use crate::interface::application::InterfaceSettings; use crate::interface::layout::{ScreenClip, ScreenPosition}; use crate::interface::theme::InterfaceTheme; +use crate::interface::windows::ChatMessage; use crate::loaders::FontLoader; -use crate::network::ChatMessage; pub struct Chat { messages: PlainRemote>, font_loader: Rc>, - // TODO: make this Remote - stamp: bool, state: ElementState, } @@ -51,14 +49,14 @@ impl Element for Chat { // padding. let mut height = 5.0 * application.get_scaling_factor(); - // NOTE: Dividing by the scaling is done to counteract the scaling being applied + // Dividing by the scaling is done to counteract the scaling being applied // twice per message. It's not the cleanest solution but it works. for message in self.messages.get().iter() { height += self .font_loader .borrow() .get_text_dimensions( - message.stamped_text(self.stamp), + &message.text, theme.chat.font_size.get().scaled(application.get_scaling()), placement_resolver.get_available().width, ) @@ -94,7 +92,7 @@ impl Element for Chat { let mut offset = 0.0; for message in self.messages.get().iter() { - let text = message.stamped_text(self.stamp); + let text = &message.text; renderer.render_text( text, @@ -106,12 +104,20 @@ impl Element for Chat { theme.chat.font_size.get(), ); - // NOTE: Dividing by the scaling is done to counteract the scaling being applied + let message_color = match message.color { + korangar_networking::MessageColor::Rgb { red, green, blue } => Color::rgb_u8(red, green, blue), + korangar_networking::MessageColor::Broadcast => theme.chat.broadcast_color.get(), + korangar_networking::MessageColor::Server => theme.chat.server_color.get(), + korangar_networking::MessageColor::Error => theme.chat.broadcast_color.get(), + korangar_networking::MessageColor::Information => theme.chat.information_color.get(), + }; + + // Dividing by the scaling is done to counteract the scaling being applied // twice per message. It's not the cleanest solution but it works. offset += renderer.render_text( text, ScreenPosition::only_top(offset), - message.color, + message_color, theme.chat.font_size.get(), ) / application.get_scaling_factor(); } diff --git a/korangar/src/interface/linked.rs b/korangar/src/interface/linked.rs new file mode 100644 index 00000000..8b834de5 --- /dev/null +++ b/korangar/src/interface/linked.rs @@ -0,0 +1,45 @@ +use std::cell::UnsafeCell; + +use korangar_interface::elements::ElementCell; + +use crate::interface::application::InterfaceSettings; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum LinkedElementInner { + Set(usize), + Hidden, + Unset, +} + +pub struct LinkedElement { + inner: UnsafeCell, +} + +impl LinkedElement { + pub fn new() -> Self { + Self { + inner: UnsafeCell::new(LinkedElementInner::Unset), + } + } + + pub fn link(&self, element: &ElementCell) { + let element_address = element.as_ptr() as *const () as usize; + unsafe { *self.inner.get() = LinkedElementInner::Set(element_address) }; + } + + pub fn link_hidden(&self) { + unsafe { *self.inner.get() = LinkedElementInner::Hidden }; + } + + pub fn is_linked(&self) -> bool { + unsafe { *self.inner.get() != LinkedElementInner::Unset } + } + + pub fn is_hidden(&self) -> bool { + unsafe { *self.inner.get() == LinkedElementInner::Hidden } + } + + pub fn is_linked_to(&self, element: &ElementCell) -> bool { + unsafe { *self.inner.get() == LinkedElementInner::Set(element.as_ptr() as *const () as usize) } + } +} diff --git a/korangar/src/interface/mod.rs b/korangar/src/interface/mod.rs index 67a7416e..990e5966 100644 --- a/korangar/src/interface/mod.rs +++ b/korangar/src/interface/mod.rs @@ -5,5 +5,6 @@ pub mod elements; pub mod application; pub mod cursor; pub mod dialog; +pub mod linked; pub mod resource; pub mod windows; diff --git a/korangar/src/interface/resource.rs b/korangar/src/interface/resource.rs index d62fc7f2..1891010c 100644 --- a/korangar/src/interface/resource.rs +++ b/korangar/src/interface/resource.rs @@ -1,4 +1,4 @@ -use ragnarok_networking::EquipPosition; +use ragnarok_packets::EquipPosition; use crate::input::HotbarSlot; use crate::inventory::{Item, Skill}; diff --git a/korangar/src/interface/theme/mod.rs b/korangar/src/interface/theme/mod.rs index 7978ac14..df6ded72 100644 --- a/korangar/src/interface/theme/mod.rs +++ b/korangar/src/interface/theme/mod.rs @@ -850,6 +850,10 @@ impl korangar_interface::theme::InputTheme for InputTheme { pub struct ChatTheme { pub background_color: Mutable, pub font_size: MutableRange, + pub broadcast_color: Mutable, + pub server_color: Mutable, + pub error_color: Mutable, + pub information_color: Mutable, } impl ThemeDefault for ChatTheme { @@ -857,6 +861,10 @@ impl ThemeDefault for ChatTheme { Self { background_color: Mutable::new(Color::rgba_u8(0, 0, 0, 170)), font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(50.0)), + broadcast_color: Mutable::new(Color::rgb_u8(210, 210, 210)), + server_color: Mutable::new(Color::rgb_u8(255, 255, 210)), + error_color: Mutable::new(Color::rgb_u8(255, 150, 150)), + information_color: Mutable::new(Color::rgb_u8(200, 255, 200)), } } } @@ -866,6 +874,10 @@ impl ThemeDefault for ChatTheme { Self { background_color: Mutable::new(Color::rgba_u8(0, 0, 0, 170)), font_size: MutableRange::new(FontSize::new(14.0), FontSize::new(6.0), FontSize::new(50.0)), + broadcast_color: Mutable::new(Color::rgb_u8(210, 210, 210)), + server_color: Mutable::new(Color::rgb_u8(255, 255, 210)), + error_color: Mutable::new(Color::rgb_u8(255, 150, 150)), + information_color: Mutable::new(Color::rgb_u8(200, 255, 200)), } } } @@ -878,6 +890,22 @@ impl korangar_interface::theme::ChatTheme for ChatTheme { fn font_size(&self) -> FontSize { self.font_size.get() } + + fn broadcast_color(&self) -> Color { + self.broadcast_color.get() + } + + fn server_color(&self) -> Color { + self.server_color.get() + } + + fn error_color(&self) -> Color { + self.error_color.get() + } + + fn information_color(&self) -> Color { + self.information_color.get() + } } #[derive(Serialize, Deserialize, PrototypeElement)] diff --git a/korangar/src/interface/windows/account/login.rs b/korangar/src/interface/windows/account/login.rs index 632fce11..336731e0 100644 --- a/korangar/src/interface/windows/account/login.rs +++ b/korangar/src/interface/windows/account/login.rs @@ -14,8 +14,8 @@ use crate::interface::application::InterfaceSettings; use crate::interface::layout::ScreenSize; use crate::interface::theme::InterfaceThemeKind; use crate::interface::windows::WindowCache; +use crate::loaders::client::LoginSettings; use crate::loaders::ClientInfo; -use crate::network::LoginSettings; #[derive(new)] pub struct LoginWindow<'a> { diff --git a/korangar/src/interface/windows/account/select_server.rs b/korangar/src/interface/windows/account/select_server.rs index e2b13d19..346fafc5 100644 --- a/korangar/src/interface/windows/account/select_server.rs +++ b/korangar/src/interface/windows/account/select_server.rs @@ -2,7 +2,7 @@ use derive_new::new; use korangar_interface::elements::{ButtonBuilder, ElementWrap}; use korangar_interface::size_bound; use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; -use ragnarok_networking::CharacterServerInformation; +use ragnarok_packets::CharacterServerInformation; use crate::input::UserEvent; use crate::interface::application::InterfaceSettings; diff --git a/korangar/src/interface/windows/character/selection.rs b/korangar/src/interface/windows/character/selection.rs index 94c06dee..ce8a95f7 100644 --- a/korangar/src/interface/windows/character/selection.rs +++ b/korangar/src/interface/windows/character/selection.rs @@ -3,7 +3,7 @@ use korangar_interface::elements::ElementWrap; use korangar_interface::size_bound; use korangar_interface::state::PlainRemote; use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; -use ragnarok_networking::CharacterInformation; +use ragnarok_packets::CharacterInformation; use crate::interface::application::InterfaceSettings; use crate::interface::elements::CharacterPreview; diff --git a/korangar/src/interface/windows/debug/maps.rs b/korangar/src/interface/windows/debug/maps.rs index bd29bd6a..8bbe114b 100644 --- a/korangar/src/interface/windows/debug/maps.rs +++ b/korangar/src/interface/windows/debug/maps.rs @@ -1,7 +1,7 @@ -use cgmath::Vector2; use korangar_interface::elements::{ButtonBuilder, ElementWrap}; use korangar_interface::size_bound; use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; +use ragnarok_packets::TilePosition; use crate::input::UserEvent; use crate::interface::application::InterfaceSettings; @@ -27,29 +27,29 @@ impl PrototypeWindow for MapsWindow { available_space: ScreenSize, ) -> Window { let map_warps = [ - ("geffen", Vector2::new(119, 59)), - ("alberta", Vector2::new(28, 234)), - ("aldebaran", Vector2::new(140, 131)), - ("amatsu", Vector2::new(198, 84)), - ("ayothaya", Vector2::new(208, 166)), - ("prontera", Vector2::new(155, 183)), - ("brasilis", Vector2::new(196, 217)), - ("einbech", Vector2::new(63, 35)), - ("einbroch", Vector2::new(64, 200)), - ("dicastes01", Vector2::new(198, 187)), - ("gonryun", Vector2::new(160, 120)), - ("hugel", Vector2::new(96, 145)), - ("izlude", Vector2::new(128, 146)), - ("jawaii", Vector2::new(251, 132)), - ("lasagna", Vector2::new(193, 182)), - ("lighthalzen", Vector2::new(158, 92)), - ("louyang", Vector2::new(217, 100)), - ("xmas", Vector2::new(147, 134)), - ("c_tower1", Vector2::new(235, 218)), - ("ama_dun01", Vector2::new(54, 107)), - ("umbala", Vector2::new(97, 153)), - ("rachel", Vector2::new(120, 120)), - ("mid_camp", Vector2::new(180, 240)), + ("geffen", TilePosition { x: 119, y: 59 }), + ("alberta", TilePosition { x: 28, y: 234 }), + ("aldebaran", TilePosition { x: 140, y: 131 }), + ("amatsu", TilePosition { x: 198, y: 84 }), + ("ayothaya", TilePosition { x: 208, y: 166 }), + ("prontera", TilePosition { x: 155, y: 183 }), + ("brasilis", TilePosition { x: 196, y: 217 }), + ("einbech", TilePosition { x: 63, y: 35 }), + ("einbroch", TilePosition { x: 64, y: 200 }), + ("dicastes01", TilePosition { x: 198, y: 187 }), + ("gonryun", TilePosition { x: 160, y: 120 }), + ("hugel", TilePosition { x: 96, y: 145 }), + ("izlude", TilePosition { x: 128, y: 146 }), + ("jawaii", TilePosition { x: 251, y: 132 }), + ("lasagna", TilePosition { x: 193, y: 182 }), + ("lighthalzen", TilePosition { x: 158, y: 92 }), + ("louyang", TilePosition { x: 217, y: 100 }), + ("xmas", TilePosition { x: 147, y: 134 }), + ("c_tower1", TilePosition { x: 235, y: 218 }), + ("ama_dun01", TilePosition { x: 54, y: 107 }), + ("umbala", TilePosition { x: 97, y: 153 }), + ("rachel", TilePosition { x: 120, y: 120 }), + ("mid_camp", TilePosition { x: 180, y: 240 }), ]; let elements = map_warps diff --git a/korangar/src/interface/windows/debug/packet.rs b/korangar/src/interface/windows/debug/packet.rs index fba3a8ac..b00fd010 100644 --- a/korangar/src/interface/windows/debug/packet.rs +++ b/korangar/src/interface/windows/debug/packet.rs @@ -1,31 +1,25 @@ -use std::cell::UnsafeCell; - -use korangar_debug::profiling::RingBuffer; -use korangar_interface::elements::{ButtonBuilder, ElementWrap, ScrollView, StateButtonBuilder, WeakElementCell}; +use korangar_interface::elements::{ButtonBuilder, ElementWrap, ScrollView, StateButtonBuilder}; use korangar_interface::event::ClickAction; -use korangar_interface::state::{PlainRemote, PlainTrackedState, Remote, TrackedState, TrackedStateBinary}; +use korangar_interface::state::{PlainTrackedState, TrackedState, TrackedStateBinary}; use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; use korangar_interface::{dimension_bound, size_bound}; use crate::input::UserEvent; use crate::interface::application::InterfaceSettings; -use crate::interface::elements::{PacketEntry, PacketView}; +use crate::interface::elements::{PacketHistoryRemote, PacketView}; use crate::interface::layout::ScreenSize; use crate::interface::windows::WindowCache; -pub struct PacketWindow { - packets: PlainRemote>>), N>>, +pub struct PacketWindow { + packets: PacketHistoryRemote, show_pings: PlainTrackedState, update: PlainTrackedState, } -impl PacketWindow { +impl PacketWindow { pub const WINDOW_CLASS: &'static str = "network"; - pub fn new( - packets: PlainRemote>>), N>>, - update: PlainTrackedState, - ) -> Self { + pub fn new(packets: PacketHistoryRemote, update: PlainTrackedState) -> Self { let show_pings = PlainTrackedState::default(); Self { @@ -36,7 +30,7 @@ impl PacketWindow { } } -impl PrototypeWindow for PacketWindow { +impl PrototypeWindow for PacketWindow { fn window_class(&self) -> Option<&str> { Self::WINDOW_CLASS.into() } @@ -51,7 +45,7 @@ impl PrototypeWindow for PacketWindow { let clear_selector = { let packets = self.packets.clone(); - move || !packets.get().is_empty() + move || !packets.is_empty() }; let clear_action = { move || vec![ClickAction::Custom(UserEvent::ClearPacketHistory)] }; diff --git a/korangar/src/interface/windows/friends/list.rs b/korangar/src/interface/windows/friends/list.rs index 78ae3379..21a7e32f 100644 --- a/korangar/src/interface/windows/friends/list.rs +++ b/korangar/src/interface/windows/friends/list.rs @@ -1,22 +1,21 @@ -use std::cell::UnsafeCell; - use derive_new::new; -use korangar_interface::elements::{ButtonBuilder, ElementWrap, InputFieldBuilder, WeakElementCell}; +use korangar_interface::elements::{ButtonBuilder, ElementWrap, InputFieldBuilder}; use korangar_interface::event::ClickAction; use korangar_interface::state::{PlainRemote, PlainTrackedState, TrackedStateTake}; use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; use korangar_interface::{dimension_bound, size_bound}; -use ragnarok_networking::Friend; +use ragnarok_packets::Friend; use crate::input::UserEvent; use crate::interface::application::InterfaceSettings; use crate::interface::elements::FriendView; use crate::interface::layout::ScreenSize; +use crate::interface::linked::LinkedElement; use crate::interface::windows::WindowCache; #[derive(new)] pub struct FriendsWindow { - friend_list: PlainRemote>>)>>, + friend_list: PlainRemote>, } impl FriendsWindow { diff --git a/korangar/src/interface/windows/friends/request.rs b/korangar/src/interface/windows/friends/request.rs index a60062a0..5809ca9d 100644 --- a/korangar/src/interface/windows/friends/request.rs +++ b/korangar/src/interface/windows/friends/request.rs @@ -2,7 +2,7 @@ use derive_new::new; use korangar_interface::elements::{ButtonBuilder, ElementWrap, Text}; use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; use korangar_interface::{dimension_bound, size_bound}; -use ragnarok_networking::Friend; +use ragnarok_packets::Friend; use crate::input::UserEvent; use crate::interface::application::InterfaceSettings; @@ -51,7 +51,7 @@ impl PrototypeWindow for FriendRequestWindow { WindowBuilder::new() .with_title("Friend request".to_string()) - // NOTE: We give the builder a class but we don't implement the `window_class` method + // We give the builder a class but we don't implement the `window_class` method // of the trait. This way we can open multiple windos of this type but we can still // close them with the class name. .with_class(Self::WINDOW_CLASS.to_owned()) diff --git a/korangar/src/interface/windows/generic/chat.rs b/korangar/src/interface/windows/generic/chat.rs index 011e9736..9fe30594 100644 --- a/korangar/src/interface/windows/generic/chat.rs +++ b/korangar/src/interface/windows/generic/chat.rs @@ -7,6 +7,7 @@ use korangar_interface::event::ClickAction; use korangar_interface::state::{PlainRemote, PlainTrackedState, TrackedState, TrackedStateTake}; use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; use korangar_interface::{dimension_bound, size_bound}; +use korangar_networking::MessageColor; use crate::input::UserEvent; use crate::interface::application::InterfaceSettings; @@ -15,7 +16,12 @@ use crate::interface::layout::ScreenSize; use crate::interface::theme::InterfaceTheme; use crate::interface::windows::WindowCache; use crate::loaders::FontLoader; -use crate::network::ChatMessage; + +#[derive(Debug, Clone)] +pub struct ChatMessage { + pub text: String, + pub color: MessageColor, +} #[derive(new)] pub struct ChatWindow { @@ -71,7 +77,7 @@ impl PrototypeWindow for ChatWindow { .with_state(input_text) .with_ghost_text("Write message or command") .with_enter_action(input_action) - .with_length(30) + .with_length(80) .with_width_bound(dimension_bound!(75%)) .build() .wrap(), diff --git a/korangar/src/interface/windows/generic/dialog.rs b/korangar/src/interface/windows/generic/dialog.rs index c1fb08d0..6d8d91ef 100644 --- a/korangar/src/interface/windows/generic/dialog.rs +++ b/korangar/src/interface/windows/generic/dialog.rs @@ -2,7 +2,7 @@ use korangar_interface::elements::ElementWrap; use korangar_interface::size_bound; use korangar_interface::state::{PlainTrackedState, TrackedState}; use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; -use ragnarok_networking::EntityId; +use ragnarok_packets::EntityId; use crate::interface::application::InterfaceSettings; use crate::interface::elements::{DialogContainer, DialogElement}; diff --git a/korangar/src/interface/windows/generic/mod.rs b/korangar/src/interface/windows/generic/mod.rs index 344b94c5..4bc8bf20 100644 --- a/korangar/src/interface/windows/generic/mod.rs +++ b/korangar/src/interface/windows/generic/mod.rs @@ -3,7 +3,7 @@ mod dialog; mod error; mod menu; -pub use self::chat::ChatWindow; +pub use self::chat::{ChatMessage, ChatWindow}; pub use self::dialog::DialogWindow; pub use self::error::ErrorWindow; pub use self::menu::MenuWindow; diff --git a/korangar/src/inventory/mod.rs b/korangar/src/inventory/mod.rs index 7f022171..624b074a 100644 --- a/korangar/src/inventory/mod.rs +++ b/korangar/src/inventory/mod.rs @@ -4,7 +4,8 @@ mod skills; use std::sync::Arc; use korangar_interface::state::{PlainRemote, PlainTrackedState, TrackedState, TrackedStateExt, ValueState}; -use ragnarok_networking::{EquipPosition, ItemId, ItemIndex}; +use korangar_networking::InventoryItem; +use ragnarok_packets::{EquipPosition, ItemId, ItemIndex}; use vulkano::image::view::ImageView; pub use self::hotbar::Hotbar; @@ -53,19 +54,20 @@ impl Inventory { game_file_loader: &mut GameFileLoader, texture_loader: &mut TextureLoader, script_loader: &ScriptLoader, - item_data: Vec<(ItemIndex, ItemId, EquipPosition, EquipPosition)>, + item_data: Vec, ) { let items = item_data .into_iter() .map(|item_data| { - let resource_name = script_loader.get_item_resource_from_id(item_data.1); + let resource_name = script_loader.get_item_resource_from_id(item_data.id); let full_path = format!("À¯ÀúÀÎÅÍÆäÀ̽º\\item\\{resource_name}.bmp"); let texture = texture_loader.get(&full_path, game_file_loader).unwrap(); + Item { - index: item_data.0, - item_id: item_data.1, - equip_position: item_data.2, - equipped_position: item_data.3, + index: item_data.index, + item_id: item_data.id, + equip_position: item_data.equip_position, + equipped_position: item_data.equipped_position, texture, } }) diff --git a/korangar/src/inventory/skills.rs b/korangar/src/inventory/skills.rs index 93c1fbaa..1531480c 100644 --- a/korangar/src/inventory/skills.rs +++ b/korangar/src/inventory/skills.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use korangar_interface::state::{PlainRemote, PlainTrackedState, TrackedState}; -use ragnarok_networking::{ClientTick, SkillId, SkillInformation, SkillLevel, SkillType}; +use ragnarok_packets::{ClientTick, SkillId, SkillInformation, SkillLevel, SkillType}; use crate::loaders::{ActionLoader, Actions, AnimationState, GameFileLoader, Sprite, SpriteLoader}; diff --git a/korangar/src/loaders/action/mod.rs b/korangar/src/loaders/action/mod.rs index 16ac4399..debf30f6 100644 --- a/korangar/src/loaders/action/mod.rs +++ b/korangar/src/loaders/action/mod.rs @@ -8,7 +8,7 @@ use derive_new::new; use korangar_debug::logging::{print_debug, Colorize, Timer}; use korangar_interface::elements::PrototypeElement; use ragnarok_bytes::{ByteConvertable, ByteStream, FromBytes}; -use ragnarok_networking::ClientTick; +use ragnarok_packets::ClientTick; use vulkano::image::view::ImageView; use super::version::InternalVersion; @@ -146,7 +146,7 @@ impl Actions { let fs = &a.motions[frame as usize % a.motions.len()]; for sprite_clip in &fs.sprite_clips { - // NOTE: `get` instead of a direct index in case a fallback was loaded + // `get` instead of a direct index in case a fallback was loaded let Some(texture) = sprite.textures.get(sprite_clip.sprite_number as usize) else { return; }; diff --git a/korangar/src/network/login.rs b/korangar/src/loaders/client/mod.rs similarity index 100% rename from korangar/src/network/login.rs rename to korangar/src/loaders/client/mod.rs diff --git a/korangar/src/loaders/effect/mod.rs b/korangar/src/loaders/effect/mod.rs index 0730ea19..d117c43d 100644 --- a/korangar/src/loaders/effect/mod.rs +++ b/korangar/src/loaders/effect/mod.rs @@ -7,7 +7,7 @@ use derive_new::new; use korangar_debug::logging::{Colorize, Timer}; use korangar_interface::elements::PrototypeElement; use ragnarok_bytes::{ByteStream, FromBytes}; -use ragnarok_networking::EntityId; +use ragnarok_packets::EntityId; use vulkano::image::view::ImageView; use super::version::InternalVersion; diff --git a/korangar/src/loaders/mod.rs b/korangar/src/loaders/mod.rs index 846ef4b0..7dcd2831 100644 --- a/korangar/src/loaders/mod.rs +++ b/korangar/src/loaders/mod.rs @@ -1,5 +1,6 @@ mod action; mod archive; +pub mod client; pub mod color; mod effect; mod font; diff --git a/korangar/src/loaders/script/mod.rs b/korangar/src/loaders/script/mod.rs index 97be6ec2..a49de5b8 100644 --- a/korangar/src/loaders/script/mod.rs +++ b/korangar/src/loaders/script/mod.rs @@ -1,5 +1,5 @@ use mlua::Lua; -use ragnarok_networking::ItemId; +use ragnarok_packets::ItemId; use crate::loaders::GameFileLoader; diff --git a/korangar/src/main.rs b/korangar/src/main.rs index 656511d1..e0f1f1f5 100644 --- a/korangar/src/main.rs +++ b/korangar/src/main.rs @@ -24,15 +24,16 @@ mod graphics; mod interface; mod inventory; mod loaders; -mod network; mod world; use std::cell::RefCell; use std::io::Cursor; +use std::net::{IpAddr, SocketAddr}; use std::rc::Rc; +use std::str::FromStr; use std::sync::Arc; -use cgmath::{Vector2, Vector3, Zero}; +use cgmath::{Vector2, Vector3}; use image::io::Reader as ImageReader; use image::{EncodableLayout, ImageFormat}; #[cfg(feature = "debug")] @@ -42,9 +43,10 @@ use korangar_debug::profile_block; #[cfg(feature = "debug")] use korangar_debug::profiling::Profiler; use korangar_interface::application::{Application, FocusState, FontSizeTrait, FontSizeTraitExt, PositionTraitExt}; -use korangar_interface::state::{PlainTrackedState, Remote, RemoteClone, TrackedState, TrackedStateVec}; +use korangar_interface::state::{PlainTrackedState, Remote, RemoteClone, TrackedState, TrackedStateExt, TrackedStateTake, TrackedStateVec}; use korangar_interface::Interface; -use ragnarok_networking::{SkillId, SkillType, UnitId}; +use korangar_networking::{DisconnectReason, LoginServerLoginData, MessageColor, NetworkEvent, NetworkingSystem}; +use ragnarok_packets::{CharacterId, CharacterServerInformation, Friend, SkillId, SkillType, TilePosition, UnitId, WorldPosition}; use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo}; #[cfg(feature = "debug")] use vulkano::instance::debug::{ @@ -64,17 +66,17 @@ use crate::interface::application::InterfaceSettings; use crate::interface::cursor::{MouseCursor, MouseCursorState}; use crate::interface::dialog::DialogSystem; use crate::interface::layout::{ScreenPosition, ScreenSize}; +use crate::interface::linked::LinkedElement; use crate::interface::resource::{ItemSource, Move, SkillSource}; use crate::interface::windows::{ - AudioSettingsWindow, CharacterCreationWindow, CharacterOverviewWindow, CharacterSelectionWindow, ChatWindow, DialogWindow, - EquipmentWindow, ErrorWindow, FriendRequestWindow, GraphicsSettingsWindow, HotbarWindow, InventoryWindow, LoginWindow, MenuWindow, - SelectServerWindow, SkillTreeWindow, + AudioSettingsWindow, CharacterCreationWindow, CharacterOverviewWindow, CharacterSelectionWindow, ChatMessage, ChatWindow, DialogWindow, + EquipmentWindow, ErrorWindow, FriendRequestWindow, FriendsWindow, GraphicsSettingsWindow, HotbarWindow, InventoryWindow, LoginWindow, + MenuWindow, SelectServerWindow, SkillTreeWindow, }; #[cfg(feature = "debug")] -use crate::interface::windows::{CommandsWindow, MapsWindow, ProfilerWindow, RenderSettingsWindow, TimeWindow}; +use crate::interface::windows::{CommandsWindow, MapsWindow, PacketWindow, ProfilerWindow, RenderSettingsWindow, TimeWindow}; use crate::inventory::{Hotbar, Inventory, SkillTree}; use crate::loaders::*; -use crate::network::{ChatMessage, NetworkEvent, NetworkingSystem}; #[cfg(feature = "debug")] use crate::system::vulkan_message_callback; use crate::system::{choose_physical_device, get_device_extensions, get_layers, GameTimer}; @@ -282,17 +284,6 @@ fn main() { swapchain_holder.window_size_u32(), ); - fn handle_result( - application: &InterfaceSettings, - interface: &mut Interface, - focus_state: &mut FocusState, - result: Result, - ) { - if let Err(message) = result { - interface.open_window(application, focus_state, &ErrorWindow::new(message)); - } - } - let mut picker_renderer = PickerRenderer::new( memory_allocator.clone(), queue.clone(), @@ -350,7 +341,7 @@ fn main() { let timer = Timer::new("initialize interface"); let mut application = InterfaceSettings::load_or_default(); - let mut interface = korangar_interface::Interface::new(swapchain_holder.window_screen_size()); + let mut interface = Interface::new(swapchain_holder.window_screen_size()); let mut focus_state = FocusState::default(); let mut mouse_cursor = MouseCursor::new(&mut game_file_loader, &mut sprite_loader, &mut action_loader); let mut dialog_system = DialogSystem::default(); @@ -386,7 +377,29 @@ fn main() { let timer = Timer::new("initialize networking"); let client_info = load_client_info(&mut game_file_loader); - let mut networking_system = NetworkingSystem::new(); + + #[cfg(not(feature = "debug"))] + let mut networking_system = NetworkingSystem::spawn(); + #[cfg(feature = "debug")] + let packet_callback = { + // SAFETY: This function leaks memory, but it's fine since we only call + // it once. + unsafe { interface::elements::PacketHistoryCallback::new() } + }; + #[cfg(feature = "debug")] + let mut networking_system = NetworkingSystem::spawn_with_callback(packet_callback.clone()); + + let mut friend_list: PlainTrackedState> = PlainTrackedState::default(); + let mut saved_login_data: Option = None; + let mut saved_character_server: Option = None; + let mut saved_characters: PlainTrackedState> = PlainTrackedState::default(); + let mut currently_deleting: Option = None; + let mut saved_player_name = String::new(); + let mut move_request: PlainTrackedState> = PlainTrackedState::default(); + let mut saved_login_server_address = None; + let mut saved_password = String::new(); + let mut saved_username = String::new(); + let mut saved_slot_count = 0; interface.open_window(&application, &mut focus_state, &LoginWindow::new(&client_info)); @@ -404,8 +417,10 @@ fn main() { "Welcome to ^ffff00★^000000 ^ff8800Korangar^000000 ^ffff00★^000000 version ^ff8800{}^000000!", env!("CARGO_PKG_VERSION") ); - let welcome_message = ChatMessage::new(welcome_string, Color::monochrome_u8(255)); - let mut chat_messages = PlainTrackedState::new(vec![welcome_message]); + let mut chat_messages = PlainTrackedState::new(vec![ChatMessage { + text: welcome_string, + color: MessageColor::Server, + }]); let thread_pool = rayon::ThreadPoolBuilder::new().num_threads(3).build().unwrap(); @@ -494,8 +509,7 @@ fn main() { #[cfg(feature = "debug")] timer_measurement.stop(); - networking_system.keep_alive(delta_time, client_tick); - let network_events = networking_system.network_events(); + let network_events = networking_system.get_events(); let (user_events, hovered_element, focused_element, mouse_target) = input_system.user_events( &mut interface, @@ -514,8 +528,7 @@ fn main() { if let Some(PickerTarget::Entity(entity_id)) = mouse_target { if let Some(entity) = entities.iter_mut().find(|entity| entity.get_entity_id() == entity_id) { - if entity.are_details_unavailable() { - networking_system.request_entity_details(entity_id); + if entity.are_details_unavailable() && networking_system.entity_details(entity_id).is_ok() { entity.set_details_requested(); } @@ -523,7 +536,7 @@ fn main() { EntityType::Npc => mouse_cursor.set_state(MouseCursorState::Dialog, client_tick), EntityType::Warp => mouse_cursor.set_state(MouseCursorState::Warp, client_tick), EntityType::Monster => mouse_cursor.set_state(MouseCursorState::Attack, client_tick), - _ => {} // TODO: fill other entity types + _ => {} } } } @@ -536,10 +549,172 @@ fn main() { for event in network_events { match event { + NetworkEvent::LoginServerConnected { character_servers, login_data } => { + saved_login_data = Some(login_data); + + interface.close_all_windows_except(&mut focus_state); + interface.open_window(&application, &mut focus_state, &SelectServerWindow::new(character_servers)); + } + NetworkEvent::LoginServerConnectionFailed { message, .. } => { + interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message.to_owned())) + } + NetworkEvent::LoginServerDisconnected { reason } => { + if reason != DisconnectReason::ClosedByClient { + // TODO: Make this an on-screen popup. + #[cfg(feature = "debug")] + print_debug!("Disconnection from the character server with error"); + + let socket_address = saved_login_server_address.unwrap(); + networking_system.connect_to_login_server(socket_address, &saved_username, &saved_password); + } + }, + NetworkEvent::CharacterServerConnected { normal_slot_count } => { + saved_slot_count = normal_slot_count; + let _ = networking_system.request_character_list(); + }, + NetworkEvent::CharacterServerConnectionFailed { message, .. } => { + interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message.to_owned())) + }, + NetworkEvent::CharacterServerDisconnected { reason } => { + if reason != DisconnectReason::ClosedByClient { + // TODO: Make this an on-screen popup. + #[cfg(feature = "debug")] + print_debug!("Disconnection from the character server with error"); + + let login_data = saved_login_data.as_ref().unwrap(); + let server = saved_character_server.clone().unwrap(); + networking_system.connect_to_character_server(login_data, server); + } + }, + NetworkEvent::MapServerDisconnected { reason } => { + if reason != DisconnectReason::ClosedByClient { + // TODO: Make this an on-screen popup. + #[cfg(feature = "debug")] + print_debug!("Disconnection from the map server with error"); + } + + let login_data = saved_login_data.as_ref().unwrap(); + let server = saved_character_server.clone().unwrap(); + networking_system.connect_to_character_server(login_data, server); + + entities.clear(); + particle_holder.clear(); + effect_holder.clear(); + + map = map_loader + .get( + DEFAULT_MAP.to_string(), + &mut game_file_loader, + &mut buffer_allocator, + &mut model_loader, + &mut texture_loader, + ) + .expect("failed to load initial map"); + + interface.close_all_windows_except(&mut focus_state); + + let character_selection_window = CharacterSelectionWindow::new(saved_characters.new_remote(), move_request.new_remote(), saved_slot_count); + interface.open_window(&application, &mut focus_state, &character_selection_window); + + start_camera.set_focus_point(cgmath::Point3::new(600.0, 0.0, 240.0)); + directional_shadow_camera.set_focus_point(cgmath::Point3::new(600.0, 0.0, 240.0)); + + }, + NetworkEvent::AccountId(..) => {}, + NetworkEvent::CharacterList { characters } => { + saved_characters.set(characters); + let character_selection_window = CharacterSelectionWindow::new(saved_characters.new_remote(), move_request.new_remote(), saved_slot_count); + + // TODO: this will do one unnecessary restore_focus. check if + // that will be problematic + interface.close_all_windows_except(&mut focus_state); + interface.open_window(&application, &mut focus_state, &character_selection_window); + } + NetworkEvent::CharacterSelectionFailed { message, .. } => { + interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message.to_owned())) + } + NetworkEvent::CharacterDeleted => { + let character_id = currently_deleting.take().unwrap(); + + saved_characters.retain(|character| character.character_id != character_id); + }, + NetworkEvent::CharacterDeletionFailed { message, .. } => { + currently_deleting = None; + interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message.to_owned())) + } + NetworkEvent::CharacterSelected { login_data, map_name } => { + let saved_login_data = saved_login_data.as_ref().unwrap(); + networking_system.connect_to_map_server(saved_login_data, login_data); + + let character_information = saved_characters + .get() + .iter() + .find(|character| character.character_id == login_data.character_id) + .cloned() + .unwrap(); + + map = map_loader + .get( + map_name, + &mut game_file_loader, + &mut buffer_allocator, + &mut model_loader, + &mut texture_loader, + ) + .unwrap(); + + saved_player_name = character_information.name.clone(); + + let player = Player::new( + &mut game_file_loader, + &mut sprite_loader, + &mut action_loader, + &script_loader, + &map, + saved_login_data.account_id, + character_information, + WorldPosition::new(0, 0), + client_tick, + ); + let player = Entity::Player(player); + + player_camera.set_focus_point(player.get_position()); + entities.push(player); + + // TODO: this will do one unnecessary restore_focus. check if + // that will be problematic + interface.close_window_with_class(&mut focus_state, CharacterSelectionWindow::WINDOW_CLASS); + interface.open_window(&application, &mut focus_state, &CharacterOverviewWindow::new()); + interface.open_window( + &application, + &mut focus_state, + &ChatWindow::new(chat_messages.new_remote(), font_loader.clone()), + ); + interface.open_window(&application, &mut focus_state, &HotbarWindow::new(hotbar.get_skills())); + + particle_holder.clear(); + let _ = networking_system.map_loaded(); + // TODO: This is just a workaround until I find a better solution to make the + // cursor always look correct. + mouse_cursor.set_start_time(client_tick); + game_timer.set_client_tick(client_tick); + }, + NetworkEvent::CharacterCreated { character_information } => { + saved_characters.push(character_information); + + interface.close_window_with_class(&mut focus_state, CharacterCreationWindow::WINDOW_CLASS); + }, + NetworkEvent::CharacterCreationFailed { message, .. } => { + interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message.to_owned())); + }, + NetworkEvent::CharacterSlotSwitched => {}, + NetworkEvent::CharacterSlotSwitchFailed => { + interface.open_window(&application, &mut focus_state, &ErrorWindow::new("Failed to switch character slots".to_owned())); + }, NetworkEvent::AddEntity(entity_appeared_data) => { // Sometimes (like after a job change) the server will tell the client // that a new entity appeared, even though it was already on screen. So - // to prevent this we remove the old entity. + // to prevent the entity existing twice, we remove the old one. entities.retain(|entity| entity.get_entity_id() != entity_appeared_data.entity_id); let npc = Npc::new( @@ -562,12 +737,17 @@ fn main() { let entity = entities.iter_mut().find(|entity| entity.get_entity_id() == entity_id); if let Some(entity) = entity { + let position_from = Vector2::new(position_from.x, position_from.y); + let position_to = Vector2::new(position_to.x, position_to.y); + entity.move_from_to(&map, position_from, position_to, starting_timestamp); /*#[cfg(feature = "debug")] entity.generate_steps_vertex_buffer(device.clone(), &map);*/ } } NetworkEvent::PlayerMove(position_from, position_to, starting_timestamp) => { + let position_from = Vector2::new(position_from.x, position_from.y); + let position_to = Vector2::new(position_to.x, position_to.y); entities[0].move_from_to(&map, position_from, position_to, starting_timestamp); /*#[cfg(feature = "debug")] @@ -586,25 +766,28 @@ fn main() { ) .unwrap(); + let player_position = Vector2::new(player_position.x as usize, player_position.y as usize); entities[0].set_position(&map, player_position, client_tick); player_camera.set_focus_point(entities[0].get_position()); particle_holder.clear(); effect_holder.clear(); - networking_system.map_loaded(); + let _ = networking_system.map_loaded(); + // TODO: This is just a workaround until I find a better solution to make the // cursor always look correct. mouse_cursor.set_start_time(client_tick); } NetworkEvent::SetPlayerPosition(player_position) => { + let player_position = Vector2::new(player_position.x, player_position.y); entities[0].set_position(&map, player_position, client_tick); player_camera.set_focus_point(entities[0].get_position()); } NetworkEvent::UpdateClientTick(client_tick) => { game_timer.set_client_tick(client_tick); } - NetworkEvent::ChatMessage(message) => { - chat_messages.push(message); + NetworkEvent::ChatMessage { text, color } => { + chat_messages.push(ChatMessage { text, color }); } NetworkEvent::UpdateEntityDetails(entity_id, name) => { let entity = entities.iter_mut().find(|entity| entity.get_entity_id() == entity_id); @@ -655,8 +838,8 @@ fn main() { particle_holder.add_quest_icon(&mut game_file_loader, &mut texture_loader, &map, quest_effect) } NetworkEvent::RemoveQuestEffect(entity_id) => particle_holder.remove_quest_icon(entity_id), - NetworkEvent::Inventory(item_data) => { - player_inventory.fill(&mut game_file_loader, &mut texture_loader, &script_loader, item_data); + NetworkEvent::SetInventory { items } => { + player_inventory.fill(&mut game_file_loader, &mut texture_loader, &script_loader, items); } NetworkEvent::AddIventoryItem(item_index, item_data, equip_position, equipped_position) => { player_inventory.add_item( @@ -681,32 +864,17 @@ fn main() { entity.set_job(job_id as usize); entity.reload_sprite(&mut game_file_loader, &mut sprite_loader, &mut action_loader, &script_loader); } - NetworkEvent::Disconnect => { + NetworkEvent::LoggedOut => { networking_system.disconnect_from_map_server(); - entities.clear(); - particle_holder.clear(); - effect_holder.clear(); - - map = map_loader - .get( - DEFAULT_MAP.to_string(), - &mut game_file_loader, - &mut buffer_allocator, - &mut model_loader, - &mut texture_loader, - ) - .expect("failed to load initial map"); - - interface.close_all_windows_except(&mut focus_state); - - let character_selection_window = networking_system.character_selection_window(); - interface.open_window(&application, &mut focus_state, &character_selection_window); - - start_camera.set_focus_point(cgmath::Point3::new(600.0, 0.0, 240.0)); - directional_shadow_camera.set_focus_point(cgmath::Point3::new(600.0, 0.0, 240.0)); } - NetworkEvent::FriendRequest(friend) => { - interface.open_window(&application, &mut focus_state, &FriendRequestWindow::new(friend)) + NetworkEvent::FriendRequest { requestee } => { + interface.open_window(&application, &mut focus_state, &FriendRequestWindow::new(requestee)) + } + NetworkEvent::FriendRemoved { account_id, character_id } => { + friend_list.retain(|(friend, _)| !(friend.account_id == account_id && friend.character_id == character_id)); + } + NetworkEvent::FriendAdded { friend } => { + friend_list.push((friend, LinkedElement::new())); } NetworkEvent::VisualEffect(path, entity_id) => { let effect = effect_loader.get(path, &mut game_file_loader, &mut texture_loader).unwrap(); @@ -725,6 +893,7 @@ fn main() { } NetworkEvent::AddSkillUnit(entity_id, unit_id, position) => match unit_id { UnitId::Firewall => { + let position = Vector2::new(position.x as usize, position.y as usize); let position = map.get_world_position(position); let effect = effect_loader .get("firewall.str", &mut game_file_loader, &mut texture_loader) @@ -746,6 +915,7 @@ fn main() { ); } UnitId::Pneuma => { + let position = Vector2::new(position.x as usize, position.y as usize); let position = map.get_world_position(position); let effect = effect_loader .get("pneuma1.str", &mut game_file_loader, &mut texture_loader) @@ -771,6 +941,11 @@ fn main() { NetworkEvent::RemoveSkillUnit(entity_id) => { effect_holder.remove_unit(entity_id); } + NetworkEvent::SetFriendList { friends } => { + friend_list.mutate(|friend_list| { + *friend_list = friends.into_iter().map(|friend| (friend, LinkedElement::new())).collect(); + }); + } } } @@ -787,31 +962,33 @@ fn main() { username, password, } => { - match networking_system.log_in(&client_info, service_id, username, password) { - Ok(servers) => { - // TODO: this will do one unnecessary restore_focus. check if - // that will be problematic - interface.close_window_with_class(&mut focus_state, LoginWindow::WINDOW_CLASS); + let service = client_info + .services + .iter() + .find(|service| service.service_id() == service_id) + .unwrap(); + let socket_address = SocketAddr::new(IpAddr::from_str(&service.address).expect("ill formatted service IP"), service.port as u16); - interface.open_window(&application, &mut focus_state, &SelectServerWindow::new(servers)); - } - Err(message) => interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message)), - } + saved_login_server_address = Some(socket_address); + saved_username = username.clone(); + saved_password = password.clone(); + + networking_system.connect_to_login_server(socket_address, username, password); } UserEvent::SelectServer(server) => { - match networking_system.select_server(server) { - Ok(()) => { - // TODO: this will do one unnecessary restore_focus. check if - // that will be problematic - interface.close_window_with_class(&mut focus_state, SelectServerWindow::WINDOW_CLASS); - - let character_selection_window = networking_system.character_selection_window(); - interface.open_window(&application, &mut focus_state, &character_selection_window); - } - Err(message) => interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message)), - } + saved_character_server = Some(server.clone()); + + networking_system.disconnect_from_login_server(); + + // Korangar should never attempt to connect to the character + // server before it logged in to the login server, so it's fine to + // unwrap here. + let login_data = saved_login_data.as_ref().unwrap(); + networking_system.connect_to_character_server(login_data, server); } - UserEvent::LogOut => networking_system.log_out().unwrap(), + UserEvent::LogOut => { + let _ = networking_system.log_out(); + }, UserEvent::Exit => *control_flow = ControlFlow::Exit, UserEvent::CameraZoom(factor) => player_camera.soft_zoom(factor), UserEvent::CameraRotate(factor) => player_camera.soft_rotate(factor), @@ -854,146 +1031,99 @@ fn main() { ), UserEvent::OpenAudioSettingsWindow => interface.open_window(&application, &mut focus_state, &AudioSettingsWindow), UserEvent::OpenFriendsWindow => { - interface.open_window(&application, &mut focus_state, &networking_system.friends_window()) + interface.open_window(&application, &mut focus_state, &FriendsWindow::new(friend_list.new_remote())); } UserEvent::ToggleShowInterface => show_interface = !show_interface, UserEvent::SetThemeFile { theme_file, theme_kind } => application.set_theme_file(theme_file, theme_kind), UserEvent::SaveTheme { theme_kind } => application.save_theme(theme_kind), UserEvent::ReloadTheme { theme_kind } => application.reload_theme(theme_kind), UserEvent::SelectCharacter(character_slot) => { - match networking_system.select_character(character_slot) { - Ok((account_id, character_information, map_name)) => { - map = map_loader - .get( - map_name, - &mut game_file_loader, - &mut buffer_allocator, - &mut model_loader, - &mut texture_loader, - ) - .unwrap(); - - let player = Player::new( - &mut game_file_loader, - &mut sprite_loader, - &mut action_loader, - &script_loader, - &map, - account_id, - character_information, - Vector2::zero(), - client_tick, - ); - let player = Entity::Player(player); - - player_camera.set_focus_point(player.get_position()); - entities.push(player); - - // TODO: this will do one unnecessary restore_focus. check if - // that will be problematic - interface.close_window_with_class(&mut focus_state, CharacterSelectionWindow::WINDOW_CLASS); - interface.open_window(&application, &mut focus_state, &CharacterOverviewWindow::new()); - interface.open_window( - &application, - &mut focus_state, - &ChatWindow::new(chat_messages.new_remote(), font_loader.clone()), - ); - interface.open_window(&application, &mut focus_state, &HotbarWindow::new(hotbar.get_skills())); - - particle_holder.clear(); - networking_system.map_loaded(); - // TODO: This is just a workaround until I find a better solution to make the - // cursor always look correct. - mouse_cursor.set_start_time(client_tick); - game_timer.set_client_tick(client_tick); - } - Err(message) => interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message)), - } - } + let _ = networking_system.select_character(character_slot); + }, UserEvent::OpenCharacterCreationWindow(character_slot) => { interface.open_window(&application, &mut focus_state, &CharacterCreationWindow::new(character_slot)) } UserEvent::CreateCharacter(character_slot, name) => { - match networking_system.create_character(character_slot, name) { - Ok(..) => interface.close_window_with_class(&mut focus_state, CharacterCreationWindow::WINDOW_CLASS), - Err(message) => interface.open_window(&application, &mut focus_state, &ErrorWindow::new(message)), + let _ = networking_system.create_character(character_slot, name); + }, + UserEvent::DeleteCharacter(character_id) => { + if currently_deleting.is_none() { + let _ = networking_system.delete_character(character_id); + currently_deleting = Some(character_id); } - } - UserEvent::DeleteCharacter(character_id) => handle_result( - &application, - &mut interface, - &mut focus_state, - networking_system.delete_character(character_id), - ), - UserEvent::RequestSwitchCharacterSlot(origin_slot) => networking_system.request_switch_character_slot(origin_slot), - UserEvent::CancelSwitchCharacterSlot => networking_system.cancel_switch_character_slot(), - UserEvent::SwitchCharacterSlot(destination_slot) => handle_result( - &application, - &mut interface, - &mut focus_state, - networking_system.switch_character_slot(destination_slot), - ), + }, + UserEvent::RequestSwitchCharacterSlot(origin_slot) => move_request.set(Some(origin_slot)), + UserEvent::CancelSwitchCharacterSlot => move_request.set(None), + UserEvent::SwitchCharacterSlot(destination_slot) => { + let _ = networking_system.switch_character_slot(move_request.take().unwrap(), destination_slot); + }, UserEvent::RequestPlayerMove(destination) => { if !entities.is_empty() { - networking_system.request_player_move(destination) + let _ = networking_system.player_move(WorldPosition::new(destination.x, destination.y)); } } UserEvent::RequestPlayerInteract(entity_id) => { let entity = entities.iter_mut().find(|entity| entity.get_entity_id() == entity_id); if let Some(entity) = entity { - match entity.get_entity_type() { + let _ = match entity.get_entity_type() { EntityType::Npc => networking_system.start_dialog(entity_id), - EntityType::Monster => networking_system.request_player_attack(entity_id), - EntityType::Warp => networking_system.request_player_move(entity.get_grid_position()), - _ => {} // TODO: add other interactions - } + EntityType::Monster => networking_system.player_attack(entity_id), + EntityType::Warp => networking_system.player_move({let position = entity.get_grid_position(); WorldPosition { x: position.x, y: position.y }}), + _ => Ok(()) + }; } } - UserEvent::RequestWarpToMap(map_name, position) => networking_system.request_warp_to_map(map_name, position), + UserEvent::RequestWarpToMap(map_name, position) => { + let _ = networking_system.warp_to_map(map_name, position); + }, UserEvent::SendMessage(message) => { - networking_system.send_message(message); + let _ = networking_system.send_chat_message(&saved_player_name, &message); // TODO: maybe find a better solution for unfocusing the message box if // this becomes problematic focus_state.remove_focus(); } - UserEvent::NextDialog(npc_id) => networking_system.next_dialog(npc_id), + UserEvent::NextDialog(npc_id) => { + let _ = networking_system.next_dialog(npc_id); + }, UserEvent::CloseDialog(npc_id) => { - networking_system.close_dialog(npc_id); + let _ = networking_system.close_dialog(npc_id); dialog_system.close_dialog(); interface.close_window_with_class(&mut focus_state, DialogWindow::WINDOW_CLASS); } UserEvent::ChooseDialogOption(npc_id, option) => { - networking_system.choose_dialog_option(npc_id, option); + let _ = networking_system.choose_dialog_option(npc_id, option); if option == -1 { dialog_system.close_dialog(); interface.close_window_with_class(&mut focus_state, DialogWindow::WINDOW_CLASS); } } - UserEvent::MoveResource(r#move) => match r#move { - Move::Item { source, destination, item } => match (source, destination) { - (ItemSource::Inventory, ItemSource::Equipment { position }) => { - networking_system.request_item_equip(item.index, position); - } - (ItemSource::Equipment { .. }, ItemSource::Inventory) => { - networking_system.request_item_unequip(item.index); - } - _ => {} - }, - Move::Skill { - source, - destination, - skill, - } => match (source, destination) { - (SkillSource::SkillTree, SkillSource::Hotbar { slot }) => { - hotbar.set_slot(skill, slot); - } - (SkillSource::Hotbar { slot: source_slot }, SkillSource::Hotbar { slot: destination_slot }) => { - hotbar.swap_slot(source_slot, destination_slot); - } - _ => {} - }, + UserEvent::MoveResource(r#move) => { + match r#move { + Move::Item { source, destination, item } => match (source, destination) { + (ItemSource::Inventory, ItemSource::Equipment { position }) => { + let _ = networking_system.request_item_equip(item.index, position); + } + (ItemSource::Equipment { .. }, ItemSource::Inventory) => { + let _ = networking_system.request_item_unequip(item.index); + } + _ => {} + }, + Move::Skill { + source, + destination, + skill, + } => match (source, destination) { + (SkillSource::SkillTree, SkillSource::Hotbar { slot }) => { + hotbar.set_slot(skill, slot); + } + (SkillSource::Hotbar { slot: source_slot }, SkillSource::Hotbar { slot: destination_slot }) => { + hotbar.swap_slot(source_slot, destination_slot); + } + _ => {} + }, + } }, UserEvent::CastSkill(slot) => { if let Some(skill) = hotbar.get_skill_in_slot(slot).as_ref() { @@ -1001,29 +1131,31 @@ fn main() { SkillType::Passive => {} SkillType::Attack => { if let Some(PickerTarget::Entity(entity_id)) = mouse_target { - networking_system.cast_skill(skill.skill_id, skill.skill_level, entity_id); + let _ = networking_system.cast_skill(skill.skill_id, skill.skill_level, entity_id); } } SkillType::Ground | SkillType::Trap => { if let Some(PickerTarget::Tile { x, y }) = mouse_target { - networking_system.cast_ground_skill(skill.skill_id, skill.skill_level, Vector2::new(x, y)); + let _ = networking_system.cast_ground_skill(skill.skill_id, skill.skill_level, TilePosition { x, y }); } } SkillType::SelfCast => match skill.skill_id == ROLLING_CUTTER_ID { - true => networking_system.cast_channeling_skill( - skill.skill_id, - skill.skill_level, - entities[0].get_entity_id(), - ), + true => { + let _ = networking_system.cast_channeling_skill( + skill.skill_id, + skill.skill_level, + entities[0].get_entity_id(), + ); + }, false => { - networking_system.cast_skill(skill.skill_id, skill.skill_level, entities[0].get_entity_id()) + let _ = networking_system.cast_skill(skill.skill_id, skill.skill_level, entities[0].get_entity_id()); } }, SkillType::Support => { if let Some(PickerTarget::Entity(entity_id)) = mouse_target { - networking_system.cast_skill(skill.skill_id, skill.skill_level, entity_id); + let _ = networking_system.cast_skill(skill.skill_id, skill.skill_level, entity_id); } else { - networking_system.cast_skill(skill.skill_id, skill.skill_level, entities[0].get_entity_id()); + let _ = networking_system.cast_skill(skill.skill_id, skill.skill_level, entities[0].get_entity_id()); } } } @@ -1032,22 +1164,27 @@ fn main() { UserEvent::StopSkill(slot) => { if let Some(skill) = hotbar.get_skill_in_slot(slot).as_ref() { if skill.skill_id == ROLLING_CUTTER_ID { - networking_system.stop_channeling_skill(skill.skill_id); + let _ = networking_system.stop_channeling_skill(skill.skill_id); } } } UserEvent::AddFriend(name) => { - networking_system.add_friend(name); + if name.len() > 24 { + #[cfg(feature = "debug")] + print_debug!("[{}] friend name {} is too long", "error".red(), name.magenta()); + } else { + let _ = networking_system.add_friend(name); + } } UserEvent::RemoveFriend { account_id, character_id } => { - networking_system.remove_friend(account_id, character_id); + let _ = networking_system.remove_friend(account_id, character_id); } UserEvent::RejectFriendRequest { account_id, character_id } => { - networking_system.reject_friend_request(account_id, character_id); + let _ = networking_system.reject_friend_request(account_id, character_id); interface.close_window_with_class(&mut focus_state, FriendRequestWindow::WINDOW_CLASS); } UserEvent::AcceptFriendRequest { account_id, character_id } => { - networking_system.accept_friend_request(account_id, character_id); + let _ = networking_system.accept_friend_request(account_id, character_id); interface.close_window_with_class(&mut focus_state, FriendRequestWindow::WINDOW_CLASS); } #[cfg(feature = "debug")] @@ -1084,10 +1221,10 @@ fn main() { UserEvent::OpenProfilerWindow => interface.open_window(&application, &mut focus_state, &ProfilerWindow::new()), #[cfg(feature = "debug")] UserEvent::OpenPacketWindow => { - interface.open_window(&application, &mut focus_state, &networking_system.packet_window()) + interface.open_window(&application, &mut focus_state, &PacketWindow::new(packet_callback.remote(), PlainTrackedState::default())) } #[cfg(feature = "debug")] - UserEvent::ClearPacketHistory => networking_system.clear_packet_history(), + UserEvent::ClearPacketHistory => /*networking_system.clear_packet_history()*/ {}, #[cfg(feature = "debug")] UserEvent::CameraLookAround(offset) => debug_camera.look_around(offset), #[cfg(feature = "debug")] @@ -1200,7 +1337,7 @@ fn main() { if framerate_limit.consume_changed() { swapchain_holder.set_frame_limit(present_mode_info, framerate_limit.cloned()); - // NOTE: For some reason the interface buffer becomes messed up when + // For some reason the interface buffer becomes messed up when // recreating the swapchain, so we need to render it again. interface.schedule_render(); } @@ -1598,7 +1735,7 @@ fn main() { #[cfg(feature = "debug")] finalize_frame_measurement.stop(); } - _ignored => (), + _ignored => {}, } }); } diff --git a/korangar/src/network/mod.rs b/korangar/src/network/mod.rs deleted file mode 100644 index 5afe4314..00000000 --- a/korangar/src/network/mod.rs +++ /dev/null @@ -1,1503 +0,0 @@ -mod login; - -use std::cell::UnsafeCell; -use std::io::prelude::*; -use std::net::{IpAddr, SocketAddr, TcpStream}; -use std::time::Duration; - -use cgmath::Vector2; -use chrono::Local; -use derive_new::new; -#[cfg(feature = "debug")] -use korangar_debug::logging::{print_debug, Colorize, Timer}; -#[cfg(feature = "debug")] -use korangar_debug::profiling::RingBuffer; -use korangar_interface::elements::{PrototypeElement, WeakElementCell}; -use korangar_interface::state::{ - PlainTrackedState, TrackedState, TrackedStateClone, TrackedStateExt, TrackedStateTake, TrackedStateVec, ValueState, -}; -use ragnarok_bytes::{ByteStream, ConversionError, ConversionResult, FromBytes}; -use ragnarok_networking::*; - -pub use self::login::LoginSettings; -use crate::graphics::Color; -use crate::interface::application::InterfaceSettings; -#[cfg(feature = "debug")] -use crate::interface::elements::PacketEntry; -#[cfg(feature = "debug")] -use crate::interface::windows::PacketWindow; -use crate::interface::windows::{CharacterSelectionWindow, FriendsWindow}; -use crate::loaders::{ClientInfo, ServiceId}; - -#[cfg(feature = "debug")] -type PacketMetadata = Vec; -#[cfg(not(feature = "debug"))] -type PacketMetadata = (); - -/// Extension trait for for [`ByteStream`] for working with network packets. -#[cfg(feature = "debug")] -pub trait ByteStreamNetworkExt { - /// Push an [`IncomingPacket`] to the metadata. - fn incoming_packet(&mut self, packet: &T) - where - T: IncomingPacket + PrototypeElement + Clone + 'static; -} - -#[cfg(feature = "debug")] -impl<'a> ByteStreamNetworkExt for ByteStream<'a, Vec> { - fn incoming_packet(&mut self, packet: &T) - where - T: IncomingPacket + PrototypeElement + Clone + 'static, - { - self.get_metadata_mut::() - .expect("wrong metadata") - .push(PacketEntry::new_incoming(packet, std::any::type_name::(), T::IS_PING)); - } -} - -/// Extension trait for reading incoming packets and recording them into the -/// metadata of the [`ByteStream`] (only when the `debug` feature is active). -pub trait IncomingPacketRecord: IncomingPacket { - /// Like [`IncomingPacket::payload_from_bytes`](ragnarok_networking::IncomingPacket::payload_from_bytes), but it records the packet into the metadata of the [`ByteStream`]. - fn payload_from_bytes_recorded(byte_stream: &mut ByteStream) -> ConversionResult; - - /// Like [`IncomingPacketExt::packet_from_bytes`](ragnarok_networking::IncomingPacketExt::packet_from_bytes), but it records the packet into the metadata of the [`ByteStream`]. - fn packet_from_bytes_recorded(byte_stream: &mut ByteStream) -> ConversionResult; -} - -impl IncomingPacketRecord for T -where - T: IncomingPacket + PrototypeElement + 'static, -{ - fn payload_from_bytes_recorded(byte_stream: &mut ByteStream) -> ConversionResult { - let packet = Self::payload_from_bytes(byte_stream)?; - - #[cfg(feature = "debug")] - byte_stream.incoming_packet(&packet); - - Ok(packet) - } - - fn packet_from_bytes_recorded(byte_stream: &mut ByteStream) -> ConversionResult { - let packet = Self::packet_from_bytes(byte_stream)?; - - #[cfg(feature = "debug")] - byte_stream.incoming_packet(&packet); - - Ok(packet) - } -} - -/// An event triggered by the map server. -pub enum NetworkEvent { - /// Add an entity to the list of entities that the client is aware of. - AddEntity(EntityData), - /// Remove an entity from the list of entities that the client is aware of - /// by its id. - RemoveEntity(EntityId), - /// The player is pathing to a new position. - PlayerMove(Vector2, Vector2, ClientTick), - /// An Entity nearby is pathing to a new position. - EntityMove(EntityId, Vector2, Vector2, ClientTick), - /// Player was moved to a new position on a different map or the current map - ChangeMap(String, Vector2), - /// Update the client side [`tick - /// counter`](crate::system::GameTimer::base_client_tick) to keep server and - /// client synchronized. - UpdateClientTick(ClientTick), - /// New chat message for the client. - ChatMessage(ChatMessage), - /// Update entity details. Mostly received when the client sends - /// [RequestDetailsPacket] after the player hovered an entity. - UpdateEntityDetails(EntityId, String), - UpdateEntityHealth(EntityId, usize, usize), - DamageEffect(EntityId, usize), - HealEffect(EntityId, usize), - UpdateStatus(StatusType), - OpenDialog(String, EntityId), - AddNextButton, - AddCloseButton, - AddChoiceButtons(Vec), - AddQuestEffect(QuestEffectPacket), - RemoveQuestEffect(EntityId), - Inventory(Vec<(ItemIndex, ItemId, EquipPosition, EquipPosition)>), - AddIventoryItem(ItemIndex, ItemId, EquipPosition, EquipPosition), - SkillTree(Vec), - UpdateEquippedPosition { - index: ItemIndex, - equipped_position: EquipPosition, - }, - ChangeJob(AccountId, u32), - SetPlayerPosition(Vector2), - Disconnect, - FriendRequest(Friend), - VisualEffect(&'static str, EntityId), - AddSkillUnit(EntityId, UnitId, Vector2), - RemoveSkillUnit(EntityId), -} - -pub struct ChatMessage { - pub text: String, - pub color: Color, - offset: usize, -} - -impl ChatMessage { - // TODO: Maybe this shouldn't modify the text directly but rather save the - // timestamp. - pub fn new(mut text: String, color: Color) -> Self { - let prefix = Local::now().format("^66BB44%H:%M:%S: ^000000").to_string(); - let offset = prefix.len(); - - text.insert_str(0, &prefix); - Self { text, color, offset } - } - - pub fn stamped_text(&self, stamp: bool) -> &str { - let start = self.offset * !stamp as usize; - &self.text[start..] - } -} - -pub struct EntityData { - pub entity_id: EntityId, - pub movement_speed: u16, - pub job: u16, - pub position: Vector2, - pub destination: Option>, - pub health_points: i32, - pub maximum_health_points: i32, - pub head_direction: usize, - pub sex: Sex, -} - -impl EntityData { - pub fn from_character(account_id: AccountId, character_information: CharacterInformation, position: Vector2) -> Self { - Self { - entity_id: EntityId(account_id.0), - movement_speed: character_information.movement_speed as u16, - job: character_information.job as u16, - position, - destination: None, - health_points: character_information.health_points as i32, - maximum_health_points: character_information.maximum_health_points as i32, - head_direction: 0, // TODO: get correct rotation - sex: character_information.sex, - } - } -} - -impl From for EntityData { - fn from(packet: EntityAppearedPacket) -> Self { - Self { - entity_id: packet.entity_id, - movement_speed: packet.movement_speed, - job: packet.job, - position: Vector2::new(packet.position.x, packet.position.y), - destination: None, - health_points: packet.health_points, - maximum_health_points: packet.maximum_health_points, - head_direction: packet.head_direction as usize, - sex: packet.sex, - } - } -} - -impl From for EntityData { - fn from(packet: EntityAppeared2Packet) -> Self { - Self { - entity_id: packet.entity_id, - movement_speed: packet.movement_speed, - job: packet.job, - position: Vector2::new(packet.position.x, packet.position.y), - destination: None, - health_points: packet.health_points, - maximum_health_points: packet.maximum_health_points, - head_direction: packet.head_direction as usize, - sex: packet.sex, - } - } -} - -impl From for EntityData { - fn from(packet: MovingEntityAppearedPacket) -> Self { - let (origin, destination) = ( - Vector2::new(packet.position.x1, packet.position.y1), - Vector2::new(packet.position.x2, packet.position.y2), - ); - - Self { - entity_id: packet.entity_id, - movement_speed: packet.movement_speed, - job: packet.job, - position: origin, - destination: Some(destination), - health_points: packet.health_points, - maximum_health_points: packet.maximum_health_points, - head_direction: packet.head_direction as usize, - sex: packet.sex, - } - } -} - -#[derive(new)] -struct NetworkTimer { - period: Duration, - #[new(default)] - accumulator: Duration, -} - -impl NetworkTimer { - pub fn update(&mut self, elapsed_time: f64) -> bool { - self.accumulator += Duration::from_secs_f64(elapsed_time); - let reset = self.accumulator > self.period; - - if reset { - self.accumulator -= self.period; - } - - reset - } -} - -#[derive(new, Clone)] -struct LoginData { - pub account_id: AccountId, - pub login_id1: u32, - pub login_id2: u32, - pub sex: Sex, -} - -// TODO: Use struct like this -// enum GameState { -// LoggingIn {}, -// SelectingCharacter { -// login_data: LoginData, -// characters: TrackedState>, -// move_request: TrackedState>, -// slot_count: usize, -// }, -// Playing { -// friend_list: TrackedState>>)>>, player_name: -// String, }, -// } - -pub struct NetworkingSystem { - login_stream: Option, - character_stream: Option, - map_stream: Option, - // TODO: Make this a heapless Vec or something - map_stream_buffer: Vec, - login_keep_alive_timer: NetworkTimer, - character_keep_alive_timer: NetworkTimer, - map_keep_alive_timer: NetworkTimer, - - // TODO: Move to GameState - login_data: Option, - characters: PlainTrackedState>, - move_request: PlainTrackedState>, - friend_list: PlainTrackedState>>)>>, - slot_count: usize, - player_name: String, - #[cfg(feature = "debug")] - update_packets: PlainTrackedState, - #[cfg(feature = "debug")] - packet_history: PlainTrackedState>>), 256>>, -} - -impl NetworkingSystem { - pub fn new() -> Self { - let login_stream = None; - let character_stream = None; - let map_stream = None; - let map_stream_buffer = Vec::new(); - let login_data = None; - let characters = PlainTrackedState::default(); - let move_request = PlainTrackedState::default(); - let friend_list = PlainTrackedState::default(); - let slot_count = 0; - let login_keep_alive_timer = NetworkTimer::new(Duration::from_secs(58)); - let character_keep_alive_timer = NetworkTimer::new(Duration::from_secs(10)); - let map_keep_alive_timer = NetworkTimer::new(Duration::from_secs(4)); - let player_name = String::new(); - #[cfg(feature = "debug")] - let update_packets = PlainTrackedState::new(true); - #[cfg(feature = "debug")] - let packet_history = PlainTrackedState::default(); - - Self { - login_stream, - character_stream, - slot_count, - login_data, - map_stream, - map_stream_buffer, - characters, - move_request, - friend_list, - login_keep_alive_timer, - character_keep_alive_timer, - map_keep_alive_timer, - player_name, - #[cfg(feature = "debug")] - update_packets, - #[cfg(feature = "debug")] - packet_history, - } - } - - pub fn log_in( - &mut self, - client_info: &ClientInfo, - service_id: ServiceId, - username: String, - password: String, - ) -> Result, String> { - #[cfg(feature = "debug")] - let timer = Timer::new("log in"); - - let service = client_info - .services - .iter() - .find(|service| service.service_id() == service_id) - .unwrap(); - let service_address = format!("{}:{}", service.address, service.port); - - let login_stream = TcpStream::connect(service_address).map_err(|_| "failed to connect to login server".to_owned())?; - login_stream.set_read_timeout(Duration::from_secs(1).into()).unwrap(); - self.login_stream = Some(login_stream); - - self.send_packet_to_login_server(LoginServerLoginPacket::new(username.clone(), password.clone())); - - let response = self.get_data_from_login_server(); - let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - - let header = u16::from_bytes(&mut byte_stream).unwrap(); - let login_server_login_success_packet = match header { - LoginFailedPacket::HEADER => { - let packet = LoginFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); - match packet.reason { - LoginFailedReason::ServerClosed => return Err("server closed".to_string()), - LoginFailedReason::AlreadyLoggedIn => return Err("someone has already logged in with this id".to_string()), - LoginFailedReason::AlreadyOnline => return Err("already online".to_string()), - } - } - LoginFailedPacket2::HEADER => { - let packet = LoginFailedPacket2::payload_from_bytes_recorded(&mut byte_stream).unwrap(); - match packet.reason { - LoginFailedReason2::UnregisteredId => return Err("unregistered id".to_string()), - LoginFailedReason2::IncorrectPassword => return Err("incorrect password".to_string()), - LoginFailedReason2::IdExpired => return Err("id has expired".to_string()), - LoginFailedReason2::RejectedFromServer => return Err("rejected from server".to_string()), - LoginFailedReason2::BlockedByGMTeam => return Err("blocked by gm team".to_string()), - LoginFailedReason2::GameOutdated => return Err("game outdated".to_string()), - LoginFailedReason2::LoginProhibitedUntil => return Err("login prohibited until".to_string()), - LoginFailedReason2::ServerFull => return Err("server is full".to_string()), - LoginFailedReason2::CompanyAccountLimitReached => return Err("company account limit reached".to_string()), - } - } - LoginServerLoginSuccessPacket::HEADER => LoginServerLoginSuccessPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(), - _ => panic!(), - }; - - self.login_data = Some(LoginData::new( - login_server_login_success_packet.account_id, - login_server_login_success_packet.login_id1, - login_server_login_success_packet.login_id2, - login_server_login_success_packet.sex, - )); - - if login_server_login_success_packet.character_server_information.is_empty() { - return Err("no character server available".to_string()); - } - - #[cfg(feature = "debug")] - self.update_packet_history(byte_stream.into_metadata()); - - #[cfg(feature = "debug")] - timer.stop(); - - Ok(login_server_login_success_packet.character_server_information) - } - - pub fn select_server(&mut self, character_server_information: CharacterServerInformation) -> Result<(), String> { - #[cfg(feature = "debug")] - let timer = Timer::new("select server"); - - let server_ip = IpAddr::V4(character_server_information.server_ip.into()); - let socket_address = SocketAddr::new(server_ip, character_server_information.server_port); - self.character_stream = TcpStream::connect_timeout(&socket_address, Duration::from_secs(1)) - .map_err(|_| "Failed to connect to character server. Please try again")? - .into(); - - let login_data = self.login_data.clone().unwrap(); - - let character_server_login_packet = CharacterServerLoginPacket::new( - login_data.account_id, - login_data.login_id1, - login_data.login_id2, - login_data.sex, - ); - - let character_stream = self.character_stream.as_mut().ok_or("no character server connection")?; - character_stream - .write_all(&character_server_login_packet.packet_to_bytes().unwrap()) - .map_err(|_| "failed to send packet to character server")?; - - let response = self.get_data_from_character_server(); - - let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - let account_id = AccountId::from_bytes(&mut byte_stream).unwrap(); - - assert_eq!(account_id, login_data.account_id); - - #[cfg(feature = "debug")] - self.update_packet_history(byte_stream.into_metadata()); - - let response = self.get_data_from_character_server(); - let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - - let header = u16::from_bytes(&mut byte_stream).unwrap(); - let character_server_login_success_packet = match header { - LoginFailedPacket::HEADER => { - let packet = LoginFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); - match packet.reason { - LoginFailedReason::ServerClosed => return Err("server closed".to_string()), - LoginFailedReason::AlreadyLoggedIn => return Err("someone has already logged in with this id".to_string()), - LoginFailedReason::AlreadyOnline => return Err("already online".to_string()), - } - } - CharacterServerLoginSuccessPacket::HEADER => { - CharacterServerLoginSuccessPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap() - } - _ => panic!(), - }; - - self.send_packet_to_character_server(RequestCharacterListPacket::default()); - - #[cfg(feature = "debug")] - self.update_packet_history(byte_stream.into_metadata()); - - let response = self.get_data_from_character_server(); - let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - - let request_character_list_success_packet = - RequestCharacterListSuccessPacket::packet_from_bytes_recorded(&mut byte_stream).unwrap(); - self.characters.set(request_character_list_success_packet.character_information); - - #[cfg(feature = "debug")] - self.update_packet_history(byte_stream.into_metadata()); - - self.slot_count = character_server_login_success_packet.normal_slot_count as usize; - - #[cfg(feature = "debug")] - timer.stop(); - - Ok(()) - } - - pub fn character_selection_window(&self) -> CharacterSelectionWindow { - CharacterSelectionWindow::new(self.characters.new_remote(), self.move_request.new_remote(), self.slot_count) - } - - pub fn friends_window(&self) -> FriendsWindow { - FriendsWindow::new(self.friend_list.new_remote()) - } - - pub fn log_out(&mut self) -> Result<(), String> { - #[cfg(feature = "debug")] - let timer = Timer::new("log out"); - - self.send_packet_to_map_server(RestartPacket::new(RestartType::Disconnect)); - - #[cfg(feature = "debug")] - timer.stop(); - - Ok(()) - } - - #[cfg(feature = "debug")] - fn update_packet_history(&mut self, mut packets: Vec) { - if self.update_packets.cloned() { - self.packet_history.mutate(|buffer| { - packets.drain(..).for_each(|packet| buffer.push((packet, UnsafeCell::new(None)))); - }); - } - } - - #[cfg(feature = "debug")] - fn new_outgoing(&mut self, packet: &T) - where - T: OutgoingPacket + korangar_interface::elements::PrototypeElement + 'static, - { - if self.update_packets.cloned() { - self.packet_history.mutate(|buffer| { - buffer.push(( - PacketEntry::new_outgoing(packet, std::any::type_name::(), T::IS_PING), - UnsafeCell::new(None), - )); - }); - } - } - - fn send_packet_to_login_server(&mut self, packet: T) - where - T: OutgoingPacket + korangar_interface::elements::PrototypeElement + 'static, - { - #[cfg(feature = "debug")] - self.new_outgoing(&packet); - - let packet_bytes = packet.packet_to_bytes().unwrap(); - let login_stream = self.login_stream.as_mut().expect("no login server connection"); - - login_stream - .write_all(&packet_bytes) - .expect("failed to send packet to login server"); - } - - fn send_packet_to_character_server(&mut self, packet: T) - where - T: OutgoingPacket + korangar_interface::elements::PrototypeElement + 'static, - { - #[cfg(feature = "debug")] - self.new_outgoing(&packet); - - let packet_bytes = packet.packet_to_bytes().unwrap(); - let character_stream = self.character_stream.as_mut().expect("no character server connection"); - character_stream - .write_all(&packet_bytes) - .expect("failed to send packet to character server"); - } - - fn send_packet_to_map_server(&mut self, packet: T) - where - T: OutgoingPacket + korangar_interface::elements::PrototypeElement + 'static, - { - #[cfg(feature = "debug")] - self.new_outgoing(&packet); - - let packet_bytes = packet.packet_to_bytes().unwrap(); - let map_stream = self.map_stream.as_mut().expect("no map server connection"); - map_stream.write_all(&packet_bytes).expect("failed to send packet to map server"); - } - - fn get_data_from_login_server(&mut self) -> Vec { - let mut buffer = [0; 4096]; - let login_stream = self.login_stream.as_mut().expect("no login server connection"); - let response_length = login_stream.read(&mut buffer).expect("failed to get response from login server"); - buffer[..response_length].to_vec() - } - - fn get_data_from_character_server(&mut self) -> Vec { - let mut buffer = [0; 4096]; - let character_stream = self.character_stream.as_mut().expect("no character server connection"); - let response_length = character_stream - .read(&mut buffer) - .expect("failed to get response from character server"); - buffer[..response_length].to_vec() - } - - fn try_get_data_from_map_server(&mut self) -> Option> { - let mut buffer = [0; 8096]; - - let stream_buffer_length = self.map_stream_buffer.len(); - let map_stream = self.map_stream.as_mut()?; - let response_length = map_stream.read(&mut buffer[stream_buffer_length..]).ok()?; - - // We copy the buffered data *after* the read call, to save so unnecessary - // computation. - buffer[..stream_buffer_length].copy_from_slice(&self.map_stream_buffer); - - self.map_stream_buffer.clear(); - - let total_length = stream_buffer_length + response_length; - Some(buffer[..total_length].to_vec()) - } - - pub fn keep_alive(&mut self, delta_time: f64, client_tick: ClientTick) { - if self.login_keep_alive_timer.update(delta_time) && self.login_stream.is_some() { - self.send_packet_to_login_server(LoginServerKeepalivePacket::default()); - } - - if self.character_keep_alive_timer.update(delta_time) && self.character_stream.is_some() { - self.send_packet_to_character_server(CharacterServerKeepalivePacket::new()); - } - - if self.map_keep_alive_timer.update(delta_time) && self.map_stream.is_some() { - self.send_packet_to_map_server(RequestServerTickPacket::new(client_tick)); - } - } - - pub fn create_character(&mut self, slot: usize, name: String) -> Result<(), String> { - #[cfg(feature = "debug")] - let timer = Timer::new("create character"); - - #[cfg(feature = "debug")] - print_debug!("character with name {} in slot {}", name.magenta(), slot.magenta()); - - let hair_color = 0; - let hair_style = 0; - let start_job = 0; - let sex = Sex::Male; - - self.send_packet_to_character_server(CreateCharacterPacket::new( - name, slot as u8, hair_color, hair_style, start_job, sex, - )); - - let response = self.get_data_from_character_server(); - let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - - let header = u16::from_bytes(&mut byte_stream).unwrap(); - let create_character_success_packet = match header { - CharacterCreationFailedPacket::HEADER => { - let packet = CharacterCreationFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); - match packet.reason { - CharacterCreationFailedReason::CharacterNameAlreadyUsed => return Err("character name is already used".to_string()), - CharacterCreationFailedReason::NotOldEnough => return Err("you are not old enough to create a character".to_string()), - CharacterCreationFailedReason::NotAllowedToUseSlot => { - return Err("you are not allowed to use that character slot".to_string()); - } - CharacterCreationFailedReason::CharacterCerationFailed => return Err("character creation failed".to_string()), - } - } - CreateCharacterSuccessPacket::HEADER => CreateCharacterSuccessPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(), - _ => panic!(), - }; - - #[cfg(feature = "debug")] - self.update_packet_history(byte_stream.into_metadata()); - - self.characters.push(create_character_success_packet.character_information); - - #[cfg(feature = "debug")] - timer.stop(); - - Ok(()) - } - - pub fn delete_character(&mut self, character_id: CharacterId) -> Result<(), String> { - #[cfg(feature = "debug")] - let timer = Timer::new("delete character"); - - let email = "a@a.com".to_string(); - - #[cfg(feature = "debug")] - print_debug!("character with id {} and email {}", character_id.0.magenta(), email.magenta()); - - self.send_packet_to_character_server(DeleteCharacterPacket::new(character_id, email)); - - let response = self.get_data_from_character_server(); - let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - - let header = u16::from_bytes(&mut byte_stream).unwrap(); - match header { - CharacterDeletionFailedPacket::HEADER => { - let packet = CharacterDeletionFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); - match packet.reason { - CharacterDeletionFailedReason::NotAllowed => return Err("you are not allowed to delete this character".to_string()), - CharacterDeletionFailedReason::CharacterNotFound => return Err("character was not found".to_string()), - CharacterDeletionFailedReason::NotEligible => return Err("character is not eligible for deletion".to_string()), - } - } - CharacterDeletionSuccessPacket::HEADER => { - let _ = CharacterDeletionSuccessPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); - } - _ => panic!(), - } - - #[cfg(feature = "debug")] - self.update_packet_history(byte_stream.into_metadata()); - - self.characters.retain(|character| character.character_id != character_id); - - #[cfg(feature = "debug")] - timer.stop(); - - Ok(()) - } - - pub fn select_character(&mut self, slot: usize) -> Result<(AccountId, CharacterInformation, String), String> { - #[cfg(feature = "debug")] - let timer = Timer::new("select character"); - - #[cfg(feature = "debug")] - print_debug!("character in slot {}", slot.magenta()); - - self.send_packet_to_character_server(SelectCharacterPacket::new(slot as u8)); - - let response = self.get_data_from_character_server(); - let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - - let header = u16::from_bytes(&mut byte_stream).unwrap(); - let character_selection_success_packet = match header { - CharacterSelectionFailedPacket::HEADER => { - let packet = CharacterSelectionFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); - match packet.reason { - CharacterSelectionFailedReason::RejectedFromServer => return Err("rejected from server".to_string()), - } - } - LoginFailedPacket::HEADER => { - let packet = LoginFailedPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); - match packet.reason { - LoginFailedReason::ServerClosed => return Err("Server closed".to_string()), - LoginFailedReason::AlreadyLoggedIn => return Err("Someone has already logged in with this ID".to_string()), - LoginFailedReason::AlreadyOnline => return Err("Already online".to_string()), - } - } - MapServerUnavailablePacket::HEADER => { - let _ = MapServerUnavailablePacket::payload_from_bytes_recorded(&mut byte_stream).unwrap(); - return Err("Map server currently unavailable".to_string()); - } - CharacterSelectionSuccessPacket::HEADER => { - CharacterSelectionSuccessPacket::payload_from_bytes_recorded(&mut byte_stream).unwrap() - } - _ => panic!(), - }; - - let server_ip = IpAddr::V4(character_selection_success_packet.map_server_ip.into()); - let server_port = character_selection_success_packet.map_server_port; - - #[cfg(feature = "debug")] - print_debug!( - "connecting to map server at {} on port {}", - server_ip.magenta(), - character_selection_success_packet.map_server_port.magenta(), - ); - - let socket_address = SocketAddr::new(server_ip, server_port); - let map_stream = TcpStream::connect_timeout(&socket_address, Duration::from_secs(1)) - .map_err(|_| "Failed to connect to map server. Please try again")?; - - map_stream.set_nonblocking(true).unwrap(); - self.map_stream = Some(map_stream); - - let login_data = self.login_data.as_ref().unwrap(); - let account_id = login_data.account_id; - - self.send_packet_to_map_server(MapServerLoginPacket::new( - account_id, - character_selection_success_packet.character_id, - login_data.login_id1, - ClientTick(100), // TODO: what is the logic here? - login_data.sex, - )); - - #[cfg(feature = "debug")] - self.update_packet_history(byte_stream.into_metadata()); - - let character_information = self - .characters - .get() - .iter() - .find(|character| character.character_number as usize == slot) - .cloned() - .unwrap(); - - self.player_name = character_information.name.clone(); - - #[cfg(feature = "debug")] - timer.stop(); - - Ok(( - account_id, - character_information, - character_selection_success_packet.map_name.replace(".gat", ""), - )) - } - - pub fn disconnect_from_map_server(&mut self) { - // Dropping the TcpStream will also close the connection. - self.map_stream = None; - } - - pub fn request_switch_character_slot(&mut self, origin_slot: usize) { - self.move_request.set(Some(origin_slot)); - } - - pub fn cancel_switch_character_slot(&mut self) { - self.move_request.take(); - } - - pub fn switch_character_slot(&mut self, destination_slot: usize) -> Result<(), String> { - #[cfg(feature = "debug")] - let timer = Timer::new("switch character slot"); - - let origin_slot = self.move_request.take().unwrap(); - - #[cfg(feature = "debug")] - print_debug!("from slot {} to slot {}", origin_slot.magenta(), destination_slot.magenta()); - - self.send_packet_to_character_server(SwitchCharacterSlotPacket::new(origin_slot as u16, destination_slot as u16)); - - let response = self.get_data_from_character_server(); - let mut byte_stream: ByteStream = ByteStream::without_metadata(&response); - - let switch_character_slot_response_packet = - SwitchCharacterSlotResponsePacket::packet_from_bytes_recorded(&mut byte_stream).unwrap(); - - match switch_character_slot_response_packet.status { - SwitchCharacterSlotResponseStatus::Success => { - let _character_server_login_success_packet = - CharacterServerLoginSuccessPacket::packet_from_bytes_recorded(&mut byte_stream).unwrap(); - let _packet_006b = Packet6b00::packet_from_bytes_recorded(&mut byte_stream).unwrap(); - - let character_count = self.characters.len(); - self.characters.clear(); - - for _index in 0..character_count { - let character_information = CharacterInformation::from_bytes(&mut byte_stream).unwrap(); - self.characters.push(character_information); - } - - // packet_length and packet 0x09a0 are left unread because we - // don't need them - } - SwitchCharacterSlotResponseStatus::Error => return Err("failed to move character to a different slot".to_string()), - } - - #[cfg(feature = "debug")] - self.update_packet_history(byte_stream.into_metadata()); - - self.move_request.take(); - - #[cfg(feature = "debug")] - timer.stop(); - - Ok(()) - } - - pub fn request_player_move(&mut self, destination: Vector2) { - self.send_packet_to_map_server(RequestPlayerMovePacket::new(WorldPosition::new(destination.x, destination.y))); - } - - pub fn request_warp_to_map(&mut self, map_name: String, position: Vector2) { - self.send_packet_to_map_server(RequestWarpToMapPacket::new(map_name, TilePosition { - x: position.x as u16, - y: position.y as u16, - })); - } - - pub fn map_loaded(&mut self) { - self.send_packet_to_map_server(MapLoadedPacket::default()); - } - - pub fn request_entity_details(&mut self, entity_id: EntityId) { - self.send_packet_to_map_server(RequestDetailsPacket::new(entity_id)); - } - - pub fn request_player_attack(&mut self, entity_id: EntityId) { - self.send_packet_to_map_server(RequestActionPacket::new(entity_id, Action::Attack)); - } - - pub fn send_message(&mut self, message: String) { - let complete_message = format!("{} : {}", self.player_name, message); - - self.send_packet_to_map_server(GlobalMessagePacket::new( - complete_message.bytes().len() as u16 + 5, - complete_message, - )); - } - - pub fn start_dialog(&mut self, npc_id: EntityId) { - self.send_packet_to_map_server(StartDialogPacket::new(npc_id)); - } - - pub fn next_dialog(&mut self, npc_id: EntityId) { - self.send_packet_to_map_server(NextDialogPacket::new(npc_id)); - } - - pub fn close_dialog(&mut self, npc_id: EntityId) { - self.send_packet_to_map_server(CloseDialogPacket::new(npc_id)); - } - - pub fn choose_dialog_option(&mut self, npc_id: EntityId, option: i8) { - self.send_packet_to_map_server(ChooseDialogOptionPacket::new(npc_id, option)); - } - - pub fn request_item_equip(&mut self, item_index: ItemIndex, equip_position: EquipPosition) { - self.send_packet_to_map_server(RequestEquipItemPacket::new(item_index, equip_position)); - } - - pub fn request_item_unequip(&mut self, item_index: ItemIndex) { - self.send_packet_to_map_server(RequestUnequipItemPacket::new(item_index)); - } - - pub fn cast_skill(&mut self, skill_id: SkillId, skill_level: SkillLevel, entity_id: EntityId) { - self.send_packet_to_map_server(UseSkillAtIdPacket::new(skill_level, skill_id, entity_id)); - } - - pub fn cast_ground_skill(&mut self, skill_id: SkillId, skill_level: SkillLevel, target_position: Vector2) { - self.send_packet_to_map_server(UseSkillOnGroundPacket::new(skill_level, skill_id, TilePosition { - x: target_position.x, - y: target_position.y, - })); - } - - pub fn cast_channeling_skill(&mut self, skill_id: SkillId, skill_level: SkillLevel, entity_id: EntityId) { - self.send_packet_to_map_server(StartUseSkillPacket::new(skill_id, skill_level, entity_id)); - } - - pub fn stop_channeling_skill(&mut self, skill_id: SkillId) { - self.send_packet_to_map_server(EndUseSkillPacket::new(skill_id)); - } - - pub fn add_friend(&mut self, name: String) { - if name.len() > 24 { - #[cfg(feature = "debug")] - print_debug!("[{}] friend name {} is too long", "error".red(), name.magenta()); - - return; - } - - self.send_packet_to_map_server(AddFriendPacket::new(name)); - } - - pub fn remove_friend(&mut self, account_id: AccountId, character_id: CharacterId) { - self.send_packet_to_map_server(RemoveFriendPacket::new(account_id, character_id)); - } - - pub fn reject_friend_request(&mut self, account_id: AccountId, character_id: CharacterId) { - self.send_packet_to_map_server(FriendRequestResponsePacket::new( - account_id, - character_id, - FriendRequestResponse::Reject, - )); - } - - pub fn accept_friend_request(&mut self, account_id: AccountId, character_id: CharacterId) { - self.send_packet_to_map_server(FriendRequestResponsePacket::new( - account_id, - character_id, - FriendRequestResponse::Accept, - )); - } - - #[cfg_attr(feature = "debug", korangar_debug::profile)] - pub fn network_events(&mut self) -> Vec { - let mut events = Vec::new(); - - while let Some(data) = self.try_get_data_from_map_server() { - let mut byte_stream: ByteStream = ByteStream::without_metadata(&data); - - while !byte_stream.is_empty() { - let save_point = byte_stream.create_save_point(); - - // Packet is cut-off at the header - let Ok(header) = u16::from_bytes(&mut byte_stream) else { - byte_stream.restore_save_point(save_point); - self.map_stream_buffer = byte_stream.remaining_bytes(); - break; - }; - - match self.handle_packet(&mut byte_stream, header, &mut events) { - Ok(true) => {} - // Unknown packet - Ok(false) => { - #[cfg(feature = "debug")] - { - byte_stream.restore_save_point(save_point); - let packet = UnknownPacket::new(byte_stream.remaining_bytes()); - byte_stream.incoming_packet(&packet); - } - - break; - } - // Cut-off packet (probably). - Err(error) if error.is_byte_stream_too_short() => { - byte_stream.restore_save_point(save_point); - self.map_stream_buffer = byte_stream.remaining_bytes(); - break; - } - Err(error) => panic!("{:?}", error), - } - } - - #[cfg(feature = "debug")] - self.update_packet_history(byte_stream.into_metadata()); - } - - events - } - - #[cfg_attr(feature = "debug", korangar_debug::profile)] - fn handle_packet( - &mut self, - byte_stream: &mut ByteStream, - header: u16, - events: &mut Vec, - ) -> ConversionResult { - match header { - BroadcastMessagePacket::HEADER => { - let packet = BroadcastMessagePacket::payload_from_bytes_recorded(byte_stream)?; - let color = Color::rgb_u8(220, 200, 30); - let chat_message = ChatMessage::new(packet.message, color); - events.push(NetworkEvent::ChatMessage(chat_message)); - } - Broadcast2MessagePacket::HEADER => { - let packet = Broadcast2MessagePacket::payload_from_bytes_recorded(byte_stream)?; - // NOTE: Drop the alpha channel because it might be 0. - let color = Color::rgb_u8(packet.font_color.red, packet.font_color.green, packet.font_color.blue); - let chat_message = ChatMessage::new(packet.message, color); - events.push(NetworkEvent::ChatMessage(chat_message)); - } - OverheadMessagePacket::HEADER => { - let packet = OverheadMessagePacket::payload_from_bytes_recorded(byte_stream)?; - let color = Color::monochrome_u8(230); - let chat_message = ChatMessage::new(packet.message, color); - events.push(NetworkEvent::ChatMessage(chat_message)); - } - ServerMessagePacket::HEADER => { - let packet = ServerMessagePacket::payload_from_bytes_recorded(byte_stream)?; - let chat_message = ChatMessage::new(packet.message, Color::monochrome_u8(255)); - events.push(NetworkEvent::ChatMessage(chat_message)); - } - EntityMessagePacket::HEADER => { - let packet = EntityMessagePacket::payload_from_bytes_recorded(byte_stream)?; - // NOTE: Drop the alpha channel because it might be 0. - let color = Color::rgb_u8(packet.color.red, packet.color.green, packet.color.blue); - let chat_message = ChatMessage::new(packet.message, color); - events.push(NetworkEvent::ChatMessage(chat_message)); - } - DisplayEmotionPacket::HEADER => { - let _packet = DisplayEmotionPacket::payload_from_bytes_recorded(byte_stream)?; - } - EntityMovePacket::HEADER => { - let packet = EntityMovePacket::payload_from_bytes_recorded(byte_stream)?; - let (origin, destination) = ( - Vector2::new(packet.from_to.x1, packet.from_to.y1), - Vector2::new(packet.from_to.x2, packet.from_to.y2), - ); - events.push(NetworkEvent::EntityMove( - packet.entity_id, - origin, - destination, - packet.timestamp, - )); - } - EntityStopMovePacket::HEADER => { - let _packet = EntityStopMovePacket::payload_from_bytes_recorded(byte_stream)?; - } - PlayerMovePacket::HEADER => { - let packet = PlayerMovePacket::payload_from_bytes_recorded(byte_stream)?; - let (origin, destination) = ( - Vector2::new(packet.from_to.x1, packet.from_to.y1), - Vector2::new(packet.from_to.x2, packet.from_to.y2), - ); - events.push(NetworkEvent::PlayerMove(origin, destination, packet.timestamp)); - } - ChangeMapPacket::HEADER => { - let packet = ChangeMapPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::ChangeMap( - packet.map_name.replace(".gat", ""), - Vector2::new(packet.position.x as usize, packet.position.y as usize), - )); - } - EntityAppearedPacket::HEADER => { - let packet = EntityAppearedPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::AddEntity(packet.into())); - } - EntityAppeared2Packet::HEADER => { - let packet = EntityAppeared2Packet::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::AddEntity(packet.into())); - } - MovingEntityAppearedPacket::HEADER => { - let packet = MovingEntityAppearedPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::AddEntity(packet.into())); - } - EntityDisappearedPacket::HEADER => { - let packet = EntityDisappearedPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::RemoveEntity(packet.entity_id)); - } - UpdateStatusPacket::HEADER => { - let packet = UpdateStatusPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::UpdateStatus(packet.status_type)); - } - UpdateStatusPacket1::HEADER => { - let packet = UpdateStatusPacket1::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::UpdateStatus(packet.status_type)); - } - UpdateStatusPacket2::HEADER => { - let packet = UpdateStatusPacket2::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::UpdateStatus(packet.status_type)); - } - UpdateStatusPacket3::HEADER => { - let packet = UpdateStatusPacket3::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::UpdateStatus(packet.status_type)); - } - UpdateAttackRangePacket::HEADER => { - let _packet = UpdateAttackRangePacket::payload_from_bytes_recorded(byte_stream)?; - } - NewMailStatusPacket::HEADER => { - let _packet = NewMailStatusPacket::payload_from_bytes_recorded(byte_stream)?; - } - AchievementUpdatePacket::HEADER => { - let _packet = AchievementUpdatePacket::payload_from_bytes_recorded(byte_stream)?; - } - AchievementListPacket::HEADER => { - let _packet = AchievementListPacket::payload_from_bytes_recorded(byte_stream)?; - } - CriticalWeightUpdatePacket::HEADER => { - let _packet = CriticalWeightUpdatePacket::payload_from_bytes_recorded(byte_stream)?; - } - SpriteChangePacket::HEADER => { - let packet = SpriteChangePacket::payload_from_bytes_recorded(byte_stream)?; - if packet.sprite_type == 0 { - events.push(NetworkEvent::ChangeJob(packet.account_id, packet.value)); - } - } - InventoyStartPacket::HEADER => { - let _packet = InventoyStartPacket::payload_from_bytes_recorded(byte_stream)?; - let mut item_data = Vec::new(); - - // TODO: it might be better for performance and resilience to instead save a - // state in the networking system instaed of buffering *all* - // inventory packets if one of them is cut off - loop { - let header = u16::from_bytes(byte_stream)?; - - match header { - InventoyEndPacket::HEADER => { - break; - } - RegularItemListPacket::HEADER => { - let packet = RegularItemListPacket::payload_from_bytes_recorded(byte_stream)?; - for item_information in packet.item_information { - item_data.push(( - item_information.index, - item_information.item_id, - EquipPosition::None, - EquipPosition::None, - )); // TODO: Don't add that data here, only equippable items need this data. - } - } - EquippableItemListPacket::HEADER => { - let packet = EquippableItemListPacket::payload_from_bytes_recorded(byte_stream)?; - for item_information in packet.item_information { - item_data.push(( - item_information.index, - item_information.item_id, - item_information.equip_position, - item_information.equipped_position, - )); - } - } - _ => return Err(ConversionError::from_message("expected inventory packet")), - } - } - - let _ = InventoyEndPacket::payload_from_bytes_recorded(byte_stream)?; - - events.push(NetworkEvent::Inventory(item_data)); - } - EquippableSwitchItemListPacket::HEADER => { - let _packet = EquippableSwitchItemListPacket::payload_from_bytes_recorded(byte_stream)?; - } - MapTypePacket::HEADER => { - let _packet = MapTypePacket::payload_from_bytes_recorded(byte_stream)?; - } - UpdateSkillTreePacket::HEADER => { - let packet = UpdateSkillTreePacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::SkillTree(packet.skill_information)); - } - UpdateHotkeysPacket::HEADER => { - let _packet = UpdateHotkeysPacket::payload_from_bytes_recorded(byte_stream)?; - } - InitialStatusPacket::HEADER => { - let _packet = InitialStatusPacket::payload_from_bytes_recorded(byte_stream)?; - } - UpdatePartyInvitationStatePacket::HEADER => { - let _packet = UpdatePartyInvitationStatePacket::payload_from_bytes_recorded(byte_stream)?; - } - UpdateShowEquipPacket::HEADER => { - let _packet = UpdateShowEquipPacket::payload_from_bytes_recorded(byte_stream)?; - } - UpdateConfigurationPacket::HEADER => { - let _packet = UpdateConfigurationPacket::payload_from_bytes_recorded(byte_stream)?; - } - NavigateToMonsterPacket::HEADER => { - let _packet = NavigateToMonsterPacket::payload_from_bytes_recorded(byte_stream)?; - } - MarkMinimapPositionPacket::HEADER => { - let _packet = MarkMinimapPositionPacket::payload_from_bytes_recorded(byte_stream)?; - } - NextButtonPacket::HEADER => { - let _packet = NextButtonPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::AddNextButton); - } - CloseButtonPacket::HEADER => { - let _packet = CloseButtonPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::AddCloseButton); - } - DialogMenuPacket::HEADER => { - let packet = DialogMenuPacket::payload_from_bytes_recorded(byte_stream)?; - let choices = packet - .message - .split(':') - .map(String::from) - .filter(|text| !text.is_empty()) - .collect(); - - events.push(NetworkEvent::AddChoiceButtons(choices)); - } - DisplaySpecialEffectPacket::HEADER => { - let _packet = DisplaySpecialEffectPacket::payload_from_bytes_recorded(byte_stream)?; - } - DisplaySkillCooldownPacket::HEADER => { - let _packet = DisplaySkillCooldownPacket::payload_from_bytes_recorded(byte_stream)?; - } - DisplaySkillEffectAndDamagePacket::HEADER => { - let _packet = DisplaySkillEffectAndDamagePacket::payload_from_bytes_recorded(byte_stream)?; - } - DisplaySkillEffectNoDamagePacket::HEADER => { - let packet = DisplaySkillEffectNoDamagePacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::HealEffect( - packet.destination_entity_id, - packet.heal_amount as usize, - )); - - //events.push(NetworkEvent::VisualEffect()); - } - DisplayPlayerHealEffect::HEADER => { - let _packet = DisplayPlayerHealEffect::payload_from_bytes_recorded(byte_stream)?; - } - StatusChangePacket::HEADER => { - let _packet = StatusChangePacket::payload_from_bytes_recorded(byte_stream)?; - } - QuestNotificationPacket1::HEADER => { - let _packet = QuestNotificationPacket1::payload_from_bytes_recorded(byte_stream)?; - } - HuntingQuestNotificationPacket::HEADER => { - let _packet = HuntingQuestNotificationPacket::payload_from_bytes_recorded(byte_stream)?; - } - HuntingQuestUpdateObjectivePacket::HEADER => { - let _packet = HuntingQuestUpdateObjectivePacket::payload_from_bytes_recorded(byte_stream)?; - } - QuestRemovedPacket::HEADER => { - let _packet = QuestRemovedPacket::payload_from_bytes_recorded(byte_stream)?; - } - QuestListPacket::HEADER => { - let _packet = QuestListPacket::payload_from_bytes_recorded(byte_stream)?; - } - VisualEffectPacket::HEADER => { - let packet = VisualEffectPacket::payload_from_bytes_recorded(byte_stream)?; - let path = match packet.effect { - VisualEffect::BaseLevelUp => "angel.str", - VisualEffect::JobLevelUp => "joblvup.str", - VisualEffect::RefineFailure => "bs_refinefailed.str", - VisualEffect::RefineSuccess => "bs_refinesuccess.str", - VisualEffect::GameOver => "help_angel\\help_angel\\help_angel.str", - VisualEffect::PharmacySuccess => "p_success.str", - VisualEffect::PharmacyFailure => "p_failed.str", - VisualEffect::BaseLevelUpSuperNovice => "help_angel\\help_angel\\help_angel.str", - VisualEffect::JobLevelUpSuperNovice => "help_angel\\help_angel\\help_angel.str", - VisualEffect::BaseLevelUpTaekwon => "help_angel\\help_angel\\help_angel.str", - }; - - events.push(NetworkEvent::VisualEffect(path, packet.entity_id)); - } - DisplayGainedExperiencePacket::HEADER => { - let _packet = DisplayGainedExperiencePacket::payload_from_bytes_recorded(byte_stream)?; - } - DisplayImagePacket::HEADER => { - let _packet = DisplayImagePacket::payload_from_bytes_recorded(byte_stream)?; - } - StateChangePacket::HEADER => { - let _packet = StateChangePacket::payload_from_bytes_recorded(byte_stream)?; - } - - QuestEffectPacket::HEADER => { - let packet = QuestEffectPacket::payload_from_bytes_recorded(byte_stream)?; - let event = match packet.effect { - QuestEffect::None => NetworkEvent::RemoveQuestEffect(packet.entity_id), - _ => NetworkEvent::AddQuestEffect(packet), - }; - events.push(event); - } - ItemPickupPacket::HEADER => { - let packet = ItemPickupPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::AddIventoryItem( - packet.index, - packet.item_id, - packet.equip_position, - EquipPosition::None, - )); - } - RemoveItemFromInventoryPacket::HEADER => { - let _packet = RemoveItemFromInventoryPacket::payload_from_bytes_recorded(byte_stream)?; - } - ServerTickPacket::HEADER => { - let packet = ServerTickPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::UpdateClientTick(packet.client_tick)); - } - RequestPlayerDetailsSuccessPacket::HEADER => { - let packet = RequestPlayerDetailsSuccessPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::UpdateEntityDetails(EntityId(packet.character_id.0), packet.name)); - } - RequestEntityDetailsSuccessPacket::HEADER => { - let packet = RequestEntityDetailsSuccessPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::UpdateEntityDetails(packet.entity_id, packet.name)); - } - UpdateEntityHealthPointsPacket::HEADER => { - let packet = UpdateEntityHealthPointsPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::UpdateEntityHealth( - packet.entity_id, - packet.health_points as usize, - packet.maximum_health_points as usize, - )); - } - RequestPlayerAttackFailedPacket::HEADER => { - let _packet = RequestPlayerAttackFailedPacket::payload_from_bytes_recorded(byte_stream)?; - } - DamagePacket::HEADER => { - let packet = DamagePacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::DamageEffect( - packet.destination_entity_id, - packet.damage_amount as usize, - )); - } - NpcDialogPacket::HEADER => { - let packet = NpcDialogPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::OpenDialog(packet.text, packet.npc_id)); - } - RequestEquipItemStatusPacket::HEADER => { - let packet = RequestEquipItemStatusPacket::payload_from_bytes_recorded(byte_stream)?; - if let RequestEquipItemStatus::Success = packet.result { - events.push(NetworkEvent::UpdateEquippedPosition { - index: packet.inventory_index, - equipped_position: packet.equipped_position, - }); - } - } - RequestUnequipItemStatusPacket::HEADER => { - let packet = RequestUnequipItemStatusPacket::payload_from_bytes_recorded(byte_stream)?; - if let RequestUnequipItemStatus::Success = packet.result { - events.push(NetworkEvent::UpdateEquippedPosition { - index: packet.inventory_index, - equipped_position: EquipPosition::None, - }); - } - } - Packet8302::HEADER => { - let _packet = Packet8302::payload_from_bytes_recorded(byte_stream)?; - } - Packet180b::HEADER => { - let _packet = Packet180b::payload_from_bytes_recorded(byte_stream)?; - } - MapServerLoginSuccessPacket::HEADER => { - let packet = MapServerLoginSuccessPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::UpdateClientTick(packet.client_tick)); - events.push(NetworkEvent::SetPlayerPosition(Vector2::new( - packet.position.x, - packet.position.y, - ))); - } - RestartResponsePacket::HEADER => { - let packet = RestartResponsePacket::payload_from_bytes_recorded(byte_stream)?; - match packet.result { - RestartResponseStatus::Ok => events.push(NetworkEvent::Disconnect), - RestartResponseStatus::Nothing => { - let color = Color::rgb_u8(255, 100, 100); - let chat_message = ChatMessage::new("Failed to log out.".to_string(), color); - events.push(NetworkEvent::ChatMessage(chat_message)); - } - } - } - DisconnectResponsePacket::HEADER => { - let packet = DisconnectResponsePacket::payload_from_bytes_recorded(byte_stream)?; - match packet.result { - DisconnectResponseStatus::Ok => events.push(NetworkEvent::Disconnect), - DisconnectResponseStatus::Wait10Seconds => { - let color = Color::rgb_u8(255, 100, 100); - let chat_message = ChatMessage::new("Please wait 10 seconds before trying to log out.".to_string(), color); - events.push(NetworkEvent::ChatMessage(chat_message)); - } - } - } - UseSkillSuccessPacket::HEADER => { - let _packet = UseSkillSuccessPacket::payload_from_bytes_recorded(byte_stream)?; - } - ToUseSkillSuccessPacket::HEADER => { - let _packet = ToUseSkillSuccessPacket::payload_from_bytes_recorded(byte_stream)?; - } - NotifySkillUnitPacket::HEADER => { - let packet = NotifySkillUnitPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::AddSkillUnit( - packet.entity_id, - packet.unit_id, - Vector2::new(packet.position.x as usize, packet.position.y as usize), - )); - } - SkillUnitDisappearPacket::HEADER => { - let packet = SkillUnitDisappearPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::RemoveSkillUnit(packet.entity_id)); - } - NotifyGroundSkillPacket::HEADER => { - let _packet = NotifyGroundSkillPacket::payload_from_bytes_recorded(byte_stream)?; - } - FriendListPacket::HEADER => { - let packet = FriendListPacket::payload_from_bytes_recorded(byte_stream)?; - self.friend_list.mutate(|friends| { - *friends = packet.friends.into_iter().map(|friend| (friend, UnsafeCell::new(None))).collect(); - }); - } - FriendOnlineStatusPacket::HEADER => { - let _packet = FriendOnlineStatusPacket::payload_from_bytes_recorded(byte_stream)?; - } - FriendRequestPacket::HEADER => { - let packet = FriendRequestPacket::payload_from_bytes_recorded(byte_stream)?; - events.push(NetworkEvent::FriendRequest(packet.friend)); - } - FriendRequestResultPacket::HEADER => { - let packet = FriendRequestResultPacket::payload_from_bytes_recorded(byte_stream)?; - if packet.result == FriendRequestResult::Accepted { - self.friend_list.push((packet.friend.clone(), UnsafeCell::new(None))); - } - - let color = Color::rgb_u8(220, 200, 30); - let chat_message = ChatMessage::new(packet.into_message(), color); - events.push(NetworkEvent::ChatMessage(chat_message)); - } - NotifyFriendRemovedPacket::HEADER => { - let packet = NotifyFriendRemovedPacket::payload_from_bytes_recorded(byte_stream)?; - self.friend_list.with_mut(|friends| { - friends.retain(|(friend, _)| !(friend.account_id == packet.account_id && friend.character_id == packet.character_id)); - ValueState::Mutated(()) - }); - } - PartyInvitePacket::HEADER => { - let _packet = PartyInvitePacket::payload_from_bytes_recorded(byte_stream)?; - } - StatusChangeSequencePacket::HEADER => { - let _packet = StatusChangeSequencePacket::payload_from_bytes_recorded(byte_stream)?; - } - ReputationPacket::HEADER => { - let _packet = ReputationPacket::payload_from_bytes_recorded(byte_stream)?; - } - ClanInfoPacket::HEADER => { - let _packet = ClanInfoPacket::payload_from_bytes_recorded(byte_stream)?; - } - ClanOnlineCountPacket::HEADER => { - let _packet = ClanOnlineCountPacket::payload_from_bytes_recorded(byte_stream)?; - } - ChangeMapCellPacket::HEADER => { - let _packet = ChangeMapCellPacket::payload_from_bytes_recorded(byte_stream)?; - } - _ => return Ok(false), - } - - Ok(true) - } - - #[cfg(feature = "debug")] - pub fn clear_packet_history(&mut self) { - self.packet_history.mutate(|buffer| { - buffer.clear(); - }); - } - - #[cfg(feature = "debug")] - pub fn packet_window(&self) -> PacketWindow<256> { - PacketWindow::new(self.packet_history.new_remote(), self.update_packets.clone()) - } -} diff --git a/korangar/src/system/timer.rs b/korangar/src/system/timer.rs index a0fce0d9..aae72163 100644 --- a/korangar/src/system/timer.rs +++ b/korangar/src/system/timer.rs @@ -1,7 +1,7 @@ use std::time::Instant; use chrono::prelude::*; -use ragnarok_networking::ClientTick; +use ragnarok_packets::ClientTick; pub struct GameTimer { global_timer: Instant, diff --git a/korangar/src/world/entity/mod.rs b/korangar/src/world/entity/mod.rs index 6940f67d..4431dbb7 100644 --- a/korangar/src/world/entity/mod.rs +++ b/korangar/src/world/entity/mod.rs @@ -4,7 +4,8 @@ use cgmath::{Array, Vector2, Vector3, VectorSpace}; use derive_new::new; use korangar_interface::elements::PrototypeElement; use korangar_interface::windows::{PrototypeWindow, Window}; -use ragnarok_networking::{AccountId, CharacterInformation, ClientTick, EntityId, Sex, StatusType}; +use korangar_networking::EntityData; +use ragnarok_packets::{AccountId, CharacterInformation, ClientTick, EntityId, Sex, StatusType, WorldPosition}; use vulkano::buffer::Subbuffer; #[cfg(feature = "debug")] @@ -15,7 +16,6 @@ use crate::interface::layout::{ScreenPosition, ScreenSize}; use crate::interface::theme::GameTheme; use crate::interface::windows::WindowCache; use crate::loaders::{ActionLoader, Actions, AnimationState, GameFileLoader, ScriptLoader, Sprite, SpriteLoader}; -use crate::network::EntityData; use crate::world::Map; #[cfg(feature = "debug")] use crate::world::MarkerIdentifier; @@ -274,6 +274,7 @@ impl Common { let entity_id = entity_data.entity_id; let job_id = entity_data.job as usize; let grid_position = entity_data.position; + let grid_position = Vector2::new(grid_position.x, grid_position.y); let position = map.get_world_position(grid_position); let head_direction = entity_data.head_direction; @@ -325,7 +326,9 @@ impl Common { }; if let Some(destination) = entity_data.destination { - common.move_from_to(map, entity_data.position, destination, client_tick); + let position_from = Vector2::new(entity_data.position.x, entity_data.position.y); + let position_to = Vector2::new(destination.x, destination.y); + common.move_from_to(map, position_from, position_to, client_tick); } common @@ -774,7 +777,7 @@ impl Player { map: &Map, account_id: AccountId, character_information: CharacterInformation, - player_position: Vector2, + player_position: WorldPosition, client_tick: ClientTick, ) -> Self { let spell_points = character_information.spell_points as usize; diff --git a/korangar/src/world/map/mod.rs b/korangar/src/world/map/mod.rs index 8cc9f03a..11d186d1 100644 --- a/korangar/src/world/map/mod.rs +++ b/korangar/src/world/map/mod.rs @@ -9,7 +9,7 @@ use korangar_debug::profiling::Profiler; use korangar_interface::windows::PrototypeWindow; #[cfg(feature = "debug")] use option_ext::OptionExt; -use ragnarok_networking::ClientTick; +use ragnarok_packets::ClientTick; use vulkano::buffer::Subbuffer; use vulkano::image::view::ImageView; diff --git a/korangar/src/world/model/mod.rs b/korangar/src/world/model/mod.rs index 4217896c..a551335e 100644 --- a/korangar/src/world/model/mod.rs +++ b/korangar/src/world/model/mod.rs @@ -5,7 +5,7 @@ use std::ops::Mul; use cgmath::{Matrix4, Vector3}; use derive_new::new; use korangar_interface::elements::PrototypeElement; -use ragnarok_networking::ClientTick; +use ragnarok_packets::ClientTick; pub use self::node::{BoundingBox, Node, OrientedBox}; use crate::graphics::{Camera, GeometryRenderer, Renderer, Transform}; diff --git a/korangar/src/world/model/node.rs b/korangar/src/world/model/node.rs index 7b0c14a9..7bd9e663 100644 --- a/korangar/src/world/model/node.rs +++ b/korangar/src/world/model/node.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use cgmath::{Array, Matrix4, SquareMatrix, Vector3, Vector4}; use derive_new::new; use korangar_interface::elements::PrototypeElement; -use ragnarok_networking::ClientTick; +use ragnarok_packets::ClientTick; use vulkano::buffer::Subbuffer; use vulkano::image::view::ImageView; diff --git a/korangar/src/world/object/mod.rs b/korangar/src/world/object/mod.rs index 2c972cdf..cb2e943f 100644 --- a/korangar/src/world/object/mod.rs +++ b/korangar/src/world/object/mod.rs @@ -4,7 +4,7 @@ use cgmath::Matrix4; use derive_new::new; use korangar_interface::elements::PrototypeElement; use korangar_interface::windows::PrototypeWindow; -use ragnarok_networking::ClientTick; +use ragnarok_packets::ClientTick; use crate::graphics::*; use crate::world::*; diff --git a/korangar_debug/src/lib.rs b/korangar_debug/src/lib.rs index a5cd8a35..222a8d04 100644 --- a/korangar_debug/src/lib.rs +++ b/korangar_debug/src/lib.rs @@ -1,6 +1,7 @@ -#![feature(thread_local)] #![feature(decl_macro)] +#![feature(inline_const)] #![feature(let_chains)] +#![feature(thread_local)] #[macro_use] pub mod logging; diff --git a/korangar_debug/src/profiling/ring_buffer.rs b/korangar_debug/src/profiling/ring_buffer.rs index 1f1addb4..e149e307 100644 --- a/korangar_debug/src/profiling/ring_buffer.rs +++ b/korangar_debug/src/profiling/ring_buffer.rs @@ -6,15 +6,13 @@ pub struct RingBuffer { impl Default for RingBuffer { fn default() -> Self { Self { - buffer: [Self::DEFAULT_ITEM; N], + buffer: [const { None }; N], index: 0, } } } impl RingBuffer { - const DEFAULT_ITEM: Option = None; - pub fn push(&mut self, item: T) { let index = self.index; self.buffer[index] = Some(item); diff --git a/korangar_interface/README.md b/korangar_interface/README.md index faf26c5c..01c2e877 100644 --- a/korangar_interface/README.md +++ b/korangar_interface/README.md @@ -1,3 +1,3 @@ -# Ragnarok Interface +# Korangar Interface A crate that exposes a UI that can be used to display Ragnarok Online windows. diff --git a/korangar_interface/src/layout/resolver.rs b/korangar_interface/src/layout/resolver.rs index 2eb9db84..bb7d98cd 100644 --- a/korangar_interface/src/layout/resolver.rs +++ b/korangar_interface/src/layout/resolver.rs @@ -39,7 +39,7 @@ where ) -> Self { let window_size = window_size.shrink(border.scaled(scaling).doubled()); - // NOTE: I'm not enirely sure why we need to subtract one border here, but if we + // I'm not enirely sure why we need to subtract one border here, but if we // don't the `super` bound is too big. let parent_limits = ParentLimits::from_bound(size_bound, screen_size.shrink(border.scaled(scaling)), scaling); let base_position = App::Position::from_size(border.scaled(scaling)); diff --git a/korangar_interface/src/lib.rs b/korangar_interface/src/lib.rs index 0f5ced57..de869e65 100644 --- a/korangar_interface/src/lib.rs +++ b/korangar_interface/src/lib.rs @@ -129,7 +129,7 @@ where { pub fn new(available_space: App::Size) -> Self { let window_cache = App::Cache::create(); - // NOTE: We need to initially clear the interface buffer + // We need to initially clear the interface buffer let post_update = PostUpdate::new().with_render(); Self { @@ -206,7 +206,7 @@ where self.window_cache.register_window(window_class, new_position, new_size); } - // NOTE: If the window got smaller, we need to re-render the entire interface. + // If the window got smaller, we need to re-render the entire interface. // If it got bigger, we can just draw over the previous frame. match previous_size.width() > new_size.width() || previous_size.height() > new_size.height() { true => self.post_update.render(), diff --git a/korangar_interface/src/state.rs b/korangar_interface/src/state.rs index 7ee7628c..ee8a3465 100644 --- a/korangar_interface/src/state.rs +++ b/korangar_interface/src/state.rs @@ -29,7 +29,7 @@ impl Version { pub trait TrackedState: Clone { type RemoteType: Remote + 'static; - /// Set the inner value. + /// Set the inner value, advancing the version. fn set(&mut self, value: Value); /// Get an immutable reference to the inner value. @@ -123,11 +123,6 @@ impl PlainTrackedState { version: self.get_version(), } } - - pub fn foo_test(&self) { - println!("Weak count: {}", Rc::weak_count(&self.0)); - println!("Strong count: {}", Rc::strong_count(&self.0)); - } } impl Clone for PlainTrackedState { diff --git a/korangar_interface/src/theme.rs b/korangar_interface/src/theme.rs index af130e6d..2e934e9b 100644 --- a/korangar_interface/src/theme.rs +++ b/korangar_interface/src/theme.rs @@ -136,6 +136,10 @@ where { fn background_color(&self) -> App::Color; fn font_size(&self) -> App::FontSize; + fn broadcast_color(&self) -> App::Color; + fn server_color(&self) -> App::Color; + fn error_color(&self) -> App::Color; + fn information_color(&self) -> App::Color; } pub trait CursorTheme diff --git a/korangar_interface/src/windows/mod.rs b/korangar_interface/src/windows/mod.rs index d9bbe844..bbdc38c1 100644 --- a/korangar_interface/src/windows/mod.rs +++ b/korangar_interface/src/windows/mod.rs @@ -210,7 +210,7 @@ where } pub fn open_popup(&mut self, element: ElementCell, position_tracker: Tracker, size_tracker: Tracker) { - // NOTE: Very important to link back + // Very important to link back let weak_element = Rc::downgrade(&element); element.borrow_mut().link_back(weak_element, None); diff --git a/korangar_networking/Cargo.toml b/korangar_networking/Cargo.toml new file mode 100644 index 00000000..e1eeb118 --- /dev/null +++ b/korangar_networking/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "korangar_networking" +version = "0.1.0" +edition = "2021" + +[dependencies] +ragnarok_bytes = { workspace = true } +ragnarok_packets = { workspace = true } +tokio = { version = "1.37", features = ["full"] } + +[dev-dependencies] +reqwest = { version = "0.12", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } +korangar_debug = { workspace = true } + +[features] +debug = [] diff --git a/korangar_networking/README.md b/korangar_networking/README.md new file mode 100644 index 00000000..b2fbb349 --- /dev/null +++ b/korangar_networking/README.md @@ -0,0 +1,4 @@ +# Korangar Networking + +An opinionated wrapper around the `ragnarok_packets` crate. +This crate exposes a networking system that can run in a seperate thread and maintain connections to the login, character, and map servers. diff --git a/korangar_networking/examples/ollama-chat-bot.rs b/korangar_networking/examples/ollama-chat-bot.rs new file mode 100644 index 00000000..20715f17 --- /dev/null +++ b/korangar_networking/examples/ollama-chat-bot.rs @@ -0,0 +1,202 @@ +use std::collections::HashMap; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::thread::sleep; +use std::time::Duration; + +use korangar_debug::logging::Colorize; +use korangar_networking::{DisconnectReason, NetworkEvent, NetworkingSystem}; +use reqwest::StatusCode; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +struct Message { + role: String, + content: String, +} + +#[derive(serde::Serialize)] +struct Request { + model: String, + messages: Vec, + stream: bool, +} + +#[allow(dead_code)] +#[derive(serde::Deserialize)] +struct Response { + model: String, + created_at: String, + message: Message, + done: bool, + total_duration: u64, + load_duration: u64, + prompt_eval_duration: u64, + eval_count: u64, + eval_duration: u64, +} + +struct MessageHistory { + hash_map: HashMap>, +} + +impl MessageHistory { + fn get_message_history_with(&mut self, user: String) -> &mut Vec { + self.hash_map.entry(user).or_insert_with(|| { + vec![ + Message { + role: "user".to_owned(), + content: "You are in a video game, are only allowed to reply in very broken english, your responses must be less that \ + 40 chars. Your name is Joe." + .to_owned(), + }, + // Llama will not be able to understand the history of the conversation if "user" + // and "assistant" roles don't alternate, so we insert this dummy response. + Message { + role: "assistant".to_owned(), + content: "Okey.".to_owned(), + }, + ] + }) + } +} + +#[tokio::main] +async fn main() { + // Chat bot settings. + const SOCKET_ADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(49, 12, 109, 207)), 6900); + const OLLAMA_ENDPOINT: &str = "http://127.0.0.1:11434/api/chat"; + const OLLAMA_MODEL: &str = "llama2:13b"; + const USERNAME: &str = "username"; + const PASSWORD: &str = "password"; + const CHARACTER_NAME: &str = "character name"; + + // Create the networking system and HTTP client. + let mut networking_system = NetworkingSystem::spawn(); + let client = reqwest::Client::new(); + + // Persistent data. + let mut saved_login_data = None; + let mut message_history = MessageHistory { hash_map: HashMap::new() }; + + // Kick of the bot by connecting to the login server. + networking_system.connect_to_login_server(SOCKET_ADDR, USERNAME.to_owned(), PASSWORD.to_owned()); + + loop { + for event in networking_system.get_events() { + match event { + NetworkEvent::LoginServerConnected { + character_servers, + login_data, + } => { + println!("[{}] Successfully connected to login server", "Setup".green()); + + networking_system.disconnect_from_login_server(); + networking_system.connect_to_character_server(&login_data, character_servers[0].clone()); + + saved_login_data = Some(login_data); + } + NetworkEvent::LoginServerConnectionFailed { message, .. } => { + panic!("Failed to connect to login server: {}", message); + } + NetworkEvent::LoginServerDisconnected { + reason: DisconnectReason::ConnectionError, + } => { + panic!("Login server connection error"); + } + NetworkEvent::CharacterServerConnected { .. } => { + println!("[{}] Successfully connected to character server", "Setup".green()); + + networking_system.request_character_list().expect("Character server disconnected"); + } + NetworkEvent::CharacterServerConnectionFailed { message, .. } => { + panic!("Failed to connect to character server: {}", message); + } + NetworkEvent::CharacterServerDisconnected { + reason: DisconnectReason::ConnectionError, + } => { + panic!("Character server connection error"); + } + NetworkEvent::CharacterSelectionFailed { message, .. } => { + panic!("Failed to select character: {}", message); + } + NetworkEvent::MapServerDisconnected { + reason: DisconnectReason::ConnectionError, + } => { + panic!("Map server connection error"); + } + NetworkEvent::CharacterList { characters } => { + let character_slot = characters + .iter() + .find(|character| character.name == CHARACTER_NAME) + .expect(&format!("Character with name \"{}\" not found for this user", CHARACTER_NAME)) + .character_number as usize; + + println!("[{}] Using character in slot: {}", "Setup".green(), character_slot.green()); + + networking_system + .select_character(character_slot) + .expect("Character server disconnected"); + } + NetworkEvent::CharacterSelected { login_data, .. } => { + let login_login_data = saved_login_data.as_ref().unwrap(); + + networking_system.disconnect_from_character_server(); + networking_system.connect_to_map_server(login_login_data, login_data); + + networking_system.map_loaded().expect("Map server disconnected"); + } + NetworkEvent::ChatMessage { text, .. } => { + if text.starts_with(CHARACTER_NAME) { + continue; + } + + let Some((user, message)) = text.split_once(" : ") else { + continue; + }; + + println!("[{}] Received Message by user: {}", "Chatbot".cyan(), user.yellow()); + println!("[{}] Message content: {}", "Chatbot".cyan(), message.yellow()); + println!("[{}] Generating response..", "LLaMA".magenta()); + + let previous_messages = message_history.get_message_history_with(user.to_owned()); + + previous_messages.push(Message { + role: "user".to_owned(), + content: message.to_owned(), + }); + + let result = client + .post(OLLAMA_ENDPOINT) + .json(&Request { + model: OLLAMA_MODEL.to_owned(), + messages: previous_messages.clone(), + stream: false, + }) + .send() + .await + .expect("failed to send request to ollama"); + + if result.status() == StatusCode::OK { + let response: Response = result.json().await.unwrap(); + let response = &response.message; + + println!("[{}] Generated response: {}", "LLaMA".magenta(), response.content.yellow()); + println!("[{}] Sending response..", "Chatbot".cyan()); + + previous_messages.push(Message { + role: response.role.to_owned(), + content: response.content.to_owned(), + }); + + networking_system + .send_chat_message(CHARACTER_NAME, &response.content) + .expect("Map server disconnected"); + } + } + _ => {} + } + } + + // After processing events, sleep for a bit. + sleep(Duration::from_millis(200)); + } +} diff --git a/korangar_networking/src/entity.rs b/korangar_networking/src/entity.rs new file mode 100644 index 00000000..41430af8 --- /dev/null +++ b/korangar_networking/src/entity.rs @@ -0,0 +1,80 @@ +use ragnarok_packets::*; + +#[derive(Debug)] +pub struct EntityData { + pub entity_id: EntityId, + pub movement_speed: u16, + pub job: u16, + pub position: WorldPosition, + pub destination: Option, + pub health_points: i32, + pub maximum_health_points: i32, + pub head_direction: usize, + pub sex: Sex, +} + +impl EntityData { + pub fn from_character(account_id: AccountId, character_information: CharacterInformation, position: WorldPosition) -> Self { + Self { + entity_id: EntityId(account_id.0), + movement_speed: character_information.movement_speed as u16, + job: character_information.job as u16, + position, + destination: None, + health_points: character_information.health_points as i32, + maximum_health_points: character_information.maximum_health_points as i32, + head_direction: 0, // TODO: get correct rotation + sex: character_information.sex, + } + } +} + +impl From for EntityData { + fn from(packet: EntityAppearedPacket) -> Self { + Self { + entity_id: packet.entity_id, + movement_speed: packet.movement_speed, + job: packet.job, + position: packet.position, + destination: None, + health_points: packet.health_points, + maximum_health_points: packet.maximum_health_points, + head_direction: packet.head_direction as usize, + sex: packet.sex, + } + } +} + +impl From for EntityData { + fn from(packet: EntityAppeared2Packet) -> Self { + Self { + entity_id: packet.entity_id, + movement_speed: packet.movement_speed, + job: packet.job, + position: packet.position, + destination: None, + health_points: packet.health_points, + maximum_health_points: packet.maximum_health_points, + head_direction: packet.head_direction as usize, + sex: packet.sex, + } + } +} + +impl From for EntityData { + fn from(packet: MovingEntityAppearedPacket) -> Self { + let (origin, destination) = packet.position.to_origin_destination(); + + Self { + entity_id: packet.entity_id, + movement_speed: packet.movement_speed, + job: packet.job, + position: origin, + destination: Some(destination), + health_points: packet.health_points, + maximum_health_points: packet.maximum_health_points, + head_direction: packet.head_direction as usize, + sex: packet.sex, + } + } +} diff --git a/korangar_networking/src/event.rs b/korangar_networking/src/event.rs new file mode 100644 index 00000000..dcdd18a4 --- /dev/null +++ b/korangar_networking/src/event.rs @@ -0,0 +1,194 @@ +use ragnarok_packets::*; + +use crate::{ + CharacterServerLoginData, EntityData, InventoryItem, LoginServerLoginData, MessageColor, UnifiedCharacterSelectionFailedReason, + UnifiedLoginFailedReason, +}; + +/// An event triggered by one of the Ragnarok Online servers. +#[derive(Debug)] +pub enum NetworkEvent { + LoginServerConnected { + character_servers: Vec, + login_data: LoginServerLoginData, + }, + LoginServerConnectionFailed { + reason: UnifiedLoginFailedReason, + message: &'static str, + }, + LoginServerDisconnected { + reason: DisconnectReason, + }, + CharacterServerConnected { + normal_slot_count: usize, + }, + CharacterServerConnectionFailed { + reason: LoginFailedReason, + message: &'static str, + }, + CharacterServerDisconnected { + reason: DisconnectReason, + }, + AccountId(AccountId), + CharacterList { + characters: Vec, + }, + CharacterSelected { + login_data: CharacterServerLoginData, + map_name: String, + }, + CharacterSelectionFailed { + reason: UnifiedCharacterSelectionFailedReason, + message: &'static str, + }, + CharacterCreated { + character_information: CharacterInformation, + }, + CharacterCreationFailed { + reason: CharacterCreationFailedReason, + message: &'static str, + }, + CharacterDeleted, + CharacterDeletionFailed { + reason: CharacterDeletionFailedReason, + message: &'static str, + }, + MapServerDisconnected { + reason: DisconnectReason, + }, + /// Add an entity to the list of entities that the client is aware of. + AddEntity(EntityData), + /// Remove an entity from the list of entities that the client is aware of + /// by its id. + RemoveEntity(EntityId), + /// The player is pathing to a new position. + PlayerMove(WorldPosition, WorldPosition, ClientTick), + /// An Entity nearby is pathing to a new position. + EntityMove(EntityId, WorldPosition, WorldPosition, ClientTick), + /// Player was moved to a new position on a different map or the current map + ChangeMap(String, TilePosition), + /// Update the client side [`tick + /// counter`](crate::system::GameTimer::base_client_tick) to keep server and + /// client synchronized. + UpdateClientTick(ClientTick), + /// New chat message for the client. + ChatMessage { + text: String, + color: MessageColor, + }, + CharacterSlotSwitched, + CharacterSlotSwitchFailed, + /// Update entity details. Mostly received when the client sends + /// [RequestDetailsPacket] after the player hovered an entity. + UpdateEntityDetails(EntityId, String), + UpdateEntityHealth(EntityId, usize, usize), + DamageEffect(EntityId, usize), + HealEffect(EntityId, usize), + UpdateStatus(StatusType), + OpenDialog(String, EntityId), + AddNextButton, + AddCloseButton, + AddChoiceButtons(Vec), + AddQuestEffect(QuestEffectPacket), + RemoveQuestEffect(EntityId), + SetInventory { + items: Vec, + }, + AddIventoryItem(ItemIndex, ItemId, EquipPosition, EquipPosition), + SkillTree(Vec), + UpdateEquippedPosition { + index: ItemIndex, + equipped_position: EquipPosition, + }, + ChangeJob(AccountId, u32), + SetPlayerPosition(WorldPosition), + LoggedOut, + FriendRequest { + requestee: Friend, + }, + VisualEffect(&'static str, EntityId), + AddSkillUnit(EntityId, UnitId, TilePosition), + RemoveSkillUnit(EntityId), + SetFriendList { + friends: Vec, + }, + FriendAdded { + friend: Friend, + }, + FriendRemoved { + account_id: AccountId, + character_id: CharacterId, + }, +} + +/// New-type so we can implement some `From` traits. This will help when +/// registering the packet handlers. +#[derive(Default)] +pub(crate) struct NetworkEventList(pub Vec); + +pub(crate) struct NoNetworkEvents; + +impl From for NetworkEventList { + fn from(event: NetworkEvent) -> Self { + Self(vec![event]) + } +} + +impl From> for NetworkEventList { + fn from(events: Vec) -> Self { + Self(events) + } +} + +impl From> for NetworkEventList { + fn from(event: Option) -> Self { + match event { + Some(event) => Self(vec![event]), + None => Self(Vec::new()), + } + } +} + +impl From<(NetworkEvent, NetworkEvent)> for NetworkEventList { + fn from(events: (NetworkEvent, NetworkEvent)) -> Self { + Self(vec![events.0, events.1]) + } +} + +impl From for NetworkEventList { + fn from(_: NoNetworkEvents) -> Self { + Self(Vec::new()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DisconnectReason { + ClosedByClient, + ConnectionError, +} + +pub(crate) trait DisconnectedEvent { + fn create_event(reason: DisconnectReason) -> NetworkEvent; +} + +pub(crate) struct LoginServerDisconnectedEvent; +pub(crate) struct CharacterServerDisconnectedEvent; +pub(crate) struct MapServerDisconnectedEvent; + +impl DisconnectedEvent for LoginServerDisconnectedEvent { + fn create_event(reason: DisconnectReason) -> NetworkEvent { + NetworkEvent::LoginServerDisconnected { reason } + } +} + +impl DisconnectedEvent for CharacterServerDisconnectedEvent { + fn create_event(reason: DisconnectReason) -> NetworkEvent { + NetworkEvent::CharacterServerDisconnected { reason } + } +} + +impl DisconnectedEvent for MapServerDisconnectedEvent { + fn create_event(reason: DisconnectReason) -> NetworkEvent { + NetworkEvent::MapServerDisconnected { reason } + } +} diff --git a/korangar_networking/src/inventory.rs b/korangar_networking/src/inventory.rs new file mode 100644 index 00000000..238860c1 --- /dev/null +++ b/korangar_networking/src/inventory.rs @@ -0,0 +1,9 @@ +use ragnarok_packets::{EquipPosition, ItemId, ItemIndex}; + +#[derive(Debug, Clone)] +pub struct InventoryItem { + pub index: ItemIndex, + pub id: ItemId, + pub equip_position: EquipPosition, + pub equipped_position: EquipPosition, +} diff --git a/korangar_networking/src/lib.rs b/korangar_networking/src/lib.rs new file mode 100644 index 00000000..2972da7a --- /dev/null +++ b/korangar_networking/src/lib.rs @@ -0,0 +1,1039 @@ +mod entity; +mod event; +mod inventory; +mod message; +mod server; + +use std::cell::RefCell; +use std::net::{IpAddr, SocketAddr}; +use std::rc::Rc; +use std::time::Duration; + +use event::{ + CharacterServerDisconnectedEvent, DisconnectedEvent, LoginServerDisconnectedEvent, MapServerDisconnectedEvent, NetworkEventList, + NoNetworkEvents, +}; +use ragnarok_bytes::{ByteStream, FromBytes}; +use ragnarok_packets::handler::{DuplicateHandlerError, HandlerResult, NoPacketCallback, PacketCallback, PacketHandler}; +use ragnarok_packets::*; +use server::{ServerConnectCommand, ServerConnection}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; +use tokio::sync::mpsc::error::TryRecvError; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use tokio::task::JoinHandle; + +pub use self::entity::EntityData; +pub use self::event::{DisconnectReason, NetworkEvent}; +pub use self::inventory::InventoryItem; +pub use self::message::MessageColor; +pub use self::server::{ + CharacterServerLoginData, LoginServerLoginData, NotConnectedError, UnifiedCharacterSelectionFailedReason, UnifiedLoginFailedReason, +}; +use crate::server::NetworkTaskError; + +pub struct NetworkingSystem { + command_sender: UnboundedSender, + login_server_connection: ServerConnection, + character_server_connection: ServerConnection, + map_server_connection: ServerConnection, + packet_callback: Callback, +} + +impl NetworkingSystem { + pub fn spawn() -> Self { + let command_sender = Self::spawn_networking_thread(NoPacketCallback); + + Self::inner_new(command_sender, NoPacketCallback) + } +} + +impl NetworkingSystem +where + Callback: PacketCallback, +{ + fn inner_new(command_sender: UnboundedSender, packet_callback: Callback) -> Self { + Self { + command_sender, + login_server_connection: ServerConnection::Disconnected, + character_server_connection: ServerConnection::Disconnected, + map_server_connection: ServerConnection::Disconnected, + packet_callback, + } + } + + pub fn spawn_with_callback(packet_callback: Callback) -> Self { + let command_sender = Self::spawn_networking_thread(packet_callback.clone()); + + Self::inner_new(command_sender, packet_callback) + } + + fn spawn_networking_thread(packet_callback: Callback) -> UnboundedSender { + let (command_sender, mut command_receiver) = tokio::sync::mpsc::unbounded_channel::(); + + std::thread::spawn(move || { + let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + + let _guard = runtime.enter(); + let local_set = tokio::task::LocalSet::new(); + + let mut login_server_task_handle: Option>> = None; + let mut character_server_task_handle: Option>> = None; + let mut map_server_task_handle: Option>> = None; + + local_set.block_on(&runtime, async { + while let Some(command) = command_receiver.recv().await { + match command { + ServerConnectCommand::Login { + address, + action_receiver, + event_sender, + } => { + if let Some(handle) = login_server_task_handle.take() { + // TODO: Maybe add a timeout here? Maybe handle Result? + let _ = handle.await.unwrap(); + } + + let packet_handler = Self::create_login_server_packet_handler(packet_callback.clone()).unwrap(); + let handle = local_set.spawn_local(Self::handle_server_thing( + address, + action_receiver, + event_sender, + packet_handler, + LoginServerKeepalivePacket::default, + Duration::from_secs(58), + false, + )); + + login_server_task_handle = Some(handle); + } + ServerConnectCommand::Character { + address, + action_receiver, + event_sender, + } => { + if let Some(handle) = character_server_task_handle.take() { + // TODO: Maybe add a timeout here? Maybe handle Result? + let _ = handle.await.unwrap(); + } + + let packet_handler = Self::create_character_server_packet_handler(packet_callback.clone()).unwrap(); + let handle = local_set.spawn_local(Self::handle_server_thing( + address, + action_receiver, + event_sender, + packet_handler, + CharacterServerKeepalivePacket::new, + Duration::from_secs(10), + true, + )); + + character_server_task_handle = Some(handle); + } + ServerConnectCommand::Map { + address, + action_receiver, + event_sender, + } => { + if let Some(handle) = map_server_task_handle.take() { + // TODO: Maybe add a timeout here? Maybe handle Result? + let _ = handle.await.unwrap(); + } + + let packet_handler = Self::create_map_server_packet_handler(packet_callback.clone()).unwrap(); + let handle = local_set.spawn_local(Self::handle_server_thing( + address, + action_receiver, + event_sender, + packet_handler, + // Always passing 100 seems to work fine for now, but it might cause + // issues when connecting to something other than rAthena. + || RequestServerTickPacket::new(ClientTick(100)), + Duration::from_secs(4), + false, + )); + + map_server_task_handle = Some(handle); + } + } + } + }); + }); + + command_sender + } + + fn handle_connection(connection: &mut ServerConnection, events: &mut Vec) + where + Event: DisconnectedEvent, + { + match connection.take() { + ServerConnection::Connected { + action_sender, + mut event_receiver, + } => loop { + match event_receiver.try_recv() { + Ok(login_event) => { + events.push(login_event); + } + Err(TryRecvError::Empty) => { + *connection = ServerConnection::Connected { + action_sender, + event_receiver, + }; + break; + } + Err(..) => { + events.push(Event::create_event(DisconnectReason::ConnectionError)); + *connection = ServerConnection::Disconnected; + break; + } + } + }, + ServerConnection::ClosingManually => { + events.push(Event::create_event(DisconnectReason::ClosedByClient)); + *connection = ServerConnection::Disconnected; + } + _ => (), + }; + } + + pub fn get_events(&mut self) -> Vec { + let mut events = Vec::new(); + + Self::handle_connection::(&mut self.login_server_connection, &mut events); + Self::handle_connection::(&mut self.character_server_connection, &mut events); + Self::handle_connection::(&mut self.map_server_connection, &mut events); + + events + } + + async fn handle_server_thing( + address: SocketAddr, + mut action_receiver: UnboundedReceiver>, + event_sender: UnboundedSender, + mut packet_handler: PacketHandler, + ping_factory: impl Fn() -> Ping, + ping_frequency: Duration, + // After logging in to the character server, it sends the account id without any packet. + // Since our packet handler has no way of working with this, we need to add some special + // logic. + mut read_account_id: bool, + ) -> Result<(), NetworkTaskError> + where + Ping: OutgoingPacket, + Callback: PacketCallback, + { + let mut stream = TcpStream::connect(address).await.map_err(|_| NetworkTaskError::FailedToConnect)?; + let mut interval = tokio::time::interval(ping_frequency); + let mut buffer = [0u8; 8192]; + let mut cut_off_buffer_base = 0; + + loop { + tokio::select! { + // Send a packet to the server. + action = action_receiver.recv() => { + let Some(action) = action else { + // Channel was closed by the main thread. + break Ok(()); + }; + + stream.write_all(&action).await.map_err(|_| NetworkTaskError::ConnectionClosed)?; + } + // Receive some packets from the server. + received_bytes = stream.read(&mut buffer[cut_off_buffer_base..]) => { + let Ok(received_bytes) = received_bytes else { + // Channel was closed by the main thread. + break Err(NetworkTaskError::ConnectionClosed); + }; + + if received_bytes == 0 { + // Receiving Ok(0) means the stream was closed by the server, most + // likely because the client sent an incorrect packet. + break Err(NetworkTaskError::ConnectionClosed); + } + + let data = &buffer[..cut_off_buffer_base + received_bytes]; + let mut byte_stream = ByteStream::without_metadata(data); + let mut events = Vec::new(); + + if read_account_id { + let account_id = AccountId::from_bytes(&mut byte_stream).unwrap(); + events.push(NetworkEvent::AccountId(account_id)); + read_account_id = false; + } + + while !byte_stream.is_empty() { + match packet_handler.process_one(&mut byte_stream) { + HandlerResult::Ok(packet_events) => events.extend(packet_events.0.into_iter()), + HandlerResult::PacketCutOff => { + let packet_start = byte_stream.get_offset(); + let packet_end = cut_off_buffer_base + received_bytes; + + if packet_start == 0 { + // If the packet_start is 0, that means the packet is allegidly bigger than the MTU of a TCP packet. + // We limit the size of a packet to the MTU, to avoid getting stuck on packets that are parsed incorrectly. + // TODO: Call the packet callback? + cut_off_buffer_base = 0; + break; + } + + buffer.copy_within(packet_start..packet_end, 0); + cut_off_buffer_base = packet_end - packet_start; + + break; + }, + // The packet callback can take care of handling these properly. + HandlerResult::UnhandledPacket => break, + HandlerResult::InternalError(..) => break, + } + } + + for event in events { + event_sender.send(event).map_err(|_| NetworkTaskError::ConnectionClosed)?; + } + } + // Send a keep-alive packet to the server. + _ = interval.tick() => { + let packet_bytes = ping_factory().packet_to_bytes().unwrap(); + stream.write_all(&packet_bytes).await.map_err(|_| NetworkTaskError::ConnectionClosed)?; + } + } + } + } + + pub fn connect_to_login_server(&mut self, address: SocketAddr, username: impl Into, password: impl Into) { + let (action_sender, action_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (event_sender, event_receiver) = tokio::sync::mpsc::unbounded_channel(); + + self.command_sender + .send(ServerConnectCommand::Login { + address, + action_receiver, + event_sender, + }) + .expect("network thread dropped"); + + let login_packet = LoginServerLoginPacket::new(username.into(), password.into()); + + self.packet_callback.outgoing_packet(&login_packet); + + action_sender + .send(login_packet.packet_to_bytes().unwrap()) + .expect("action receiver instantly dropped"); + + self.login_server_connection = ServerConnection::Connected { + action_sender, + event_receiver, + }; + } + + pub fn connect_to_character_server(&mut self, login_data: &LoginServerLoginData, server: CharacterServerInformation) { + let (action_sender, action_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (event_sender, event_receiver) = tokio::sync::mpsc::unbounded_channel(); + + let address = SocketAddr::new(IpAddr::V4(server.server_ip.into()), server.server_port); + + self.command_sender + .send(ServerConnectCommand::Character { + address, + action_receiver, + event_sender, + }) + .expect("network thread dropped"); + + let login_packet = CharacterServerLoginPacket::new( + login_data.account_id, + login_data.login_id1, + login_data.login_id2, + login_data.sex, + ); + + self.packet_callback.outgoing_packet(&login_packet); + + action_sender + .send(login_packet.packet_to_bytes().unwrap()) + .expect("action receiver instantly dropped"); + + self.character_server_connection = ServerConnection::Connected { + action_sender, + event_receiver, + }; + } + + pub fn connect_to_map_server( + &mut self, + login_server_login_data: &LoginServerLoginData, + character_server_login_data: CharacterServerLoginData, + ) { + let (action_sender, action_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (event_sender, event_receiver) = tokio::sync::mpsc::unbounded_channel(); + + let address = SocketAddr::new(character_server_login_data.server_ip, character_server_login_data.server_port); + + self.command_sender + .send(ServerConnectCommand::Map { + address, + action_receiver, + event_sender, + }) + .expect("network thread dropped"); + + let login_packet = MapServerLoginPacket::new( + login_server_login_data.account_id, + character_server_login_data.character_id, + login_server_login_data.login_id1, + // Always passing 100 seems to work fine for now, but it might cause + // issues when connecting to something other than rAthena. + ClientTick(100), + login_server_login_data.sex, + ); + + self.packet_callback.outgoing_packet(&login_packet); + + action_sender + .send(login_packet.packet_to_bytes().unwrap()) + .expect("action receiver instantly dropped"); + + self.map_server_connection = ServerConnection::Connected { + action_sender, + event_receiver, + }; + } + + pub fn disconnect_from_login_server(&mut self) { + self.login_server_connection = ServerConnection::ClosingManually; + } + + pub fn disconnect_from_character_server(&mut self) { + self.character_server_connection = ServerConnection::ClosingManually; + } + + pub fn disconnect_from_map_server(&mut self) { + self.map_server_connection = ServerConnection::ClosingManually; + } + + pub fn send_login_server_packet(&mut self, packet: &Packet) -> Result<(), NotConnectedError> + where + Packet: OutgoingPacket + LoginServerPacket, + { + match &mut self.login_server_connection { + ServerConnection::Connected { action_sender, .. } => { + self.packet_callback.outgoing_packet(packet); + + // FIX: Don't unwrap. + action_sender.send(packet.packet_to_bytes().unwrap()).map_err(|_| NotConnectedError) + } + _ => Err(NotConnectedError), + } + } + + pub fn send_character_server_packet(&mut self, packet: &Packet) -> Result<(), NotConnectedError> + where + Packet: OutgoingPacket + CharacterServerPacket, + { + match &mut self.character_server_connection { + ServerConnection::Connected { action_sender, .. } => { + self.packet_callback.outgoing_packet(packet); + + // FIX: Don't unwrap. + action_sender.send(packet.packet_to_bytes().unwrap()).map_err(|_| NotConnectedError) + } + _ => Err(NotConnectedError), + } + } + + pub fn send_map_server_packet(&mut self, packet: &Packet) -> Result<(), NotConnectedError> + where + Packet: OutgoingPacket + MapServerPacket, + { + match &mut self.map_server_connection { + ServerConnection::Connected { action_sender, .. } => { + self.packet_callback.outgoing_packet(packet); + + // FIX: Don't unwrap. + action_sender.send(packet.packet_to_bytes().unwrap()).map_err(|_| NotConnectedError) + } + _ => Err(NotConnectedError), + } + } + + fn create_login_server_packet_handler( + packet_callback: Callback, + ) -> Result, DuplicateHandlerError> { + let mut packet_handler = PacketHandler::::with_callback(packet_callback); + + packet_handler.register(|packet: LoginServerLoginSuccessPacket| NetworkEvent::LoginServerConnected { + character_servers: packet.character_server_information, + login_data: LoginServerLoginData { + account_id: packet.account_id, + login_id1: packet.login_id1, + login_id2: packet.login_id2, + sex: packet.sex, + }, + })?; + packet_handler.register(|packet: LoginFailedPacket| { + let (reason, message) = match packet.reason { + LoginFailedReason::ServerClosed => (UnifiedLoginFailedReason::ServerClosed, "Server closed"), + LoginFailedReason::AlreadyLoggedIn => ( + UnifiedLoginFailedReason::AlreadyLoggedIn, + "Someone has already logged in with this id", + ), + LoginFailedReason::AlreadyOnline => (UnifiedLoginFailedReason::AlreadyOnline, "Already online"), + }; + + NetworkEvent::LoginServerConnectionFailed { reason, message } + })?; + packet_handler.register(|packet: LoginFailedPacket2| { + let (reason, message) = match packet.reason { + LoginFailedReason2::UnregisteredId => (UnifiedLoginFailedReason::UnregisteredId, "Unregistered id"), + LoginFailedReason2::IncorrectPassword => (UnifiedLoginFailedReason::IncorrectPassword, "Incorrect password"), + LoginFailedReason2::IdExpired => (UnifiedLoginFailedReason::IdExpired, "Id has expired"), + LoginFailedReason2::RejectedFromServer => (UnifiedLoginFailedReason::RejectedFromServer, "Rejected from server"), + LoginFailedReason2::BlockedByGMTeam => (UnifiedLoginFailedReason::BlockedByGMTeam, "Blocked by gm team"), + LoginFailedReason2::GameOutdated => (UnifiedLoginFailedReason::GameOutdated, "Game outdated"), + LoginFailedReason2::LoginProhibitedUntil => (UnifiedLoginFailedReason::LoginProhibitedUntil, "Login prohibited until"), + LoginFailedReason2::ServerFull => (UnifiedLoginFailedReason::ServerFull, "Server is full"), + LoginFailedReason2::CompanyAccountLimitReached => ( + UnifiedLoginFailedReason::CompanyAccountLimitReached, + "Company account limit reached", + ), + }; + + NetworkEvent::LoginServerConnectionFailed { reason, message } + })?; + + Ok(packet_handler) + } + + fn create_character_server_packet_handler( + packet_callback: Callback, + ) -> Result, DuplicateHandlerError> { + let mut packet_handler = PacketHandler::::with_callback(packet_callback); + + packet_handler.register(|packet: LoginFailedPacket| { + let reason = packet.reason; + let message = match reason { + LoginFailedReason::ServerClosed => "Server closed", + LoginFailedReason::AlreadyLoggedIn => "Someone has already logged in with this id", + LoginFailedReason::AlreadyOnline => "Already online", + }; + + NetworkEvent::CharacterServerConnectionFailed { reason, message } + })?; + packet_handler.register( + |packet: CharacterServerLoginSuccessPacket| NetworkEvent::CharacterServerConnected { + normal_slot_count: packet.normal_slot_count as usize, + }, + )?; + packet_handler.register(|packet: RequestCharacterListSuccessPacket| NetworkEvent::CharacterList { + characters: packet.character_information, + })?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register(|packet: CharacterSelectionSuccessPacket| { + let login_data = CharacterServerLoginData { + server_ip: IpAddr::V4(packet.map_server_ip.into()), + server_port: packet.map_server_port, + character_id: packet.character_id, + }; + let map_name = packet.map_name.strip_suffix(".gat").unwrap().to_owned(); + + NetworkEvent::CharacterSelected { login_data, map_name } + })?; + packet_handler.register(|packet: CharacterSelectionFailedPacket| { + let (reason, message) = match packet.reason { + CharacterSelectionFailedReason::RejectedFromServer => ( + UnifiedCharacterSelectionFailedReason::RejectedFromServer, + "Rejected from server", + ), + }; + + NetworkEvent::CharacterSelectionFailed { reason, message } + })?; + packet_handler.register(|_: MapServerUnavailablePacket| { + let reason = UnifiedCharacterSelectionFailedReason::MapServerUnavailable; + let message = "Map server currently unavailable"; + + NetworkEvent::CharacterSelectionFailed { reason, message } + })?; + packet_handler.register(|packet: CreateCharacterSuccessPacket| NetworkEvent::CharacterCreated { + character_information: packet.character_information, + })?; + packet_handler.register(|packet: CharacterCreationFailedPacket| { + let reason = packet.reason; + let message = match reason { + CharacterCreationFailedReason::CharacterNameAlreadyUsed => "Character name is already used", + CharacterCreationFailedReason::NotOldEnough => "You are not old enough to create a character", + CharacterCreationFailedReason::NotAllowedToUseSlot => "You are not allowed to use this character slot", + CharacterCreationFailedReason::CharacterCerationFailed => "Character creation failed", + }; + + NetworkEvent::CharacterCreationFailed { reason, message } + })?; + packet_handler.register(|_: CharacterDeletionSuccessPacket| NetworkEvent::CharacterDeleted)?; + packet_handler.register(|packet: CharacterDeletionFailedPacket| { + let reason = packet.reason; + let message = match reason { + CharacterDeletionFailedReason::NotAllowed => "You are not allowed to delete this character", + CharacterDeletionFailedReason::CharacterNotFound => "Character was not found", + CharacterDeletionFailedReason::NotEligible => "Character is not eligible for deletion", + }; + NetworkEvent::CharacterDeletionFailed { reason, message } + })?; + packet_handler.register(|packet: SwitchCharacterSlotResponsePacket| match packet.status { + SwitchCharacterSlotResponseStatus::Success => NetworkEvent::CharacterSlotSwitched, + SwitchCharacterSlotResponseStatus::Error => NetworkEvent::CharacterSlotSwitchFailed, + })?; + + Ok(packet_handler) + } + + fn create_map_server_packet_handler( + packet_callback: Callback, + ) -> Result, DuplicateHandlerError> { + let mut packet_handler = PacketHandler::::with_callback(packet_callback); + + // This is a bit of a workaround for the way that the inventory is + // sent. There is a single packet to start the inventory list, + // followed by an arbitary number of item packets, and in the + // end a sinle packet to mark the list as complete. + // + // This variable provides some transient storage shared by all the inventory + // handlers. + let inventory_items: Rc>>> = Rc::new(RefCell::new(None)); + + packet_handler.register(|_: MapServerPingPacket| NoNetworkEvents)?; + packet_handler.register(|packet: BroadcastMessagePacket| NetworkEvent::ChatMessage { + text: packet.message, + color: MessageColor::Broadcast, + })?; + packet_handler.register(|packet: Broadcast2MessagePacket| { + // Drop the alpha channel because it might be 0. + let color = MessageColor::Rgb { + red: packet.font_color.red, + green: packet.font_color.green, + blue: packet.font_color.blue, + }; + NetworkEvent::ChatMessage { + text: packet.message, + color, + } + })?; + packet_handler.register(|packet: OverheadMessagePacket| { + // FIX: This should be a different event. + NetworkEvent::ChatMessage { + text: packet.message, + color: MessageColor::Broadcast, + } + })?; + packet_handler.register(|packet: ServerMessagePacket| NetworkEvent::ChatMessage { + text: packet.message, + color: MessageColor::Server, + })?; + packet_handler.register(|packet: EntityMessagePacket| { + // Drop the alpha channel because it might be 0. + let color = MessageColor::Rgb { + red: packet.color.red, + green: packet.color.green, + blue: packet.color.blue, + }; + NetworkEvent::ChatMessage { + text: packet.message, + color, + } + })?; + packet_handler.register_noop::()?; + packet_handler.register(|packet: EntityMovePacket| { + let (origin, destination) = packet.from_to.to_origin_destination(); + NetworkEvent::EntityMove(packet.entity_id, origin, destination, packet.timestamp) + })?; + packet_handler.register_noop::()?; + packet_handler.register(|packet: PlayerMovePacket| { + let (origin, destination) = packet.from_to.to_origin_destination(); + NetworkEvent::PlayerMove(origin, destination, packet.timestamp) + })?; + packet_handler.register(|packet: ChangeMapPacket| NetworkEvent::ChangeMap(packet.map_name.replace(".gat", ""), packet.position))?; + packet_handler.register(|packet: EntityAppearedPacket| NetworkEvent::AddEntity(packet.into()))?; + packet_handler.register(|packet: EntityAppeared2Packet| NetworkEvent::AddEntity(packet.into()))?; + packet_handler.register(|packet: MovingEntityAppearedPacket| NetworkEvent::AddEntity(packet.into()))?; + packet_handler.register(|packet: EntityDisappearedPacket| NetworkEvent::RemoveEntity(packet.entity_id))?; + packet_handler.register(|packet: UpdateStatusPacket| NetworkEvent::UpdateStatus(packet.status_type))?; + packet_handler.register(|packet: UpdateStatusPacket1| NetworkEvent::UpdateStatus(packet.status_type))?; + packet_handler.register(|packet: UpdateStatusPacket2| NetworkEvent::UpdateStatus(packet.status_type))?; + packet_handler.register(|packet: UpdateStatusPacket3| NetworkEvent::UpdateStatus(packet.status_type))?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register(|packet: SpriteChangePacket| { + (packet.sprite_type == 0).then_some(NetworkEvent::ChangeJob(packet.account_id, packet.value)) + })?; + packet_handler.register({ + let inventory_items = inventory_items.clone(); + + move |_: InventoyStartPacket| { + *inventory_items.borrow_mut() = Some(Vec::new()); + NoNetworkEvents + } + })?; + packet_handler.register({ + let inventory_items = inventory_items.clone(); + + move |packet: RegularItemListPacket| { + inventory_items.borrow_mut().as_mut().expect("Unexpected inventory packet").extend( + packet.item_information.into_iter().map(|item| InventoryItem { + index: item.index, + id: item.item_id, + equip_position: EquipPosition::None, + equipped_position: EquipPosition::None, + }), + ); + NoNetworkEvents + } + })?; + packet_handler.register({ + let inventory_items = inventory_items.clone(); + + move |packet: EquippableItemListPacket| { + inventory_items.borrow_mut().as_mut().expect("Unexpected inventory packet").extend( + packet.item_information.into_iter().map(|item| InventoryItem { + index: item.index, + id: item.item_id, + equip_position: item.equip_position, + equipped_position: item.equipped_position, + }), + ); + NoNetworkEvents + } + })?; + packet_handler.register({ + let inventory_items = inventory_items.clone(); + + move |_: InventoyEndPacket| { + let items = inventory_items.borrow_mut().take().expect("Unexpected inventory end packet"); + NetworkEvent::SetInventory { items } + } + })?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register(|packet: UpdateSkillTreePacket| NetworkEvent::SkillTree(packet.skill_information))?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register(|_: NextButtonPacket| NetworkEvent::AddNextButton)?; + packet_handler.register(|_: CloseButtonPacket| NetworkEvent::AddCloseButton)?; + packet_handler.register(|packet: DialogMenuPacket| { + let choices = packet + .message + .split(':') + .map(String::from) + .filter(|text| !text.is_empty()) + .collect(); + + NetworkEvent::AddChoiceButtons(choices) + })?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register(|packet: DisplaySkillEffectNoDamagePacket| { + NetworkEvent::HealEffect(packet.destination_entity_id, packet.heal_amount as usize) + })?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register(|packet: VisualEffectPacket| { + let path = match packet.effect { + VisualEffect::BaseLevelUp => "angel.str", + VisualEffect::JobLevelUp => "joblvup.str", + VisualEffect::RefineFailure => "bs_refinefailed.str", + VisualEffect::RefineSuccess => "bs_refinesuccess.str", + VisualEffect::GameOver => "help_angel\\help_angel\\help_angel.str", + VisualEffect::PharmacySuccess => "p_success.str", + VisualEffect::PharmacyFailure => "p_failed.str", + VisualEffect::BaseLevelUpSuperNovice => "help_angel\\help_angel\\help_angel.str", + VisualEffect::JobLevelUpSuperNovice => "help_angel\\help_angel\\help_angel.str", + VisualEffect::BaseLevelUpTaekwon => "help_angel\\help_angel\\help_angel.str", + }; + + NetworkEvent::VisualEffect(path, packet.entity_id) + })?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + + packet_handler.register(|packet: QuestEffectPacket| match packet.effect { + QuestEffect::None => NetworkEvent::RemoveQuestEffect(packet.entity_id), + _ => NetworkEvent::AddQuestEffect(packet), + })?; + packet_handler.register(|packet: ItemPickupPacket| { + NetworkEvent::AddIventoryItem(packet.index, packet.item_id, packet.equip_position, EquipPosition::None) + })?; + packet_handler.register_noop::()?; + packet_handler.register(|packet: ServerTickPacket| NetworkEvent::UpdateClientTick(packet.client_tick))?; + packet_handler.register(|packet: RequestPlayerDetailsSuccessPacket| { + NetworkEvent::UpdateEntityDetails(EntityId(packet.character_id.0), packet.name) + })?; + packet_handler + .register(|packet: RequestEntityDetailsSuccessPacket| NetworkEvent::UpdateEntityDetails(packet.entity_id, packet.name))?; + packet_handler.register(|packet: UpdateEntityHealthPointsPacket| { + NetworkEvent::UpdateEntityHealth( + packet.entity_id, + packet.health_points as usize, + packet.maximum_health_points as usize, + ) + })?; + packet_handler.register_noop::()?; + packet_handler + .register(|packet: DamagePacket| NetworkEvent::DamageEffect(packet.destination_entity_id, packet.damage_amount as usize))?; + packet_handler.register(|packet: NpcDialogPacket| NetworkEvent::OpenDialog(packet.text, packet.npc_id))?; + packet_handler.register(|packet: RequestEquipItemStatusPacket| match packet.result { + RequestEquipItemStatus::Success => Some(NetworkEvent::UpdateEquippedPosition { + index: packet.inventory_index, + equipped_position: packet.equipped_position, + }), + _ => None, + })?; + packet_handler.register(|packet: RequestUnequipItemStatusPacket| match packet.result { + RequestUnequipItemStatus::Success => Some(NetworkEvent::UpdateEquippedPosition { + index: packet.inventory_index, + equipped_position: EquipPosition::None, + }), + _ => None, + })?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register(|packet: MapServerLoginSuccessPacket| { + ( + NetworkEvent::UpdateClientTick(packet.client_tick), + NetworkEvent::SetPlayerPosition(packet.position), + ) + })?; + packet_handler.register(|packet: RestartResponsePacket| match packet.result { + RestartResponseStatus::Ok => NetworkEvent::LoggedOut, + RestartResponseStatus::Nothing => NetworkEvent::ChatMessage { + text: "Failed to log out.".to_string(), + color: MessageColor::Error, + }, + })?; + packet_handler.register(|packet: DisconnectResponsePacket| match packet.result { + DisconnectResponseStatus::Ok => NetworkEvent::LoggedOut, + DisconnectResponseStatus::Wait10Seconds => NetworkEvent::ChatMessage { + text: "Please wait 10 seconds before trying to log out.".to_string(), + color: MessageColor::Error, + }, + })?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler + .register(|packet: NotifySkillUnitPacket| NetworkEvent::AddSkillUnit(packet.entity_id, packet.unit_id, packet.position))?; + packet_handler.register(|packet: SkillUnitDisappearPacket| NetworkEvent::RemoveSkillUnit(packet.entity_id))?; + packet_handler.register_noop::()?; + packet_handler.register(|packet: FriendListPacket| NetworkEvent::SetFriendList { friends: packet.friends })?; + packet_handler.register_noop::()?; + packet_handler.register(|packet: FriendRequestPacket| NetworkEvent::FriendRequest { + requestee: packet.requestee, + })?; + packet_handler.register(|packet: FriendRequestResultPacket| { + let text = match packet.result { + FriendRequestResult::Accepted => format!("You have become friends with {}.", packet.friend.name), + FriendRequestResult::Rejected => format!("{} does not want to be friends with you.", packet.friend.name), + FriendRequestResult::OwnFriendListFull => "Your Friend List is full.".to_owned(), + FriendRequestResult::OtherFriendListFull => format!("{}'s Friend List is full.", packet.friend.name), + }; + + let mut events = vec![NetworkEvent::ChatMessage { + text, + color: MessageColor::Information, + }]; + + if matches!(packet.result, FriendRequestResult::Accepted) { + events.push(NetworkEvent::FriendAdded { friend: packet.friend }); + } + + events + })?; + packet_handler.register(|packet: NotifyFriendRemovedPacket| NetworkEvent::FriendRemoved { + account_id: packet.account_id, + character_id: packet.character_id, + })?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + packet_handler.register_noop::()?; + + Ok(packet_handler) + } + + pub fn request_character_list(&mut self) -> Result<(), NotConnectedError> { + self.send_character_server_packet(&RequestCharacterListPacket::default()) + } + + pub fn select_character(&mut self, character_slot: usize) -> Result<(), NotConnectedError> { + self.send_character_server_packet(&SelectCharacterPacket::new(character_slot as u8)) + } + + pub fn map_loaded(&mut self) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&MapLoadedPacket::default()) + } + + pub fn log_out(&mut self) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&RestartPacket::new(RestartType::Disconnect)) + } + + pub fn player_move(&mut self, position: WorldPosition) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&RequestPlayerMovePacket::new(position)) + } + + pub fn warp_to_map(&mut self, map_name: String, position: TilePosition) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&RequestWarpToMapPacket::new(map_name, position)) + } + + pub fn entity_details(&mut self, entity_id: EntityId) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&RequestDetailsPacket::new(entity_id)) + } + + pub fn player_attack(&mut self, entity_id: EntityId) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&RequestActionPacket::new(entity_id, Action::Attack)) + } + + pub fn send_chat_message(&mut self, player_name: &str, message: &str) -> Result<(), NotConnectedError> { + let complete_message = format!("{} : {}", player_name, message); + + self.send_map_server_packet(&GlobalMessagePacket::new( + complete_message.bytes().len() as u16 + 5, + complete_message, + )) + } + + pub fn start_dialog(&mut self, npc_id: EntityId) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&StartDialogPacket::new(npc_id)) + } + + pub fn next_dialog(&mut self, npc_id: EntityId) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&NextDialogPacket::new(npc_id)) + } + + pub fn close_dialog(&mut self, npc_id: EntityId) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&CloseDialogPacket::new(npc_id)) + } + + pub fn choose_dialog_option(&mut self, npc_id: EntityId, option: i8) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&ChooseDialogOptionPacket::new(npc_id, option)) + } + + pub fn request_item_equip(&mut self, item_index: ItemIndex, equip_position: EquipPosition) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&RequestEquipItemPacket::new(item_index, equip_position)) + } + + pub fn request_item_unequip(&mut self, item_index: ItemIndex) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&RequestUnequipItemPacket::new(item_index)) + } + + pub fn cast_skill(&mut self, skill_id: SkillId, skill_level: SkillLevel, entity_id: EntityId) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&UseSkillAtIdPacket::new(skill_level, skill_id, entity_id)) + } + + pub fn cast_ground_skill( + &mut self, + skill_id: SkillId, + skill_level: SkillLevel, + target_position: TilePosition, + ) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&UseSkillOnGroundPacket::new(skill_level, skill_id, target_position)) + } + + pub fn cast_channeling_skill( + &mut self, + skill_id: SkillId, + skill_level: SkillLevel, + entity_id: EntityId, + ) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&StartUseSkillPacket::new(skill_id, skill_level, entity_id)) + } + + pub fn stop_channeling_skill(&mut self, skill_id: SkillId) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&EndUseSkillPacket::new(skill_id)) + } + + pub fn add_friend(&mut self, name: String) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&AddFriendPacket::new(name)) + } + + pub fn remove_friend(&mut self, account_id: AccountId, character_id: CharacterId) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&RemoveFriendPacket::new(account_id, character_id)) + } + + pub fn reject_friend_request(&mut self, account_id: AccountId, character_id: CharacterId) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&FriendRequestResponsePacket::new( + account_id, + character_id, + FriendRequestResponse::Reject, + )) + } + + pub fn accept_friend_request(&mut self, account_id: AccountId, character_id: CharacterId) -> Result<(), NotConnectedError> { + self.send_map_server_packet(&FriendRequestResponsePacket::new( + account_id, + character_id, + FriendRequestResponse::Accept, + )) + } + + pub fn create_character(&mut self, slot: usize, name: String) -> Result<(), NotConnectedError> { + let hair_color = 0; + let hair_style = 0; + let start_job = 0; + let sex = Sex::Male; + + self.send_character_server_packet(&CreateCharacterPacket::new( + name, slot as u8, hair_color, hair_style, start_job, sex, + )) + } + + pub fn delete_character(&mut self, character_id: CharacterId) -> Result<(), NotConnectedError> { + let email = "a@a.com".to_string(); + + self.send_character_server_packet(&DeleteCharacterPacket::new(character_id, email)) + } + + pub fn switch_character_slot(&mut self, origin_slot: usize, destination_slot: usize) -> Result<(), NotConnectedError> { + self.send_character_server_packet(&SwitchCharacterSlotPacket::new(origin_slot as u16, destination_slot as u16)) + } +} + +#[cfg(test)] +mod packet_handlers { + use ragnarok_packets::handler::NoPacketCallback; + + use crate::NetworkingSystem; + + #[test] + fn login_server() { + let result = NetworkingSystem::create_login_server_packet_handler(NoPacketCallback); + assert!(result.is_ok()); + } + + #[test] + fn character_server() { + let result = NetworkingSystem::create_character_server_packet_handler(NoPacketCallback); + assert!(result.is_ok()); + } + + #[test] + fn map_server() { + let result = NetworkingSystem::create_map_server_packet_handler(NoPacketCallback); + assert!(result.is_ok()); + } +} diff --git a/korangar_networking/src/message.rs b/korangar_networking/src/message.rs new file mode 100644 index 00000000..89b64c29 --- /dev/null +++ b/korangar_networking/src/message.rs @@ -0,0 +1,8 @@ +#[derive(Debug, Clone, Copy)] +pub enum MessageColor { + Rgb { red: u8, green: u8, blue: u8 }, + Broadcast, + Server, + Error, + Information, +} diff --git a/korangar_networking/src/server.rs b/korangar_networking/src/server.rs new file mode 100644 index 00000000..ba617ac6 --- /dev/null +++ b/korangar_networking/src/server.rs @@ -0,0 +1,85 @@ +use std::net::{IpAddr, SocketAddr}; + +use ragnarok_packets::{AccountId, CharacterId, Sex}; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; + +use crate::event::NetworkEvent; + +#[derive(Debug, Clone, Copy)] +pub struct LoginServerLoginData { + pub account_id: AccountId, + pub login_id1: u32, + pub login_id2: u32, + pub sex: Sex, +} + +#[derive(Debug, Clone, Copy)] +pub enum UnifiedLoginFailedReason { + ServerClosed, + AlreadyLoggedIn, + AlreadyOnline, + UnregisteredId, + IncorrectPassword, + IdExpired, + RejectedFromServer, + BlockedByGMTeam, + GameOutdated, + LoginProhibitedUntil, + ServerFull, + CompanyAccountLimitReached, +} + +#[derive(Debug, Clone, Copy)] +pub enum UnifiedCharacterSelectionFailedReason { + RejectedFromServer, + MapServerUnavailable, +} + +#[derive(Debug, Clone, Copy)] +pub struct CharacterServerLoginData { + pub server_ip: IpAddr, + pub server_port: u16, + pub character_id: CharacterId, +} + +pub(crate) enum ServerConnectCommand { + Login { + address: SocketAddr, + action_receiver: UnboundedReceiver>, + event_sender: UnboundedSender, + }, + Character { + address: SocketAddr, + action_receiver: UnboundedReceiver>, + event_sender: UnboundedSender, + }, + Map { + address: SocketAddr, + action_receiver: UnboundedReceiver>, + event_sender: UnboundedSender, + }, +} + +#[derive(Debug)] +pub(crate) enum NetworkTaskError { + FailedToConnect, + ConnectionClosed, +} + +#[derive(Debug)] +pub struct NotConnectedError; + +pub(crate) enum ServerConnection { + Connected { + action_sender: UnboundedSender>, + event_receiver: UnboundedReceiver, + }, + ClosingManually, + Disconnected, +} + +impl ServerConnection { + pub fn take(&mut self) -> Self { + std::mem::replace(self, ServerConnection::Disconnected) + } +} diff --git a/ragnarok_networking/Cargo.toml b/ragnarok_packets/Cargo.toml similarity index 82% rename from ragnarok_networking/Cargo.toml rename to ragnarok_packets/Cargo.toml index 8f613f09..1e61984e 100644 --- a/ragnarok_networking/Cargo.toml +++ b/ragnarok_packets/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ragnarok_networking" +name = "ragnarok_packets" version = "0.1.0" edition = "2021" @@ -13,3 +13,4 @@ korangar_interface = { workspace = true, optional = true } debug = [] derive = [] interface = ["korangar_interface"] +packet-to-prototype-element = ["interface"] diff --git a/ragnarok_networking/README.md b/ragnarok_packets/README.md similarity index 77% rename from ragnarok_networking/README.md rename to ragnarok_packets/README.md index e5107828..c1761a7f 100644 --- a/ragnarok_networking/README.md +++ b/ragnarok_packets/README.md @@ -1,3 +1,3 @@ -# Ragnarok Networking +# Ragnarok Packets A crate that exposes types for Ragnarok Online server-client communication. diff --git a/ragnarok_packets/src/handler.rs b/ragnarok_packets/src/handler.rs new file mode 100644 index 00000000..80ac5a64 --- /dev/null +++ b/ragnarok_packets/src/handler.rs @@ -0,0 +1,194 @@ +use std::collections::HashMap; + +use ragnarok_bytes::{ByteStream, ConversionError, ConversionResult, FromBytes}; + +use crate::{IncomingPacket, OutgoingPacket, PacketHeader}; + +/// Possible results of [`PacketHandler::process_one`]. +pub enum HandlerResult { + /// Packet was successfully processed and produced some output. + Ok(Output), + /// No packet handler was registered for the incoming packet. + UnhandledPacket, + /// Packet was most likely cut-off. + PacketCutOff, + /// An error occurred inside the packet handler. + InternalError(Box), +} + +/// Error when trying to register two separate handlers for the same packet. +#[derive(Debug, Clone)] +pub struct DuplicateHandlerError { + /// Header of the packet. + pub packet_header: PacketHeader, +} + +/// Trait for monitoring the incoming and outgoing packets. +pub trait PacketCallback: Clone + Send + 'static { + /// Called by the [`PacketHandler`] when a packet is received. + fn incoming_packet(&self, packet: &Packet) + where + Packet: IncomingPacket; + + /// Called by the [`NetworkingSystem`](super::NetworkingSystem) when a + /// packet is sent. + fn outgoing_packet(&self, packet: &Packet) + where + Packet: OutgoingPacket; + + /// Called by the [`PacketHandler`] when a packet arrives that doesn't have + /// a handler registered. + fn unknown_packet(&self, bytes: Vec); + + /// Called by the [`PacketHandler`] when a packet handler returned an error. + fn failed_packet(&self, bytes: Vec, error: Box); +} + +#[derive(Debug, Default, Clone)] +pub struct NoPacketCallback; + +impl PacketCallback for NoPacketCallback { + fn incoming_packet(&self, _packet: &Packet) + where + Packet: IncomingPacket, + { + } + + fn outgoing_packet(&self, _packet: &Packet) + where + Packet: OutgoingPacket, + { + } + + fn unknown_packet(&self, _bytes: Vec) {} + + fn failed_packet(&self, _bytes: Vec, _error: Box) {} +} + +pub type HandlerFunction = Box) -> ConversionResult>; + +/// A struct to help with reading packets from from a [`ByteStream`] and +/// converting them to some common event type. +/// +/// It allows passing a packet callback to monitor incoming packets. +pub struct PacketHandler +where + Meta: 'static, +{ + handlers: HashMap>, + packet_callback: Callback, +} + +impl Default for PacketHandler +where + Meta: 'static, + Callback: Default, +{ + fn default() -> Self { + Self { + handlers: Default::default(), + packet_callback: Default::default(), + } + } +} + +impl PacketHandler +where + Meta: Default + 'static, + Output: Default, + Callback: PacketCallback, +{ + /// Create a new packet handler with a callback. + pub fn with_callback(packet_callback: Callback) -> Self { + Self { + handlers: Default::default(), + packet_callback, + } + } + + /// Register a new packet handler. + pub fn register(&mut self, handler: impl Fn(Packet) -> Return + 'static) -> Result<(), DuplicateHandlerError> + where + Packet: IncomingPacket, + Return: Into, + { + let packet_callback = self.packet_callback.clone(); + let old_handler = self.handlers.insert( + Packet::HEADER, + Box::new(move |byte_stream| { + let packet = Packet::payload_from_bytes(byte_stream)?; + + packet_callback.incoming_packet(&packet); + + Ok(handler(packet).into()) + }), + ); + + match old_handler.is_some() { + true => Err(DuplicateHandlerError { + packet_header: Packet::HEADER, + }), + false => Ok(()), + } + } + + /// Register a noop packet handler. + pub fn register_noop(&mut self) -> Result<(), DuplicateHandlerError> + where + Packet: IncomingPacket, + { + let packet_callback = self.packet_callback.clone(); + let old_handler = self.handlers.insert( + Packet::HEADER, + Box::new(move |byte_stream| { + let packet = Packet::payload_from_bytes(byte_stream)?; + + packet_callback.incoming_packet(&packet); + + Ok(Output::default()) + }), + ); + + match old_handler.is_some() { + true => Err(DuplicateHandlerError { + packet_header: Packet::HEADER, + }), + false => Ok(()), + } + } + + /// Take a single packet from the byte stream. + pub fn process_one(&mut self, byte_stream: &mut ByteStream) -> HandlerResult { + let save_point = byte_stream.create_save_point(); + + let Ok(header) = PacketHeader::from_bytes(byte_stream) else { + // Packet is cut-off at the header. + byte_stream.restore_save_point(save_point); + return HandlerResult::PacketCutOff; + }; + + let Some(handler) = self.handlers.get(&header) else { + byte_stream.restore_save_point(save_point); + + self.packet_callback.unknown_packet(byte_stream.remaining_bytes()); + + return HandlerResult::UnhandledPacket; + }; + + match handler(byte_stream) { + Ok(output) => HandlerResult::Ok(output), + // Cut-off packet (probably). + Err(error) if error.is_byte_stream_too_short() => { + byte_stream.restore_save_point(save_point); + HandlerResult::PacketCutOff + } + Err(error) => { + byte_stream.restore_save_point(save_point); + + self.packet_callback.failed_packet(byte_stream.remaining_bytes(), error.clone()); + + HandlerResult::InternalError(error) + } + } + } +} diff --git a/ragnarok_networking/src/lib.rs b/ragnarok_packets/src/lib.rs similarity index 86% rename from ragnarok_networking/src/lib.rs rename to ragnarok_packets/src/lib.rs index 279ae9ff..46238b76 100644 --- a/ragnarok_networking/src/lib.rs +++ b/ragnarok_packets/src/lib.rs @@ -1,3 +1,5 @@ +pub mod handler; + use std::net::Ipv4Addr; use derive_new::new; @@ -5,12 +7,16 @@ use ragnarok_bytes::{ ByteConvertable, ByteStream, ConversionError, ConversionResult, ConversionResultExt, FixedByteSize, FromBytes, ToBytes, }; #[cfg(feature = "derive")] -pub use ragnarok_procedural::{IncomingPacket, OutgoingPacket}; +pub use ragnarok_procedural::{CharacterServer, IncomingPacket, LoginServer, MapServer, OutgoingPacket}; #[cfg(not(feature = "derive"))] -use ragnarok_procedural::{IncomingPacket, OutgoingPacket}; +use ragnarok_procedural::{CharacterServer, IncomingPacket, LoginServer, MapServer, OutgoingPacket}; // To make proc macros work in korangar_interface. -extern crate self as ragnarok_networking; +extern crate self as ragnarok_packets; + +/// The header of a Ragnarok Online packet. It is always two bytes long. +#[derive(Debug, Clone, Copy, PartialEq, Eq, ByteConvertable, PartialOrd, Ord, Hash)] +pub struct PacketHeader(pub u16); /// Base trait that all incoming packets implement. /// All packets in Ragnarok online consist of a header, two bytes in size, @@ -19,11 +25,16 @@ extern crate self as ragnarok_networking; /// header. Packets are sent in little endian. pub trait IncomingPacket: Clone { const IS_PING: bool; - const HEADER: u16; + const HEADER: PacketHeader; /// Read packet **without the header**. To read the packet with the header, /// use [`IncomingPacketExt::packet_from_bytes`]. fn payload_from_bytes(byte_stream: &mut ByteStream) -> ConversionResult; + + #[cfg(feature = "packet-to-prototype-element")] + fn to_prototype_element( + &self, + ) -> Box + Send>; } /// Base trait that all outgoing packets implement. @@ -35,6 +46,11 @@ pub trait OutgoingPacket: Clone { const IS_PING: bool; fn packet_to_bytes(&self) -> ConversionResult>; + + #[cfg(feature = "packet-to-prototype-element")] + fn to_prototype_element( + &self, + ) -> Box + Send>; } /// Extension trait for reading incoming packets with the header. @@ -49,7 +65,7 @@ where T: IncomingPacket, { fn packet_from_bytes(byte_stream: &mut ByteStream) -> ConversionResult { - let header = u16::from_bytes(byte_stream)?; + let header = PacketHeader::from_bytes(byte_stream)?; if header != Self::HEADER { return Err(ConversionError::from_message("mismatched header")); @@ -59,6 +75,15 @@ where } } +/// Marker trait for login server packets. +pub trait LoginServerPacket /* : Packet */ {} + +/// Marker trait for character server packets. +pub trait CharacterServerPacket /* : Packet */ {} + +/// Marker trait for map server packets. +pub trait MapServerPacket /* : Packet */ {} + #[derive(Clone, Copy, Debug, ByteConvertable, FixedByteSize)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct ClientTick(pub u32); @@ -113,7 +138,7 @@ pub struct LargeTilePosition { pub y: u32, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct ColorBGRA { pub blue: u8, @@ -122,7 +147,7 @@ pub struct ColorBGRA { pub alpha: u8, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct ColorRGBA { pub red: u8, @@ -152,7 +177,7 @@ impl ToBytes for ItemIndex { #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct ItemId(pub u32); -#[derive(Copy, Clone, Debug, ByteConvertable, FixedByteSize, PartialEq)] +#[derive(Copy, Debug, Clone, ByteConvertable, FixedByteSize, PartialEq)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum Sex { Female, @@ -164,7 +189,7 @@ pub enum Sex { /// Sent by the client to the login server. /// The very first packet sent when logging in, it is sent after the user has /// entered email and password. -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, LoginServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0064)] pub struct LoginServerLoginPacket { @@ -183,7 +208,7 @@ pub struct LoginServerLoginPacket { /// Sent by the login server as a response to [LoginServerLoginPacket] /// succeeding. After receiving this packet, the client will connect to one of /// the character servers provided by this packet. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, LoginServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0AC4)] pub struct LoginServerLoginSuccessPacket { @@ -207,7 +232,7 @@ pub struct LoginServerLoginSuccessPacket { /// Sent by the character server as a response to [CharacterServerLoginPacket] /// succeeding. Provides basic information about the number of available /// character slots. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x082D)] pub struct CharacterServerLoginSuccessPacket { @@ -221,10 +246,10 @@ pub struct CharacterServerLoginSuccessPacket { pub unused: [u8; 20], } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x006B)] -pub struct Packet6b00 { +pub struct Packet006b { pub unused: u16, pub maximum_slot_count: u8, pub available_slot_count: u8, @@ -232,15 +257,15 @@ pub struct Packet6b00 { pub unknown: [u8; 20], } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B18)] -pub struct Packet180b { +pub struct Packet0b18 { /// Possibly inventory related pub unknown: u16, } -#[derive(Clone, Debug, new)] +#[derive(Debug, Clone, Copy, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct WorldPosition { pub x: usize, @@ -274,7 +299,7 @@ impl ToBytes for WorldPosition { } } -#[derive(Clone, Debug, new)] +#[derive(Debug, Clone, Copy, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct WorldPosition2 { pub x1: usize, @@ -283,6 +308,15 @@ pub struct WorldPosition2 { pub y2: usize, } +impl WorldPosition2 { + pub fn to_origin_destination(self) -> (WorldPosition, WorldPosition) { + (WorldPosition { x: self.x1, y: self.y1 }, WorldPosition { + x: self.x2, + y: self.y2, + }) + } +} + impl FromBytes for WorldPosition2 { fn from_bytes(byte_stream: &mut ByteStream) -> ConversionResult { let coordinates: Vec = byte_stream.slice::(6)?.iter().map(|byte| *byte as usize).collect(); @@ -298,7 +332,7 @@ impl FromBytes for WorldPosition2 { } /// Sent by the map server as a response to [MapServerLoginPacket] succeeding. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x02EB)] pub struct MapServerLoginSuccessPacket { @@ -309,7 +343,7 @@ pub struct MapServerLoginSuccessPacket { pub font: u16, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum LoginFailedReason { #[numeric_value(1)] @@ -320,14 +354,14 @@ pub enum LoginFailedReason { AlreadyOnline, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, LoginServer, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0081)] pub struct LoginFailedPacket { pub reason: LoginFailedReason, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0840)] pub struct MapServerUnavailablePacket { @@ -336,7 +370,7 @@ pub struct MapServerUnavailablePacket { pub unknown: String, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum LoginFailedReason2 { UnregisteredId, @@ -350,14 +384,14 @@ pub enum LoginFailedReason2 { CompanyAccountLimitReached, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, LoginServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x083E)] pub struct LoginFailedPacket2 { pub reason: LoginFailedReason2, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum CharacterSelectionFailedReason { RejectedFromServer, @@ -365,7 +399,7 @@ pub enum CharacterSelectionFailedReason { /// Sent by the character server as a response to [SelectCharacterPacket] /// failing. Provides a reason for the character selection failing. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x006C)] pub struct CharacterSelectionFailedPacket { @@ -375,7 +409,7 @@ pub struct CharacterSelectionFailedPacket { /// Sent by the character server as a response to [SelectCharacterPacket] /// succeeding. Provides a map server to connect to, along with the ID of our /// selected character. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0AC5)] pub struct CharacterSelectionSuccessPacket { @@ -387,7 +421,7 @@ pub struct CharacterSelectionSuccessPacket { pub unknown: [u8; 128], } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum CharacterCreationFailedReason { CharacterNameAlreadyUsed, @@ -400,7 +434,7 @@ pub enum CharacterCreationFailedReason { /// Sent by the character server as a response to [CreateCharacterPacket] /// failing. Provides a reason for the character creation failing. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x006E)] pub struct CharacterCreationFailedPacket { @@ -409,7 +443,7 @@ pub struct CharacterCreationFailedPacket { /// Sent by the client to the login server every 60 seconds to keep the /// connection alive. -#[derive(Clone, Debug, Default, OutgoingPacket)] +#[derive(Debug, Clone, Default, OutgoingPacket, LoginServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0200)] #[ping] @@ -417,7 +451,7 @@ pub struct LoginServerKeepalivePacket { pub user_id: [u8; 24], } -#[derive(Clone, Debug, FromBytes, FixedByteSize)] +#[derive(Debug, Clone, FromBytes, FixedByteSize)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct CharacterServerInformation { pub server_ip: ServerAddress, @@ -433,7 +467,7 @@ pub struct CharacterServerInformation { /// Sent by the client to the character server after after successfully logging /// into the login server. /// Attempts to log into the character server using the provided information. -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, CharacterServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0065)] pub struct CharacterServerLoginPacket { @@ -448,7 +482,7 @@ pub struct CharacterServerLoginPacket { /// Sent by the client to the map server after after successfully selecting a /// character. Attempts to log into the map server using the provided /// information. -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0436)] pub struct MapServerLoginPacket { @@ -461,7 +495,7 @@ pub struct MapServerLoginPacket { pub unknown: [u8; 4], } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0283)] pub struct Packet8302 { @@ -472,7 +506,7 @@ pub struct Packet8302 { /// a new character. /// Attempts to create a new character in an empty slot using the provided /// information. -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, CharacterServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0A39)] pub struct CreateCharacterPacket { @@ -487,7 +521,7 @@ pub struct CreateCharacterPacket { pub sex: Sex, } -#[derive(Clone, Debug, ByteConvertable, FixedByteSize)] +#[derive(Debug, Clone, ByteConvertable, FixedByteSize)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct CharacterInformation { pub character_id: CharacterId, @@ -541,7 +575,7 @@ pub struct CharacterInformation { /// Sent by the character server as a response to [CreateCharacterPacket] /// succeeding. Provides all character information of the newly created /// character. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B6F)] pub struct CreateCharacterSuccessPacket { @@ -550,14 +584,14 @@ pub struct CreateCharacterSuccessPacket { /// Sent by the client to the character server. /// Requests a list of every character associated with the account. -#[derive(Clone, Debug, Default, OutgoingPacket)] +#[derive(Debug, Clone, Default, OutgoingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x09A1)] pub struct RequestCharacterListPacket {} /// Sent by the character server as a response to [RequestCharacterListPacket] /// succeeding. Provides the requested list of character information. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B72)] pub struct RequestCharacterListSuccessPacket { @@ -567,9 +601,16 @@ pub struct RequestCharacterListSuccessPacket { pub character_information: Vec, } +/// Sent by the map server to the client. +#[derive(Debug, Clone, Default, IncomingPacket, MapServer)] +#[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] +#[header(0x0B1D)] +#[ping] +pub struct MapServerPingPacket {} + /// Sent by the client to the map server when the player wants to move. /// Attempts to path the player towards the provided position. -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0881)] pub struct RequestPlayerMovePacket { @@ -579,7 +620,7 @@ pub struct RequestPlayerMovePacket { /// Sent by the client to the map server when the player wants to warp. /// Attempts to warp the player to a specific position on a specific map using /// the provided information. -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0140)] pub struct RequestWarpToMapPacket { @@ -592,7 +633,7 @@ pub struct RequestWarpToMapPacket { /// Informs the client that an entity is pathing towards a new position. /// Provides the initial position and destination of the movement, as well as a /// timestamp of when it started (for synchronization). -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0086)] pub struct EntityMovePacket { @@ -601,7 +642,7 @@ pub struct EntityMovePacket { pub timestamp: ClientTick, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0088)] pub struct EntityStopMovePacket { @@ -613,7 +654,7 @@ pub struct EntityStopMovePacket { /// Informs the client that the player is pathing towards a new position. /// Provides the initial position and destination of the movement, as well as a /// timestamp of when it started (for synchronization). -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0087)] pub struct PlayerMovePacket { @@ -625,7 +666,7 @@ pub struct PlayerMovePacket { /// character. /// Attempts to delete a character from the user account using the provided /// information. -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, CharacterServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x01FB)] pub struct DeleteCharacterPacket { @@ -639,7 +680,7 @@ pub struct DeleteCharacterPacket { pub unknown: [u8; 10], } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum CharacterDeletionFailedReason { NotAllowed, @@ -649,7 +690,7 @@ pub enum CharacterDeletionFailedReason { /// Sent by the character server as a response to [DeleteCharacterPacket] /// failing. Provides a reason for the character deletion failing. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0070)] pub struct CharacterDeletionFailedPacket { @@ -658,14 +699,14 @@ pub struct CharacterDeletionFailedPacket { /// Sent by the character server as a response to [DeleteCharacterPacket] /// succeeding. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x006F)] pub struct CharacterDeletionSuccessPacket {} /// Sent by the client to the character server when the user selects a /// character. Attempts to select the character in the specified slot. -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, CharacterServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0066)] pub struct SelectCharacterPacket { @@ -674,7 +715,7 @@ pub struct SelectCharacterPacket { /// Sent by the map server to the client when there is a new chat message from /// the server. Provides the message to be displayed in the chat window. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x008E)] pub struct ServerMessagePacket { @@ -686,7 +727,7 @@ pub struct ServerMessagePacket { /// Sent by the client to the map server when the user hovers over an entity. /// Attempts to fetch additional information about the entity, such as the /// display name. -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0368)] pub struct RequestDetailsPacket { @@ -695,7 +736,7 @@ pub struct RequestDetailsPacket { /// Sent by the map server to the client as a response to /// [RequestDetailsPacket]. Provides additional information about the player. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0A30)] pub struct RequestPlayerDetailsSuccessPacket { @@ -713,7 +754,7 @@ pub struct RequestPlayerDetailsSuccessPacket { /// Sent by the map server to the client as a response to /// [RequestDetailsPacket]. Provides additional information about the entity. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0ADF)] pub struct RequestEntityDetailsSuccessPacket { @@ -725,14 +766,14 @@ pub struct RequestEntityDetailsSuccessPacket { pub title: String, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x09E7)] pub struct NewMailStatusPacket { pub new_available: u8, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct AchievementData { pub acheivement_id: u32, @@ -742,7 +783,7 @@ pub struct AchievementData { pub got_rewarded: u8, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0A24)] pub struct AchievementUpdatePacket { @@ -753,7 +794,7 @@ pub struct AchievementUpdatePacket { pub acheivement_data: AchievementData, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0A23)] pub struct AchievementListPacket { @@ -768,14 +809,14 @@ pub struct AchievementListPacket { pub acheivement_data: Vec, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0ADE)] pub struct CriticalWeightUpdatePacket { pub packet_length: u32, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x01D7)] pub struct SpriteChangePacket { @@ -785,7 +826,7 @@ pub struct SpriteChangePacket { pub value2: u32, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B08)] pub struct InventoyStartPacket { @@ -795,7 +836,7 @@ pub struct InventoyStartPacket { pub inventory_name: String, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B0B)] pub struct InventoyEndPacket { @@ -803,7 +844,7 @@ pub struct InventoyEndPacket { pub flag: u8, // maybe char ? } -#[derive(Clone, Debug, ByteConvertable, FixedByteSize)] +#[derive(Debug, Clone, ByteConvertable, FixedByteSize)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct ItemOptions { pub index: u16, @@ -811,7 +852,7 @@ pub struct ItemOptions { pub parameter: u8, } -#[derive(Clone, Debug, ByteConvertable, FixedByteSize)] +#[derive(Debug, Clone, ByteConvertable, FixedByteSize)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct RegularItemInformation { pub index: ItemIndex, @@ -824,7 +865,7 @@ pub struct RegularItemInformation { pub fags: u8, // bit 1 - is_identified; bit 2 - place_in_etc_tab; } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B09)] pub struct RegularItemListPacket { @@ -835,7 +876,7 @@ pub struct RegularItemListPacket { pub item_information: Vec, } -#[derive(Clone, Debug, ByteConvertable, FixedByteSize)] +#[derive(Debug, Clone, ByteConvertable, FixedByteSize)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct EquippableItemInformation { pub index: ItemIndex, @@ -854,7 +895,7 @@ pub struct EquippableItemInformation { pub fags: u8, // bit 1 - is_identified; bit 2 - is_damaged; bit 3 - place_in_etc_tab } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B39)] pub struct EquippableItemListPacket { @@ -865,14 +906,14 @@ pub struct EquippableItemListPacket { pub item_information: Vec, } -#[derive(Clone, Debug, ByteConvertable, FixedByteSize)] +#[derive(Debug, Clone, ByteConvertable, FixedByteSize)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct EquippableSwitchItemInformation { pub index: ItemIndex, pub position: u32, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0A9B)] pub struct EquippableSwitchItemListPacket { @@ -882,7 +923,7 @@ pub struct EquippableSwitchItemListPacket { pub item_information: Vec, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x099B)] pub struct MapTypePacket { @@ -893,7 +934,7 @@ pub struct MapTypePacket { /// Sent by the map server to the client when there is a new chat message from /// ??. Provides the message to be displayed in the chat window, as well as /// information on how the message should be displayed. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x01C3)] pub struct Broadcast2MessagePacket { @@ -909,7 +950,7 @@ pub struct Broadcast2MessagePacket { /// Sent by the map server to the client when when someone uses the @broadcast /// command. Provides the message to be displayed in the chat window. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x009A)] pub struct BroadcastMessagePacket { @@ -921,20 +962,20 @@ pub struct BroadcastMessagePacket { /// Sent by the map server to the client when when someone writes in proximity /// chat. Provides the source player and message to be displayed in the chat /// window and the speach bubble. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x008D)] pub struct OverheadMessagePacket { pub packet_length: u16, pub entity_id: EntityId, - #[length_hint(self.packet_length - 6)] + #[length_hint(self.packet_length - 8)] pub message: String, } /// Sent by the map server to the client when there is a new chat message from /// an entity. Provides the message to be displayed in the chat window, the /// color of the message, and the ID of the entity it originated from. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x02C1)] pub struct EntityMessagePacket { @@ -945,7 +986,7 @@ pub struct EntityMessagePacket { pub message: String, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00C0)] pub struct DisplayEmotionPacket { @@ -957,7 +998,7 @@ pub struct DisplayEmotionPacket { /// [UpdateStatusPacket1], [UpdateStatusPacket2], and [UpdateStatusPacket3]. /// All UpdateStatusPackets do the same, they just have different sizes /// correlating to the space the updated value requires. -#[derive(Clone, Debug)] +#[derive(Debug, Clone)] pub enum StatusType { Weight(u32), MaximumWeight(u32), @@ -1110,7 +1151,7 @@ impl korangar_interface::elem } } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00B0)] pub struct UpdateStatusPacket { @@ -1118,7 +1159,7 @@ pub struct UpdateStatusPacket { pub status_type: StatusType, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0196)] pub struct StatusChangeSequencePacket { @@ -1130,7 +1171,7 @@ pub struct StatusChangeSequencePacket { /// Sent by the character server to the client when loading onto a new map. /// This packet is ignored by Korangar since all of the provided values are set /// again individually using the UpdateStatusPackets. -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00BD)] pub struct InitialStatusPacket { @@ -1164,7 +1205,7 @@ pub struct InitialStatusPacket { pub bonus_attack_speed: u16, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0141)] pub struct UpdateStatusPacket1 { @@ -1172,7 +1213,7 @@ pub struct UpdateStatusPacket1 { pub status_type: StatusType, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0ACB)] pub struct UpdateStatusPacket2 { @@ -1180,7 +1221,7 @@ pub struct UpdateStatusPacket2 { pub status_type: StatusType, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00BE)] pub struct UpdateStatusPacket3 { @@ -1188,14 +1229,14 @@ pub struct UpdateStatusPacket3 { pub status_type: StatusType, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x013A)] pub struct UpdateAttackRangePacket { pub attack_range: u16, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, CharacterServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x08D4)] pub struct SwitchCharacterSlotPacket { @@ -1207,7 +1248,7 @@ pub struct SwitchCharacterSlotPacket { pub remaining_moves: u16, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum Action { Attack, @@ -1221,7 +1262,7 @@ pub enum Action { TouchSkill, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0437)] pub struct RequestActionPacket { @@ -1229,7 +1270,7 @@ pub struct RequestActionPacket { pub action: Action, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00F3)] pub struct GlobalMessagePacket { @@ -1237,7 +1278,7 @@ pub struct GlobalMessagePacket { pub message: String, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0139)] pub struct RequestPlayerAttackFailedPacket { @@ -1247,7 +1288,7 @@ pub struct RequestPlayerAttackFailedPacket { pub attack_range: u16, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0977)] pub struct UpdateEntityHealthPointsPacket { @@ -1256,11 +1297,11 @@ pub struct UpdateEntityHealthPointsPacket { pub maximum_health_points: u32, } -/*#[derive(Clone, Debug, ByteConvertable)] +/*#[derive(Debug, Clone, ByteConvertable)] pub enum DamageType { }*/ -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x08C8)] pub struct DamagePacket { @@ -1277,7 +1318,7 @@ pub struct DamagePacket { pub damage_amount2: u32, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x007F)] #[ping] @@ -1285,7 +1326,7 @@ pub struct ServerTickPacket { pub client_tick: ClientTick, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0360)] #[ping] @@ -1293,7 +1334,7 @@ pub struct RequestServerTickPacket { pub client_tick: ClientTick, } -#[derive(Clone, Debug, PartialEq, Eq, ByteConvertable)] +#[derive(Debug, Clone, PartialEq, Eq, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u16)] pub enum SwitchCharacterSlotResponseStatus { @@ -1301,7 +1342,7 @@ pub enum SwitchCharacterSlotResponseStatus { Error, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, CharacterServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B70)] pub struct SwitchCharacterSlotResponsePacket { @@ -1310,7 +1351,7 @@ pub struct SwitchCharacterSlotResponsePacket { pub remaining_moves: u16, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0091)] pub struct ChangeMapPacket { @@ -1319,7 +1360,7 @@ pub struct ChangeMapPacket { pub position: TilePosition, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum DissapearanceReason { OutOfSight, @@ -1329,7 +1370,7 @@ pub enum DissapearanceReason { TrickDead, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0080)] pub struct EntityDisappearedPacket { @@ -1337,7 +1378,7 @@ pub struct EntityDisappearedPacket { pub reason: DissapearanceReason, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x09FD)] pub struct MovingEntityAppearedPacket { @@ -1380,7 +1421,7 @@ pub struct MovingEntityAppearedPacket { pub name: String, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x09FE)] pub struct EntityAppearedPacket { @@ -1422,7 +1463,7 @@ pub struct EntityAppearedPacket { pub name: String, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x09FF)] pub struct EntityAppeared2Packet { @@ -1483,7 +1524,7 @@ pub enum SkillType { Trap, } -#[derive(Clone, Debug, ByteConvertable, FixedByteSize)] +#[derive(Debug, Clone, ByteConvertable, FixedByteSize)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct SkillInformation { pub skill_id: SkillId, @@ -1496,7 +1537,7 @@ pub struct SkillInformation { pub upgraded: u8, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x010F)] pub struct UpdateSkillTreePacket { @@ -1506,7 +1547,7 @@ pub struct UpdateSkillTreePacket { pub skill_information: Vec, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct HotkeyData { pub is_skill: u8, @@ -1514,7 +1555,7 @@ pub struct HotkeyData { pub quantity_or_skill_level: SkillLevel, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B20)] pub struct UpdateHotkeysPacket { @@ -1523,21 +1564,21 @@ pub struct UpdateHotkeysPacket { pub hotkeys: [HotkeyData; 38], } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x02C9)] pub struct UpdatePartyInvitationStatePacket { pub allowed: u8, // always 0 on rAthena } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x02DA)] pub struct UpdateShowEquipPacket { pub open_equip_window: u8, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x02D9)] pub struct UpdateConfigurationPacket { @@ -1545,7 +1586,7 @@ pub struct UpdateConfigurationPacket { pub value: u32, // only enabled and disabled ? } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x08E2)] pub struct NavigateToMonsterPacket { @@ -1558,7 +1599,7 @@ pub struct NavigateToMonsterPacket { pub target_monster_id: u16, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u32)] pub enum MarkerType { @@ -1567,7 +1608,7 @@ pub enum MarkerType { RemoveMark, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0144)] pub struct MarkMinimapPositionPacket { @@ -1578,21 +1619,21 @@ pub struct MarkMinimapPositionPacket { pub color: ColorRGBA, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00B5)] pub struct NextButtonPacket { pub entity_id: EntityId, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00B6)] pub struct CloseButtonPacket { pub entity_id: EntityId, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00B7)] pub struct DialogMenuPacket { @@ -1602,7 +1643,7 @@ pub struct DialogMenuPacket { pub message: String, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x01F3)] pub struct DisplaySpecialEffectPacket { @@ -1610,7 +1651,7 @@ pub struct DisplaySpecialEffectPacket { pub effect_id: u32, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x043D)] pub struct DisplaySkillCooldownPacket { @@ -1618,7 +1659,7 @@ pub struct DisplaySkillCooldownPacket { pub until: ClientTick, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x01DE)] pub struct DisplaySkillEffectAndDamagePacket { @@ -1634,7 +1675,7 @@ pub struct DisplaySkillEffectAndDamagePacket { pub skill_type: u8, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u16)] pub enum HealType { @@ -1644,7 +1685,7 @@ pub enum HealType { SpellPoints, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0A27)] pub struct DisplayPlayerHealEffect { @@ -1652,7 +1693,7 @@ pub struct DisplayPlayerHealEffect { pub heal_amount: u32, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x09CB)] pub struct DisplaySkillEffectNoDamagePacket { @@ -1663,7 +1704,7 @@ pub struct DisplaySkillEffectNoDamagePacket { pub result: u8, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0983)] pub struct StatusChangePacket { @@ -1675,7 +1716,7 @@ pub struct StatusChangePacket { pub value: [u32; 3], } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct ObjectiveDetails1 { pub hunt_identification: u32, @@ -1688,7 +1729,7 @@ pub struct ObjectiveDetails1 { pub mob_name: String, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x09F9)] pub struct QuestNotificationPacket1 { @@ -1702,7 +1743,7 @@ pub struct QuestNotificationPacket1 { pub objective_details: [ObjectiveDetails1; 3], } -#[derive(Clone, Debug, ByteConvertable, FixedByteSize)] +#[derive(Debug, Clone, ByteConvertable, FixedByteSize)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct HuntingObjective { pub quest_id: u32, @@ -1711,7 +1752,7 @@ pub struct HuntingObjective { pub current_count: u16, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x08FE)] pub struct HuntingQuestNotificationPacket { @@ -1721,7 +1762,7 @@ pub struct HuntingQuestNotificationPacket { pub objective_details: Vec, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x09FA)] pub struct HuntingQuestUpdateObjectivePacket { @@ -1732,14 +1773,14 @@ pub struct HuntingQuestUpdateObjectivePacket { pub objective_details: Vec, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x02B4)] pub struct QuestRemovedPacket { pub quest_id: u32, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct QuestDetails { pub hunt_identification: u32, @@ -1753,7 +1794,7 @@ pub struct QuestDetails { pub mob_name: String, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct Quest { #[packet_length] @@ -1766,7 +1807,7 @@ pub struct Quest { pub objective_details: Vec, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x09F8)] pub struct QuestListPacket { @@ -1777,7 +1818,7 @@ pub struct QuestListPacket { pub quests: Vec, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u32)] pub enum VisualEffect { @@ -1793,7 +1834,7 @@ pub enum VisualEffect { BaseLevelUpTaekwon, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x019B)] pub struct VisualEffectPacket { @@ -1801,7 +1842,7 @@ pub struct VisualEffectPacket { pub effect: VisualEffect, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u16)] pub enum ExperienceType { @@ -1810,7 +1851,7 @@ pub enum ExperienceType { JobExperience, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u16)] pub enum ExperienceSource { @@ -1818,7 +1859,7 @@ pub enum ExperienceSource { Quest, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0ACC)] pub struct DisplayGainedExperiencePacket { @@ -1828,7 +1869,7 @@ pub struct DisplayGainedExperiencePacket { pub experience_source: ExperienceSource, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum ImageLocation { BottomLeft, @@ -1840,7 +1881,7 @@ pub enum ImageLocation { ClearAll, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x01B3)] pub struct DisplayImagePacket { @@ -1849,7 +1890,7 @@ pub struct DisplayImagePacket { pub location: ImageLocation, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0229)] pub struct StateChangePacket { @@ -1860,7 +1901,7 @@ pub struct StateChangePacket { pub is_pk_mode_on: u8, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B41)] pub struct ItemPickupPacket { @@ -1882,7 +1923,7 @@ pub struct ItemPickupPacket { pub enchantment_level: u8, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u16)] pub enum RemoveItemReason { @@ -1896,7 +1937,7 @@ pub enum RemoveItemReason { ConsumedByFourSpiritAnalysis, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x07FA)] pub struct RemoveItemFromInventoryPacket { @@ -1906,7 +1947,7 @@ pub struct RemoveItemFromInventoryPacket { } // TODO: improve names -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u16)] pub enum QuestEffect { @@ -1925,7 +1966,7 @@ pub enum QuestEffect { None, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u16)] pub enum QuestColor { @@ -1935,7 +1976,7 @@ pub enum QuestColor { Purple, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0446)] pub struct QuestEffectPacket { @@ -1945,7 +1986,7 @@ pub struct QuestEffectPacket { pub color: QuestColor, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00B4)] pub struct NpcDialogPacket { @@ -1955,12 +1996,12 @@ pub struct NpcDialogPacket { pub text: String, } -#[derive(Clone, Debug, Default, OutgoingPacket)] +#[derive(Debug, Clone, Default, OutgoingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x007D)] pub struct MapLoadedPacket {} -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, CharacterServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0187)] #[ping] @@ -1970,7 +2011,7 @@ pub struct CharacterServerKeepalivePacket { pub account_id: AccountId, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0090)] pub struct StartDialogPacket { @@ -1979,21 +2020,21 @@ pub struct StartDialogPacket { pub dialog_type: u8, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00B9)] pub struct NextDialogPacket { pub npc_id: EntityId, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0146)] pub struct CloseDialogPacket { pub npc_id: EntityId, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00B8)] pub struct ChooseDialogOptionPacket { @@ -2057,39 +2098,7 @@ pub enum EquipPosition { ShadowLeftRightAccessory, } -impl EquipPosition { - pub fn display_name(&self) -> &'static str { - match self { - EquipPosition::None => panic!(), - EquipPosition::HeadLower => "Head lower", - EquipPosition::HeadMiddle => "Head middle", - EquipPosition::HeadTop => "Head top", - EquipPosition::RightHand => "Right hand", - EquipPosition::LeftHand => "Left hand", - EquipPosition::Armor => "Armor", - EquipPosition::Shoes => "Shoes", - EquipPosition::Garment => "Garment", - EquipPosition::LeftAccessory => "Left accessory", - EquipPosition::RigthAccessory => "Right accessory", - EquipPosition::CostumeHeadTop => "Costume head top", - EquipPosition::CostumeHeadMiddle => "Costume head middle", - EquipPosition::CostumeHeadLower => "Costume head lower", - EquipPosition::CostumeGarment => "Costume garment", - EquipPosition::Ammo => "Ammo", - EquipPosition::ShadowArmor => "Shadow ammo", - EquipPosition::ShadowWeapon => "Shadow weapon", - EquipPosition::ShadowShield => "Shadow shield", - EquipPosition::ShadowShoes => "Shadow shoes", - EquipPosition::ShadowRightAccessory => "Shadow right accessory", - EquipPosition::ShadowLeftAccessory => "Shadow left accessory", - EquipPosition::LeftRightAccessory => "Accessory", - EquipPosition::LeftRightHand => "Two hand weapon", - EquipPosition::ShadowLeftRightAccessory => "Shadow accessory", - } - } -} - -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0998)] pub struct RequestEquipItemPacket { @@ -2097,7 +2106,7 @@ pub struct RequestEquipItemPacket { pub equip_position: EquipPosition, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum RequestEquipItemStatus { Success, @@ -2105,7 +2114,7 @@ pub enum RequestEquipItemStatus { FailedDueToLevelRequirement, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0999)] pub struct RequestEquipItemStatusPacket { @@ -2115,21 +2124,21 @@ pub struct RequestEquipItemStatusPacket { pub result: RequestEquipItemStatus, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00AB)] pub struct RequestUnequipItemPacket { pub inventory_index: ItemIndex, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum RequestUnequipItemStatus { Success, Failed, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x099A)] pub struct RequestUnequipItemStatusPacket { @@ -2138,14 +2147,14 @@ pub struct RequestUnequipItemStatusPacket { pub result: RequestUnequipItemStatus, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum RestartType { Respawn, Disconnect, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00B2)] pub struct RestartPacket { @@ -2154,14 +2163,14 @@ pub struct RestartPacket { // TODO: check that this can be only 1 and 0, if not ByteConvertable // should be implemented manually -#[derive(Clone, Debug, ByteConvertable, PartialEq, Eq)] +#[derive(Debug, Clone, ByteConvertable, PartialEq, Eq)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum RestartResponseStatus { Nothing, Ok, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x00B3)] pub struct RestartResponsePacket { @@ -2170,7 +2179,7 @@ pub struct RestartResponsePacket { // TODO: check that this can be only 1 and 0, if not Named, ByteConvertable // should be implemented manually -#[derive(Clone, Debug, ByteConvertable, PartialEq, Eq)] +#[derive(Debug, Clone, ByteConvertable, PartialEq, Eq)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u16)] pub enum DisconnectResponseStatus { @@ -2178,14 +2187,14 @@ pub enum DisconnectResponseStatus { Wait10Seconds, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x018B)] pub struct DisconnectResponsePacket { pub result: DisconnectResponseStatus, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0438)] pub struct UseSkillAtIdPacket { @@ -2194,7 +2203,7 @@ pub struct UseSkillAtIdPacket { pub target_id: EntityId, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0AF4)] pub struct UseSkillOnGroundPacket { @@ -2205,7 +2214,7 @@ pub struct UseSkillOnGroundPacket { pub unused: u8, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B10)] pub struct StartUseSkillPacket { @@ -2214,14 +2223,14 @@ pub struct StartUseSkillPacket { pub target_id: EntityId, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B11)] pub struct EndUseSkillPacket { pub skill_id: SkillId, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x07FB)] pub struct UseSkillSuccessPacket { @@ -2234,7 +2243,7 @@ pub struct UseSkillSuccessPacket { pub disposable: u8, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0110)] pub struct ToUseSkillSuccessPacket { @@ -2245,7 +2254,7 @@ pub struct ToUseSkillSuccessPacket { pub cause: u8, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u32)] pub enum UnitId { @@ -2440,7 +2449,7 @@ pub enum UnitId { Max, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x09CA)] pub struct NotifySkillUnitPacket { @@ -2454,7 +2463,7 @@ pub struct NotifySkillUnitPacket { pub skill_level: u8, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0117)] pub struct NotifyGroundSkillPacket { @@ -2465,14 +2474,14 @@ pub struct NotifyGroundSkillPacket { pub start_time: ClientTick, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0120)] pub struct SkillUnitDisappearPacket { pub entity_id: EntityId, } -#[derive(Clone, Debug, ByteConvertable, FixedByteSize)] +#[derive(Debug, Clone, ByteConvertable, FixedByteSize)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct Friend { pub account_id: AccountId, @@ -2481,7 +2490,7 @@ pub struct Friend { pub name: String, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0202)] pub struct AddFriendPacket { @@ -2489,7 +2498,7 @@ pub struct AddFriendPacket { pub name: String, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0203)] pub struct RemoveFriendPacket { @@ -2497,7 +2506,7 @@ pub struct RemoveFriendPacket { pub character_id: CharacterId, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x020A)] pub struct NotifyFriendRemovedPacket { @@ -2505,7 +2514,7 @@ pub struct NotifyFriendRemovedPacket { pub character_id: CharacterId, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0201)] pub struct FriendListPacket { @@ -2515,14 +2524,14 @@ pub struct FriendListPacket { pub friends: Vec, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub enum OnlineState { Online, Offline, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0206)] pub struct FriendOnlineStatusPacket { @@ -2533,14 +2542,14 @@ pub struct FriendOnlineStatusPacket { pub name: String, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0207)] pub struct FriendRequestPacket { - pub friend: Friend, + pub requestee: Friend, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u32)] pub enum FriendRequestResponse { @@ -2548,7 +2557,7 @@ pub enum FriendRequestResponse { Accept, } -#[derive(Clone, Debug, OutgoingPacket, new)] +#[derive(Debug, Clone, OutgoingPacket, MapServer, new)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0208)] pub struct FriendRequestResponsePacket { @@ -2557,7 +2566,7 @@ pub struct FriendRequestResponsePacket { pub response: FriendRequestResponse, } -#[derive(Clone, Debug, PartialEq, Eq, ByteConvertable)] +#[derive(Debug, Clone, PartialEq, Eq, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[numeric_type(u16)] pub enum FriendRequestResult { @@ -2567,7 +2576,7 @@ pub enum FriendRequestResult { OtherFriendListFull, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0209)] pub struct FriendRequestResultPacket { @@ -2575,19 +2584,7 @@ pub struct FriendRequestResultPacket { pub friend: Friend, } -impl FriendRequestResultPacket { - pub fn into_message(self) -> String { - // Messages taken from rAthena - match self.result { - FriendRequestResult::Accepted => format!("You have become friends with {}.", self.friend.name), - FriendRequestResult::Rejected => format!("{} does not want to be friends with you.", self.friend.name), - FriendRequestResult::OwnFriendListFull => "Your Friend List is full.".to_owned(), - FriendRequestResult::OtherFriendListFull => format!("{}'s Friend List is full.", self.friend.name), - } - } -} - -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x02C6)] pub struct PartyInvitePacket { @@ -2596,14 +2593,14 @@ pub struct PartyInvitePacket { pub party_name: String, } -#[derive(Clone, Debug, ByteConvertable, FixedByteSize)] +#[derive(Debug, Clone, ByteConvertable, FixedByteSize)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct ReputationEntry { pub reputation_type: u64, pub points: i64, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0B8D)] pub struct ReputationPacket { @@ -2614,21 +2611,21 @@ pub struct ReputationPacket { pub entries: Vec, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct Aliance { #[length_hint(24)] pub name: String, } -#[derive(Clone, Debug, ByteConvertable)] +#[derive(Debug, Clone, ByteConvertable)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] pub struct Antagonist { #[length_hint(24)] pub name: String, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x098A)] pub struct ClanInfoPacket { @@ -2649,7 +2646,7 @@ pub struct ClanInfoPacket { pub antagonists: Vec, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0988)] pub struct ClanOnlineCountPacket { @@ -2657,52 +2654,12 @@ pub struct ClanOnlineCountPacket { pub maximum_members: u16, } -#[derive(Clone, Debug, IncomingPacket)] +#[derive(Debug, Clone, IncomingPacket, MapServer)] #[cfg_attr(feature = "interface", derive(korangar_interface::elements::PrototypeElement))] #[header(0x0192)] pub struct ChangeMapCellPacket { - position: TilePosition, - cell_type: u16, + pub position: TilePosition, + pub cell_type: u16, #[length_hint(16)] - map_name: String, -} - -/// Helper struct for working with unknown incoming packets. -#[derive(Clone, new)] -pub struct UnknownPacket { - bytes: Vec, -} - -impl IncomingPacket for UnknownPacket { - const HEADER: u16 = 0; - const IS_PING: bool = false; - - fn payload_from_bytes(byte_stream: &mut ByteStream) -> ConversionResult { - let _ = byte_stream; - unimplemented!() - } -} - -#[cfg(feature = "interface")] -impl korangar_interface::elements::PrototypeElement for UnknownPacket { - fn to_element(&self, display: String) -> korangar_interface::elements::ElementCell { - use korangar_interface::elements::{ElementWrap, Expandable}; - - let mut byte_stream = ByteStream::<()>::without_metadata(&self.bytes); - - let elements = match self.bytes.len() >= 2 { - true => { - let signature = u16::from_bytes(&mut byte_stream).unwrap(); - let header = format!("0x{:0>4x}", signature); - let data = &self.bytes[byte_stream.get_offset()..]; - - vec![header.to_element("header".to_owned()), data.to_element("data".to_owned())] - } - false => { - vec![self.bytes.to_element("data".to_owned())] - } - }; - - Expandable::new(display, elements, false).wrap() - } + pub map_name: String, } diff --git a/ragnarok_procedural/README.md b/ragnarok_procedural/README.md index eca7f771..c17b2b07 100644 --- a/ragnarok_procedural/README.md +++ b/ragnarok_procedural/README.md @@ -2,4 +2,4 @@ A proc macro crate to derive Ragnarok Online traits. -#### Note: This crate is not supposed to be included directly. Use it by activating the `derive` feature of `ragnarok_bytes` or `ragnarok_networking`. +#### Note: This crate is not supposed to be included directly. Use it by activating the `derive` feature of `ragnarok_bytes` or `ragnarok_packets`. diff --git a/ragnarok_procedural/src/lib.rs b/ragnarok_procedural/src/lib.rs index a8ef4725..8aef802a 100644 --- a/ragnarok_procedural/src/lib.rs +++ b/ragnarok_procedural/src/lib.rs @@ -8,6 +8,7 @@ mod packet; mod utils; use proc_macro::TokenStream as InterfaceTokenStream; +use quote::quote; use syn::{parse, Data, DeriveInput}; use self::convertable::*; @@ -160,3 +161,36 @@ pub fn derive_packet(token_stream: InterfaceTokenStream) -> InterfaceTokenStream Data::Union(..) => panic!("union types may not be derived"), } } + +#[proc_macro_derive(LoginServer)] +pub fn derive_login_server_packet(token_stream: InterfaceTokenStream) -> InterfaceTokenStream { + let DeriveInput { ident, generics, .. } = parse(token_stream).expect("failed to parse token stream"); + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics ragnarok_packets::LoginServerPacket for #ident #type_generics #where_clause {} + } + .into() +} + +#[proc_macro_derive(CharacterServer)] +pub fn derive_character_server_packet(token_stream: InterfaceTokenStream) -> InterfaceTokenStream { + let DeriveInput { ident, generics, .. } = parse(token_stream).expect("failed to parse token stream"); + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics ragnarok_packets::CharacterServerPacket for #ident #type_generics #where_clause {} + } + .into() +} + +#[proc_macro_derive(MapServer)] +pub fn derive_map_server_packet(token_stream: InterfaceTokenStream) -> InterfaceTokenStream { + let DeriveInput { ident, generics, .. } = parse(token_stream).expect("failed to parse token stream"); + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics ragnarok_packets::MapServerPacket for #ident #type_generics #where_clause {} + } + .into() +} diff --git a/ragnarok_procedural/src/packet.rs b/ragnarok_procedural/src/packet.rs index bbf5db8a..8f8085a7 100644 --- a/ragnarok_procedural/src/packet.rs +++ b/ragnarok_procedural/src/packet.rs @@ -29,9 +29,9 @@ pub fn derive_incoming_packet_struct( }; quote! { - impl #impl_generics ragnarok_networking::IncomingPacket for #name #type_generics #where_clause { + impl #impl_generics ragnarok_packets::IncomingPacket for #name #type_generics #where_clause { const IS_PING: bool = #is_ping; - const HEADER: u16 = #signature; + const HEADER: ragnarok_packets::PacketHeader = ragnarok_packets::PacketHeader(#signature); fn payload_from_bytes(byte_stream: &mut ragnarok_bytes::ByteStream) -> ragnarok_bytes::ConversionResult { let base_offset = byte_stream.get_offset(); @@ -40,6 +40,13 @@ pub fn derive_incoming_packet_struct( Ok(packet) } + + #[cfg(feature = "packet-to-prototype-element")] + fn to_prototype_element( + &self, + ) -> Box + Send> { + Box::new(self.clone()) + } } } .into() @@ -64,7 +71,7 @@ pub fn derive_outgoing_packet_struct( let to_bytes = quote!([&#signature.to_le_bytes()[..], #(#to_bytes_implementations),*].concat()); quote! { - impl #impl_generics ragnarok_networking::OutgoingPacket for #name #type_generics #where_clause { + impl #impl_generics ragnarok_packets::OutgoingPacket for #name #type_generics #where_clause { const IS_PING: bool = #is_ping; // Temporary until serialization is always possible @@ -72,6 +79,13 @@ pub fn derive_outgoing_packet_struct( fn packet_to_bytes(&self) -> ragnarok_bytes::ConversionResult> { Ok(#to_bytes) } + + #[cfg(feature = "packet-to-prototype-element")] + fn to_prototype_element( + &self, + ) -> Box + Send> { + Box::new(self.clone()) + } } } .into()