From 69584a302d132dc2bcc3837437e7347a3e0a5114 Mon Sep 17 00:00:00 2001
From: Alexander Foremny <aforemny@posteo.de>
Date: Thu, 14 Mar 2024 06:43:53 +0100
Subject: feat: players can teleport

---
 Cargo.lock            | 710 +++++++++++++++++++++++++++++++++++++++++++++++++-
 Cargo.toml            |   4 +
 README.md             |  11 +
 shell.nix             |   1 +
 src/client.rs         |  82 ++++++
 src/client/network.rs |  35 +++
 src/main.rs           |  26 +-
 src/protocol.rs       |  47 ++++
 src/server.rs         |  83 ++++++
 src/server/network.rs |  31 +++
 src/shared.rs         |  40 +++
 11 files changed, 1068 insertions(+), 2 deletions(-)
 create mode 100644 README.md
 create mode 100644 src/client.rs
 create mode 100644 src/client/network.rs
 create mode 100644 src/protocol.rs
 create mode 100644 src/server.rs
 create mode 100644 src/server/network.rs
 create mode 100644 src/shared.rs

diff --git a/Cargo.lock b/Cargo.lock
index 503a4d7..337fd1b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -72,17 +72,40 @@ dependencies = [
  "winit",
 ]
 
+[[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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
 [[package]]
 name = "agame"
 version = "0.1.0"
 dependencies = [
  "bevy",
+ "crossbeam-channel",
+ "lightyear",
+ "rand",
+ "serde",
 ]
 
 [[package]]
@@ -161,6 +184,12 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
 
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
 [[package]]
 name = "android_log-sys"
 version = "0.3.1"
@@ -176,6 +205,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "anyhow"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
+
 [[package]]
 name = "approx"
 version = "0.5.1"
@@ -235,6 +270,19 @@ dependencies = [
  "pin-project-lite",
 ]
 
+[[package]]
+name = "async-compat"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f68a707c1feb095d8c07f8a65b9f506b117d30af431cab89374357de7c11461b"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "once_cell",
+ "pin-project-lite",
+ "tokio",
+]
+
 [[package]]
 name = "async-executor"
 version = "1.8.0"
@@ -289,6 +337,21 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
 [[package]]
 name = "base64"
 version = "0.21.7"
@@ -1063,7 +1126,7 @@ dependencies = [
  "bitflags 2.4.2",
  "cexpr",
  "clang-sys",
- "itertools",
+ "itertools 0.12.1",
  "lazy_static",
  "lazycell",
  "proc-macro2",
@@ -1089,6 +1152,32 @@ version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
 
+[[package]]
+name = "bitcode_derive"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2ab471e684e2f6f3c3071361e2c30e41d7543f505658daae3ceb89c202d933e"
+dependencies = [
+ "packagemerge",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "bitcode_lightyear_patch"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c82d186a0148ef9a83a4c8129acf71485a273a5650fcb47cbf66d361615dc660"
+dependencies = [
+ "bitcode_derive",
+ "bytemuck",
+ "from_bytes_or_zeroed",
+ "residua-zigzag",
+ "serde",
+ "simdutf8",
+]
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -1214,6 +1303,9 @@ name = "bytes"
 version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "calloop"
@@ -1266,6 +1358,55 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
 
+[[package]]
+name = "chacha20"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "chacha20poly1305"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
+dependencies = [
+ "aead",
+ "chacha20",
+ "cipher",
+ "poly1305",
+ "zeroize",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets 0.52.4",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+ "zeroize",
+]
+
 [[package]]
 name = "clang-sys"
 version = "1.7.0"
@@ -1386,6 +1527,12 @@ dependencies = [
  "const_soft_float",
 ]
 
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
 [[package]]
 name = "core-foundation"
 version = "0.9.4"
@@ -1469,6 +1616,15 @@ dependencies = [
  "windows 0.54.0",
 ]
 
+[[package]]
+name = "cpufeatures"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "crc32fast"
 version = "1.4.0"
@@ -1493,6 +1649,17 @@ version = "0.8.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
 
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "rand_core",
+ "typenum",
+]
+
 [[package]]
 name = "cursor-icon"
 version = "1.1.0"
@@ -1510,6 +1677,54 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "darling"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[package]]
+name = "dashmap"
+version = "5.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
+dependencies = [
+ "cfg-if",
+ "hashbrown",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
 [[package]]
 name = "dasp_sample"
 version = "0.11.0"
@@ -1528,8 +1743,10 @@ version = "0.99.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
 dependencies = [
+ "convert_case",
  "proc-macro2",
  "quote",
+ "rustc_version",
  "syn 1.0.109",
 ]
 
@@ -1592,6 +1809,42 @@ dependencies = [
  "syn 2.0.52",
 ]
 
+[[package]]
+name = "enum_delegate"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8ea75f31022cba043afe037940d73684327e915f88f62478e778c3de914cd0a"
+dependencies = [
+ "enum_delegate_lib",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "enum_delegate_lib"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e1f6c3800b304a6be0012039e2a45a322a093539c45ab818d9e6895a39c90fe"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rand",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "enum_dispatch"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e"
+dependencies = [
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
 [[package]]
 name = "equivalent"
 version = "1.0.1"
@@ -1738,12 +1991,54 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
 
+[[package]]
+name = "from_bytes_or_zeroed"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d25934a78435889223e575c7b0fc36a290c5a312e7a7ae901f10587792e142a"
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
 [[package]]
 name = "futures-core"
 version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
 
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
 [[package]]
 name = "futures-io"
 version = "0.3.30"
@@ -1763,6 +2058,63 @@ dependencies = [
  "pin-project-lite",
 ]
 
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
+
+[[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-timer"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
 [[package]]
 name = "gethostname"
 version = "0.4.3"
@@ -1820,6 +2172,12 @@ dependencies = [
  "windows 0.54.0",
 ]
 
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
 [[package]]
 name = "gl_generator"
 version = "0.14.0"
@@ -1915,6 +2273,26 @@ dependencies = [
  "xi-unicode",
 ]
 
+[[package]]
+name = "governor"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b"
+dependencies = [
+ "cfg-if",
+ "dashmap",
+ "futures",
+ "futures-timer",
+ "no-std-compat",
+ "nonzero_ext",
+ "parking_lot",
+ "portable-atomic",
+ "quanta",
+ "rand",
+ "smallvec",
+ "spinning_top",
+]
+
 [[package]]
 name = "gpu-alloc"
 version = "0.6.0"
@@ -2025,6 +2403,29 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
 
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core 0.52.0",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
 [[package]]
 name = "icrate"
 version = "0.0.4"
@@ -2036,6 +2437,12 @@ dependencies = [
  "objc2 0.4.1",
 ]
 
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
 [[package]]
 name = "image"
 version = "0.24.9"
@@ -2085,6 +2492,24 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
 [[package]]
 name = "io-kit-sys"
 version = "0.4.0"
@@ -2095,6 +2520,12 @@ dependencies = [
  "mach2",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a9b56eb56058f43dc66e58f40a214b2ccbc9f3df51861b63d51dec7b65bc3f"
+
 [[package]]
 name = "itertools"
 version = "0.12.1"
@@ -2246,6 +2677,60 @@ dependencies = [
  "pkg-config",
 ]
 
+[[package]]
+name = "lightyear"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "410945fe163a11eee759ffbe8beff893d259e276ce1823ff63ec5b18b8e249ba"
+dependencies = [
+ "anyhow",
+ "async-compat",
+ "base64",
+ "bevy",
+ "bitcode_lightyear_patch",
+ "byteorder",
+ "bytes",
+ "cfg-if",
+ "chacha20poly1305",
+ "chrono",
+ "console_error_panic_hook",
+ "crossbeam-channel",
+ "derive_more",
+ "enum_delegate",
+ "enum_dispatch",
+ "getrandom",
+ "governor",
+ "hashbrown",
+ "instant",
+ "lightyear_macros",
+ "nonzero_ext",
+ "paste",
+ "rand",
+ "ringbuffer",
+ "seahash",
+ "self_cell",
+ "serde",
+ "thiserror",
+ "tracing",
+ "tracing-log 0.2.0",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "lightyear_macros"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acc977b2d3f117608514cc692340599953f7666235c878f02f44e58f51bd9ad0"
+dependencies = [
+ "anyhow",
+ "darling",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn 2.0.52",
+ "uuid",
+]
+
 [[package]]
 name = "linux-raw-sys"
 version = "0.4.13"
@@ -2415,6 +2900,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "no-std-compat"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
+
 [[package]]
 name = "nom"
 version = "7.1.3"
@@ -2431,6 +2922,12 @@ version = "0.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51"
 
+[[package]]
+name = "nonzero_ext"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
+
 [[package]]
 name = "ntapi"
 version = "0.4.1"
@@ -2569,6 +3066,15 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "oboe"
 version = "0.5.0"
@@ -2627,6 +3133,12 @@ version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
 [[package]]
 name = "orbclient"
 version = "0.3.47"
@@ -2651,6 +3163,15 @@ dependencies = [
  "ttf-parser",
 ]
 
+[[package]]
+name = "packagemerge"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0efcf6ee55f8f7a24333bc8d1dd0e541a6cedf903dbc07ae6479d7f8ff32ed08"
+dependencies = [
+ "itertools 0.4.19",
+]
+
 [[package]]
 name = "parking"
 version = "2.2.0"
@@ -2708,6 +3229,12 @@ version = "0.2.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
 
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
 [[package]]
 name = "piper"
 version = "0.2.1"
@@ -2752,6 +3279,23 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "poly1305"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+dependencies = [
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
+
 [[package]]
 name = "pp-rs"
 version = "0.2.1"
@@ -2761,6 +3305,12 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "presser"
 version = "0.3.1"
@@ -2791,6 +3341,21 @@ version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
 
+[[package]]
+name = "quanta"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ca0b7bac0b97248c40bb77288fc52029cf1459c0461ea1b05ee32ccf011de2c"
+dependencies = [
+ "crossbeam-utils",
+ "libc",
+ "once_cell",
+ "raw-cpuid",
+ "wasi",
+ "web-sys",
+ "winapi",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.35"
@@ -2806,12 +3371,51 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "17fd96390ed3feda12e1dfe2645ed587e0bea749e319333f104a33ff62f77a0b"
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
 [[package]]
 name = "range-alloc"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"
 
+[[package]]
+name = "raw-cpuid"
+version = "11.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1"
+dependencies = [
+ "bitflags 2.4.2",
+]
+
 [[package]]
 name = "raw-window-handle"
 version = "0.6.0"
@@ -2892,6 +3496,18 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
 
+[[package]]
+name = "residua-zigzag"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b37805477eee599a61753230f511ae94d737f69b536e468e294723ad5f1b75f"
+
+[[package]]
+name = "ringbuffer"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3df6368f71f205ff9c33c076d170dd56ebf68e8161c733c0caa07a7a5509ed53"
+
 [[package]]
 name = "rodio"
 version = "0.17.3"
@@ -2914,12 +3530,27 @@ dependencies = [
  "serde_derive",
 ]
 
+[[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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
 [[package]]
 name = "rustix"
 version = "0.38.31"
@@ -2965,6 +3596,24 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
+[[package]]
+name = "seahash"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
+
+[[package]]
+name = "self_cell"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba"
+
+[[package]]
+name = "semver"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
+
 [[package]]
 name = "serde"
 version = "1.0.197"
@@ -3017,6 +3666,12 @@ version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
 
+[[package]]
+name = "simdutf8"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
+
 [[package]]
 name = "slab"
 version = "0.4.9"
@@ -3053,6 +3708,15 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "spinning_top"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300"
+dependencies = [
+ "lock_api",
+]
+
 [[package]]
 name = "spirv"
 version = "0.3.0+sdk-1.3.268.0"
@@ -3068,6 +3732,18 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
 [[package]]
 name = "svg_fmt"
 version = "0.4.2"
@@ -3176,6 +3852,16 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
+[[package]]
+name = "tokio"
+version = "1.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
+dependencies = [
+ "backtrace",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "toml_datetime"
 version = "0.6.5"
@@ -3292,6 +3978,12 @@ dependencies = [
  "static_assertions",
 ]
 
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.12"
@@ -3316,6 +4008,16 @@ version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
 
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
 [[package]]
 name = "uuid"
 version = "1.7.0"
@@ -4000,3 +4702,9 @@ dependencies = [
  "quote",
  "syn 2.0.52",
 ]
+
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
diff --git a/Cargo.toml b/Cargo.toml
index 926f0a5..7662bce 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,10 @@ edition = "2021"
 
 [dependencies]
 bevy = { version = "0.13.0", features = ["dynamic_linking"] }
+crossbeam-channel = { version = "0.5.12" }
+lightyear = { version = "0.12.0" }
+rand = { version = "0.8.5" }
+serde = { version = "1.0.197" }
 
 [profile.dev]
 opt-level = 1
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..99ae3c7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+# agame
+
+## run locally (single player)
+
+```console
+cargo run
+```
+
+## controls
+
+-   **left mouse** teleport
diff --git a/shell.nix b/shell.nix
index 5214dde..c70c2eb 100644
--- a/shell.nix
+++ b/shell.nix
@@ -5,6 +5,7 @@ pkgs.mkShell rec {
     pkgs.cargo
     pkgs.pkg-config
     pkgs.rustc
+    pkgs.rustfmt
   ];
   buildInputs = [
     pkgs.alsa-lib
diff --git a/src/client.rs b/src/client.rs
new file mode 100644
index 0000000..b36bba9
--- /dev/null
+++ b/src/client.rs
@@ -0,0 +1,82 @@
+use crate::client::network::*;
+use crate::protocol::*;
+use crate::shared::*;
+use bevy::input::mouse::MouseButton;
+use bevy::prelude::*;
+use bevy::sprite::{MaterialMesh2dBundle, Mesh2dHandle};
+use lightyear::client::input::InputSystemSet;
+use lightyear::prelude::*;
+
+mod network;
+
+pub fn main(transport: TransportConfig) {
+    App::new()
+        .add_plugins(DefaultPlugins)
+        .add_plugins(ClientPlugin { transport })
+        .run();
+}
+
+struct ClientPlugin {
+    pub transport: TransportConfig,
+}
+
+impl Plugin for ClientPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_plugins(NetworkPlugin {
+            transport: self.transport.clone(),
+        })
+        .add_systems(Startup, setup)
+        .add_systems(Update, spawn_players)
+        .add_systems(Update, move_players)
+        .add_systems(
+            FixedPreUpdate,
+            buffer_input.in_set(InputSystemSet::BufferInputs),
+        );
+    }
+}
+
+fn setup(mut client: ClientMut, mut commands: Commands) {
+    commands.spawn(Camera2dBundle::default());
+    client.connect().unwrap();
+}
+
+fn spawn_players(
+    mut commands: Commands,
+    mut materials: ResMut<Assets<ColorMaterial>>,
+    mut meshes: ResMut<Assets<Mesh>>,
+    players: Query<(Entity, &PlayerPosition, &PlayerColor), Added<PlayerId>>,
+) {
+    for (entity, position, color) in players.iter() {
+        commands.entity(entity).insert(MaterialMesh2dBundle {
+            mesh: Mesh2dHandle(meshes.add(Circle { radius: 10. })),
+            material: materials.add(color.0),
+            transform: Transform::from_xyz(position.0.x, position.0.y, 0.),
+            ..Default::default()
+        });
+    }
+}
+
+fn move_players(mut players: Query<(&mut Transform, &PlayerPosition), Changed<PlayerPosition>>) {
+    for (mut transform, player_position) in players.iter_mut() {
+        transform.translation = Vec3::new(player_position.0.x, player_position.0.y, 0.);
+    }
+}
+
+fn buffer_input(
+    cameras: Query<(&Camera, &GlobalTransform)>,
+    mouse_input: Res<ButtonInput<MouseButton>>,
+    mut client: ClientMut,
+    windows: Query<&Window>,
+) {
+    let window = windows.single();
+    let (camera, camera_transform) = cameras.single();
+    if mouse_input.just_pressed(MouseButton::Left) {
+        if let Some(cursor_position) = window.cursor_position() {
+            if let Some(world_position) =
+                camera.viewport_to_world_2d(camera_transform, cursor_position)
+            {
+                client.add_input(Inputs::Teleport(world_position));
+            }
+        }
+    }
+}
diff --git a/src/client/network.rs b/src/client/network.rs
new file mode 100644
index 0000000..4d1a128
--- /dev/null
+++ b/src/client/network.rs
@@ -0,0 +1,35 @@
+use crate::protocol::*;
+use crate::shared::*;
+use bevy::prelude::*;
+use lightyear::client::config::*;
+use lightyear::client::plugin::ClientPlugin;
+use lightyear::client::plugin::PluginConfig;
+use lightyear::client::resource::Authentication;
+use lightyear::prelude::client::NetConfig;
+use lightyear::prelude::*;
+use lightyear::transport::LOCAL_SOCKET;
+
+pub struct NetworkPlugin {
+    pub transport: TransportConfig,
+}
+
+impl Plugin for NetworkPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_plugins(ClientPlugin::new(PluginConfig::new(
+            ClientConfig {
+                net: NetConfig::Netcode {
+                    config: Default::default(),
+                    auth: Authentication::Manual {
+                        server_addr: LOCAL_SOCKET,
+                        client_id: CLIENT_ID,
+                        private_key: KEY,
+                        protocol_id: PROTOCOL_ID,
+                    },
+                    io: IoConfig::from_transport(self.transport.clone()),
+                },
+                ..Default::default()
+            },
+            protocol(),
+        )));
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index e7a11a9..cdd7c17 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,27 @@
+use lightyear::transport::io::TransportConfig;
+use shared::SERVER_PORT;
+use std::net::Ipv4Addr;
+use std::net::SocketAddr;
+use std::thread;
+
+mod client;
+mod protocol;
+mod server;
+mod shared;
+
 fn main() {
-    println!("Hello, world!");
+    let (from_server_send, from_server_recv) = crossbeam_channel::unbounded();
+    let (to_server_send, to_server_recv) = crossbeam_channel::unbounded();
+
+    thread::spawn(|| {
+        let server_addr = SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), SERVER_PORT);
+        server::main(TransportConfig::Channels {
+            channels: [(server_addr, to_server_recv, from_server_send)].to_vec(),
+        });
+    });
+
+    client::main(TransportConfig::LocalChannel {
+        recv: from_server_recv,
+        send: to_server_send,
+    });
 }
diff --git a/src/protocol.rs b/src/protocol.rs
new file mode 100644
index 0000000..a9e2086
--- /dev/null
+++ b/src/protocol.rs
@@ -0,0 +1,47 @@
+use crate::shared::*;
+use bevy::prelude::*;
+use lightyear::prelude::*;
+use serde::Deserialize;
+use serde::Serialize;
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
+pub enum Inputs {
+    Teleport(Vec2),
+    None,
+}
+impl UserAction for Inputs {}
+
+#[derive(Message, Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct Message1(pub usize);
+
+#[message_protocol(protocol = "MyProtocol")]
+pub enum Messages {
+    Message1(Message1),
+}
+
+#[component_protocol(protocol = "MyProtocol")]
+pub enum Components {
+    #[sync(once)]
+    PlayerId(PlayerId),
+    PlayerPosition(PlayerPosition),
+    PlayerColor(PlayerColor),
+}
+
+#[derive(Channel)]
+struct Channel1;
+
+protocolize! {
+    Self = MyProtocol,
+    Message = Messages,
+    Component = Components,
+    Input = Inputs,
+}
+
+pub fn protocol() -> MyProtocol {
+    let mut protocol = MyProtocol::default();
+    protocol.add_channel::<Channel1>(ChannelSettings {
+        mode: ChannelMode::OrderedReliable(ReliableSettings::default()),
+        ..Default::default()
+    });
+    protocol
+}
diff --git a/src/server.rs b/src/server.rs
new file mode 100644
index 0000000..3a7dd40
--- /dev/null
+++ b/src/server.rs
@@ -0,0 +1,83 @@
+use crate::protocol::*;
+use crate::server::network::*;
+use crate::shared::*;
+use bevy::prelude::*;
+use bevy::utils::HashMap;
+use lightyear::prelude::*;
+use rand::Rng;
+
+mod network;
+
+#[derive(Resource, Default)]
+struct EntityMap(HashMap<ClientId, Entity>);
+
+pub fn main(transport: TransportConfig) {
+    App::new()
+        .add_plugins(MinimalPlugins)
+        .add_plugins(ServerPlugin { transport })
+        .run();
+}
+
+struct ServerPlugin {
+    pub transport: TransportConfig,
+}
+
+impl Plugin for ServerPlugin {
+    fn build(&self, app: &mut App) {
+        app.insert_resource(EntityMap::default())
+            .add_plugins(NetworkPlugin {
+                transport: self.transport.clone(),
+            })
+            .add_systems(Update, connections)
+            .add_systems(FixedUpdate, player_input);
+    }
+}
+
+fn connections(
+    mut commands: Commands,
+    mut connects: EventReader<server::ConnectEvent>,
+    mut disconnects: EventReader<server::DisconnectEvent>,
+    mut entity_map: ResMut<EntityMap>,
+) {
+    let mut rng = rand::thread_rng();
+    for connection in connects.read() {
+        let client_id = connection.context();
+        info!("connected: {:?}", client_id);
+        let position = Vec2::new(
+            50. * rng.gen_range(-2..=2) as f32,
+            50. * rng.gen_range(-2..=2) as f32,
+        );
+        let color = Color::hsl(360. * rng.gen_range(0..=15) as f32 / 16., 0.95, 0.7);
+        let entity = commands.spawn(PlayerBundle::new(*client_id, position, color));
+        entity_map.0.insert(*client_id, entity.id());
+    }
+    for connection in disconnects.read() {
+        let client_id = connection.context();
+        info!("disconnected: {:?}", client_id);
+        if let Some(entity_id) = entity_map.0.remove(client_id) {
+            commands.entity(entity_id).despawn();
+        }
+    }
+}
+
+fn player_input(
+    entity_map: Res<EntityMap>,
+    mut input_reader: EventReader<server::InputEvent<Inputs>>,
+    mut positions: Query<&mut PlayerPosition>,
+) {
+    for input in input_reader.read() {
+        let client_id = input.context();
+        if let Some(input) = input.input() {
+            if let Some(entity_id) = entity_map.0.get(client_id) {
+                match input {
+                    Inputs::Teleport(new_position) => {
+                        if let Ok(mut position) = positions.get_mut(*entity_id) {
+                            position.0 = *new_position;
+                        }
+                    }
+                    _ => {}
+                }
+            }
+        }
+    }
+}
diff --git a/src/server/network.rs b/src/server/network.rs
new file mode 100644
index 0000000..0beead7
--- /dev/null
+++ b/src/server/network.rs
@@ -0,0 +1,31 @@
+use crate::protocol::*;
+use crate::shared::*;
+use bevy::prelude::*;
+use lightyear::prelude::server::NetConfig;
+use lightyear::prelude::*;
+use lightyear::server::config::*;
+use lightyear::server::plugin::PluginConfig;
+use lightyear::server::plugin::ServerPlugin;
+
+pub struct NetworkPlugin {
+    pub transport: TransportConfig,
+}
+
+impl Plugin for NetworkPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_plugins(ServerPlugin::new(PluginConfig::new(
+            ServerConfig {
+                net: [NetConfig::Netcode {
+                    config: NetcodeConfig::default()
+                        .with_protocol_id(PROTOCOL_ID)
+                        .with_key(KEY),
+                    io: IoConfig::from_transport(self.transport.clone()),
+                }]
+                .to_vec(),
+                ping: PingConfig::default(),
+                ..Default::default()
+            },
+            protocol(),
+        )));
+    }
+}
diff --git a/src/shared.rs b/src/shared.rs
new file mode 100644
index 0000000..b907b57
--- /dev/null
+++ b/src/shared.rs
@@ -0,0 +1,40 @@
+use crate::protocol::Replicate;
+use bevy::prelude::*;
+use lightyear::prelude::*;
+use serde::Deserialize;
+use serde::Serialize;
+
+pub const CLIENT_ID: u64 = 0;
+pub const KEY: [u8; 32] = [
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+];
+pub const PROTOCOL_ID: u64 = 0;
+pub const SERVER_PORT: u16 = 16384;
+
+#[derive(Bundle)]
+pub struct PlayerBundle {
+    id: PlayerId,
+    position: PlayerPosition,
+    color: PlayerColor,
+    replicate: Replicate,
+}
+
+#[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct PlayerId(pub ClientId);
+
+#[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct PlayerPosition(pub Vec2);
+
+#[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct PlayerColor(pub Color);
+
+impl PlayerBundle {
+    pub fn new(id: ClientId, position: Vec2, color: Color) -> Self {
+        PlayerBundle {
+            id: PlayerId(id),
+            position: PlayerPosition(position),
+            color: PlayerColor(color),
+            replicate: Replicate::default(),
+        }
+    }
+}
-- 
cgit v1.2.3