diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ccfd6ea8a..193c05eb1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v4 - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 - - run: cargo clippy --all-targets --all-features --no-default-features + - run: cargo clippy --all-targets --all-features build_and_test: name: Build project and test runs-on: ${{ matrix.os }} @@ -77,4 +77,4 @@ jobs: - uses: actions/checkout@v4 - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 - - run: cargo clippy --release --all-targets --all-features --no-default-features + - run: cargo clippy --release --all-targets --all-features diff --git a/.gitignore b/.gitignore index 9016d3545..4b08876e5 100644 --- a/.gitignore +++ b/.gitignore @@ -74,7 +74,6 @@ gradle-app.setting *.zip *.tar.gz *.rar - # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 06bed628c..3599738cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,6 +29,8 @@ There are several ways you can contribute to Pumpkin: The Documentation of Pumpkin can be found at +**Tip: [typos](https://github.com/crate-ci/typos) is a great Project to detect and automatically fix typos + ### Coding Guidelines - **Working with Tokio and Rayon:** diff --git a/Cargo.toml b/Cargo.toml index dd3e6ba0e..7122a3541 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,6 @@ members = [ version = "0.1.0" edition = "2021" -[profile.dev.package."*"] -opt-level = 3 [profile.dev] opt-level = 1 @@ -61,5 +59,3 @@ uuid = { version = "1.11.0", features = ["serde", "v3", "v4"] } derive_more = { version = "1.0.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" - -itertools = "0.13.0" diff --git a/Dockerfile b/Dockerfile index 57db48cf1..7fc544257 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM rust:1-alpine3.20 AS builder +FROM rust:1-alpine3.21 AS builder ARG GIT_VERSION=Docker ENV GIT_VERSION=$GIT_VERSION -ENV RUSTFLAGS="-C target-feature=-crt-static -C target-cpu=native" +ENV RUSTFLAGS="-C target-feature=-crt-static" RUN apk add --no-cache musl-dev WORKDIR /pumpkin @@ -16,7 +16,7 @@ RUN --mount=type=cache,sharing=private,target=/pumpkin/target \ # strip debug symbols from binary RUN strip pumpkin.release -FROM alpine:3.20 +FROM alpine:3.21 # Identifying information for registries like ghcr.io LABEL org.opencontainers.image.source=https://github.com/Snowiiii/Pumpkin diff --git a/README.md b/README.md index d2673e824..e62595150 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ -[Pumpkin](https://snowiiii.github.io/Pumpkin/) is a Minecraft server built entirely in Rust, offering a fast, efficient, +[Pumpkin](https://snowiiii.github.io/Pumpkin-Website/) is a Minecraft server built entirely in Rust, offering a fast, efficient, and customizable experience. It prioritizes performance and player enjoyment while adhering to the core mechanics of the game. ![image](https://github.com/user-attachments/assets/7e2e865e-b150-4675-a2d5-b52f9900378e) @@ -88,7 +88,7 @@ Check out our [Github Project](https://github.com/users/Snowiiii/projects/12/vie ## How to run -See our [Quick Start](https://snowiiii.github.io/Pumpkin/about/quick-start.html) Guide to get Pumpkin running +See our [Quick Start](https://snowiiii.github.io/Pumpkin-Website/about/quick-start.html) Guide to get Pumpkin running ## Contributions @@ -96,7 +96,7 @@ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) ## Docs -The Documentation of Pumpkin can be found at https://snowiiii.github.io/Pumpkin/ +The Documentation of Pumpkin can be found at https://snowiiii.github.io/Pumpkin-Website/ ## Communication diff --git a/assets/entities.json b/assets/entities.json new file mode 100644 index 000000000..ac4995f5d --- /dev/null +++ b/assets/entities.json @@ -0,0 +1 @@ +{"acacia_boat":{"id":0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"acacia_chest_boat":{"id":1,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"allay":{"id":2,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.35,0.6]},"area_effect_cloud":{"id":3,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[6.0,0.5]},"armadillo":{"id":4,"max_health":12.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,0.65]},"armor_stand":{"id":5,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,1.975]},"arrow":{"id":6,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"axolotl":{"id":7,"max_health":14.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.75,0.42]},"bamboo_chest_raft":{"id":8,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"bamboo_raft":{"id":9,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"bat":{"id":10,"max_health":6.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.9]},"bee":{"id":11,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,0.6]},"birch_boat":{"id":12,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"birch_chest_boat":{"id":13,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"blaze":{"id":14,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.6,1.8]},"block_display":{"id":15,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"bogged":{"id":16,"max_health":16.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.99]},"breeze":{"id":17,"max_health":30.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.77]},"breeze_wind_charge":{"id":18,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.3125,0.3125]},"camel":{"id":19,"max_health":32.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.7,2.375]},"cat":{"id":20,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,0.7]},"cave_spider":{"id":21,"max_health":12.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,0.5]},"cherry_boat":{"id":22,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"cherry_chest_boat":{"id":23,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"chest_minecart":{"id":24,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"chicken":{"id":25,"max_health":4.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.4,0.7]},"cod":{"id":26,"max_health":3.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.3]},"command_block_minecart":{"id":27,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"cow":{"id":28,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.4]},"creaking":{"id":29,"max_health":1.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,2.7]},"creeper":{"id":30,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.7]},"dark_oak_boat":{"id":31,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"dark_oak_chest_boat":{"id":32,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"dolphin":{"id":33,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,0.6]},"donkey":{"id":34,"max_health":53.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.5]},"dragon_fireball":{"id":35,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.0,1.0]},"drowned":{"id":36,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"egg":{"id":37,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"elder_guardian":{"id":38,"max_health":80.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.9975,1.9975]},"enderman":{"id":39,"max_health":40.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,2.9]},"endermite":{"id":40,"max_health":8.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.4,0.3]},"ender_dragon":{"id":41,"max_health":200.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[16.0,8.0]},"ender_pearl":{"id":42,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"end_crystal":{"id":43,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[2.0,2.0]},"evoker":{"id":44,"max_health":24.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"evoker_fangs":{"id":45,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.8]},"experience_bottle":{"id":46,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"experience_orb":{"id":47,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"eye_of_ender":{"id":48,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"falling_block":{"id":49,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.98,0.98]},"fireball":{"id":50,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.0,1.0]},"firework_rocket":{"id":51,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"fox":{"id":52,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,0.7]},"frog":{"id":53,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"furnace_minecart":{"id":54,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"ghast":{"id":55,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[4.0,4.0]},"giant":{"id":56,"max_health":100.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[3.6,12.0]},"glow_item_frame":{"id":57,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"glow_squid":{"id":58,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.8,0.8]},"goat":{"id":59,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.3]},"guardian":{"id":60,"max_health":30.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.85,0.85]},"hoglin":{"id":61,"max_health":40.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.4]},"hopper_minecart":{"id":62,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"horse":{"id":63,"max_health":53.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.6]},"husk":{"id":64,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"illusioner":{"id":65,"max_health":32.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"interaction":{"id":66,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"iron_golem":{"id":67,"max_health":100.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.4,2.7]},"item":{"id":68,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"item_display":{"id":69,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"item_frame":{"id":70,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"jungle_boat":{"id":71,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"jungle_chest_boat":{"id":72,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"leash_knot":{"id":73,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.375,0.5]},"lightning_bolt":{"id":74,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"llama":{"id":75,"max_health":53.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.87]},"llama_spit":{"id":76,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"magma_cube":{"id":77,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.52,0.52]},"mangrove_boat":{"id":78,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"mangrove_chest_boat":{"id":79,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"marker":{"id":80,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"minecart":{"id":81,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"mooshroom":{"id":82,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.4]},"mule":{"id":83,"max_health":53.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.6]},"oak_boat":{"id":84,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"oak_chest_boat":{"id":85,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"ocelot":{"id":86,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,0.7]},"ominous_item_spawner":{"id":87,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"painting":{"id":88,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"pale_oak_boat":{"id":89,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"pale_oak_chest_boat":{"id":90,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"panda":{"id":91,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3,1.25]},"parrot":{"id":92,"max_health":6.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.9]},"phantom":{"id":93,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,0.5]},"pig":{"id":94,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,0.9]},"piglin":{"id":95,"max_health":16.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"piglin_brute":{"id":96,"max_health":50.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"pillager":{"id":97,"max_health":24.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"polar_bear":{"id":98,"max_health":30.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.4,1.4]},"potion":{"id":99,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"pufferfish":{"id":100,"max_health":3.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,0.7]},"rabbit":{"id":101,"max_health":3.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.4,0.5]},"ravager":{"id":102,"max_health":100.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.95,2.2]},"salmon":{"id":103,"max_health":3.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,0.4]},"sheep":{"id":104,"max_health":8.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.3]},"shulker":{"id":105,"max_health":30.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[1.0,1.0]},"shulker_bullet":{"id":106,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.3125,0.3125]},"silverfish":{"id":107,"max_health":8.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.4,0.3]},"skeleton":{"id":108,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.99]},"skeleton_horse":{"id":109,"max_health":15.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.6]},"slime":{"id":110,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.52,0.52]},"small_fireball":{"id":111,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.3125,0.3125]},"sniffer":{"id":112,"max_health":14.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.9,1.75]},"snowball":{"id":113,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"snow_golem":{"id":114,"max_health":4.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,1.9]},"spawner_minecart":{"id":115,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"spectral_arrow":{"id":116,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"spider":{"id":117,"max_health":16.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.4,0.9]},"spruce_boat":{"id":118,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"spruce_chest_boat":{"id":119,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"squid":{"id":120,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.8,0.8]},"stray":{"id":121,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.99]},"strider":{"id":122,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.9,1.7]},"tadpole":{"id":123,"max_health":6.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.4,0.3]},"text_display":{"id":124,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"tnt":{"id":125,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.98,0.98]},"tnt_minecart":{"id":126,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"trader_llama":{"id":127,"max_health":53.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.87]},"trident":{"id":128,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"tropical_fish":{"id":129,"max_health":3.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.4]},"turtle":{"id":130,"max_health":30.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.2,0.4]},"vex":{"id":131,"max_health":14.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.4,0.8]},"villager":{"id":132,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"vindicator":{"id":133,"max_health":24.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"wandering_trader":{"id":134,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"warden":{"id":135,"max_health":500.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.9,2.9]},"wind_charge":{"id":136,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.3125,0.3125]},"witch":{"id":137,"max_health":26.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"wither":{"id":138,"max_health":300.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.9,3.5]},"wither_skeleton":{"id":139,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.7,2.4]},"wither_skull":{"id":140,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.3125,0.3125]},"wolf":{"id":141,"max_health":8.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,0.85]},"zoglin":{"id":142,"max_health":40.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[1.3964844,1.4]},"zombie":{"id":143,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"zombie_horse":{"id":144,"max_health":15.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.6]},"zombie_villager":{"id":145,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"zombified_piglin":{"id":146,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.6,1.95]},"fishing_bobber":{"id":148,"attackable":true,"summonable":false,"fire_immune":false,"dimension":[0.25,0.25]}} \ No newline at end of file diff --git a/extractor/build.gradle.kts b/extractor/build.gradle.kts index 0fccb5cc8..944a30d35 100644 --- a/extractor/build.gradle.kts +++ b/extractor/build.gradle.kts @@ -2,8 +2,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "2.0.21" - id("fabric-loom") version "1.8.9" + kotlin("jvm") version "2.1.0" + id("fabric-loom") version "1.9-SNAPSHOT" id("maven-publish") } diff --git a/extractor/gradle.properties b/extractor/gradle.properties index f028ef93c..880c97721 100644 --- a/extractor/gradle.properties +++ b/extractor/gradle.properties @@ -4,13 +4,11 @@ org.gradle.parallel=true # Fabric Properties # check these on https://modmuss50.me/fabric.html minecraft_version=1.21.4 -yarn_mappings=1.21.4+build.1 +yarn_mappings=1.21.4+build.2 loader_version=0.16.9 -kotlin_loader_version=1.12.3+kotlin.2.0.21 +kotlin_loader_version=1.13.0+kotlin.2.1.0 # Mod Properties mod_version=1.0-SNAPSHOT maven_group=de.snowii archives_base_name=extractor -# Dependencies -# check this on https://modmuss50.me/fabric.html -fabric_version=0.110.5+1.21.4 +fabric_version=0.113.0+1.21.4 diff --git a/extractor/gradle/wrapper/gradle-wrapper.properties b/extractor/gradle/wrapper/gradle-wrapper.properties index e2847c820..cea7a793a 100644 --- a/extractor/gradle/wrapper/gradle-wrapper.properties +++ b/extractor/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/extractor/gradlew b/extractor/gradlew index f5feea6d6..057afac53 100755 --- a/extractor/gradlew +++ b/extractor/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -203,7 +202,7 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, diff --git a/extractor/gradlew.bat b/extractor/gradlew.bat index 9b42019c7..6a90cee9b 100644 --- a/extractor/gradlew.bat +++ b/extractor/gradlew.bat @@ -36,7 +36,7 @@ set APP_HOME=%DIRNAME% for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" +set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/extractor/src/main/kotlin/de/snowii/extractor/Extractor.kt b/extractor/src/main/kotlin/de/snowii/extractor/Extractor.kt index d16e89b6d..2f47fe63c 100644 --- a/extractor/src/main/kotlin/de/snowii/extractor/Extractor.kt +++ b/extractor/src/main/kotlin/de/snowii/extractor/Extractor.kt @@ -1,6 +1,5 @@ package de.snowii.extractor -import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonElement import de.snowii.extractor.extractors.* @@ -31,6 +30,7 @@ class Extractor : ModInitializer { Packets(), Screens(), Tags(), + Entities(), Items(), Blocks(), Tests(), diff --git a/extractor/src/main/kotlin/de/snowii/extractor/extractors/Entities.kt b/extractor/src/main/kotlin/de/snowii/extractor/extractors/Entities.kt new file mode 100644 index 000000000..475047118 --- /dev/null +++ b/extractor/src/main/kotlin/de/snowii/extractor/extractors/Entities.kt @@ -0,0 +1,42 @@ +package de.snowii.extractor.extractors + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import de.snowii.extractor.Extractor +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.SpawnReason +import net.minecraft.registry.Registries +import net.minecraft.server.MinecraftServer + +class Entities : Extractor.Extractor { + override fun fileName(): String { + return "entities.json" + } + + override fun extract(server: MinecraftServer): JsonElement { + val entitiesJson = JsonObject() + for (entityType in Registries.ENTITY_TYPE) { + val entity = entityType.create(server.overworld!!, SpawnReason.NATURAL) ?: continue + val entityJson = JsonObject() + entityJson.addProperty("id", Registries.ENTITY_TYPE.getRawId(entityType)) + if (entity is LivingEntity) { + entityJson.addProperty("max_health", entity.maxHealth) + + } + entityJson.addProperty("attackable", entity.isAttackable) + entityJson.addProperty("summonable", entityType.isSummonable) + entityJson.addProperty("fire_immune", entityType.isFireImmune) + val dimension = JsonArray() + dimension.add(entityType.dimensions.width) + dimension.add(entityType.dimensions.height) + entityJson.add("dimension", dimension) + + entitiesJson.add( + Registries.ENTITY_TYPE.getId(entityType).path, entityJson + ) + } + + return entitiesJson + } +} diff --git a/extractor/src/main/kotlin/de/snowii/extractor/extractors/Tests.kt b/extractor/src/main/kotlin/de/snowii/extractor/extractors/Tests.kt index ac81ec76c..e75a1797d 100644 --- a/extractor/src/main/kotlin/de/snowii/extractor/extractors/Tests.kt +++ b/extractor/src/main/kotlin/de/snowii/extractor/extractors/Tests.kt @@ -1,8 +1,5 @@ - package de.snowii.extractor.extractors -import com.google.gson.Gson -import com.google.gson.GsonBuilder import com.google.gson.JsonArray import com.google.gson.JsonElement import de.snowii.extractor.Extractor @@ -11,129 +8,127 @@ import net.minecraft.block.BlockState import net.minecraft.block.Blocks import net.minecraft.registry.BuiltinRegistries import net.minecraft.registry.RegistryKeys -import net.minecraft.registry.RegistryWrapper -import net.minecraft.registry.RegistryWrapper.WrapperLookup -import net.minecraft.registry.entry.RegistryEntry.Reference import net.minecraft.server.MinecraftServer -import net.minecraft.util.math.noise.DoublePerlinNoiseSampler.NoiseParameters import net.minecraft.util.math.ChunkPos -import net.minecraft.world.gen.chunk.AquiferSampler -import net.minecraft.world.gen.chunk.Blender -import net.minecraft.world.gen.chunk.ChunkGeneratorSettings -import net.minecraft.world.gen.chunk.ChunkNoiseSampler -import net.minecraft.world.gen.chunk.GenerationShapeConfig -import net.minecraft.world.gen.densityfunction.DensityFunction.NoisePos; -import net.minecraft.world.gen.densityfunction.DensityFunction.EachApplier; +import net.minecraft.world.gen.chunk.* +import net.minecraft.world.gen.densityfunction.DensityFunction.EachApplier +import net.minecraft.world.gen.densityfunction.DensityFunction.NoisePos import net.minecraft.world.gen.densityfunction.DensityFunctionTypes import net.minecraft.world.gen.noise.NoiseConfig - -import java.lang.reflect.Method -import java.util.Arrays -import kotlin.reflect.full.createType -import kotlin.reflect.full.declaredFunctions -import kotlin.reflect.jvm.javaMethod import kotlin.reflect.KFunction +import kotlin.reflect.full.declaredFunctions class Tests : Extractor.Extractor { override fun fileName(): String = "chunk.json" - private fun createFluidLevelSampler(settings: ChunkGeneratorSettings): AquiferSampler.FluidLevelSampler { - val fluidLevel = AquiferSampler.FluidLevel(-54, Blocks.LAVA.getDefaultState()); - val i = settings.seaLevel(); - val fluidLevel2 = AquiferSampler.FluidLevel(i, settings.defaultFluid()); - return AquiferSampler.FluidLevelSampler {_, y, _ -> if (y < Math.min(-54, i)) fluidLevel else fluidLevel2}; - } - - private fun get_index(config: GenerationShapeConfig, x: Int, y: Int, z: Int): Int { - if (x < 0 || y < 0 || z < 0) { - System.err.println("Bad local pos"); - System.exit(1); - } - return config.height() * 16 * x + 16 * y + z - } - - // This is basically just what NoiseChunkGenerator is doing - private fun populate_noise(start_x: Int, start_z: Int, sampler: ChunkNoiseSampler, config: GenerationShapeConfig, settings: ChunkGeneratorSettings): IntArray? { - val result = IntArray(16 * 16 * config.height()) - - for (method: KFunction<*> in sampler::class.declaredFunctions) { - if (method.name.equals("sampleBlockState")) { - sampler.sampleStartDensity() - val k = config.horizontalCellBlockCount() - val l = config.verticalCellBlockCount() - - val m = 16 / k - val n = 16 / k + private fun createFluidLevelSampler(settings: ChunkGeneratorSettings): AquiferSampler.FluidLevelSampler { + val fluidLevel = AquiferSampler.FluidLevel(-54, Blocks.LAVA.defaultState) + val i = settings.seaLevel() + val fluidLevel2 = AquiferSampler.FluidLevel(i, settings.defaultFluid()) + return AquiferSampler.FluidLevelSampler { _, y, _ -> if (y < Math.min(-54, i)) fluidLevel else fluidLevel2 } + } - val cellHeight = config.height() / l - val minimumCellY = config.minimumY() / l + private fun get_index(config: GenerationShapeConfig, x: Int, y: Int, z: Int): Int { + if (x < 0 || y < 0 || z < 0) { + System.err.println("Bad local pos") + System.exit(1) + } + return config.height() * 16 * x + 16 * y + z + } - for (o in 0.. in sampler::class.declaredFunctions) { + if (method.name.equals("sampleBlockState")) { + sampler.sampleStartDensity() + val k = config.horizontalCellBlockCount() + val l = config.verticalCellBlockCount() + + val m = 16 / k + val n = 16 / k + + val cellHeight = config.height() / l + val minimumCellY = config.minimumY() / l + + for (o in 0.. + val seed = 0L + val chunk_pos = ChunkPos(7, 4) + + val lookup = BuiltinRegistries.createWrapperLookup() + val wrapper = lookup.getOrThrow(RegistryKeys.CHUNK_GENERATOR_SETTINGS) + val noise_params = lookup.getOrThrow(RegistryKeys.NOISE_PARAMETERS) + + val ref = wrapper.getOrThrow(ChunkGeneratorSettings.OVERWORLD) + val settings = ref.value() + val config = NoiseConfig.create(settings, noise_params, seed) + + // Overworld shape config + val shape = GenerationShapeConfig(-64, 384, 1, 2) + val test_sampler = + ChunkNoiseSampler( + 16 / shape.horizontalCellBlockCount(), config, chunk_pos.startX, chunk_pos.startZ, + shape, object : DensityFunctionTypes.Beardifying { + override fun maxValue(): Double = 0.0 + override fun minValue(): Double = 0.0 + override fun sample(pos: NoisePos): Double = 0.0 + override fun fill(densities: DoubleArray, applier: EachApplier) { + densities.fill(0.0) + } + }, settings, createFluidLevelSampler(settings), Blender.getNoBlending() + ) + + val data = populate_noise(chunk_pos.startX, chunk_pos.startZ, test_sampler, shape, settings) + data?.forEach { state -> topLevelJson.add(state) } diff --git a/pumpkin-config/src/compression.rs b/pumpkin-config/src/compression.rs index 2f3835f07..7a7b22c81 100644 --- a/pumpkin-config/src/compression.rs +++ b/pumpkin-config/src/compression.rs @@ -21,7 +21,7 @@ impl Default for CompressionConfig { #[derive(Deserialize, Serialize, Clone)] #[serde(default)] -/// We have this in a Seperate struct so we can use it outside of the Config +/// We have this in a Separate struct so we can use it outside of the Config pub struct CompressionInfo { /// The compression threshold used when compression is enabled pub threshold: u32, diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index 3e1431abd..94feda0b8 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -5,8 +5,9 @@ use query::QueryConfig; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{ - fs, + env, fs, net::{Ipv4Addr, SocketAddr}, + num::NonZeroU8, path::Path, sync::LazyLock, }; @@ -36,6 +37,8 @@ mod server_links; use proxy::ProxyConfig; use resource_pack::ResourcePackConfig; +const CONFIG_ROOT_FOLDER: &str = "config/"; + pub static ADVANCED_CONFIG: LazyLock = LazyLock::new(AdvancedConfiguration::load); @@ -72,9 +75,9 @@ pub struct BasicConfiguration { /// The maximum number of players allowed on the server. Specifying `0` disables the limit. pub max_players: u32, /// The maximum view distance for players. - pub view_distance: u8, + pub view_distance: NonZeroU8, /// The maximum simulated view distance. - pub simulation_distance: u8, + pub simulation_distance: NonZeroU8, /// The default game difficulty. pub default_difficulty: Difficulty, /// The op level assign by the /op command @@ -106,8 +109,8 @@ impl Default for BasicConfiguration { server_address: SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 25565), seed: "".to_string(), max_players: 100000, - view_distance: 10, - simulation_distance: 10, + view_distance: NonZeroU8::new(10).unwrap(), + simulation_distance: NonZeroU8::new(10).unwrap(), default_difficulty: Difficulty::Normal, op_permission_level: PermissionLvl::Four, allow_nether: true, @@ -129,26 +132,32 @@ trait LoadConfiguration { where Self: Sized + Default + Serialize + DeserializeOwned, { - let path = Self::get_path(); + let exe_dir = env::current_dir().unwrap(); + let config_dir = exe_dir.join(CONFIG_ROOT_FOLDER); + if !config_dir.exists() { + log::debug!("creating new config root folder"); + fs::create_dir(&config_dir).expect("Failed to create Config root folder"); + } + let path = config_dir.join(Self::get_path()); let config = if path.exists() { - let file_content = fs::read_to_string(path) - .unwrap_or_else(|_| panic!("Couldn't read configuration file at {:?}", path)); + let file_content = fs::read_to_string(&path) + .unwrap_or_else(|_| panic!("Couldn't read configuration file at {:?}", &path)); toml::from_str(&file_content).unwrap_or_else(|err| { panic!( "Couldn't parse config at {:?}. Reason: {}. This is is proberbly caused by an Config update, Just delete the old Config and start Pumpkin again", - path, + &path, err.message() ) }) } else { let content = Self::default(); - if let Err(err) = fs::write(path, toml::to_string(&content).unwrap()) { + if let Err(err) = fs::write(&path, toml::to_string(&content).unwrap()) { warn!( "Couldn't write default config to {:?}. Reason: {}. This is is proberbly caused by an Config update, Just delete the old Config and start Pumpkin again", - path, err + &path, err ); } @@ -180,9 +189,14 @@ impl LoadConfiguration for BasicConfiguration { } fn validate(&self) { - assert!(self.view_distance >= 2, "View distance must be at least 2"); assert!( - self.view_distance <= 32, + self.view_distance + .ge(unsafe { &NonZeroU8::new_unchecked(2) }), + "View distance must be at least 2" + ); + assert!( + self.view_distance + .le(unsafe { &NonZeroU8::new_unchecked(32) }), "View distance must be less than 32" ); if self.online_mode { diff --git a/pumpkin-core/Cargo.toml b/pumpkin-core/Cargo.toml index c56368a99..31fd66e52 100644 --- a/pumpkin-core/Cargo.toml +++ b/pumpkin-core/Cargo.toml @@ -13,5 +13,3 @@ num-derive.workspace = true colored = "2" md5 = "0.7.0" - -enum_dispatch = "0.3.13" diff --git a/pumpkin-core/src/lib.rs b/pumpkin-core/src/lib.rs index e3b11ab57..8116211b1 100644 --- a/pumpkin-core/src/lib.rs +++ b/pumpkin-core/src/lib.rs @@ -9,7 +9,7 @@ pub use permission::PermissionLvl; use serde::{Deserialize, Serialize}; -#[derive(PartialEq, Serialize, Deserialize)] +#[derive(PartialEq, Serialize, Deserialize, Clone)] pub enum Difficulty { Peaceful, Easy, diff --git a/pumpkin-core/src/math/position.rs b/pumpkin-core/src/math/position.rs index 839890646..a3b82cbeb 100644 --- a/pumpkin-core/src/math/position.rs +++ b/pumpkin-core/src/math/position.rs @@ -5,7 +5,7 @@ use crate::math::vector2::Vector2; use num_traits::Euclid; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] /// Aka Block Position pub struct WorldPosition(pub Vector3); diff --git a/pumpkin-core/src/random/mod.rs b/pumpkin-core/src/random/mod.rs index 546e082ef..d52b58fde 100644 --- a/pumpkin-core/src/random/mod.rs +++ b/pumpkin-core/src/random/mod.rs @@ -1,3 +1,8 @@ +use std::{ + sync::atomic::{AtomicU64, Ordering}, + time, +}; + use legacy_rand::{LegacyRand, LegacySplitter}; use xoroshiro128::{Xoroshiro, XoroshiroSplitter}; @@ -5,6 +10,26 @@ mod gaussian; pub mod legacy_rand; pub mod xoroshiro128; +static SEED_UNIQUIFIER: AtomicU64 = AtomicU64::new(8682522807148012u64); + +pub fn get_seed() -> u64 { + let seed = SEED_UNIQUIFIER + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |val| { + Some(val.wrapping_mul(1181783497276652981u64)) + }) + // We always return Some, so there will always be an Ok result + .unwrap(); + + let nanos = time::SystemTime::now() + .duration_since(time::SystemTime::UNIX_EPOCH) + .unwrap() + .as_nanos(); + + let nano_upper = (nanos >> 8) as u64; + let nano_lower = nanos as u64; + seed ^ nano_upper ^ nano_lower +} + pub enum RandomGenerator { Xoroshiro(Xoroshiro), Legacy(LegacyRand), @@ -186,7 +211,7 @@ pub trait RandomDeriverImpl { fn split_pos(&self, x: i32, y: i32, z: i32) -> impl RandomImpl; } -fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 { +pub fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 { let l = (x.wrapping_mul(3129871) as i64) ^ ((z as i64).wrapping_mul(116129781i64)) ^ (y as i64); let l = l .wrapping_mul(l) @@ -195,7 +220,7 @@ fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 { l >> 16 } -fn java_string_hash(string: &str) -> i32 { +pub fn java_string_hash(string: &str) -> i32 { // All byte values of latin1 align with // the values of U+0000 - U+00FF making this code // equivalent to both java hash implementations @@ -254,7 +279,7 @@ mod tests { -1992287231i32, ), ("求同存异", 847053876), - // This might look wierd because hebrew is text is right to left + // This might look weird because hebrew is text is right to left ("אבְּרֵאשִׁ֖ית בָּרָ֣א אֱלֹהִ֑ים אֵ֥ת הַשָּׁמַ֖יִם וְאֵ֥ת הָאָֽרֶץ:", 1372570871), ("संस्कृत-", 1748614838), ("minecraft:offset", -920384768i32), diff --git a/pumpkin-core/src/text/click.rs b/pumpkin-core/src/text/click.rs index 0e917e15c..caa78f61d 100644 --- a/pumpkin-core/src/text/click.rs +++ b/pumpkin-core/src/text/click.rs @@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize}; pub enum ClickEvent<'a> { /// Opens a URL OpenUrl(Cow<'a, str>), + /// Opens a File + OpenFile(Cow<'a, str>), /// Works in signs, but only on the root text component RunCommand(Cow<'a, str>), /// Replaces the contents of the chat box with the text, not necessarily a diff --git a/pumpkin-core/src/text/hover.rs b/pumpkin-core/src/text/hover.rs index 867a4a3b6..35182e8e3 100644 --- a/pumpkin-core/src/text/hover.rs +++ b/pumpkin-core/src/text/hover.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use serde::{Deserialize, Serialize}; -use super::Text; +use super::TextComponent; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(tag = "action", content = "contents", rename_all = "snake_case")] @@ -28,6 +28,6 @@ pub enum HoverEvent<'a> { kind: Option>, /// Optional custom name for the entity #[serde(default, skip_serializing_if = "Option::is_none")] - name: Option>, + name: Option>>, }, } diff --git a/pumpkin-core/src/text/mod.rs b/pumpkin-core/src/text/mod.rs index 2284f60f9..f94e39563 100644 --- a/pumpkin-core/src/text/mod.rs +++ b/pumpkin-core/src/text/mod.rs @@ -14,11 +14,7 @@ pub mod color; pub mod hover; pub mod style; -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -#[serde(transparent)] -pub struct Text<'a>(pub Box>); - -// Represents a Text component +/// Represents a Text component #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "camelCase")] pub struct TextComponent<'a> { @@ -29,6 +25,7 @@ pub struct TextComponent<'a> { /// Also has `ClickEvent #[serde(flatten)] pub style: Style<'a>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] /// Extra text components pub extra: Vec>, } @@ -100,7 +97,7 @@ impl serde::Serialize for TextComponent<'_> { where S: serde::Serializer, { - serializer.serialize_bytes(self.encode().as_slice()) + serializer.serialize_bytes(&self.encode()) } } @@ -181,7 +178,7 @@ impl<'a> TextComponent<'a> { self } - pub fn encode(&self) -> Vec { + pub fn encode(&self) -> bytes::BytesMut { // TODO: Somehow fix this ugly mess #[derive(serde::Serialize)] #[serde(rename_all = "camelCase")] @@ -215,9 +212,7 @@ impl<'a> TextComponent<'a> { // dbg!(pumpkin_nbt::serializer::to_bytes_unnamed(&astruct).unwrap().to_vec()); // TODO - pumpkin_nbt::serializer::to_bytes_unnamed(&astruct) - .unwrap() - .to_vec() + pumpkin_nbt::serializer::to_bytes_unnamed(&astruct).unwrap() } } @@ -230,7 +225,7 @@ pub enum TextContent<'a> { Translate { translate: Cow<'a, str>, #[serde(default, skip_serializing_if = "Vec::is_empty")] - with: Vec>, + with: Vec>, }, /// Displays the name of one or more entities found by a selector. EntityNames { diff --git a/pumpkin-entity/src/entity_type.rs b/pumpkin-entity/src/entity_type.rs index 282ac1297..e065722fd 100644 --- a/pumpkin-entity/src/entity_type.rs +++ b/pumpkin-entity/src/entity_type.rs @@ -1,4 +1,4 @@ -// TODO +// TODO make this dynamic #[derive(Clone)] #[repr(i32)] pub enum EntityType { diff --git a/pumpkin-inventory/Cargo.toml b/pumpkin-inventory/Cargo.toml index 28beef942..79ff82f60 100644 --- a/pumpkin-inventory/Cargo.toml +++ b/pumpkin-inventory/Cargo.toml @@ -4,15 +4,15 @@ version.workspace = true edition.workspace = true [dependencies] -# For items +pumpkin-protocol = { path = "../pumpkin-protocol" } + pumpkin-world = { path = "../pumpkin-world" } pumpkin-registry = {path = "../pumpkin-registry"} pumpkin-macros = { path = "../pumpkin-macros" } +pumpkin-core = { path = "../pumpkin-core" } log.workspace = true rayon.workspace = true -itertools.workspace = true -crossbeam.workspace = true tokio.workspace = true thiserror.workspace = true diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index 5d1039a9a..d93ba3d3c 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -1,4 +1,5 @@ use crate::InventoryError; +use pumpkin_protocol::server::play::SlotActionType; use pumpkin_world::item::ItemStack; pub struct Click { @@ -7,23 +8,22 @@ pub struct Click { } impl Click { - pub fn new(mode: u8, button: i8, slot: i16) -> Result { + pub fn new(mode: SlotActionType, button: i8, slot: i16) -> Result { match mode { - 0 => Self::new_normal_click(button, slot), + SlotActionType::Pickup => Self::new_normal_click(button, slot), // Both buttons do the same here, so we omit it - 1 => Self::new_shift_click(slot), - 2 => Self::new_key_click(button, slot), - 3 => Ok(Self { + SlotActionType::QuickMove => Self::new_shift_click(slot), + SlotActionType::Swap => Self::new_key_click(button, slot), + SlotActionType::Clone => Ok(Self { click_type: ClickType::CreativePickItem, slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), }), - 4 => Self::new_drop_item(button), - 5 => Self::new_drag_item(button, slot), - 6 => Ok(Self { + SlotActionType::Throw => Self::new_drop_item(button), + SlotActionType::QuickCraft => Self::new_drag_item(button, slot), + SlotActionType::PickupAll => Ok(Self { click_type: ClickType::DoubleClick, slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), }), - _ => Err(InventoryError::InvalidPacket), } } @@ -31,7 +31,7 @@ impl Click { let slot = match slot { -999 => Slot::OutsideInventory, _ => { - let slot = slot.try_into().or(Err(InventoryError::InvalidSlot))?; + let slot = slot.try_into().unwrap_or(0); Slot::Normal(slot) } }; diff --git a/pumpkin-inventory/src/crafting.rs b/pumpkin-inventory/src/crafting.rs index 61e2a3590..643ffbd36 100644 --- a/pumpkin-inventory/src/crafting.rs +++ b/pumpkin-inventory/src/crafting.rs @@ -1,4 +1,3 @@ -use itertools::Itertools; use pumpkin_registry::{ flatten_3x3, get_tag_values, IngredientSlot, IngredientType, RecipeResult, TagCategory, RECIPES, }; @@ -76,13 +75,13 @@ fn shapeless_crafting_match( input: [[Option; 3]; 3], pattern: &[[[Option; 3]; 3]], ) -> bool { - let mut pattern = pattern + let mut pattern: Vec = pattern .iter() .flatten() .flatten() .flatten() .cloned() - .collect_vec(); + .collect(); for item in input.into_iter().flatten().flatten() { if let Some(index) = pattern.iter().enumerate().find_map(|(i, recipe_item)| { if ingredient_slot_check(recipe_item, item) { diff --git a/pumpkin-inventory/src/drag_handler.rs b/pumpkin-inventory/src/drag_handler.rs index 70336c562..4a6ea4388 100644 --- a/pumpkin-inventory/src/drag_handler.rs +++ b/pumpkin-inventory/src/drag_handler.rs @@ -1,6 +1,5 @@ use crate::container_click::MouseDragType; use crate::{Container, InventoryError}; -use itertools::Itertools; use num_traits::Euclid; use pumpkin_world::item::ItemStack; use std::collections::HashMap; @@ -73,10 +72,10 @@ impl DragHandler { Err(InventoryError::MultiplePlayersDragging)? } let mut slots = container.all_slots(); - let slots_cloned = slots + let slots_cloned: Vec> = slots .iter() .map(|stack| stack.map(|item| item.to_owned())) - .collect_vec(); + .collect(); let Some(carried_item) = maybe_carried_item else { return Ok(()); }; @@ -121,8 +120,12 @@ impl DragHandler { let changing_slots = drag.possibly_changing_slots(&slots_cloned, carried_item.item_id); let amount_of_slots = changing_slots.clone().count(); - let (amount_per_slot, remainder) = - (carried_item.item_count as usize).div_rem_euclid(&amount_of_slots); + let (amount_per_slot, remainder) = if amount_of_slots == 0 { + // TODO: please work lol + (1, 0) + } else { + (carried_item.item_count as usize).div_rem_euclid(&amount_of_slots) + }; let mut item_in_each_slot = *carried_item; item_in_each_slot.item_count = amount_per_slot as u8; changing_slots.for_each(|slot| *slots[slot] = Some(item_in_each_slot)); diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index bf56dca63..f1273b054 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -94,6 +94,13 @@ pub trait Container: Sync + Send { fn all_slots_ref(&self) -> Vec>; + fn clear_all_slots(&mut self) { + let all_slots = self.all_slots(); + for stack in all_slots { + *stack = None; + } + } + fn all_combinable_slots(&self) -> Vec> { self.all_slots_ref() } diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index fdabf9124..3025ed92f 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -1,11 +1,18 @@ use crate::crafting::check_if_matches_crafting; use crate::{Container, WindowType}; +use pumpkin_core::math::position::WorldPosition; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::ItemStack; use std::sync::Arc; use tokio::sync::Mutex; + pub struct OpenContainer { + // TODO: unique id should be here + // TODO: should this be uuid? players: Vec, container: Arc>>, + location: Option, + block: Option, } impl OpenContainer { @@ -36,16 +43,50 @@ impl OpenContainer { } } - pub fn new_empty_container(player_id: i32) -> Self { + pub fn new_empty_container( + player_id: i32, + location: Option, + block: Option, + ) -> Self { Self { players: vec![player_id], container: Arc::new(Mutex::new(Box::new(C::default()))), + location, + block, + } + } + + pub fn is_location(&self, try_position: WorldPosition) -> bool { + if let Some(location) = self.location { + location == try_position + } else { + false } } + pub async fn clear_all_slots(&self) { + self.container.lock().await.clear_all_slots(); + } + + pub fn clear_all_players(&mut self) { + self.players = vec![]; + } + pub fn all_player_ids(&self) -> Vec { self.players.clone() } + + pub fn get_location(&self) -> Option { + self.location + } + + pub async fn set_location(&mut self, location: Option) { + self.location = location; + } + + pub fn get_block(&self) -> Option { + self.block.clone() + } } #[derive(Default)] pub struct Chest([Option; 27]); @@ -139,3 +180,33 @@ impl Container for CraftingTable { }) } } + +#[derive(Default)] +pub struct Furnace { + cook: Option, + fuel: Option, + output: Option, +} + +impl Container for Furnace { + fn window_type(&self) -> &'static WindowType { + &WindowType::Furnace + } + + fn window_name(&self) -> &'static str { + "Furnace" + } + fn all_slots(&mut self) -> Vec<&mut Option> { + let mut slots = vec![&mut self.cook]; + slots.push(&mut self.fuel); + slots.push(&mut self.output); + slots + } + + fn all_slots_ref(&self) -> Vec> { + let mut slots = vec![self.cook.as_ref()]; + slots.push(self.fuel.as_ref()); + slots.push(self.output.as_ref()); + slots + } +} diff --git a/pumpkin-macros/Cargo.toml b/pumpkin-macros/Cargo.toml index 233af3bdd..9f2428886 100644 --- a/pumpkin-macros/Cargo.toml +++ b/pumpkin-macros/Cargo.toml @@ -9,7 +9,6 @@ proc-macro = true [dependencies] serde.workspace = true serde_json.workspace = true -itertools.workspace = true proc-macro2 = "1.0" quote = "1.0" diff --git a/pumpkin-nbt/src/deserializer.rs b/pumpkin-nbt/src/deserializer.rs index ae017b821..80e3b96f4 100644 --- a/pumpkin-nbt/src/deserializer.rs +++ b/pumpkin-nbt/src/deserializer.rs @@ -23,7 +23,7 @@ impl<'de, T: Buf> Deserializer<'de, T> { } } -/// Deserializes struct using Serde Deserializer from unnamed (network) NBT +/// Deserializes struct using Serde Deserializer from normal NBT pub fn from_bytes<'a, T>(s: &'a mut impl Buf) -> Result where T: Deserialize<'a>, @@ -32,20 +32,20 @@ where T::deserialize(&mut deserializer) } -pub fn from_cursor<'a, T>(cursor: &'a mut Cursor<&[u8]>) -> Result +/// Deserializes struct using Serde Deserializer from normal NBT +pub fn from_bytes_unnamed<'a, T>(s: &'a mut impl Buf) -> Result where T: Deserialize<'a>, { - let mut deserializer = Deserializer::new(cursor, true); + let mut deserializer = Deserializer::new(s, false); T::deserialize(&mut deserializer) } -/// Deserializes struct using Serde Deserializer from normal NBT -pub fn from_bytes_unnamed<'a, T>(s: &'a mut impl Buf) -> Result +pub fn from_cursor<'a, T>(cursor: &'a mut Cursor<&[u8]>) -> Result where T: Deserialize<'a>, { - let mut deserializer = Deserializer::new(s, false); + let mut deserializer = Deserializer::new(cursor, true); T::deserialize(&mut deserializer) } diff --git a/pumpkin-nbt/src/lib.rs b/pumpkin-nbt/src/lib.rs index b2be0d657..5ed48da9b 100644 --- a/pumpkin-nbt/src/lib.rs +++ b/pumpkin-nbt/src/lib.rs @@ -200,3 +200,62 @@ macro_rules! impl_array { impl_array!(IntArray, "int"); impl_array!(LongArray, "long"); impl_array!(BytesArray, "byte"); + +#[cfg(test)] +mod test { + use serde::{Deserialize, Serialize}; + + use crate::BytesArray; + use crate::IntArray; + use crate::LongArray; + use crate::{deserializer::from_bytes_unnamed, serializer::to_bytes_unnamed}; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Test { + byte: i8, + short: i16, + int: i32, + long: i64, + float: f32, + string: String, + } + + #[test] + fn test_simple_ser_de_unamed() { + let test = Test { + byte: 123, + short: 1342, + int: 4313, + long: 34, + float: 1.00, + string: "Hello test".to_string(), + }; + let mut bytes = to_bytes_unnamed(&test).unwrap(); + let recreated_struct: Test = from_bytes_unnamed(&mut bytes).unwrap(); + + assert_eq!(test, recreated_struct); + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct TestArray { + #[serde(with = "BytesArray")] + byte_array: Vec, + #[serde(with = "IntArray")] + int_array: Vec, + #[serde(with = "LongArray")] + long_array: Vec, + } + + #[test] + fn test_simple_ser_de_array() { + let test = TestArray { + byte_array: vec![0, 3, 2], + int_array: vec![13, 1321, 2], + long_array: vec![1, 0, 200301, 1], + }; + let mut bytes = to_bytes_unnamed(&test).unwrap(); + let recreated_struct: TestArray = from_bytes_unnamed(&mut bytes).unwrap(); + + assert_eq!(test, recreated_struct); + } +} diff --git a/pumpkin-protocol/Cargo.toml b/pumpkin-protocol/Cargo.toml index b2a4c7803..f04d77311 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -3,9 +3,15 @@ name = "pumpkin-protocol" version.workspace = true edition.workspace = true +[features] +default = ["packets", "query"] +packets = ["serverbound", "clientbound"] +serverbound = [] +clientbound = [] +query = [] + [dependencies] pumpkin-nbt = { path = "../pumpkin-nbt" } -pumpkin-config = { path = "../pumpkin-config" } pumpkin-macros = { path = "../pumpkin-macros" } pumpkin-world = { path = "../pumpkin-world" } pumpkin-core = { path = "../pumpkin-core" } @@ -13,7 +19,6 @@ pumpkin-core = { path = "../pumpkin-core" } uuid.workspace = true serde.workspace = true thiserror.workspace = true -itertools.workspace = true log.workspace = true tokio.workspace = true num-traits.workspace = true @@ -25,4 +30,4 @@ aes = "0.8.4" cfb8 = "0.8.1" # decryption -libdeflater = "1.22.0" \ No newline at end of file +libdeflater = "1.23.0" \ No newline at end of file diff --git a/pumpkin-protocol/src/bytebuf/deserializer.rs b/pumpkin-protocol/src/bytebuf/deserializer.rs index 829eb515e..c7f574c30 100644 --- a/pumpkin-protocol/src/bytebuf/deserializer.rs +++ b/pumpkin-protocol/src/bytebuf/deserializer.rs @@ -1,34 +1,27 @@ use std::fmt::Display; +use super::{ByteBuf, ReadingError}; +use bytes::Buf; use serde::de::{self, DeserializeSeed, SeqAccess}; -use thiserror::Error; -use super::ByteBuffer; - -pub struct Deserializer<'a> { - inner: &'a mut ByteBuffer, -} - -#[derive(Debug, Error)] -pub enum DeserializerError { - #[error("serializer error {0}")] - Message(String), +pub struct Deserializer<'a, B: Buf> { + inner: &'a mut B, } -impl de::Error for DeserializerError { +impl de::Error for ReadingError { fn custom(msg: T) -> Self { Self::Message(msg.to_string()) } } -impl<'a> Deserializer<'a> { - pub fn new(buf: &'a mut ByteBuffer) -> Self { +impl<'a, B: Buf> Deserializer<'a, B> { + pub fn new(buf: &'a mut B) -> Self { Self { inner: buf } } } -impl<'de> de::Deserializer<'de> for Deserializer<'_> { - type Error = DeserializerError; +impl<'de, B: Buf> de::Deserializer<'de> for Deserializer<'_, B> { + type Error = ReadingError; fn deserialize_any(self, _visitor: V) -> Result where @@ -43,77 +36,77 @@ impl<'de> de::Deserializer<'de> for Deserializer<'_> { where V: de::Visitor<'de>, { - visitor.visit_bool(self.inner.get_bool()?) + visitor.visit_bool(self.inner.try_get_bool()?) } fn deserialize_i8(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_i8(self.inner.get_i8()?) + visitor.visit_i8(self.inner.try_get_i8()?) } fn deserialize_i16(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_i16(self.inner.get_i16()?) + visitor.visit_i16(self.inner.try_get_i16()?) } fn deserialize_i32(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_i32(self.inner.get_i32()?) + visitor.visit_i32(self.inner.try_get_i32()?) } fn deserialize_i64(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_i64(self.inner.get_i64()?) + visitor.visit_i64(self.inner.try_get_i64()?) } fn deserialize_u8(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_u8(self.inner.get_u8()?) + visitor.visit_u8(self.inner.try_get_u8()?) } fn deserialize_u16(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_u16(self.inner.get_u16()?) + visitor.visit_u16(self.inner.try_get_u16()?) } fn deserialize_u32(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_u32(self.inner.get_u32()?) + visitor.visit_u32(self.inner.try_get_u32()?) } fn deserialize_u64(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_u64(self.inner.get_u64()?) + visitor.visit_u64(self.inner.try_get_u64()?) } fn deserialize_f32(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_f32(self.inner.get_f32()?) + visitor.visit_f32(self.inner.try_get_f32()?) } fn deserialize_f64(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_f64(self.inner.get_f64()?) + visitor.visit_f64(self.inner.try_get_f64()?) } fn deserialize_char(self, _visitor: V) -> Result @@ -127,16 +120,14 @@ impl<'de> de::Deserializer<'de> for Deserializer<'_> { where V: de::Visitor<'de>, { - let string = self.inner.get_string()?; - visitor.visit_str(&string) + visitor.visit_str(&self.inner.try_get_string()?) } fn deserialize_string(self, visitor: V) -> Result where V: de::Visitor<'de>, { - let string = self.inner.get_string()?; - visitor.visit_str(&string) + visitor.visit_str(&self.inner.try_get_string()?) } fn deserialize_bytes(self, _visitor: V) -> Result @@ -193,12 +184,12 @@ impl<'de> de::Deserializer<'de> for Deserializer<'_> { where V: de::Visitor<'de>, { - struct Access<'a, 'b> { - deserializer: &'a mut Deserializer<'b>, + struct Access<'a, 'b, B: Buf> { + deserializer: &'a mut Deserializer<'b, B>, } - impl<'de, 'a, 'b: 'a> SeqAccess<'de> for Access<'a, 'b> { - type Error = DeserializerError; + impl<'de, 'a, 'b: 'a, B: Buf> SeqAccess<'de> for Access<'a, 'b, B> { + type Error = ReadingError; fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> where @@ -225,13 +216,13 @@ impl<'de> de::Deserializer<'de> for Deserializer<'_> { where V: de::Visitor<'de>, { - struct Access<'a, 'b> { - deserializer: &'a mut Deserializer<'b>, + struct Access<'a, 'b, B: Buf> { + deserializer: &'a mut Deserializer<'b, B>, len: usize, } - impl<'de, 'a, 'b: 'a> SeqAccess<'de> for Access<'a, 'b> { - type Error = DeserializerError; + impl<'de, 'a, 'b: 'a, B: Buf> SeqAccess<'de> for Access<'a, 'b, B> { + type Error = ReadingError; fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> where diff --git a/pumpkin-protocol/src/bytebuf/mod.rs b/pumpkin-protocol/src/bytebuf/mod.rs index 3b46f2f90..edfa15c79 100644 --- a/pumpkin-protocol/src/bytebuf/mod.rs +++ b/pumpkin-protocol/src/bytebuf/mod.rs @@ -1,418 +1,395 @@ -use crate::{BitSet, FixedBitSet, VarInt, VarLongType}; -use bytes::{Buf, BufMut, BytesMut}; use core::str; +use crate::{ + codec::{ + bit_set::BitSet, identifier::Identifier, var_int::VarInt, var_long::VarLong, Codec, + DecodeError, + }, + FixedBitSet, +}; +use bytes::{Buf, BufMut}; + mod deserializer; -pub use deserializer::DeserializerError; +use thiserror::Error; pub mod packet_id; mod serializer; -const SEGMENT_BITS: u8 = 0x7F; -const CONTINUE_BIT: u8 = 0x80; - -#[derive(Debug)] -pub struct ByteBuffer { - buffer: BytesMut, +use std::mem::size_of; + +#[derive(Debug, Error)] +pub enum ReadingError { + /// End-of-File + #[error("EOF, Tried to read {0} but No bytes left to consume")] + EOF(String), + #[error("{0} is Incomplete")] + Incomplete(String), + #[error("{0} is too Large")] + TooLarge(String), + #[error("{0}")] + Message(String), } -impl ByteBuffer { - pub fn empty() -> Self { - Self { - buffer: BytesMut::new(), - } - } - pub fn new(buffer: BytesMut) -> Self { - Self { buffer } - } +pub trait ByteBuf: Buf { + fn try_get_bool(&mut self) -> Result; - pub fn get_var_int(&mut self) -> Result { - let mut value: i32 = 0; - let mut position: i32 = 0; + fn try_get_u8(&mut self) -> Result; - loop { - let read = self.get_u8()?; + fn try_get_i8(&mut self) -> Result; - value |= ((read & SEGMENT_BITS) as i32) << position; + fn try_get_u16(&mut self) -> Result; - if read & CONTINUE_BIT == 0 { - break; - } + fn try_get_i16(&mut self) -> Result; - position += 7; + fn try_get_u32(&mut self) -> Result; - if position >= 32 { - return Err(DeserializerError::Message("VarInt is too big".to_string())); - } - } + fn try_get_i32(&mut self) -> Result; - Ok(VarInt(value)) - } + fn try_get_u64(&mut self) -> Result; - pub fn get_var_long(&mut self) -> Result { - let mut value: i64 = 0; - let mut position: i64 = 0; + fn try_get_i64(&mut self) -> Result; - loop { - let read = self.get_u8()?; + fn try_get_f32(&mut self) -> Result; - value |= ((read & SEGMENT_BITS) as i64) << position; + fn try_get_f64(&mut self) -> Result; - if read & CONTINUE_BIT == 0 { - break; - } + fn try_copy_to_bytes(&mut self, len: usize) -> Result; - position += 7; + fn try_copy_to_bytes_len( + &mut self, + len: usize, + max_length: usize, + ) -> Result; - if position >= 64 { - return Err(DeserializerError::Message("VarLong is too big".to_string())); - } - } + fn try_copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), ReadingError>; - Ok(value) - } + fn try_get_var_int(&mut self) -> Result; - pub fn get_string(&mut self) -> Result { - self.get_string_len(i16::MAX as i32) - } + fn try_get_var_long(&mut self) -> Result; - pub fn get_string_len(&mut self, max_size: i32) -> Result { - let size = self.get_var_int()?.0; - if size > max_size { - return Err(DeserializerError::Message( - "String length is bigger than max size".to_string(), - )); - } + fn try_get_identifer(&mut self) -> Result; - let data = self.copy_to_bytes(size as usize)?; - if data.len() as i32 > max_size { - return Err(DeserializerError::Message( - "String is bigger than max size".to_string(), - )); - } - match str::from_utf8(&data) { - Ok(string_result) => Ok(string_result.to_string()), - Err(e) => Err(DeserializerError::Message(e.to_string())), - } - } + fn try_get_string(&mut self) -> Result; - pub fn get_bool(&mut self) -> Result { - Ok(self.get_u8()? != 0) - } + fn try_get_string_len(&mut self, max_size: usize) -> Result; - pub fn get_uuid(&mut self) -> Result { - let mut bytes = [0u8; 16]; - self.copy_to_slice(&mut bytes)?; - Ok(uuid::Uuid::from_slice(&bytes).expect("Failed to parse UUID")) - } + /// Reads a boolean. If true, the closure is called, and the returned value is + /// wrapped in Some. Otherwise, this returns None. + fn try_get_option( + &mut self, + val: impl FnOnce(&mut Self) -> Result, + ) -> Result, ReadingError>; - pub fn get_fixed_bitset(&mut self, bits: usize) -> Result { - self.copy_to_bytes(bits.div_ceil(8)) - } + fn get_list( + &mut self, + val: impl Fn(&mut Self) -> Result, + ) -> Result, ReadingError>; - pub fn put_bool(&mut self, v: bool) { - if v { - self.buffer.put_u8(1); - } else { - self.buffer.put_u8(0); - } - } + fn try_get_uuid(&mut self) -> Result; - pub fn put_uuid(&mut self, v: &uuid::Uuid) { - // thats the vanilla way - let pair = v.as_u64_pair(); - self.put_u64(pair.0); - self.put_u64(pair.1); - } + fn try_get_fixed_bitset(&mut self, bits: usize) -> Result; +} - pub fn put_string(&mut self, val: &str) { - self.put_string_len(val, i16::MAX as i32); +impl ByteBuf for T { + fn try_get_bool(&mut self) -> Result { + Ok(self.try_get_u8()? != 0) } - pub fn put_string_len(&mut self, val: &str, max_size: i32) { - if val.len() as i32 > max_size { - // Should be panic?, I mean its our fault - panic!("String is too big"); + fn try_get_u8(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_u8()) + } else { + Err(ReadingError::EOF("u8".to_string())) } - self.put_var_int(&val.len().into()); - self.buffer.put(val.as_bytes()); } - pub fn put_string_array(&mut self, array: &[String]) { - for string in array { - self.put_string(string) + fn try_get_i8(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_i8()) + } else { + Err(ReadingError::EOF("i8".to_string())) } } - pub fn put_var_int(&mut self, value: &VarInt) { - let mut val = value.0; - for _ in 0..5 { - let mut b: u8 = val as u8 & 0b01111111; - val >>= 7; - if val != 0 { - b |= 0b10000000; - } - self.buffer.put_u8(b); - if val == 0 { - break; - } + fn try_get_u16(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_u16()) + } else { + Err(ReadingError::EOF("u16".to_string())) } } - pub fn put_bit_set(&mut self, set: &BitSet) { - self.put_var_int(&set.0); - for b in set.1 { - self.put_i64(*b); + fn try_get_i16(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_i16()) + } else { + Err(ReadingError::EOF("i16".to_string())) } } - /// Reads a boolean. If true, the closure is called, and the returned value is - /// wrapped in Some. Otherwise, this returns None. - pub fn get_option( - &mut self, - val: impl FnOnce(&mut Self) -> Result, - ) -> Result, DeserializerError> { - if self.get_bool()? { - Ok(Some(val(self)?)) + fn try_get_u32(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_u32()) } else { - Ok(None) - } - } - /// Writes `true` if the option is Some, or `false` if None. If the option is - /// some, then it also calls the `write` closure. - pub fn put_option(&mut self, val: &Option, write: impl FnOnce(&mut Self, &T)) { - self.put_bool(val.is_some()); - if let Some(v) = val { - write(self, v) + Err(ReadingError::EOF("u32".to_string())) } } - pub fn get_list( - &mut self, - val: impl Fn(&mut Self) -> Result, - ) -> Result, DeserializerError> { - let len = self.get_var_int()?.0 as usize; - let mut list = Vec::with_capacity(len); - for _ in 0..len { - list.push(val(self)?); - } - Ok(list) - } - /// Writes a list to the buffer. - pub fn put_list(&mut self, list: &[T], write: impl Fn(&mut Self, &T)) { - self.put_var_int(&list.len().into()); - for v in list { - write(self, v); + fn try_get_i32(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_i32()) + } else { + Err(ReadingError::EOF("i32".to_string())) } } - pub fn put_varint_arr(&mut self, v: &[i32]) { - self.put_list(v, |p, &v| p.put_var_int(&v.into())) - } - - /* pub fn get_nbt(&mut self) -> Option { - match crab_nbt::NbtTag::deserialize(self.buf()) { - Ok(v) => Some(v), - Err(err) => None, - } - } - - pub fn put_nbt(&mut self, nbt: N) { - self.buffer.put_slice(&nbt.serialize()); + fn try_get_u64(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_u64()) + } else { + Err(ReadingError::EOF("u64".to_string())) } - */ - pub fn buf(&mut self) -> &mut BytesMut { - &mut self.buffer } - // Trait equivalents - pub fn get_u8(&mut self) -> Result { - if self.buffer.has_remaining() { - Ok(self.buffer.get_u8()) + fn try_get_i64(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_i64()) } else { - Err(DeserializerError::Message( - "No bytes left to consume".to_string(), - )) + Err(ReadingError::EOF("i64".to_string())) } } - pub fn get_i8(&mut self) -> Result { - if self.buffer.has_remaining() { - Ok(self.buffer.get_i8()) + fn try_get_f32(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_f32()) } else { - Err(DeserializerError::Message( - "No bytes left to consume".to_string(), - )) + Err(ReadingError::EOF("f32".to_string())) } } - pub fn get_u16(&mut self) -> Result { - if self.buffer.remaining() >= 2 { - Ok(self.buffer.get_u16()) + fn try_get_f64(&mut self) -> Result { + if size_of::() <= self.remaining() { + Ok(self.get_f64()) } else { - Err(DeserializerError::Message( - "Less than 2 bytes left to consume".to_string(), - )) + Err(ReadingError::EOF("f64".to_string())) } } - pub fn get_i16(&mut self) -> Result { - if self.buffer.remaining() >= 2 { - Ok(self.buffer.get_i16()) + fn try_copy_to_bytes(&mut self, len: usize) -> Result { + if self.remaining() >= len { + Ok(self.copy_to_bytes(len)) } else { - Err(DeserializerError::Message( - "Less than 2 bytes left to consume".to_string(), - )) + Err(ReadingError::Message("Unable to copy bytes".to_string())) } } - pub fn get_u32(&mut self) -> Result { - if self.buffer.remaining() >= 4 { - Ok(self.buffer.get_u32()) + fn try_copy_to_bytes_len( + &mut self, + len: usize, + max_size: usize, + ) -> Result { + if len > max_size { + return Err(ReadingError::Message( + "Tried to copy bytes but length exceeds maximum length".to_string(), + )); + } + if self.remaining() >= len { + Ok(self.copy_to_bytes(len)) } else { - Err(DeserializerError::Message( - "Less than 4 bytes left to consume".to_string(), - )) + Err(ReadingError::Message("Unable to copy bytes".to_string())) } } - pub fn get_i32(&mut self) -> Result { - if self.buffer.remaining() >= 4 { - Ok(self.buffer.get_i32()) + fn try_copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), ReadingError> { + if self.remaining() >= dst.len() { + self.copy_to_slice(dst); + Ok(()) } else { - Err(DeserializerError::Message( - "Less than 4 bytes left to consume".to_string(), - )) + Err(ReadingError::Message("Unable to copy slice".to_string())) } } - pub fn get_u64(&mut self) -> Result { - if self.buffer.remaining() >= 8 { - Ok(self.buffer.get_u64()) - } else { - Err(DeserializerError::Message( - "Less than 8 bytes left to consume".to_string(), - )) + fn try_get_var_int(&mut self) -> Result { + match VarInt::decode(self) { + Ok(var_int) => Ok(var_int), + Err(error) => match error { + DecodeError::Incomplete => Err(ReadingError::Incomplete("varint".to_string())), + DecodeError::TooLarge => Err(ReadingError::TooLarge("varint".to_string())), + }, } } - - pub fn get_i64(&mut self) -> Result { - if self.buffer.remaining() >= 8 { - Ok(self.buffer.get_i64()) - } else { - Err(DeserializerError::Message( - "Less than 8 bytes left to consume".to_string(), - )) + fn try_get_var_long(&mut self) -> Result { + match VarLong::decode(self) { + Ok(var_long) => Ok(var_long), + Err(error) => match error { + DecodeError::Incomplete => Err(ReadingError::Incomplete("varint".to_string())), + DecodeError::TooLarge => Err(ReadingError::TooLarge("varlong".to_string())), + }, } } - pub fn get_f32(&mut self) -> Result { - if self.buffer.remaining() >= 4 { - Ok(self.buffer.get_f32()) - } else { - Err(DeserializerError::Message( - "Less than 4 bytes left to consume".to_string(), - )) + fn try_get_string(&mut self) -> Result { + self.try_get_string_len(i16::MAX as usize) + } + + fn try_get_string_len(&mut self, max_size: usize) -> Result { + let size = self.try_get_var_int()?.0; + if size as usize > max_size { + return Err(ReadingError::TooLarge("string".to_string())); + } + + let data = self.try_copy_to_bytes(size as usize)?; + if data.len() > max_size { + return Err(ReadingError::TooLarge("string".to_string())); + } + match str::from_utf8(&data) { + Ok(string_result) => Ok(string_result.to_string()), + Err(e) => Err(ReadingError::Message(e.to_string())), } } - pub fn get_f64(&mut self) -> Result { - if self.buffer.remaining() >= 8 { - Ok(self.buffer.get_f64()) + fn try_get_option( + &mut self, + val: impl FnOnce(&mut Self) -> Result, + ) -> Result, ReadingError> { + if self.try_get_bool()? { + Ok(Some(val(self)?)) } else { - Err(DeserializerError::Message( - "Less than 8 bytes left to consume".to_string(), - )) + Ok(None) } } - // TODO: SerializerError? - pub fn put_u8(&mut self, n: u8) { - self.buffer.put_u8(n) + fn get_list( + &mut self, + val: impl Fn(&mut Self) -> Result, + ) -> Result, ReadingError> { + let len = self.try_get_var_int()?.0 as usize; + let mut list = Vec::with_capacity(len); + for _ in 0..len { + list.push(val(self)?); + } + Ok(list) } - pub fn put_i8(&mut self, n: i8) { - self.buffer.put_i8(n) + fn try_get_uuid(&mut self) -> Result { + let mut bytes = [0u8; 16]; + self.try_copy_to_slice(&mut bytes)?; + Ok(uuid::Uuid::from_slice(&bytes).expect("Failed to parse UUID")) } - pub fn put_u16(&mut self, n: u16) { - self.buffer.put_u16(n) + fn try_get_fixed_bitset(&mut self, bits: usize) -> Result { + self.try_copy_to_bytes(bits.div_ceil(8)) } - pub fn put_i16(&mut self, n: i16) { - self.buffer.put_i16(n) + fn try_get_identifer(&mut self) -> Result { + match Identifier::decode(self) { + Ok(identifer) => Ok(identifer), + Err(error) => match error { + DecodeError::Incomplete => Err(ReadingError::Incomplete("identifer".to_string())), + DecodeError::TooLarge => Err(ReadingError::TooLarge("identifer".to_string())), + }, + } } +} - pub fn put_u32(&mut self, n: u32) { - self.buffer.put_u32(n) - } +pub trait ByteBufMut { + fn put_bool(&mut self, v: bool); - pub fn put_i32(&mut self, n: i32) { - self.buffer.put_i32(n) - } + fn put_uuid(&mut self, v: &uuid::Uuid); - pub fn put_u64(&mut self, n: u64) { - self.buffer.put_u64(n) - } + fn put_string(&mut self, val: &str); + + fn put_string_len(&mut self, val: &str, max_size: usize); + + fn put_string_array(&mut self, array: &[String]); + + fn put_bit_set(&mut self, set: &BitSet); - pub fn put_i64(&mut self, n: i64) { - self.buffer.put_i64(n) + /// Writes `true` if the option is Some, or `false` if None. If the option is + /// some, then it also calls the `write` closure. + fn put_option(&mut self, val: &Option, write: impl FnOnce(&mut Self, &G)); + + fn put_list(&mut self, list: &[G], write: impl Fn(&mut Self, &G)); + + fn put_identifier(&mut self, val: &Identifier); + + fn put_var_int(&mut self, value: &VarInt); + + fn put_varint_arr(&mut self, v: &[i32]); +} + +impl ByteBufMut for T { + fn put_bool(&mut self, v: bool) { + if v { + self.put_u8(1); + } else { + self.put_u8(0); + } } - pub fn put_f32(&mut self, n: f32) { - self.buffer.put_f32(n) + fn put_uuid(&mut self, v: &uuid::Uuid) { + // thats the vanilla way + let pair = v.as_u64_pair(); + self.put_u64(pair.0); + self.put_u64(pair.1); } - pub fn put_f64(&mut self, n: f64) { - self.buffer.put_f64(n) + fn put_string(&mut self, val: &str) { + self.put_string_len(val, i16::MAX as usize); } - pub fn copy_to_bytes(&mut self, len: usize) -> Result { - if self.buffer.len() >= len { - Ok(self.buffer.copy_to_bytes(len)) - } else { - Err(DeserializerError::Message( - "Unable to copy bytes".to_string(), - )) + fn put_string_len(&mut self, val: &str, max_size: usize) { + if val.len() > max_size { + // Should be panic?, I mean its our fault + panic!("String is too big"); } + self.put_var_int(&val.len().into()); + self.put(val.as_bytes()); } - pub fn copy_to_slice(&mut self, dst: &mut [u8]) -> Result<(), DeserializerError> { - if self.buffer.remaining() >= dst.len() { - self.buffer.copy_to_slice(dst); - Ok(()) - } else { - Err(DeserializerError::Message( - "Unable to copy slice".to_string(), - )) + fn put_string_array(&mut self, array: &[String]) { + for string in array { + self.put_string(string) } } - pub fn put_slice(&mut self, src: &[u8]) { - self.buffer.put_slice(src) + fn put_var_int(&mut self, var_int: &VarInt) { + var_int.encode(self); + } + + fn put_bit_set(&mut self, bit_set: &BitSet) { + bit_set.encode(self); + } + + fn put_option(&mut self, val: &Option, write: impl FnOnce(&mut Self, &G)) { + self.put_bool(val.is_some()); + if let Some(v) = val { + write(self, v) + } } - pub fn put(&mut self, src: T) - where - Self: Sized, - { - self.buffer.put(src) + fn put_list(&mut self, list: &[G], write: impl Fn(&mut Self, &G)) { + self.put_var_int(&list.len().into()); + for v in list { + write(self, v); + } } - pub fn reserve(&mut self, additional: usize) { - self.buffer.reserve(additional) + fn put_varint_arr(&mut self, v: &[i32]) { + self.put_list(v, |p, &v| p.put_var_int(&v.into())) } - pub fn get_slice(&mut self) -> BytesMut { - self.buffer.split() + fn put_identifier(&mut self, val: &Identifier) { + val.encode(self); } } #[cfg(test)] mod test { + use bytes::{Bytes, BytesMut}; use serde::{Deserialize, Serialize}; use crate::{ - bytebuf::{deserializer, serializer, ByteBuffer}, + bytebuf::{deserializer, serializer}, VarInt, }; @@ -423,12 +400,12 @@ mod test { bar: i32, } let foo = Foo { bar: 69 }; - let mut serializer = serializer::Serializer::new(ByteBuffer::empty()); + let mut bytes = BytesMut::new(); + let mut serializer = serializer::Serializer::new(&mut bytes); foo.serialize(&mut serializer).unwrap(); - let mut serialized: ByteBuffer = serializer.into(); let deserialized: Foo = - Foo::deserialize(deserializer::Deserializer::new(&mut serialized)).unwrap(); + Foo::deserialize(deserializer::Deserializer::new(&mut Bytes::from(bytes))).unwrap(); assert_eq!(foo, deserialized); } @@ -440,12 +417,12 @@ mod test { bar: VarInt, } let foo = Foo { bar: 69.into() }; - let mut serializer = serializer::Serializer::new(ByteBuffer::empty()); + let mut bytes = BytesMut::new(); + let mut serializer = serializer::Serializer::new(&mut bytes); foo.serialize(&mut serializer).unwrap(); - let mut serialized: ByteBuffer = serializer.into(); let deserialized: Foo = - Foo::deserialize(deserializer::Deserializer::new(&mut serialized)).unwrap(); + Foo::deserialize(deserializer::Deserializer::new(&mut Bytes::from(bytes))).unwrap(); assert_eq!(foo, deserialized); } diff --git a/pumpkin-protocol/src/bytebuf/packet_id.rs b/pumpkin-protocol/src/bytebuf/packet_id.rs index 9f66e67d3..6534033eb 100644 --- a/pumpkin-protocol/src/bytebuf/packet_id.rs +++ b/pumpkin-protocol/src/bytebuf/packet_id.rs @@ -1,134 +1,9 @@ -use bytes::BufMut; -use serde::{ - de::{self, DeserializeOwned, SeqAccess, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; +use bytes::{Buf, BufMut}; +use serde::{de::DeserializeOwned, Serialize}; -use crate::{BitSet, ClientPacket, ServerPacket, VarInt, VarIntType, VarLong}; +use crate::{codec::var_int::VarIntType, ClientPacket, ServerPacket}; -use super::{deserializer, serializer, ByteBuffer, DeserializerError}; - -impl Serialize for BitSet<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - // TODO: make this right - (&self.0, self.1).serialize(serializer) - } -} - -impl Serialize for VarInt { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut value = self.0 as u32; - let mut buf = Vec::new(); - - while value > 0x7F { - buf.put_u8(value as u8 | 0x80); - value >>= 7; - } - - buf.put_u8(value as u8); - - serializer.serialize_bytes(&buf) - } -} - -impl<'de> Deserialize<'de> for VarInt { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct VarIntVisitor; - - impl<'de> Visitor<'de> for VarIntVisitor { - type Value = VarInt; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid VarInt encoded in a byte sequence") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut val = 0; - for i in 0..VarInt::MAX_SIZE { - if let Some(byte) = seq.next_element::()? { - val |= (i32::from(byte) & 0b01111111) << (i * 7); - if byte & 0b10000000 == 0 { - return Ok(VarInt(val)); - } - } else { - break; - } - } - Err(de::Error::custom("VarInt was too large")) - } - } - - deserializer.deserialize_seq(VarIntVisitor) - } -} - -impl Serialize for VarLong { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut value = self.0 as u64; - let mut buf = Vec::new(); - - while value > 0x7F { - buf.put_u8(value as u8 | 0x80); - value >>= 7; - } - - buf.put_u8(value as u8); - - serializer.serialize_bytes(&buf) - } -} - -impl<'de> Deserialize<'de> for VarLong { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct VarLongVisitor; - - impl<'de> Visitor<'de> for VarLongVisitor { - type Value = VarLong; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid VarInt encoded in a byte sequence") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut val = 0; - for i in 0..VarLong::MAX_SIZE { - if let Some(byte) = seq.next_element::()? { - val |= (i64::from(byte) & 0b01111111) << (i * 7); - if byte & 0b10000000 == 0 { - return Ok(VarLong(val)); - } - } else { - break; - } - } - Err(de::Error::custom("VarInt was too large")) - } - } - - deserializer.deserialize_seq(VarLongVisitor) - } -} +use super::{deserializer, serializer, ReadingError}; pub trait Packet { const PACKET_ID: VarIntType; @@ -138,14 +13,10 @@ impl

ClientPacket for P where P: Packet + Serialize, { - fn write(&self, bytebuf: &mut ByteBuffer) { - let mut serializer = serializer::Serializer::new(ByteBuffer::empty()); + fn write(&self, bytebuf: &mut impl BufMut) { + let mut serializer = serializer::Serializer::new(bytebuf); self.serialize(&mut serializer) .expect("Could not serialize packet"); - // We write the packet in an empty bytebuffer and then put it into our current one. - // In the future we may do packet batching thats the reason i don't let every packet create a new bytebuffer and use - // an existing instead - bytebuf.put(serializer.output.buf()); } } @@ -153,7 +24,7 @@ impl

ServerPacket for P where P: Packet + DeserializeOwned, { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { let deserializer = deserializer::Deserializer::new(bytebuf); P::deserialize(deserializer) } diff --git a/pumpkin-protocol/src/bytebuf/serializer.rs b/pumpkin-protocol/src/bytebuf/serializer.rs index cb2f78660..5682aa7b8 100644 --- a/pumpkin-protocol/src/bytebuf/serializer.rs +++ b/pumpkin-protocol/src/bytebuf/serializer.rs @@ -1,41 +1,24 @@ use std::fmt::Display; +use bytes::BufMut; use serde::{ ser::{self}, Serialize, }; use thiserror::Error; -use super::ByteBuffer; +use super::ByteBufMut; -pub struct Serializer { - pub output: ByteBuffer, +pub struct Serializer { + pub output: B, } -impl Serializer { - pub fn new(buf: ByteBuffer) -> Self { +impl Serializer { + pub fn new(buf: B) -> Self { Self { output: buf } } } -impl From for ByteBuffer { - fn from(val: Serializer) -> Self { - val.output - } -} - -impl AsRef for Serializer { - fn as_ref(&self) -> &ByteBuffer { - &self.output - } -} - -impl AsMut for Serializer { - fn as_mut(&mut self) -> &mut ByteBuffer { - &mut self.output - } -} - #[derive(Debug, Error)] pub enum SerializerError { #[error("serializer error {0}")] @@ -55,8 +38,8 @@ impl ser::Error for SerializerError { // Enums are written as a varint of the index // Structs are ignored // Iterables' values are written in order, but NO information (e.g. size) about the -// iterable itself is written (list sizes should be a seperate field) -impl ser::Serializer for &mut Serializer { +// iterable itself is written (list sizes should be a separate field) +impl ser::Serializer for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -137,7 +120,7 @@ impl ser::Serializer for &mut Serializer { Ok(()) } fn serialize_seq(self, _len: Option) -> Result { - // here is where all arrays/list getting written, usally we prefix the length of every length with an var int. The problem is + // here is where all arrays/list getting written, usually we prefix the length of every length with an var int. The problem is // that byte arrays also getting thrown in here, and we don't want to prefix them Ok(self) } @@ -226,7 +209,7 @@ impl ser::Serializer for &mut Serializer { } } -impl ser::SerializeSeq for &mut Serializer { +impl ser::SerializeSeq for &mut Serializer { // Must match the `Ok` type of the serializer. type Ok = (); // Must match the `Error` type of the serializer. @@ -246,7 +229,7 @@ impl ser::SerializeSeq for &mut Serializer { } } -impl ser::SerializeTuple for &mut Serializer { +impl ser::SerializeTuple for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -263,7 +246,7 @@ impl ser::SerializeTuple for &mut Serializer { } // Same thing but for tuple structs. -impl ser::SerializeTupleStruct for &mut Serializer { +impl ser::SerializeTupleStruct for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -288,7 +271,7 @@ impl ser::SerializeTupleStruct for &mut Serializer { // // So the `end` method in this impl is responsible for closing both the `]` and // the `}`. -impl ser::SerializeTupleVariant for &mut Serializer { +impl ser::SerializeTupleVariant for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -312,7 +295,7 @@ impl ser::SerializeTupleVariant for &mut Serializer { // `serialize_entry` method allows serializers to optimize for the case where // key and value are both available simultaneously. In JSON it doesn't make a // difference so the default behavior for `serialize_entry` is fine. -impl ser::SerializeMap for &mut Serializer { +impl ser::SerializeMap for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -348,7 +331,7 @@ impl ser::SerializeMap for &mut Serializer { // Structs are like maps in which the keys are constrained to be compile-time // constant strings. -impl ser::SerializeStruct for &mut Serializer { +impl ser::SerializeStruct for &mut Serializer { type Ok = (); type Error = SerializerError; @@ -371,7 +354,7 @@ impl ser::SerializeStruct for &mut Serializer { // Similar to `SerializeTupleVariant`, here the `end` method is responsible for // closing both of the curly braces opened by `serialize_struct_variant`. -impl ser::SerializeStructVariant for &mut Serializer { +impl ser::SerializeStructVariant for &mut Serializer { type Ok = (); type Error = SerializerError; diff --git a/pumpkin-protocol/src/client/config/c_cookie_request.rs b/pumpkin-protocol/src/client/config/c_cookie_request.rs index 08bdcedb6..71fb9aca3 100644 --- a/pumpkin-protocol/src/client/config/c_cookie_request.rs +++ b/pumpkin-protocol/src/client/config/c_cookie_request.rs @@ -1,6 +1,6 @@ use pumpkin_macros::client_packet; -use crate::Identifier; +use crate::codec::identifier::Identifier; #[derive(serde::Serialize)] #[client_packet("config:cookie_request")] diff --git a/pumpkin-protocol/src/client/config/c_known_packs.rs b/pumpkin-protocol/src/client/config/c_known_packs.rs index 80b375808..7622b93ef 100644 --- a/pumpkin-protocol/src/client/config/c_known_packs.rs +++ b/pumpkin-protocol/src/client/config/c_known_packs.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{bytebuf::ByteBuffer, ClientPacket, KnownPack}; +use crate::{bytebuf::ByteBufMut, ClientPacket, KnownPack}; #[client_packet("config:select_known_packs")] pub struct CKnownPacks<'a> { @@ -14,7 +15,7 @@ impl<'a> CKnownPacks<'a> { } impl ClientPacket for CKnownPacks<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_list::(self.known_packs, |p, v| { p.put_string(v.namespace); p.put_string(v.id); diff --git a/pumpkin-protocol/src/client/config/c_registry_data.rs b/pumpkin-protocol/src/client/config/c_registry_data.rs index d346ca45e..5a04e6823 100644 --- a/pumpkin-protocol/src/client/config/c_registry_data.rs +++ b/pumpkin-protocol/src/client/config/c_registry_data.rs @@ -1,16 +1,16 @@ -use bytes::BytesMut; +use bytes::{BufMut, BytesMut}; use pumpkin_macros::client_packet; -use crate::{bytebuf::ByteBuffer, ClientPacket}; +use crate::{bytebuf::ByteBufMut, codec::identifier::Identifier, ClientPacket}; #[client_packet("config:registry_data")] pub struct CRegistryData<'a> { - registry_id: &'a str, - entries: &'a [RegistryEntry<'a>], + registry_id: &'a Identifier, + entries: &'a [RegistryEntry], } impl<'a> CRegistryData<'a> { - pub fn new(registry_id: &'a str, entries: &'a [RegistryEntry]) -> Self { + pub fn new(registry_id: &'a Identifier, entries: &'a [RegistryEntry]) -> Self { Self { registry_id, entries, @@ -18,18 +18,17 @@ impl<'a> CRegistryData<'a> { } } -pub struct RegistryEntry<'a> { - pub entry_id: &'a str, - pub data: BytesMut, +pub struct RegistryEntry { + pub entry_id: Identifier, + pub data: Option, } impl ClientPacket for CRegistryData<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { - bytebuf.put_string(self.registry_id); + fn write(&self, bytebuf: &mut impl BufMut) { + bytebuf.put_identifier(self.registry_id); bytebuf.put_list::(self.entries, |p, v| { - p.put_string(v.entry_id); - p.put_bool(!v.data.is_empty()); - p.put_slice(&v.data); + p.put_identifier(&v.entry_id); + p.put_option(&v.data, |p, v| p.put_slice(v)); }); } } diff --git a/pumpkin-protocol/src/client/config/c_server_links.rs b/pumpkin-protocol/src/client/config/c_server_links.rs index b4150d8ef..b14547ca5 100644 --- a/pumpkin-protocol/src/client/config/c_server_links.rs +++ b/pumpkin-protocol/src/client/config/c_server_links.rs @@ -1,87 +1,16 @@ -use crate::VarInt; -use pumpkin_core::text::TextComponent; +use crate::{Link, VarInt}; use pumpkin_macros::client_packet; -use serde::{Serialize, Serializer}; +use serde::Serialize; #[derive(Serialize)] #[client_packet("config:server_links")] -pub struct CServerLinks<'a> { +pub struct CConfigServerLinks<'a> { links_count: &'a VarInt, links: &'a [Link<'a>], } -impl<'a> CServerLinks<'a> { +impl<'a> CConfigServerLinks<'a> { pub fn new(links_count: &'a VarInt, links: &'a [Link<'a>]) -> Self { Self { links_count, links } } } - -pub enum Label<'a> { - BuiltIn(LinkType), - TextComponent(TextComponent<'a>), -} - -impl Serialize for Label<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Label::BuiltIn(link_type) => link_type.serialize(serializer), - Label::TextComponent(component) => component.serialize(serializer), - } - } -} - -#[derive(Serialize)] -pub struct Link<'a> { - pub is_built_in: bool, - pub label: Label<'a>, - pub url: &'a String, -} - -impl<'a> Link<'a> { - pub fn new(label: Label<'a>, url: &'a String) -> Self { - Self { - is_built_in: match label { - Label::BuiltIn(_) => true, - Label::TextComponent(_) => false, - }, - label, - url, - } - } -} - -pub enum LinkType { - BugReport, - CommunityGuidelines, - Support, - Status, - Feedback, - Community, - Website, - Forums, - News, - Announcements, -} - -impl Serialize for LinkType { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - LinkType::BugReport => VarInt(0).serialize(serializer), - LinkType::CommunityGuidelines => VarInt(1).serialize(serializer), - LinkType::Support => VarInt(2).serialize(serializer), - LinkType::Status => VarInt(3).serialize(serializer), - LinkType::Feedback => VarInt(4).serialize(serializer), - LinkType::Community => VarInt(5).serialize(serializer), - LinkType::Website => VarInt(6).serialize(serializer), - LinkType::Forums => VarInt(7).serialize(serializer), - LinkType::News => VarInt(8).serialize(serializer), - LinkType::Announcements => VarInt(9).serialize(serializer), - } - } -} diff --git a/pumpkin-protocol/src/client/config/c_store_cookie.rs b/pumpkin-protocol/src/client/config/c_store_cookie.rs index 511bdd0d1..0334df2d1 100644 --- a/pumpkin-protocol/src/client/config/c_store_cookie.rs +++ b/pumpkin-protocol/src/client/config/c_store_cookie.rs @@ -1,4 +1,4 @@ -use crate::{Identifier, VarInt}; +use crate::{codec::identifier::Identifier, VarInt}; use pumpkin_macros::client_packet; #[derive(serde::Serialize)] diff --git a/pumpkin-protocol/src/client/login/c_cookie_request.rs b/pumpkin-protocol/src/client/login/c_cookie_request.rs index df592625d..174e85691 100644 --- a/pumpkin-protocol/src/client/login/c_cookie_request.rs +++ b/pumpkin-protocol/src/client/login/c_cookie_request.rs @@ -1,15 +1,16 @@ -use crate::Identifier; use pumpkin_macros::client_packet; use serde::Serialize; +use crate::codec::identifier::Identifier; + #[derive(Serialize)] #[client_packet("login:cookie_request")] /// Requests a cookie that was previously stored. -pub struct CCookieRequest<'a> { +pub struct CLoginCookieRequest<'a> { key: &'a Identifier, } -impl<'a> CCookieRequest<'a> { +impl<'a> CLoginCookieRequest<'a> { pub fn new(key: &'a Identifier) -> Self { Self { key } } diff --git a/pumpkin-protocol/src/client/login/c_login_success.rs b/pumpkin-protocol/src/client/login/c_login_success.rs index 462fcce5a..290ca4c28 100644 --- a/pumpkin-protocol/src/client/login/c_login_success.rs +++ b/pumpkin-protocol/src/client/login/c_login_success.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{bytebuf::ByteBuffer, ClientPacket, Property}; +use crate::{bytebuf::ByteBufMut, ClientPacket, Property}; #[client_packet("login:login_finished")] pub struct CLoginSuccess<'a> { @@ -20,7 +21,7 @@ impl<'a> CLoginSuccess<'a> { } impl ClientPacket for CLoginSuccess<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_uuid(self.uuid); bytebuf.put_string(self.username); bytebuf.put_list::(self.properties, |p, v| { diff --git a/pumpkin-protocol/src/client/play/c_boss_event.rs b/pumpkin-protocol/src/client/play/c_boss_event.rs index 344526545..e5b2292d5 100644 --- a/pumpkin-protocol/src/client/play/c_boss_event.rs +++ b/pumpkin-protocol/src/client/play/c_boss_event.rs @@ -1,6 +1,7 @@ -use crate::bytebuf::ByteBuffer; +use crate::bytebuf::ByteBufMut; use crate::client::play::bossevent_action::BosseventAction; use crate::{ClientPacket, VarInt}; +use bytes::BufMut; use pumpkin_macros::client_packet; #[client_packet("play:boss_event")] @@ -16,7 +17,7 @@ impl<'a> CBossEvent<'a> { } impl ClientPacket for CBossEvent<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_uuid(&self.uuid); let action = &self.action; match action { @@ -28,7 +29,7 @@ impl ClientPacket for CBossEvent<'_> { flags, } => { bytebuf.put_var_int(&VarInt::from(0u8)); - bytebuf.put_slice(title.encode().as_slice()); + bytebuf.put_slice(&title.encode()); bytebuf.put_f32(*health); bytebuf.put_var_int(color); bytebuf.put_var_int(division); @@ -43,7 +44,7 @@ impl ClientPacket for CBossEvent<'_> { } BosseventAction::UpdateTile(title) => { bytebuf.put_var_int(&VarInt::from(3u8)); - bytebuf.put_slice(title.encode().as_slice()); + bytebuf.put_slice(&title.encode()); } BosseventAction::UpdateStyle { color, dividers } => { bytebuf.put_var_int(&VarInt::from(4u8)); diff --git a/pumpkin-protocol/src/client/play/c_chunk_data.rs b/pumpkin-protocol/src/client/play/c_chunk_data.rs index cd98d1cf0..1356508f7 100644 --- a/pumpkin-protocol/src/client/play/c_chunk_data.rs +++ b/pumpkin-protocol/src/client/play/c_chunk_data.rs @@ -1,6 +1,6 @@ -use crate::{bytebuf::ByteBuffer, BitSet, ClientPacket, VarInt}; -use itertools::Itertools; +use crate::{bytebuf::ByteBufMut, codec::bit_set::BitSet, ClientPacket, VarInt}; +use bytes::{BufMut, BytesMut}; use pumpkin_macros::client_packet; use pumpkin_world::{chunk::ChunkData, DIRECT_PALETTE_BITS}; @@ -8,7 +8,7 @@ use pumpkin_world::{chunk::ChunkData, DIRECT_PALETTE_BITS}; pub struct CChunkData<'a>(pub &'a ChunkData); impl ClientPacket for CChunkData<'_> { - fn write(&self, buf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, buf: &mut impl BufMut) { // Chunk X buf.put_i32(self.0.position.x); // Chunk Z @@ -19,14 +19,14 @@ impl ClientPacket for CChunkData<'_> { // Heightmaps buf.put_slice(&heightmap_nbt); - let mut data_buf = ByteBuffer::empty(); + let mut data_buf = BytesMut::new(); self.0.blocks.iter_subchunks().for_each(|chunk| { let block_count = chunk.len() as i16; // Block count data_buf.put_i16(block_count); //// Block states - let palette = chunk.iter().dedup().collect_vec(); + let palette = chunk; // TODO: make dynamic block_size work // TODO: make direct block_size work enum PaletteType { @@ -55,7 +55,7 @@ impl ClientPacket for CChunkData<'_> { palette.iter().for_each(|id| { // Palette - data_buf.put_var_int(&VarInt(**id as i32)); + data_buf.put_var_int(&VarInt(*id as i32)); }); // Data array length let data_array_len = chunk.len().div_ceil(64 / block_size as usize); @@ -67,7 +67,7 @@ impl ClientPacket for CChunkData<'_> { for block in block_clump.iter().rev() { let index = palette .iter() - .position(|b| *b == block) + .position(|b| b == block) .expect("Its just got added, ofc it should be there"); out_long = out_long << block_size | (index as i64); } @@ -103,9 +103,9 @@ impl ClientPacket for CChunkData<'_> { }); // Size - buf.put_var_int(&VarInt(data_buf.buf().len() as i32)); + buf.put_var_int(&VarInt(data_buf.len() as i32)); // Data - buf.put_slice(data_buf.buf()); + buf.put_slice(&data_buf); // TODO: block entities buf.put_var_int(&VarInt(0)); @@ -113,13 +113,13 @@ impl ClientPacket for CChunkData<'_> { // Sky Light Mask // All of the chunks, this is not optimal and uses way more data than needed but will be // overhauled with full lighting system. - buf.put_bit_set(&BitSet(VarInt(1), &[0b01111111111111111111111110])); + buf.put_bit_set(&BitSet(VarInt(1), vec![0b01111111111111111111111110])); // Block Light Mask - buf.put_bit_set(&BitSet(VarInt(1), &[0])); + buf.put_bit_set(&BitSet(VarInt(1), vec![0])); // Empty Sky Light Mask - buf.put_bit_set(&BitSet(VarInt(1), &[0b0])); + buf.put_bit_set(&BitSet(VarInt(1), vec![0b0])); // Empty Block Light Mask - buf.put_bit_set(&BitSet(VarInt(1), &[0])); + buf.put_bit_set(&BitSet(VarInt(1), vec![0])); buf.put_var_int(&VarInt(self.0.blocks.subchunks_len() as i32)); self.0.blocks.iter_subchunks().for_each(|chunk| { diff --git a/pumpkin-protocol/src/client/play/c_command_suggestions.rs b/pumpkin-protocol/src/client/play/c_command_suggestions.rs index fae9b04f3..c137b9ad7 100644 --- a/pumpkin-protocol/src/client/play/c_command_suggestions.rs +++ b/pumpkin-protocol/src/client/play/c_command_suggestions.rs @@ -1,7 +1,8 @@ +use bytes::BufMut; use pumpkin_core::text::TextComponent; use pumpkin_macros::client_packet; -use crate::{ClientPacket, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, VarInt}; #[client_packet("play:command_suggestions")] pub struct CCommandSuggestions<'a> { @@ -28,13 +29,13 @@ impl<'a> CCommandSuggestions<'a> { } impl ClientPacket for CCommandSuggestions<'_> { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(&self.id); bytebuf.put_var_int(&self.start); bytebuf.put_var_int(&self.length); bytebuf.put_list(&self.matches, |bytebuf, suggestion| { - bytebuf.put_string(suggestion.suggestion); + bytebuf.put_string(&suggestion.suggestion); bytebuf.put_bool(suggestion.tooltip.is_some()); if let Some(tooltip) = &suggestion.tooltip { bytebuf.put_slice(&tooltip.encode()); @@ -45,12 +46,12 @@ impl ClientPacket for CCommandSuggestions<'_> { #[derive(PartialEq, Eq, Hash, Debug)] pub struct CommandSuggestion<'a> { - pub suggestion: &'a str, + pub suggestion: String, pub tooltip: Option>, } impl<'a> CommandSuggestion<'a> { - pub fn new(suggestion: &'a str, tooltip: Option>) -> Self { + pub fn new(suggestion: String, tooltip: Option>) -> Self { Self { suggestion, tooltip, diff --git a/pumpkin-protocol/src/client/play/c_commands.rs b/pumpkin-protocol/src/client/play/c_commands.rs index 32dc71df9..264a51331 100644 --- a/pumpkin-protocol/src/client/play/c_commands.rs +++ b/pumpkin-protocol/src/client/play/c_commands.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{bytebuf::ByteBuffer, ClientPacket, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, VarInt}; #[client_packet("play:commands")] pub struct CCommands<'a> { @@ -18,7 +19,7 @@ impl<'a> CCommands<'a> { } impl ClientPacket for CCommands<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_list(&self.nodes, |bytebuf, node: &ProtoNode| { node.write_to(bytebuf) }); @@ -51,7 +52,7 @@ impl ProtoNode<'_> { const FLAG_HAS_REDIRECT: u8 = 8; const FLAG_HAS_SUGGESTION_TYPE: u8 = 16; - pub fn write_to(&self, bytebuf: &mut ByteBuffer) { + pub fn write_to(&self, bytebuf: &mut impl BufMut) { // flags let flags = match self.node_type { ProtoNodeType::Root => 0, @@ -69,10 +70,10 @@ impl ProtoNode<'_> { name: _, is_executable, parser: _, - override_suggestion_type: override_suggestion_tpye, + override_suggestion_type, } => { let mut n = 2; - if override_suggestion_tpye.is_some() { + if override_suggestion_type.is_some() { n |= Self::FLAG_HAS_SUGGESTION_TYPE } if is_executable { @@ -187,7 +188,7 @@ impl ProtoCmdArgParser<'_> { pub const SCORE_HOLDER_FLAG_ALLOW_MULTIPLE: u8 = 1; - pub fn write_to_buffer(&self, bytebuf: &mut ByteBuffer) { + pub fn write_to_buffer(&self, bytebuf: &mut impl BufMut) { match self { Self::Bool => bytebuf.put_var_int(&0.into()), Self::Float { min, max } => Self::write_number_arg(&1.into(), *min, *max, bytebuf), @@ -269,7 +270,7 @@ impl ProtoCmdArgParser<'_> { id: &VarInt, min: Option, max: Option, - bytebuf: &mut ByteBuffer, + bytebuf: &mut impl BufMut, ) { let mut flags: u8 = 0; if min.is_some() { @@ -290,13 +291,13 @@ impl ProtoCmdArgParser<'_> { } } - fn write_with_flags(id: &VarInt, flags: u8, bytebuf: &mut ByteBuffer) { + fn write_with_flags(id: &VarInt, flags: u8, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(id); bytebuf.put_u8(flags); } - fn write_with_identifier(id: &VarInt, extra_identifier: &str, bytebuf: &mut ByteBuffer) { + fn write_with_identifier(id: &VarInt, extra_identifier: &str, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(id); bytebuf.put_string(extra_identifier); @@ -312,29 +313,29 @@ pub enum StringProtoArgBehavior { } trait NumberCmdArg { - fn write(self, bytebuf: &mut ByteBuffer); + fn write(self, bytebuf: &mut impl BufMut); } impl NumberCmdArg for f32 { - fn write(self, bytebuf: &mut ByteBuffer) { + fn write(self, bytebuf: &mut impl BufMut) { bytebuf.put_f32(self); } } impl NumberCmdArg for f64 { - fn write(self, bytebuf: &mut ByteBuffer) { + fn write(self, bytebuf: &mut impl BufMut) { bytebuf.put_f64(self); } } impl NumberCmdArg for i32 { - fn write(self, bytebuf: &mut ByteBuffer) { + fn write(self, bytebuf: &mut impl BufMut) { bytebuf.put_i32(self); } } impl NumberCmdArg for i64 { - fn write(self, bytebuf: &mut ByteBuffer) { + fn write(self, bytebuf: &mut impl BufMut) { bytebuf.put_i64(self); } } diff --git a/pumpkin-protocol/src/client/play/c_cookie_request.rs b/pumpkin-protocol/src/client/play/c_cookie_request.rs index 58d494942..fb9bc806a 100644 --- a/pumpkin-protocol/src/client/play/c_cookie_request.rs +++ b/pumpkin-protocol/src/client/play/c_cookie_request.rs @@ -1,15 +1,16 @@ -use crate::Identifier; use pumpkin_macros::client_packet; use serde::Serialize; +use crate::codec::identifier::Identifier; + #[derive(Serialize)] #[client_packet("play:cookie_request")] /// Requests a cookie that was previously stored. -pub struct CCookieRequest<'a> { +pub struct CPlayCookieRequest<'a> { key: &'a Identifier, } -impl<'a> CCookieRequest<'a> { +impl<'a> CPlayCookieRequest<'a> { pub fn new(key: &'a Identifier) -> Self { Self { key } } diff --git a/pumpkin-protocol/src/client/play/c_entity_sound_effect.rs b/pumpkin-protocol/src/client/play/c_entity_sound_effect.rs index 62afd46d2..27de764fe 100644 --- a/pumpkin-protocol/src/client/play/c_entity_sound_effect.rs +++ b/pumpkin-protocol/src/client/play/c_entity_sound_effect.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{ClientPacket, IDOrSoundEvent, SoundCategory, SoundEvent, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, IDOrSoundEvent, SoundCategory, SoundEvent, VarInt}; #[client_packet("play:sound_entity")] pub struct CEntitySoundEffect { @@ -38,11 +39,11 @@ impl CEntitySoundEffect { } impl ClientPacket for CEntitySoundEffect { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(&self.sound_event.id); if self.sound_event.id.0 == 0 { if let Some(test) = &self.sound_event.sound_event { - bytebuf.put_string(&test.sound_name); + bytebuf.put_identifier(&test.sound_name); bytebuf.put_option(&test.range, |p, v| { p.put_f32(*v); diff --git a/pumpkin-protocol/src/client/play/c_initialize_world_border.rs b/pumpkin-protocol/src/client/play/c_initialize_world_border.rs index f4d6663a7..f2ab72f6c 100644 --- a/pumpkin-protocol/src/client/play/c_initialize_world_border.rs +++ b/pumpkin-protocol/src/client/play/c_initialize_world_border.rs @@ -1,7 +1,7 @@ use pumpkin_macros::client_packet; use serde::Serialize; -use crate::{VarInt, VarLong}; +use crate::{codec::var_long::VarLong, VarInt}; #[derive(Serialize)] #[client_packet("play:initialize_border")] diff --git a/pumpkin-protocol/src/client/play/c_login.rs b/pumpkin-protocol/src/client/play/c_login.rs index 12a594f88..ec6e23bb4 100644 --- a/pumpkin-protocol/src/client/play/c_login.rs +++ b/pumpkin-protocol/src/client/play/c_login.rs @@ -3,7 +3,7 @@ use pumpkin_core::math::position::WorldPosition; use pumpkin_macros::client_packet; use serde::Serialize; -use crate::VarInt; +use crate::{codec::identifier::Identifier, VarInt}; #[derive(Serialize)] #[client_packet("play:login")] @@ -11,7 +11,7 @@ pub struct CLogin<'a> { entity_id: i32, is_hardcore: bool, dimension_count: VarInt, - dimension_names: &'a [&'a str], + dimension_names: &'a [Identifier], max_players: VarInt, view_distance: VarInt, simulated_distance: VarInt, @@ -20,13 +20,13 @@ pub struct CLogin<'a> { limited_crafting: bool, // Spawn Info dimension_type: VarInt, - dimension_name: &'a str, + dimension_name: Identifier, hashed_seed: i64, game_mode: u8, previous_gamemode: i8, debug: bool, is_flat: bool, - death_dimension_name: Option<(&'a str, WorldPosition)>, + death_dimension_name: Option<(Identifier, WorldPosition)>, portal_cooldown: VarInt, sealevel: VarInt, enforce_secure_chat: bool, @@ -37,7 +37,7 @@ impl<'a> CLogin<'a> { pub fn new( entity_id: i32, is_hardcore: bool, - dimension_names: &'a [&'a str], + dimension_names: &'a [Identifier], max_players: VarInt, view_distance: VarInt, simulated_distance: VarInt, @@ -45,13 +45,13 @@ impl<'a> CLogin<'a> { enabled_respawn_screen: bool, limited_crafting: bool, dimension_type: VarInt, - dimension_name: &'a str, + dimension_name: Identifier, hashed_seed: i64, game_mode: u8, previous_gamemode: i8, debug: bool, is_flat: bool, - death_dimension_name: Option<(&'a str, WorldPosition)>, + death_dimension_name: Option<(Identifier, WorldPosition)>, portal_cooldown: VarInt, sealevel: VarInt, enforce_secure_chat: bool, diff --git a/pumpkin-protocol/src/client/play/c_player_chat_message.rs b/pumpkin-protocol/src/client/play/c_player_chat_message.rs index b774a2f28..0db6c3df0 100644 --- a/pumpkin-protocol/src/client/play/c_player_chat_message.rs +++ b/pumpkin-protocol/src/client/play/c_player_chat_message.rs @@ -3,7 +3,7 @@ use pumpkin_core::text::TextComponent; use pumpkin_macros::client_packet; use serde::Serialize; -use crate::{BitSet, VarInt}; +use crate::{codec::bit_set::BitSet, VarInt}; #[derive(Serialize)] #[client_packet("play:player_chat")] @@ -18,7 +18,7 @@ pub struct CPlayerChatMessage<'a> { previous_messages_count: VarInt, previous_messages: &'a [PreviousMessage<'a>], // max 20 unsigned_content: Option>, - filter_type: FilterType<'a>, + filter_type: FilterType, chat_type: VarInt, sender_name: TextComponent<'a>, target_name: Option>, @@ -35,7 +35,7 @@ impl<'a> CPlayerChatMessage<'a> { salt: i64, previous_messages: &'a [PreviousMessage<'a>], unsigned_content: Option>, - filter_type: FilterType<'a>, + filter_type: FilterType, chat_type: VarInt, sender_name: TextComponent<'a>, target_name: Option>, @@ -66,11 +66,11 @@ pub struct PreviousMessage<'a> { #[derive(Serialize)] #[repr(i32)] -pub enum FilterType<'a> { +pub enum FilterType { /// Message is not filtered at all PassThrough = 0, /// Message is fully filtered FullyFiltered = 1, /// Only some characters in the message are filtered - PartiallyFiltered(BitSet<'a>) = 2, + PartiallyFiltered(BitSet) = 2, } diff --git a/pumpkin-protocol/src/client/play/c_player_info_update.rs b/pumpkin-protocol/src/client/play/c_player_info_update.rs index 09fbff949..72d912cbf 100644 --- a/pumpkin-protocol/src/client/play/c_player_info_update.rs +++ b/pumpkin-protocol/src/client/play/c_player_info_update.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{bytebuf::ByteBuffer, ClientPacket, Property}; +use crate::{bytebuf::ByteBufMut, ClientPacket, Property}; use super::PlayerAction; @@ -22,7 +23,7 @@ impl<'a> CPlayerInfoUpdate<'a> { } impl ClientPacket for CPlayerInfoUpdate<'_> { - fn write(&self, bytebuf: &mut ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_i8(self.actions); bytebuf.put_list::(self.players, |p, v| { p.put_uuid(&v.uuid); diff --git a/pumpkin-protocol/src/client/play/c_player_position.rs b/pumpkin-protocol/src/client/play/c_player_position.rs index 0a99a9e09..287c1b2ff 100644 --- a/pumpkin-protocol/src/client/play/c_player_position.rs +++ b/pumpkin-protocol/src/client/play/c_player_position.rs @@ -1,7 +1,8 @@ +use bytes::BufMut; use pumpkin_core::math::vector3::Vector3; use pumpkin_macros::client_packet; -use crate::{ClientPacket, PositionFlag, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, PositionFlag, VarInt}; #[client_packet("play:player_position")] pub struct CPlayerPosition<'a> { @@ -34,7 +35,7 @@ impl<'a> CPlayerPosition<'a> { } impl ClientPacket for CPlayerPosition<'_> { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(&self.teleport_id); bytebuf.put_f64(self.position.x); bytebuf.put_f64(self.position.y); diff --git a/pumpkin-protocol/src/client/play/c_respawn.rs b/pumpkin-protocol/src/client/play/c_respawn.rs index b892d53ec..9d68b059c 100644 --- a/pumpkin-protocol/src/client/play/c_respawn.rs +++ b/pumpkin-protocol/src/client/play/c_respawn.rs @@ -2,35 +2,35 @@ use pumpkin_core::math::position::WorldPosition; use pumpkin_macros::client_packet; use serde::Serialize; -use crate::VarInt; +use crate::{codec::identifier::Identifier, VarInt}; #[derive(Serialize)] #[client_packet("play:respawn")] -pub struct CRespawn<'a> { +pub struct CRespawn { dimension_type: VarInt, - dimension_name: &'a str, + dimension_name: Identifier, hashed_seed: i64, game_mode: u8, previous_gamemode: i8, debug: bool, is_flat: bool, - death_dimension_name: Option<(&'a str, WorldPosition)>, + death_dimension_name: Option<(Identifier, WorldPosition)>, portal_cooldown: VarInt, sealevel: VarInt, data_kept: u8, } -impl<'a> CRespawn<'a> { +impl CRespawn { #[expect(clippy::too_many_arguments)] pub fn new( dimension_type: VarInt, - dimension_name: &'a str, + dimension_name: Identifier, hashed_seed: i64, game_mode: u8, previous_gamemode: i8, debug: bool, is_flat: bool, - death_dimension_name: Option<(&'a str, WorldPosition)>, + death_dimension_name: Option<(Identifier, WorldPosition)>, portal_cooldown: VarInt, sealevel: VarInt, data_kept: u8, diff --git a/pumpkin-protocol/src/client/play/c_server_links.rs b/pumpkin-protocol/src/client/play/c_server_links.rs index 7619a0c65..a2cb76fa6 100644 --- a/pumpkin-protocol/src/client/play/c_server_links.rs +++ b/pumpkin-protocol/src/client/play/c_server_links.rs @@ -1,87 +1,16 @@ -use crate::VarInt; -use pumpkin_core::text::TextComponent; +use crate::{Link, VarInt}; use pumpkin_macros::client_packet; -use serde::{Serialize, Serializer}; +use serde::Serialize; #[derive(Serialize)] #[client_packet("play:server_links")] -pub struct CServerLinks<'a> { +pub struct CPlayServerLinks<'a> { links_count: &'a VarInt, links: &'a [Link<'a>], } -impl<'a> CServerLinks<'a> { +impl<'a> CPlayServerLinks<'a> { pub fn new(links_count: &'a VarInt, links: &'a [Link<'a>]) -> Self { Self { links_count, links } } } - -pub enum Label<'a> { - BuiltIn(LinkType), - TextComponent(TextComponent<'a>), -} - -impl Serialize for Label<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Label::BuiltIn(link_type) => link_type.serialize(serializer), - Label::TextComponent(component) => component.serialize(serializer), - } - } -} - -#[derive(Serialize)] -pub struct Link<'a> { - pub is_built_in: bool, - pub label: Label<'a>, - pub url: &'a String, -} - -impl<'a> Link<'a> { - pub fn new(label: Label<'a>, url: &'a String) -> Self { - Self { - is_built_in: match label { - Label::BuiltIn(_) => true, - Label::TextComponent(_) => false, - }, - label, - url, - } - } -} - -pub enum LinkType { - BugReport, - CommunityGuidelines, - Support, - Status, - Feedback, - Community, - Website, - Forums, - News, - Announcements, -} - -impl Serialize for LinkType { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - LinkType::BugReport => VarInt(0).serialize(serializer), - LinkType::CommunityGuidelines => VarInt(1).serialize(serializer), - LinkType::Support => VarInt(2).serialize(serializer), - LinkType::Status => VarInt(3).serialize(serializer), - LinkType::Feedback => VarInt(4).serialize(serializer), - LinkType::Community => VarInt(5).serialize(serializer), - LinkType::Website => VarInt(6).serialize(serializer), - LinkType::Forums => VarInt(7).serialize(serializer), - LinkType::News => VarInt(8).serialize(serializer), - LinkType::Announcements => VarInt(9).serialize(serializer), - } - } -} diff --git a/pumpkin-protocol/src/client/play/c_set_border_lerp_size.rs b/pumpkin-protocol/src/client/play/c_set_border_lerp_size.rs index 0d15ab9bc..5f8861a71 100644 --- a/pumpkin-protocol/src/client/play/c_set_border_lerp_size.rs +++ b/pumpkin-protocol/src/client/play/c_set_border_lerp_size.rs @@ -1,7 +1,7 @@ use pumpkin_macros::client_packet; use serde::Serialize; -use crate::VarLong; +use crate::codec::var_long::VarLong; #[derive(Serialize)] #[client_packet("play:set_border_lerp_size")] diff --git a/pumpkin-protocol/src/client/play/c_set_container_content.rs b/pumpkin-protocol/src/client/play/c_set_container_content.rs index 38e6b39ac..275739f23 100644 --- a/pumpkin-protocol/src/client/play/c_set_container_content.rs +++ b/pumpkin-protocol/src/client/play/c_set_container_content.rs @@ -1,4 +1,4 @@ -use crate::slot::Slot; +use crate::codec::slot::Slot; use crate::VarInt; use pumpkin_macros::client_packet; diff --git a/pumpkin-protocol/src/client/play/c_set_container_slot.rs b/pumpkin-protocol/src/client/play/c_set_container_slot.rs index 19e6b484a..e632da4e0 100644 --- a/pumpkin-protocol/src/client/play/c_set_container_slot.rs +++ b/pumpkin-protocol/src/client/play/c_set_container_slot.rs @@ -1,4 +1,4 @@ -use crate::slot::Slot; +use crate::codec::slot::Slot; use crate::VarInt; use pumpkin_macros::client_packet; diff --git a/pumpkin-protocol/src/client/play/c_sound_effect.rs b/pumpkin-protocol/src/client/play/c_sound_effect.rs index 14e92ec94..d34af9711 100644 --- a/pumpkin-protocol/src/client/play/c_sound_effect.rs +++ b/pumpkin-protocol/src/client/play/c_sound_effect.rs @@ -1,6 +1,7 @@ +use bytes::BufMut; use pumpkin_macros::client_packet; -use crate::{ClientPacket, IDOrSoundEvent, SoundCategory, SoundEvent, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, IDOrSoundEvent, SoundCategory, SoundEvent, VarInt}; #[client_packet("play:sound")] pub struct CSoundEffect { @@ -44,11 +45,11 @@ impl CSoundEffect { } impl ClientPacket for CSoundEffect { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(&self.sound_event.id); if self.sound_event.id.0 == 0 { if let Some(test) = &self.sound_event.sound_event { - bytebuf.put_string(&test.sound_name); + bytebuf.put_identifier(&test.sound_name); bytebuf.put_option(&test.range, |p, v| { p.put_f32(*v); diff --git a/pumpkin-protocol/src/client/play/c_store_cookie.rs b/pumpkin-protocol/src/client/play/c_store_cookie.rs index b9fc02cea..da715f22b 100644 --- a/pumpkin-protocol/src/client/play/c_store_cookie.rs +++ b/pumpkin-protocol/src/client/play/c_store_cookie.rs @@ -1,4 +1,4 @@ -use crate::{Identifier, VarInt}; +use crate::{codec::identifier::Identifier, VarInt}; use pumpkin_macros::client_packet; use serde::Serialize; diff --git a/pumpkin-protocol/src/client/play/c_teleport_entity.rs b/pumpkin-protocol/src/client/play/c_teleport_entity.rs index 4327ed658..2ec004922 100644 --- a/pumpkin-protocol/src/client/play/c_teleport_entity.rs +++ b/pumpkin-protocol/src/client/play/c_teleport_entity.rs @@ -1,10 +1,11 @@ +use bytes::BufMut; use pumpkin_core::math::vector3::Vector3; use pumpkin_macros::client_packet; -use crate::{ClientPacket, PositionFlag, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, PositionFlag, VarInt}; #[client_packet("play:teleport_entity")] -pub struct CTeleportEntitiy<'a> { +pub struct CTeleportEntity<'a> { entity_id: VarInt, position: Vector3, delta: Vector3, @@ -14,7 +15,7 @@ pub struct CTeleportEntitiy<'a> { on_ground: bool, } -impl<'a> CTeleportEntitiy<'a> { +impl<'a> CTeleportEntity<'a> { pub fn new( entity_id: VarInt, position: Vector3, @@ -36,8 +37,8 @@ impl<'a> CTeleportEntitiy<'a> { } } -impl ClientPacket for CTeleportEntitiy<'_> { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { +impl ClientPacket for CTeleportEntity<'_> { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_var_int(&self.entity_id); bytebuf.put_f64(self.position.x); bytebuf.put_f64(self.position.y); diff --git a/pumpkin-protocol/src/client/play/c_update_objectives.rs b/pumpkin-protocol/src/client/play/c_update_objectives.rs index e5ae2e2d1..41c45ff58 100644 --- a/pumpkin-protocol/src/client/play/c_update_objectives.rs +++ b/pumpkin-protocol/src/client/play/c_update_objectives.rs @@ -1,7 +1,8 @@ +use bytes::BufMut; use pumpkin_core::text::TextComponent; use pumpkin_macros::client_packet; -use crate::{ClientPacket, NumberFormat, VarInt}; +use crate::{bytebuf::ByteBufMut, ClientPacket, NumberFormat, VarInt}; #[client_packet("play:set_objective")] pub struct CUpdateObjectives<'a> { @@ -31,7 +32,7 @@ impl<'a> CUpdateObjectives<'a> { } impl ClientPacket for CUpdateObjectives<'_> { - fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) { + fn write(&self, bytebuf: &mut impl BufMut) { bytebuf.put_string(self.objective_name); bytebuf.put_u8(self.mode); if self.mode == 0 || self.mode == 2 { diff --git a/pumpkin-protocol/src/codec/bit_set.rs b/pumpkin-protocol/src/codec/bit_set.rs new file mode 100644 index 000000000..27aa329d3 --- /dev/null +++ b/pumpkin-protocol/src/codec/bit_set.rs @@ -0,0 +1,53 @@ +use std::num::NonZeroUsize; + +use bytes::{Buf, BufMut}; +use serde::{Serialize, Serializer}; + +use crate::bytebuf::ByteBuf; +use crate::bytebuf::ByteBufMut; + +use super::{var_int::VarInt, Codec, DecodeError}; + +pub struct BitSet(pub VarInt, pub Vec); + +impl Codec for BitSet { + /// The maximum size of the BitSet is `remaining / 8`. + const MAX_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(usize::MAX) }; + + fn written_size(&self) -> usize { + todo!() + } + + fn encode(&self, write: &mut impl BufMut) { + write.put_var_int(&self.0); + for b in &self.1 { + write.put_i64(*b); + } + } + + fn decode(read: &mut impl Buf) -> Result { + // read length + let length = read + .try_get_var_int() + .map_err(|_| DecodeError::Incomplete)?; + // vanilla uses remaining / 8 + if length.0 as usize >= read.remaining() / 8 { + return Err(DecodeError::TooLarge); + } + let mut array: Vec = Vec::with_capacity(size_of::() * length.0 as usize); + for _ in 0..length.0 { + let long = read.try_get_i64().map_err(|_| DecodeError::Incomplete)?; + array.push(long); + } + Ok(BitSet(length, array)) + } +} + +impl Serialize for BitSet { + fn serialize(&self, _serializer: S) -> Result + where + S: Serializer, + { + todo!() + } +} diff --git a/pumpkin-protocol/src/codec/identifier.rs b/pumpkin-protocol/src/codec/identifier.rs new file mode 100644 index 000000000..6be5675c7 --- /dev/null +++ b/pumpkin-protocol/src/codec/identifier.rs @@ -0,0 +1,101 @@ +use std::num::NonZeroUsize; + +use bytes::{Buf, BufMut}; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; + +use crate::bytebuf::{ByteBuf, ByteBufMut}; + +use super::{Codec, DecodeError}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Identifier { + pub namespace: String, + pub path: String, +} + +impl Identifier { + pub fn vanilla(path: &str) -> Self { + Self { + namespace: "minecraft".to_string(), + path: path.to_string(), + } + } +} +impl Codec for Identifier { + /// The maximum number of bytes a `Identifer` is the same as for a normal String. + const MAX_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(i16::MAX as usize) }; + + fn written_size(&self) -> usize { + todo!() + } + + fn encode(&self, write: &mut impl BufMut) { + write.put_string_len(&self.to_string(), Self::MAX_SIZE.get()); + } + + fn decode(read: &mut impl Buf) -> Result { + let identifer = read + .try_get_string_len(Self::MAX_SIZE.get()) + .map_err(|_| DecodeError::Incomplete)?; + match identifer.split_once(":") { + Some((namespace, path)) => Ok(Identifier { + namespace: namespace.to_string(), + path: path.to_string(), + }), + None => Err(DecodeError::Incomplete), + } + } +} + +impl Serialize for Identifier { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for Identifier { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct IdentifierVisitor; + + impl Visitor<'_> for IdentifierVisitor { + type Value = Identifier; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid Identifier (namespace:path)") + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + self.visit_str(&v) + } + + fn visit_str(self, identifer: &str) -> Result + where + E: serde::de::Error, + { + match identifer.split_once(":") { + Some((namespace, path)) => Ok(Identifier { + namespace: namespace.to_string(), + path: path.to_string(), + }), + None => Err(serde::de::Error::custom("Identifier can't be split")), + } + } + } + deserializer.deserialize_str(IdentifierVisitor) + } +} + +impl std::fmt::Display for Identifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.namespace, self.path) + } +} diff --git a/pumpkin-protocol/src/codec/mod.rs b/pumpkin-protocol/src/codec/mod.rs new file mode 100644 index 000000000..57af2e198 --- /dev/null +++ b/pumpkin-protocol/src/codec/mod.rs @@ -0,0 +1,28 @@ +use std::num::NonZeroUsize; + +use bytes::{Buf, BufMut}; +use thiserror::Error; + +pub mod bit_set; +pub mod identifier; +pub mod slot; +pub mod var_int; +pub mod var_long; + +pub trait Codec { + const MAX_SIZE: NonZeroUsize; + + fn written_size(&self) -> usize; + + fn encode(&self, write: &mut impl BufMut); + + fn decode(read: &mut impl Buf) -> Result; +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)] +pub enum DecodeError { + #[error("Incomplete VarInt decode")] + Incomplete, + #[error("VarInt is too large")] + TooLarge, +} diff --git a/pumpkin-protocol/src/slot.rs b/pumpkin-protocol/src/codec/slot.rs similarity index 100% rename from pumpkin-protocol/src/slot.rs rename to pumpkin-protocol/src/codec/slot.rs diff --git a/pumpkin-protocol/src/codec/var_int.rs b/pumpkin-protocol/src/codec/var_int.rs new file mode 100644 index 000000000..4f502687f --- /dev/null +++ b/pumpkin-protocol/src/codec/var_int.rs @@ -0,0 +1,157 @@ +use std::{num::NonZeroUsize, ops::Deref}; + +use super::{Codec, DecodeError}; +use bytes::{Buf, BufMut}; +use serde::{ + de::{SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; + +pub type VarIntType = i32; + +/** + * A variable-length integer type used by the Minecraft network protocol. + */ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VarInt(pub VarIntType); + +impl Codec for VarInt { + /// The maximum number of bytes a `VarInt` can occupy. + const MAX_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(5) }; + + /// Returns the exact number of bytes this varint will write when + /// [`Encode::encode`] is called, assuming no error occurs. + fn written_size(&self) -> usize { + match self.0 { + 0 => 1, + n => (31 - n.leading_zeros() as usize) / 7 + 1, + } + } + + fn encode(&self, write: &mut impl BufMut) { + let mut val = self.0; + for _ in 0..Self::MAX_SIZE.get() { + let b: u8 = val as u8 & 0b01111111; + val >>= 7; + write.put_u8(if val == 0 { b } else { b | 0b10000000 }); + if val == 0 { + break; + } + } + } + + fn decode(read: &mut impl Buf) -> Result { + let mut val = 0; + for i in 0..Self::MAX_SIZE.get() { + if !read.has_remaining() { + return Err(DecodeError::Incomplete); + } + let byte = read.get_u8(); + val |= (i32::from(byte) & 0x7F) << (i * 7); + if byte & 0x80 == 0 { + return Ok(VarInt(val)); + } + } + Err(DecodeError::TooLarge) + } +} + +impl From for VarInt { + fn from(value: i32) -> Self { + VarInt(value) + } +} + +impl From for VarInt { + fn from(value: u32) -> Self { + VarInt(value as i32) + } +} + +impl From for VarInt { + fn from(value: u8) -> Self { + VarInt(value as i32) + } +} + +impl From for VarInt { + fn from(value: usize) -> Self { + VarInt(value as i32) + } +} + +impl From for i32 { + fn from(value: VarInt) -> Self { + value.0 + } +} + +impl AsRef for VarInt { + fn as_ref(&self) -> &i32 { + &self.0 + } +} + +impl Deref for VarInt { + type Target = i32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Serialize for VarInt { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut value = self.0 as u32; + let mut buf = Vec::new(); + + while value > 0x7F { + buf.put_u8(value as u8 | 0x80); + value >>= 7; + } + + buf.put_u8(value as u8); + + serializer.serialize_bytes(&buf) + } +} + +impl<'de> Deserialize<'de> for VarInt { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VarIntVisitor; + + impl<'de> Visitor<'de> for VarIntVisitor { + type Value = VarInt; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid VarInt encoded in a byte sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut val = 0; + for i in 0..VarInt::MAX_SIZE.get() { + if let Some(byte) = seq.next_element::()? { + val |= (i32::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(VarInt(val)); + } + } else { + break; + } + } + Err(serde::de::Error::custom("VarInt was too large")) + } + } + + deserializer.deserialize_seq(VarIntVisitor) + } +} diff --git a/pumpkin-protocol/src/codec/var_long.rs b/pumpkin-protocol/src/codec/var_long.rs new file mode 100644 index 000000000..ceb2c1c99 --- /dev/null +++ b/pumpkin-protocol/src/codec/var_long.rs @@ -0,0 +1,158 @@ +use std::{num::NonZeroUsize, ops::Deref}; + +use super::{Codec, DecodeError}; +use bytes::{Buf, BufMut}; +use serde::{ + de::{self, SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; + +pub type VarLongType = i64; + +/** + * A variable-length long type used by the Minecraft network protocol. + */ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VarLong(pub VarLongType); + +impl Codec for VarLong { + /// The maximum number of bytes a `VarLong` can occupy. + const MAX_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(10) }; + + /// Returns the exact number of bytes this varlong will write when + /// [`Encode::encode`] is called, assuming no error occurs. + fn written_size(&self) -> usize { + match self.0 { + 0 => 1, + n => (31 - n.leading_zeros() as usize) / 7 + 1, + } + } + + fn encode(&self, write: &mut impl BufMut) { + let mut x = self.0; + for _ in 0..Self::MAX_SIZE.get() { + let byte = (x & 0x7F) as u8; + x >>= 7; + if x == 0 { + write.put_slice(&[byte]); + break; + } + write.put_slice(&[byte | 0x80]); + } + } + + fn decode(read: &mut impl Buf) -> Result { + let mut val = 0; + for i in 0..Self::MAX_SIZE.get() { + if !read.has_remaining() { + return Err(DecodeError::Incomplete); + } + let byte = read.get_u8(); + val |= (i64::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(VarLong(val)); + } + } + Err(DecodeError::TooLarge) + } +} + +impl From for VarLong { + fn from(value: i64) -> Self { + VarLong(value) + } +} + +impl From for VarLong { + fn from(value: u32) -> Self { + VarLong(value as i64) + } +} + +impl From for VarLong { + fn from(value: u8) -> Self { + VarLong(value as i64) + } +} + +impl From for VarLong { + fn from(value: usize) -> Self { + VarLong(value as i64) + } +} + +impl From for i64 { + fn from(value: VarLong) -> Self { + value.0 + } +} + +impl AsRef for VarLong { + fn as_ref(&self) -> &i64 { + &self.0 + } +} + +impl Deref for VarLong { + type Target = i64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Serialize for VarLong { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut value = self.0 as u64; + let mut buf = Vec::new(); + + while value > 0x7F { + buf.put_u8(value as u8 | 0x80); + value >>= 7; + } + + buf.put_u8(value as u8); + + serializer.serialize_bytes(&buf) + } +} + +impl<'de> Deserialize<'de> for VarLong { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VarLongVisitor; + + impl<'de> Visitor<'de> for VarLongVisitor { + type Value = VarLong; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid VarInt encoded in a byte sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut val = 0; + for i in 0..VarLong::MAX_SIZE.get() { + if let Some(byte) = seq.next_element::()? { + val |= (i64::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(VarLong(val)); + } + } else { + break; + } + } + Err(de::Error::custom("VarInt was too large")) + } + } + + deserializer.deserialize_seq(VarLongVisitor) + } +} diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index e4e20b22a..fec62014a 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -1,34 +1,44 @@ -use bytebuf::{packet_id::Packet, ByteBuffer, DeserializerError}; +use std::num::NonZeroU16; + +use bytebuf::{packet_id::Packet, ReadingError}; +use bytes::{Buf, BufMut, Bytes}; +use codec::{identifier::Identifier, var_int::VarInt}; use pumpkin_core::text::{style::Style, TextComponent}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, Serializer}; pub mod bytebuf; +#[cfg(feature = "clientbound")] pub mod client; +pub mod codec; pub mod packet_decoder; pub mod packet_encoder; +#[cfg(feature = "query")] pub mod query; +#[cfg(feature = "serverbound")] pub mod server; -pub mod slot; - -mod var_int; -pub use var_int::*; - -mod var_long; -pub use var_long::*; /// To current Minecraft protocol /// Don't forget to change this when porting -pub const CURRENT_MC_PROTOCOL: u32 = 769; +pub const CURRENT_MC_PROTOCOL: NonZeroU16 = unsafe { NonZeroU16::new_unchecked(769) }; pub const MAX_PACKET_SIZE: i32 = 2097152; -/// usally uses a namespace like "minecraft:thing" -pub type Identifier = String; -pub type VarIntType = i32; -pub type VarLongType = i64; pub type FixedBitSet = bytes::Bytes; -pub struct BitSet<'a>(pub VarInt, pub &'a [i64]); +/// Represents a compression threshold. +/// +/// The threshold determines the minimum size of data that should be compressed. +/// Data smaller than the threshold will not be compressed. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CompressionThreshold(pub u32); + +/// Represents a compression level. +/// +/// The level controls the amount of compression applied to the data. +/// Higher levels generally result in higher compression ratios but also +/// increase CPU usage. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CompressionLevel(pub u32); #[derive(Debug, PartialEq, Clone, Copy)] pub enum ConnectionState { @@ -76,21 +86,21 @@ pub struct IDOrSoundEvent { #[derive(Serialize)] pub struct SoundEvent { - pub sound_name: String, + pub sound_name: Identifier, pub range: Option, } pub struct RawPacket { pub id: VarInt, - pub bytebuf: ByteBuffer, + pub bytebuf: Bytes, } pub trait ClientPacket: Packet { - fn write(&self, bytebuf: &mut ByteBuffer); + fn write(&self, bytebuf: &mut impl BufMut); } pub trait ServerPacket: Packet + Sized { - fn read(bytebuf: &mut ByteBuffer) -> Result; + fn read(bytebuf: &mut impl Buf) -> Result; } #[derive(Serialize)] @@ -191,3 +201,73 @@ impl PositionFlag { flags.iter().fold(0, |acc, flag| acc | flag.get_mask()) } } + +pub enum Label<'a> { + BuiltIn(LinkType), + TextComponent(TextComponent<'a>), +} + +impl Serialize for Label<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Label::BuiltIn(link_type) => link_type.serialize(serializer), + Label::TextComponent(component) => component.serialize(serializer), + } + } +} + +#[derive(Serialize)] +pub struct Link<'a> { + pub is_built_in: bool, + pub label: Label<'a>, + pub url: &'a String, +} + +impl<'a> Link<'a> { + pub fn new(label: Label<'a>, url: &'a String) -> Self { + Self { + is_built_in: match label { + Label::BuiltIn(_) => true, + Label::TextComponent(_) => false, + }, + label, + url, + } + } +} + +pub enum LinkType { + BugReport, + CommunityGuidelines, + Support, + Status, + Feedback, + Community, + Website, + Forums, + News, + Announcements, +} + +impl Serialize for LinkType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + LinkType::BugReport => VarInt(0).serialize(serializer), + LinkType::CommunityGuidelines => VarInt(1).serialize(serializer), + LinkType::Support => VarInt(2).serialize(serializer), + LinkType::Status => VarInt(3).serialize(serializer), + LinkType::Feedback => VarInt(4).serialize(serializer), + LinkType::Community => VarInt(5).serialize(serializer), + LinkType::Website => VarInt(6).serialize(serializer), + LinkType::Forums => VarInt(7).serialize(serializer), + LinkType::News => VarInt(8).serialize(serializer), + LinkType::Announcements => VarInt(9).serialize(serializer), + } + } +} diff --git a/pumpkin-protocol/src/packet_decoder.rs b/pumpkin-protocol/src/packet_decoder.rs index 2a8986ca9..89ec4f42b 100644 --- a/pumpkin-protocol/src/packet_decoder.rs +++ b/pumpkin-protocol/src/packet_decoder.rs @@ -1,21 +1,23 @@ use aes::cipher::{generic_array::GenericArray, BlockDecryptMut, BlockSizeUser, KeyIvInit}; -use bytes::{Buf, BytesMut}; +use bytes::{Buf, Bytes, BytesMut}; use libdeflater::{DecompressionError, Decompressor}; use thiserror::Error; -use crate::{bytebuf::ByteBuffer, RawPacket, VarInt, VarIntDecodeError, MAX_PACKET_SIZE}; +use crate::{ + codec::{Codec, DecodeError}, + RawPacket, VarInt, MAX_PACKET_SIZE, +}; type Cipher = cfb8::Decryptor; // Decoder: Client -> Server // Supports ZLib decoding/decompression -// Supports Aes128 Encyption +// Supports Aes128 Encryption pub struct PacketDecoder { buf: BytesMut, decompress_buf: BytesMut, - compression: bool, cipher: Option, - decompressor: Decompressor, + decompressor: Option, } // Manual implementation of Default trait for PacketDecoder @@ -25,9 +27,8 @@ impl Default for PacketDecoder { Self { buf: BytesMut::new(), decompress_buf: BytesMut::new(), - compression: false, cipher: None, - decompressor: Decompressor::new(), + decompressor: None, } } } @@ -36,11 +37,12 @@ impl PacketDecoder { pub fn decode(&mut self) -> Result, PacketDecodeError> { let mut r = &self.buf[..]; - let packet_len = match VarInt::decode_partial(&mut r) { + let packet_len = match VarInt::decode(&mut r) { Ok(len) => len, - Err(VarIntDecodeError::Incomplete) => return Ok(None), - Err(VarIntDecodeError::TooLarge) => Err(PacketDecodeError::MalformedLength)?, + Err(DecodeError::Incomplete) => return Ok(None), + Err(DecodeError::TooLarge) => Err(PacketDecodeError::MalformedLength)?, }; + let packet_len = packet_len.0; if !(0..=MAX_PACKET_SIZE).contains(&packet_len) { Err(PacketDecodeError::OutOfBounds)? @@ -54,7 +56,7 @@ impl PacketDecoder { let packet_len_len = VarInt(packet_len).written_size(); let mut data; - if self.compression { + if let Some(decompressor) = &mut self.decompressor { r = &r[..packet_len as usize]; let data_len = VarInt::decode(&mut r) @@ -73,8 +75,7 @@ impl PacketDecoder { self.decompress_buf.resize(data_len as usize, 0); // Perform decompression using libdeflater - let decompressed_size = self - .decompressor + let decompressed_size = decompressor .zlib_decompress(r, &mut self.decompress_buf) .map_err(PacketDecodeError::from)?; @@ -112,31 +113,30 @@ impl PacketDecoder { data.advance(data.len() - r.len()); Ok(Some(RawPacket { id: packet_id, - bytebuf: ByteBuffer::new(data), + bytebuf: Bytes::from(data), })) } pub fn set_encryption(&mut self, key: Option<&[u8; 16]>) { if let Some(key) = key { assert!(self.cipher.is_none(), "encryption is already enabled"); - let mut cipher = Cipher::new_from_slices(key, key).expect("invalid key"); - // Don't forget to decrypt the data we already have. - Self::decrypt_bytes(&mut cipher, &mut self.buf); - self.cipher = Some(cipher); } else { assert!(self.cipher.is_some(), "encryption is already disabled"); - self.cipher = None; } } - /// Sets ZLib Deompression + /// Sets ZLib Decompression pub fn set_compression(&mut self, compression: bool) { - self.compression = compression; + if compression { + self.decompressor = Some(Decompressor::new()); + } else { + self.decompressor = None + } } fn decrypt_bytes(cipher: &mut Cipher, bytes: &mut [u8]) { @@ -182,10 +182,6 @@ impl PacketDecoder { pub enum PacketDecodeError { #[error("failed to decode packet ID")] DecodeID, - #[error("failed to write into decoder: {0}")] - FailedWrite(String), - #[error("failed to flush decoder")] - FailedFinish, #[error("packet exceeds maximum length")] TooLong, #[error("packet length is out of bounds")] @@ -204,12 +200,14 @@ impl From for PacketDecodeError { #[cfg(test)] mod tests { + use crate::bytebuf::ByteBufMut; + use super::*; use aes::Aes128; + use bytes::BufMut; use cfb8::cipher::AsyncStreamCipher; use cfb8::Encryptor as Cfb8Encryptor; use libdeflater::{CompressionLvl, Compressor}; - use std::io::Cursor; /// Helper function to compress data using libdeflater's Zlib compressor fn compress_zlib(data: &[u8]) -> Vec { @@ -223,11 +221,9 @@ mod tests { } /// Helper function to encrypt data using AES-128 CFB-8 mode - fn encrypt_aes128(data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Vec { + fn encrypt_aes128(data: &mut [u8], key: &[u8; 16], iv: &[u8; 16]) { let encryptor = Cfb8Encryptor::::new_from_slices(key, iv).expect("Invalid key/iv"); - let mut encrypted = data.to_vec(); - encryptor.encrypt(&mut encrypted); - encrypted + encryptor.encrypt(data); } /// Helper function to build a packet with optional compression and encryption @@ -238,18 +234,18 @@ mod tests { key: Option<&[u8; 16]>, iv: Option<&[u8; 16]>, ) -> Vec { - let mut buffer = ByteBuffer::empty(); + let mut buffer = BytesMut::new(); if compress { // Create a buffer that includes packet_id_varint and payload - let mut data_to_compress = ByteBuffer::empty(); + let mut data_to_compress = BytesMut::new(); let packet_id_varint = VarInt(packet_id); data_to_compress.put_var_int(&packet_id_varint); data_to_compress.put_slice(payload); // Compress the combined data - let compressed_payload = compress_zlib(data_to_compress.buf()); - let data_len = data_to_compress.buf().len() as i32; // 1 + payload.len() + let compressed_payload = compress_zlib(&data_to_compress); + let data_len = data_to_compress.len() as i32; // 1 + payload.len() let data_len_varint = VarInt(data_len); buffer.put_var_int(&data_len_varint); buffer.put_slice(&compressed_payload); @@ -261,24 +257,22 @@ mod tests { } // Calculate packet length: length of buffer - let packet_len = buffer.buf().len() as i32; + let packet_len = buffer.len() as i32; let packet_len_varint = VarInt(packet_len); let mut packet_length_encoded = Vec::new(); { - let mut cursor = Cursor::new(&mut packet_length_encoded); - packet_len_varint - .encode(&mut cursor) - .expect("VarInt encoding failed"); + packet_len_varint.encode(&mut packet_length_encoded); } // Create a new buffer for the entire packet let mut packet = Vec::new(); packet.extend_from_slice(&packet_length_encoded); - packet.extend_from_slice(buffer.buf()); + packet.extend_from_slice(&buffer); // Encrypt if key and iv are provided if let (Some(k), Some(v)) = (key, iv) { - encrypt_aes128(&packet, k, v) + encrypt_aes128(&mut packet, k, v); + packet } else { packet } @@ -305,9 +299,9 @@ mod tests { let result = decoder.decode().expect("Decoding failed"); assert!(result.is_some()); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!(raw_packet.id.0, packet_id); - assert_eq!(raw_packet.bytebuf.buf().as_ref(), payload); + assert_eq!(raw_packet.bytebuf.as_ref(), payload); } /// Test decoding with compression @@ -331,9 +325,9 @@ mod tests { let result = decoder.decode().expect("Decoding failed"); assert!(result.is_some()); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!(raw_packet.id.0, packet_id); - assert_eq!(raw_packet.bytebuf.buf().as_ref(), payload); + assert_eq!(raw_packet.bytebuf.as_ref(), payload); } /// Test decoding with encryption @@ -362,9 +356,9 @@ mod tests { let result = decoder.decode().expect("Decoding failed"); assert!(result.is_some()); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!(raw_packet.id.0, packet_id); - assert_eq!(raw_packet.bytebuf.buf().as_ref(), payload); + assert_eq!(raw_packet.bytebuf.as_ref(), payload); } /// Test decoding with both compression and encryption @@ -393,9 +387,9 @@ mod tests { let result = decoder.decode().expect("Decoding failed"); assert!(result.is_some()); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!(raw_packet.id.0, packet_id); - assert_eq!(raw_packet.bytebuf.buf().as_ref(), payload); + assert_eq!(raw_packet.bytebuf.as_ref(), payload); } /// Test decoding with invalid compressed data @@ -406,21 +400,21 @@ mod tests { let invalid_compressed_data = vec![0xFF, 0xFF, 0xFF]; // Invalid Zlib data // Build the packet with compression enabled but invalid compressed data - let mut buffer = ByteBuffer::empty(); + let mut buffer = BytesMut::new(); let data_len_varint = VarInt(data_len); buffer.put_var_int(&data_len_varint); buffer.put_slice(&invalid_compressed_data); // Calculate packet length: VarInt(data_len) + invalid compressed data - let packet_len = buffer.buf().len() as i32; + let packet_len = buffer.len() as i32; let packet_len_varint = VarInt(packet_len); // Create a new buffer for the entire packet - let mut packet_buffer = ByteBuffer::empty(); + let mut packet_buffer = BytesMut::new(); packet_buffer.put_var_int(&packet_len_varint); - packet_buffer.put_slice(buffer.buf()); + packet_buffer.put_slice(&buffer); - let packet_bytes = packet_buffer.buf().to_vec(); + let packet_bytes = packet_buffer; // Initialize the decoder with compression enabled let mut decoder = PacketDecoder::default(); @@ -458,9 +452,9 @@ mod tests { let result = decoder.decode().expect("Decoding failed"); assert!(result.is_some()); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!(raw_packet.id.0, packet_id); - assert_eq!(raw_packet.bytebuf.buf().as_ref(), payload); + assert_eq!(raw_packet.bytebuf.as_ref(), payload); } /// Test decoding with maximum length packet @@ -492,13 +486,13 @@ mod tests { "Decoder returned None when it should have decoded a packet" ); - let mut raw_packet = result.unwrap(); + let raw_packet = result.unwrap(); assert_eq!( raw_packet.id.0, packet_id, "Decoded packet_id does not match" ); assert_eq!( - raw_packet.bytebuf.buf().as_ref(), + raw_packet.bytebuf.as_ref(), &payload[..], "Decoded payload does not match" ); diff --git a/pumpkin-protocol/src/packet_encoder.rs b/pumpkin-protocol/src/packet_encoder.rs index 6d162a193..006e1a995 100644 --- a/pumpkin-protocol/src/packet_encoder.rs +++ b/pumpkin-protocol/src/packet_encoder.rs @@ -1,25 +1,24 @@ -use std::io::Write; - use aes::cipher::{generic_array::GenericArray, BlockEncryptMut, BlockSizeUser, KeyIvInit}; use bytes::{BufMut, BytesMut}; -use pumpkin_config::compression::CompressionInfo; use thiserror::Error; use libdeflater::{CompressionLvl, Compressor}; -use crate::{bytebuf::ByteBuffer, ClientPacket, VarInt, MAX_PACKET_SIZE}; +use crate::{ + codec::Codec, ClientPacket, CompressionLevel, CompressionThreshold, VarInt, MAX_PACKET_SIZE, +}; type Cipher = cfb8::Encryptor; -// Encoder: Server -> Client -// Supports ZLib endecoding/compression -// Supports Aes128 Encyption +/// Encoder: Server -> Client +/// Supports ZLib endecoding/compression +/// Supports Aes128 Encryption pub struct PacketEncoder { buf: BytesMut, compress_buf: Vec, - compression: Option, cipher: Option, - compressor: Compressor, // Reuse the compressor for all packets + // compression and compression threshold + compression: Option<(Compressor, CompressionThreshold)>, } // Manual implementation of Default trait for PacketEncoder @@ -29,32 +28,53 @@ impl Default for PacketEncoder { Self { buf: BytesMut::with_capacity(1024), compress_buf: Vec::with_capacity(1024), - compression: None, cipher: None, - compressor: Compressor::new(CompressionLvl::fastest()), // init compressor with no compression level + compression: None, // init compressor with fastest compression level } } } impl PacketEncoder { + /// Appends a Clientbound `ClientPacket` to the internal buffer and applies compression when needed. + /// + /// If compression is enabled and the packet size exceeds the threshold, the packet is compressed. + /// The packet is prefixed with its length and, if compressed, the uncompressed data length. + /// The packet format is as follows: + /// + /// **Uncompressed:** + /// |-----------------------| + /// | Packet Length (VarInt)| + /// |-----------------------| + /// | Packet ID (VarInt) | + /// |-----------------------| + /// | Data (Byte Array) | + /// |-----------------------| + /// + /// **Compressed:** + /// |------------------------| + /// | Packet Length (VarInt) | + /// |------------------------| + /// | Data Length (VarInt) | + /// |------------------------| + /// | Packet ID (VarInt) | + /// |------------------------| + /// | Data (Byte Array) | + /// |------------------------| + /// + /// - `Packet Length`: The total length of the packet *excluding* the `Packet Length` field itself. + /// - `Data Length`: (Only present in compressed packets) The length of the uncompressed `Packet ID` and `Data`. + /// - `Packet ID`: The ID of the packet. + /// - `Data`: The packet's data. pub fn append_packet(&mut self, packet: &P) -> Result<(), PacketEncodeError> { let start_len = self.buf.len(); - let mut writer = (&mut self.buf).writer(); - - let mut packet_buf = ByteBuffer::empty(); - VarInt(P::PACKET_ID) - .encode(&mut writer) - .map_err(|_| PacketEncodeError::EncodeID)?; - packet.write(&mut packet_buf); - - writer - .write(packet_buf.buf()) - .map_err(|_| PacketEncodeError::EncodeFailedWrite)?; - + // Write the Packet ID first + VarInt(P::PACKET_ID).encode(&mut self.buf); + // Now write the packet into an empty buffer + packet.write(&mut self.buf); let data_len = self.buf.len() - start_len; - if let Some(compression) = &self.compression { - if data_len > compression.threshold as usize { + if let Some((compressor, compression_threshold)) = &mut self.compression { + if data_len > compression_threshold.0 as usize { // Get the data to compress let data_to_compress = &self.buf[start_len..]; @@ -62,17 +82,15 @@ impl PacketEncoder { self.compress_buf.clear(); // Compute the maximum size of compressed data - let max_compressed_size = - self.compressor.zlib_compress_bound(data_to_compress.len()); + let max_compressed_size = compressor.zlib_compress_bound(data_to_compress.len()); // Ensure compress_buf has enough capacity self.compress_buf.resize(max_compressed_size, 0); // Compress the data - let compressed_size = self - .compressor + let compressed_size = compressor .zlib_compress(data_to_compress, &mut self.compress_buf) - .map_err(|_| PacketEncodeError::CompressionFailed)?; + .map_err(|e| PacketEncodeError::CompressionFailed(e.to_string()))?; // Resize compress_buf to actual compressed size self.compress_buf.resize(compressed_size, 0); @@ -82,26 +100,20 @@ impl PacketEncoder { let packet_len = data_len_size + compressed_size; if packet_len >= MAX_PACKET_SIZE as usize { - return Err(PacketEncodeError::TooLong); + return Err(PacketEncodeError::TooLong(packet_len)); } self.buf.truncate(start_len); - let mut writer = (&mut self.buf).writer(); - - VarInt(packet_len as i32) - .encode(&mut writer) - .map_err(|_| PacketEncodeError::EncodeLength)?; - VarInt(data_len as i32) - .encode(&mut writer) - .map_err(|_| PacketEncodeError::EncodeData)?; + VarInt(packet_len as i32).encode(&mut self.buf); + VarInt(data_len as i32).encode(&mut self.buf); self.buf.extend_from_slice(&self.compress_buf); } else { let data_len_size = 1; let packet_len = data_len_size + data_len; if packet_len >= MAX_PACKET_SIZE as usize { - Err(PacketEncodeError::TooLong)? + Err(PacketEncodeError::TooLong(packet_len))? } let packet_len_size = VarInt(packet_len as i32).written_size(); @@ -114,13 +126,9 @@ impl PacketEncoder { let mut front = &mut self.buf[start_len..]; - VarInt(packet_len as i32) - .encode(&mut front) - .map_err(|_| PacketEncodeError::EncodeLength)?; + VarInt(packet_len as i32).encode(&mut front); // Zero for no compression on this packet. - VarInt(0) - .encode(front) - .map_err(|_| PacketEncodeError::EncodeData)?; + VarInt(0).encode(&mut front); } return Ok(()); @@ -129,7 +137,7 @@ impl PacketEncoder { let packet_len = data_len; if packet_len >= MAX_PACKET_SIZE as usize { - Err(PacketEncodeError::TooLong)? + Err(PacketEncodeError::TooLong(packet_len))? } let packet_len_size = VarInt(packet_len as i32).written_size(); @@ -138,13 +146,12 @@ impl PacketEncoder { self.buf .copy_within(start_len..start_len + data_len, start_len + packet_len_size); - let front = &mut self.buf[start_len..]; - VarInt(packet_len as i32) - .encode(front) - .map_err(|_| PacketEncodeError::EncodeID)?; + let mut front = &mut self.buf[start_len..]; + VarInt(packet_len as i32).encode(&mut front); Ok(()) } + /// Enable encryption for taking all packets buffer ` pub fn set_encryption(&mut self, key: Option<&[u8; 16]>) { if let Some(key) = key { assert!(self.cipher.is_none(), "encryption is already enabled"); @@ -157,23 +164,41 @@ impl PacketEncoder { } } - /// Enables ZLib Compression - pub fn set_compression(&mut self, compression: Option) { - self.compression = compression; - - // Reset the compressor with the new compression level - if let Some(compression) = &self.compression { - let compression_level = compression.level as i32; - - let level = match CompressionLvl::new(compression_level) { - Ok(level) => level, - Err(_) => return, - }; - - self.compressor = Compressor::new(level); + /// Enables or disables Zlib compression. + /// + /// If `compression` is `Some`, compression is enabled with the given `threshold` + /// for triggering compression and the specified `level`. If `compression` is + /// `None`, compression is disabled. + /// + /// # Errors + /// + /// Returns an `CompressionLevelError` if an invalid compression level is provided. + pub fn set_compression( + &mut self, + compression: Option<(CompressionThreshold, CompressionLevel)>, + ) -> Result<(), CompressionLevelError> { + match compression { + Some((threshold, level)) => { + let level = + CompressionLvl::new(level.0 as i32).map_err(|_| CompressionLevelError)?; + self.compression = Some((Compressor::new(level), threshold)); + } + None => { + self.compression = None; + } } + Ok(()) } + /// Encrypts the data in the internal buffer and returns it as a `BytesMut`. + /// + /// If a cipher is set, the data is encrypted in-place using block cipher encryption. + /// The buffer is processed in chunks of the cipher's block size. If the buffer's + /// length is not a multiple of the block size, the last partial block is *not* encrypted. + /// It's important to ensure that the data being encrypted is padded appropriately + /// beforehand if necessary. + /// + /// If no cipher is set, the buffer is returned as is. pub fn take(&mut self) -> BytesMut { if let Some(cipher) = &mut self.cipher { for chunk in self.buf.chunks_mut(Cipher::block_size()) { @@ -187,36 +212,23 @@ impl PacketEncoder { } #[derive(Error, Debug)] -pub enum PacketEncodeError { - #[error("failed to encode packet ID")] - EncodeID, - #[error("failed to encode packet Length")] - EncodeLength, - #[error("failed to encode packet data")] - EncodeData, - #[error("failed to write encoded packet")] - EncodeFailedWrite, - #[error("packet exceeds maximum length")] - TooLong, - #[error("invalid compression level")] - InvalidCompressionLevel, - #[error("compression failed")] - CompressionFailed, -} +#[error("Invalid compression Level")] +pub struct CompressionLevelError; -impl PacketEncodeError { - pub fn kickable(&self) -> bool { - // We no longer have a connection, so dont try to kick the player, just close - !matches!(self, Self::EncodeData | Self::EncodeFailedWrite) - } +/// Errors that can occur during packet encoding. +#[derive(Error, Debug)] +pub enum PacketEncodeError { + #[error("Packet exceeds maximum length: {0}")] + TooLong(usize), + #[error("Compression failed {0}")] + CompressionFailed(String), } #[cfg(test)] mod tests { use super::*; - use crate::bytebuf::packet_id::Packet; use crate::client::status::CStatusResponse; - use crate::VarIntDecodeError; + use crate::{bytebuf::packet_id::Packet, codec::DecodeError}; use aes::Aes128; use cfb8::cipher::AsyncStreamCipher; use cfb8::Decryptor as Cfb8Decryptor; @@ -240,7 +252,7 @@ mod tests { } /// Helper function to decode a VarInt from bytes - fn decode_varint(buffer: &mut &[u8]) -> Result { + fn decode_varint(buffer: &mut &[u8]) -> Result { VarInt::decode(buffer).map(|varint| varint.0) } @@ -254,25 +266,23 @@ mod tests { } /// Helper function to decrypt data using AES-128 CFB-8 mode - fn decrypt_aes128(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Vec { + fn decrypt_aes128(encrypted_data: &mut [u8], key: &[u8; 16], iv: &[u8; 16]) { let decryptor = Cfb8Decryptor::::new_from_slices(key, iv).expect("Invalid key/iv"); - let mut decrypted = encrypted_data.to_vec(); - decryptor.decrypt(&mut decrypted); - decrypted + decryptor.decrypt(encrypted_data); } /// Helper function to build a packet with optional compression and encryption fn build_packet_with_encoder( packet: &T, - compression_info: Option, + compression_info: Option<(CompressionThreshold, CompressionLevel)>, key: Option<&[u8; 16]>, ) -> BytesMut { let mut encoder = PacketEncoder::default(); if let Some(compression) = compression_info { - encoder.set_compression(Some(compression)); + encoder.set_compression(Some(compression)).unwrap(); } else { - encoder.set_compression(None); + encoder.set_compression(None).unwrap(); } if let Some(key) = key { @@ -312,10 +322,10 @@ mod tests { // Remaining buffer is the payload // We need to obtain the expected payload - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); - assert_eq!(buffer, expected_payload.buf()); + assert_eq!(buffer, expected_payload); } /// Test encoding with compression @@ -324,14 +334,12 @@ mod tests { // Create a CStatusResponse packet let packet = CStatusResponse::new("{\"description\": \"A Minecraft Server\"}"); - // Compression threshold is set to 0 to force compression - let compression_info = CompressionInfo { - threshold: 0, - level: 6, // Standard compression level - }; - // Build the packet with compression enabled - let packet_bytes = build_packet_with_encoder(&packet, Some(compression_info), None); + let packet_bytes = build_packet_with_encoder( + &packet, + Some((CompressionThreshold(0), CompressionLevel(6))), + None, + ); // Decode the packet manually to verify correctness let mut buffer = &packet_bytes[..]; @@ -346,10 +354,10 @@ mod tests { // Read data length VarInt (uncompressed data length) let data_length = decode_varint(&mut buffer).expect("Failed to decode data length"); - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); let uncompressed_data_length = - VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.buf().len(); + VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.len(); assert_eq!(data_length as usize, uncompressed_data_length); // Remaining buffer is the compressed data @@ -368,7 +376,7 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload - assert_eq!(decompressed_buffer, expected_payload.buf()); + assert_eq!(decompressed_buffer, expected_payload); } /// Test encoding with encryption @@ -381,13 +389,13 @@ mod tests { let key = [0x00u8; 16]; // Example key // Build the packet with encryption enabled (no compression) - let packet_bytes = build_packet_with_encoder(&packet, None, Some(&key)); + let mut packet_bytes = build_packet_with_encoder(&packet, None, Some(&key)); // Decrypt the packet - let decrypted_packet = decrypt_aes128(&packet_bytes, &key, &key); + decrypt_aes128(&mut packet_bytes, &key, &key); // Decode the packet manually to verify correctness - let mut buffer = &decrypted_packet[..]; + let mut buffer = &packet_bytes[..]; // Read packet length VarInt let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length"); @@ -402,10 +410,10 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); - assert_eq!(buffer, expected_payload.buf()); + assert_eq!(buffer, expected_payload); } /// Test encoding with both compression and encryption @@ -414,23 +422,22 @@ mod tests { // Create a CStatusResponse packet let packet = CStatusResponse::new("{\"description\": \"A Minecraft Server\"}"); - // Compression threshold is set to 0 to force compression - let compression_info = CompressionInfo { - threshold: 0, - level: 6, // Standard compression level - }; - // Encryption key and IV (IV is the same as key in this case) let key = [0x01u8; 16]; // Example key // Build the packet with both compression and encryption enabled - let packet_bytes = build_packet_with_encoder(&packet, Some(compression_info), Some(&key)); + // Compression threshold is set to 0 to force compression + let mut packet_bytes = build_packet_with_encoder( + &packet, + Some((CompressionThreshold(0), CompressionLevel(6))), + Some(&key), + ); // Decrypt the packet - let decrypted_packet = decrypt_aes128(&packet_bytes, &key, &key); + decrypt_aes128(&mut packet_bytes, &key, &key); // Decode the packet manually to verify correctness - let mut buffer = &decrypted_packet[..]; + let mut buffer = &packet_bytes[..]; // Read packet length VarInt let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length"); @@ -442,10 +449,10 @@ mod tests { // Read data length VarInt (uncompressed data length) let data_length = decode_varint(&mut buffer).expect("Failed to decode data length"); - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); let uncompressed_data_length = - VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.buf().len(); + VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.len(); assert_eq!(data_length as usize, uncompressed_data_length); // Remaining buffer is the compressed data @@ -464,7 +471,7 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload - assert_eq!(decompressed_buffer, expected_payload.buf()); + assert_eq!(decompressed_buffer, expected_payload); } /// Test encoding with zero-length payload @@ -492,15 +499,15 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload (empty) - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); assert_eq!( buffer.len(), - expected_payload.buf().len(), + expected_payload.len(), "Payload length mismatch" ); - assert_eq!(buffer, expected_payload.buf()); + assert_eq!(buffer, expected_payload); } /// Test encoding with maximum length payload @@ -537,10 +544,10 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); - assert_eq!(buffer, expected_payload.buf()); + assert_eq!(buffer, expected_payload); } /// Test encoding a packet that exceeds MAX_PACKET_SIZE @@ -562,14 +569,13 @@ mod tests { // Create a CStatusResponse packet with small payload let packet = CStatusResponse::new("Hi"); - // Compression threshold is set to a value higher than payload length - let compression_info = CompressionInfo { - threshold: 10, - level: 6, // Standard compression level - }; - // Build the packet with compression enabled - let packet_bytes = build_packet_with_encoder(&packet, Some(compression_info), None); + // Compression threshold is set to a value higher than payload length + let packet_bytes = build_packet_with_encoder( + &packet, + Some((CompressionThreshold(10), CompressionLevel(6))), + None, + ); // Decode the packet manually to verify that it was not compressed let mut buffer = &packet_bytes[..]; @@ -594,9 +600,9 @@ mod tests { assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID); // Remaining buffer is the payload - let mut expected_payload = ByteBuffer::empty(); + let mut expected_payload = BytesMut::new(); packet.write(&mut expected_payload); - assert_eq!(buffer, expected_payload.buf()); + assert_eq!(buffer, expected_payload); } } diff --git a/pumpkin-protocol/src/query.rs b/pumpkin-protocol/src/query.rs index 8943d91ef..9cd0444ce 100644 --- a/pumpkin-protocol/src/query.rs +++ b/pumpkin-protocol/src/query.rs @@ -24,7 +24,7 @@ impl RawQueryPacket { match reader.read_u16().await.map_err(|_| ())? { // Magic should always equal 65277 - // Since it denotes the protocl being used + // Since it denotes the protocol being used // Should not attempt to decode packets with other magic values 65277 => Ok(Self { packet_type: PacketType::from_u8(reader.read_u8().await.map_err(|_| ())?) @@ -52,7 +52,7 @@ impl SHandshake { #[derive(PartialEq, Debug)] pub struct SStatusRequest { pub session_id: i32, - pub challange_token: i32, + pub challenge_token: i32, // Full status request and basic status request are pretty much similar // So might as just use the same struct pub is_full_request: bool, @@ -62,7 +62,7 @@ impl SStatusRequest { pub async fn decode(packet: &mut RawQueryPacket) -> Result { Ok(Self { session_id: packet.reader.read_i32().await.map_err(|_| ())?, - challange_token: packet.reader.read_i32().await.map_err(|_| ())?, + challenge_token: packet.reader.read_i32().await.map_err(|_| ())?, is_full_request: { let mut buf = [0; 4]; @@ -74,7 +74,7 @@ impl SStatusRequest { Ok(0) => false, Ok(4) => true, _ => { - // Just ingnore malformed packets or errors + // Just ignore malformed packets or errors return Err(()); } } @@ -88,7 +88,7 @@ pub struct CHandshake { // For simplicity use a number type // Should be encoded as string here // Will be converted in encoding - pub challange_token: i32, + pub challenge_token: i32, } impl CHandshake { @@ -99,10 +99,10 @@ impl CHandshake { buf.write_u8(9).await.unwrap(); // Session ID buf.write_i32(self.session_id).await.unwrap(); - // Challange token + // Challenge token // Use CString to add null terminator and ensure no null bytes in the middle of data // Unwrap here since there should be no errors with nulls in the middle of data - let token = CString::new(self.challange_token.to_string()).unwrap(); + let token = CString::new(self.challenge_token.to_string()).unwrap(); buf.extend_from_slice(token.as_bytes_with_nul()); buf @@ -249,7 +249,7 @@ async fn test_handshake_response() { let packet = CHandshake { session_id: 1, - challange_token: 9513307, + challenge_token: 9513307, }; assert_eq!(bytes, packet.encode().await) @@ -265,7 +265,7 @@ async fn test_basic_stat_request() { let actual_packet = SStatusRequest { session_id: 1, - challange_token: 9513307, + challenge_token: 9513307, is_full_request: false, }; @@ -304,7 +304,7 @@ async fn test_full_stat_request() { let actual_packet = SStatusRequest { session_id: 1, - challange_token: 9513307, + challenge_token: 9513307, is_full_request: true, }; diff --git a/pumpkin-protocol/src/server/config/s_cookie_response.rs b/pumpkin-protocol/src/server/config/s_cookie_response.rs index 3f8185351..9f683a5ca 100644 --- a/pumpkin-protocol/src/server/config/s_cookie_response.rs +++ b/pumpkin-protocol/src/server/config/s_cookie_response.rs @@ -1,25 +1,28 @@ +use bytes::Buf; use pumpkin_macros::server_packet; -use serde::de; -use crate::bytebuf::{ByteBuffer, DeserializerError}; -use crate::{Identifier, ServerPacket, VarInt}; +use crate::{ + bytebuf::{ByteBuf, ReadingError}, + codec::identifier::Identifier, + ServerPacket, VarInt, +}; #[server_packet("config:cookie_response")] /// Response to a Cookie Request (configuration) from the server. /// The Notchian (vanilla) server only accepts responses of up to 5 kiB in size. -pub struct SCookieResponse { +pub struct SConfigCookieResponse { pub key: Identifier, pub has_payload: bool, pub payload_length: Option, - pub payload: Option>, // 5120, + pub payload: Option, // 5120, } -const MAX_PAYLOAD_SIZE: i32 = 5120; +const MAX_COOKIE_LENGTH: usize = 5120; -impl ServerPacket for SCookieResponse { - fn read(bytebuf: &mut ByteBuffer) -> Result { - let key = bytebuf.get_string()?; - let has_payload = bytebuf.get_bool()?; +impl ServerPacket for SConfigCookieResponse { + fn read(bytebuf: &mut impl Buf) -> Result { + let key = bytebuf.try_get_identifer()?; + let has_payload = bytebuf.try_get_bool()?; if !has_payload { return Ok(Self { @@ -30,16 +33,10 @@ impl ServerPacket for SCookieResponse { }); } - let payload_length = bytebuf.get_var_int()?; + let payload_length = bytebuf.try_get_var_int()?; let length = payload_length.0; - if length > MAX_PAYLOAD_SIZE { - return Err(de::Error::custom( - "Payload exceeds the maximum allowed size (5120 bytes)", - )); - } - - let payload = bytebuf.copy_to_bytes(length as usize)?.to_vec(); + let payload = bytebuf.try_copy_to_bytes_len(length as usize, MAX_COOKIE_LENGTH)?; Ok(Self { key, diff --git a/pumpkin-protocol/src/server/config/s_plugin_message.rs b/pumpkin-protocol/src/server/config/s_plugin_message.rs index ea20a35c0..e5313443b 100644 --- a/pumpkin-protocol/src/server/config/s_plugin_message.rs +++ b/pumpkin-protocol/src/server/config/s_plugin_message.rs @@ -1,21 +1,24 @@ +use bytes::Buf; use pumpkin_macros::server_packet; use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, - Identifier, ServerPacket, + bytebuf::{ByteBuf, ReadingError}, + codec::identifier::Identifier, + ServerPacket, }; +const MAX_PAYLOAD_SIZE: usize = 1048576; #[server_packet("config:custom_payload")] pub struct SPluginMessage { pub channel: Identifier, - pub data: Vec, + pub data: bytes::Bytes, } impl ServerPacket for SPluginMessage { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - channel: bytebuf.get_string()?, - data: bytebuf.get_slice().to_vec(), + channel: bytebuf.try_get_identifer()?, + data: bytebuf.try_copy_to_bytes_len(bytebuf.remaining(), MAX_PAYLOAD_SIZE)?, }) } } diff --git a/pumpkin-protocol/src/server/handshake/mod.rs b/pumpkin-protocol/src/server/handshake/mod.rs index a1d743528..9436e5696 100644 --- a/pumpkin-protocol/src/server/handshake/mod.rs +++ b/pumpkin-protocol/src/server/handshake/mod.rs @@ -1,7 +1,8 @@ +use bytes::Buf; use pumpkin_macros::server_packet; use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, + bytebuf::{ByteBuf, ReadingError}, ConnectionState, ServerPacket, VarInt, }; @@ -14,12 +15,12 @@ pub struct SHandShake { } impl ServerPacket for SHandShake { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - protocol_version: bytebuf.get_var_int()?, - server_address: bytebuf.get_string_len(255)?, - server_port: bytebuf.get_u16()?, - next_state: bytebuf.get_var_int()?.into(), + protocol_version: bytebuf.try_get_var_int()?, + server_address: bytebuf.try_get_string_len(255)?, + server_port: bytebuf.try_get_u16()?, + next_state: bytebuf.try_get_var_int()?.into(), }) } } diff --git a/pumpkin-protocol/src/server/login/s_cookie_response.rs b/pumpkin-protocol/src/server/login/s_cookie_response.rs index a5fcf06ee..6a6d7383c 100644 --- a/pumpkin-protocol/src/server/login/s_cookie_response.rs +++ b/pumpkin-protocol/src/server/login/s_cookie_response.rs @@ -1,24 +1,27 @@ -use crate::bytebuf::{ByteBuffer, DeserializerError}; -use crate::{Identifier, ServerPacket, VarInt}; +use crate::{ + bytebuf::{ByteBuf, ReadingError}, + codec::identifier::Identifier, + ServerPacket, VarInt, +}; +use bytes::Buf; use pumpkin_macros::server_packet; -use serde::de; #[server_packet("login:cookie_response")] /// Response to a Cookie Request (login) from the server. /// The Notchian server only accepts responses of up to 5 kiB in size. -pub struct SCookieResponse { +pub struct SLoginCookieResponse { pub key: Identifier, pub has_payload: bool, pub payload_length: Option, - pub payload: Option>, // 5120, + pub payload: Option, // 5120, } -const MAX_PAYLOAD_SIZE: i32 = 5120; +const MAX_COOKIE_LENGTH: usize = 5120; -impl ServerPacket for SCookieResponse { - fn read(bytebuf: &mut ByteBuffer) -> Result { - let key = bytebuf.get_string()?; - let has_payload = bytebuf.get_bool()?; +impl ServerPacket for SLoginCookieResponse { + fn read(bytebuf: &mut impl Buf) -> Result { + let key = bytebuf.try_get_identifer()?; + let has_payload = bytebuf.try_get_bool()?; if !has_payload { return Ok(Self { @@ -29,16 +32,10 @@ impl ServerPacket for SCookieResponse { }); } - let payload_length = bytebuf.get_var_int()?; + let payload_length = bytebuf.try_get_var_int()?; let length = payload_length.0; - if length > MAX_PAYLOAD_SIZE { - return Err(de::Error::custom( - "Payload exceeds the maximum allowed size (5120 bytes)", - )); - } - - let payload = bytebuf.copy_to_bytes(length as usize)?.to_vec(); + let payload = bytebuf.try_copy_to_bytes_len(length as usize, MAX_COOKIE_LENGTH)?; Ok(Self { key, diff --git a/pumpkin-protocol/src/server/login/s_encryption_response.rs b/pumpkin-protocol/src/server/login/s_encryption_response.rs index 06c224624..f094231dd 100644 --- a/pumpkin-protocol/src/server/login/s_encryption_response.rs +++ b/pumpkin-protocol/src/server/login/s_encryption_response.rs @@ -1,29 +1,30 @@ +use bytes::Buf; use pumpkin_macros::server_packet; use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, + bytebuf::{ByteBuf, ReadingError}, ServerPacket, VarInt, }; #[server_packet("login:key")] pub struct SEncryptionResponse { pub shared_secret_length: VarInt, - pub shared_secret: Vec, + pub shared_secret: bytes::Bytes, pub verify_token_length: VarInt, - pub verify_token: Vec, + pub verify_token: bytes::Bytes, } impl ServerPacket for SEncryptionResponse { - fn read(bytebuf: &mut ByteBuffer) -> Result { - let shared_secret_length = bytebuf.get_var_int()?; - let shared_secret = bytebuf.copy_to_bytes(shared_secret_length.0 as usize)?; - let verify_token_length = bytebuf.get_var_int()?; - let verify_token = bytebuf.copy_to_bytes(shared_secret_length.0 as usize)?; + fn read(bytebuf: &mut impl Buf) -> Result { + let shared_secret_length = bytebuf.try_get_var_int()?; + let shared_secret = bytebuf.try_copy_to_bytes(shared_secret_length.0 as usize)?; + let verify_token_length = bytebuf.try_get_var_int()?; + let verify_token = bytebuf.try_copy_to_bytes(shared_secret_length.0 as usize)?; Ok(Self { shared_secret_length, - shared_secret: shared_secret.to_vec(), + shared_secret, verify_token_length, - verify_token: verify_token.to_vec(), + verify_token, }) } } diff --git a/pumpkin-protocol/src/server/login/s_login_start.rs b/pumpkin-protocol/src/server/login/s_login_start.rs index 7a4a724da..88bda1877 100644 --- a/pumpkin-protocol/src/server/login/s_login_start.rs +++ b/pumpkin-protocol/src/server/login/s_login_start.rs @@ -1,7 +1,8 @@ +use bytes::Buf; use pumpkin_macros::server_packet; use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, + bytebuf::{ByteBuf, ReadingError}, ServerPacket, }; @@ -12,10 +13,10 @@ pub struct SLoginStart { } impl ServerPacket for SLoginStart { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - name: bytebuf.get_string_len(16)?, - uuid: bytebuf.get_uuid()?, + name: bytebuf.try_get_string_len(16)?, + uuid: bytebuf.try_get_uuid()?, }) } } diff --git a/pumpkin-protocol/src/server/login/s_plugin_response.rs b/pumpkin-protocol/src/server/login/s_plugin_response.rs index 24656471e..bffc72b93 100644 --- a/pumpkin-protocol/src/server/login/s_plugin_response.rs +++ b/pumpkin-protocol/src/server/login/s_plugin_response.rs @@ -1,21 +1,24 @@ use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, + bytebuf::{ByteBuf, ReadingError}, ServerPacket, VarInt, }; -use bytes::BytesMut; +use bytes::{Buf, Bytes}; use pumpkin_macros::server_packet; +const MAX_PAYLOAD_SIZE: usize = 1048576; + #[server_packet("login:custom_query_answer")] pub struct SLoginPluginResponse { pub message_id: VarInt, - pub data: Option, + pub data: Option, } impl ServerPacket for SLoginPluginResponse { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - message_id: bytebuf.get_var_int()?, - data: bytebuf.get_option(|v| Ok(v.get_slice()))?, + message_id: bytebuf.try_get_var_int()?, + data: bytebuf + .try_get_option(|v| v.try_copy_to_bytes_len(v.remaining(), MAX_PAYLOAD_SIZE))?, }) } } diff --git a/pumpkin-protocol/src/server/play/s_chat_message.rs b/pumpkin-protocol/src/server/play/s_chat_message.rs index a8e61d746..c0049af28 100644 --- a/pumpkin-protocol/src/server/play/s_chat_message.rs +++ b/pumpkin-protocol/src/server/play/s_chat_message.rs @@ -1,8 +1,8 @@ -use bytes::Bytes; +use bytes::{Buf, Bytes}; use pumpkin_macros::server_packet; use crate::{ - bytebuf::{ByteBuffer, DeserializerError}, + bytebuf::{ByteBuf, ReadingError}, FixedBitSet, ServerPacket, VarInt, }; @@ -19,14 +19,14 @@ pub struct SChatMessage { // TODO impl ServerPacket for SChatMessage { - fn read(bytebuf: &mut ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - message: bytebuf.get_string()?, - timestamp: bytebuf.get_i64()?, - salt: bytebuf.get_i64()?, - signature: bytebuf.get_option(|v| v.copy_to_bytes(256))?, - message_count: bytebuf.get_var_int()?, - acknowledged: bytebuf.get_fixed_bitset(20)?, + message: bytebuf.try_get_string()?, + timestamp: bytebuf.try_get_i64()?, + salt: bytebuf.try_get_i64()?, + signature: bytebuf.try_get_option(|v| v.try_copy_to_bytes(256))?, + message_count: bytebuf.try_get_var_int()?, + acknowledged: bytebuf.try_get_fixed_bitset(20)?, }) } } diff --git a/pumpkin-protocol/src/server/play/s_click_container.rs b/pumpkin-protocol/src/server/play/s_click_container.rs index 8de3f8a79..6dcf4abdd 100644 --- a/pumpkin-protocol/src/server/play/s_click_container.rs +++ b/pumpkin-protocol/src/server/play/s_click_container.rs @@ -1,17 +1,18 @@ -use crate::slot::Slot; +use crate::codec::slot::Slot; use crate::VarInt; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; use pumpkin_macros::server_packet; use serde::de::SeqAccess; use serde::{de, Deserialize}; -#[derive(Debug)] #[server_packet("play:container_click")] pub struct SClickContainer { pub window_id: VarInt, pub state_id: VarInt, pub slot: i16, pub button: i8, - pub mode: VarInt, + pub mode: SlotActionType, pub length_of_array: VarInt, pub array_of_changed_slots: Vec<(i16, Slot)>, pub carried_item: Slot, @@ -73,7 +74,8 @@ impl<'de> Deserialize<'de> for SClickContainer { state_id, slot, button, - mode, + mode: SlotActionType::from_i32(mode.0) + .expect("Invalid Slot action, TODO better error handling ;D"), length_of_array, array_of_changed_slots, carried_item, @@ -84,3 +86,24 @@ impl<'de> Deserialize<'de> for SClickContainer { deserializer.deserialize_seq(Visitor) } } + +#[derive(Deserialize, FromPrimitive)] +pub enum SlotActionType { + /// Performs a normal slot click. This can pickup or place items in the slot, possibly merging the cursor stack into the slot, or swapping the slot stack with the cursor stack if they can't be merged. + Pickup, + /// Performs a shift-click. This usually quickly moves items between the player's inventory and the open screen handler. + QuickMove, + /// Exchanges items between a slot and a hotbar slot. This is usually triggered by the player pressing a 1-9 number key while hovering over a slot. + /// When the action type is swap, the click data is the hotbar slot to swap with (0-8). + Swap, + /// Clones the item in the slot. Usually triggered by middle clicking an item in creative mode. + Clone, + /// Throws the item out of the inventory. This is usually triggered by the player pressing Q while hovering over a slot, or clicking outside the window. + /// When the action type is throw, the click data determines whether to throw a whole stack (1) or a single item from that stack (0). + Throw, + /// Drags items between multiple slots. This is usually triggered by the player clicking and dragging between slots. + /// This action happens in 3 stages. Stage 0 signals that the drag has begun, and stage 2 signals that the drag has ended. In between multiple stage 1s signal which slots were dragged on. + QuickCraft, + /// Replenishes the cursor stack with items from the screen handler. This is usually triggered by the player double clicking + PickupAll, +} diff --git a/pumpkin-protocol/src/server/play/s_cookie_response.rs b/pumpkin-protocol/src/server/play/s_cookie_response.rs index 97311b311..e8e5d4bb5 100644 --- a/pumpkin-protocol/src/server/play/s_cookie_response.rs +++ b/pumpkin-protocol/src/server/play/s_cookie_response.rs @@ -1,7 +1,10 @@ -use crate::bytebuf::{ByteBuffer, DeserializerError}; -use crate::{Identifier, ServerPacket, VarInt}; +use crate::{ + bytebuf::{ByteBuf, ReadingError}, + codec::identifier::Identifier, + ServerPacket, VarInt, +}; +use bytes::Buf; use pumpkin_macros::server_packet; -use serde::de; #[server_packet("play:cookie_response")] /// Response to a Cookie Request (play) from the server. @@ -10,15 +13,15 @@ pub struct SCookieResponse { pub key: Identifier, pub has_payload: bool, pub payload_length: Option, - pub payload: Option>, // 5120, + pub payload: Option, // 5120, } -const MAX_PAYLOAD_SIZE: i32 = 5120; +const MAX_COOKIE_LENGTH: usize = 5120; impl ServerPacket for SCookieResponse { - fn read(bytebuf: &mut ByteBuffer) -> Result { - let key = bytebuf.get_string()?; - let has_payload = bytebuf.get_bool()?; + fn read(bytebuf: &mut impl Buf) -> Result { + let key = bytebuf.try_get_identifer()?; + let has_payload = bytebuf.try_get_bool()?; if !has_payload { return Ok(Self { @@ -29,16 +32,10 @@ impl ServerPacket for SCookieResponse { }); } - let payload_length = bytebuf.get_var_int()?; + let payload_length = bytebuf.try_get_var_int()?; let length = payload_length.0; - if length > MAX_PAYLOAD_SIZE { - return Err(de::Error::custom( - "Payload exceeds the maximum allowed size (5120 bytes)", - )); - } - - let payload = bytebuf.copy_to_bytes(length as usize)?.to_vec(); + let payload = bytebuf.try_copy_to_bytes_len(length as usize, MAX_COOKIE_LENGTH)?; Ok(Self { key, diff --git a/pumpkin-protocol/src/server/play/s_interact.rs b/pumpkin-protocol/src/server/play/s_interact.rs index f283f0487..a6a3844d9 100644 --- a/pumpkin-protocol/src/server/play/s_interact.rs +++ b/pumpkin-protocol/src/server/play/s_interact.rs @@ -1,9 +1,13 @@ +use bytes::Buf; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use pumpkin_core::math::vector3::Vector3; use pumpkin_macros::server_packet; -use crate::{bytebuf::DeserializerError, ServerPacket, VarInt}; +use crate::{ + bytebuf::{ByteBuf, ReadingError}, + ServerPacket, VarInt, +}; #[server_packet("play:interact")] pub struct SInteract { @@ -16,27 +20,24 @@ pub struct SInteract { // Great job Mojang ;D impl ServerPacket for SInteract { - fn read( - bytebuf: &mut crate::bytebuf::ByteBuffer, - ) -> Result { - let entity_id = bytebuf.get_var_int()?; - let typ = bytebuf.get_var_int()?; - let action = ActionType::from_i32(typ.0).ok_or(DeserializerError::Message( - "invalid action type".to_string(), - ))?; + fn read(bytebuf: &mut impl Buf) -> Result { + let entity_id = bytebuf.try_get_var_int()?; + let typ = bytebuf.try_get_var_int()?; + let action = ActionType::from_i32(typ.0) + .ok_or(ReadingError::Message("invalid action type".to_string()))?; let target_position: Option> = match action { ActionType::Interact => None, ActionType::Attack => None, ActionType::InteractAt => Some(Vector3::new( - bytebuf.get_f32()?, - bytebuf.get_f32()?, - bytebuf.get_f32()?, + bytebuf.try_get_f32()?, + bytebuf.try_get_f32()?, + bytebuf.try_get_f32()?, )), }; let hand = match action { - ActionType::Interact => Some(bytebuf.get_var_int()?), + ActionType::Interact => Some(bytebuf.try_get_var_int()?), ActionType::Attack => None, - ActionType::InteractAt => Some(bytebuf.get_var_int()?), + ActionType::InteractAt => Some(bytebuf.try_get_var_int()?), }; Ok(Self { @@ -44,7 +45,7 @@ impl ServerPacket for SInteract { typ, target_position, hand, - sneaking: bytebuf.get_bool()?, + sneaking: bytebuf.try_get_bool()?, }) } } diff --git a/pumpkin-protocol/src/server/play/s_player_command.rs b/pumpkin-protocol/src/server/play/s_player_command.rs index aeafcf153..6a9b65a49 100644 --- a/pumpkin-protocol/src/server/play/s_player_command.rs +++ b/pumpkin-protocol/src/server/play/s_player_command.rs @@ -1,7 +1,11 @@ +use bytes::Buf; use num_derive::FromPrimitive; use pumpkin_macros::server_packet; -use crate::{bytebuf::DeserializerError, ServerPacket, VarInt}; +use crate::{ + bytebuf::{ByteBuf, ReadingError}, + ServerPacket, VarInt, +}; #[server_packet("play:player_command")] pub struct SPlayerCommand { @@ -23,11 +27,11 @@ pub enum Action { } impl ServerPacket for SPlayerCommand { - fn read(bytebuf: &mut crate::bytebuf::ByteBuffer) -> Result { + fn read(bytebuf: &mut impl Buf) -> Result { Ok(Self { - entity_id: bytebuf.get_var_int()?, - action: bytebuf.get_var_int()?, - jump_boost: bytebuf.get_var_int()?, + entity_id: bytebuf.try_get_var_int()?, + action: bytebuf.try_get_var_int()?, + jump_boost: bytebuf.try_get_var_int()?, }) } } diff --git a/pumpkin-protocol/src/server/play/s_set_creative_slot.rs b/pumpkin-protocol/src/server/play/s_set_creative_slot.rs index 0dff80b44..59835a434 100644 --- a/pumpkin-protocol/src/server/play/s_set_creative_slot.rs +++ b/pumpkin-protocol/src/server/play/s_set_creative_slot.rs @@ -1,6 +1,6 @@ use pumpkin_macros::server_packet; -use crate::slot::Slot; +use crate::codec::slot::Slot; #[derive(serde::Deserialize, Debug)] #[server_packet("play:set_creative_mode_slot")] diff --git a/pumpkin-protocol/src/var_int.rs b/pumpkin-protocol/src/var_int.rs deleted file mode 100644 index 61f596caf..000000000 --- a/pumpkin-protocol/src/var_int.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::io::{self, Write}; - -use bytes::Buf; -use thiserror::Error; - -use crate::VarIntType; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct VarInt(pub VarIntType); - -impl VarInt { - /// The maximum number of bytes a `VarInt` could occupy when read from and - /// written to the Minecraft protocol. - pub const MAX_SIZE: usize = 5; - - /// Returns the exact number of bytes this varint will write when - /// [`Encode::encode`] is called, assuming no error occurs. - pub const fn written_size(self) -> usize { - match self.0 { - 0 => 1, - n => (31 - n.leading_zeros() as usize) / 7 + 1, - } - } - - pub fn decode_partial(r: &mut &[u8]) -> Result { - let mut val = 0; - for i in 0..Self::MAX_SIZE { - if !r.has_remaining() { - return Err(VarIntDecodeError::Incomplete); - } - let byte = r.get_u8(); - val |= (i32::from(byte) & 0b01111111) << (i * 7); - if byte & 0b10000000 == 0 { - return Ok(val); - } - } - - Err(VarIntDecodeError::TooLarge) - } - - pub fn encode(&self, mut w: impl Write) -> Result<(), io::Error> { - let mut x = self.0 as u64; - loop { - let byte = (x & 0x7F) as u8; - x >>= 7; - if x == 0 { - w.write_all(&[byte])?; - break; - } - w.write_all(&[byte | 0x80])?; - } - Ok(()) - } - - pub fn decode(r: &mut &[u8]) -> Result { - let mut val = 0; - for i in 0..Self::MAX_SIZE { - if !r.has_remaining() { - return Err(VarIntDecodeError::Incomplete); - } - let byte = r.get_u8(); - val |= (i32::from(byte) & 0b01111111) << (i * 7); - if byte & 0b10000000 == 0 { - return Ok(VarInt(val)); - } - } - Err(VarIntDecodeError::TooLarge) - } -} - -impl From for VarInt { - fn from(value: i32) -> Self { - VarInt(value) - } -} - -impl From for VarInt { - fn from(value: u32) -> Self { - VarInt(value as i32) - } -} - -impl From for VarInt { - fn from(value: u8) -> Self { - VarInt(value as i32) - } -} - -impl From for VarInt { - fn from(value: usize) -> Self { - VarInt(value as i32) - } -} - -impl From for i32 { - fn from(value: VarInt) -> Self { - value.0 - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)] -pub enum VarIntDecodeError { - #[error("incomplete VarInt decode")] - Incomplete, - #[error("VarInt is too large")] - TooLarge, -} diff --git a/pumpkin-protocol/src/var_long.rs b/pumpkin-protocol/src/var_long.rs deleted file mode 100644 index 91a3bbc2a..000000000 --- a/pumpkin-protocol/src/var_long.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::io::{self, Write}; - -use bytes::Buf; -use thiserror::Error; - -use crate::VarLongType; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct VarLong(pub VarLongType); - -impl VarLong { - /// The maximum number of bytes a `VarInt` could occupy when read from and - /// written to the Minecraft protocol. - pub const MAX_SIZE: usize = 10; - - /// Returns the exact number of bytes this varint will write when - /// [`Encode::encode`] is called, assuming no error occurs. - pub const fn written_size(self) -> usize { - match self.0 { - 0 => 1, - n => (31 - n.leading_zeros() as usize) / 7 + 1, - } - } - - pub fn encode(&self, mut w: impl Write) -> Result<(), io::Error> { - let mut x = self.0 as u64; - loop { - let byte = (x & 0x7F) as u8; - x >>= 7; - if x == 0 { - w.write_all(&[byte])?; - break; - } - w.write_all(&[byte | 0x80])?; - } - Ok(()) - } - - pub fn decode(r: &mut &[u8]) -> Result { - let mut val = 0; - for i in 0..Self::MAX_SIZE { - if !r.has_remaining() { - return Err(VarLongDecodeError::Incomplete); - } - let byte = r.get_u8(); - val |= (i64::from(byte) & 0b01111111) << (i * 7); - if byte & 0b10000000 == 0 { - return Ok(VarLong(val)); - } - } - Err(VarLongDecodeError::TooLarge) - } -} - -impl From for VarLong { - fn from(value: i64) -> Self { - VarLong(value) - } -} - -impl From for VarLong { - fn from(value: u32) -> Self { - VarLong(value as i64) - } -} - -impl From for VarLong { - fn from(value: u8) -> Self { - VarLong(value as i64) - } -} - -impl From for VarLong { - fn from(value: usize) -> Self { - VarLong(value as i64) - } -} - -impl From for i64 { - fn from(value: VarLong) -> Self { - value.0 - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)] -pub enum VarLongDecodeError { - #[error("incomplete VarLong decode")] - Incomplete, - #[error("VarLong is too large")] - TooLarge, -} diff --git a/pumpkin-registry/Cargo.toml b/pumpkin-registry/Cargo.toml index 3d55b6bb7..a90646d70 100644 --- a/pumpkin-registry/Cargo.toml +++ b/pumpkin-registry/Cargo.toml @@ -13,9 +13,3 @@ indexmap = { version = "2.7.0", features = ["serde"] } serde.workspace = true serde_json.workspace = true -rayon.workspace = true - -num-traits.workspace = true -num-derive.workspace = true - -itertools.workspace = true diff --git a/pumpkin-registry/src/banner_pattern.rs b/pumpkin-registry/src/banner_pattern.rs index bac63fda2..da631c331 100644 --- a/pumpkin-registry/src/banner_pattern.rs +++ b/pumpkin-registry/src/banner_pattern.rs @@ -1,7 +1,8 @@ +use pumpkin_protocol::codec::identifier::Identifier; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BannerPattern { - asset_id: String, + asset_id: Identifier, translation_key: String, } diff --git a/pumpkin-registry/src/biome.rs b/pumpkin-registry/src/biome.rs index cbe03b412..e2d522535 100644 --- a/pumpkin-registry/src/biome.rs +++ b/pumpkin-registry/src/biome.rs @@ -1,4 +1,4 @@ -use pumpkin_protocol::VarInt; +use pumpkin_protocol::codec::var_int::VarInt; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/pumpkin-registry/src/lib.rs b/pumpkin-registry/src/lib.rs index 8be13d6f5..80786b17a 100644 --- a/pumpkin-registry/src/lib.rs +++ b/pumpkin-registry/src/lib.rs @@ -10,7 +10,7 @@ use indexmap::IndexMap; use instrument::Instrument; use jukebox_song::JukeboxSong; use paint::Painting; -use pumpkin_protocol::client::config::RegistryEntry; +use pumpkin_protocol::{client::config::RegistryEntry, codec::identifier::Identifier}; pub use recipe::{ flatten_3x3, IngredientSlot, IngredientType, Recipe, RecipeResult, RecipeType, RECIPES, }; @@ -41,8 +41,8 @@ pub static SYNCED_REGISTRIES: LazyLock = LazyLock::new(|| { }); pub struct Registry { - pub registry_id: String, - pub registry_entries: Vec>, + pub registry_id: Identifier, + pub registry_entries: Vec, } #[derive(Serialize, Deserialize)] @@ -78,12 +78,12 @@ struct DataPool { } impl DimensionType { - pub fn name(&self) -> &str { + pub fn name(&self) -> Identifier { match self { - Self::Overworld => "minecraft:overworld", - Self::OverworldCaves => "minecraft:overworld_caves", - Self::TheEnd => "minecraft:the_end", - Self::TheNether => "minecraft:the_nether", + Self::Overworld => Identifier::vanilla("overworld"), + Self::OverworldCaves => Identifier::vanilla("overworld_caves"), + Self::TheEnd => Identifier::vanilla("the_end"), + Self::TheNether => Identifier::vanilla("the_nether"), } } } @@ -94,12 +94,12 @@ impl Registry { .biome .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let biome = Registry { - registry_id: "minecraft:worldgen/biome".to_string(), + registry_id: Identifier::vanilla("worldgen/biome"), registry_entries, }; @@ -107,12 +107,12 @@ impl Registry { .chat_type .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let chat_type = Registry { - registry_id: "minecraft:chat_type".to_string(), + registry_id: Identifier::vanilla("chat_type"), registry_entries, }; @@ -120,7 +120,7 @@ impl Registry { // .trim_pattern // .iter() // .map(|s| RegistryEntry { - // entry_id: s.0, + // entry_id: Identifier::vanilla(s.0), // data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), // }) // .collect(); @@ -133,7 +133,7 @@ impl Registry { // .trim_material // .iter() // .map(|s| RegistryEntry { - // entry_id: s.0, + // entry_id: Identifier::vanilla(s.0), // data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), // }) // .collect(); @@ -146,16 +146,15 @@ impl Registry { .wolf_variant .iter() .map(|s| { - // I present to you, A ugly hack which is done because Mojang developers decited to put is_ instead of just on 3 wolf varients while all others have just the biome, this causes the client to not find the biome and disconnect - let varient = s.1.clone(); + let variant = s.1.clone(); RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&varient).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&variant).unwrap()), } }) .collect(); let wolf_variant = Registry { - registry_id: "minecraft:wolf_variant".to_string(), + registry_id: Identifier::vanilla("wolf_variant"), registry_entries, }; @@ -163,12 +162,12 @@ impl Registry { .painting_variant .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let painting_variant = Registry { - registry_id: "minecraft:painting_variant".to_string(), + registry_id: Identifier::vanilla("painting_variant"), registry_entries, }; @@ -176,12 +175,12 @@ impl Registry { .dimension_type .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let dimension_type = Registry { - registry_id: "minecraft:dimension_type".to_string(), + registry_id: Identifier::vanilla("dimension_type"), registry_entries, }; @@ -189,12 +188,12 @@ impl Registry { .damage_type .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let damage_type = Registry { - registry_id: "minecraft:damage_type".to_string(), + registry_id: Identifier::vanilla("damage_type"), registry_entries, }; @@ -202,12 +201,12 @@ impl Registry { .banner_pattern .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let banner_pattern = Registry { - registry_id: "minecraft:banner_pattern".to_string(), + registry_id: Identifier::vanilla("banner_pattern"), registry_entries, }; @@ -216,7 +215,7 @@ impl Registry { // .enchantment // .iter() // .map(|s| RegistryEntry { - // entry_id: s.0, + // entry_id: Identifier::vanilla(s.0), // data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), // }) // .collect(); @@ -229,12 +228,12 @@ impl Registry { .jukebox_song .iter() .map(|s| RegistryEntry { - entry_id: s.0, - data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), + entry_id: Identifier::vanilla(s.0), + data: Some(pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap()), }) .collect(); let jukebox_song = Registry { - registry_id: "minecraft:jukebox_song".to_string(), + registry_id: Identifier::vanilla("jukebox_song"), registry_entries, }; @@ -242,7 +241,7 @@ impl Registry { // .instrument // .iter() // .map(|s| RegistryEntry { - // entry_id: s.0, + // entry_id: Identifier::vanilla(s.0), // data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(), // }) // .collect(); diff --git a/pumpkin-registry/src/paint.rs b/pumpkin-registry/src/paint.rs index 9c383f6fe..1217798ec 100644 --- a/pumpkin-registry/src/paint.rs +++ b/pumpkin-registry/src/paint.rs @@ -1,8 +1,9 @@ +use pumpkin_protocol::codec::identifier::Identifier; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Painting { - asset_id: String, + asset_id: Identifier, // #[serde(skip_serializing_if = "Option::is_none")] // title: Option>, // #[serde(skip_serializing_if = "Option::is_none")] diff --git a/pumpkin-registry/src/recipe/recipe_formats.rs b/pumpkin-registry/src/recipe/recipe_formats.rs index 7c26376a0..5726c2824 100644 --- a/pumpkin-registry/src/recipe/recipe_formats.rs +++ b/pumpkin-registry/src/recipe/recipe_formats.rs @@ -2,8 +2,6 @@ use super::super::recipe::RecipeType; use super::read::{ ingredients::IngredientSlot, CraftingType, RecipeKeys, RecipeResult, RecipeTrait, }; -use itertools::Itertools; - pub struct ShapedCrafting { keys: RecipeKeys, pattern: [[Option; 3]; 3], @@ -85,7 +83,7 @@ impl RecipeTrait for ShapelessCrafting { [v1, v2, v3] }) - .collect_vec() + .collect() } fn result(self) -> RecipeResult { diff --git a/pumpkin-registry/src/trim_pattern.rs b/pumpkin-registry/src/trim_pattern.rs index 48746df1f..62f07daa9 100644 --- a/pumpkin-registry/src/trim_pattern.rs +++ b/pumpkin-registry/src/trim_pattern.rs @@ -1,8 +1,9 @@ +use pumpkin_protocol::codec::identifier::Identifier; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TrimPattern { - asset_id: String, + asset_id: Identifier, template_item: String, // description: TextComponent<'static>, decal: bool, diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 18745f731..1c7eacd87 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -4,6 +4,7 @@ version.workspace = true edition.workspace = true [dependencies] +pumpkin-nbt = { path = "../pumpkin-nbt" } pumpkin-core = { path = "../pumpkin-core" } pumpkin-config = { path = "../pumpkin-config" } pumpkin-macros = { path = "../pumpkin-macros" } @@ -11,24 +12,22 @@ pumpkin-macros = { path = "../pumpkin-macros" } tokio.workspace = true rayon.workspace = true derive_more.workspace = true -itertools.workspace = true thiserror.workspace = true serde.workspace = true serde_json.workspace = true log.workspace = true -parking_lot.workspace = true num-traits.workspace = true num-derive.workspace = true -futures = "0.3" dashmap = "6.1.0" -# Compression +# Compression flate2 = "1.0" lz4 = "1.28.0" +file-guard = "0.2.0" + enum_dispatch = "0.3.13" -derive-getters = "0.5.0" fastnbt = { git = "https://github.com/owengage/fastnbt.git" } @@ -39,7 +38,6 @@ rand = "0.8.5" [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } - [[bench]] name = "chunk_noise" harness = false diff --git a/pumpkin-world/src/chunk/anvil.rs b/pumpkin-world/src/chunk/anvil.rs index 6d13e9604..2391537f3 100644 --- a/pumpkin-world/src/chunk/anvil.rs +++ b/pumpkin-world/src/chunk/anvil.rs @@ -4,9 +4,8 @@ use std::{ }; use flate2::bufread::{GzDecoder, ZlibDecoder}; -use itertools::Itertools; -use crate::level::SaveFile; +use crate::level::LevelFolder; use super::{ChunkData, ChunkReader, ChunkReadingError, CompressionError}; @@ -88,7 +87,7 @@ impl Compression { impl ChunkReader for AnvilChunkReader { fn read_chunk( &self, - save_file: &SaveFile, + save_file: &LevelFolder, at: &pumpkin_core::math::vector2::Vector2, ) -> Result { let region = (at.x >> 5, at.z >> 5); @@ -143,7 +142,7 @@ impl ChunkReader for AnvilChunkReader { }; // TODO: check checksum to make sure chunk is not corrupted - let header = file_buf.drain(0..5).collect_vec(); + let header: Vec = file_buf.drain(0..5).collect(); let compression = Compression::from_byte(header[4]).ok_or( ChunkReadingError::Compression(CompressionError::UnknownCompression), @@ -152,12 +151,12 @@ impl ChunkReader for AnvilChunkReader { let size = u32::from_be_bytes(header[..4].try_into().unwrap()); // size includes the compression scheme byte, so we need to subtract 1 - let chunk_data = file_buf.drain(0..size as usize - 1).collect_vec(); + let chunk_data = file_buf.drain(0..size as usize - 1).collect(); let decompressed_chunk = compression .decompress_data(chunk_data) .map_err(ChunkReadingError::Compression)?; - ChunkData::from_bytes(decompressed_chunk, *at).map_err(ChunkReadingError::ParsingError) + ChunkData::from_bytes(&decompressed_chunk, *at).map_err(ChunkReadingError::ParsingError) } } @@ -169,14 +168,14 @@ mod tests { use crate::{ chunk::{anvil::AnvilChunkReader, ChunkReader, ChunkReadingError}, - level::SaveFile, + level::LevelFolder, }; #[test] fn not_existing() { let region_path = PathBuf::from("not_existing"); let result = AnvilChunkReader::new().read_chunk( - &SaveFile { + &LevelFolder { root_folder: PathBuf::from(""), region_folder: region_path, }, diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index 93edbbfaa..8473e0301 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -9,7 +9,7 @@ use thiserror::Error; use crate::{ block::BlockState, coordinates::{ChunkRelativeBlockCoordinates, Height}, - level::SaveFile, + level::LevelFolder, WORLD_HEIGHT, }; @@ -22,7 +22,7 @@ const CHUNK_VOLUME: usize = CHUNK_AREA * WORLD_HEIGHT; pub trait ChunkReader: Sync + Send { fn read_chunk( &self, - save_file: &SaveFile, + save_file: &LevelFolder, at: &Vector2, ) -> Result; } @@ -232,15 +232,15 @@ impl Index for ChunkBlocks { } impl ChunkData { - pub fn from_bytes(chunk_data: Vec, at: Vector2) -> Result { - if fastnbt::from_bytes::(&chunk_data) + pub fn from_bytes(chunk_data: &[u8], at: Vector2) -> Result { + if fastnbt::from_bytes::(chunk_data) .map_err(|_| ChunkParsingError::FailedReadStatus)? != ChunkStatus::Full { return Err(ChunkParsingError::ChunkNotGenerated); } - let chunk_data = fastnbt::from_bytes::(chunk_data.as_slice()) + let chunk_data = fastnbt::from_bytes::(chunk_data) .map_err(|e| ChunkParsingError::ErrorDeserializingChunk(e.to_string()))?; // this needs to be boxed, otherwise it will cause a stack-overflow @@ -273,22 +273,22 @@ impl ChunkData { Some(d) => d, }; - // How many bits each block has in one of the pallete u64s + // How many bits each block has in one of the palette u64s let block_bit_size = { let size = 64 - (palette.len() as i64 - 1).leading_zeros(); max(4, size) }; - // How many blocks there are in one of the palletes u64s - let blocks_in_pallete = 64 / block_bit_size; + // How many blocks there are in one of the palettes u64s + let blocks_in_palette = 64 / block_bit_size; let mask = (1 << block_bit_size) - 1; 'block_loop: for block in block_data.iter() { - for i in 0..blocks_in_pallete { + for i in 0..blocks_in_palette { let index = (block >> (i * block_bit_size)) & mask; let block = &palette[index as usize]; // TODO allow indexing blocks directly so we can just use block_index and save some time? - // this is fine because we initalized the heightmap of `blocks` + // this is fine because we initialized the heightmap of `blocks` // from the cached value in the world file blocks.set_block_no_heightmap_update( ChunkRelativeBlockCoordinates { @@ -301,7 +301,7 @@ impl ChunkData { block_index += 1; - // if `SUBCHUNK_VOLUME `is not divisible by `blocks_in_pallete` the block_data + // if `SUBCHUNK_VOLUME `is not divisible by `blocks_in_palette` the block_data // can sometimes spill into other subchunks. We avoid that by aborting early if (block_index % SUBCHUNK_VOLUME) == 0 { break 'block_loop; diff --git a/pumpkin-world/src/cylindrical_chunk_iterator.rs b/pumpkin-world/src/cylindrical_chunk_iterator.rs index 67d1f498e..d8b65fe34 100644 --- a/pumpkin-world/src/cylindrical_chunk_iterator.rs +++ b/pumpkin-world/src/cylindrical_chunk_iterator.rs @@ -1,14 +1,15 @@ -use itertools::Itertools; +use std::num::NonZeroU8; + use pumpkin_core::math::vector2::Vector2; #[derive(Debug, Clone, Copy, PartialEq)] pub struct Cylindrical { pub center: Vector2, - pub view_distance: u8, + pub view_distance: NonZeroU8, } impl Cylindrical { - pub fn new(center: Vector2, view_distance: u8) -> Self { + pub fn new(center: Vector2, view_distance: NonZeroU8) -> Self { Self { center, view_distance, @@ -37,19 +38,19 @@ impl Cylindrical { } fn left(&self) -> i32 { - self.center.x - self.view_distance as i32 - 1 + self.center.x - self.view_distance.get() as i32 - 1 } fn bottom(&self) -> i32 { - self.center.z - self.view_distance as i32 - 1 + self.center.z - self.view_distance.get() as i32 - 1 } fn right(&self) -> i32 { - self.center.x + self.view_distance as i32 + 1 + self.center.x + self.view_distance.get() as i32 + 1 } fn top(&self) -> i32 { - self.center.z + self.view_distance as i32 + 1 + self.center.z + self.view_distance.get() as i32 + 1 } fn is_within_distance(&self, x: i32, z: i32) -> bool { @@ -60,7 +61,7 @@ impl Cylindrical { let min_leg = rel_x.min(rel_z) as i64; let hyp_sqr = max_leg * max_leg + min_leg * min_leg; - hyp_sqr < (self.view_distance as i64 * self.view_distance as i64) + hyp_sqr < (self.view_distance.get() as i64 * self.view_distance.get() as i64) } /// Returns an iterator of all chunks within this cylinder @@ -76,19 +77,21 @@ impl Cylindrical { all_chunks .into_iter() .filter(|chunk| self.is_within_distance(chunk.x, chunk.z)) - .collect_vec() + .collect() } } #[cfg(test)] mod test { + use std::num::NonZeroU8; + use super::Cylindrical; use pumpkin_core::math::vector2::Vector2; #[test] fn test_bounds() { - let cylinder = Cylindrical::new(Vector2::new(0, 0), 10); + let cylinder = Cylindrical::new(Vector2::new(0, 0), unsafe { NonZeroU8::new_unchecked(1) }); for chunk in cylinder.all_chunks_within() { assert!(chunk.x >= cylinder.left() && chunk.x <= cylinder.right()); assert!(chunk.z >= cylinder.bottom() && chunk.z <= cylinder.top()); diff --git a/pumpkin-world/src/world_gen/aquifer_sampler.rs b/pumpkin-world/src/generation/aquifer_sampler.rs similarity index 99% rename from pumpkin-world/src/world_gen/aquifer_sampler.rs rename to pumpkin-world/src/generation/aquifer_sampler.rs index ebe4d6ac9..8dfb46469 100644 --- a/pumpkin-world/src/world_gen/aquifer_sampler.rs +++ b/pumpkin-world/src/generation/aquifer_sampler.rs @@ -661,7 +661,7 @@ mod test { use crate::{ block::BlockState, - world_gen::{ + generation::{ chunk_noise::{ BlockStateSampler, ChunkNoiseDensityFunctions, ChunkNoiseGenerator, ChunkNoiseState, LAVA_BLOCK, WATER_BLOCK, diff --git a/pumpkin-world/src/world_gen/blender/mod.rs b/pumpkin-world/src/generation/blender/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/blender/mod.rs rename to pumpkin-world/src/generation/blender/mod.rs diff --git a/pumpkin-world/src/world_gen/chunk_noise.rs b/pumpkin-world/src/generation/chunk_noise.rs similarity index 99% rename from pumpkin-world/src/world_gen/chunk_noise.rs rename to pumpkin-world/src/generation/chunk_noise.rs index 5ff2bb270..cab1abf04 100644 --- a/pumpkin-world/src/world_gen/chunk_noise.rs +++ b/pumpkin-world/src/generation/chunk_noise.rs @@ -6,11 +6,11 @@ use pumpkin_macros::block_state; use crate::{ block::BlockState, - match_ref_implementations, - world_gen::{ + generation::{ noise::{density::basic::WrapperType, lerp3}, section_coords, }, + match_ref_implementations, }; use super::{ @@ -1379,7 +1379,7 @@ impl ChunkNoiseGenerator { mod test { use pumpkin_core::math::vector2::Vector2; - use crate::world_gen::{ + use crate::generation::{ aquifer_sampler::{FluidLevel, FluidLevelSampler}, generation_shapes::GenerationShape, noise::{config::NoiseConfig, router::OVERWORLD_NOISE_ROUTER}, diff --git a/pumpkin-world/src/world_gen/generation_shapes.rs b/pumpkin-world/src/generation/generation_shapes.rs similarity index 100% rename from pumpkin-world/src/world_gen/generation_shapes.rs rename to pumpkin-world/src/generation/generation_shapes.rs diff --git a/pumpkin-world/src/world_gen/generator.rs b/pumpkin-world/src/generation/generator.rs similarity index 97% rename from pumpkin-world/src/world_gen/generator.rs rename to pumpkin-world/src/generation/generator.rs index 8a0120c78..a570c8c33 100644 --- a/pumpkin-world/src/world_gen/generator.rs +++ b/pumpkin-world/src/generation/generator.rs @@ -6,7 +6,7 @@ use crate::biome::Biome; use crate::block::block_state::BlockState; use crate::chunk::{ChunkBlocks, ChunkData}; use crate::coordinates::{BlockCoordinates, ChunkRelativeBlockCoordinates, XZBlockCoordinates}; -use crate::world_gen::Seed; +use crate::generation::Seed; pub trait GeneratorInit { fn new(seed: Seed) -> Self; diff --git a/pumpkin-world/src/world_gen/generic_generator.rs b/pumpkin-world/src/generation/generic_generator.rs similarity index 100% rename from pumpkin-world/src/world_gen/generic_generator.rs rename to pumpkin-world/src/generation/generic_generator.rs diff --git a/pumpkin-world/src/world_gen/height_limit.rs b/pumpkin-world/src/generation/height_limit.rs similarity index 100% rename from pumpkin-world/src/world_gen/height_limit.rs rename to pumpkin-world/src/generation/height_limit.rs diff --git a/pumpkin-world/src/world_gen/implementation/mod.rs b/pumpkin-world/src/generation/implementation/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/implementation/mod.rs rename to pumpkin-world/src/generation/implementation/mod.rs diff --git a/pumpkin-world/src/world_gen/implementation/overworld/biome/mod.rs b/pumpkin-world/src/generation/implementation/overworld/biome/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/implementation/overworld/biome/mod.rs rename to pumpkin-world/src/generation/implementation/overworld/biome/mod.rs diff --git a/pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs b/pumpkin-world/src/generation/implementation/overworld/biome/plains.rs similarity index 99% rename from pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs rename to pumpkin-world/src/generation/implementation/overworld/biome/plains.rs index dde27c9c9..775d3e5f1 100644 --- a/pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs +++ b/pumpkin-world/src/generation/implementation/overworld/biome/plains.rs @@ -7,7 +7,7 @@ use crate::{ biome::Biome, chunk::ChunkBlocks, coordinates::{BlockCoordinates, ChunkRelativeBlockCoordinates, XZBlockCoordinates}, - world_gen::{ + generation::{ generator::{BiomeGenerator, GeneratorInit, PerlinTerrainGenerator}, generic_generator::GenericGenerator, Seed, diff --git a/pumpkin-world/src/world_gen/implementation/overworld/mod.rs b/pumpkin-world/src/generation/implementation/overworld/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/implementation/overworld/mod.rs rename to pumpkin-world/src/generation/implementation/overworld/mod.rs diff --git a/pumpkin-world/src/world_gen/implementation/superflat.rs b/pumpkin-world/src/generation/implementation/superflat.rs similarity index 98% rename from pumpkin-world/src/world_gen/implementation/superflat.rs rename to pumpkin-world/src/generation/implementation/superflat.rs index 2ec9fdad6..4e12eba77 100644 --- a/pumpkin-world/src/world_gen/implementation/superflat.rs +++ b/pumpkin-world/src/generation/implementation/superflat.rs @@ -4,7 +4,7 @@ use crate::{ biome::Biome, block::block_state::BlockState, coordinates::XZBlockCoordinates, - world_gen::{ + generation::{ generator::{BiomeGenerator, GeneratorInit, TerrainGenerator}, generic_generator::GenericGenerator, Seed, diff --git a/pumpkin-world/src/world_gen/implementation/test.rs b/pumpkin-world/src/generation/implementation/test.rs similarity index 99% rename from pumpkin-world/src/world_gen/implementation/test.rs rename to pumpkin-world/src/generation/implementation/test.rs index 6b360c45a..3a1f08f10 100644 --- a/pumpkin-world/src/world_gen/implementation/test.rs +++ b/pumpkin-world/src/generation/implementation/test.rs @@ -14,7 +14,7 @@ use crate::{ coordinates::{ ChunkRelativeBlockCoordinates, ChunkRelativeXZBlockCoordinates, XZBlockCoordinates, }, - world_gen::{ + generation::{ generator::{BiomeGenerator, GeneratorInit, TerrainGenerator}, proto_chunk::ProtoChunk, Seed, WorldGenerator, @@ -115,7 +115,7 @@ impl TerrainGenerator for TestTerrainGenerator { let entry = self.chunks.entry(*at); match entry { Entry::Vacant(entry) => { - let mut proto_chunk = ProtoChunk::new(*at, self.seed.0 as u64); + let mut proto_chunk = ProtoChunk::new(*at, self.seed.0); //let inst = std::time::Instant::now(); //println!("Populating chunk: {:?}", at); proto_chunk.populate_noise(); diff --git a/pumpkin-world/src/world_gen/mod.rs b/pumpkin-world/src/generation/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/mod.rs rename to pumpkin-world/src/generation/mod.rs diff --git a/pumpkin-world/src/world_gen/noise/config.rs b/pumpkin-world/src/generation/noise/config.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/config.rs rename to pumpkin-world/src/generation/noise/config.rs index 4ce194ee1..a1e16a0f8 100644 --- a/pumpkin-world/src/world_gen/noise/config.rs +++ b/pumpkin-world/src/generation/noise/config.rs @@ -151,8 +151,7 @@ mod test { }; use crate::{ - read_data_from_file, - world_gen::noise::{ + generation::noise::{ config::NoiseConfig, density::{ built_in_density_function::{ @@ -165,6 +164,7 @@ mod test { }, router::OVERWORLD_NOISE_ROUTER, }, + read_data_from_file, }; use super::LegacyChunkNoiseVisitor; diff --git a/pumpkin-world/src/world_gen/noise/density/basic.rs b/pumpkin-world/src/generation/noise/density/basic.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/basic.rs rename to pumpkin-world/src/generation/noise/density/basic.rs index cb118a4a0..a96be6361 100644 --- a/pumpkin-world/src/world_gen/noise/density/basic.rs +++ b/pumpkin-world/src/generation/noise/density/basic.rs @@ -1,6 +1,6 @@ use std::{hash::Hash, marker::PhantomData}; -use crate::{match_ref_implementations, world_gen::noise::clamped_map}; +use crate::{generation::noise::clamped_map, match_ref_implementations}; use super::{ component_functions::{ @@ -419,10 +419,10 @@ mod test { use std::{fs, path::Path}; use crate::{ - read_data_from_file, - world_gen::noise::density::{ + generation::noise::density::{ component_functions::ImmutableComponentFunctionImpl, NoisePos, UnblendedNoisePos, }, + read_data_from_file, }; use super::YClampedFunction; diff --git a/pumpkin-world/src/world_gen/noise/density/blend.rs b/pumpkin-world/src/generation/noise/density/blend.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/blend.rs rename to pumpkin-world/src/generation/noise/density/blend.rs index 309fa345e..bf6bbb706 100644 --- a/pumpkin-world/src/world_gen/noise/density/blend.rs +++ b/pumpkin-world/src/generation/noise/density/blend.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use crate::world_gen::blender::BlenderImpl; +use crate::generation::blender::BlenderImpl; use super::{ component_functions::{ diff --git a/pumpkin-world/src/world_gen/noise/density/component_functions.rs b/pumpkin-world/src/generation/noise/density/component_functions.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/component_functions.rs rename to pumpkin-world/src/generation/noise/density/component_functions.rs index 6c47665ec..036c4eba0 100644 --- a/pumpkin-world/src/world_gen/noise/density/component_functions.rs +++ b/pumpkin-world/src/generation/noise/density/component_functions.rs @@ -953,7 +953,7 @@ mod test { use pumpkin_core::random::{legacy_rand::LegacyRand, RandomDeriver, RandomImpl}; - use crate::world_gen::noise::{ + use crate::generation::noise::{ built_in_noise_params, density::{ noise::{InternalNoise, NoiseFunction}, diff --git a/pumpkin-world/src/world_gen/noise/density/end.rs b/pumpkin-world/src/generation/noise/density/end.rs similarity index 97% rename from pumpkin-world/src/world_gen/noise/density/end.rs rename to pumpkin-world/src/generation/noise/density/end.rs index 39ee37bc2..c1c3db33d 100644 --- a/pumpkin-world/src/world_gen/noise/density/end.rs +++ b/pumpkin-world/src/generation/noise/density/end.rs @@ -1,6 +1,6 @@ use pumpkin_core::random::{legacy_rand::LegacyRand, RandomImpl}; -use crate::world_gen::noise::simplex::SimplexNoiseSampler; +use crate::generation::noise::simplex::SimplexNoiseSampler; use super::{ component_functions::{ diff --git a/pumpkin-world/src/world_gen/noise/density/math.rs b/pumpkin-world/src/generation/noise/density/math.rs similarity index 100% rename from pumpkin-world/src/world_gen/noise/density/math.rs rename to pumpkin-world/src/generation/noise/density/math.rs diff --git a/pumpkin-world/src/world_gen/noise/density/mod.rs b/pumpkin-world/src/generation/noise/density/mod.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/mod.rs rename to pumpkin-world/src/generation/noise/density/mod.rs index ecec83509..e789b3bd0 100644 --- a/pumpkin-world/src/world_gen/noise/density/mod.rs +++ b/pumpkin-world/src/generation/noise/density/mod.rs @@ -10,7 +10,7 @@ use component_functions::{ use enum_dispatch::enum_dispatch; use noise::{InternalNoise, NoiseFunction}; -use crate::world_gen::{blender::Blender, chunk_noise::ChunkNoisePos}; +use crate::generation::{blender::Blender, chunk_noise::ChunkNoisePos}; use super::perlin::DoublePerlinNoiseParameters; @@ -76,8 +76,8 @@ pub trait NoisePosImpl { pub mod built_in_density_function { use std::sync::{Arc, LazyLock}; - use crate::world_gen::noise::built_in_noise_params::{self}; - use crate::world_gen::positions::{MAX_COLUMN_HEIGHT, MIN_HEIGHT}; + use crate::generation::noise::built_in_noise_params::{self}; + use crate::generation::positions::{MAX_COLUMN_HEIGHT, MIN_HEIGHT}; use pumpkin_core::math::floor_div; @@ -879,7 +879,7 @@ mod test { legacy_rand::LegacyRand, RandomDeriver, RandomDeriverImpl, RandomImpl, }; - use crate::world_gen::noise::{ + use crate::generation::noise::{ built_in_noise_params, density::{built_in_density_function::*, NoisePos, UnblendedNoisePos}, perlin::DoublePerlinNoiseSampler, diff --git a/pumpkin-world/src/world_gen/noise/density/noise.rs b/pumpkin-world/src/generation/noise/density/noise.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/noise.rs rename to pumpkin-world/src/generation/noise/density/noise.rs index 353273b42..1fe139376 100644 --- a/pumpkin-world/src/world_gen/noise/density/noise.rs +++ b/pumpkin-world/src/generation/noise/density/noise.rs @@ -3,11 +3,11 @@ use std::{marker::PhantomData, sync::Arc}; use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomGenerator, RandomImpl}; use crate::{ - match_ref_implementations, - world_gen::noise::{ + generation::noise::{ clamped_lerp, perlin::{DoublePerlinNoiseParameters, DoublePerlinNoiseSampler, OctavePerlinNoiseSampler}, }, + match_ref_implementations, }; use super::{ diff --git a/pumpkin-world/src/world_gen/noise/density/offset.rs b/pumpkin-world/src/generation/noise/density/offset.rs similarity index 100% rename from pumpkin-world/src/world_gen/noise/density/offset.rs rename to pumpkin-world/src/generation/noise/density/offset.rs diff --git a/pumpkin-world/src/world_gen/noise/density/spline.rs b/pumpkin-world/src/generation/noise/density/spline.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/spline.rs rename to pumpkin-world/src/generation/noise/density/spline.rs index 9f4698188..142079a9d 100644 --- a/pumpkin-world/src/world_gen/noise/density/spline.rs +++ b/pumpkin-world/src/generation/noise/density/spline.rs @@ -1,9 +1,8 @@ use std::{marker::PhantomData, sync::Arc}; use enum_dispatch::enum_dispatch; -use itertools::Itertools; -use crate::world_gen::noise::lerp; +use crate::generation::noise::lerp; use super::{ component_functions::{ @@ -302,13 +301,13 @@ impl> MutableSpline = converted_points .into_iter() .map(|point| match point { SplinePoint::Immutable(point) => point, _ => unreachable!(), }) - .collect_vec(); + .collect(); SplineRef::Immutable( ImmutableSpline { function: shared, @@ -387,7 +386,7 @@ impl> MutableSplineImpl< let converted_points = points .into_iter() .map(|point| point.convert(converter)) - .collect_vec(); + .collect(); Self::create_new_spline(converted_base, converted_points) } @@ -398,7 +397,7 @@ impl> MutableSplineImpl< .points .iter() .map(|point| point.clone_to_new_point()) - .collect_vec(); + .collect(); Self::create_new_spline(cloned_function, cloned_points) } @@ -462,18 +461,18 @@ impl ImmutableSplineRef { converter: &mut dyn ConverterImpl, ) -> Option> { let converted_base = self.0.function.maybe_convert(converter); - let maybe_converted_points = self + let maybe_converted_points: Vec>> = self .0 .points .iter() .map(|point| point.maybe_convert(converter)) - .collect_vec(); + .collect(); if converted_base.is_none() && maybe_converted_points.iter().all(|point| point.is_none()) { None } else { let converted_base = converted_base.unwrap_or_else(|| self.0.function.clone().into()); - let converted_points = maybe_converted_points + let converted_points: Vec> = maybe_converted_points .into_iter() .enumerate() .map(|(index, point)| { @@ -483,7 +482,7 @@ impl ImmutableSplineRef { self.0.points[index].clone().into() } }) - .collect_vec(); + .collect(); Some(match converted_base { ComponentReferenceImplementation::Shared(shared) => { @@ -491,13 +490,13 @@ impl ImmutableSplineRef { .iter() .all(|point| matches!(point, SplinePoint::Immutable(_))) { - let immutable_points = converted_points + let immutable_points: Vec = converted_points .into_iter() .map(|point| match point { SplinePoint::Immutable(point) => point, _ => unreachable!(), }) - .collect_vec(); + .collect(); SplineRef::Immutable( ImmutableSpline { function: shared, @@ -745,7 +744,7 @@ mod test { use pumpkin_core::random::{legacy_rand::LegacyRand, RandomDeriver, RandomImpl}; - use crate::world_gen::noise::density::{ + use crate::generation::noise::density::{ built_in_density_function::CONTINENTS_OVERWORLD, component_functions::{ComponentReference, NoEnvironment, SharedComponentReference}, test::{FakeEnvironment, OwnedConverter, TestConverter}, diff --git a/pumpkin-world/src/world_gen/noise/density/terrain_helpers.rs b/pumpkin-world/src/generation/noise/density/terrain_helpers.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/density/terrain_helpers.rs rename to pumpkin-world/src/generation/noise/density/terrain_helpers.rs index b19df8c63..cc8ad7040 100644 --- a/pumpkin-world/src/world_gen/noise/density/terrain_helpers.rs +++ b/pumpkin-world/src/generation/noise/density/terrain_helpers.rs @@ -1,7 +1,7 @@ // From da java -use crate::world_gen::noise::density::peaks_valleys_noise; -use crate::world_gen::noise::lerp; +use crate::generation::noise::density::peaks_valleys_noise; +use crate::generation::noise::lerp; use super::component_functions::SharedComponentReference; use super::spline::{FloatAmplifier, ImmutableSpline, ImmutableSplineRef, SplineBuilder}; @@ -523,7 +523,7 @@ pub fn create_jaggedness_spline( mod test { use pumpkin_core::random::{legacy_rand::LegacyRand, RandomDeriver, RandomImpl}; - use crate::world_gen::noise::density::{ + use crate::generation::noise::density::{ built_in_density_function::{ CONTINENTS_OVERWORLD, EROSION_OVERWORLD, RIDGES_FOLDED_OVERWORLD, RIDGES_OVERWORLD, }, diff --git a/pumpkin-world/src/world_gen/noise/density/unary.rs b/pumpkin-world/src/generation/noise/density/unary.rs similarity index 100% rename from pumpkin-world/src/world_gen/noise/density/unary.rs rename to pumpkin-world/src/generation/noise/density/unary.rs diff --git a/pumpkin-world/src/world_gen/noise/density/weird.rs b/pumpkin-world/src/generation/noise/density/weird.rs similarity index 100% rename from pumpkin-world/src/world_gen/noise/density/weird.rs rename to pumpkin-world/src/generation/noise/density/weird.rs diff --git a/pumpkin-world/src/world_gen/noise/mod.rs b/pumpkin-world/src/generation/noise/mod.rs similarity index 100% rename from pumpkin-world/src/world_gen/noise/mod.rs rename to pumpkin-world/src/generation/noise/mod.rs diff --git a/pumpkin-world/src/world_gen/noise/perlin.rs b/pumpkin-world/src/generation/noise/perlin.rs similarity index 97% rename from pumpkin-world/src/world_gen/noise/perlin.rs rename to pumpkin-world/src/generation/noise/perlin.rs index 4faf760f4..4e8de285c 100644 --- a/pumpkin-world/src/world_gen/noise/perlin.rs +++ b/pumpkin-world/src/generation/noise/perlin.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use itertools::{izip, Itertools}; use num_traits::Pow; use pumpkin_core::random::RandomGenerator; @@ -226,19 +225,6 @@ impl OctavePerlinNoiseSampler { random.skip(262); } } - - #[cfg(debug_assertions)] - { - use itertools::Itertools; - use num_traits::Zero; - - if let Ok(length1) = samplers.iter().filter(|x| x.is_some()).try_len() { - if let Ok(length2) = amplitudes.iter().filter(|x| !x.is_zero()).try_len() { - assert_eq!(length1, length2); - } - } - assert!(j >= i as i32 - 1); - } } else { let splitter = random.next_splitter(); for k in 0..i { @@ -256,20 +242,20 @@ impl OctavePerlinNoiseSampler { let mut lacunarity = 2f64.pow((-j) as f64); let max_value = Self::get_total_amplitude(2f64, persistence, amplitudes); - let persistences = (0..amplitudes.len()) + let persistences: Vec = (0..amplitudes.len()) .map(|_| { let result = persistence; persistence /= 2f64; result }) - .collect_vec(); - let lacunarities = (0..amplitudes.len()) + .collect(); + let lacunarities: Vec = (0..amplitudes.len()) .map(|_| { let result = lacunarity; lacunarity *= 2f64; result }) - .collect_vec(); + .collect(); Self { octave_samplers: samplers.into(), @@ -284,13 +270,13 @@ impl OctavePerlinNoiseSampler { pub fn sample(&self, x: f64, y: f64, z: f64) -> f64 { let mut d = 0f64; - for (sampler, amplitude, persistence, lacunarity) in izip!( - &self.octave_samplers, - &self.amplitudes, - &self.persistences, - &self.lacunarities - ) { - if let Some(sampler) = sampler { + let num_octaves = self.octave_samplers.len(); + for i in 0..num_octaves { + if let Some(sampler) = &self.octave_samplers[i] { + let lacunarity = self.lacunarities[i]; + let amplitude = self.amplitudes[i]; + let persistence = self.persistences[i]; + let g = sampler.sample_no_fade( Self::maintain_precision(x * lacunarity), Self::maintain_precision(y * lacunarity), @@ -395,7 +381,7 @@ mod double_perlin_noise_sampler_test { legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, RandomGenerator, RandomImpl, }; - use crate::world_gen::noise::perlin::{DoublePerlinNoiseParameters, DoublePerlinNoiseSampler}; + use crate::generation::noise::perlin::{DoublePerlinNoiseParameters, DoublePerlinNoiseSampler}; #[test] fn sample_legacy() { @@ -774,7 +760,7 @@ mod perlin_noise_sampler_test { random::{xoroshiro128::Xoroshiro, RandomDeriverImpl, RandomGenerator, RandomImpl}, }; - use crate::{read_data_from_file, world_gen::noise::perlin::PerlinNoiseSampler}; + use crate::{generation::noise::perlin::PerlinNoiseSampler, read_data_from_file}; use super::OctavePerlinNoiseSampler; diff --git a/pumpkin-world/src/world_gen/noise/router.rs b/pumpkin-world/src/generation/noise/router.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/router.rs rename to pumpkin-world/src/generation/noise/router.rs index 782600fdf..cb8093c16 100644 --- a/pumpkin-world/src/world_gen/noise/router.rs +++ b/pumpkin-world/src/generation/noise/router.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, LazyLock}; -use crate::world_gen::{ +use crate::generation::{ noise::density::{ apply_blend_density, basic::RangeFunction, @@ -271,7 +271,7 @@ impl BaseRouter { .clone() .min(ConstantFunction::new(5f64).mul(CAVES_ENTRANCES_OVERWORLD.clone())); - let mapped_cave_entraces_overworld = RangeFunction::< + let mapped_cave_entrances_overworld = RangeFunction::< NoEnvironment, SharedComponentReference, SharedComponentReference, @@ -286,7 +286,7 @@ impl BaseRouter { let blended_cave_entrances_overworld = apply_blend_density(apply_surface_slides( amplified, - mapped_cave_entraces_overworld.into(), + mapped_cave_entrances_overworld.into(), )) .min(CAVES_NOODLE_OVERWORLD.clone()); @@ -476,8 +476,7 @@ mod test { }; use crate::{ - read_data_from_file, - world_gen::noise::{ + generation::noise::{ config::LegacyChunkNoiseVisitor, density::{ built_in_density_function::{EROSION_OVERWORLD, SLOPED_CHEESE_OVERWORLD}, @@ -491,6 +490,7 @@ mod test { perlin::DoublePerlinNoiseSampler, router::OVERWORLD_NOISE_ROUTER, }, + read_data_from_file, }; use super::{apply_surface_slides, create_caves}; diff --git a/pumpkin-world/src/world_gen/noise/simplex.rs b/pumpkin-world/src/generation/noise/simplex.rs similarity index 99% rename from pumpkin-world/src/world_gen/noise/simplex.rs rename to pumpkin-world/src/generation/noise/simplex.rs index ff9afd6ef..c69128c3c 100644 --- a/pumpkin-world/src/world_gen/noise/simplex.rs +++ b/pumpkin-world/src/generation/noise/simplex.rs @@ -276,7 +276,7 @@ impl OctaveSimplexNoiseSampler { mod octave_simplex_noise_sampler_test { use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomImpl}; - use crate::world_gen::noise::simplex::OctaveSimplexNoiseSampler; + use crate::generation::noise::simplex::OctaveSimplexNoiseSampler; #[test] fn test_new() { @@ -413,7 +413,7 @@ mod simplex_noise_sampler_test { use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomImpl}; - use crate::world_gen::noise::simplex::SimplexNoiseSampler; + use crate::generation::noise::simplex::SimplexNoiseSampler; #[test] fn test_create() { diff --git a/pumpkin-world/src/world_gen/ore_sampler.rs b/pumpkin-world/src/generation/ore_sampler.rs similarity index 100% rename from pumpkin-world/src/world_gen/ore_sampler.rs rename to pumpkin-world/src/generation/ore_sampler.rs diff --git a/pumpkin-world/src/world_gen/positions.rs b/pumpkin-world/src/generation/positions.rs similarity index 100% rename from pumpkin-world/src/world_gen/positions.rs rename to pumpkin-world/src/generation/positions.rs diff --git a/pumpkin-world/src/world_gen/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs similarity index 97% rename from pumpkin-world/src/world_gen/proto_chunk.rs rename to pumpkin-world/src/generation/proto_chunk.rs index 366df514c..056a702f4 100644 --- a/pumpkin-world/src/world_gen/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -2,7 +2,7 @@ use pumpkin_core::math::{vector2::Vector2, vector3::Vector3}; use crate::{ block::BlockState, - world_gen::{ + generation::{ chunk_noise::CHUNK_DIM, generation_shapes::GenerationShape, noise::{config::NoiseConfig, router::OVERWORLD_NOISE_ROUTER}, @@ -120,7 +120,7 @@ impl ProtoChunk { let horizontal_cell_block_count = self.sampler.horizontal_cell_block_count(); let vertical_cell_block_count = self.sampler.vertical_cell_block_count(); - let horizonal_cells = CHUNK_DIM / horizontal_cell_block_count; + let horizontal_cells = CHUNK_DIM / horizontal_cell_block_count; let min_y = self.sampler.min_y(); let minimum_cell_y = min_y / vertical_cell_block_count as i8; @@ -133,10 +133,10 @@ impl ProtoChunk { // - All unsafe functions are encapsulated and no mutable references are leaked unsafe { self.sampler.sample_start_density(); - for cell_x in 0..horizonal_cells { + for cell_x in 0..horizontal_cells { self.sampler.sample_end_density(cell_x); - for cell_z in 0..horizonal_cells { + for cell_z in 0..horizontal_cells { for cell_y in (0..cell_height).rev() { self.sampler.on_sampled_cell_corners(cell_y, cell_z); for local_y in (0..vertical_cell_block_count).rev() { @@ -216,7 +216,6 @@ impl ProtoChunk { mod test { use std::{fs, path::Path}; - use itertools::Itertools; use pumpkin_core::math::vector2::Vector2; use crate::read_data_from_file; @@ -235,7 +234,7 @@ mod test { .flat_block_map .into_iter() .map(|state| state.state_id) - .collect_vec() + .collect::>() ); } @@ -252,7 +251,7 @@ mod test { .flat_block_map .into_iter() .map(|state| state.state_id) - .collect_vec() + .collect::>() ); } } diff --git a/pumpkin-world/src/generation/seed.rs b/pumpkin-world/src/generation/seed.rs new file mode 100644 index 000000000..1d9aa9cbf --- /dev/null +++ b/pumpkin-world/src/generation/seed.rs @@ -0,0 +1,20 @@ +use pumpkin_core::random::{get_seed, java_string_hash, legacy_rand::LegacyRand, RandomImpl}; + +#[derive(Clone, Copy)] +pub struct Seed(pub u64); + +impl From<&str> for Seed { + fn from(value: &str) -> Self { + let trimmed = value.trim(); + let value = if !trimmed.is_empty() { + let i64_value = trimmed + .parse::() + .unwrap_or_else(|_| java_string_hash(trimmed) as i64); + Some(i64_value as u64) + } else { + None + }; + + Seed(value.unwrap_or_else(|| LegacyRand::from_seed(get_seed()).next_i64() as u64)) + } +} diff --git a/pumpkin-world/src/item/item_registry.rs b/pumpkin-world/src/item/item_registry.rs index e446e3622..e393acc1d 100644 --- a/pumpkin-world/src/item/item_registry.rs +++ b/pumpkin-world/src/item/item_registry.rs @@ -57,6 +57,15 @@ pub struct Modifier { pub type_val: String, pub id: String, pub amount: f64, - pub operation: String, + pub operation: Operation, + // TODO: Make this an enum pub slot: String, } + +#[derive(Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum Operation { + AddValue, + AddMultipliedBase, + AddMultipliedTotal, +} diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index b86280828..ede2d6168 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -2,7 +2,6 @@ use std::{path::PathBuf, sync::Arc}; use dashmap::{DashMap, Entry}; use num_traits::Zero; -use pumpkin_config::BASIC_CONFIG; use pumpkin_core::math::vector2::Vector2; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use tokio::{ @@ -14,7 +13,9 @@ use crate::{ chunk::{ anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError, }, - world_gen::{get_world_gen, Seed, WorldGenerator}, + generation::{get_world_gen, Seed, WorldGenerator}, + lock::{anvil::AnvilLevelLocker, LevelLocker}, + world_info::{anvil::AnvilLevelInfo, LevelData, WorldInfoReader, WorldInfoWriter}, }; /// The `Level` module provides functionality for working with chunks within or outside a Minecraft world. @@ -28,60 +29,71 @@ use crate::{ /// For more details on world generation, refer to the `WorldGenerator` module. pub struct Level { pub seed: Seed, - save_file: Option, + pub level_info: LevelData, + world_info_writer: Arc, + level_folder: LevelFolder, loaded_chunks: Arc, Arc>>>, chunk_watchers: Arc, usize>>, chunk_reader: Arc, world_gen: Arc, + // Gets unlocked when dropped + // TODO: Make this a trait + _locker: Arc, } #[derive(Clone)] -pub struct SaveFile { +pub struct LevelFolder { pub root_folder: PathBuf, pub region_folder: PathBuf, } -fn get_or_create_seed() -> Seed { - // TODO: if there is a seed in the config (!= 0) use it. Otherwise make a random one - Seed::from(BASIC_CONFIG.seed.as_str()) -} - impl Level { pub fn from_root_folder(root_folder: PathBuf) -> Self { // If we are using an already existing world we want to read the seed from the level.dat, If not we want to check if there is a seed in the config, if not lets create a random one - if root_folder.exists() { - let region_folder = root_folder.join("region"); - assert!( - region_folder.exists(), - "World region folder does not exist, despite there being a root folder." - ); - // TODO: read seed from level.dat - let seed = Seed(0); - let world_gen = get_world_gen(seed).into(); // TODO Read Seed from config. + let region_folder = root_folder.join("region"); + if !region_folder.exists() { + std::fs::create_dir_all(®ion_folder).expect("Failed to create Region folder"); + } + let level_folder = LevelFolder { + root_folder, + region_folder, + }; - Self { - seed, - world_gen, - save_file: Some(SaveFile { - root_folder, - region_folder, - }), - chunk_reader: Arc::new(AnvilChunkReader::new()), - loaded_chunks: Arc::new(DashMap::new()), - chunk_watchers: Arc::new(DashMap::new()), - } - } else { - let seed = get_or_create_seed(); - let world_gen = get_world_gen(seed).into(); // TODO Read Seed from config. - Self { - seed, - world_gen, - save_file: None, - chunk_reader: Arc::new(AnvilChunkReader::new()), - loaded_chunks: Arc::new(DashMap::new()), - chunk_watchers: Arc::new(DashMap::new()), - } + // if we fail to lock, lets crash ???. maybe not the best soultion when we have a large server with many worlds and one is locked. + // So TODO + let locker = AnvilLevelLocker::look(&level_folder).expect("Failed to lock level"); + + // TODO: Load info correctly based on world format type + let level_info = AnvilLevelInfo + .read_world_info(&level_folder) + .unwrap_or_default(); // TODO: Improve error handling + let seed = Seed(level_info.world_gen_settings.seed as u64); + let world_gen = get_world_gen(seed).into(); + + Self { + seed, + world_gen, + world_info_writer: Arc::new(AnvilLevelInfo), + level_folder, + chunk_reader: Arc::new(AnvilChunkReader::new()), + loaded_chunks: Arc::new(DashMap::new()), + chunk_watchers: Arc::new(DashMap::new()), + level_info, + _locker: Arc::new(locker), + } + } + + pub async fn save(&self) { + log::info!("Saving level..."); + // lets first save all chunks + for chunk in self.loaded_chunks.iter() { + let chunk = chunk.read().await; + self.clean_chunk(&chunk.position); } + // then lets save the world info + self.world_info_writer + .write_world_info(self.level_info.clone(), &self.level_folder) + .expect("Failed to save world info"); } pub fn get_block() {} @@ -195,10 +207,10 @@ impl Level { fn load_chunk_from_save( chunk_reader: Arc, - save_file: SaveFile, + save_file: &LevelFolder, chunk_pos: Vector2, ) -> Result>>, ChunkReadingError> { - match chunk_reader.read_chunk(&save_file, &chunk_pos) { + match chunk_reader.read_chunk(save_file, &chunk_pos) { Ok(data) => Ok(Some(Arc::new(RwLock::new(data)))), Err( ChunkReadingError::ChunkNotExist @@ -223,7 +235,7 @@ impl Level { let channel = channel.clone(); let loaded_chunks = self.loaded_chunks.clone(); let chunk_reader = self.chunk_reader.clone(); - let save_file = self.save_file.clone(); + let level_info = self.level_folder.clone(); let world_gen = self.world_gen.clone(); let chunk_pos = *at; @@ -231,20 +243,18 @@ impl Level { .get(&chunk_pos) .map(|entry| entry.value().clone()) .unwrap_or_else(|| { - let loaded_chunk = save_file - .and_then(|save_file| { - match Self::load_chunk_from_save(chunk_reader, save_file, chunk_pos) { - Ok(chunk) => chunk, - Err(err) => { - log::error!( - "Failed to read chunk (regenerating) {:?}: {:?}", - chunk_pos, - err - ); - None - } + let loaded_chunk = + match Self::load_chunk_from_save(chunk_reader, &level_info, chunk_pos) { + Ok(chunk) => chunk, + Err(err) => { + log::error!( + "Failed to read chunk (regenerating) {:?}: {:?}", + chunk_pos, + err + ); + None } - }) + } .unwrap_or_else(|| { Arc::new(RwLock::new(world_gen.generate_chunk(chunk_pos))) }); diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs index d1d6284c6..ec23b1516 100644 --- a/pumpkin-world/src/lib.rs +++ b/pumpkin-world/src/lib.rs @@ -1,11 +1,11 @@ -use pumpkin_core::math::vector2::Vector2; -use world_gen::{ +use generation::{ aquifer_sampler::{FluidLevel, FluidLevelSampler}, chunk_noise::{ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK}, generation_shapes::GenerationShape, noise::{config::NoiseConfig, router::OVERWORLD_NOISE_ROUTER}, proto_chunk::{ProtoChunk, StandardChunkFluidLevelSampler}, }; +use pumpkin_core::math::vector2::Vector2; pub mod biome; pub mod block; @@ -13,10 +13,11 @@ pub mod chunk; pub mod coordinates; pub mod cylindrical_chunk_iterator; pub mod dimension; +mod generation; pub mod item; pub mod level; -mod world_gen; - +mod lock; +pub mod world_info; pub const WORLD_HEIGHT: usize = 384; pub const WORLD_LOWEST_Y: i16 = -64; pub const WORLD_MAX_Y: i16 = WORLD_HEIGHT as i16 - WORLD_LOWEST_Y.abs(); diff --git a/pumpkin-world/src/lock/anvil.rs b/pumpkin-world/src/lock/anvil.rs new file mode 100644 index 000000000..7864cf711 --- /dev/null +++ b/pumpkin-world/src/lock/anvil.rs @@ -0,0 +1,32 @@ +use file_guard::{FileGuard, Lock}; + +use super::{LevelLocker, LockError}; + +use std::{fs::File, io::Write, sync::Arc}; + +pub struct AnvilLevelLocker { + _lock: Option>>, +} + +const SESSION_LOCK_FILE_NAME: &str = "session.lock"; + +const SNOWMAN: &[u8] = "☃".as_bytes(); + +impl LevelLocker for AnvilLevelLocker { + fn look(folder: &crate::level::LevelFolder) -> Result { + let file_path = folder.root_folder.join(SESSION_LOCK_FILE_NAME); + let mut file = File::options() + .create(true) + .truncate(false) + .write(true) + .open(file_path) + .unwrap(); + // im not joking, mojang writes a snowman into the lock file + file.write_all(SNOWMAN) + .map_err(|_| LockError::FailedWrite)?; + let file_arc = Arc::new(file); + let lock = file_guard::try_lock(file_arc, Lock::Exclusive, 0, 1) + .map_err(|_| LockError::AlreadyLocked(SESSION_LOCK_FILE_NAME.to_string()))?; + Ok(Self { _lock: Some(lock) }) + } +} diff --git a/pumpkin-world/src/lock/mod.rs b/pumpkin-world/src/lock/mod.rs new file mode 100644 index 000000000..bf4b262f2 --- /dev/null +++ b/pumpkin-world/src/lock/mod.rs @@ -0,0 +1,18 @@ +use thiserror::Error; + +use crate::level::LevelFolder; + +pub mod anvil; + +// Gets unlocked when dropped +pub trait LevelLocker: Send + Sync { + fn look(folder: &LevelFolder) -> Result; +} + +#[derive(Error, Debug)] +pub enum LockError { + #[error("Oh no, Level is already locked by {0}")] + AlreadyLocked(String), + #[error("Failed to write into lock file")] + FailedWrite, +} diff --git a/pumpkin-world/src/world_gen/seed.rs b/pumpkin-world/src/world_gen/seed.rs deleted file mode 100644 index 137a6ecdd..000000000 --- a/pumpkin-world/src/world_gen/seed.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::hash::{DefaultHasher, Hash, Hasher}; - -#[derive(Clone, Copy)] -pub struct Seed(pub i64); - -impl From<&str> for Seed { - fn from(value: &str) -> Self { - // TODO replace with a deterministic hasher (the same as vanilla?) - let mut hasher = DefaultHasher::new(); - value.hash(&mut hasher); - - // TODO use cast_signed once the feature is stabilized. - Self(hasher.finish() as i64) - } -} diff --git a/pumpkin-world/src/world_info/anvil.rs b/pumpkin-world/src/world_info/anvil.rs new file mode 100644 index 000000000..080d61e19 --- /dev/null +++ b/pumpkin-world/src/world_info/anvil.rs @@ -0,0 +1,88 @@ +use std::{ + fs::OpenOptions, + io::{Read, Write}, + time::{SystemTime, UNIX_EPOCH}, +}; + +use flate2::{read::GzDecoder, write::GzEncoder, Compression}; +use serde::{Deserialize, Serialize}; + +use crate::level::LevelFolder; + +use super::{LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter}; + +const LEVEL_DAT_FILE_NAME: &str = "level.dat"; + +pub struct AnvilLevelInfo; + +impl WorldInfoReader for AnvilLevelInfo { + fn read_world_info(&self, level_folder: &LevelFolder) -> Result { + let path = level_folder.root_folder.join(LEVEL_DAT_FILE_NAME); + + let mut world_info_file = OpenOptions::new().read(true).open(path)?; + + let mut buffer = Vec::new(); + world_info_file.read_to_end(&mut buffer)?; + + // try to decompress using GZip + let mut decoder = GzDecoder::new(&buffer[..]); + let mut decompressed_data = Vec::new(); + decoder.read_to_end(&mut decompressed_data)?; + + let info = fastnbt::from_bytes::(&decompressed_data) + .map_err(|e| WorldInfoError::DeserializationError(e.to_string()))?; + + // todo check version + + Ok(info.data) + } +} + +impl WorldInfoWriter for AnvilLevelInfo { + fn write_world_info( + &self, + info: LevelData, + level_folder: &LevelFolder, + ) -> Result<(), WorldInfoError> { + let start = SystemTime::now(); + let since_the_epoch = start + .duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + let level = LevelDat { + data: LevelData { + allow_commands: info.allow_commands, + data_version: info.data_version, + difficulty: info.difficulty, + world_gen_settings: info.world_gen_settings, + last_played: since_the_epoch.as_millis() as i64, + level_name: info.level_name, + spawn_x: info.spawn_x, + spawn_y: info.spawn_y, + spawn_z: info.spawn_z, + nbt_version: info.nbt_version, + version: info.version, + }, + }; + // convert it into nbt + let nbt = pumpkin_nbt::serializer::to_bytes_unnamed(&level).unwrap(); + // now compress using GZip, TODO: im not sure about the to_vec, but writer is not implemented for BytesMut, see https://github.com/tokio-rs/bytes/pull/478 + let mut encoder = GzEncoder::new(nbt.to_vec(), Compression::best()); + let compressed_data = Vec::new(); + encoder.write_all(&compressed_data)?; + + // open file + let path = level_folder.root_folder.join(LEVEL_DAT_FILE_NAME); + let mut world_info_file = OpenOptions::new().write(true).open(path)?; + // write compressed data into file + world_info_file.write_all(&compressed_data).unwrap(); + + Ok(()) + } +} + +#[derive(Serialize, Deserialize)] +pub struct LevelDat { + // This tag contains all the level data. + #[serde(rename = "Data")] + pub data: LevelData, +} diff --git a/pumpkin-world/src/world_info/mod.rs b/pumpkin-world/src/world_info/mod.rs new file mode 100644 index 000000000..d06be497b --- /dev/null +++ b/pumpkin-world/src/world_info/mod.rs @@ -0,0 +1,130 @@ +use pumpkin_config::BASIC_CONFIG; +use pumpkin_core::Difficulty; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::{generation::Seed, level::LevelFolder}; + +pub mod anvil; + +pub(crate) trait WorldInfoReader { + fn read_world_info(&self, level_folder: &LevelFolder) -> Result; +} + +pub(crate) trait WorldInfoWriter: Sync + Send { + fn write_world_info( + &self, + info: LevelData, + level_folder: &LevelFolder, + ) -> Result<(), WorldInfoError>; +} + +#[derive(Serialize, Deserialize, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct LevelData { + // true if cheats are enabled. + pub allow_commands: bool, + // An integer displaying the data version. + pub data_version: i32, + // The current difficulty setting. + pub difficulty: Difficulty, + // the generation settings for each dimension. + pub world_gen_settings: WorldGenSettings, + // The Unix time in milliseconds when the level was last loaded. + pub last_played: i64, + // The name of the level. + pub level_name: String, + // The X coordinate of the world spawn. + pub spawn_x: i32, + // The Y coordinate of the world spawn. + pub spawn_y: i32, + // The Z coordinate of the world spawn. + pub spawn_z: i32, + #[serde(rename = "version")] + // The NBT version of the level + pub nbt_version: i32, + #[serde(rename = "Version")] + pub version: WorldVersion, + // TODO: Implement the rest of the fields +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct WorldGenSettings { + // the numerical seed of the world + pub seed: i64, +} + +fn get_or_create_seed() -> Seed { + // TODO: if there is a seed in the config (!= 0) use it. Otherwise make a random one + Seed::from(BASIC_CONFIG.seed.as_str()) +} + +impl Default for WorldGenSettings { + fn default() -> Self { + Self { + seed: get_or_create_seed().0 as i64, + } + } +} + +#[derive(Serialize, Deserialize, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct WorldVersion { + // The version name as a string, e.g. "15w32b". + pub name: String, + // An integer displaying the data version. + pub id: i32, + // Whether the version is a snapshot or not. + pub snapshot: bool, + // Developing series. In 1.18 experimental snapshots, it was set to "ccpreview". In others, set to "main". + pub series: String, +} + +impl Default for WorldVersion { + fn default() -> Self { + Self { + name: "1.24.4".to_string(), + id: -1, + snapshot: false, + series: "main".to_string(), + } + } +} + +impl Default for LevelData { + fn default() -> Self { + Self { + allow_commands: true, + // TODO + data_version: -1, + difficulty: Difficulty::Normal, + world_gen_settings: Default::default(), + last_played: -1, + level_name: "world".to_string(), + spawn_x: 0, + spawn_y: 200, + spawn_z: 0, + nbt_version: -1, + version: Default::default(), + } + } +} + +#[derive(Error, Debug)] +pub enum WorldInfoError { + #[error("Io error: {0}")] + IoError(std::io::ErrorKind), + #[error("Info not found!")] + InfoNotFound, + #[error("Deserialization error: {0}")] + DeserializationError(String), +} + +impl From for WorldInfoError { + fn from(value: std::io::Error) -> Self { + match value.kind() { + std::io::ErrorKind::NotFound => Self::InfoNotFound, + value => Self::IoError(value), + } + } +} diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 38bd71902..87b030201 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -21,7 +21,6 @@ pumpkin-protocol = { path = "../pumpkin-protocol" } pumpkin-registry = { path = "../pumpkin-registry" } pumpkin-macros = { path = "../pumpkin-macros" } -itertools.workspace = true log.workspace = true crossbeam.workspace = true uuid.workspace = true @@ -31,7 +30,6 @@ thiserror.workspace = true num-traits.workspace = true num-derive.workspace = true -parking_lot.workspace = true # config serde.workspace = true @@ -48,7 +46,7 @@ rsa = "0.9.7" rsa-der = "0.3.0" # authentication -reqwest = { version = "0.12.9", default-features = false, features = [ +reqwest = { version = "0.12.10", default-features = false, features = [ "http2", "json", "macos-system-configuration", @@ -56,14 +54,14 @@ reqwest = { version = "0.12.9", default-features = false, features = [ ] } sha1 = "0.10.6" -digest = "=0.11.0-pre.9" # velocity en hmac = "0.12.1" sha2 = "0.10.8" -# icon loading base64 = "0.22.1" + +# icon loading png = "0.17.15" # logging diff --git a/pumpkin/src/block/block_manager.rs b/pumpkin/src/block/block_manager.rs index 50ab938e8..0fb26ede3 100644 --- a/pumpkin/src/block/block_manager.rs +++ b/pumpkin/src/block/block_manager.rs @@ -2,6 +2,7 @@ use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; use crate::entity::player::Player; use crate::server::Server; use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::OpenContainer; use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; use std::collections::HashMap; @@ -21,8 +22,7 @@ pub struct BlockManager { impl BlockManager { pub fn register(&mut self, block: T) { - self.blocks - .insert(block.name().to_string(), Arc::new(block)); + self.blocks.insert(block.name(), Arc::new(block)); } pub async fn on_use( @@ -34,7 +34,7 @@ impl BlockManager { ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.on_use(player, location, server).await; + pumpkin_block.on_use(block, player, location, server).await; } } @@ -49,7 +49,7 @@ impl BlockManager { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { return pumpkin_block - .on_use_with_item(player, location, item, server) + .on_use_with_item(block, player, location, item, server) .await; } BlockActionResult::Continue @@ -64,7 +64,9 @@ impl BlockManager { ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.on_placed(player, location, server).await; + pumpkin_block + .on_placed(block, player, location, server) + .await; } } @@ -77,7 +79,25 @@ impl BlockManager { ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.on_broken(player, location, server).await; + pumpkin_block + .on_broken(block, player, location, server) + .await; + } + } + + pub async fn on_close( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + container: &mut OpenContainer, + ) { + let pumpkin_block = self.get_pumpkin_block(block); + if let Some(pumpkin_block) = pumpkin_block { + pumpkin_block + .on_close(block, player, location, server, container) + .await; } } diff --git a/pumpkin/src/block/blocks/chest.rs b/pumpkin/src/block/blocks/chest.rs new file mode 100644 index 000000000..313d9a5bf --- /dev/null +++ b/pumpkin/src/block/blocks/chest.rs @@ -0,0 +1,91 @@ +use async_trait::async_trait; +use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::{Chest, OpenContainer, WindowType}; +use pumpkin_macros::{pumpkin_block, sound}; +use pumpkin_world::{block::block_registry::Block, item::item_registry::Item}; + +use crate::{ + block::{block_manager::BlockActionResult, pumpkin_block::PumpkinBlock}, + entity::player::Player, + server::Server, +}; + +#[pumpkin_block("minecraft:chest")] +pub struct ChestBlock; + +#[async_trait] +impl PumpkinBlock for ChestBlock { + async fn on_use<'a>( + &self, + block: &Block, + player: &Player, + _location: WorldPosition, + server: &Server, + ) { + self.open_chest_block(block, player, _location, server) + .await; + player + .world() + .play_block_sound(sound!("block.chest.open"), _location) + .await; + } + + async fn on_use_with_item<'a>( + &self, + block: &Block, + player: &Player, + _location: WorldPosition, + _item: &Item, + server: &Server, + ) -> BlockActionResult { + self.open_chest_block(block, player, _location, server) + .await; + BlockActionResult::Consume + } + + async fn on_broken<'a>( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + super::standard_on_broken_with_container(block, player, location, server).await; + } + + async fn on_close<'a>( + &self, + _block: &Block, + player: &Player, + _location: WorldPosition, + _server: &Server, + _container: &OpenContainer, + ) { + player + .world() + .play_block_sound(sound!("block.chest.close"), _location) + .await; + // TODO: send entity updates close + } +} + +impl ChestBlock { + pub async fn open_chest_block( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + // TODO: shouldn't Chest and window type be constrained together to avoid errors? + super::standard_open_container::( + block, + player, + location, + server, + WindowType::Generic9x3, + ) + .await; + // TODO: send entity updates open + } +} diff --git a/pumpkin/src/block/blocks/crafting_table.rs b/pumpkin/src/block/blocks/crafting_table.rs index 22e224fca..01ad41795 100644 --- a/pumpkin/src/block/blocks/crafting_table.rs +++ b/pumpkin/src/block/blocks/crafting_table.rs @@ -6,45 +6,83 @@ use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_inventory::{CraftingTable, OpenContainer, WindowType}; use pumpkin_macros::pumpkin_block; -use pumpkin_world::item::item_registry::Item; +use pumpkin_world::{block::block_registry::Block, item::item_registry::Item}; #[pumpkin_block("minecraft:crafting_table")] pub struct CraftingTableBlock; #[async_trait] impl PumpkinBlock for CraftingTableBlock { - async fn on_use<'a>(&self, player: &Player, _location: WorldPosition, server: &Server) { - self.open_crafting_screen(player, server).await; + async fn on_use<'a>( + &self, + block: &Block, + player: &Player, + _location: WorldPosition, + server: &Server, + ) { + self.open_crafting_screen(block, player, _location, server) + .await; } async fn on_use_with_item<'a>( &self, + block: &Block, player: &Player, _location: WorldPosition, _item: &Item, server: &Server, ) -> BlockActionResult { - self.open_crafting_screen(player, server).await; + self.open_crafting_screen(block, player, _location, server) + .await; BlockActionResult::Consume } -} -impl CraftingTableBlock { - pub async fn open_crafting_screen(&self, player: &Player, server: &Server) { - //TODO: Adjust /craft command to real crafting table + async fn on_broken<'a>( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + super::standard_on_broken_with_container(block, player, location, server).await; + } + + async fn on_close<'a>( + &self, + _block: &Block, + player: &Player, + _location: WorldPosition, + _server: &Server, + container: &OpenContainer, + ) { let entity_id = player.entity_id(); - player.open_container.store(Some(1)); - { - let mut open_containers = server.open_containers.write().await; - if let Some(ender_chest) = open_containers.get_mut(&1) { - ender_chest.add_player(entity_id); - } else { - let open_container = OpenContainer::new_empty_container::(entity_id); - open_containers.insert(1, open_container); + for player_id in container.all_player_ids() { + if entity_id == player_id { + container.clear_all_slots().await; } } - player - .open_container(server, WindowType::CraftingTable) - .await; + + // TODO: items should be re-added to player inventory or dropped dependending on if they are in movement. + // TODO: unique containers should be implemented as a separate stack internally (optimizes large player servers for example) + // TODO: ephemeral containers (crafting tables) might need to be separate data structure than stored (ender chest) + } +} + +impl CraftingTableBlock { + pub async fn open_crafting_screen( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + super::standard_open_container_unique::( + block, + player, + location, + server, + WindowType::CraftingTable, + ) + .await; } } diff --git a/pumpkin/src/block/blocks/furnace.rs b/pumpkin/src/block/blocks/furnace.rs new file mode 100644 index 000000000..1ecdf9950 --- /dev/null +++ b/pumpkin/src/block/blocks/furnace.rs @@ -0,0 +1,70 @@ +use crate::block::block_manager::BlockActionResult; +use crate::entity::player::Player; +use async_trait::async_trait; +use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::Furnace; +use pumpkin_inventory::WindowType; +use pumpkin_macros::pumpkin_block; +use pumpkin_world::block::block_registry::Block; +use pumpkin_world::item::item_registry::Item; + +use crate::{block::pumpkin_block::PumpkinBlock, server::Server}; + +#[pumpkin_block("minecraft:furnace")] +pub struct FurnaceBlock; + +#[async_trait] +impl PumpkinBlock for FurnaceBlock { + async fn on_use<'a>( + &self, + block: &Block, + player: &Player, + _location: WorldPosition, + server: &Server, + ) { + self.open_furnace_screen(block, player, _location, server) + .await; + } + + async fn on_use_with_item<'a>( + &self, + block: &Block, + player: &Player, + _location: WorldPosition, + _item: &Item, + server: &Server, + ) -> BlockActionResult { + self.open_furnace_screen(block, player, _location, server) + .await; + BlockActionResult::Consume + } + + async fn on_broken<'a>( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + super::standard_on_broken_with_container(block, player, location, server).await; + } +} + +impl FurnaceBlock { + pub async fn open_furnace_screen( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + super::standard_open_container::( + block, + player, + location, + server, + WindowType::Furnace, + ) + .await; + } +} diff --git a/pumpkin/src/block/blocks/jukebox.rs b/pumpkin/src/block/blocks/jukebox.rs index 3c52395ba..4836c4be7 100644 --- a/pumpkin/src/block/blocks/jukebox.rs +++ b/pumpkin/src/block/blocks/jukebox.rs @@ -6,6 +6,7 @@ use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_macros::pumpkin_block; use pumpkin_registry::SYNCED_REGISTRIES; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; #[pumpkin_block("minecraft:jukebox")] @@ -13,7 +14,13 @@ pub struct JukeboxBlock; #[async_trait] impl PumpkinBlock for JukeboxBlock { - async fn on_use<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) { + async fn on_use<'a>( + &self, + _block: &Block, + player: &Player, + location: WorldPosition, + _server: &Server, + ) { // For now just stop the music at this position let world = &player.living_entity.entity.world; @@ -22,6 +29,7 @@ impl PumpkinBlock for JukeboxBlock { async fn on_use_with_item<'a>( &self, + _block: &Block, player: &Player, location: WorldPosition, item: &Item, @@ -49,7 +57,13 @@ impl PumpkinBlock for JukeboxBlock { BlockActionResult::Consume } - async fn on_broken<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) { + async fn on_broken<'a>( + &self, + _block: &Block, + player: &Player, + location: WorldPosition, + _server: &Server, + ) { // For now just stop the music at this position let world = &player.living_entity.entity.world; diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index 8e7fde733..b0b128028 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -1,2 +1,114 @@ +use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::{Container, OpenContainer, WindowType}; +use pumpkin_world::block::block_registry::Block; + +use crate::{entity::player::Player, server::Server}; + +pub(crate) mod chest; pub(crate) mod crafting_table; +pub(crate) mod furnace; pub(crate) mod jukebox; + +/// The standard destroy with container removes the player forcibly from the container, +/// drops items to the floor, and back to the player's inventory if the item stack is in movement. +pub async fn standard_on_broken_with_container( + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, +) { + // TODO: drop all items and back to players inventory if in motion + if let Some(all_container_ids) = server.get_all_container_ids(location, block.clone()).await { + let mut open_containers = server.open_containers.write().await; + for individual_id in all_container_ids { + if let Some(container) = open_containers.get_mut(&u64::from(individual_id)) { + container.clear_all_slots().await; + player.open_container.store(None); + close_all_in_container(player, container).await; + container.clear_all_players(); + } + } + } +} + +/// The standard open container creates a new container if a container of the same block +/// type does not exist at the selected block location. If a container of the same type exists, the player +/// is added to the currently connected players to that container. +pub async fn standard_open_container( + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + window_type: WindowType, +) { + let entity_id = player.entity_id(); + // If container exists, add player to container, otherwise create new container + if let Some(container_id) = server.get_container_id(location, block.clone()).await { + let mut open_containers = server.open_containers.write().await; + log::debug!("Using previous standard container ID: {}", container_id); + if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { + container.add_player(entity_id); + player.open_container.store(Some(container_id.into())); + } + } else { + let mut open_containers = server.open_containers.write().await; + let new_id = server.new_container_id(); + log::debug!("Creating new standard container ID: {}", new_id); + let open_container = + OpenContainer::new_empty_container::(entity_id, Some(location), Some(block.clone())); + open_containers.insert(new_id.into(), open_container); + player.open_container.store(Some(new_id.into())); + } + player.open_container(server, window_type).await; +} + +pub async fn standard_open_container_unique( + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + window_type: WindowType, +) { + let entity_id = player.entity_id(); + let mut open_containers = server.open_containers.write().await; + let mut id_to_use = -1; + + // TODO: we can do better than brute force + for (id, container) in open_containers.iter() { + if let Some(a_block) = container.get_block() { + if a_block.id == block.id && container.all_player_ids().is_empty() { + id_to_use = *id as i64; + } + } + } + + if id_to_use == -1 { + let new_id = server.new_container_id(); + log::debug!("Creating new unqiue container ID: {}", new_id); + let open_container = + OpenContainer::new_empty_container::(entity_id, Some(location), Some(block.clone())); + + open_containers.insert(new_id.into(), open_container); + + player.open_container.store(Some(new_id.into())); + } else { + log::debug!("Using previous unqiue container ID: {}", id_to_use); + if let Some(unique_container) = open_containers.get_mut(&(id_to_use as u64)) { + unique_container.set_location(Some(location)).await; + unique_container.add_player(entity_id); + player + .open_container + .store(Some(id_to_use.try_into().unwrap())); + } + } + drop(open_containers); + player.open_container(server, window_type).await; +} + +pub async fn close_all_in_container(player: &Player, container: &OpenContainer) { + for id in container.all_player_ids() { + if let Some(remote_player) = player.world().get_player_by_entityid(id).await { + remote_player.close_container().await; + } + } +} diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index 10d1bc3ff..b07790339 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -1,3 +1,6 @@ +use blocks::chest::ChestBlock; +use blocks::furnace::FurnaceBlock; + use crate::block::block_manager::BlockManager; use crate::block::blocks::crafting_table::CraftingTableBlock; use crate::block::blocks::jukebox::JukeboxBlock; @@ -13,6 +16,8 @@ pub fn default_block_manager() -> Arc { manager.register(JukeboxBlock); manager.register(CraftingTableBlock); + manager.register(FurnaceBlock); + manager.register(ChestBlock); Arc::new(manager) } diff --git a/pumpkin/src/block/pumpkin_block.rs b/pumpkin/src/block/pumpkin_block.rs index 78a3bd057..c0a65c316 100644 --- a/pumpkin/src/block/pumpkin_block.rs +++ b/pumpkin/src/block/pumpkin_block.rs @@ -3,6 +3,8 @@ use crate::entity::player::Player; use crate::server::Server; use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::OpenContainer; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; pub trait BlockMetadata { @@ -15,9 +17,17 @@ pub trait BlockMetadata { #[async_trait] pub trait PumpkinBlock: Send + Sync { - async fn on_use<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} + async fn on_use<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + ) { + } async fn on_use_with_item<'a>( &self, + _block: &Block, _player: &Player, _location: WorldPosition, _item: &Item, @@ -26,7 +36,31 @@ pub trait PumpkinBlock: Send + Sync { BlockActionResult::Continue } - async fn on_placed<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} + async fn on_placed<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + ) { + } - async fn on_broken<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} + async fn on_broken<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + ) { + } + + async fn on_close<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + _container: &OpenContainer, + ) { + } } diff --git a/pumpkin/src/command/args/arg_block.rs b/pumpkin/src/command/args/arg_block.rs index 41e3ebd31..779625522 100644 --- a/pumpkin/src/command/args/arg_block.rs +++ b/pumpkin/src/command/args/arg_block.rs @@ -31,7 +31,7 @@ impl GetClientSideArgParser for BlockArgumentConsumer { #[async_trait] impl ArgumentConsumer for BlockArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -41,7 +41,7 @@ impl ArgumentConsumer for BlockArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -51,26 +51,24 @@ impl ArgumentConsumer for BlockArgumentConsumer { } impl DefaultNameArgConsumer for BlockArgumentConsumer { - fn default_name(&self) -> &'static str { - "block" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + fn default_name(&self) -> String { + "block".to_string() } } impl<'a> FindArg<'a> for BlockArgumentConsumer { type Data = &'a Block; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { - Some(Arg::Block(name)) => match block_registry::get_block(name) { - Some(block) => Ok(block), - None => Err(CommandError::GeneralCommandIssue(format!( - "Block {name} does not exist." - ))), - }, + Some(Arg::Block(name)) => block_registry::get_block(name).map_or_else( + || { + Err(CommandError::GeneralCommandIssue(format!( + "Block {name} does not exist." + ))) + }, + Result::Ok, + ), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), } } diff --git a/pumpkin/src/command/args/arg_bool.rs b/pumpkin/src/command/args/arg_bool.rs index fc47f590d..39b69c899 100644 --- a/pumpkin/src/command/args/arg_bool.rs +++ b/pumpkin/src/command/args/arg_bool.rs @@ -23,7 +23,7 @@ impl GetClientSideArgParser for BoolArgConsumer { #[async_trait] impl ArgumentConsumer for BoolArgConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -38,7 +38,7 @@ impl ArgumentConsumer for BoolArgConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -50,7 +50,7 @@ impl ArgumentConsumer for BoolArgConsumer { impl<'a> FindArg<'a> for BoolArgConsumer { type Data = bool; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Bool(data)) => Ok(*data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_bossbar_color.rs b/pumpkin/src/command/args/arg_bossbar_color.rs index 94df5a4c8..664264ea9 100644 --- a/pumpkin/src/command/args/arg_bossbar_color.rs +++ b/pumpkin/src/command/args/arg_bossbar_color.rs @@ -27,7 +27,7 @@ impl GetClientSideArgParser for BossbarColorArgumentConsumer { #[async_trait] impl ArgumentConsumer for BossbarColorArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -49,7 +49,7 @@ impl ArgumentConsumer for BossbarColorArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -57,26 +57,22 @@ impl ArgumentConsumer for BossbarColorArgumentConsumer { let colors = ["blue", "green", "pink", "purple", "red", "white", "yellow"]; let suggestions: Vec = colors .iter() - .map(|color| CommandSuggestion::new(color, None)) + .map(|color| CommandSuggestion::new((*color).to_string(), None)) .collect(); Ok(Some(suggestions)) } } impl DefaultNameArgConsumer for BossbarColorArgumentConsumer { - fn default_name(&self) -> &'static str { - "color" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + fn default_name(&self) -> String { + "color".to_string() } } impl<'a> FindArg<'a> for BossbarColorArgumentConsumer { type Data = &'a BossbarColor; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::BossbarColor(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_bossbar_style.rs b/pumpkin/src/command/args/arg_bossbar_style.rs index 6111688be..6d3b2d441 100644 --- a/pumpkin/src/command/args/arg_bossbar_style.rs +++ b/pumpkin/src/command/args/arg_bossbar_style.rs @@ -27,7 +27,7 @@ impl GetClientSideArgParser for BossbarStyleArgumentConsumer { #[async_trait] impl ArgumentConsumer for BossbarStyleArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -47,7 +47,7 @@ impl ArgumentConsumer for BossbarStyleArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -61,26 +61,22 @@ impl ArgumentConsumer for BossbarStyleArgumentConsumer { ]; let suggestions: Vec = styles .iter() - .map(|style| CommandSuggestion::new(style, None)) + .map(|style| CommandSuggestion::new((*style).to_string(), None)) .collect(); Ok(Some(suggestions)) } } impl DefaultNameArgConsumer for BossbarStyleArgumentConsumer { - fn default_name(&self) -> &'static str { - "style" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + fn default_name(&self) -> String { + "style".to_string() } } impl<'a> FindArg<'a> for BossbarStyleArgumentConsumer { type Data = &'a BossbarDivisions; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::BossbarStyle(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_bounded_num.rs b/pumpkin/src/command/args/arg_bounded_num.rs index 4a9f718bb..0cd20af26 100644 --- a/pumpkin/src/command/args/arg_bounded_num.rs +++ b/pumpkin/src/command/args/arg_bounded_num.rs @@ -22,10 +22,10 @@ pub(crate) struct BoundedNumArgumentConsumer { #[async_trait] impl ArgumentConsumer for BoundedNumArgumentConsumer where - BoundedNumArgumentConsumer: GetClientSideArgParser, + Self: GetClientSideArgParser, { async fn consume<'a>( - &self, + &'a self, _src: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -48,7 +48,7 @@ where } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -236,14 +236,10 @@ impl GetClientSideArgParser for BoundedNumArgumentConsumer { impl DefaultNameArgConsumer for BoundedNumArgumentConsumer where - BoundedNumArgumentConsumer: ArgumentConsumer, + Self: ArgumentConsumer, { - fn default_name(&self) -> &'static str { + fn default_name(&self) -> String { // setting a single default name for all BoundedNumArgumentConsumer variants is probably a bad idea since it would lead to confusion - self.name.expect("Only use *_default variants of methods with a BoundedNumArgumentConsumer that has a name.") - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + self.name.expect("Only use *_default variants of methods with a BoundedNumArgumentConsumer that has a name.").to_string() } } diff --git a/pumpkin/src/command/args/arg_command.rs b/pumpkin/src/command/args/arg_command.rs index ef9339778..a8ea82344 100644 --- a/pumpkin/src/command/args/arg_command.rs +++ b/pumpkin/src/command/args/arg_command.rs @@ -30,22 +30,21 @@ impl GetClientSideArgParser for CommandTreeArgumentConsumer { #[async_trait] impl ArgumentConsumer for CommandTreeArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, server: &'a Server, args: &mut RawArgs<'a>, ) -> Option> { let s = args.pop()?; - let dispatcher = &server.command_dispatcher; - return match dispatcher.get_tree(s) { - Ok(tree) => Some(Arg::CommandTree(tree)), - Err(_) => None, - }; + let dispatcher = server.command_dispatcher.read().await; + dispatcher + .get_tree(s) + .map_or_else(|_| None, |tree| Some(Arg::CommandTree(tree.clone()))) } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, server: &'a Server, input: &'a str, @@ -54,31 +53,27 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer { return Ok(None); }; - let suggestions = server - .command_dispatcher + let dispatcher = server.command_dispatcher.read().await; + let suggestions = dispatcher .commands .keys() .filter(|suggestion| suggestion.starts_with(input)) - .map(|suggestion| CommandSuggestion::new(suggestion, None)) + .map(|suggestion| CommandSuggestion::new(suggestion.to_string(), None)) .collect(); Ok(Some(suggestions)) } } impl DefaultNameArgConsumer for CommandTreeArgumentConsumer { - fn default_name(&self) -> &'static str { - "cmd" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &CommandTreeArgumentConsumer + fn default_name(&self) -> String { + "cmd".to_string() } } impl<'a> FindArg<'a> for CommandTreeArgumentConsumer { - type Data = &'a CommandTree<'a>; + type Data = &'a CommandTree; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::CommandTree(tree)) => Ok(tree), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_entities.rs b/pumpkin/src/command/args/arg_entities.rs index 9f0775973..0c202ac70 100644 --- a/pumpkin/src/command/args/arg_entities.rs +++ b/pumpkin/src/command/args/arg_entities.rs @@ -34,7 +34,7 @@ impl GetClientSideArgParser for EntitiesArgumentConsumer { #[async_trait] impl ArgumentConsumer for EntitiesArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, server: &'a Server, args: &mut RawArgs<'a>, @@ -47,7 +47,7 @@ impl ArgumentConsumer for EntitiesArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -57,19 +57,15 @@ impl ArgumentConsumer for EntitiesArgumentConsumer { } impl DefaultNameArgConsumer for EntitiesArgumentConsumer { - fn default_name(&self) -> &'static str { - "targets" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &EntitiesArgumentConsumer + fn default_name(&self) -> String { + "targets".to_string() } } impl<'a> FindArg<'a> for EntitiesArgumentConsumer { type Data = &'a [Arc]; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Entities(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_entity.rs b/pumpkin/src/command/args/arg_entity.rs index 996d1e60b..bbbed1900 100644 --- a/pumpkin/src/command/args/arg_entity.rs +++ b/pumpkin/src/command/args/arg_entity.rs @@ -14,7 +14,7 @@ use crate::server::Server; use super::super::args::ArgumentConsumer; use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; -/// todo: implement for entitites that aren't players +/// todo: implement for entities that aren't players /// /// For selecting a single entity, eg. using @s, a player name or entity uuid. /// @@ -37,7 +37,7 @@ impl GetClientSideArgParser for EntityArgumentConsumer { #[async_trait] impl ArgumentConsumer for EntityArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, server: &'a Server, args: &mut RawArgs<'a>, @@ -69,7 +69,7 @@ impl ArgumentConsumer for EntityArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -79,19 +79,15 @@ impl ArgumentConsumer for EntityArgumentConsumer { } impl DefaultNameArgConsumer for EntityArgumentConsumer { - fn default_name(&self) -> &'static str { - "target" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &EntityArgumentConsumer + fn default_name(&self) -> String { + "target".to_string() } } impl<'a> FindArg<'a> for EntityArgumentConsumer { type Data = Arc; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Entity(data)) => Ok(data.clone()), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_gamemode.rs b/pumpkin/src/command/args/arg_gamemode.rs index 469a92f9d..fee1264d4 100644 --- a/pumpkin/src/command/args/arg_gamemode.rs +++ b/pumpkin/src/command/args/arg_gamemode.rs @@ -29,7 +29,7 @@ impl GetClientSideArgParser for GamemodeArgumentConsumer { #[async_trait] impl ArgumentConsumer for GamemodeArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -50,7 +50,7 @@ impl ArgumentConsumer for GamemodeArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -60,19 +60,15 @@ impl ArgumentConsumer for GamemodeArgumentConsumer { } impl DefaultNameArgConsumer for GamemodeArgumentConsumer { - fn default_name(&self) -> &'static str { - "gamemode" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &GamemodeArgumentConsumer + fn default_name(&self) -> String { + "gamemode".to_string() } } impl<'a> FindArg<'a> for GamemodeArgumentConsumer { type Data = GameMode; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::GameMode(data)) => Ok(*data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_item.rs b/pumpkin/src/command/args/arg_item.rs index 3deeb2fe7..bf4254b94 100644 --- a/pumpkin/src/command/args/arg_item.rs +++ b/pumpkin/src/command/args/arg_item.rs @@ -29,7 +29,7 @@ impl GetClientSideArgParser for ItemArgumentConsumer { #[async_trait] impl ArgumentConsumer for ItemArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -39,7 +39,7 @@ impl ArgumentConsumer for ItemArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -49,26 +49,24 @@ impl ArgumentConsumer for ItemArgumentConsumer { } impl DefaultNameArgConsumer for ItemArgumentConsumer { - fn default_name(&self) -> &'static str { - "item" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + fn default_name(&self) -> String { + "item".to_string() } } impl<'a> FindArg<'a> for ItemArgumentConsumer { type Data = (&'a str, &'a Item); - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { - Some(Arg::Item(name)) => match item_registry::get_item(name) { - Some(item) => Ok((name, item)), - None => Err(CommandError::GeneralCommandIssue(format!( - "Item {name} does not exist." - ))), - }, + Some(Arg::Item(name)) => item_registry::get_item(name).map_or_else( + || { + Err(CommandError::GeneralCommandIssue(format!( + "Item {name} does not exist." + ))) + }, + |item| Ok((*name, item)), + ), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), } } diff --git a/pumpkin/src/command/args/arg_message.rs b/pumpkin/src/command/args/arg_message.rs index e2815de29..8d4081d68 100644 --- a/pumpkin/src/command/args/arg_message.rs +++ b/pumpkin/src/command/args/arg_message.rs @@ -29,7 +29,7 @@ impl GetClientSideArgParser for MsgArgConsumer { #[async_trait] impl ArgumentConsumer for MsgArgConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -45,7 +45,7 @@ impl ArgumentConsumer for MsgArgConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -55,19 +55,15 @@ impl ArgumentConsumer for MsgArgConsumer { } impl DefaultNameArgConsumer for MsgArgConsumer { - fn default_name(&self) -> &'static str { - "msg" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &MsgArgConsumer + fn default_name(&self) -> String { + "msg".to_string() } } impl<'a> FindArg<'a> for MsgArgConsumer { type Data = &'a str; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Msg(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_players.rs b/pumpkin/src/command/args/arg_players.rs index 7c06f8e4d..c7fdc70af 100644 --- a/pumpkin/src/command/args/arg_players.rs +++ b/pumpkin/src/command/args/arg_players.rs @@ -33,7 +33,7 @@ impl GetClientSideArgParser for PlayersArgumentConsumer { #[async_trait] impl ArgumentConsumer for PlayersArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, server: &'a Server, args: &mut RawArgs<'a>, @@ -53,11 +53,7 @@ impl ArgumentConsumer for PlayersArgumentConsumer { _ => None, }, "@r" => { - if let Some(p) = server.get_random_player().await { - Some(vec![p.clone()]) - } else { - Some(vec![]) - } + (server.get_random_player().await).map_or_else(|| Some(vec![]), |p| Some(vec![p])) } "@a" | "@e" => Some(server.get_all_players().await), name => server.get_player_by_name(name).await.map(|p| vec![p]), @@ -67,7 +63,7 @@ impl ArgumentConsumer for PlayersArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -77,19 +73,15 @@ impl ArgumentConsumer for PlayersArgumentConsumer { } impl DefaultNameArgConsumer for PlayersArgumentConsumer { - fn default_name(&self) -> &'static str { - "player" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &PlayersArgumentConsumer + fn default_name(&self) -> String { + "player".to_string() } } impl<'a> FindArg<'a> for PlayersArgumentConsumer { type Data = &'a [Arc]; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Players(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_position_2d.rs b/pumpkin/src/command/args/arg_position_2d.rs index b855348ec..9a9c5b56a 100644 --- a/pumpkin/src/command/args/arg_position_2d.rs +++ b/pumpkin/src/command/args/arg_position_2d.rs @@ -32,20 +32,20 @@ impl GetClientSideArgParser for Position2DArgumentConsumer { #[async_trait] impl ArgumentConsumer for Position2DArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, ) -> Option> { let pos = MaybeRelativePosition2D::try_new(args.pop()?, args.pop()?)?; - let vec2 = pos.try_to_abolute(src.position())?; + let vec2 = pos.try_to_absolute(src.position())?; Some(Arg::Pos2D(vec2)) } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -64,7 +64,7 @@ impl MaybeRelativePosition2D { Some(Self(x.try_into().ok()?, z.try_into().ok()?)) } - fn try_to_abolute(self, origin: Option>) -> Option> { + fn try_to_absolute(self, origin: Option>) -> Option> { Some(Vector2::new( self.0.into_absolute(origin.map(|o| o.x))?, self.1.into_absolute(origin.map(|o| o.z))?, @@ -73,19 +73,15 @@ impl MaybeRelativePosition2D { } impl DefaultNameArgConsumer for Position2DArgumentConsumer { - fn default_name(&self) -> &'static str { - "pos2d" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Position2DArgumentConsumer + fn default_name(&self) -> String { + "pos2d".to_string() } } impl<'a> FindArg<'a> for Position2DArgumentConsumer { type Data = Vector2; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Pos2D(data)) => Ok(*data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_position_3d.rs b/pumpkin/src/command/args/arg_position_3d.rs index 4940b4dbc..8df9761ca 100644 --- a/pumpkin/src/command/args/arg_position_3d.rs +++ b/pumpkin/src/command/args/arg_position_3d.rs @@ -29,7 +29,7 @@ impl GetClientSideArgParser for Position3DArgumentConsumer { #[async_trait] impl ArgumentConsumer for Position3DArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -42,7 +42,7 @@ impl ArgumentConsumer for Position3DArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -76,19 +76,15 @@ impl MaybeRelativePosition3D { } impl DefaultNameArgConsumer for Position3DArgumentConsumer { - fn default_name(&self) -> &'static str { - "pos" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Position3DArgumentConsumer + fn default_name(&self) -> String { + "pos".to_string() } } impl<'a> FindArg<'a> for Position3DArgumentConsumer { type Data = Vector3; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Pos3D(data)) => Ok(*data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_postition_block.rs b/pumpkin/src/command/args/arg_position_block.rs similarity index 89% rename from pumpkin/src/command/args/arg_postition_block.rs rename to pumpkin/src/command/args/arg_position_block.rs index b8ae25dba..4f5b3a404 100644 --- a/pumpkin/src/command/args/arg_postition_block.rs +++ b/pumpkin/src/command/args/arg_position_block.rs @@ -30,7 +30,7 @@ impl GetClientSideArgParser for BlockPosArgumentConsumer { #[async_trait] impl ArgumentConsumer for BlockPosArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -43,7 +43,7 @@ impl ArgumentConsumer for BlockPosArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -77,19 +77,15 @@ impl MaybeRelativeBlockPos { } impl DefaultNameArgConsumer for BlockPosArgumentConsumer { - fn default_name(&self) -> &'static str { - "block_pos" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &BlockPosArgumentConsumer + fn default_name(&self) -> String { + "block_pos".to_string() } } impl<'a> FindArg<'a> for BlockPosArgumentConsumer { type Data = WorldPosition; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::BlockPos(data)) => Ok(*data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_resource_location.rs b/pumpkin/src/command/args/arg_resource_location.rs index 027815937..c156a57f1 100644 --- a/pumpkin/src/command/args/arg_resource_location.rs +++ b/pumpkin/src/command/args/arg_resource_location.rs @@ -27,7 +27,7 @@ impl GetClientSideArgParser for ResourceLocationArgumentConsumer { #[async_trait] impl ArgumentConsumer for ResourceLocationArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -36,7 +36,7 @@ impl ArgumentConsumer for ResourceLocationArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -60,19 +60,15 @@ impl ArgumentConsumer for ResourceLocationArgumentConsumer { } impl DefaultNameArgConsumer for ResourceLocationArgumentConsumer { - fn default_name(&self) -> &'static str { - "id" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + fn default_name(&self) -> String { + "id".to_string() } } impl<'a> FindArg<'a> for ResourceLocationArgumentConsumer { type Data = &'a str; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::ResourceLocation(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_rotation.rs b/pumpkin/src/command/args/arg_rotation.rs index 4efdbde08..91b3363bb 100644 --- a/pumpkin/src/command/args/arg_rotation.rs +++ b/pumpkin/src/command/args/arg_rotation.rs @@ -27,7 +27,7 @@ impl GetClientSideArgParser for RotationArgumentConsumer { #[async_trait] impl ArgumentConsumer for RotationArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _src: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -51,7 +51,7 @@ impl ArgumentConsumer for RotationArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -61,19 +61,15 @@ impl ArgumentConsumer for RotationArgumentConsumer { } impl DefaultNameArgConsumer for RotationArgumentConsumer { - fn default_name(&self) -> &'static str { - "rotation" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &RotationArgumentConsumer + fn default_name(&self) -> String { + "rotation".to_string() } } impl<'a> FindArg<'a> for RotationArgumentConsumer { type Data = (f32, f32); - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Rotation(yaw, pitch)) => Ok((*yaw, *pitch)), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_simple.rs b/pumpkin/src/command/args/arg_simple.rs index 1412d63ed..67207fb8b 100644 --- a/pumpkin/src/command/args/arg_simple.rs +++ b/pumpkin/src/command/args/arg_simple.rs @@ -30,7 +30,7 @@ impl GetClientSideArgParser for SimpleArgConsumer { #[async_trait] impl ArgumentConsumer for SimpleArgConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -39,7 +39,7 @@ impl ArgumentConsumer for SimpleArgConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -51,7 +51,7 @@ impl ArgumentConsumer for SimpleArgConsumer { impl<'a> FindArg<'a> for SimpleArgConsumer { type Data = &'a str; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Simple(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs index 3274dd8d4..f1fa8f85b 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -32,7 +32,7 @@ pub(crate) mod arg_message; pub(crate) mod arg_players; pub(crate) mod arg_position_2d; pub(crate) mod arg_position_3d; -pub(crate) mod arg_postition_block; +pub(crate) mod arg_position_block; pub(crate) mod arg_resource_location; pub(crate) mod arg_rotation; pub(crate) mod arg_simple; @@ -42,21 +42,21 @@ mod coordinate; #[async_trait] pub(crate) trait ArgumentConsumer: Sync + GetClientSideArgParser { async fn consume<'a>( - &self, + &'a self, sender: &CommandSender<'a>, server: &'a Server, args: &mut RawArgs<'a>, - ) -> Option>; + ) -> Option; /// Used for tab completion (but only if argument suggestion type is "minecraft:ask_server"!). /// - /// NOTE: This is called after this consumer's [`ArgumentConsumer::consume`] method returnd None, so if args is used here, make sure [`ArgumentConsumer::consume`] never returns None after mutating args. + /// NOTE: This is called after this consumer's [`ArgumentConsumer::consume`] method returned None, so if args is used here, make sure [`ArgumentConsumer::consume`] never returns None after mutating args. async fn suggest<'a>( - &self, + &'a self, sender: &CommandSender<'a>, server: &'a Server, input: &'a str, - ) -> Result>>, CommandError>; + ) -> Result>, CommandError>; } pub(crate) trait GetClientSideArgParser { @@ -67,10 +67,7 @@ pub(crate) trait GetClientSideArgParser { } pub(crate) trait DefaultNameArgConsumer: ArgumentConsumer { - fn default_name(&self) -> &'static str; - - /// needed because trait upcasting is not stable - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer; + fn default_name(&self) -> String; } #[derive(Clone)] @@ -83,7 +80,7 @@ pub(crate) enum Arg<'a> { Pos2D(Vector2), Rotation(f32, f32), GameMode(GameMode), - CommandTree(&'a CommandTree<'a>), + CommandTree(CommandTree), Item(&'a str), ResourceLocation(&'a str), Block(&'a str), @@ -112,7 +109,7 @@ impl GetCloned for HashMap { pub(crate) trait FindArg<'a> { type Data; - fn find_arg(args: &'a ConsumedArgs, name: &'a str) -> Result; + fn find_arg(args: &'a ConsumedArgs, name: &str) -> Result; } pub(crate) trait FindArgDefaultName<'a, T> { @@ -121,7 +118,7 @@ pub(crate) trait FindArgDefaultName<'a, T> { impl<'a, T, C: FindArg<'a, Data = T> + DefaultNameArgConsumer> FindArgDefaultName<'a, T> for C { fn find_arg_default_name(&self, args: &'a ConsumedArgs) -> Result { - C::find_arg(args, self.default_name()) + C::find_arg(args, &self.default_name()) } } diff --git a/pumpkin/src/command/client_cmd_suggestions.rs b/pumpkin/src/command/client_cmd_suggestions.rs index a1bec5da8..707dfcf50 100644 --- a/pumpkin/src/command/client_cmd_suggestions.rs +++ b/pumpkin/src/command/client_cmd_suggestions.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use pumpkin_protocol::client::play::{CCommands, ProtoNode, ProtoNodeType}; +use tokio::sync::RwLock; use crate::entity::player::Player; @@ -9,13 +10,11 @@ use super::{ tree::{Node, NodeType}, }; -pub async fn send_c_commands_packet<'a>( - player: &Arc, - dispatcher: &'a CommandDispatcher<'a>, -) { +pub async fn send_c_commands_packet(player: &Arc, dispatcher: &RwLock) { let cmd_src = super::CommandSender::Player(player.clone()); let mut first_level = Vec::new(); + let dispatcher = dispatcher.read().await; for key in dispatcher.commands.keys() { let Ok(tree) = dispatcher.get_tree(key) else { continue; @@ -72,15 +71,15 @@ impl<'a> ProtoNodeBuilder<'a> { fn nodes_to_proto_node_builders<'a>( cmd_src: &super::CommandSender, - nodes: &'a [Node<'a>], - children: &'a [usize], + nodes: &'a [Node], + children: &[usize], ) -> (bool, Vec>) { let mut child_nodes = Vec::new(); let mut is_executable = false; for i in children { let node = &nodes[*i]; - match node.node_type { + match &node.node_type { NodeType::Argument { name, consumer } => { let (node_is_executable, node_children) = nodes_to_proto_node_builders(cmd_src, nodes, &node.children); diff --git a/pumpkin/src/command/commands/cmd_bossbar.rs b/pumpkin/src/command/commands/cmd_bossbar.rs index eb9d3c8a2..25e449a75 100644 --- a/pumpkin/src/command/commands/cmd_bossbar.rs +++ b/pumpkin/src/command/commands/cmd_bossbar.rs @@ -28,10 +28,12 @@ const ARG_NAME: &str = "name"; const ARG_VISIBLE: &str = "visible"; -const AUTOCOMPLETE_CONSUMER: ResourceLocationArgumentConsumer = - ResourceLocationArgumentConsumer::new(true); -const NON_AUTOCOMPLETE_CONSUMER: ResourceLocationArgumentConsumer = - ResourceLocationArgumentConsumer::new(false); +const fn autocomplete_consumer() -> ResourceLocationArgumentConsumer { + ResourceLocationArgumentConsumer::new(true) +} +const fn non_autocomplete_consumer() -> ResourceLocationArgumentConsumer { + ResourceLocationArgumentConsumer::new(false) +} enum CommandValueGet { Max, @@ -61,7 +63,7 @@ impl CommandExecutor for BossbarAddExecuter { server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let namespace = NON_AUTOCOMPLETE_CONSUMER.find_arg_default_name(args)?; + let namespace = non_autocomplete_consumer().find_arg_default_name(args)?; let Some(Arg::Simple(name)) = args.get(ARG_NAME) else { return Err(InvalidConsumption(Some(ARG_NAME.into()))); }; @@ -96,7 +98,7 @@ impl CommandExecutor for BossbarGetExecuter { server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let namespace = AUTOCOMPLETE_CONSUMER.find_arg_default_name(args)?; + let namespace = autocomplete_consumer().find_arg_default_name(args)?; let Some(bossbar) = server.bossbars.lock().await.get_bossbar(namespace) else { send_error_message( @@ -197,7 +199,7 @@ impl CommandExecutor for BossbarRemoveExecuter { server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let namespace = AUTOCOMPLETE_CONSUMER.find_arg_default_name(args)?; + let namespace = autocomplete_consumer().find_arg_default_name(args)?; if !server.bossbars.lock().await.has_bossbar(namespace) { send_error_message( @@ -237,7 +239,7 @@ impl CommandExecutor for BossbarSetExecuter { server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let namespace = AUTOCOMPLETE_CONSUMER.find_arg_default_name(args)?; + let namespace = autocomplete_consumer().find_arg_default_name(args)?; let Some(bossbar) = server.bossbars.lock().await.get_bossbar(namespace) else { handle_bossbar_error( @@ -275,10 +277,10 @@ impl CommandExecutor for BossbarSetExecuter { Ok(()) } CommandValueSet::Max => { - let Ok(max_value) = MAX_VALUE_CONSUMER.find_arg_default_name(args)? else { + let Ok(max_value) = max_value_consumer().find_arg_default_name(args)? else { send_error_message( sender, - format!("{} is out of bounds.", MAX_VALUE_CONSUMER.default_name()), + format!("{} is out of bounds.", max_value_consumer().default_name()), ) .await; return Ok(()); @@ -423,10 +425,10 @@ impl CommandExecutor for BossbarSetExecuter { Ok(()) } CommandValueSet::Value => { - let Ok(value) = VALUE_CONSUMER.find_arg_default_name(args)? else { + let Ok(value) = value_consumer().find_arg_default_name(args)? else { send_error_message( sender, - format!("{} is out of bounds.", VALUE_CONSUMER.default_name()), + format!("{} is out of bounds.", value_consumer().default_name()), ) .await; return Ok(()); @@ -463,7 +465,7 @@ impl CommandExecutor for BossbarSetExecuter { .bossbars .lock() .await - .update_visibilty(server, namespace.to_string(), visibility) + .update_visibility(server, namespace.to_string(), visibility) .await { Ok(()) => {} @@ -488,85 +490,86 @@ impl CommandExecutor for BossbarSetExecuter { } } -static MAX_VALUE_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0).name("max"); +fn max_value_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0).name("max") +} -static VALUE_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0).name("value"); +fn value_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0).name("value") +} -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) .with_child( literal("add").with_child( - argument_default_name(&NON_AUTOCOMPLETE_CONSUMER).with_child( - argument(ARG_NAME, &SimpleArgConsumer).execute(&BossbarAddExecuter), - ), + argument_default_name(non_autocomplete_consumer()) + .with_child(argument(ARG_NAME, SimpleArgConsumer).execute(BossbarAddExecuter)), ), ) .with_child( literal("get").with_child( - argument_default_name(&AUTOCOMPLETE_CONSUMER) - .with_child(literal("max").execute(&BossbarGetExecuter(CommandValueGet::Max))) + argument_default_name(autocomplete_consumer()) + .with_child(literal("max").execute(BossbarGetExecuter(CommandValueGet::Max))) .with_child( - literal("players").execute(&BossbarGetExecuter(CommandValueGet::Players)), + literal("players").execute(BossbarGetExecuter(CommandValueGet::Players)), ) .with_child( - literal("value").execute(&BossbarGetExecuter(CommandValueGet::Value)), + literal("value").execute(BossbarGetExecuter(CommandValueGet::Value)), ) .with_child( - literal("visible").execute(&BossbarGetExecuter(CommandValueGet::Visible)), + literal("visible").execute(BossbarGetExecuter(CommandValueGet::Visible)), ), ), ) - .with_child(literal("list").execute(&BossbarListExecuter)) + .with_child(literal("list").execute(BossbarListExecuter)) .with_child(literal("remove").with_child( - argument_default_name(&AUTOCOMPLETE_CONSUMER).execute(&BossbarRemoveExecuter), + argument_default_name(autocomplete_consumer()).execute(BossbarRemoveExecuter), )) .with_child( literal("set").with_child( - argument_default_name(&AUTOCOMPLETE_CONSUMER) + argument_default_name(autocomplete_consumer()) .with_child( literal("color").with_child( - argument_default_name(&BossbarColorArgumentConsumer) - .execute(&BossbarSetExecuter(CommandValueSet::Color)), + argument_default_name(BossbarColorArgumentConsumer) + .execute(BossbarSetExecuter(CommandValueSet::Color)), ), ) .with_child( literal("max").with_child( - argument_default_name(&MAX_VALUE_CONSUMER) - .execute(&BossbarSetExecuter(CommandValueSet::Max)), + argument_default_name(max_value_consumer()) + .execute(BossbarSetExecuter(CommandValueSet::Max)), ), ) .with_child( literal("name").with_child( - argument(ARG_NAME, &SimpleArgConsumer) - .execute(&BossbarSetExecuter(CommandValueSet::Name)), + argument(ARG_NAME, SimpleArgConsumer) + .execute(BossbarSetExecuter(CommandValueSet::Name)), ), ) .with_child( literal("players") .with_child( - argument_default_name(&PlayersArgumentConsumer) - .execute(&BossbarSetExecuter(CommandValueSet::Players(true))), + argument_default_name(PlayersArgumentConsumer) + .execute(BossbarSetExecuter(CommandValueSet::Players(true))), ) - .execute(&BossbarSetExecuter(CommandValueSet::Players(false))), + .execute(BossbarSetExecuter(CommandValueSet::Players(false))), ) .with_child( literal("style").with_child( - argument_default_name(&BossbarStyleArgumentConsumer) - .execute(&BossbarSetExecuter(CommandValueSet::Style)), + argument_default_name(BossbarStyleArgumentConsumer) + .execute(BossbarSetExecuter(CommandValueSet::Style)), ), ) .with_child( literal("value").with_child( - argument_default_name(&VALUE_CONSUMER) - .execute(&BossbarSetExecuter(CommandValueSet::Value)), + argument_default_name(value_consumer()) + .execute(BossbarSetExecuter(CommandValueSet::Value)), ), ) .with_child( literal("visible").with_child( - argument(ARG_VISIBLE, &BoolArgConsumer) - .execute(&BossbarSetExecuter(CommandValueSet::Visible)), + argument(ARG_VISIBLE, BoolArgConsumer) + .execute(BossbarSetExecuter(CommandValueSet::Visible)), ), ), ), diff --git a/pumpkin/src/command/commands/cmd_clear.rs b/pumpkin/src/command/commands/cmd_clear.rs index 073795878..b948d0df8 100644 --- a/pumpkin/src/command/commands/cmd_clear.rs +++ b/pumpkin/src/command/commands/cmd_clear.rs @@ -107,8 +107,8 @@ impl CommandExecutor for ClearSelfExecutor { } #[allow(clippy::redundant_closure_for_method_calls)] // causes lifetime issues -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child(argument(ARG_TARGET, &EntitiesArgumentConsumer).execute(&ClearExecutor)) - .with_child(require(&|sender| sender.is_player()).execute(&ClearSelfExecutor)) + .with_child(argument(ARG_TARGET, EntitiesArgumentConsumer).execute(ClearExecutor)) + .with_child(require(|sender| sender.is_player()).execute(ClearSelfExecutor)) } diff --git a/pumpkin/src/command/commands/cmd_fill.rs b/pumpkin/src/command/commands/cmd_fill.rs index 596ddbe0a..4f5e3d06a 100644 --- a/pumpkin/src/command/commands/cmd_fill.rs +++ b/pumpkin/src/command/commands/cmd_fill.rs @@ -1,5 +1,5 @@ use crate::command::args::arg_block::BlockArgumentConsumer; -use crate::command::args::arg_postition_block::BlockPosArgumentConsumer; +use crate::command::args::arg_position_block::BlockPosArgumentConsumer; use crate::command::args::{ConsumedArgs, FindArg}; use crate::command::tree::CommandTree; use crate::command::tree_builder::{argument, literal, require}; @@ -155,23 +155,21 @@ impl CommandExecutor for SetblockExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| { - sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some() - }) - .with_child( - argument(ARG_FROM, &BlockPosArgumentConsumer).with_child( - argument(ARG_TO, &BlockPosArgumentConsumer).with_child( - argument(ARG_BLOCK, &BlockArgumentConsumer) - .with_child(literal("destroy").execute(&SetblockExecutor(Mode::Destroy))) - .with_child(literal("hollow").execute(&SetblockExecutor(Mode::Hollow))) - .with_child(literal("keep").execute(&SetblockExecutor(Mode::Keep))) - .with_child(literal("outline").execute(&SetblockExecutor(Mode::Outline))) - .with_child(literal("replace").execute(&SetblockExecutor(Mode::Replace))) - .execute(&SetblockExecutor(Mode::Replace)), + require(|sender| sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some()) + .with_child( + argument(ARG_FROM, BlockPosArgumentConsumer).with_child( + argument(ARG_TO, BlockPosArgumentConsumer).with_child( + argument(ARG_BLOCK, BlockArgumentConsumer) + .with_child(literal("destroy").execute(SetblockExecutor(Mode::Destroy))) + .with_child(literal("hollow").execute(SetblockExecutor(Mode::Hollow))) + .with_child(literal("keep").execute(SetblockExecutor(Mode::Keep))) + .with_child(literal("outline").execute(SetblockExecutor(Mode::Outline))) + .with_child(literal("replace").execute(SetblockExecutor(Mode::Replace))) + .execute(SetblockExecutor(Mode::Replace)), + ), ), ), - ), ) } diff --git a/pumpkin/src/command/commands/cmd_gamemode.rs b/pumpkin/src/command/commands/cmd_gamemode.rs index 9112b170c..1b586130c 100644 --- a/pumpkin/src/command/commands/cmd_gamemode.rs +++ b/pumpkin/src/command/commands/cmd_gamemode.rs @@ -107,13 +107,13 @@ impl CommandExecutor for GamemodeTargetPlayer { } #[allow(clippy::redundant_closure_for_method_calls)] -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Two)).with_child( - argument(ARG_GAMEMODE, &GamemodeArgumentConsumer) - .with_child(require(&|sender| sender.is_player()).execute(&GamemodeTargetSelf)) + require(|sender| sender.has_permission_lvl(PermissionLvl::Two)).with_child( + argument(ARG_GAMEMODE, GamemodeArgumentConsumer) + .with_child(require(|sender| sender.is_player()).execute(GamemodeTargetSelf)) .with_child( - argument(ARG_TARGET, &PlayersArgumentConsumer).execute(&GamemodeTargetPlayer), + argument(ARG_TARGET, PlayersArgumentConsumer).execute(GamemodeTargetPlayer), ), ), ) diff --git a/pumpkin/src/command/commands/cmd_give.rs b/pumpkin/src/command/commands/cmd_give.rs index f306e114e..d3f91bafa 100644 --- a/pumpkin/src/command/commands/cmd_give.rs +++ b/pumpkin/src/command/commands/cmd_give.rs @@ -17,10 +17,12 @@ const DESCRIPTION: &str = "Give items to player(s)."; const ARG_ITEM: &str = "item"; -static ITEM_COUNT_CONSUMER: BoundedNumArgumentConsumer = BoundedNumArgumentConsumer::new() - .name("count") - .min(0) - .max(6400); +fn item_count_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new() + .name("count") + .min(0) + .max(6400) +} struct GiveExecutor; @@ -36,7 +38,7 @@ impl CommandExecutor for GiveExecutor { let (item_name, item) = ItemArgumentConsumer::find_arg(args, ARG_ITEM)?; - let item_count = match ITEM_COUNT_CONSUMER.find_arg_default_name(args) { + let item_count = match item_count_consumer().find_arg_default_name(args) { Err(_) => 1, Ok(Ok(count)) => count, Ok(Err(())) => { @@ -72,13 +74,13 @@ impl CommandExecutor for GiveExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Two)).with_child( - argument_default_name(&PlayersArgumentConsumer).with_child( - argument(ARG_ITEM, &ItemArgumentConsumer) - .execute(&GiveExecutor) - .with_child(argument_default_name(&ITEM_COUNT_CONSUMER).execute(&GiveExecutor)), + require(|sender| sender.has_permission_lvl(PermissionLvl::Two)).with_child( + argument_default_name(PlayersArgumentConsumer).with_child( + argument(ARG_ITEM, ItemArgumentConsumer) + .execute(GiveExecutor) + .with_child(argument_default_name(item_count_consumer()).execute(GiveExecutor)), ), ), ) diff --git a/pumpkin/src/command/commands/cmd_help.rs b/pumpkin/src/command/commands/cmd_help.rs index 8c714dcbc..c0ab3eb79 100644 --- a/pumpkin/src/command/commands/cmd_help.rs +++ b/pumpkin/src/command/commands/cmd_help.rs @@ -22,8 +22,9 @@ const ARG_COMMAND: &str = "command"; const COMMANDS_PER_PAGE: i32 = 7; -static PAGE_NUMBER_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().name("page").min(1); +fn page_number_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().name("page").min(1) +} struct CommandHelpExecutor; @@ -41,7 +42,7 @@ impl CommandExecutor for CommandHelpExecutor { let command_names = tree.names.join(", /"); let usage = format!("{tree}"); - let description = tree.description; + let description = &tree.description; let header_text = format!(" Help - /{} ", tree.names[0]); @@ -107,7 +108,7 @@ impl CommandExecutor for BaseHelpExecutor { server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let page_number = match PAGE_NUMBER_CONSUMER.find_arg_default_name(args) { + let page_number = match page_number_consumer().find_arg_default_name(args) { Err(_) => 1, Ok(Ok(number)) => number, Ok(Err(())) => { @@ -121,8 +122,8 @@ impl CommandExecutor for BaseHelpExecutor { } }; - let mut commands: Vec<&CommandTree> = server - .command_dispatcher + let dispatcher = server.command_dispatcher.read().await; + let mut commands: Vec<&CommandTree> = dispatcher .commands .values() .filter_map(|cmd| match cmd { @@ -131,7 +132,7 @@ impl CommandExecutor for BaseHelpExecutor { }) .collect(); - commands.sort_by(|a, b| a.names[0].cmp(b.names[0])); + commands.sort_by(|a, b| a.names[0].cmp(&b.names[0])); let total_pages = (commands.len().to_i32().unwrap() + COMMANDS_PER_PAGE - 1) / COMMANDS_PER_PAGE; @@ -183,7 +184,7 @@ impl CommandExecutor for BaseHelpExecutor { .color_named(NamedColor::Gold) .add_child(TextComponent::text(" - ").color_named(NamedColor::Yellow)) .add_child( - TextComponent::text_string(tree.description.to_owned() + "\n") + TextComponent::text_string(tree.description.clone() + "\n") .color_named(NamedColor::White), ) .add_child(TextComponent::text(" Usage: ").color_named(NamedColor::Yellow)) @@ -220,11 +221,9 @@ impl CommandExecutor for BaseHelpExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child( - argument(ARG_COMMAND, &CommandTreeArgumentConsumer).execute(&CommandHelpExecutor), - ) - .with_child(argument_default_name(&PAGE_NUMBER_CONSUMER).execute(&BaseHelpExecutor)) - .execute(&BaseHelpExecutor) + .with_child(argument(ARG_COMMAND, CommandTreeArgumentConsumer).execute(CommandHelpExecutor)) + .with_child(argument_default_name(page_number_consumer()).execute(BaseHelpExecutor)) + .execute(BaseHelpExecutor) } diff --git a/pumpkin/src/command/commands/cmd_kill.rs b/pumpkin/src/command/commands/cmd_kill.rs index d763054b5..2b36b0c48 100644 --- a/pumpkin/src/command/commands/cmd_kill.rs +++ b/pumpkin/src/command/commands/cmd_kill.rs @@ -65,8 +65,8 @@ impl CommandExecutor for KillSelfExecutor { } #[allow(clippy::redundant_closure_for_method_calls)] // causes lifetime issues -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child(argument(ARG_TARGET, &EntitiesArgumentConsumer).execute(&KillExecutor)) - .with_child(require(&|sender| sender.is_player()).execute(&KillSelfExecutor)) + .with_child(argument(ARG_TARGET, EntitiesArgumentConsumer).execute(KillExecutor)) + .with_child(require(|sender| sender.is_player()).execute(KillSelfExecutor)) } diff --git a/pumpkin/src/command/commands/cmd_list.rs b/pumpkin/src/command/commands/cmd_list.rs index fe1b4e0c7..bbcc36418 100644 --- a/pumpkin/src/command/commands/cmd_list.rs +++ b/pumpkin/src/command/commands/cmd_list.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use async_trait::async_trait; -use itertools::Itertools; use pumpkin_config::BASIC_CONFIG; use pumpkin_core::text::TextComponent; @@ -35,10 +34,7 @@ impl CommandExecutor for ListExecutor { "There are {} of a max of {} players online: {}", players.len(), BASIC_CONFIG.max_players, - players - .iter() - .map(|player| &player.gameprofile.name) - .join(", ") + get_player_names(players) ) }; @@ -48,6 +44,17 @@ impl CommandExecutor for ListExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { - CommandTree::new(NAMES, DESCRIPTION).execute(&ListExecutor) +fn get_player_names(players: Vec>) -> String { + let mut names = String::new(); + for player in players { + if !names.is_empty() { + names.push_str(", "); + } + names.push_str(&player.gameprofile.name); + } + names +} + +pub fn init_command_tree() -> CommandTree { + CommandTree::new(NAMES, DESCRIPTION).execute(ListExecutor) } diff --git a/pumpkin/src/command/commands/cmd_pumpkin.rs b/pumpkin/src/command/commands/cmd_pumpkin.rs index 2b2f41a21..a76a1be99 100644 --- a/pumpkin/src/command/commands/cmd_pumpkin.rs +++ b/pumpkin/src/command/commands/cmd_pumpkin.rs @@ -59,6 +59,32 @@ impl CommandExecutor for PumpkinExecutor { "Click to Copy Minecraft Version", ))) .color_named(NamedColor::Gold), + ) + .add_child(TextComponent::text(" ")) + // https://snowiiii.github.io/Pumpkin/ + .add_child( + TextComponent::text("Github Repository") + .click_event(ClickEvent::OpenUrl(Cow::from( + "https://github.com/Snowiiii/Pumpkin", + ))) + .hover_event(HoverEvent::ShowText(Cow::from( + "Click to open repository.", + ))) + .color_named(NamedColor::Blue) + .bold() + .underlined(), + ) + // Added docs. and a space for spacing + .add_child(TextComponent::text(" ")) + .add_child( + TextComponent::text("Docs") + .click_event(ClickEvent::OpenUrl(Cow::from( + "https://snowiiii.github.io/Pumpkin/", + ))) + .hover_event(HoverEvent::ShowText(Cow::from("Click to open docs."))) + .color_named(NamedColor::Blue) + .bold() + .underlined(), ), ) .await; @@ -66,6 +92,6 @@ impl CommandExecutor for PumpkinExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { - CommandTree::new(NAMES, DESCRIPTION).execute(&PumpkinExecutor) +pub fn init_command_tree() -> CommandTree { + CommandTree::new(NAMES, DESCRIPTION).execute(PumpkinExecutor) } diff --git a/pumpkin/src/command/commands/cmd_say.rs b/pumpkin/src/command/commands/cmd_say.rs index 7ced92b6d..ffeb9c884 100644 --- a/pumpkin/src/command/commands/cmd_say.rs +++ b/pumpkin/src/command/commands/cmd_say.rs @@ -41,9 +41,9 @@ impl CommandExecutor for SayExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Two)) - .with_child(argument(ARG_MESSAGE, &MsgArgConsumer).execute(&SayExecutor)), + require(|sender| sender.has_permission_lvl(PermissionLvl::Two)) + .with_child(argument(ARG_MESSAGE, MsgArgConsumer).execute(SayExecutor)), ) } diff --git a/pumpkin/src/command/commands/cmd_seed.rs b/pumpkin/src/command/commands/cmd_seed.rs index acb3cb46e..50fe6ecd8 100644 --- a/pumpkin/src/command/commands/cmd_seed.rs +++ b/pumpkin/src/command/commands/cmd_seed.rs @@ -24,11 +24,9 @@ impl CommandExecutor for PumpkinExecutor { _args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { let seed = match sender { - CommandSender::Player(player) => { - player.living_entity.entity.world.level.seed.0.to_string() - } + CommandSender::Player(player) => player.living_entity.entity.world.level.seed.0, _ => match server.worlds.first() { - Some(world) => world.level.seed.0.to_string(), + Some(world) => world.level.seed.0, None => { return Err(CommandError::GeneralCommandIssue( "Unable to get Seed".to_string(), @@ -36,6 +34,7 @@ impl CommandExecutor for PumpkinExecutor { } }, }; + let seed = (seed as i64).to_string(); sender .send_message( @@ -55,10 +54,10 @@ impl CommandExecutor for PumpkinExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child(require(&|sender| { + .with_child(require(|sender| { sender.has_permission_lvl(PermissionLvl::Two) })) - .execute(&PumpkinExecutor) + .execute(PumpkinExecutor) } diff --git a/pumpkin/src/command/commands/cmd_setblock.rs b/pumpkin/src/command/commands/cmd_setblock.rs index b17c961c9..82e813924 100644 --- a/pumpkin/src/command/commands/cmd_setblock.rs +++ b/pumpkin/src/command/commands/cmd_setblock.rs @@ -3,7 +3,7 @@ use pumpkin_core::text::color::NamedColor; use pumpkin_core::text::TextComponent; use crate::command::args::arg_block::BlockArgumentConsumer; -use crate::command::args::arg_postition_block::BlockPosArgumentConsumer; +use crate::command::args::arg_position_block::BlockPosArgumentConsumer; use crate::command::args::{ConsumedArgs, FindArg}; use crate::command::tree::CommandTree; use crate::command::tree_builder::{argument, literal, require}; @@ -79,19 +79,17 @@ impl CommandExecutor for SetblockExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| { - sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some() - }) - .with_child( - argument(ARG_BLOCK_POS, &BlockPosArgumentConsumer).with_child( - argument(ARG_BLOCK, &BlockArgumentConsumer) - .with_child(literal("replace").execute(&SetblockExecutor(Mode::Replace))) - .with_child(literal("destroy").execute(&SetblockExecutor(Mode::Destroy))) - .with_child(literal("keep").execute(&SetblockExecutor(Mode::Keep))) - .execute(&SetblockExecutor(Mode::Replace)), + require(|sender| sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some()) + .with_child( + argument(ARG_BLOCK_POS, BlockPosArgumentConsumer).with_child( + argument(ARG_BLOCK, BlockArgumentConsumer) + .with_child(literal("replace").execute(SetblockExecutor(Mode::Replace))) + .with_child(literal("destroy").execute(SetblockExecutor(Mode::Destroy))) + .with_child(literal("keep").execute(SetblockExecutor(Mode::Keep))) + .execute(SetblockExecutor(Mode::Replace)), + ), ), - ), ) } diff --git a/pumpkin/src/command/commands/cmd_stop.rs b/pumpkin/src/command/commands/cmd_stop.rs index a15b6184b..6368988c1 100644 --- a/pumpkin/src/command/commands/cmd_stop.rs +++ b/pumpkin/src/command/commands/cmd_stop.rs @@ -32,13 +32,13 @@ impl CommandExecutor for StopExecutor { for player in server.get_all_players().await { player.kick(kick_message.clone()).await; } - + server.save().await; std::process::exit(0) } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Four)).execute(&StopExecutor), + require(|sender| sender.has_permission_lvl(PermissionLvl::Four)).execute(StopExecutor), ) } diff --git a/pumpkin/src/command/commands/cmd_teleport.rs b/pumpkin/src/command/commands/cmd_teleport.rs index 5865fbfe9..59ccc9606 100644 --- a/pumpkin/src/command/commands/cmd_teleport.rs +++ b/pumpkin/src/command/commands/cmd_teleport.rs @@ -237,41 +237,41 @@ impl CommandExecutor for TpSelfToPosExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Two)) + require(|sender| sender.has_permission_lvl(PermissionLvl::Two)) .with_child( - argument(ARG_LOCATION, &Position3DArgumentConsumer).execute(&TpSelfToPosExecutor), + argument(ARG_LOCATION, Position3DArgumentConsumer).execute(TpSelfToPosExecutor), ) .with_child( - argument(ARG_DESTINATION, &EntityArgumentConsumer).execute(&TpSelfToEntityExecutor), + argument(ARG_DESTINATION, EntityArgumentConsumer).execute(TpSelfToEntityExecutor), ) .with_child( - argument(ARG_TARGETS, &EntitiesArgumentConsumer) + argument(ARG_TARGETS, EntitiesArgumentConsumer) .with_child( - argument(ARG_LOCATION, &Position3DArgumentConsumer) - .execute(&TpEntitiesToPosExecutor) + argument(ARG_LOCATION, Position3DArgumentConsumer) + .execute(TpEntitiesToPosExecutor) .with_child( - argument(ARG_ROTATION, &RotationArgumentConsumer) - .execute(&TpEntitiesToPosWithRotationExecutor), + argument(ARG_ROTATION, RotationArgumentConsumer) + .execute(TpEntitiesToPosWithRotationExecutor), ) .with_child( literal("facing") .with_child( literal("entity").with_child( - argument(ARG_FACING_ENTITY, &EntityArgumentConsumer) - .execute(&TpEntitiesToPosFacingEntityExecutor), + argument(ARG_FACING_ENTITY, EntityArgumentConsumer) + .execute(TpEntitiesToPosFacingEntityExecutor), ), ) .with_child( - argument(ARG_FACING_LOCATION, &Position3DArgumentConsumer) - .execute(&TpEntitiesToPosFacingPosExecutor), + argument(ARG_FACING_LOCATION, Position3DArgumentConsumer) + .execute(TpEntitiesToPosFacingPosExecutor), ), ), ) .with_child( - argument(ARG_DESTINATION, &EntityArgumentConsumer) - .execute(&TpEntitiesToEntityExecutor), + argument(ARG_DESTINATION, EntityArgumentConsumer) + .execute(TpEntitiesToEntityExecutor), ), ), ) diff --git a/pumpkin/src/command/commands/cmd_time.rs b/pumpkin/src/command/commands/cmd_time.rs index eefc582e5..9cc45ec67 100644 --- a/pumpkin/src/command/commands/cmd_time.rs +++ b/pumpkin/src/command/commands/cmd_time.rs @@ -16,10 +16,12 @@ const NAMES: [&str; 1] = ["time"]; const DESCRIPTION: &str = "Query the world time."; // TODO: This should be either higher or not bounded -static ARG_NUMBER: BoundedNumArgumentConsumer = BoundedNumArgumentConsumer::new() - .name("time") - .min(0) - .max(24000); +fn arg_number() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new() + .name("time") + .min(0) + .max(24000) +} #[derive(Clone, Copy)] enum Mode { @@ -81,7 +83,7 @@ impl CommandExecutor for TimeChangeExecutor { server: &crate::server::Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let time_count = match ARG_NUMBER.find_arg_default_name(args) { + let time_count = match arg_number().find_arg_default_name(args) { Err(_) => 1, Ok(Ok(count)) => count, Ok(Err(())) => { @@ -122,22 +124,20 @@ impl CommandExecutor for TimeChangeExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Two)) + require(|sender| sender.has_permission_lvl(PermissionLvl::Two)) .with_child(literal("add").with_child( - argument_default_name(&ARG_NUMBER).execute(&TimeChangeExecutor(Mode::Add)), + argument_default_name(arg_number()).execute(TimeChangeExecutor(Mode::Add)), )) .with_child( literal("query") - .with_child(literal("daytime").execute(&TimeQueryExecutor(QueryMode::DayTime))) - .with_child( - literal("gametime").execute(&TimeQueryExecutor(QueryMode::GameTime)), - ) - .with_child(literal("day").execute(&TimeQueryExecutor(QueryMode::Day))), + .with_child(literal("daytime").execute(TimeQueryExecutor(QueryMode::DayTime))) + .with_child(literal("gametime").execute(TimeQueryExecutor(QueryMode::GameTime))) + .with_child(literal("day").execute(TimeQueryExecutor(QueryMode::Day))), ) .with_child(literal("set").with_child( - argument_default_name(&ARG_NUMBER).execute(&TimeChangeExecutor(Mode::Set)), + argument_default_name(arg_number()).execute(TimeChangeExecutor(Mode::Set)), )), ) } diff --git a/pumpkin/src/command/commands/cmd_transfer.rs b/pumpkin/src/command/commands/cmd_transfer.rs index 3b11520f5..8aa412511 100644 --- a/pumpkin/src/command/commands/cmd_transfer.rs +++ b/pumpkin/src/command/commands/cmd_transfer.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use pumpkin_core::text::color::{Color, NamedColor}; use pumpkin_core::text::TextComponent; use pumpkin_protocol::client::play::CTransfer; -use pumpkin_protocol::VarInt; +use pumpkin_protocol::codec::var_int::VarInt; use crate::command::args::arg_bounded_num::BoundedNumArgumentConsumer; use crate::command::args::arg_players::PlayersArgumentConsumer; @@ -23,10 +23,12 @@ const ARG_HOSTNAME: &str = "hostname"; const ARG_PLAYERS: &str = "players"; -static PORT_CONSUMER: BoundedNumArgumentConsumer = BoundedNumArgumentConsumer::new() - .name("port") - .min(1) - .max(65535); +fn port_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new() + .name("port") + .min(1) + .max(65535) +} struct TransferTargetSelf; @@ -42,7 +44,7 @@ impl CommandExecutor for TransferTargetSelf { return Err(InvalidConsumption(Some(ARG_HOSTNAME.into()))); }; - let port = match PORT_CONSUMER.find_arg_default_name(args) { + let port = match port_consumer().find_arg_default_name(args) { Err(_) => 25565, Ok(Ok(count)) => count, Ok(Err(())) => { @@ -84,7 +86,7 @@ impl CommandExecutor for TransferTargetPlayer { return Err(InvalidConsumption(Some(ARG_HOSTNAME.into()))); }; - let port = match PORT_CONSUMER.find_arg_default_name(args) { + let port = match port_consumer().find_arg_default_name(args) { Err(_) => 25565, Ok(Ok(count)) => count, Ok(Err(())) => { @@ -117,19 +119,19 @@ impl CommandExecutor for TransferTargetPlayer { } #[allow(clippy::redundant_closure_for_method_calls)] -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Three)).with_child( - argument(ARG_HOSTNAME, &SimpleArgConsumer) - .with_child(require(&|sender| sender.is_player()).execute(&TransferTargetSelf)) + require(|sender| sender.has_permission_lvl(PermissionLvl::Three)).with_child( + argument(ARG_HOSTNAME, SimpleArgConsumer) + .with_child(require(|sender| sender.is_player()).execute(TransferTargetSelf)) .with_child( - argument_default_name(&PORT_CONSUMER) + argument_default_name(port_consumer()) .with_child( - require(&|sender| sender.is_player()).execute(&TransferTargetSelf), + require(|sender| sender.is_player()).execute(TransferTargetSelf), ) .with_child( - argument(ARG_PLAYERS, &PlayersArgumentConsumer) - .execute(&TransferTargetPlayer), + argument(ARG_PLAYERS, PlayersArgumentConsumer) + .execute(TransferTargetPlayer), ), ), ), diff --git a/pumpkin/src/command/commands/cmd_worldborder.rs b/pumpkin/src/command/commands/cmd_worldborder.rs index fd9ae1a24..b52ac6b54 100644 --- a/pumpkin/src/command/commands/cmd_worldborder.rs +++ b/pumpkin/src/command/commands/cmd_worldborder.rs @@ -25,6 +25,28 @@ const NAMES: [&str; 1] = ["worldborder"]; const DESCRIPTION: &str = "Worldborder command."; +fn distance_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0.0).name("distance") +} + +fn time_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0).name("time") +} + +fn damage_per_block_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new() + .min(0.0) + .name("damage_per_block") +} + +fn damage_buffer_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0.0).name("buffer") +} + +fn warning_distance_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0).name("distance") +} + struct WorldborderGetExecutor; #[async_trait] @@ -67,12 +89,12 @@ impl CommandExecutor for WorldborderSetExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else { + let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DISTANCE_CONSUMER.default_name() + distance_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -116,24 +138,24 @@ impl CommandExecutor for WorldborderSetTimeExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else { + let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DISTANCE_CONSUMER.default_name() + distance_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) .await; return Ok(()); }; - let Ok(time) = TIME_CONSUMER.find_arg_default_name(args)? else { + let Ok(time) = time_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - TIME_CONSUMER.default_name() + time_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -188,12 +210,12 @@ impl CommandExecutor for WorldborderAddExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else { + let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DISTANCE_CONSUMER.default_name() + distance_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -239,24 +261,24 @@ impl CommandExecutor for WorldborderAddTimeExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else { + let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DISTANCE_CONSUMER.default_name() + distance_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) .await; return Ok(()); }; - let Ok(time) = TIME_CONSUMER.find_arg_default_name(args)? else { + let Ok(time) = time_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - TIME_CONSUMER.default_name() + time_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -341,12 +363,12 @@ impl CommandExecutor for WorldborderDamageAmountExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(damage_per_block) = DAMAGE_PER_BLOCK_CONSUMER.find_arg_default_name(args)? else { + let Ok(damage_per_block) = damage_per_block_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DAMAGE_PER_BLOCK_CONSUMER.default_name() + damage_per_block_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -392,12 +414,12 @@ impl CommandExecutor for WorldborderDamageBufferExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(buffer) = DAMAGE_BUFFER_CONSUMER.find_arg_default_name(args)? else { + let Ok(buffer) = damage_buffer_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DAMAGE_BUFFER_CONSUMER.default_name() + damage_buffer_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -443,12 +465,12 @@ impl CommandExecutor for WorldborderWarningDistanceExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(distance) = WARNING_DISTANCE_CONSUMER.find_arg_default_name(args)? else { + let Ok(distance) = warning_distance_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - WARNING_DISTANCE_CONSUMER.default_name() + warning_distance_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -494,12 +516,12 @@ impl CommandExecutor for WorldborderWarningTimeExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(time) = TIME_CONSUMER.find_arg_default_name(args)? else { + let Ok(time) = time_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - TIME_CONSUMER.default_name() + time_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -529,59 +551,42 @@ impl CommandExecutor for WorldborderWarningTimeExecutor { } } -static DISTANCE_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0.0).name("distance"); - -static TIME_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0).name("time"); - -static DAMAGE_PER_BLOCK_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new() - .min(0.0) - .name("damage_per_block"); - -static DAMAGE_BUFFER_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0.0).name("buffer"); - -static WARNING_DISTANCE_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0).name("distance"); - -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) .with_child( literal("add").with_child( - argument_default_name(&DISTANCE_CONSUMER) - .execute(&WorldborderAddExecutor) + argument_default_name(distance_consumer()) + .execute(WorldborderAddExecutor) .with_child( - argument_default_name(&TIME_CONSUMER).execute(&WorldborderAddTimeExecutor), + argument_default_name(time_consumer()).execute(WorldborderAddTimeExecutor), ), ), ) .with_child(literal("center").with_child( - argument_default_name(&Position2DArgumentConsumer).execute(&WorldborderCenterExecutor), + argument_default_name(Position2DArgumentConsumer).execute(WorldborderCenterExecutor), )) .with_child( literal("damage") .with_child( literal("amount").with_child( - argument_default_name(&DAMAGE_PER_BLOCK_CONSUMER) - .execute(&WorldborderDamageAmountExecutor), + argument_default_name(damage_per_block_consumer()) + .execute(WorldborderDamageAmountExecutor), ), ) .with_child( literal("buffer").with_child( - argument_default_name(&DAMAGE_BUFFER_CONSUMER) - .execute(&WorldborderDamageBufferExecutor), + argument_default_name(damage_buffer_consumer()) + .execute(WorldborderDamageBufferExecutor), ), ), ) - .with_child(literal("get").execute(&WorldborderGetExecutor)) + .with_child(literal("get").execute(WorldborderGetExecutor)) .with_child( literal("set").with_child( - argument_default_name(&DISTANCE_CONSUMER) - .execute(&WorldborderSetExecutor) + argument_default_name(distance_consumer()) + .execute(WorldborderSetExecutor) .with_child( - argument_default_name(&TIME_CONSUMER).execute(&WorldborderSetTimeExecutor), + argument_default_name(time_consumer()).execute(WorldborderSetTimeExecutor), ), ), ) @@ -589,12 +594,12 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { literal("warning") .with_child( literal("distance").with_child( - argument_default_name(&WARNING_DISTANCE_CONSUMER) - .execute(&WorldborderWarningDistanceExecutor), + argument_default_name(warning_distance_consumer()) + .execute(WorldborderWarningDistanceExecutor), ), ) .with_child(literal("time").with_child( - argument_default_name(&TIME_CONSUMER).execute(&WorldborderWarningTimeExecutor), + argument_default_name(time_consumer()).execute(WorldborderWarningTimeExecutor), )), ) } diff --git a/pumpkin/src/command/dispatcher.rs b/pumpkin/src/command/dispatcher.rs index 6b25bb25b..4d1bb943c 100644 --- a/pumpkin/src/command/dispatcher.rs +++ b/pumpkin/src/command/dispatcher.rs @@ -32,11 +32,11 @@ impl CommandError { pub fn into_string_or_pumpkin_error(self, cmd: &str) -> Result> { match self { InvalidConsumption(s) => { - println!("Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed"); + log::error!("Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed"); Ok("Internal Error (See logs for details)".into()) } InvalidRequirement => { - println!("Error while parsing command \"{cmd}\": a requirement that was expected was not met."); + log::error!("Error while parsing command \"{cmd}\": a requirement that was expected was not met."); Ok("Internal Error (See logs for details)".into()) } GeneralCommandIssue(s) => Ok(s), @@ -46,13 +46,13 @@ impl CommandError { } #[derive(Default)] -pub struct CommandDispatcher<'a> { - pub(crate) commands: HashMap<&'a str, Command<'a>>, +pub struct CommandDispatcher { + pub(crate) commands: HashMap, } /// Stores registered [`CommandTree`]s and dispatches commands to them. -impl<'a> CommandDispatcher<'a> { - pub async fn handle_command( +impl CommandDispatcher { + pub async fn handle_command<'a>( &'a self, sender: &mut CommandSender<'a>, server: &'a Server, @@ -70,7 +70,7 @@ impl<'a> CommandDispatcher<'a> { } Err(pumpkin_error) => { pumpkin_error.log(); - sender.send_message(TextComponent::text("Unknown internal error occured while running command. Please see server log").color(Color::Named(NamedColor::Red))).await; + sender.send_message(TextComponent::text("Unknown internal error occurred while running command. Please see server log").color(Color::Named(NamedColor::Red))).await; } } } @@ -81,7 +81,7 @@ impl<'a> CommandDispatcher<'a> { /// # todo /// - make this less ugly /// - do not query suggestions for the same consumer multiple times just because they are on different paths through the tree - pub(crate) async fn find_suggestions( + pub(crate) async fn find_suggestions<'a>( &'a self, src: &mut CommandSender<'a>, server: &'a Server, @@ -129,12 +129,12 @@ impl<'a> CommandDispatcher<'a> { } let mut suggestions = Vec::from_iter(suggestions); - suggestions.sort_by(|a, b| a.suggestion.cmp(b.suggestion)); + suggestions.sort_by(|a, b| a.suggestion.cmp(&b.suggestion)); suggestions } /// Execute a command using its corresponding [`CommandTree`]. - pub(crate) async fn dispatch( + pub(crate) async fn dispatch<'a>( &'a self, src: &mut CommandSender<'a>, server: &'a Server, @@ -145,13 +145,13 @@ impl<'a> CommandDispatcher<'a> { let key = parts .next() .ok_or(GeneralCommandIssue("Empty Command".to_string()))?; - let mut raw_args: Vec<&str> = parts.rev().collect(); + let raw_args: Vec<&str> = parts.rev().collect(); let tree = self.get_tree(key)?; // try paths until fitting path is found for path in tree.iter_paths() { - if Self::try_is_fitting_path(src, server, &path, tree, &mut raw_args).await? { + if Self::try_is_fitting_path(src, server, &path, tree, &mut raw_args.clone()).await? { return Ok(()); } } @@ -160,7 +160,7 @@ impl<'a> CommandDispatcher<'a> { ))) } - pub(crate) fn get_tree(&'a self, key: &str) -> Result<&'a CommandTree<'a>, CommandError> { + pub(crate) fn get_tree(&self, key: &str) -> Result<&CommandTree, CommandError> { let command = self .commands .get(key) @@ -169,7 +169,7 @@ impl<'a> CommandDispatcher<'a> { match command { Command::Tree(tree) => Ok(tree), Command::Alias(target) => { - let Some(Command::Tree(tree)) = &self.commands.get(target) else { + let Some(Command::Tree(tree)) = self.commands.get(target) else { log::error!("Error while parsing command alias \"{key}\": pointing to \"{target}\" which is not a valid tree"); return Err(GeneralCommandIssue( "Internal Error (See logs for details)".into(), @@ -180,17 +180,17 @@ impl<'a> CommandDispatcher<'a> { } } - async fn try_is_fitting_path( + async fn try_is_fitting_path<'a>( src: &mut CommandSender<'a>, server: &'a Server, path: &[usize], - tree: &CommandTree<'a>, + tree: &'a CommandTree, raw_args: &mut RawArgs<'a>, ) -> Result { let mut parsed_args: ConsumedArgs = HashMap::new(); for node in path.iter().map(|&i| &tree.nodes[i]) { - match node.node_type { + match &node.node_type { NodeType::ExecuteLeaf { executor } => { return if raw_args.is_empty() { executor.execute(src, server, &parsed_args).await?; @@ -223,18 +223,18 @@ impl<'a> CommandDispatcher<'a> { Ok(false) } - async fn try_find_suggestions_on_path( + async fn try_find_suggestions_on_path<'a>( src: &mut CommandSender<'a>, server: &'a Server, path: &[usize], - tree: &CommandTree<'a>, + tree: &'a CommandTree, raw_args: &mut RawArgs<'a>, input: &'a str, ) -> Result>>, CommandError> { let mut parsed_args: ConsumedArgs = HashMap::new(); for node in path.iter().map(|&i| &tree.nodes[i]) { - match node.node_type { + match &node.node_type { NodeType::ExecuteLeaf { .. } => { return Ok(None); } @@ -270,15 +270,29 @@ impl<'a> CommandDispatcher<'a> { } /// Register a command with the dispatcher. - pub(crate) fn register(&mut self, tree: CommandTree<'a>) { + pub(crate) fn register(&mut self, tree: CommandTree) { let mut names = tree.names.iter(); let primary_name = names.next().expect("at least one name must be provided"); - for &name in names { - self.commands.insert(name, Command::Alias(primary_name)); + for name in names { + self.commands + .insert(name.to_string(), Command::Alias(primary_name.to_string())); } - self.commands.insert(primary_name, Command::Tree(tree)); + self.commands + .insert(primary_name.to_string(), Command::Tree(tree)); + } +} + +#[cfg(test)] +mod test { + use crate::command::{default_dispatcher, tree::CommandTree}; + + #[test] + fn test_dynamic_command() { + let mut dispatcher = default_dispatcher(); + let tree = CommandTree::new(["test"], "test_desc"); + dispatcher.register(tree); } } diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index e47e1ed07..2633d4fb7 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -109,7 +109,7 @@ impl<'a> CommandSender<'a> { } #[must_use] -pub fn default_dispatcher<'a>() -> Arc> { +pub fn default_dispatcher() -> CommandDispatcher { let mut dispatcher = CommandDispatcher::default(); dispatcher.register(cmd_pumpkin::init_command_tree()); @@ -132,7 +132,7 @@ pub fn default_dispatcher<'a>() -> Arc> { dispatcher.register(cmd_fill::init_command_tree()); dispatcher.register(cmd_op::init_command_tree()); - Arc::new(dispatcher) + dispatcher } #[async_trait] diff --git a/pumpkin/src/command/tree.rs b/pumpkin/src/command/tree.rs index 06d9c0e58..8f54f949c 100644 --- a/pumpkin/src/command/tree.rs +++ b/pumpkin/src/command/tree.rs @@ -1,33 +1,34 @@ use super::{args::ArgumentConsumer, CommandExecutor}; use crate::command::CommandSender; -use std::{collections::VecDeque, fmt::Debug}; +use std::{collections::VecDeque, fmt::Debug, sync::Arc}; /// see [`crate::commands::tree_builder::argument`] pub type RawArgs<'a> = Vec<&'a str>; -#[derive(Debug)] -pub struct Node<'a> { +#[derive(Debug, Clone)] +pub struct Node { pub(crate) children: Vec, - pub(crate) node_type: NodeType<'a>, + pub(crate) node_type: NodeType, } -pub enum NodeType<'a> { +#[derive(Clone)] +pub enum NodeType { ExecuteLeaf { - executor: &'a dyn CommandExecutor, + executor: Arc, }, Literal { - string: &'a str, + string: String, }, Argument { - name: &'a str, - consumer: &'a dyn ArgumentConsumer, + name: String, + consumer: Arc, }, Require { - predicate: &'a (dyn Fn(&CommandSender) -> bool + Sync), + predicate: Arc bool + Send + Sync>, }, } -impl Debug for NodeType<'_> { +impl Debug for NodeType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::ExecuteLeaf { .. } => f @@ -45,28 +46,28 @@ impl Debug for NodeType<'_> { } } -pub enum Command<'a> { - Tree(CommandTree<'a>), - Alias(&'a str), +pub enum Command { + Tree(CommandTree), + Alias(String), } -#[derive(Debug)] -pub struct CommandTree<'a> { - pub(crate) nodes: Vec>, +#[derive(Debug, Clone)] +pub struct CommandTree { + pub(crate) nodes: Vec, pub(crate) children: Vec, - pub(crate) names: Vec<&'a str>, - pub(crate) description: &'a str, + pub(crate) names: Vec, + pub(crate) description: String, } -impl<'a> CommandTree<'a> { +impl CommandTree { /// iterate over all possible paths that end in a [`NodeType::ExecuteLeaf`] - pub(crate) fn iter_paths(&'a self) -> impl Iterator> + 'a { + pub(crate) fn iter_paths(&self) -> impl Iterator> + use<'_> { let mut todo = VecDeque::<(usize, usize)>::new(); // add root's children todo.extend(self.children.iter().map(|&i| (0, i))); - TraverseAllPathsIter::<'a> { + TraverseAllPathsIter { tree: self, path: Vec::::new(), todo, @@ -75,7 +76,7 @@ impl<'a> CommandTree<'a> { } struct TraverseAllPathsIter<'a> { - tree: &'a CommandTree<'a>, + tree: &'a CommandTree, path: Vec, /// (depth, i) todo: VecDeque<(usize, usize)>, diff --git a/pumpkin/src/command/tree_builder.rs b/pumpkin/src/command/tree_builder.rs index e750ea0bb..92f65d9e6 100644 --- a/pumpkin/src/command/tree_builder.rs +++ b/pumpkin/src/command/tree_builder.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use crate::command::args::ArgumentConsumer; use crate::command::tree::{CommandTree, Node, NodeType}; use crate::command::CommandSender; @@ -5,9 +7,9 @@ use crate::command::CommandSender; use super::args::DefaultNameArgConsumer; use super::CommandExecutor; -impl<'a> CommandTree<'a> { +impl CommandTree { /// Add a child [Node] to the root of this [`CommandTree`]. - pub fn with_child(mut self, child: impl NodeBuilder<'a>) -> Self { + pub fn with_child(mut self, child: impl NodeBuilder) -> Self { let node = child.build(&mut self); self.children.push(self.nodes.len()); self.nodes.push(node); @@ -15,23 +17,17 @@ impl<'a> CommandTree<'a> { } /// provide at least one name - pub fn new( - names: [&'a str; NAME_COUNT], - description: &'a str, + pub fn new( + names: impl IntoIterator>, + description: impl Into, ) -> Self { - assert!(NAME_COUNT > 0); - - let mut names_vec = Vec::with_capacity(NAME_COUNT); - - for name in names { - names_vec.push(name); - } + let names_vec = names.into_iter().map(Into::into).collect(); Self { nodes: Vec::new(), children: Vec::new(), names: names_vec, - description, + description: description.into(), } } @@ -42,9 +38,11 @@ impl<'a> CommandTree<'a> { /// desired type. /// /// Also see [`NonLeafNodeBuilder::execute`]. - pub fn execute(mut self, executor: &'a dyn CommandExecutor) -> Self { + pub fn execute(mut self, executor: impl CommandExecutor + 'static + Send) -> Self { let node = Node { - node_type: NodeType::ExecuteLeaf { executor }, + node_type: NodeType::ExecuteLeaf { + executor: Arc::new(executor), + }, children: Vec::new(), }; @@ -55,16 +53,16 @@ impl<'a> CommandTree<'a> { } } -pub trait NodeBuilder<'a> { - fn build(self, tree: &mut CommandTree<'a>) -> Node<'a>; +pub trait NodeBuilder { + fn build(self, tree: &mut CommandTree) -> Node; } -struct LeafNodeBuilder<'a> { - node_type: NodeType<'a>, +struct LeafNodeBuilder { + node_type: NodeType, } -impl<'a> NodeBuilder<'a> for LeafNodeBuilder<'a> { - fn build(self, _tree: &mut CommandTree<'a>) -> Node<'a> { +impl NodeBuilder for LeafNodeBuilder { + fn build(self, _tree: &mut CommandTree) -> Node { Node { children: Vec::new(), node_type: self.node_type, @@ -72,14 +70,14 @@ impl<'a> NodeBuilder<'a> for LeafNodeBuilder<'a> { } } -pub struct NonLeafNodeBuilder<'a> { - node_type: NodeType<'a>, - child_nodes: Vec>, - leaf_nodes: Vec>, +pub struct NonLeafNodeBuilder { + node_type: NodeType, + child_nodes: Vec, + leaf_nodes: Vec, } -impl<'a> NodeBuilder<'a> for NonLeafNodeBuilder<'a> { - fn build(self, tree: &mut CommandTree<'a>) -> Node<'a> { +impl NodeBuilder for NonLeafNodeBuilder { + fn build(self, tree: &mut CommandTree) -> Node { let mut child_indices = Vec::new(); for node_builder in self.child_nodes { @@ -101,7 +99,7 @@ impl<'a> NodeBuilder<'a> for NonLeafNodeBuilder<'a> { } } -impl<'a> NonLeafNodeBuilder<'a> { +impl NonLeafNodeBuilder { /// Add a child [Node] to this one. pub fn with_child(mut self, child: Self) -> Self { self.child_nodes.push(child); @@ -115,9 +113,11 @@ impl<'a> NonLeafNodeBuilder<'a> { /// desired type. /// /// Also see [`CommandTree::execute`]. - pub fn execute(mut self, executor: &'a dyn CommandExecutor) -> Self { + pub fn execute(mut self, executor: impl CommandExecutor + 'static + Send) -> Self { self.leaf_nodes.push(LeafNodeBuilder { - node_type: NodeType::ExecuteLeaf { executor }, + node_type: NodeType::ExecuteLeaf { + executor: Arc::new(executor), + }, }); self @@ -125,9 +125,11 @@ impl<'a> NonLeafNodeBuilder<'a> { } /// Matches a sting literal. -pub const fn literal(string: &str) -> NonLeafNodeBuilder { +pub fn literal(string: impl Into) -> NonLeafNodeBuilder { NonLeafNodeBuilder { - node_type: NodeType::Literal { string }, + node_type: NodeType::Literal { + string: string.into(), + }, child_nodes: Vec::new(), leaf_nodes: Vec::new(), } @@ -141,20 +143,28 @@ pub const fn literal(string: &str) -> NonLeafNodeBuilder { /// [`NonLeafNodeBuilder::execute`] nodes in a [`ConsumedArgs`] instance. It must remove consumed arg(s) /// from [`RawArgs`] and return them. It must return None if [`RawArgs`] are invalid. [`RawArgs`] is /// reversed, so [`Vec::pop`] can be used to obtain args in ltr order. -pub fn argument<'a>(name: &'a str, consumer: &'a dyn ArgumentConsumer) -> NonLeafNodeBuilder<'a> { +pub fn argument( + name: impl Into, + consumer: impl ArgumentConsumer + 'static + Send, +) -> NonLeafNodeBuilder { NonLeafNodeBuilder { - node_type: NodeType::Argument { name, consumer }, + node_type: NodeType::Argument { + name: name.into(), + consumer: Arc::new(consumer), + }, child_nodes: Vec::new(), leaf_nodes: Vec::new(), } } /// same as [`crate::command::tree_builder::argument`], but uses default arg name of consumer -pub fn argument_default_name(consumer: &dyn DefaultNameArgConsumer) -> NonLeafNodeBuilder<'_> { +pub fn argument_default_name( + consumer: impl DefaultNameArgConsumer + 'static + Send, +) -> NonLeafNodeBuilder { NonLeafNodeBuilder { node_type: NodeType::Argument { name: consumer.default_name(), - consumer: consumer.get_argument_consumer(), + consumer: Arc::new(consumer), }, child_nodes: Vec::new(), leaf_nodes: Vec::new(), @@ -163,9 +173,13 @@ pub fn argument_default_name(consumer: &dyn DefaultNameArgConsumer) -> NonLeafNo /// ```predicate``` should return ```false``` if requirement for reaching following [Node]s is not /// met. -pub fn require(predicate: &(dyn Fn(&CommandSender) -> bool + Sync)) -> NonLeafNodeBuilder { +pub fn require( + predicate: impl Fn(&CommandSender) -> bool + Send + Sync + 'static, +) -> NonLeafNodeBuilder { NonLeafNodeBuilder { - node_type: NodeType::Require { predicate }, + node_type: NodeType::Require { + predicate: Arc::new(predicate), + }, child_nodes: Vec::new(), leaf_nodes: Vec::new(), } diff --git a/pumpkin/src/command/tree_format.rs b/pumpkin/src/command/tree_format.rs index 406d95f24..597aece0d 100644 --- a/pumpkin/src/command/tree_format.rs +++ b/pumpkin/src/command/tree_format.rs @@ -7,7 +7,7 @@ trait IsVisible { fn is_visible(&self) -> bool; } -impl IsVisible for Node<'_> { +impl IsVisible for Node { fn is_visible(&self) -> bool { matches!( self.node_type, @@ -16,9 +16,9 @@ impl IsVisible for Node<'_> { } } -impl Display for Node<'_> { +impl Display for Node { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.node_type { + match &self.node_type { NodeType::Literal { string } => { f.write_str(string)?; } @@ -50,10 +50,10 @@ fn flatten_require_nodes(nodes: &[Node], children: &[usize]) -> Vec { new_children } -impl Display for CommandTree<'_> { +impl Display for CommandTree { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_char('/')?; - f.write_str(self.names[0])?; + f.write_str(&self.names[0])?; let mut todo = VecDeque::<&[usize]>::with_capacity(self.children.len()); todo.push_back(&self.children); diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 9be5e8aab..649144a7d 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -11,8 +11,8 @@ use pumpkin_core::math::{ }; use pumpkin_entity::{entity_type::EntityType, pose::EntityPose, EntityId}; use pumpkin_protocol::{ - client::play::{CSetEntityMetadata, CTeleportEntitiy, Metadata}, - VarInt, + client::play::{CSetEntityMetadata, CTeleportEntity, Metadata}, + codec::var_int::VarInt, }; use crate::world::World; @@ -134,7 +134,7 @@ impl Entity { pub async fn teleport(&self, position: Vector3, yaw: f32, pitch: f32) { self.world - .broadcast_packet_all(&CTeleportEntitiy::new( + .broadcast_packet_all(&CTeleportEntity::new( self.entity_id.into(), position, Vector3::new(0.0, 0.0, 0.0), diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index b9695ed45..a87fe7912 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1,4 +1,5 @@ use std::{ + num::NonZeroU8, sync::{ atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU32, AtomicU8}, Arc, @@ -24,7 +25,9 @@ use pumpkin_core::{ use pumpkin_entity::{entity_type::EntityType, EntityId}; use pumpkin_inventory::player::PlayerInventory; use pumpkin_macros::sound; -use pumpkin_protocol::server::play::{SCookieResponse as SPCookieResponse, SPlayPingRequest}; +use pumpkin_protocol::server::play::{ + SCloseContainer, SCookieResponse as SPCookieResponse, SPlayPingRequest, +}; use pumpkin_protocol::{ bytebuf::packet_id::Packet, client::play::{ @@ -38,23 +41,26 @@ use pumpkin_protocol::{ SPlayerCommand, SPlayerInput, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUseItem, SUseItemOn, }, - RawPacket, ServerPacket, SoundCategory, VarInt, + RawPacket, ServerPacket, SoundCategory, }; +use pumpkin_protocol::{client::play::CUpdateTime, codec::var_int::VarInt}; use pumpkin_protocol::{ client::play::{CSetEntityMetadata, Metadata}, server::play::{SClickContainer, SKeepAlive}, }; use pumpkin_world::{ cylindrical_chunk_iterator::Cylindrical, - item::{item_registry::get_item_by_id, ItemStack}, + item::{ + item_registry::{get_item_by_id, Operation}, + ItemStack, + }, }; use tokio::sync::{Mutex, Notify}; use super::Entity; -use crate::error::PumpkinError; +use crate::{error::PumpkinError, net::GameProfile}; use crate::{ - client::{ - authentication::GameProfile, + net::{ combat::{self, player_attack_sound, AttackType}, Client, PlayerConfig, }, @@ -89,7 +95,7 @@ pub struct Player { /// The item currently being held by the player. pub carried_item: AtomicCell>, - /// send `send_abilties_update` when changed + /// send `send_abilities_update` when changed /// The player's abilities and special powers. /// /// This field represents the various abilities that the player possesses, such as flight, invulnerability, and other special effects. @@ -130,7 +136,7 @@ impl Player { ) -> Self { let gameprofile = client.gameprofile.lock().await.clone().map_or_else( || { - log::error!("No gameprofile?. Impossible"); + log::error!("Client {} has no game profile!", client.id); GameProfile { id: uuid::Uuid::new_v4(), name: String::new(), @@ -143,7 +149,6 @@ impl Player { let gameprofile_clone = gameprofile.clone(); let config = client.config.lock().await.clone().unwrap_or_default(); - let view_distance = config.view_distance; let bounding_box_size = BoundingBoxSize { width: 0.6, height: 1.8, @@ -174,7 +179,13 @@ impl Player { teleport_id_count: AtomicI32::new(0), abilities: Mutex::new(Abilities::default()), gamemode: AtomicCell::new(gamemode), - watched_section: AtomicCell::new(Cylindrical::new(Vector2::new(0, 0), view_distance)), + // We want this to be an impossible watched section so that `player_chunker::update_position` + // will mark chunks as watched for a new join rather than a respawn + // (We left shift by one so we can search around that chunk) + watched_section: AtomicCell::new(Cylindrical::new( + Vector2::new(i32::MAX >> 1, i32::MAX >> 1), + unsafe { NonZeroU8::new_unchecked(1) }, + )), wait_for_keep_alive: AtomicBool::new(false), keep_alive_id: AtomicI64::new(0), last_keep_alive_time: AtomicCell::new(std::time::Instant::now()), @@ -223,18 +234,18 @@ impl Player { ); // Decrement value of watched chunks - let chunks_to_clean = world.mark_chunks_as_not_watched(&radial_chunks); + let chunks_to_clean = world.level.mark_chunks_as_not_watched(&radial_chunks); // Remove chunks with no watchers from the cache - world.clean_chunks(&chunks_to_clean); + world.level.clean_chunks(&chunks_to_clean); // Remove left over entries from all possiblily loaded chunks - world.clean_memory(&radial_chunks); + world.level.clean_memory(&radial_chunks); log::debug!( "Removed player id {} ({}) ({} chunks remain cached)", self.gameprofile.name, self.client.id, - self.world().get_cached_chunk_len() + self.world().level.loaded_chunk_count() ); //self.world().level.list_cached(); @@ -262,7 +273,7 @@ impl Player { // TODO: this should be cached in memory if let Some(modifiers) = &item.components.attribute_modifiers { for item_mod in &modifiers.modifiers { - if item_mod.operation == "add_value" { + if item_mod.operation == Operation::AddValue { if item_mod.id == "minecraft:base_attack_damage" { add_damage = item_mod.amount; } @@ -404,7 +415,7 @@ impl Player { } /// Updates the current abilities the Player has - pub async fn send_abilties_update(&self) { + pub async fn send_abilities_update(&self) { let mut b = 0i8; let abilities = &self.abilities.lock().await; @@ -459,6 +470,18 @@ impl Player { *self.permission_lvl.lock() } + /// Sends the world time to just the player. + pub async fn send_time(&self, world: &World) { + let l_world = world.level_time.lock().await; + self.client + .send_packet(&CUpdateTime::new( + l_world.world_age, + l_world.time_of_day, + true, + )) + .await; + } + /// Yaw and Pitch in degrees /// Rarly used, For example when waking up player from bed or first time spawn. Otherwise entity teleport is used /// Player should respond with the `SConfirmTeleport` packet @@ -562,8 +585,6 @@ impl Player { "Setting the same gamemode as already is" ); self.gamemode.store(gamemode); - // The client is using the same method for setting abilities when receiving the CGameEvent ChangeGameMode packet. - // So we can just update the abilities without sending them. { // use another scope so we instantly unlock abilities let mut abilities = self.abilities.lock().await; @@ -587,6 +608,7 @@ impl Player { } } } + self.send_abilities_update().await; self.living_entity .entity .world @@ -757,6 +779,10 @@ impl Player { SPCookieResponse::PACKET_ID => { self.handle_cookie_response(SPCookieResponse::read(bytebuf)?); } + SCloseContainer::PACKET_ID => { + self.handle_close_container(server, SCloseContainer::read(bytebuf)?) + .await; + } _ => { log::warn!("Failed to handle player packet id {}", packet.id.0); // TODO: We give an error if all play packets are implemented @@ -792,7 +818,7 @@ impl Default for Abilities { flying: false, allow_flying: false, creative: false, - fly_speed: 0.4, + fly_speed: 0.05, walk_speed_fov: 0.1, } } diff --git a/pumpkin/src/error.rs b/pumpkin/src/error.rs index d83e9a3e1..10cd79ca3 100644 --- a/pumpkin/src/error.rs +++ b/pumpkin/src/error.rs @@ -1,6 +1,6 @@ use log::log; use pumpkin_inventory::InventoryError; -use pumpkin_protocol::bytebuf::DeserializerError; +use pumpkin_protocol::bytebuf::ReadingError; use std::fmt::Display; pub trait PumpkinError: Send + std::error::Error + Display { @@ -52,7 +52,7 @@ impl PumpkinError for InventoryError { } } -impl PumpkinError for DeserializerError { +impl PumpkinError for ReadingError { fn is_kick(&self) -> bool { true } diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 7ba557f64..d0dbe4019 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -2,6 +2,22 @@ #![deny(clippy::pedantic)] // #![warn(clippy::restriction)] #![deny(clippy::cargo)] +// to keep consistency +#![deny(clippy::if_then_some_else_none)] +#![deny(clippy::empty_enum_variants_with_brackets)] +#![deny(clippy::empty_structs_with_brackets)] +#![deny(clippy::separated_literal_suffix)] +#![deny(clippy::semicolon_outside_block)] +#![deny(clippy::non_zero_suggestions)] +#![deny(clippy::string_lit_chars_any)] +#![deny(clippy::use_self)] +#![deny(clippy::useless_let_if_seq)] +#![deny(clippy::branches_sharing_code)] +#![deny(clippy::equatable_if_let)] +#![deny(clippy::option_if_let_else)] +// use log crate +#![deny(clippy::print_stdout)] +#![deny(clippy::print_stderr)] // REMOVE SOME WHEN RELEASE #![expect(clippy::cargo_common_metadata)] #![expect(clippy::multiple_crate_versions)] @@ -19,7 +35,7 @@ compile_error!("Compiling for WASI targets is not supported!"); use log::LevelFilter; -use client::Client; +use net::{lan_broadcast, query, rcon::RCONServer, Client}; use server::{ticker::Ticker, Server}; use std::io::{self}; use tokio::io::{AsyncBufReadExt, BufReader}; @@ -34,20 +50,15 @@ use crate::server::CURRENT_MC_VERSION; use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; use pumpkin_core::text::{color::NamedColor, TextComponent}; use pumpkin_protocol::CURRENT_MC_PROTOCOL; -use rcon::RCONServer; use std::time::Instant; // Setup some tokens to allow us to identify which event is for which socket. pub mod block; -pub mod client; pub mod command; pub mod data; pub mod entity; pub mod error; -pub mod lan_broadcast; -pub mod proxy; -pub mod query; -pub mod rcon; +pub mod net; pub mod server; pub mod world; @@ -182,8 +193,8 @@ async fn main() { let server = server.clone(); tokio::spawn(async move { ticker.run(&server).await; - }); - } + }) + }; let mut master_client_id: u16 = 0; loop { @@ -296,7 +307,7 @@ fn setup_console(server: Arc) { .expect("Failed to read console line"); if !out.is_empty() { - let dispatcher = server.command_dispatcher.clone(); + let dispatcher = server.command_dispatcher.read().await; dispatcher .handle_command(&mut command::CommandSender::Console, &server, &out) .await; diff --git a/pumpkin/src/client/authentication.rs b/pumpkin/src/net/authentication.rs similarity index 85% rename from pumpkin/src/client/authentication.rs rename to pumpkin/src/net/authentication.rs index f99a62cbb..4136a74a6 100644 --- a/pumpkin/src/client/authentication.rs +++ b/pumpkin/src/net/authentication.rs @@ -2,15 +2,14 @@ use std::{collections::HashMap, net::IpAddr}; use base64::{engine::general_purpose, Engine}; use pumpkin_config::{auth::TextureConfig, ADVANCED_CONFIG}; -use pumpkin_core::ProfileAction; use pumpkin_protocol::Property; use reqwest::{StatusCode, Url}; use serde::Deserialize; -use sha1::Digest; -use sha2::Sha256; use thiserror::Error; use uuid::Uuid; +use super::GameProfile; + #[derive(Deserialize, Clone, Debug)] #[expect(dead_code)] #[serde(rename_all = "camelCase")] @@ -29,14 +28,8 @@ pub struct Texture { metadata: Option>, } -#[derive(Deserialize, Clone, Debug)] -pub struct GameProfile { - pub id: Uuid, - pub name: String, - pub properties: Vec, - #[serde(rename = "profileActions")] - pub profile_actions: Option>, -} +const MOJANG_AUTHENTICATION_URL: &str = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}"; +const MOJANG_PREVENT_PROXY_AUTHENTICATION_URL: &str = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}"; /// Sends a GET request to Mojang's authentication servers to verify a client's Minecraft account. /// @@ -50,20 +43,19 @@ pub struct GameProfile { /// 2. Mojang's servers verify the client's credentials and add the player to the their Servers /// 3. Now our server will send a Request to the Session servers and check if the Player has joined the Session Server . /// -/// See +/// See pub async fn authenticate( username: &str, server_hash: &str, ip: &IpAddr, auth_client: &reqwest::Client, ) -> Result { - assert!(ADVANCED_CONFIG.authentication.enabled); let address = if ADVANCED_CONFIG.authentication.prevent_proxy_connections { let auth_url = ADVANCED_CONFIG .authentication .prevent_proxy_connection_auth_url .as_deref() - .unwrap_or("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}"); + .unwrap_or(MOJANG_PREVENT_PROXY_AUTHENTICATION_URL); auth_url .replace("{username}", username) @@ -74,7 +66,7 @@ pub async fn authenticate( .authentication .auth_url .as_deref() - .unwrap_or("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}"); + .unwrap_or(MOJANG_AUTHENTICATION_URL); auth_url .replace("{username}", username) @@ -129,10 +121,6 @@ pub fn is_texture_url_valid(url: &Url, config: &TextureConfig) -> Result<(), Tex Ok(()) } -pub fn offline_uuid(username: &str) -> Result { - Uuid::from_slice(&Sha256::digest(username)[..16]) -} - #[derive(Error, Debug)] pub enum AuthError { #[error("Missing auth client")] diff --git a/pumpkin/src/client/combat.rs b/pumpkin/src/net/combat.rs similarity index 98% rename from pumpkin/src/client/combat.rs rename to pumpkin/src/net/combat.rs index 8b5b0ed10..12cb0a90f 100644 --- a/pumpkin/src/client/combat.rs +++ b/pumpkin/src/net/combat.rs @@ -4,7 +4,8 @@ use pumpkin_core::math::vector3::Vector3; use pumpkin_macros::{particle, sound}; use pumpkin_protocol::{ client::play::{CEntityVelocity, CParticle}, - SoundCategory, VarInt, + codec::var_int::VarInt, + SoundCategory, }; use pumpkin_world::item::ItemStack; diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/net/container.rs similarity index 93% rename from pumpkin/src/client/container.rs rename to pumpkin/src/net/container.rs index 2ebbb4a17..c157b5e25 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/net/container.rs @@ -1,6 +1,5 @@ use crate::entity::player::Player; use crate::server::Server; -use itertools::Itertools; use pumpkin_core::text::TextComponent; use pumpkin_core::GameMode; use pumpkin_inventory::container_click::{ @@ -13,9 +12,9 @@ use pumpkin_inventory::{Container, WindowType}; use pumpkin_protocol::client::play::{ CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, }; +use pumpkin_protocol::codec::slot::Slot; +use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::server::play::SClickContainer; -use pumpkin_protocol::slot::Slot; -use pumpkin_protocol::VarInt; use pumpkin_world::item::item_registry::Item; use pumpkin_world::item::ItemStack; use std::sync::Arc; @@ -59,11 +58,11 @@ impl Player { let container = OptionallyCombinedContainer::new(&mut inventory, container); - let slots = container + let slots: Vec = container .all_slots_ref() .into_iter() .map(Slot::from) - .collect_vec(); + .collect(); let carried_item = self .carried_item @@ -82,6 +81,7 @@ impl Player { } /// The official Minecraft client is weird, and will always just close *any* window that is opened when this gets sent + // TODO: is this just bc ids are not synced? pub async fn close_container(&self) { let mut inventory = self.inventory().lock().await; inventory.total_opened_containers += 1; @@ -135,15 +135,7 @@ impl Player { return Err(InventoryError::ClosedContainerInteract(self.entity_id())); } - let click = Click::new( - packet - .mode - .0 - .try_into() - .expect("Mode can only be between 0-6"), - packet.button, - packet.slot, - )?; + let click = Click::new(packet.mode, packet.button, packet.slot)?; let (crafted_item, crafted_item_slot) = { let mut inventory = self.inventory().lock().await; let combined = @@ -314,15 +306,9 @@ impl Player { let find_condition = |(slot_number, slot): (usize, &mut Option)| { // TODO: Check for max item count here match slot { - Some(item) => { - if item.item_id == item_in_pressed_slot.item_id - && item.item_count != 64 - { - Some(slot_number) - } else { - None - } - } + Some(item) => (item.item_id == item_in_pressed_slot.item_id + && item.item_count != 64) + .then_some(slot_number), None => Some(slot_number), } }; @@ -468,7 +454,7 @@ impl Player { } async fn get_current_players_in_container(&self, server: &Server) -> Vec> { - let player_ids = { + let player_ids: Vec = { let open_containers = server.open_containers.read().await; open_containers .get(&self.open_container.load().unwrap()) @@ -476,7 +462,7 @@ impl Player { .all_player_ids() .into_iter() .filter(|player_id| *player_id != self.entity_id()) - .collect_vec() + .collect() }; let player_token = self.gameprofile.id; @@ -496,14 +482,10 @@ impl Player { None } else { let entity_id = player.entity_id(); - if player_ids.contains(&entity_id) { - Some(player.clone()) - } else { - None - } + player_ids.contains(&entity_id).then(|| player.clone()) } }) - .collect_vec(); + .collect(); players } @@ -560,13 +542,11 @@ impl Player { let slots = inventory.slots_with_hotbar_first(); let matching_slots = slots.filter_map(|slot| { - if let Some(item_slot) = slot.as_ref() { - if item_slot.item_id == item.id && item_slot.item_count < max_stack { + if let Some(item_slot) = slot.as_mut() { + (item_slot.item_id == item.id && item_slot.item_count < max_stack).then(|| { let item_count = item_slot.item_count; - Some((slot, item_count)) - } else { - None - } + (item_slot, item_count) + }) } else { None } @@ -579,15 +559,15 @@ impl Player { let amount_to_add = max_stack - item_count; if let Some(amount_left) = amount.checked_sub(u32::from(amount_to_add)) { amount = amount_left; - *slot = Some(ItemStack { + *slot = ItemStack { item_id: item.id, item_count: item.components.max_stack_size, - }); + }; } else { - *slot = Some(ItemStack { + *slot = ItemStack { item_id: item.id, item_count: max_stack - (amount_to_add - amount as u8), - }); + }; return; } } diff --git a/pumpkin/src/lan_broadcast.rs b/pumpkin/src/net/lan_broadcast.rs similarity index 100% rename from pumpkin/src/lan_broadcast.rs rename to pumpkin/src/net/lan_broadcast.rs diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/net/mod.rs similarity index 89% rename from pumpkin/src/client/mod.rs rename to pumpkin/src/net/mod.rs index 1c8d1057c..b43cb1cbb 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/net/mod.rs @@ -1,6 +1,7 @@ use std::{ collections::VecDeque, net::SocketAddr, + num::NonZeroU8, sync::{ atomic::{AtomicBool, AtomicI32}, Arc, @@ -12,34 +13,58 @@ use crate::{ server::Server, }; -use authentication::GameProfile; use crossbeam::atomic::AtomicCell; use pumpkin_config::compression::CompressionInfo; -use pumpkin_core::text::TextComponent; +use pumpkin_core::{text::TextComponent, ProfileAction}; use pumpkin_protocol::{ - bytebuf::{packet_id::Packet, DeserializerError}, + bytebuf::{packet_id::Packet, ReadingError}, client::{config::CConfigDisconnect, login::CLoginDisconnect, play::CPlayDisconnect}, packet_decoder::PacketDecoder, packet_encoder::{PacketEncodeError, PacketEncoder}, server::{ - config::{SAcknowledgeFinishConfig, SClientInformationConfig, SKnownPacks, SPluginMessage}, + config::{ + SAcknowledgeFinishConfig, SClientInformationConfig, SConfigCookieResponse, SKnownPacks, + SPluginMessage, + }, handshake::SHandShake, - login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart}, + login::{ + SEncryptionResponse, SLoginAcknowledged, SLoginCookieResponse, SLoginPluginResponse, + SLoginStart, + }, status::{SStatusPingRequest, SStatusRequest}, }, - ClientPacket, ConnectionState, RawPacket, ServerPacket, + ClientPacket, CompressionLevel, CompressionThreshold, ConnectionState, Property, RawPacket, + ServerPacket, }; +use serde::Deserialize; +use sha1::Digest; +use sha2::Sha256; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::sync::Mutex; -use pumpkin_protocol::server::config::SCookieResponse as SCCookieResponse; -use pumpkin_protocol::server::login::SCookieResponse as SLCookieResponse; use thiserror::Error; -pub mod authentication; -mod client_packet; +use uuid::Uuid; +mod authentication; pub mod combat; mod container; -pub mod player_packet; +pub mod lan_broadcast; +mod packet; +mod proxy; +pub mod query; +pub mod rcon; + +#[derive(Deserialize, Clone, Debug)] +pub struct GameProfile { + pub id: Uuid, + pub name: String, + pub properties: Vec, + #[serde(rename = "profileActions")] + pub profile_actions: Option>, +} + +pub fn offline_uuid(username: &str) -> Result { + Uuid::from_slice(&Sha256::digest(username)[..16]) +} /// Represents a player's configuration settings. /// @@ -53,7 +78,7 @@ pub struct PlayerConfig { /// The player's preferred language. pub locale: String, // 16 /// The maximum distance at which chunks are rendered. - pub view_distance: u8, + pub view_distance: NonZeroU8, /// The player's chat mode settings pub chat_mode: ChatMode, /// Whether chat colors are enabled. @@ -72,7 +97,7 @@ impl Default for PlayerConfig { fn default() -> Self { Self { locale: "en_us".to_string(), - view_distance: 2, + view_distance: unsafe { NonZeroU8::new_unchecked(10) }, chat_mode: ChatMode::Enabled, chat_colors: true, skin_parts: 0, @@ -101,8 +126,6 @@ pub struct Client { pub server_address: Mutex, /// The current connection state of the client (e.g., Handshaking, Status, Play). pub connection_state: AtomicCell, - /// Whether encryption is enabled for the connection. - pub encryption: AtomicBool, /// Indicates if the client connection is closed. pub closed: AtomicBool, /// The underlying TCP connection to the client. @@ -137,7 +160,6 @@ impl Client { connection_writer: Arc::new(Mutex::new(connection_writer)), enc: Arc::new(Mutex::new(PacketEncoder::default())), dec: Arc::new(Mutex::new(PacketDecoder::default())), - encryption: AtomicBool::new(false), closed: AtomicBool::new(false), client_packets_queue: Arc::new(Mutex::new(VecDeque::new())), make_player: AtomicBool::new(false), @@ -181,8 +203,6 @@ impl Client { shared_secret: Option<&[u8]>, // decrypted ) -> Result<(), EncryptionError> { if let Some(shared_secret) = shared_secret { - self.encryption - .store(true, std::sync::atomic::Ordering::Relaxed); let crypt_key: [u8; 16] = shared_secret .try_into() .map_err(|_| EncryptionError::SharedWrongLength)?; @@ -205,7 +225,13 @@ impl Client { /// * `compression`: An optional `CompressionInfo` struct containing the compression threshold and compression level. pub async fn set_compression(&self, compression: Option) { self.dec.lock().await.set_compression(compression.is_some()); - self.enc.lock().await.set_compression(compression); + self.enc + .lock() + .await + .set_compression( + compression.map(|s| (CompressionThreshold(s.threshold), CompressionLevel(s.level))), + ) + .unwrap_or_else(|_| log::warn!("invalid compression level")); } /// Sends a clientbound packet to the connected client. @@ -216,17 +242,19 @@ impl Client { pub async fn send_packet(&self, packet: &P) { //log::debug!("Sending packet with id {} to {}", P::PACKET_ID, self.id); // assert!(!self.closed); + if self.closed.load(std::sync::atomic::Ordering::Relaxed) { + return; + } + let mut enc = self.enc.lock().await; if let Err(error) = enc.append_packet(packet) { - if error.kickable() { - self.kick(&error.to_string()).await; - } + self.kick(&error.to_string()).await; return; } let mut writer = self.connection_writer.lock().await; if let Err(error) = writer.write_all(&enc.take()).await { - log::debug!("{}", error.to_string()); + log::debug!("Unable to write to connection: {}", error.to_string()); } /* @@ -293,8 +321,8 @@ impl Client { /// /// # Arguments /// - /// * `server`: A reference to the `Arc` instance. - pub async fn process_packets(&self, server: &Arc) { + /// * `server`: A reference to the `Server` instance. + pub async fn process_packets(&self, server: &Server) { let mut packet_queue = self.client_packets_queue.lock().await; while let Some(mut packet) = packet_queue.pop_front() { if self.closed.load(std::sync::atomic::Ordering::Relaxed) { @@ -327,7 +355,7 @@ impl Client { /// /// # Arguments /// - /// * `server`: A reference to the `Arc` instance. + /// * `server`: A reference to the `Server` instance. /// * `packet`: A mutable reference to the `RawPacket` to be processed. /// /// # Returns @@ -339,9 +367,9 @@ impl Client { /// Returns a `DeserializerError` if an error occurs during packet deserialization. pub async fn handle_packet( &self, - server: &Arc, + server: &Server, packet: &mut RawPacket, - ) -> Result<(), DeserializerError> { + ) -> Result<(), ReadingError> { match self.connection_state.load() { pumpkin_protocol::ConnectionState::HandShake => { self.handle_handshake_packet(packet).await @@ -364,10 +392,7 @@ impl Client { } } - async fn handle_handshake_packet( - &self, - packet: &mut RawPacket, - ) -> Result<(), DeserializerError> { + async fn handle_handshake_packet(&self, packet: &mut RawPacket) -> Result<(), ReadingError> { log::debug!("Handling handshake group"); let bytebuf = &mut packet.bytebuf; match packet.id.0 { @@ -386,9 +411,9 @@ impl Client { async fn handle_status_packet( &self, - server: &Arc, + server: &Server, packet: &mut RawPacket, - ) -> Result<(), DeserializerError> { + ) -> Result<(), ReadingError> { log::debug!("Handling status group"); let bytebuf = &mut packet.bytebuf; match packet.id.0 { @@ -412,9 +437,9 @@ impl Client { async fn handle_login_packet( &self, - server: &Arc, + server: &Server, packet: &mut RawPacket, - ) -> Result<(), DeserializerError> { + ) -> Result<(), ReadingError> { log::debug!("Handling login group for id"); let bytebuf = &mut packet.bytebuf; match packet.id.0 { @@ -433,15 +458,14 @@ impl Client { SLoginAcknowledged::PACKET_ID => { self.handle_login_acknowledged(server).await; } - SLCookieResponse::PACKET_ID => { - self.handle_login_cookie_response(SLCookieResponse::read(bytebuf)?); + SLoginCookieResponse::PACKET_ID => { + self.handle_login_cookie_response(SLoginCookieResponse::read(bytebuf)?); } _ => { log::error!( "Failed to handle client packet id {} in Login State", packet.id.0 ); - return Ok(()); } }; Ok(()) @@ -449,9 +473,9 @@ impl Client { async fn handle_config_packet( &self, - server: &Arc, + server: &Server, packet: &mut RawPacket, - ) -> Result<(), DeserializerError> { + ) -> Result<(), ReadingError> { log::debug!("Handling config group"); let bytebuf = &mut packet.bytebuf; match packet.id.0 { @@ -470,8 +494,8 @@ impl Client { self.handle_known_packs(server, SKnownPacks::read(bytebuf)?) .await; } - SCCookieResponse::PACKET_ID => { - self.handle_config_cookie_response(SCCookieResponse::read(bytebuf)?); + SConfigCookieResponse::PACKET_ID => { + self.handle_config_cookie_response(SConfigCookieResponse::read(bytebuf)?); } _ => { log::error!( diff --git a/pumpkin/src/net/packet/config.rs b/pumpkin/src/net/packet/config.rs new file mode 100644 index 000000000..ed1d8769a --- /dev/null +++ b/pumpkin/src/net/packet/config.rs @@ -0,0 +1,98 @@ +use std::num::NonZeroU8; + +use crate::{ + entity::player::{ChatMode, Hand}, + net::{Client, PlayerConfig}, + server::Server, +}; +use core::str; +use num_traits::FromPrimitive; +use pumpkin_protocol::{ + client::config::{CFinishConfig, CRegistryData}, + codec::var_int::VarInt, + server::config::{ + SClientInformationConfig, SConfigCookieResponse, SKnownPacks, SPluginMessage, + }, + ConnectionState, +}; + +impl Client { + pub async fn handle_client_information_config( + &self, + client_information: SClientInformationConfig, + ) { + log::debug!("Handling client settings"); + if client_information.view_distance <= 0 { + self.kick("Cannot have zero or negative view distance!") + .await; + return; + } + + if let (Some(main_hand), Some(chat_mode)) = ( + Hand::from_i32(client_information.main_hand.into()), + ChatMode::from_i32(client_information.chat_mode.into()), + ) { + *self.config.lock().await = Some(PlayerConfig { + locale: client_information.locale, + view_distance: unsafe { + NonZeroU8::new_unchecked(client_information.view_distance as u8) + }, + chat_mode, + chat_colors: client_information.chat_colors, + skin_parts: client_information.skin_parts, + main_hand, + text_filtering: client_information.text_filtering, + server_listing: client_information.server_listing, + }); + } else { + self.kick("Invalid hand or chat type").await; + } + } + + pub async fn handle_plugin_message(&self, plugin_message: SPluginMessage) { + log::debug!("Handling plugin message"); + if plugin_message + .channel + .to_string() + .starts_with("minecraft:brand") + { + log::debug!("got a client brand"); + match str::from_utf8(&plugin_message.data) { + Ok(brand) => *self.brand.lock().await = Some(brand.to_string()), + Err(e) => self.kick(&e.to_string()).await, + } + } + } + + pub fn handle_config_cookie_response(&self, packet: SConfigCookieResponse) { + // TODO: allow plugins to access this + log::debug!( + "Received cookie_response[config]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"", + packet.key.to_string(), + packet.has_payload, + packet.payload_length.unwrap_or(VarInt::from(0)).0 + ); + } + + pub async fn handle_known_packs(&self, server: &Server, _config_acknowledged: SKnownPacks) { + log::debug!("Handling known packs"); + for registry in &server.cached_registry { + self.send_packet(&CRegistryData::new( + ®istry.registry_id, + ®istry.registry_entries, + )) + .await; + } + + // We are done with configuring + log::debug!("finished config"); + self.send_packet(&CFinishConfig::new()).await; + } + + pub fn handle_config_acknowledged(&self) { + log::debug!("Handling config acknowledge"); + self.connection_state.store(ConnectionState::Play); + self.make_player + .store(true, std::sync::atomic::Ordering::Relaxed); + } +} diff --git a/pumpkin/src/net/packet/handshake.rs b/pumpkin/src/net/packet/handshake.rs new file mode 100644 index 000000000..58705210f --- /dev/null +++ b/pumpkin/src/net/packet/handshake.rs @@ -0,0 +1,29 @@ +use std::num::NonZeroI32; + +use pumpkin_protocol::{server::handshake::SHandShake, ConnectionState, CURRENT_MC_PROTOCOL}; + +use crate::{net::Client, server::CURRENT_MC_VERSION}; + +impl Client { + pub async fn handle_handshake(&self, handshake: SHandShake) { + let version = handshake.protocol_version.0; + self.protocol_version + .store(version, std::sync::atomic::Ordering::Relaxed); + *self.server_address.lock().await = handshake.server_address; + + log::debug!("Handshake: next state {:?}", &handshake.next_state); + self.connection_state.store(handshake.next_state); + if self.connection_state.load() != ConnectionState::Status { + let protocol = version; + match protocol.cmp(&NonZeroI32::from(CURRENT_MC_PROTOCOL).get()) { + std::cmp::Ordering::Less => { + self.kick(&format!("Client outdated ({protocol}), Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await; + } + std::cmp::Ordering::Equal => {} + std::cmp::Ordering::Greater => { + self.kick(&format!("Server outdated, Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await; + } + } + } + } +} diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/net/packet/login.rs similarity index 63% rename from pumpkin/src/client/client_packet.rs rename to pumpkin/src/net/packet/login.rs index a95c4b40a..585413d63 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/net/packet/login.rs @@ -1,36 +1,29 @@ -use super::{authentication::AuthError, Client, PlayerConfig}; -use crate::{ - client::authentication::{self, offline_uuid, validate_textures, GameProfile}, - entity::player::{ChatMode, Hand}, - proxy::{ - bungeecord, - velocity::{self, velocity_login}, - }, - server::{Server, CURRENT_MC_VERSION}, -}; -use num_traits::FromPrimitive; +use std::sync::LazyLock; + use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; use pumpkin_core::text::TextComponent; -use pumpkin_protocol::client::config::{CServerLinks, Label, Link, LinkType}; -use pumpkin_protocol::server::config::SCookieResponse as SCCookieResponse; -use pumpkin_protocol::server::login::SCookieResponse as SLCookieResponse; use pumpkin_protocol::{ client::{ - config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData}, + config::{CConfigAddResourcePack, CConfigServerLinks, CKnownPacks}, login::{CLoginSuccess, CSetCompression}, - status::CPingResponse, - }, - server::{ - config::{SClientInformationConfig, SKnownPacks, SPluginMessage}, - handshake::SHandShake, - login::{SEncryptionResponse, SLoginPluginResponse, SLoginStart}, - status::SStatusPingRequest, }, - ConnectionState, KnownPack, VarInt, CURRENT_MC_PROTOCOL, + codec::var_int::VarInt, + server::login::{SEncryptionResponse, SLoginCookieResponse, SLoginPluginResponse, SLoginStart}, + ConnectionState, KnownPack, Label, Link, LinkType, }; -use std::sync::LazyLock; use uuid::Uuid; +use crate::{ + net::{ + authentication::{self, AuthError}, + offline_uuid, + packet::is_valid_player_name, + proxy::{bungeecord, velocity}, + Client, GameProfile, + }, + server::Server, +}; + static LINKS: LazyLock> = LazyLock::new(|| { let mut links: Vec = Vec::new(); @@ -91,52 +84,7 @@ static LINKS: LazyLock> = LazyLock::new(|| { links }); -/// Processes incoming Packets from the Client to the Server -/// Implements the `Client` Packets -/// NEVER TRUST THE CLIENT. HANDLE EVERY ERROR, UNWRAP/EXPECT impl Client { - pub async fn handle_handshake(&self, handshake: SHandShake) { - let version = handshake.protocol_version.0; - self.protocol_version - .store(version, std::sync::atomic::Ordering::Relaxed); - *self.server_address.lock().await = handshake.server_address; - - log::debug!("Handshake: next state {:?}", &handshake.next_state); - self.connection_state.store(handshake.next_state); - if self.connection_state.load() != ConnectionState::Status { - let protocol = version; - match protocol.cmp(&(CURRENT_MC_PROTOCOL as i32)) { - std::cmp::Ordering::Less => { - self.kick(&format!("Client outdated ({protocol}), Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await; - } - std::cmp::Ordering::Equal => {} - std::cmp::Ordering::Greater => { - self.kick(&format!("Server outdated, Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await; - } - } - } - } - - pub async fn handle_status_request(&self, server: &Server) { - log::debug!("Handling status request"); - let status = server.get_status(); - self.send_packet(&status.lock().await.get_status()).await; - } - - pub async fn handle_ping_request(&self, ping_request: SStatusPingRequest) { - log::debug!("Handling ping request"); - self.send_packet(&CPingResponse::new(ping_request.payload)) - .await; - self.close(); - } - - fn is_valid_player_name(name: &str) -> bool { - name.len() <= 16 - && name - .chars() - .all(|c| c > 32_u8 as char && c < 127_u8 as char) - } - pub async fn handle_login_start(&self, server: &Server, login_start: SLoginStart) { log::debug!("login start"); @@ -150,7 +98,7 @@ impl Client { return; } - if !Self::is_valid_player_name(&login_start.name) { + if !is_valid_player_name(&login_start.name) { self.kick("Invalid characters in username").await; return; } @@ -160,9 +108,15 @@ impl Client { let proxy = &ADVANCED_CONFIG.proxy; if proxy.enabled { if proxy.velocity.enabled { - velocity_login(self).await; + velocity::velocity_login(self).await; } else if proxy.bungeecord.enabled { - match bungeecord::bungeecord_login(self, login_start.name).await { + match bungeecord::bungeecord_login( + &self.address, + &self.server_address.lock().await, + login_start.name, + ) + .await + { Ok((_ip, profile)) => { // self.address.lock() = ip; self.finish_login(&profile).await; @@ -305,24 +259,26 @@ impl Client { } // validate textures for property in &profile.properties { - validate_textures(property, &ADVANCED_CONFIG.authentication.textures) - .map_err(AuthError::TextureError)?; + authentication::validate_textures( + property, + &ADVANCED_CONFIG.authentication.textures, + ) + .map_err(AuthError::TextureError)?; } return Ok(profile); } Err(AuthError::MissingAuthClient) } - pub fn handle_login_cookie_response(&self, packet: SLCookieResponse) { + pub fn handle_login_cookie_response(&self, packet: SLoginCookieResponse) { // TODO: allow plugins to access this log::debug!( - "Received cookie_response[login]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"", - packet.key, - packet.has_payload, - packet.payload_length.unwrap_or(VarInt::from(0)).0 - ); + "Received cookie_response[login]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"", + packet.key.to_string(), + packet.has_payload, + packet.payload_length.unwrap_or(VarInt::from(0)).0 + ); } - pub async fn handle_plugin_response(&self, plugin_response: SLoginPluginResponse) { log::debug!("Handling plugin"); let velocity_config = &ADVANCED_CONFIG.proxy.velocity; @@ -369,8 +325,11 @@ impl Client { } if ADVANCED_CONFIG.server_links.enabled { - self.send_packet(&CServerLinks::new(&VarInt(LINKS.len() as i32), &LINKS)) - .await; + self.send_packet(&CConfigServerLinks::new( + &VarInt(LINKS.len() as i32), + &LINKS, + )) + .await; } // known data packs @@ -382,72 +341,4 @@ impl Client { .await; log::debug!("login acknowledged"); } - pub async fn handle_client_information_config( - &self, - client_information: SClientInformationConfig, - ) { - log::debug!("Handling client settings"); - if let (Some(main_hand), Some(chat_mode)) = ( - Hand::from_i32(client_information.main_hand.into()), - ChatMode::from_i32(client_information.chat_mode.into()), - ) { - *self.config.lock().await = Some(PlayerConfig { - locale: client_information.locale, - view_distance: client_information.view_distance as u8, - chat_mode, - chat_colors: client_information.chat_colors, - skin_parts: client_information.skin_parts, - main_hand, - text_filtering: client_information.text_filtering, - server_listing: client_information.server_listing, - }); - } else { - self.kick("Invalid hand or chat type").await; - } - } - - pub async fn handle_plugin_message(&self, plugin_message: SPluginMessage) { - log::debug!("Handling plugin message"); - if plugin_message.channel.starts_with("minecraft:brand") - || plugin_message.channel.starts_with("MC|Brand") - { - log::debug!("got a client brand"); - match String::from_utf8(plugin_message.data) { - Ok(brand) => *self.brand.lock().await = Some(brand), - Err(e) => self.kick(&e.to_string()).await, - } - } - } - - pub fn handle_config_cookie_response(&self, packet: SCCookieResponse) { - // TODO: allow plugins to access this - log::debug!( - "Received cookie_response[config]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"", - packet.key, - packet.has_payload, - packet.payload_length.unwrap_or(VarInt::from(0)).0 - ); - } - - pub async fn handle_known_packs(&self, server: &Server, _config_acknowledged: SKnownPacks) { - log::debug!("Handling known packs"); - for registry in &server.cached_registry { - self.send_packet(&CRegistryData::new( - ®istry.registry_id, - ®istry.registry_entries, - )) - .await; - } - - // We are done with configuring - log::debug!("finished config"); - self.send_packet(&CFinishConfig::new()).await; - } - - pub fn handle_config_acknowledged(&self) { - log::debug!("Handling config acknowledge"); - self.connection_state.store(ConnectionState::Play); - self.make_player - .store(true, std::sync::atomic::Ordering::Relaxed); - } } diff --git a/pumpkin/src/net/packet/mod.rs b/pumpkin/src/net/packet/mod.rs new file mode 100644 index 000000000..0dbc697d1 --- /dev/null +++ b/pumpkin/src/net/packet/mod.rs @@ -0,0 +1,9 @@ +mod config; +mod handshake; +mod login; +mod play; +mod status; + +fn is_valid_player_name(name: &str) -> bool { + name.len() <= 16 && name.chars().all(|c| c > 32u8 as char && c < 127u8 as char) +} diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/net/packet/play.rs similarity index 88% rename from pumpkin/src/client/player_packet.rs rename to pumpkin/src/net/packet/play.rs index 1dd55bf0f..baf904659 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/net/packet/play.rs @@ -1,7 +1,8 @@ +use std::num::NonZeroU8; use std::sync::Arc; -use super::PlayerConfig; use crate::block::block_manager::BlockActionResult; +use crate::net::PlayerConfig; use crate::{ command::CommandSender, entity::player::{ChatMode, Hand, Player}, @@ -17,12 +18,12 @@ use pumpkin_core::{ text::TextComponent, GameMode, }; -use pumpkin_inventory::{InventoryError, WindowType}; +use pumpkin_inventory::InventoryError; +use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::server::play::SCookieResponse as SPCookieResponse; use pumpkin_protocol::{ client::play::CCommandSuggestions, server::play::{SCloseContainer, SCommandSuggestion, SKeepAlive, SSetPlayerGround, SUseItem}, - VarInt, }; use pumpkin_protocol::{ client::play::{ @@ -285,7 +286,7 @@ impl Player { server: &Arc, command: SChatCommand, ) { - let dispatcher = server.command_dispatcher.clone(); + let dispatcher = server.command_dispatcher.read().await; dispatcher .handle_command( &mut CommandSender::Player(self.clone()), @@ -435,28 +436,70 @@ impl Player { ) */ } - pub async fn handle_client_information(&self, client_information: SClientInformationPlay) { + pub async fn handle_client_information( + self: &Arc, + client_information: SClientInformationPlay, + ) { if let (Some(main_hand), Some(chat_mode)) = ( Hand::from_i32(client_information.main_hand.into()), ChatMode::from_i32(client_information.chat_mode.into()), ) { - let mut config = self.config.lock().await; - let update = - config.main_hand != main_hand || config.skin_parts != client_information.skin_parts; - - *config = PlayerConfig { - locale: client_information.locale, - // A Negative view distance would be impossible and make no sense right ?, Mojang: Lets make is signed :D - view_distance: client_information.view_distance as u8, - chat_mode, - chat_colors: client_information.chat_colors, - skin_parts: client_information.skin_parts, - main_hand, - text_filtering: client_information.text_filtering, - server_listing: client_information.server_listing, + if client_information.view_distance <= 0 { + self.kick(TextComponent::text( + "Cannot have zero or negative view distance!", + )) + .await; + return; + } + + let (update_skin, update_watched) = { + let mut config = self.config.lock().await; + let update_skin = config.main_hand != main_hand + || config.skin_parts != client_information.skin_parts; + + let old_view_distance = config.view_distance; + + let update_watched = + if old_view_distance.get() == client_information.view_distance as u8 { + false + } else { + log::debug!( + "Player {} ({}) updated render distance: {} -> {}.", + self.gameprofile.name, + self.client.id, + old_view_distance, + client_information.view_distance + ); + + true + }; + + *config = PlayerConfig { + locale: client_information.locale, + // A Negative view distance would be impossible and make no sense right ?, Mojang: Lets make is signed :D + view_distance: unsafe { + NonZeroU8::new_unchecked(client_information.view_distance as u8) + }, + chat_mode, + chat_colors: client_information.chat_colors, + skin_parts: client_information.skin_parts, + main_hand, + text_filtering: client_information.text_filtering, + server_listing: client_information.server_listing, + }; + (update_skin, update_watched) }; - drop(config); - if update { + + if update_watched { + player_chunker::update_position(self).await; + } + + if update_skin { + log::debug!( + "Player {} ({}) updated their skin.", + self.gameprofile.name, + self.client.id, + ); self.update_client_information().await; } } else { @@ -721,9 +764,14 @@ impl Player { } let block_bounding_box = BoundingBox::from_block(&world_pos); - let bounding_box = entity.bounding_box.load(); - //TODO: Make this check for every entity in that posistion - if !bounding_box.intersects(&block_bounding_box) { + let mut intersects = false; + for player in world.get_nearby_players(entity.pos.load(), 20).await { + let bounding_box = player.1.living_entity.entity.bounding_box.load(); + if bounding_box.intersects(&block_bounding_box) { + intersects = true; + } + } + if !intersects { world .set_block_state(world_pos, block.default_state_id) .await; @@ -790,11 +838,13 @@ impl Player { // TODO: // This function will in the future be used to keep track of if the client is in a valid state. // But this is not possible yet - pub async fn handle_close_container(&self, server: &Server, packet: SCloseContainer) { - let Some(_window_type) = WindowType::from_i32(packet.window_id.0) else { - self.kick(TextComponent::text("Invalid window ID")).await; - return; - }; + pub async fn handle_close_container(&self, server: &Server, _packet: SCloseContainer) { + // TODO: This should check if player sent this packet before + // let Some(_window_type) = WindowType::from_i32(packet.window_id.0) else { + // log::info!("Closed ID: {}", packet.window_id.0); + // self.kick(TextComponent::text("Invalid window ID")).await; + // return; + // }; // window_id 0 represents both 9x1 Generic AND inventory here let mut inventory = self.inventory().lock().await; @@ -803,6 +853,16 @@ impl Player { if let Some(id) = open_container { let mut open_containers = server.open_containers.write().await; if let Some(container) = open_containers.get_mut(&id) { + // If container contains both a location and a type, run the on_close block_manager handler + if let Some(pos) = container.get_location() { + if let Some(block) = container.get_block() { + server + .block_manager + .on_close(&block, self, pos, server, container) //block, self, location, server) + .await; + } + } + // Remove the player from the container container.remove_player(self.entity_id()); } self.open_container.store(None); @@ -824,10 +884,8 @@ impl Player { return; }; - let suggestions = server - .command_dispatcher - .find_suggestions(&mut src, server, cmd) - .await; + let dispatcher = server.command_dispatcher.read().await; + let suggestions = dispatcher.find_suggestions(&mut src, server, cmd).await; let response = CCommandSuggestions::new( packet.id, @@ -843,7 +901,7 @@ impl Player { // TODO: allow plugins to access this log::debug!( "Received cookie_response[play]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"", - packet.key, + packet.key.to_string(), packet.has_payload, packet.payload_length.unwrap_or(VarInt::from(0)).0 ); diff --git a/pumpkin/src/net/packet/status.rs b/pumpkin/src/net/packet/status.rs new file mode 100644 index 000000000..0e9790c29 --- /dev/null +++ b/pumpkin/src/net/packet/status.rs @@ -0,0 +1,18 @@ +use pumpkin_protocol::{client::status::CPingResponse, server::status::SStatusPingRequest}; + +use crate::{net::Client, server::Server}; + +impl Client { + pub async fn handle_status_request(&self, server: &Server) { + log::debug!("Handling status request"); + let status = server.get_status(); + self.send_packet(&status.lock().await.get_status()).await; + } + + pub async fn handle_ping_request(&self, ping_request: SStatusPingRequest) { + log::debug!("Handling ping request"); + self.send_packet(&CPingResponse::new(ping_request.payload)) + .await; + self.close(); + } +} diff --git a/pumpkin/src/proxy/bungeecord.rs b/pumpkin/src/net/proxy/bungeecord.rs similarity index 59% rename from pumpkin/src/proxy/bungeecord.rs rename to pumpkin/src/net/proxy/bungeecord.rs index c8c9d1cea..dc1976713 100644 --- a/pumpkin/src/proxy/bungeecord.rs +++ b/pumpkin/src/net/proxy/bungeecord.rs @@ -1,12 +1,10 @@ -use std::net::IpAddr; +use std::{net::IpAddr, net::SocketAddr}; use pumpkin_protocol::Property; use thiserror::Error; +use tokio::sync::Mutex; -use crate::{ - client::authentication::{offline_uuid, GameProfile}, - Client, -}; +use crate::net::{offline_uuid, GameProfile}; #[derive(Error, Debug)] pub enum BungeeCordError { @@ -20,11 +18,23 @@ pub enum BungeeCordError { FailedMakeOfflineUUID, } +/// Attempts to login a player via `BungeeCord`. +/// +/// This function should be called when receiving the `SLoginStart` packet. +/// It utilizes the `server_address` received in the `SHandShake` packet, +/// which may contain optional data about the client: +/// +/// 1. IP address (if `ip_forward` is enabled on the `BungeeCord` server) +/// 2. UUID (if `ip_forward` is enabled on the `BungeeCord` server) +/// 3. Game profile properties (if `ip_forward` and `online_mode` are enabled on the `BungeeCord` server) +/// +/// If any of the optional data is missing, the function will attempt to +/// determine the player's information locally. pub async fn bungeecord_login( - client: &Client, - username: String, + client_address: &Mutex, + server_address: &str, + name: String, ) -> Result<(IpAddr, GameProfile), BungeeCordError> { - let server_address = client.server_address.lock().await; let data = server_address.split('\0').take(4).collect::>(); // Ip of player, only given if ip_forward on bungee is true @@ -32,15 +42,13 @@ pub async fn bungeecord_login( Some(ip) => ip .parse() .map_err(|_| BungeeCordError::FailedParseAddress)?, - None => client.address.lock().await.ip(), + None => client_address.lock().await.ip(), }; // Uuid of player, only given if ip_forward on bungee is true let id = match data.get(2) { Some(uuid) => uuid.parse().map_err(|_| BungeeCordError::FailedParseUUID)?, - None => { - offline_uuid(username.as_str()).map_err(|_| BungeeCordError::FailedMakeOfflineUUID)? - } + None => offline_uuid(name.as_str()).map_err(|_| BungeeCordError::FailedMakeOfflineUUID)?, }; // Read properties and get textures @@ -57,7 +65,7 @@ pub async fn bungeecord_login( ip, GameProfile { id, - name: username, + name, properties, profile_actions: None, }, diff --git a/pumpkin/src/proxy/mod.rs b/pumpkin/src/net/proxy/mod.rs similarity index 100% rename from pumpkin/src/proxy/mod.rs rename to pumpkin/src/net/proxy/mod.rs diff --git a/pumpkin/src/proxy/velocity.rs b/pumpkin/src/net/proxy/velocity.rs similarity index 86% rename from pumpkin/src/proxy/velocity.rs rename to pumpkin/src/net/proxy/velocity.rs index cf4bcfc19..461c9e0f2 100644 --- a/pumpkin/src/proxy/velocity.rs +++ b/pumpkin/src/net/proxy/velocity.rs @@ -7,14 +7,14 @@ use bytes::{BufMut, BytesMut}; use hmac::{Hmac, Mac}; use pumpkin_config::proxy::VelocityConfig; use pumpkin_protocol::{ - bytebuf::ByteBuffer, client::login::CLoginPluginRequest, server::login::SLoginPluginResponse, + bytebuf::ByteBuf, client::login::CLoginPluginRequest, server::login::SLoginPluginResponse, Property, }; use rand::Rng; use sha2::Sha256; use thiserror::Error; -use crate::client::{authentication::GameProfile, Client}; +use crate::net::{Client, GameProfile}; type HmacSha256 = Hmac; @@ -34,7 +34,7 @@ pub enum VelocityError { #[error("Failed to read address")] FailedReadAddress, #[error("Failed to parse address")] - FailedParseAddres, + FailedParseAddress, #[error("Failed to read game profile name")] FailedReadProfileName, #[error("Failed to read game profile UUID")] @@ -68,19 +68,19 @@ pub fn check_integrity(data: (&[u8], &[u8]), secret: &str) -> bool { mac.verify_slice(signature).is_ok() } -fn read_game_profile(buf: &mut ByteBuffer) -> Result { +fn read_game_profile(buf: &mut BytesMut) -> Result { let id = buf - .get_uuid() + .try_get_uuid() .map_err(|_| VelocityError::FailedReadProfileUUID)?; let name = buf - .get_string() + .try_get_string() .map_err(|_| VelocityError::FailedReadProfileName)?; let properties = buf .get_list(|data| { - let name = data.get_string()?; - let value = data.get_string()?; - let signature = data.get_option(pumpkin_protocol::bytebuf::ByteBuffer::get_string)?; + let name = data.try_get_string()?; + let value = data.try_get_string()?; + let signature = data.try_get_option(ByteBuf::try_get_string)?; Ok(Property { name, @@ -109,12 +109,12 @@ pub fn receive_velocity_plugin_response( if !check_integrity((signature, data_without_signature), &config.secret) { return Err(VelocityError::FailedVerifyIntegrity); } - let mut buf = ByteBuffer::new(BytesMut::new()); + let mut buf = BytesMut::new(); buf.put_slice(data_without_signature); // check velocity version let version = buf - .get_var_int() + .try_get_var_int() .map_err(|_| VelocityError::FailedReadForwardVersion)?; let version = version.0 as u8; if version > MAX_SUPPORTED_FORWARDING_VERSION { @@ -124,12 +124,12 @@ pub fn receive_velocity_plugin_response( )); } let addr = buf - .get_string() + .try_get_string() .map_err(|_| VelocityError::FailedReadAddress)?; let socket_addr: SocketAddr = SocketAddr::new( addr.parse::() - .map_err(|_| VelocityError::FailedParseAddres)?, + .map_err(|_| VelocityError::FailedParseAddress)?, port, ); let profile = read_game_profile(&mut buf)?; diff --git a/pumpkin/src/query.rs b/pumpkin/src/net/query.rs similarity index 89% rename from pumpkin/src/query.rs rename to pumpkin/src/net/query.rs index 39375ac6d..0968c58cd 100644 --- a/pumpkin/src/query.rs +++ b/pumpkin/src/net/query.rs @@ -27,16 +27,16 @@ pub async fn start_query_handler(server: Arc, bound_addr: SocketAddr) { .expect("Unable to bind to address"), ); - // Challange tokens are bound to the IP address and port - let valid_challange_tokens = Arc::new(RwLock::new(HashMap::new())); - let valid_challange_tokens_clone = valid_challange_tokens.clone(); - // All challange tokens ever created are expired every 30 seconds + // Challenge tokens are bound to the IP address and port + let valid_challenge_tokens = Arc::new(RwLock::new(HashMap::new())); + let valid_challenge_tokens_clone = valid_challenge_tokens.clone(); + // All challenge tokens ever created are expired every 30 seconds tokio::spawn(async move { let mut interval = time::interval(Duration::from_secs(30)); loop { interval.tick().await; - valid_challange_tokens_clone.write().await.clear(); + valid_challenge_tokens_clone.write().await.clear(); } }); @@ -49,7 +49,7 @@ pub async fn start_query_handler(server: Arc, bound_addr: SocketAddr) { loop { let socket = socket.clone(); - let valid_challange_tokens = valid_challange_tokens.clone(); + let valid_challenge_tokens = valid_challenge_tokens.clone(); let server = server.clone(); let mut buf = vec![0; 1024]; let (_, addr) = socket.recv_from(&mut buf).await.unwrap(); @@ -57,7 +57,7 @@ pub async fn start_query_handler(server: Arc, bound_addr: SocketAddr) { tokio::spawn(async move { if let Err(err) = handle_packet( buf, - valid_challange_tokens, + valid_challenge_tokens, server, socket, addr, @@ -87,10 +87,10 @@ async fn handle_packet( match raw_packet.packet_type { PacketType::Handshake => { if let Ok(packet) = SHandshake::decode(&mut raw_packet).await { - let challange_token = rand::thread_rng().gen_range(1..=i32::MAX); + let challenge_token = rand::thread_rng().gen_range(1..=i32::MAX); let response = CHandshake { session_id: packet.session_id, - challange_token, + challenge_token, }; // Ignore all errors since we don't want the query handler to crash @@ -99,7 +99,7 @@ async fn handle_packet( .send_to(response.encode().await.as_slice(), addr) .await; - clients.write().await.insert(challange_token, addr); + clients.write().await.insert(challenge_token, addr); } } PacketType::Status => { @@ -107,7 +107,7 @@ async fn handle_packet( if clients .read() .await - .get(&packet.challange_token) + .get(&packet.challenge_token) .is_some_and(|token_bound_ip: &SocketAddr| token_bound_ip == &addr) { if packet.is_full_request { @@ -150,7 +150,7 @@ async fn handle_packet( .send_to(response.encode().await.as_slice(), addr) .await; } else { - let resposne = CBasicStatus { + let response = CBasicStatus { session_id: packet.session_id, motd: CString::new(BASIC_CONFIG.motd.as_str())?, map: CString::new("world")?, @@ -161,7 +161,7 @@ async fn handle_packet( }; let _ = socket - .send_to(resposne.encode().await.as_slice(), addr) + .send_to(response.encode().await.as_slice(), addr) .await; } } diff --git a/pumpkin/src/rcon/mod.rs b/pumpkin/src/net/rcon/mod.rs similarity index 97% rename from pumpkin/src/rcon/mod.rs rename to pumpkin/src/net/rcon/mod.rs index 20bc12b03..1f36d827b 100644 --- a/pumpkin/src/rcon/mod.rs +++ b/pumpkin/src/net/rcon/mod.rs @@ -13,7 +13,6 @@ pub struct RCONServer; impl RCONServer { pub async fn new(config: &RCONConfig, server: Arc) -> Result { - assert!(config.enabled, "RCON is not enabled"); let listener = tokio::net::TcpListener::bind(config.address).await.unwrap(); let password = Arc::new(config.password.clone()); @@ -105,7 +104,7 @@ impl RCONClient { ServerboundPacket::ExecCommand => { if self.logged_in { let output = tokio::sync::Mutex::new(Vec::new()); - let dispatcher = server.command_dispatcher.clone(); + let dispatcher = server.command_dispatcher.read().await; dispatcher .handle_command( &mut crate::command::CommandSender::Rcon(&output), diff --git a/pumpkin/src/rcon/packet.rs b/pumpkin/src/net/rcon/packet.rs similarity index 100% rename from pumpkin/src/rcon/packet.rs rename to pumpkin/src/net/rcon/packet.rs diff --git a/pumpkin/src/server/connection_cache.rs b/pumpkin/src/server/connection_cache.rs index a8fa05d68..ccc8e9dd7 100644 --- a/pumpkin/src/server/connection_cache.rs +++ b/pumpkin/src/server/connection_cache.rs @@ -2,6 +2,7 @@ use core::error; use std::{ fs::File, io::{Cursor, Read}, + num::NonZeroU32, path::Path, }; @@ -9,7 +10,8 @@ use base64::{engine::general_purpose, Engine as _}; use pumpkin_config::{BasicConfiguration, BASIC_CONFIG}; use pumpkin_protocol::{ client::{config::CPluginMessage, status::CStatusResponse}, - Players, StatusResponse, VarInt, Version, CURRENT_MC_PROTOCOL, + codec::{var_int::VarInt, Codec}, + Players, StatusResponse, Version, CURRENT_MC_PROTOCOL, }; use super::CURRENT_MC_VERSION; @@ -24,6 +26,7 @@ fn load_icon_from_file>(path: P) -> Result Result> { + assert!(!png_data.is_empty(), "PNG data is empty"); let icon = png::Decoder::new(Cursor::new(&png_data)); let reader = icon.read_info()?; let info = reader.info(); @@ -60,14 +63,15 @@ impl CachedBranding { } fn build_brand() -> Vec { let brand = "Pumpkin"; - let mut buf = vec![]; - let _ = VarInt(brand.len() as i32).encode(&mut buf); + let mut buf = Vec::new(); + VarInt(brand.len() as i32).encode(&mut buf); buf.extend_from_slice(brand.as_bytes()); buf } } impl CachedStatus { + #[must_use] pub fn new() -> Self { let status_response = Self::build_response(&BASIC_CONFIG); let status_response_json = serde_json::to_string(&status_response) @@ -105,7 +109,7 @@ impl CachedStatus { } pub fn build_response(config: &BasicConfiguration) -> StatusResponse { - let icon = if config.use_favicon { + let favicon = if config.use_favicon { let icon_path = &config.favicon_path; log::debug!("Loading server favicon from '{}'", icon_path); match load_icon_from_file(icon_path).or_else(|err| { @@ -142,7 +146,7 @@ impl CachedStatus { StatusResponse { version: Some(Version { name: CURRENT_MC_VERSION.into(), - protocol: CURRENT_MC_PROTOCOL, + protocol: NonZeroU32::from(CURRENT_MC_PROTOCOL).get(), }), players: Some(Players { max: config.max_players, @@ -150,7 +154,7 @@ impl CachedStatus { sample: vec![], }), description: config.motd.clone(), - favicon: icon, + favicon, enforce_secure_chat: false, } } diff --git a/pumpkin/src/server/key_store.rs b/pumpkin/src/server/key_store.rs index daf7f734a..84d2c0d84 100644 --- a/pumpkin/src/server/key_store.rs +++ b/pumpkin/src/server/key_store.rs @@ -5,7 +5,7 @@ use rsa::{traits::PublicKeyParts as _, Pkcs1v15Encrypt, RsaPrivateKey}; use sha1::Sha1; use sha2::Digest; -use crate::client::EncryptionError; +use crate::net::EncryptionError; pub struct KeyStore { pub private_key: RsaPrivateKey, @@ -13,6 +13,7 @@ pub struct KeyStore { } impl KeyStore { + #[must_use] pub fn new() -> Self { log::debug!("Creating encryption keys..."); let private_key = Self::generate_private_key(); diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index b01d7d0c8..96090ae8d 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,6 +1,7 @@ use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; use pumpkin_config::BASIC_CONFIG; +use pumpkin_core::math::position::WorldPosition; use pumpkin_core::math::vector2::Vector2; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; @@ -9,9 +10,11 @@ use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_protocol::client::login::CEncryptionRequest; use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; use pumpkin_registry::{DimensionType, Registry}; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::dimension::Dimension; use rand::prelude::SliceRandom; use std::collections::HashMap; +use std::sync::atomic::AtomicU32; use std::{ sync::{ atomic::{AtomicI32, Ordering}, @@ -23,12 +26,12 @@ use tokio::sync::{Mutex, RwLock}; use crate::block::block_manager::BlockManager; use crate::block::default_block_manager; -use crate::client::EncryptionError; +use crate::net::EncryptionError; use crate::world::custom_bossbar::CustomBossbars; use crate::{ - client::Client, command::{default_dispatcher, dispatcher::CommandDispatcher}, entity::player::Player, + net::Client, world::World, }; @@ -47,7 +50,7 @@ pub struct Server { /// Saves server branding information. server_branding: CachedBranding, /// Saves and Dispatches commands to appropriate handlers. - pub command_dispatcher: Arc>, + pub command_dispatcher: RwLock, /// Saves and calls blocks blocks pub block_manager: Arc, /// Manages multiple worlds within the server. @@ -57,10 +60,13 @@ pub struct Server { /// Caches game registries for efficient access. pub cached_registry: Vec, /// Tracks open containers used for item interactions. + // TODO: should have per player open_containers pub open_containers: RwLock>, pub drag_handler: DragHandler, /// Assigns unique IDs to entities. entity_id: AtomicI32, + /// Assigns unique IDs to containers. + container_id: AtomicU32, /// Manages authentication with a authentication server, if enabled. pub auth_client: Option, /// The server's custom bossbars @@ -71,21 +77,15 @@ impl Server { #[allow(clippy::new_without_default)] #[must_use] pub fn new() -> Self { - // TODO: only create when needed - - let auth_client = if BASIC_CONFIG.online_mode { - Some( - reqwest::Client::builder() - .timeout(Duration::from_millis(5000)) - .build() - .expect("Failed to to make reqwest client"), - ) - } else { - None - }; + let auth_client = BASIC_CONFIG.online_mode.then(|| { + reqwest::Client::builder() + .timeout(Duration::from_millis(5000)) + .build() + .expect("Failed to to make reqwest client") + }); // First register default command, after that plugins can put in their own - let command_dispatcher = default_dispatcher(); + let command_dispatcher = RwLock::new(default_dispatcher()); let world = World::load( Dimension::OverWorld.into_level( @@ -108,6 +108,7 @@ impl Server { drag_handler: DragHandler::new(), // 0 is invalid entity_id: 2.into(), + container_id: 0.into(), worlds: vec![Arc::new(world)], dimensions: vec![ DimensionType::Overworld, @@ -180,6 +181,12 @@ impl Server { self.server_listing.lock().await.remove_player(); } + pub async fn save(&self) { + for world in &self.worlds { + world.save().await; + } + } + pub async fn try_get_container( &self, player_id: EntityId, @@ -192,6 +199,51 @@ impl Server { .cloned() } + /// Returns the first id with a matching location and block type. If this is used with unique + /// blocks, the output will return a random result. + pub async fn get_container_id(&self, location: WorldPosition, block: Block) -> Option { + let open_containers = self.open_containers.read().await; + // TODO: do better than brute force + for (id, container) in open_containers.iter() { + if container.is_location(location) { + if let Some(container_block) = container.get_block() { + if container_block.id == block.id { + log::debug!("Found container id: {}", id); + return Some(*id as u32); + } + } + } + } + + drop(open_containers); + + None + } + + pub async fn get_all_container_ids( + &self, + location: WorldPosition, + block: Block, + ) -> Option> { + let open_containers = self.open_containers.read().await; + let mut matching_container_ids: Vec = vec![]; + // TODO: do better than brute force + for (id, container) in open_containers.iter() { + if container.is_location(location) { + if let Some(container_block) = container.get_block() { + if container_block.id == block.id { + log::debug!("Found matching container id: {}", id); + matching_container_ids.push(*id as u32); + } + } + } + } + + drop(open_containers); + + Some(matching_container_ids) + } + /// Broadcasts a packet to all players in all worlds. /// /// This function sends the specified packet to every connected player in every world managed by the server. @@ -303,6 +355,11 @@ impl Server { self.entity_id.fetch_add(1, Ordering::SeqCst) } + /// Generates a new container id + pub fn new_container_id(&self) -> u32 { + self.container_id.fetch_add(1, Ordering::SeqCst) + } + pub fn get_branding(&self) -> CPluginMessage<'_> { self.server_branding.get_branding() } diff --git a/pumpkin/src/world/bossbar.rs b/pumpkin/src/world/bossbar.rs index fdc062438..dc9bb74d5 100644 --- a/pumpkin/src/world/bossbar.rs +++ b/pumpkin/src/world/bossbar.rs @@ -43,7 +43,7 @@ pub struct Bossbar { impl Bossbar { #[must_use] - pub fn new(title: String) -> Bossbar { + pub fn new(title: String) -> Self { let uuid = Uuid::new_v4(); Self { diff --git a/pumpkin/src/world/custom_bossbar.rs b/pumpkin/src/world/custom_bossbar.rs index 3f7542e62..99a7a0b86 100644 --- a/pumpkin/src/world/custom_bossbar.rs +++ b/pumpkin/src/world/custom_bossbar.rs @@ -53,7 +53,7 @@ impl Default for CustomBossbars { impl CustomBossbars { #[must_use] - pub fn new() -> CustomBossbars { + pub fn new() -> Self { Self { custom_bossbars: HashMap::new(), } @@ -189,7 +189,7 @@ impl CustomBossbars { )) } - pub async fn update_visibilty( + pub async fn update_visibility( &mut self, server: &Server, resource_location: String, @@ -365,7 +365,7 @@ impl CustomBossbars { ) -> Result<(), BossbarUpdateError> { let bossbar = self.custom_bossbars.get_mut(&resource_location); if let Some(bossbar) = bossbar { - // Get differnce between old and new player list and remove bossbars from old players + // Get difference between old and new player list and remove bossbars from old players let removed_players: Vec = bossbar .player .iter() diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index b36f51ce7..92239719b 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -9,14 +9,16 @@ use crate::{ error::PumpkinError, server::Server, }; -use itertools::Itertools; use level_time::LevelTime; use pumpkin_config::BasicConfiguration; use pumpkin_core::math::vector2::Vector2; use pumpkin_core::math::{position::WorldPosition, vector3::Vector3}; use pumpkin_core::text::{color::NamedColor, TextComponent}; use pumpkin_entity::{entity_type::EntityType, EntityId}; -use pumpkin_protocol::client::play::CLevelEvent; +use pumpkin_protocol::{ + client::play::CLevelEvent, + codec::{identifier::Identifier, var_int::VarInt}, +}; use pumpkin_protocol::{ client::play::{CBlockUpdate, CRespawn, CSoundEffect, CWorldEvent}, SoundCategory, @@ -26,7 +28,7 @@ use pumpkin_protocol::{ CChunkData, CGameEvent, CLogin, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo, CSetEntityMetadata, CSpawnEntity, GameEvent, Metadata, PlayerAction, }, - ClientPacket, VarInt, + ClientPacket, }; use pumpkin_registry::DimensionType; use pumpkin_world::chunk::ChunkData; @@ -116,6 +118,10 @@ impl World { } } + pub async fn save(&self) { + self.level.save().await; + } + /// Broadcasts a packet to all connected players within the world. /// /// Sends the specified packet to every player currently logged in to the world. @@ -150,16 +156,16 @@ impl World { &self, sound_id: u16, category: SoundCategory, - posistion: &Vector3, + position: &Vector3, ) { let seed = thread_rng().gen::(); self.broadcast_packet_all(&CSoundEffect::new( VarInt(i32::from(sound_id)), None, category, - posistion.x, - posistion.y, - posistion.z, + position.x, + position.y, + position.z, 1.0, 1.0, seed, @@ -167,6 +173,16 @@ impl World { .await; } + pub async fn play_block_sound(&self, sound_id: u16, position: WorldPosition) { + let new_vec = Vector3::new( + f64::from(position.0.x) + 0.5, + f64::from(position.0.y) + 0.5, + f64::from(position.0.z) + 0.5, + ); + self.play_sound(sound_id, SoundCategory::Blocks, &new_vec) + .await; + } + pub async fn play_record(&self, record_id: i32, position: WorldPosition) { self.broadcast_packet_all(&CLevelEvent::new(1010, position, record_id, false)) .await; @@ -213,12 +229,8 @@ impl World { player: Arc, server: &Server, ) { - let command_dispatcher = &server.command_dispatcher; - let dimensions = &server - .dimensions - .iter() - .map(DimensionType::name) - .collect_vec(); + let dimensions: Vec = + server.dimensions.iter().map(DimensionType::name).collect(); // This code follows the vanilla packet order let entity_id = player.entity_id(); @@ -235,10 +247,10 @@ impl World { .send_packet(&CLogin::new( entity_id, base_config.hardcore, - dimensions, + &dimensions, base_config.max_players.into(), - base_config.view_distance.into(), // TODO: view distance - base_config.simulation_distance.into(), // TODO: sim view dinstance + base_config.view_distance.get().into(), // TODO: view distance + base_config.simulation_distance.get().into(), // TODO: sim view dinstance false, true, false, @@ -257,8 +269,7 @@ impl World { .await; // permissions, i. e. the commands a player may use player.send_permission_lvl_update().await; - client_cmd_suggestions::send_c_commands_packet(&player, command_dispatcher).await; - + client_cmd_suggestions::send_c_commands_packet(&player, &server.command_dispatcher).await; // teleport let mut position = Vector3::new(10.0, 120.0, 10.0); let yaw = 10.0; @@ -319,7 +330,7 @@ impl World { .client .send_packet(&CPlayerInfoUpdate::new(0x01 | 0x08, &entries)) .await; - } + }; let gameprofile = &player.gameprofile; @@ -401,8 +412,11 @@ impl World { .init_client(&player.client) .await; + // Sends initial time + player.send_time(self).await; + // Spawn in initial chunks - player_chunker::player_join(self, player.clone()).await; + player_chunker::player_join(&player).await; // if let Some(bossbars) = self..lock().await.get_player_bars(&player.gameprofile.id) { // for bossbar in bossbars { @@ -442,7 +456,7 @@ impl World { .await; log::debug!("Sending player abilities to {}", player.gameprofile.name); - player.send_abilties_update().await; + player.send_abilities_update().await; player.send_permission_lvl_update().await; @@ -504,33 +518,13 @@ impl World { ) .await; - player_chunker::player_join(self, player.clone()).await; + player_chunker::player_join(player).await; self.broadcast_packet_all(&entity_metadata_packet).await; // update commands player.set_health(20.0, 20, 20.0).await; } - pub fn mark_chunks_as_not_watched(&self, chunks: &[Vector2]) -> Vec> { - self.level.mark_chunks_as_not_watched(chunks) - } - - pub fn mark_chunks_as_watched(&self, chunks: &[Vector2]) { - self.level.mark_chunks_as_newly_watched(chunks); - } - - pub fn clean_chunks(&self, chunks: &[Vector2]) { - self.level.clean_chunks(chunks); - } - - pub fn clean_memory(&self, chunks_to_check: &[Vector2]) { - self.level.clean_memory(chunks_to_check); - } - - pub fn get_cached_chunk_len(&self) -> usize { - self.level.loaded_chunk_count() - } - /// IMPORTANT: Chunks have to be non-empty fn spawn_world_chunks( &self, @@ -557,7 +551,6 @@ impl World { rel_x * rel_x + rel_z * rel_z }); - player.world().mark_chunks_as_watched(&chunks); let mut receiver = self.receive_chunks(chunks); let level = self.level.clone(); @@ -567,10 +560,9 @@ impl World { let packet = CChunkData(&chunk_data); #[cfg(debug_assertions)] if chunk_data.position == (0, 0).into() { - use pumpkin_protocol::bytebuf::ByteBuffer; - let mut test = ByteBuffer::empty(); + let mut test = bytes::BytesMut::new(); packet.write(&mut test); - let len = test.buf().len(); + let len = test.len(); log::debug!( "Chunk packet size: {}B {}KB {}MB", len, @@ -638,6 +630,68 @@ impl World { return self.current_players.lock().await.get(&id).cloned(); } + /// Gets a list of players who's location equals the given position in the world. + /// + /// It iterates through the players in the world and checks their location. If the player's location matches the + /// given position it will add this to a Vec which it later returns. If no + /// player was found in that position it will just return an empty Vec. + /// + /// # Arguments + /// + /// * `position`: The position the function will check. + pub async fn get_players_by_pos( + &self, + position: WorldPosition, + ) -> HashMap> { + self.current_players + .lock() + .await + .iter() + .filter_map(|(uuid, player)| { + let player_block_pos = player.living_entity.entity.block_pos.load().0; + (position.0.x == player_block_pos.x + && position.0.y == player_block_pos.y + && position.0.z == player_block_pos.z) + .then(|| (*uuid, Arc::clone(player))) + }) + .collect::>>() + } + + /// Gets the nearby players around a given world position + /// It "creates" a sphere and checks if whether players are inside + /// and returns a hashmap where the uuid is the key and the player + /// object the value. + /// + /// # Arguments + /// * `pos`: The middlepoint of the sphere + /// * `radius`: The radius of the sphere. The higher the radius + /// the more area will be checked, in every direction. + pub async fn get_nearby_players( + &self, + pos: Vector3, + radius: u16, + ) -> HashMap> { + let radius_squared = (f64::from(radius)).powi(2); + + let mut found_players = HashMap::new(); + for player in self.current_players.lock().await.iter() { + let player_pos = player.1.living_entity.entity.pos.load(); + + let diff = Vector3::new( + player_pos.x - pos.x, + player_pos.y - pos.y, + player_pos.z - pos.z, + ); + + let distance_squared = diff.x.powi(2) + diff.y.powi(2) + diff.z.powi(2); + if distance_squared <= radius_squared { + found_players.insert(*player.0, player.1.clone()); + } + } + + found_players + } + /// Adds a player to the world and broadcasts a join message if enabled. /// /// This function takes a player's UUID and an `Arc` reference. diff --git a/pumpkin/src/world/player_chunker.rs b/pumpkin/src/world/player_chunker.rs index 73f5c3246..98e1157d3 100644 --- a/pumpkin/src/world/player_chunker.rs +++ b/pumpkin/src/world/player_chunker.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{num::NonZeroU8, sync::Arc}; use pumpkin_config::BASIC_CONFIG; use pumpkin_core::{ @@ -10,29 +10,16 @@ use pumpkin_world::cylindrical_chunk_iterator::Cylindrical; use crate::entity::player::Player; -use super::World; - -pub async fn get_view_distance(player: &Player) -> u8 { - player - .config - .lock() - .await - .view_distance - .clamp(2, BASIC_CONFIG.view_distance) +pub async fn get_view_distance(player: &Player) -> NonZeroU8 { + player.config.lock().await.view_distance.clamp( + unsafe { NonZeroU8::new_unchecked(2) }, + BASIC_CONFIG.view_distance, + ) } -pub async fn player_join(world: &World, player: Arc) { - let new_watched = chunk_section_from_pos(&player.living_entity.entity.block_pos.load()); - - let mut cylindrical = player.watched_section.load(); - cylindrical.center = new_watched.into(); - player.watched_section.store(cylindrical); - +pub async fn player_join(player: &Arc) { let chunk_pos = player.living_entity.entity.chunk_pos.load(); - assert_eq!(new_watched.x, chunk_pos.x); - assert_eq!(new_watched.z, chunk_pos.z); - log::debug!("Sending center chunk to {}", player.gameprofile.name); player .client @@ -41,20 +28,15 @@ pub async fn player_join(world: &World, player: Arc) { chunk_z: chunk_pos.z.into(), }) .await; - let view_distance = get_view_distance(&player).await; + let view_distance = get_view_distance(player).await; log::debug!( "Player {} ({}) joined with view distance: {}", player.gameprofile.name, - player.gameprofile.name, + player.client.id, view_distance ); - let new_cylindrical = Cylindrical::new(chunk_pos, view_distance); - let loading_chunks = new_cylindrical.all_chunks_within(); - - if !loading_chunks.is_empty() { - world.spawn_world_chunks(player, loading_chunks, chunk_pos); - } + update_position(player).await; } pub async fn update_position(player: &Arc) { @@ -74,8 +56,6 @@ pub async fn update_position(player: &Arc) { let new_cylindrical = Cylindrical::new(new_chunk_center, view_distance); if old_cylindrical != new_cylindrical { - player.watched_section.store(new_cylindrical); - player .client .send_packet(&CCenterChunk { @@ -97,14 +77,21 @@ pub async fn update_position(player: &Arc) { }, ); - if !unloading_chunks.is_empty() { - //let inst = std::time::Instant::now(); + // Make sure the watched section and the chunk watcher updates are async atomic. We want to + // ensure what we unload when the player disconnects is correct + entity + .world + .level + .mark_chunks_as_newly_watched(&loading_chunks); + let chunks_to_clean = entity + .world + .level + .mark_chunks_as_not_watched(&unloading_chunks); + player.watched_section.store(new_cylindrical); - //log::debug!("Unloading chunks took {:?} (1)", inst.elapsed()); - let chunks_to_clean = entity.world.mark_chunks_as_not_watched(&unloading_chunks); - entity.world.clean_chunks(&chunks_to_clean); + if !chunks_to_clean.is_empty() { + entity.world.level.clean_chunks(&chunks_to_clean); - //log::debug!("Unloading chunks took {:?} (2)", inst.elapsed()); // This can take a little if we are sending a bunch of packets, queue it up :p let client = player.client.clone(); tokio::spawn(async move { @@ -118,22 +105,12 @@ pub async fn update_position(player: &Arc) { .await; } }); - //log::debug!("Unloading chunks took {:?} (3)", inst.elapsed()); } if !loading_chunks.is_empty() { - //let inst = std::time::Instant::now(); - - // loading_chunks.sort_by(|a, b| { - // let distance_a_squared = a.sub(a).length_squared(); - // let distance_b_squared = b.sub(a).length_squared(); - // distance_a_squared.cmp(&distance_b_squared) - // }); - entity .world .spawn_world_chunks(player.clone(), loading_chunks, new_chunk_center); - //log::debug!("Loading chunks took {:?}", inst.elapsed()); } } } diff --git a/pumpkin/src/world/scoreboard.rs b/pumpkin/src/world/scoreboard.rs index 61279eed4..16a3e3295 100644 --- a/pumpkin/src/world/scoreboard.rs +++ b/pumpkin/src/world/scoreboard.rs @@ -3,7 +3,8 @@ use std::collections::HashMap; use pumpkin_core::text::TextComponent; use pumpkin_protocol::{ client::play::{CDisplayObjective, CUpdateObjectives, CUpdateScore, RenderType}, - NumberFormat, VarInt, + codec::var_int::VarInt, + NumberFormat, }; use super::World; diff --git a/pumpkin/src/world/worldborder.rs b/pumpkin/src/world/worldborder.rs index 6402a3f92..3426d6c70 100644 --- a/pumpkin/src/world/worldborder.rs +++ b/pumpkin/src/world/worldborder.rs @@ -3,7 +3,7 @@ use pumpkin_protocol::client::play::{ CSetBorderWarningDelay, CSetBorderWarningDistance, }; -use crate::client::Client; +use crate::net::Client; use super::World;