diff --git a/README.md b/README.md index 7e4df0ef..e6259515 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) @@ -79,6 +79,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi - [x] Particles - [x] Chat - [x] Commands + - [x] OP Permission - Proxy - [x] Bungeecord - [x] Velocity @@ -87,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 @@ -95,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 00000000..ac4995f5 --- /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/docker-compose.yml b/docker-compose.yml index a8c019a0..418bdac0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,8 @@ services: pumpkin: build: . ports: - - 25565:25565 + - "25565:25565" volumes: - ./data:/pumpkin + stdin_open: true + tty: true diff --git a/extractor/gradle.properties b/extractor/gradle.properties index 5b4def5d..880c9772 100644 --- a/extractor/gradle.properties +++ b/extractor/gradle.properties @@ -11,4 +11,4 @@ kotlin_loader_version=1.13.0+kotlin.2.1.0 mod_version=1.0-SNAPSHOT maven_group=de.snowii archives_base_name=extractor -fabric_version=0.112.1+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 e2847c82..cea7a793 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 f5feea6d..057afac5 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 9b42019c..6a90cee9 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 d16e89b6..2f47fe63 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 00000000..47504711 --- /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 ac81ec76..e75a1797 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/Cargo.toml b/pumpkin-config/Cargo.toml index d3ce22e4..c9bf9ba8 100644 --- a/pumpkin-config/Cargo.toml +++ b/pumpkin-config/Cargo.toml @@ -7,5 +7,6 @@ edition.workspace = true pumpkin-core = { path = "../pumpkin-core" } serde.workspace = true log.workspace = true +uuid.workspace = true toml = "0.8" diff --git a/pumpkin-config/src/commands.rs b/pumpkin-config/src/commands.rs index 23aa2711..bf686b0f 100644 --- a/pumpkin-config/src/commands.rs +++ b/pumpkin-config/src/commands.rs @@ -1,3 +1,4 @@ +use pumpkin_core::PermissionLvl; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize)] @@ -7,6 +8,8 @@ pub struct CommandsConfig { pub use_console: bool, /// Should be commands from players be logged in console? pub log_console: bool, // TODO: commands... + /// The op permission level of everyone that is not in the ops file + pub default_op_level: PermissionLvl, } impl Default for CommandsConfig { @@ -14,6 +17,7 @@ impl Default for CommandsConfig { Self { use_console: true, log_console: true, + default_op_level: PermissionLvl::Zero, } } } diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index 5e82ae33..94feda0b 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -1,6 +1,6 @@ use log::warn; use logging::LoggingConfig; -use pumpkin_core::{Difficulty, GameMode}; +use pumpkin_core::{Difficulty, GameMode, PermissionLvl}; use query::QueryConfig; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -29,6 +29,7 @@ pub use server_links::ServerLinksConfig; mod commands; pub mod compression; mod lan_broadcast; +pub mod op; mod pvp; mod rcon; mod server_links; @@ -79,6 +80,8 @@ pub struct BasicConfiguration { pub simulation_distance: NonZeroU8, /// The default game difficulty. pub default_difficulty: Difficulty, + /// The op level assign by the /op command + pub op_permission_level: PermissionLvl, /// Whether the Nether dimension is enabled. pub allow_nether: bool, /// Whether the server is in hardcore mode. @@ -109,6 +112,7 @@ impl Default for BasicConfiguration { view_distance: NonZeroU8::new(10).unwrap(), simulation_distance: NonZeroU8::new(10).unwrap(), default_difficulty: Difficulty::Normal, + op_permission_level: PermissionLvl::Four, allow_nether: true, hardcore: false, online_mode: true, diff --git a/pumpkin-config/src/op.rs b/pumpkin-config/src/op.rs new file mode 100644 index 00000000..edbd71bc --- /dev/null +++ b/pumpkin-config/src/op.rs @@ -0,0 +1,27 @@ +use pumpkin_core::permission::PermissionLvl; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct Op { + pub uuid: Uuid, + pub name: String, + pub level: PermissionLvl, + pub bypasses_player_limit: bool, +} + +impl Op { + pub fn new( + uuid: Uuid, + name: String, + level: PermissionLvl, + bypasses_player_limit: bool, + ) -> Self { + Self { + uuid, + name, + level, + bypasses_player_limit, + } + } +} diff --git a/pumpkin-core/src/lib.rs b/pumpkin-core/src/lib.rs index 8ffd25b2..8116211b 100644 --- a/pumpkin-core/src/lib.rs +++ b/pumpkin-core/src/lib.rs @@ -1,9 +1,11 @@ pub mod gamemode; pub mod math; +pub mod permission; pub mod random; pub mod text; pub use gamemode::GameMode; +pub use permission::PermissionLvl; use serde::{Deserialize, Serialize}; diff --git a/pumpkin-core/src/permission.rs b/pumpkin-core/src/permission.rs new file mode 100644 index 00000000..7229cbcb --- /dev/null +++ b/pumpkin-core/src/permission.rs @@ -0,0 +1,56 @@ +use num_derive::{FromPrimitive, ToPrimitive}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// Represents the player's permission level +/// +/// Permission levels determine the player's access to commands and server operations. +/// Each numeric level corresponds to a specific role: +/// - `Zero`: `normal`: Player can use basic commands. +/// - `One`: `moderator`: Player can bypass spawn protection. +/// - `Two`: `gamemaster`: Player or executor can use more commands and player can use command blocks. +/// - `Three`: `admin`: Player or executor can use commands related to multiplayer management. +/// - `Four`: `owner`: Player or executor can use all of the commands, including commands related to server management. +#[derive(FromPrimitive, ToPrimitive, Clone, Copy, Default, PartialEq, Eq)] +#[repr(i8)] +pub enum PermissionLvl { + #[default] + Zero = 0, + One = 1, + Two = 2, + Three = 3, + Four = 4, +} + +impl PartialOrd for PermissionLvl { + fn partial_cmp(&self, other: &Self) -> Option { + (*self as u8).partial_cmp(&(*other as u8)) + } +} + +impl Serialize for PermissionLvl { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + serializer.serialize_u8(*self as u8) + } +} + +impl<'de> Deserialize<'de> for PermissionLvl { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = u8::deserialize(deserializer)?; + match value { + 0 => Ok(PermissionLvl::Zero), + 2 => Ok(PermissionLvl::Two), + 3 => Ok(PermissionLvl::Three), + 4 => Ok(PermissionLvl::Four), + _ => Err(serde::de::Error::custom(format!( + "Invalid value for OpLevel: {}", + value + ))), + } + } +} diff --git a/pumpkin-core/src/text/click.rs b/pumpkin-core/src/text/click.rs index 0e917e15..caa78f61 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-entity/src/entity_type.rs b/pumpkin-entity/src/entity_type.rs index 282ac129..e065722f 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-nbt/src/deserializer.rs b/pumpkin-nbt/src/deserializer.rs index ae017b82..80e3b96f 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 b2be0d65..5ed48da9 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 674b290e..f04d7731 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -12,7 +12,6 @@ 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" } @@ -31,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/client/play/c_block_event.rs b/pumpkin-protocol/src/client/play/c_block_event.rs new file mode 100644 index 00000000..c1b2e6e0 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_block_event.rs @@ -0,0 +1,31 @@ +use pumpkin_core::math::position::WorldPosition; + +use pumpkin_macros::client_packet; +use serde::Serialize; + +use crate::VarInt; + +#[derive(Serialize)] +#[client_packet("play:block_event")] +pub struct CBlockAction<'a> { + location: &'a WorldPosition, + action_id: u8, + action_parameter: u8, + block_type: VarInt, +} + +impl<'a> CBlockAction<'a> { + pub fn new( + location: &'a WorldPosition, + action_id: u8, + action_parameter: u8, + block_type: VarInt, + ) -> Self { + Self { + location, + action_id, + action_parameter, + block_type, + } + } +} diff --git a/pumpkin-protocol/src/client/play/c_command_suggestions.rs b/pumpkin-protocol/src/client/play/c_command_suggestions.rs index 25ae6683..c137b9ad 100644 --- a/pumpkin-protocol/src/client/play/c_command_suggestions.rs +++ b/pumpkin-protocol/src/client/play/c_command_suggestions.rs @@ -35,7 +35,7 @@ impl ClientPacket for CCommandSuggestions<'_> { 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()); @@ -46,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/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 79f7e3ea..68a7cc57 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -2,6 +2,7 @@ mod bossevent_action; mod c_acknowledge_block; mod c_actionbar; mod c_block_destroy_stage; +mod c_block_event; mod c_block_update; mod c_boss_event; mod c_center_chunk; @@ -72,6 +73,7 @@ pub use bossevent_action::*; pub use c_acknowledge_block::*; pub use c_actionbar::*; pub use c_block_destroy_stage::*; +pub use c_block_event::*; pub use c_block_update::*; pub use c_boss_event::*; pub use c_center_chunk::*; diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index 8ef37f7a..fec62014 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -25,6 +25,21 @@ pub const MAX_PACKET_SIZE: i32 = 2097152; pub type FixedBitSet = bytes::Bytes; +/// 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 { HandShake, diff --git a/pumpkin-protocol/src/packet_decoder.rs b/pumpkin-protocol/src/packet_decoder.rs index 5059a2c5..89ec4f42 100644 --- a/pumpkin-protocol/src/packet_decoder.rs +++ b/pumpkin-protocol/src/packet_decoder.rs @@ -17,8 +17,7 @@ pub struct PacketDecoder { buf: BytesMut, decompress_buf: BytesMut, cipher: Option, - compression: bool, - decompressor: Decompressor, + decompressor: Option, } // Manual implementation of Default trait for PacketDecoder @@ -29,8 +28,7 @@ impl Default for PacketDecoder { buf: BytesMut::new(), decompress_buf: BytesMut::new(), cipher: None, - compression: false, - decompressor: Decompressor::new(), + decompressor: None, } } } @@ -58,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) @@ -77,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)?; @@ -135,7 +132,11 @@ impl PacketDecoder { /// 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]) { diff --git a/pumpkin-protocol/src/packet_encoder.rs b/pumpkin-protocol/src/packet_encoder.rs index 1636d56e..006e1a99 100644 --- a/pumpkin-protocol/src/packet_encoder.rs +++ b/pumpkin-protocol/src/packet_encoder.rs @@ -1,11 +1,12 @@ 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::{codec::Codec, ClientPacket, VarInt, MAX_PACKET_SIZE}; +use crate::{ + codec::Codec, ClientPacket, CompressionLevel, CompressionThreshold, VarInt, MAX_PACKET_SIZE, +}; type Cipher = cfb8::Encryptor; @@ -16,8 +17,8 @@ pub struct PacketEncoder { buf: BytesMut, compress_buf: Vec, cipher: Option, - compression_threshold: Option, - compressor: Compressor, // Reuse the compressor for all packets + // compression and compression threshold + compression: Option<(Compressor, CompressionThreshold)>, } // Manual implementation of Default trait for PacketEncoder @@ -28,8 +29,7 @@ impl Default for PacketEncoder { buf: BytesMut::with_capacity(1024), compress_buf: Vec::with_capacity(1024), cipher: None, - compression_threshold: None, - compressor: Compressor::new(CompressionLvl::fastest()), // init compressor with fastest compression level + compression: None, // init compressor with fastest compression level } } } @@ -73,8 +73,8 @@ impl PacketEncoder { packet.write(&mut self.buf); let data_len = self.buf.len() - start_len; - if let Some(compression_threshold) = self.compression_threshold { - 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..]; @@ -82,15 +82,13 @@ 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(|e| PacketEncodeError::CompressionFailed(e.to_string()))?; @@ -166,26 +164,30 @@ impl PacketEncoder { } } - /// Enables or disables Zlib compression with the given options. + /// Enables or disables Zlib compression. /// - /// If `compression` is `Some`, compression is enabled with the provided - /// options. If `compression` is `None`, compression is disabled. - pub fn set_compression(&mut self, compression: Option) { - // Reset the compressor with the new compression level - if let Some(compression) = &compression { - self.compression_threshold = Some(compression.threshold); - let compression_level = compression.level as i32; - let level = match CompressionLvl::new(compression_level) { - Ok(level) => level, - Err(error) => { - log::error!("Invalid compression level {:?}", error); - return; - } - }; - self.compressor = Compressor::new(level); - } else { - self.compression_threshold = None; + /// 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`. @@ -209,6 +211,10 @@ impl PacketEncoder { } } +#[derive(Error, Debug)] +#[error("Invalid compression Level")] +pub struct CompressionLevelError; + /// Errors that can occur during packet encoding. #[derive(Error, Debug)] pub enum PacketEncodeError { @@ -268,15 +274,15 @@ mod tests { /// 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 { @@ -328,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[..]; @@ -418,18 +422,16 @@ 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 mut 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 decrypt_aes128(&mut packet_bytes, &key, &key); @@ -567,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[..]; diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 495ee3ff..1c7eacd8 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -25,6 +25,8 @@ dashmap = "6.1.0" flate2 = "1.0" lz4 = "1.28.0" +file-guard = "0.2.0" + enum_dispatch = "0.3.13" fastnbt = { git = "https://github.com/owengage/fastnbt.git" } diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index 930363ec..ede2d616 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -14,6 +14,7 @@ use crate::{ anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError, }, generation::{get_world_gen, Seed, WorldGenerator}, + lock::{anvil::AnvilLevelLocker, LevelLocker}, world_info::{anvil::AnvilLevelInfo, LevelData, WorldInfoReader, WorldInfoWriter}, }; @@ -35,6 +36,9 @@ pub struct Level { chunk_watchers: Arc, usize>>, chunk_reader: Arc, world_gen: Arc, + // Gets unlocked when dropped + // TODO: Make this a trait + _locker: Arc, } #[derive(Clone)] @@ -55,6 +59,10 @@ impl Level { region_folder, }; + // 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) @@ -71,6 +79,7 @@ impl Level { loaded_chunks: Arc::new(DashMap::new()), chunk_watchers: Arc::new(DashMap::new()), level_info, + _locker: Arc::new(locker), } } diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs index 8e26b106..ec23b151 100644 --- a/pumpkin-world/src/lib.rs +++ b/pumpkin-world/src/lib.rs @@ -16,6 +16,7 @@ pub mod dimension; mod generation; pub mod item; pub mod level; +mod lock; pub mod world_info; pub const WORLD_HEIGHT: usize = 384; pub const WORLD_LOWEST_Y: i16 = -64; diff --git a/pumpkin-world/src/lock/anvil.rs b/pumpkin-world/src/lock/anvil.rs new file mode 100644 index 00000000..7864cf71 --- /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 00000000..bf4b262f --- /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/Cargo.toml b/pumpkin/Cargo.toml index a3f4a14e..87b03020 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -46,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", diff --git a/pumpkin/src/block/blocks/chest.rs b/pumpkin/src/block/blocks/chest.rs index eca901c0..0809038e 100644 --- a/pumpkin/src/block/blocks/chest.rs +++ b/pumpkin/src/block/blocks/chest.rs @@ -2,7 +2,12 @@ use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_inventory::Chest; use pumpkin_macros::{pumpkin_block, sound}; -use pumpkin_world::{block::block_registry::Block, item::item_registry::Item}; +use pumpkin_protocol::client::play::CBlockAction; +use pumpkin_protocol::codec::var_int::VarInt; +use pumpkin_world::{ + block::block_registry::{get_block, Block}, + item::item_registry::Item, +}; use crate::block::container::ContainerBlock; use crate::{ @@ -10,7 +15,6 @@ use crate::{ entity::player::Player, server::Server, }; - #[pumpkin_block("minecraft:chest")] pub struct ChestBlock; @@ -24,10 +28,7 @@ impl PumpkinBlock for ChestBlock { server: &Server, ) { self.open(block, player, location, server).await; - player - .world() - .play_block_sound(sound!("block.chest.open"), location) - .await; + self.play_chest_action(player, location, server).await; } async fn on_use_with_item<'a>( @@ -54,13 +55,49 @@ impl PumpkinBlock for ChestBlock { async fn on_close<'a>(&self, player: &Player, location: WorldPosition, server: &Server) { self.close(location, server, player).await; - player - .world() - .play_block_sound(sound!("block.chest.close"), location) - .await; - // TODO: send entity updates close + self.play_chest_action(player, location, server).await; + } +} + +impl ChestBlock { + pub async fn play_chest_action( + &self, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + let num_players = server + .open_containers + .read() + .await + .get_by_location(&location) + .map(|container| container.all_player_ids().len()) + .unwrap_or_default(); + if num_players == 0 { + player + .world() + .play_block_sound(sound!("block.chest.close"), location) + .await; + } else if num_players == 1 { + player + .world() + .play_block_sound(sound!("block.chest.open"), location) + .await; + } + + if let Some(e) = get_block("minecraft:chest").cloned() { + server + .broadcast_packet_all(&CBlockAction::new( + &location, + 1, + num_players as u8, + VarInt(e.id.into()), + )) + .await; + } } } + impl ContainerBlock for ChestBlock { const UNIQUE: bool = false; } diff --git a/pumpkin/src/command/args/arg_block.rs b/pumpkin/src/command/args/arg_block.rs index fe11228f..77962552 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,19 +51,15 @@ 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)) => block_registry::get_block(name).map_or_else( || { diff --git a/pumpkin/src/command/args/arg_bool.rs b/pumpkin/src/command/args/arg_bool.rs index fc47f590..39b69c89 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 94df5a4c..664264ea 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 6111688b..6d3b2d44 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 41410367..0cd20af2 100644 --- a/pumpkin/src/command/args/arg_bounded_num.rs +++ b/pumpkin/src/command/args/arg_bounded_num.rs @@ -25,7 +25,7 @@ where 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, @@ -238,12 +238,8 @@ impl DefaultNameArgConsumer for BoundedNumArgumentConsumer where 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 9055fde1..a8ea8234 100644 --- a/pumpkin/src/command/args/arg_command.rs +++ b/pumpkin/src/command/args/arg_command.rs @@ -30,7 +30,7 @@ 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>, @@ -40,11 +40,11 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer { let dispatcher = server.command_dispatcher.read().await; dispatcher .get_tree(s) - .map_or_else(|_| None, |tree| Some(Arg::CommandTree(tree))) + .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, @@ -58,26 +58,22 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer { .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 { - &Self + 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 102a177b..0c202ac7 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 { - &Self + 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 37e4dac0..bbbed190 100644 --- a/pumpkin/src/command/args/arg_entity.rs +++ b/pumpkin/src/command/args/arg_entity.rs @@ -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 { - &Self + 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 d520b25c..fee1264d 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 { - &Self + 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 481feb7d..bf4254b9 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,19 +49,15 @@ 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)) => item_registry::get_item(name).map_or_else( || { diff --git a/pumpkin/src/command/args/arg_message.rs b/pumpkin/src/command/args/arg_message.rs index 00af2d87..8d4081d6 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 { - &Self + 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 0e8876a7..c7fdc70a 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>, @@ -63,7 +63,7 @@ impl ArgumentConsumer for PlayersArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -73,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 { - &Self + 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 107ea432..9a9c5b56 100644 --- a/pumpkin/src/command/args/arg_position_2d.rs +++ b/pumpkin/src/command/args/arg_position_2d.rs @@ -32,7 +32,7 @@ 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>, @@ -45,7 +45,7 @@ impl ArgumentConsumer for Position2DArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -73,19 +73,15 @@ impl MaybeRelativePosition2D { } impl DefaultNameArgConsumer for Position2DArgumentConsumer { - fn default_name(&self) -> &'static str { - "pos2d" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Self + 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 1e58ad81..8df9761c 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 { - &Self + 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_position_block.rs b/pumpkin/src/command/args/arg_position_block.rs index 861eec12..4f5b3a40 100644 --- a/pumpkin/src/command/args/arg_position_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 { - &Self + 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 02781593..c156a57f 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 6e53f2f9..91b3363b 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 { - &Self + 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 1412d63e..67207fb8 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 8342386b..f1fa8f85 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -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 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(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 7c8c7b2f..707dfcf5 100644 --- a/pumpkin/src/command/client_cmd_suggestions.rs +++ b/pumpkin/src/command/client_cmd_suggestions.rs @@ -10,10 +10,7 @@ use super::{ tree::{Node, NodeType}, }; -pub async fn send_c_commands_packet<'a>( - player: &Arc, - dispatcher: &RwLock>, -) { +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(); @@ -74,7 +71,7 @@ impl<'a> ProtoNodeBuilder<'a> { fn nodes_to_proto_node_builders<'a>( cmd_src: &super::CommandSender, - nodes: &[Node<'a>], + nodes: &'a [Node], children: &[usize], ) -> (bool, Vec>) { let mut child_nodes = Vec::new(); @@ -82,7 +79,7 @@ fn nodes_to_proto_node_builders<'a>( 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 9caaea21..25e449a7 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(()); @@ -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 07379587..b948d0df 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 1a01fb15..4f5e3d06 100644 --- a/pumpkin/src/command/commands/cmd_fill.rs +++ b/pumpkin/src/command/commands/cmd_fill.rs @@ -4,10 +4,11 @@ use crate::command::args::{ConsumedArgs, FindArg}; use crate::command::tree::CommandTree; use crate::command::tree_builder::{argument, literal, require}; use crate::command::{CommandError, CommandExecutor, CommandSender}; -use crate::entity::player::PermissionLvl; + use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_core::math::vector3::Vector3; +use pumpkin_core::permission::PermissionLvl; use pumpkin_core::text::TextComponent; const NAMES: [&str; 1] = ["fill"]; @@ -154,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 faab9537..1b586130 100644 --- a/pumpkin/src/command/commands/cmd_gamemode.rs +++ b/pumpkin/src/command/commands/cmd_gamemode.rs @@ -3,8 +3,8 @@ use async_trait::async_trait; use crate::command::args::arg_gamemode::GamemodeArgumentConsumer; use crate::command::args::GetCloned; -use crate::entity::player::PermissionLvl; use crate::TextComponent; +use pumpkin_core::permission::PermissionLvl; use crate::command::args::arg_players::PlayersArgumentConsumer; @@ -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 36edb640..d3f91baf 100644 --- a/pumpkin/src/command/commands/cmd_give.rs +++ b/pumpkin/src/command/commands/cmd_give.rs @@ -9,7 +9,7 @@ use crate::command::args::{ConsumedArgs, FindArg, FindArgDefaultName}; use crate::command::tree::CommandTree; use crate::command::tree_builder::{argument, argument_default_name, require}; use crate::command::{CommandError, CommandExecutor, CommandSender}; -use crate::entity::player::PermissionLvl; +use pumpkin_core::permission::PermissionLvl; const NAMES: [&str; 1] = ["give"]; @@ -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 e382febf..c0ab3eb7 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(())) => { @@ -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_kick.rs b/pumpkin/src/command/commands/cmd_kick.rs index d0a8eec9..51832258 100644 --- a/pumpkin/src/command/commands/cmd_kick.rs +++ b/pumpkin/src/command/commands/cmd_kick.rs @@ -49,7 +49,8 @@ impl CommandExecutor for KickExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +// TODO: Permission +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child(argument(ARG_TARGET, &PlayersArgumentConsumer).execute(&KickExecutor)) + .with_child(argument(ARG_TARGET, PlayersArgumentConsumer).execute(KickExecutor)) } diff --git a/pumpkin/src/command/commands/cmd_kill.rs b/pumpkin/src/command/commands/cmd_kill.rs index d763054b..2b36b0c4 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 5a81f8f9..bbcc3641 100644 --- a/pumpkin/src/command/commands/cmd_list.rs +++ b/pumpkin/src/command/commands/cmd_list.rs @@ -55,6 +55,6 @@ fn get_player_names(players: Vec>) -> String { names } -pub fn init_command_tree<'a>() -> CommandTree<'a> { - CommandTree::new(NAMES, DESCRIPTION).execute(&ListExecutor) +pub fn init_command_tree() -> CommandTree { + CommandTree::new(NAMES, DESCRIPTION).execute(ListExecutor) } diff --git a/pumpkin/src/command/commands/cmd_op.rs b/pumpkin/src/command/commands/cmd_op.rs new file mode 100644 index 00000000..614ffcc7 --- /dev/null +++ b/pumpkin/src/command/commands/cmd_op.rs @@ -0,0 +1,80 @@ +use crate::{ + command::{ + args::{arg_players::PlayersArgumentConsumer, Arg, ConsumedArgs}, + tree::CommandTree, + tree_builder::{argument, require}, + CommandError, CommandExecutor, CommandSender, + }, + data::{op_data::OPERATOR_CONFIG, SaveJSONConfiguration}, +}; +use async_trait::async_trait; +use pumpkin_config::{op::Op, BASIC_CONFIG}; +use pumpkin_core::permission::PermissionLvl; +use pumpkin_core::text::TextComponent; +use CommandError::InvalidConsumption; + +const NAMES: [&str; 1] = ["op"]; +const DESCRIPTION: &str = "Grants operator status to a player."; +const ARG_TARGET: &str = "player"; + +struct OpExecutor; + +#[async_trait] +impl CommandExecutor for OpExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let mut config = OPERATOR_CONFIG.write().await; + + let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else { + return Err(InvalidConsumption(Some(ARG_TARGET.into()))); + }; + + // log each player to the console. + for player in targets { + let new_level = if BASIC_CONFIG.op_permission_level > sender.permission_lvl() { + sender.permission_lvl() + } else { + BASIC_CONFIG.op_permission_level + }; + + let op_entry = Op::new( + player.gameprofile.id, + player.gameprofile.name.clone(), + new_level, + false, + ); + if let Some(op) = config + .ops + .iter_mut() + .find(|o| o.uuid == player.gameprofile.id) + { + op.level = new_level; + } else { + config.ops.push(op_entry); + } + config.save(); + + player + .set_permission_lvl(new_level, &server.command_dispatcher) + .await; + + let player_name = player.gameprofile.name.clone(); + let message = format!("Made {player_name} a server operator."); + let msg = TextComponent::text(&message); + sender.send_message(msg).await; + } + + Ok(()) + } +} + +pub fn init_command_tree() -> CommandTree { + CommandTree::new(NAMES, DESCRIPTION).with_child( + require(|sender| sender.has_permission_lvl(PermissionLvl::Three)) + .with_child(argument(ARG_TARGET, PlayersArgumentConsumer).execute(OpExecutor)), + ) +} diff --git a/pumpkin/src/command/commands/cmd_pumpkin.rs b/pumpkin/src/command/commands/cmd_pumpkin.rs index 3dad98ec..a76a1be9 100644 --- a/pumpkin/src/command/commands/cmd_pumpkin.rs +++ b/pumpkin/src/command/commands/cmd_pumpkin.rs @@ -92,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 66b74338..ffeb9c88 100644 --- a/pumpkin/src/command/commands/cmd_say.rs +++ b/pumpkin/src/command/commands/cmd_say.rs @@ -2,15 +2,13 @@ use async_trait::async_trait; use pumpkin_core::text::TextComponent; use pumpkin_protocol::client::play::CSystemChatMessage; -use crate::{ - command::{ - args::{arg_message::MsgArgConsumer, Arg, ConsumedArgs}, - tree::CommandTree, - tree_builder::{argument, require}, - CommandError, CommandExecutor, CommandSender, - }, - entity::player::PermissionLvl, +use crate::command::{ + args::{arg_message::MsgArgConsumer, Arg, ConsumedArgs}, + tree::CommandTree, + tree_builder::{argument, require}, + CommandError, CommandExecutor, CommandSender, }; +use pumpkin_core::permission::PermissionLvl; use CommandError::InvalidConsumption; const NAMES: [&str; 1] = ["say"]; @@ -43,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 fb93acb0..50fe6ecd 100644 --- a/pumpkin/src/command/commands/cmd_seed.rs +++ b/pumpkin/src/command/commands/cmd_seed.rs @@ -2,8 +2,8 @@ use crate::command::tree_builder::require; use crate::command::{ args::ConsumedArgs, tree::CommandTree, CommandError, CommandExecutor, CommandSender, }; -use crate::entity::player::PermissionLvl; use async_trait::async_trait; +use pumpkin_core::permission::PermissionLvl; use pumpkin_core::text::click::ClickEvent; use pumpkin_core::text::hover::HoverEvent; use pumpkin_core::text::{color::NamedColor, TextComponent}; @@ -54,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 0dd6ec9f..82e81392 100644 --- a/pumpkin/src/command/commands/cmd_setblock.rs +++ b/pumpkin/src/command/commands/cmd_setblock.rs @@ -8,7 +8,7 @@ use crate::command::args::{ConsumedArgs, FindArg}; use crate::command::tree::CommandTree; use crate::command::tree_builder::{argument, literal, require}; use crate::command::{CommandError, CommandExecutor, CommandSender}; -use crate::entity::player::PermissionLvl; +use pumpkin_core::permission::PermissionLvl; const NAMES: [&str; 1] = ["setblock"]; @@ -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 de7803b1..6368988c 100644 --- a/pumpkin/src/command/commands/cmd_stop.rs +++ b/pumpkin/src/command/commands/cmd_stop.rs @@ -6,7 +6,7 @@ use crate::command::args::ConsumedArgs; use crate::command::tree::CommandTree; use crate::command::tree_builder::require; use crate::command::{CommandError, CommandExecutor, CommandSender}; -use crate::entity::player::PermissionLvl; +use pumpkin_core::permission::PermissionLvl; const NAMES: [&str; 1] = ["stop"]; @@ -37,8 +37,8 @@ impl CommandExecutor for StopExecutor { } } -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 3bc4cf08..59ccc960 100644 --- a/pumpkin/src/command/commands/cmd_teleport.rs +++ b/pumpkin/src/command/commands/cmd_teleport.rs @@ -12,7 +12,7 @@ use crate::command::tree::CommandTree; use crate::command::tree_builder::{argument, literal, require}; use crate::command::CommandError; use crate::command::{CommandExecutor, CommandSender}; -use crate::entity::player::PermissionLvl; +use pumpkin_core::permission::PermissionLvl; const NAMES: [&str; 2] = ["teleport", "tp"]; const DESCRIPTION: &str = "Teleports entities, including players."; // todo @@ -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 ad289401..9cc45ec6 100644 --- a/pumpkin/src/command/commands/cmd_time.rs +++ b/pumpkin/src/command/commands/cmd_time.rs @@ -5,23 +5,23 @@ use pumpkin_core::text::TextComponent; use crate::command::args::arg_bounded_num::BoundedNumArgumentConsumer; use crate::command::args::FindArgDefaultName; use crate::command::tree_builder::{argument_default_name, literal}; -use crate::{ - command::{ - tree::CommandTree, tree_builder::require, CommandError, CommandExecutor, CommandSender, - ConsumedArgs, - }, - entity::player::PermissionLvl, +use crate::command::{ + tree::CommandTree, tree_builder::require, CommandError, CommandExecutor, CommandSender, + ConsumedArgs, }; +use pumpkin_core::permission::PermissionLvl; 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 { @@ -83,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(())) => { @@ -124,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 7660b808..8aa41251 100644 --- a/pumpkin/src/command/commands/cmd_transfer.rs +++ b/pumpkin/src/command/commands/cmd_transfer.rs @@ -13,7 +13,7 @@ use crate::command::tree_builder::{argument, argument_default_name, require}; use crate::command::{ args::ConsumedArgs, tree::CommandTree, CommandError, CommandExecutor, CommandSender, }; -use crate::entity::player::PermissionLvl; +use pumpkin_core::permission::PermissionLvl; const NAMES: [&str; 1] = ["transfer"]; @@ -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 fd9ae1a2..b52ac6b5 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/commands/mod.rs b/pumpkin/src/command/commands/mod.rs index 6ea8c01a..5b463593 100644 --- a/pumpkin/src/command/commands/mod.rs +++ b/pumpkin/src/command/commands/mod.rs @@ -7,6 +7,7 @@ pub mod cmd_help; pub mod cmd_kick; pub mod cmd_kill; pub mod cmd_list; +pub mod cmd_op; pub mod cmd_pumpkin; pub mod cmd_say; pub mod cmd_seed; diff --git a/pumpkin/src/command/dispatcher.rs b/pumpkin/src/command/dispatcher.rs index b2dc27ae..4d1bb943 100644 --- a/pumpkin/src/command/dispatcher.rs +++ b/pumpkin/src/command/dispatcher.rs @@ -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, @@ -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, @@ -102,7 +102,7 @@ impl<'a> CommandDispatcher<'a> { // try paths and collect the nodes that fail // todo: make this more fine-grained for path in tree.iter_paths() { - match Self::try_find_suggestions_on_path(src, server, &path, &tree, &mut raw_args, cmd) + match Self::try_find_suggestions_on_path(src, server, &path, tree, &mut raw_args, cmd) .await { Err(InvalidConsumption(s)) => { @@ -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, @@ -151,7 +151,7 @@ impl<'a> CommandDispatcher<'a> { // 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.clone()).await? { + if Self::try_is_fitting_path(src, server, &path, tree, &mut raw_args.clone()).await? { return Ok(()); } } @@ -160,14 +160,14 @@ impl<'a> CommandDispatcher<'a> { ))) } - pub(crate) fn get_tree(&self, key: &str) -> Result, CommandError> { + pub(crate) fn get_tree(&self, key: &str) -> Result<&CommandTree, CommandError> { let command = self .commands .get(key) .ok_or(GeneralCommandIssue("Command not found".to_string()))?; match command { - Command::Tree(tree) => Ok(tree.clone()), + Command::Tree(tree) => Ok(tree), Command::Alias(target) => { 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"); @@ -175,22 +175,22 @@ impl<'a> CommandDispatcher<'a> { "Internal Error (See logs for details)".into(), )); }; - Ok(tree.clone()) + Ok(tree) } } } - 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,16 +270,18 @@ 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)); } } diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 9ab33038..c05017ed 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -4,17 +4,19 @@ use std::sync::Arc; use crate::command::commands::cmd_seed; use crate::command::commands::{cmd_bossbar, cmd_transfer}; use crate::command::dispatcher::CommandDispatcher; -use crate::entity::player::{PermissionLvl, Player}; +use crate::entity::player::Player; use crate::server::Server; use crate::world::World; use args::ConsumedArgs; use async_trait::async_trait; +use commands::cmd_op; use commands::{ cmd_clear, cmd_fill, cmd_gamemode, cmd_give, cmd_help, cmd_kick, cmd_kill, cmd_list, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, cmd_time, cmd_worldborder, }; use dispatcher::CommandError; use pumpkin_core::math::vector3::Vector3; +use pumpkin_core::permission::PermissionLvl; use pumpkin_core::text::TextComponent; pub mod args; @@ -76,7 +78,7 @@ impl<'a> CommandSender<'a> { pub fn permission_lvl(&self) -> PermissionLvl { match self { CommandSender::Console | CommandSender::Rcon(_) => PermissionLvl::Four, - CommandSender::Player(p) => p.permission_lvl(), + CommandSender::Player(p) => p.permission_lvl.load(), } } @@ -84,7 +86,7 @@ impl<'a> CommandSender<'a> { pub fn has_permission_lvl(&self, lvl: PermissionLvl) -> bool { match self { CommandSender::Console | CommandSender::Rcon(_) => true, - CommandSender::Player(p) => (p.permission_lvl() as i8) >= (lvl as i8), + CommandSender::Player(p) => p.permission_lvl.load().ge(&lvl), } } @@ -107,7 +109,7 @@ impl<'a> CommandSender<'a> { } #[must_use] -pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> { +pub fn default_dispatcher() -> CommandDispatcher { let mut dispatcher = CommandDispatcher::default(); dispatcher.register(cmd_pumpkin::init_command_tree()); @@ -128,6 +130,7 @@ pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> { dispatcher.register(cmd_seed::init_command_tree()); dispatcher.register(cmd_transfer::init_command_tree()); dispatcher.register(cmd_fill::init_command_tree()); + dispatcher.register(cmd_op::init_command_tree()); dispatcher } diff --git a/pumpkin/src/command/tree.rs b/pumpkin/src/command/tree.rs index 9ea18e35..8f54f949 100644 --- a/pumpkin/src/command/tree.rs +++ b/pumpkin/src/command/tree.rs @@ -1,34 +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, Clone)] -pub struct Node<'a> { +pub struct Node { pub(crate) children: Vec, - pub(crate) node_type: NodeType<'a>, + pub(crate) node_type: NodeType, } #[derive(Clone)] -pub enum NodeType<'a> { +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 @@ -46,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, Clone)] -pub struct CommandTree<'a> { - pub(crate) nodes: Vec>, +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, @@ -76,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 e750ea0b..92f65d9e 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 406d95f2..597aece0 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/data/mod.rs b/pumpkin/src/data/mod.rs new file mode 100644 index 00000000..7faf2d16 --- /dev/null +++ b/pumpkin/src/data/mod.rs @@ -0,0 +1,88 @@ +use std::{env, fs, path::Path}; + +use serde::{Deserialize, Serialize}; + +const DATA_FOLDER: &str = "data/"; + +pub mod op_data; + +pub trait LoadJSONConfiguration { + #[must_use] + fn load() -> Self + where + Self: Sized + Default + Serialize + for<'de> Deserialize<'de>, + { + let exe_dir = env::current_dir().unwrap(); + let data_dir = exe_dir.join(DATA_FOLDER); + if !data_dir.exists() { + log::debug!("creating new data root folder"); + fs::create_dir(&data_dir).expect("Failed to create data root folder"); + } + let path = data_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:?}")); + + serde_json::from_str(&file_content).unwrap_or_else(|err| { + panic!( + "Couldn't parse data config at {path:?}. Reason: {err}. This is probably caused by a config update. Just delete the old data config and restart.", + ) + }) + } else { + let content = Self::default(); + + if let Err(err) = fs::write(&path, serde_json::to_string_pretty(&content).unwrap()) { + log::error!( + "Couldn't write default data config to {path:?}. Reason: {err}. This is probably caused by a config update. Just delete the old data config and restart.", + ); + } + + content + }; + + config.validate(); + config + } + + fn get_path() -> &'static Path; + + fn validate(&self); +} + +pub trait SaveJSONConfiguration: LoadJSONConfiguration { + // suppress clippy warning + + fn save(&self) + where + Self: Sized + Default + Serialize + for<'de> Deserialize<'de>, + { + let exe_dir = env::current_dir().unwrap(); + let data_dir = exe_dir.join(DATA_FOLDER); + if !data_dir.exists() { + log::debug!("creating new data root folder"); + fs::create_dir(&data_dir).expect("Failed to create data root folder"); + } + let path = data_dir.join(Self::get_path()); + + let content = match serde_json::to_string_pretty(self) { + Ok(content) => content, + Err(err) => { + log::warn!( + "Couldn't serialize operator data config to {:?}. Reason: {}", + path, + err + ); + return; + } + }; + + if let Err(err) = std::fs::write(&path, content) { + log::warn!( + "Couldn't write operator config to {:?}. Reason: {}", + path, + err + ); + } + } +} diff --git a/pumpkin/src/data/op_data.rs b/pumpkin/src/data/op_data.rs new file mode 100644 index 00000000..c1dada05 --- /dev/null +++ b/pumpkin/src/data/op_data.rs @@ -0,0 +1,26 @@ +use std::{path::Path, sync::LazyLock}; + +use pumpkin_config::op; +use serde::{Deserialize, Serialize}; + +use super::{LoadJSONConfiguration, SaveJSONConfiguration}; + +pub static OPERATOR_CONFIG: LazyLock> = + LazyLock::new(|| tokio::sync::RwLock::new(OperatorConfig::load())); + +#[derive(Deserialize, Serialize, Default)] +#[serde(transparent)] +pub struct OperatorConfig { + pub ops: Vec, +} + +impl LoadJSONConfiguration for OperatorConfig { + fn get_path() -> &'static Path { + Path::new("ops.json") + } + fn validate(&self) { + // TODO: Validate the operator configuration + } +} + +impl SaveJSONConfiguration for OperatorConfig {} diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index f958cd91..6f4920e0 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -8,7 +8,7 @@ use std::{ }; use crossbeam::atomic::AtomicCell; -use num_derive::{FromPrimitive, ToPrimitive}; +use num_derive::FromPrimitive; use num_traits::Pow; use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; use pumpkin_core::{ @@ -18,6 +18,7 @@ use pumpkin_core::{ vector2::Vector2, vector3::Vector3, }, + permission::PermissionLvl, text::TextComponent, GameMode, }; @@ -54,11 +55,12 @@ use pumpkin_world::{ ItemStack, }, }; -use tokio::sync::{Mutex, Notify}; +use tokio::sync::{Mutex, Notify, RwLock}; use super::Entity; -use crate::{error::PumpkinError, net::GameProfile}; use crate::{ + command::{client_cmd_suggestions, dispatcher::CommandDispatcher}, + data::op_data::OPERATOR_CONFIG, net::{ combat::{self, player_attack_sound, AttackType}, Client, PlayerConfig, @@ -66,6 +68,7 @@ use crate::{ server::Server, world::World, }; +use crate::{error::PumpkinError, net::GameProfile}; use super::living::LivingEntity; @@ -116,12 +119,10 @@ pub struct Player { pub last_keep_alive_time: AtomicCell, /// Amount of ticks since last attack pub last_attacked_ticks: AtomicU32, - + /// The players op permission level + pub permission_lvl: AtomicCell, /// Tell tasks to stop if we are closing cancel_tasks: Notify, - - /// the players op permission level - permission_lvl: PermissionLvl, } impl Player { @@ -143,6 +144,8 @@ impl Player { }, |profile| profile, ); + + let gameprofile_clone = gameprofile.clone(); let config = client.config.lock().await.clone().unwrap_or_default(); let bounding_box_size = BoundingBoxSize { width: 0.6, @@ -186,8 +189,18 @@ impl Player { last_keep_alive_time: AtomicCell::new(std::time::Instant::now()), last_attacked_ticks: AtomicU32::new(0), cancel_tasks: Notify::new(), - // TODO: change this - permission_lvl: PermissionLvl::Four, + // Minecraft has no why to change the default permission level of new players. + // Minecrafts default permission level is 0 + permission_lvl: OPERATOR_CONFIG + .read() + .await + .ops + .iter() + .find(|op| op.uuid == gameprofile_clone.id) + .map_or( + AtomicCell::new(ADVANCED_CONFIG.commands.default_op_level), + |op| AtomicCell::new(op.level), + ), } } @@ -431,20 +444,20 @@ impl Player { self.client .send_packet(&CEntityStatus::new( self.entity_id(), - 24 + self.permission_lvl as i8, + 24 + self.permission_lvl.load() as i8, )) .await; } /// sets the players permission level and syncs it with the client - pub async fn set_permission_lvl(&mut self, lvl: PermissionLvl) { - self.permission_lvl = lvl; + pub async fn set_permission_lvl( + self: &Arc, + lvl: PermissionLvl, + command_dispatcher: &RwLock, + ) { + self.permission_lvl.store(lvl); self.send_permission_lvl_update().await; - } - - /// get the players permission level - pub fn permission_lvl(&self) -> PermissionLvl { - self.permission_lvl + client_cmd_suggestions::send_c_commands_packet(self, command_dispatcher).await; } /// Sends the world time to just the player. @@ -821,19 +834,3 @@ pub enum ChatMode { /// All messages should be hidden Hidden, } - -/// the player's permission level -#[derive(Debug, FromPrimitive, ToPrimitive, Clone, Copy)] -#[repr(i8)] -pub enum PermissionLvl { - /// `normal`: Player can use basic commands. - Zero = 0, - /// `moderator`: Player can bypass spawn protection. - One = 1, - /// `gamemaster`: Player or executor can use more commands and player can use command blocks. - Two = 2, - /// `admin`: Player or executor can use commands related to multiplayer management. - Three = 3, - /// `owner`: Player or executor can use all of the commands, including commands related to server management. - Four = 4, -} diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 5009e96f..d0dbe401 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -55,6 +55,7 @@ use std::time::Instant; pub mod block; pub mod command; +pub mod data; pub mod entity; pub mod error; pub mod net; diff --git a/pumpkin/src/net/mod.rs b/pumpkin/src/net/mod.rs index 0e5f9e9c..b43cb1cb 100644 --- a/pumpkin/src/net/mod.rs +++ b/pumpkin/src/net/mod.rs @@ -33,7 +33,8 @@ use pumpkin_protocol::{ }, status::{SStatusPingRequest, SStatusRequest}, }, - ClientPacket, ConnectionState, Property, RawPacket, ServerPacket, + ClientPacket, CompressionLevel, CompressionThreshold, ConnectionState, Property, RawPacket, + ServerPacket, }; use serde::Deserialize; use sha1::Digest; @@ -224,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. diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 22554257..d929d565 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -47,7 +47,7 @@ pub struct Server { /// Saves server branding information. server_branding: CachedBranding, /// Saves and Dispatches commands to appropriate handlers. - pub command_dispatcher: RwLock>, + pub command_dispatcher: RwLock, /// Saves and calls blocks blocks pub block_manager: Arc, /// Manages multiple worlds within the server. @@ -57,7 +57,6 @@ 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.