supportedBedrockVersions();
+
+ /**
+ * Gets the {@link CommandSource} for the console.
+ *
+ * @return the console command source
+ */
+ @NonNull
+ CommandSource consoleCommandSource();
+
/**
* Gets the current {@link GeyserApiBase} instance.
*
diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraData.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraData.java
new file mode 100644
index 000000000..f208879d1
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraData.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.bedrock.camera;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.api.connection.GeyserConnection;
+
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * This interface holds all the methods that relate to a client's camera.
+ * Can be accessed through {@link GeyserConnection#camera()}.
+ */
+public interface CameraData {
+
+ /**
+ * Sends a camera fade instruction to the client.
+ * If an existing camera fade is already in progress, the current fade will be prolonged.
+ * Can be built using {@link CameraFade.Builder}.
+ * To stop a fade early, use {@link #clearCameraInstructions()}.
+ *
+ * @param fade the camera fade instruction to send
+ */
+ void sendCameraFade(@NonNull CameraFade fade);
+
+ /**
+ * Sends a camera position instruction to the client.
+ * If an existing camera movement is already in progress,
+ * the final camera position will be the one of the latest instruction, and
+ * the (optional) camera fade will be added on top of the existing fade.
+ * Can be built using {@link CameraPosition.Builder}.
+ * To stop reset the camera position/stop ongoing instructions, use {@link #clearCameraInstructions()}.
+ *
+ * @param position the camera position instruction to send
+ */
+ void sendCameraPosition(@NonNull CameraPosition position);
+
+ /**
+ * Stops all sent camera instructions (fades, movements, and perspective locks).
+ * This will not stop any camera shakes/input locks/fog effects, use the respective methods for those.
+ */
+ void clearCameraInstructions();
+
+ /**
+ * Forces a {@link CameraPerspective} on the client. This will prevent the client
+ * from changing their camera perspective until it is unlocked via {@link #clearCameraInstructions()}.
+ *
+ * Note: You cannot force a client into a free camera perspective with this method.
+ * To do that, send a {@link CameraPosition} via {@link #sendCameraPosition(CameraPosition)} - it requires a set position
+ * instead of being relative to the player.
+ *
+ * @param perspective the {@link CameraPerspective} to force
+ */
+ void forceCameraPerspective(@NonNull CameraPerspective perspective);
+
+ /**
+ * Gets the client's current {@link CameraPerspective}, if one is currently forced.
+ * This will return {@code null} if the client is not currently forced into a perspective.
+ * If a perspective is forced, the client will not be able to change their camera perspective until it is unlocked.
+ *
+ * @return the forced perspective, or {@code null} if none is forced
+ */
+ @Nullable CameraPerspective forcedCameraPerspective();
+
+ /**
+ * Shakes the client's camera.
+ *
+ * If the camera is already shaking with the same {@link CameraShake} type, then the additional intensity
+ * will be layered on top of the existing intensity, with their own distinct durations.
+ * If the existing shake type is different and the new intensity/duration are not positive, the existing shake only
+ * switches to the new type. Otherwise, the existing shake is completely overridden.
+ *
+ * @param intensity the intensity of the shake. The client has a maximum total intensity of 4.
+ * @param duration the time in seconds that the shake will occur for
+ * @param type the type of shake
+ */
+ void shakeCamera(float intensity, float duration, @NonNull CameraShake type);
+
+ /**
+ * Stops all camera shakes of any type.
+ */
+ void stopCameraShake();
+
+ /**
+ * Adds the given fog IDs to the fog cache, then sends all fog IDs in the cache to the client.
+ *
+ * Fog IDs can be found here
+ *
+ * @param fogNameSpaces the fog IDs to add. If empty, the existing cached IDs will still be sent.
+ */
+ void sendFog(String... fogNameSpaces);
+
+ /**
+ * Removes the given fog IDs from the fog cache, then sends all fog IDs in the cache to the client.
+ *
+ * @param fogNameSpaces the fog IDs to remove. If empty, all fog IDs will be removed.
+ */
+ void removeFog(String... fogNameSpaces);
+
+ /**
+ * Returns an immutable copy of all fog affects currently applied to this client.
+ */
+ @NonNull
+ Set fogEffects();
+
+ /**
+ * (Un)locks the client's camera, so that they cannot look around.
+ * To ensure the camera is only unlocked when all locks are released, you must supply
+ * a UUID when using method, and use the same UUID to unlock the camera.
+ *
+ * @param lock whether to lock the camera
+ * @param owner the owner of the lock, represented with a UUID
+ * @return if the camera is locked after this method call
+ */
+ boolean lockCamera(boolean lock, @NonNull UUID owner);
+
+ /**
+ * Returns whether the client's camera is locked.
+ *
+ * @return whether the camera is currently locked
+ */
+ boolean isCameraLocked();
+
+ /**
+ * Hides a {@link GuiElement} on the client's side.
+ *
+ * @param element the {@link GuiElement} to hide
+ */
+ void hideElement(@NonNull GuiElement... element);
+
+ /**
+ * Resets a {@link GuiElement} on the client's side.
+ * This makes the client decide on its own - e.g. based on client settings -
+ * whether to show or hide the gui element.
+ *
+ * If no elements are specified, this will reset all currently hidden elements
+ *
+ * @param element the {@link GuiElement} to reset
+ */
+ void resetElement(@NonNull GuiElement @Nullable... element);
+
+ /**
+ * Determines whether a {@link GuiElement} is currently hidden.
+ *
+ * @param element the {@link GuiElement} to check
+ */
+ boolean isHudElementHidden(@NonNull GuiElement element);
+
+ /**
+ * Returns the currently hidden {@link GuiElement}s.
+ *
+ * @return an unmodifiable view of all currently hidden {@link GuiElement}s
+ */
+ @NonNull Set hiddenElements();
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraEaseType.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraEaseType.java
new file mode 100644
index 000000000..64c313ec1
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraEaseType.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.bedrock.camera;
+
+/**
+ * These are all the easing types that can be used when sending a {@link CameraPosition} instruction.
+ * When using these, the client won't teleport to the new camera position, but instead transition to it.
+ *
+ * See https://easings.net/ for more information.
+ */
+public enum CameraEaseType {
+ LINEAR("linear"),
+ SPRING("spring"),
+ EASE_IN_SINE("in_sine"),
+ EASE_OUT_SINE("out_sine"),
+ EASE_IN_OUT_SINE("in_out_sine"),
+ EASE_IN_QUAD("in_quad"),
+ EASE_OUT_QUAD("out_quad"),
+ EASE_IN_OUT_QUAD("in_out_quad"),
+ EASE_IN_CUBIC("in_cubic"),
+ EASE_OUT_CUBIC("out_cubic"),
+ EASE_IN_OUT_CUBIC("in_out_cubic"),
+ EASE_IN_QUART("in_quart"),
+ EASE_OUT_QUART("out_quart"),
+ EASE_IN_OUT_QUART("in_out_quart"),
+ EASE_IN_QUINT("in_quint"),
+ EASE_OUT_QUINT("out_quint"),
+ EASE_IN_OUT_QUINT("in_out_quint"),
+ EASE_IN_EXPO("in_expo"),
+ EASE_OUT_EXPO("out_expo"),
+ EASE_IN_OUT_EXPO("in_out_expo"),
+ EASE_IN_CIRC("in_circ"),
+ EASE_OUT_CIRC("out_circ"),
+ EASE_IN_OUT_CIRC("in_out_circ"),
+ EASE_IN_BACK("in_back"),
+ EASE_OUT_BACK("out_back"),
+ EASE_IN_OUT_BACK("in_out_back"),
+ EASE_IN_ELASTIC("in_elastic"),
+ EASE_OUT_ELASTIC("out_elastic"),
+ EASE_IN_OUT_ELASTIC("in_out_elastic"),
+ EASE_IN_BOUNCE("in_bounce"),
+ EASE_OUT_BOUNCE("out_bounce"),
+ EASE_IN_OUT_BOUNCE("in_out_bounce");
+
+ private final String id;
+
+ CameraEaseType(String id) {
+ this.id = id;
+ }
+
+ public String id() {
+ return this.id;
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraFade.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraFade.java
new file mode 100644
index 000000000..38baa73bb
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraFade.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.bedrock.camera;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.value.qual.IntRange;
+import org.geysermc.geyser.api.GeyserApi;
+
+import java.awt.Color;
+
+/**
+ * Represents a coloured fade overlay on the camera.
+ *
+ * Can be sent with {@link CameraData#sendCameraFade(CameraFade)}, or with a {@link CameraPosition} instruction.
+ */
+public interface CameraFade {
+
+ /**
+ * Gets the color overlay of the camera.
+ * Bedrock uses an RGB color system.
+ *
+ * @return the color of the fade
+ */
+ @NonNull Color color();
+
+ /**
+ * Gets the seconds it takes to fade in.
+ * All fade times combined must take at least 0.5 seconds, and at most 30 seconds.
+ *
+ * @return the seconds it takes to fade in
+ */
+ float fadeInSeconds();
+
+ /**
+ * Gets the seconds the overlay is held.
+ * All fade times combined must take at least 0.5 seconds, and at most 30 seconds.
+ *
+ * @return the seconds the overlay is held
+ */
+ float fadeHoldSeconds();
+
+ /**
+ * Gets the seconds it takes to fade out.
+ * All fade times combined must take at least 0.5 seconds, and at most 30 seconds.
+ *
+ * @return the seconds it takes to fade out
+ */
+ float fadeOutSeconds();
+
+ /**
+ * Creates a Builder for CameraFade
+ *
+ * @return a CameraFade Builder
+ */
+ static CameraFade.Builder builder() {
+ return GeyserApi.api().provider(CameraFade.Builder.class);
+ }
+
+ interface Builder {
+
+ Builder color(@NonNull Color color);
+
+ Builder fadeInSeconds(@IntRange(from = 0, to = 10) float fadeInSeconds);
+
+ Builder fadeHoldSeconds(@IntRange(from = 0, to = 10) float fadeHoldSeconds);
+
+ Builder fadeOutSeconds(@IntRange(from = 0, to = 10) float fadeOutSeconds);
+
+ CameraFade build();
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPerspective.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPerspective.java
new file mode 100644
index 000000000..4167f2a34
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPerspective.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.bedrock.camera;
+
+/**
+ * Represents a camera perspective that a player's camera can take.
+ * All perspectives except for {@link #FREE} are locked to the player's head,
+ * and are therefore relative to the player's position and rotation.
+ */
+public enum CameraPerspective {
+ FIRST_PERSON("minecraft:first_person"),
+ FREE("minecraft:free"),
+ THIRD_PERSON("minecraft:third_person"),
+ THIRD_PERSON_FRONT("minecraft:third_person_front");
+
+ private final String id;
+
+ CameraPerspective(String id) {
+ this.id = id;
+ }
+
+ public String id() {
+ return this.id;
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPosition.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPosition.java
new file mode 100644
index 000000000..6d42d499e
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPosition.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.bedrock.camera;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.value.qual.IntRange;
+import org.cloudburstmc.math.vector.Vector3f;
+import org.geysermc.geyser.api.GeyserApi;
+
+/**
+ * This interface represents a camera position instruction. Can be built with the {@link #builder()}.
+ *
+ * Any camera position instruction pins the client camera to a specific position and rotation.
+ * You can set {@link CameraEaseType} to ensure a smooth transition that will last {@link #easeSeconds()} seconds.
+ * A {@link CameraFade} can also be sent, which will transition the player to a coloured transition during the transition.
+ *
+ * Use {@link CameraData#sendCameraPosition(CameraPosition)} to send such an instruction to any connection.
+ */
+public interface CameraPosition {
+
+ /**
+ * Gets the camera's position.
+ *
+ * @return camera position vector
+ */
+ @NonNull Vector3f position();
+
+ /**
+ * Gets the {@link CameraEaseType} of the camera.
+ * If not set, there is no easing.
+ *
+ * @return camera ease type
+ */
+ @Nullable CameraEaseType easeType();
+
+ /**
+ * Gets the {@link CameraFade} to be sent along the camera position instruction.
+ * If set, they will run at once.
+ *
+ * @return camera fade, or null if not present
+ */
+ @Nullable CameraFade cameraFade();
+
+ /**
+ * Gets the easing duration of the camera, in seconds.
+ * Is only used if a {@link CameraEaseType} is set.
+ *
+ * @return camera easing duration in seconds
+ */
+ float easeSeconds();
+
+ /**
+ * Gets the x-axis rotation of the camera.
+ * To prevent the camera from being upside down, Bedrock limits the range to -90 to 90.
+ * Will be overridden if {@link #facingPosition()} is set.
+ *
+ * @return camera x-axis rotation
+ */
+ @IntRange(from = -90, to = 90) int rotationX();
+
+ /**
+ * Gets the y-axis rotation of the camera.
+ * Will be overridden if {@link #facingPosition()} is set.
+ *
+ * @return camera y-axis rotation
+ */
+ int rotationY();
+
+ /**
+ * Gets the position that the camera is facing.
+ * Can be used instead of manually setting rotation values.
+ *
+ * If set, the rotation values set via {@link #rotationX()} and {@link #rotationY()} will be ignored.
+ *
+ * @return Camera's facing position
+ */
+ @Nullable Vector3f facingPosition();
+
+ /**
+ * Controls whether player effects, such as night vision or blindness, should be rendered on the camera.
+ * Defaults to false.
+ *
+ * @return whether player effects should be rendered
+ */
+ boolean renderPlayerEffects();
+
+ /**
+ * Controls whether the player position should be used for directional audio.
+ * If false, the camera position will be used instead.
+ *
+ * @return whether the players position should be used for directional audio
+ */
+ boolean playerPositionForAudio();
+
+ /**
+ * Creates a Builder for CameraPosition
+ *
+ * @return a CameraPosition Builder
+ */
+ static CameraPosition.Builder builder() {
+ return GeyserApi.api().provider(CameraPosition.Builder.class);
+ }
+
+ interface Builder {
+
+ Builder cameraFade(@Nullable CameraFade cameraFade);
+
+ Builder renderPlayerEffects(boolean renderPlayerEffects);
+
+ Builder playerPositionForAudio(boolean playerPositionForAudio);
+
+ Builder easeType(@Nullable CameraEaseType easeType);
+
+ Builder easeSeconds(float easeSeconds);
+
+ Builder position(@NonNull Vector3f position);
+
+ Builder rotationX(@IntRange(from = -90, to = 90) int rotationX);
+
+ Builder rotationY(int rotationY);
+
+ Builder facingPosition(@Nullable Vector3f facingPosition);
+
+ CameraPosition build();
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraShake.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraShake.java
index 67b56b1f5..304969edb 100644
--- a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraShake.java
+++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraShake.java
@@ -25,6 +25,9 @@
package org.geysermc.geyser.api.bedrock.camera;
+/**
+ * Represents a camera shake instruction. Can be sent in {@link CameraData#shakeCamera(float, float, CameraShake)}
+ */
public enum CameraShake {
POSITIONAL,
ROTATIONAL
diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/GuiElement.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/GuiElement.java
new file mode 100644
index 000000000..4d3653648
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/GuiElement.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.bedrock.camera;
+
+/**
+ * Represent GUI elements on the players HUD display.
+ * These can be hidden using {@link CameraData#hideElement(GuiElement...)},
+ * and one can reset their visibility using {@link CameraData#resetElement(GuiElement...)}.
+ */
+public class GuiElement {
+ public static final GuiElement PAPER_DOLL = new GuiElement(0);
+ public static final GuiElement ARMOR = new GuiElement(1);
+ public static final GuiElement TOOL_TIPS = new GuiElement(2);
+ public static final GuiElement TOUCH_CONTROLS = new GuiElement(3);
+ public static final GuiElement CROSSHAIR = new GuiElement(4);
+ public static final GuiElement HOTBAR = new GuiElement(5);
+ public static final GuiElement HEALTH = new GuiElement(6);
+ public static final GuiElement PROGRESS_BAR = new GuiElement(7);
+ public static final GuiElement FOOD_BAR = new GuiElement(8);
+ public static final GuiElement AIR_BUBBLES_BAR = new GuiElement(9);
+ public static final GuiElement VEHICLE_HEALTH = new GuiElement(10);
+ public static final GuiElement EFFECTS_BAR = new GuiElement(11);
+ public static final GuiElement ITEM_TEXT_POPUP = new GuiElement(12);
+
+ private GuiElement(int id) {
+ this.id = id;
+ }
+
+ private final int id;
+
+ /**
+ * Internal use only; don't depend on these values being consistent.
+ */
+ public int id() {
+ return this.id;
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/block/custom/CustomBlockData.java b/api/src/main/java/org/geysermc/geyser/api/block/custom/CustomBlockData.java
index 9f142faab..2605cda21 100644
--- a/api/src/main/java/org/geysermc/geyser/api/block/custom/CustomBlockData.java
+++ b/api/src/main/java/org/geysermc/geyser/api/block/custom/CustomBlockData.java
@@ -61,16 +61,16 @@ public interface CustomBlockData {
boolean includedInCreativeInventory();
/**
- * Gets the item's creative category, or tab id.
+ * Gets the block's creative category, or tab id.
*
- * @return the item's creative category
+ * @return the block's creative category
*/
@Nullable CreativeCategory creativeCategory();
/**
- * Gets the item's creative group.
+ * Gets the block's creative group.
*
- * @return the item's creative group
+ * @return the block's creative group
*/
@Nullable String creativeGroup();
diff --git a/api/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java b/api/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java
index 63788df8e..06608f787 100644
--- a/api/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java
+++ b/api/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java
@@ -129,8 +129,11 @@ public interface CustomBlockComponents {
* Gets the unit cube component
* Equivalent to "minecraft:unit_cube"
*
+ * @deprecated Use {@link #geometry()} and compare with `minecraft:geometry.full_block` instead.
+ *
* @return The rotation.
*/
+ @Deprecated
boolean unitCube();
/**
@@ -181,6 +184,10 @@ public interface CustomBlockComponents {
Builder transformation(TransformationComponent transformation);
+ /**
+ * @deprecated Use {@link #geometry(GeometryComponent)} with `minecraft:geometry.full_block` instead.
+ */
+ @Deprecated
Builder unitCube(boolean unitCube);
Builder placeAir(boolean placeAir);
diff --git a/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java b/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java
index f7da4b932..0dd0d3b33 100644
--- a/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java
+++ b/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java
@@ -73,7 +73,10 @@ public interface JavaBlockState {
* Gets whether the block state has block entity
*
* @return whether the block state has block entity
+ * @deprecated Does not have an effect. If you were using this to
+ * set piston behavior, use {@link #pistonBehavior()} instead.
*/
+ @Deprecated(forRemoval = true)
boolean hasBlockEntity();
/**
@@ -104,6 +107,11 @@ public interface JavaBlockState {
Builder pistonBehavior(@Nullable String pistonBehavior);
+ /**
+ * @deprecated Does not have an effect. If you were using this to
+ * * set piston behavior, use {@link #pistonBehavior(String)} instead.
+ */
+ @Deprecated(forRemoval = true)
Builder hasBlockEntity(boolean hasBlockEntity);
JavaBlockState build();
diff --git a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java
index 7094812a0..9bda4f903 100644
--- a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java
+++ b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java
@@ -29,8 +29,10 @@ import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.connection.Connection;
+import org.geysermc.geyser.api.bedrock.camera.CameraData;
import org.geysermc.geyser.api.bedrock.camera.CameraShake;
import org.geysermc.geyser.api.command.CommandSource;
+import org.geysermc.geyser.api.entity.EntityData;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
@@ -41,10 +43,29 @@ import java.util.concurrent.CompletableFuture;
* Represents a player connection used in Geyser.
*/
public interface GeyserConnection extends Connection, CommandSource {
+
+ /**
+ * Exposes the {@link CameraData} for this connection.
+ * It allows you to send fogs, camera shakes, force camera perspectives, and more.
+ *
+ * @return the CameraData for this connection.
+ */
+ @NonNull CameraData camera();
+
+ /**
+ * Exposes the {@link EntityData} for this connection.
+ * It allows you to get entities by their Java entity ID, show emotes, and get the player entity.
+ *
+ * @return the EntityData for this connection.
+ */
+ @NonNull EntityData entities();
+
/**
* @param javaId the Java entity ID to look up.
* @return a {@link GeyserEntity} if present in this connection's entity tracker.
+ * @deprecated Use {@link EntityData#entityByJavaId(int)} instead
*/
+ @Deprecated
@NonNull
CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId);
@@ -53,11 +74,14 @@ public interface GeyserConnection extends Connection, CommandSource {
*
* @param emoter the player entity emoting.
* @param emoteId the emote ID to send to this client.
+ * @deprecated use {@link EntityData#showEmote(GeyserPlayerEntity, String)} instead
*/
+ @Deprecated
void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId);
/**
- * Shakes the client's camera.
+ * Shakes the client's camera.
+ *
* If the camera is already shaking with the same {@link CameraShake} type, then the additional intensity
* will be layered on top of the existing intensity, with their own distinct durations.
* If the existing shake type is different and the new intensity/duration are not positive, the existing shake only
@@ -66,12 +90,18 @@ public interface GeyserConnection extends Connection, CommandSource {
* @param intensity the intensity of the shake. The client has a maximum total intensity of 4.
* @param duration the time in seconds that the shake will occur for
* @param type the type of shake
+ *
+ * @deprecated Use {@link CameraData#shakeCamera(float, float, CameraShake)} instead.
*/
+ @Deprecated
void shakeCamera(float intensity, float duration, @NonNull CameraShake type);
/**
* Stops all camera shake of any type.
+ *
+ * @deprecated Use {@link CameraData#stopCameraShake()} instead.
*/
+ @Deprecated
void stopCameraShake();
/**
@@ -80,19 +110,26 @@ public interface GeyserConnection extends Connection, CommandSource {
* Fog IDs can be found here
*
* @param fogNameSpaces the fog IDs to add. If empty, the existing cached IDs will still be sent.
+ * @deprecated Use {@link CameraData#sendFog(String...)} instead.
*/
+ @Deprecated
void sendFog(String... fogNameSpaces);
/**
* Removes the given fog IDs from the fog cache, then sends all fog IDs in the cache to the client.
*
* @param fogNameSpaces the fog IDs to remove. If empty, all fog IDs will be removed.
+ * @deprecated Use {@link CameraData#removeFog(String...)} instead.
*/
+ @Deprecated
void removeFog(String... fogNameSpaces);
/**
* Returns an immutable copy of all fog affects currently applied to this client.
+ *
+ * @deprecated Use {@link CameraData#fogEffects()} instead.
*/
+ @Deprecated
@NonNull
Set fogEffects();
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java b/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java
new file mode 100644
index 000000000..90b3fc821
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.entity;
+
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.api.connection.GeyserConnection;
+import org.geysermc.geyser.api.entity.type.GeyserEntity;
+import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * This class holds all the methods that relate to entities.
+ * Can be accessed through {@link GeyserConnection#entities()}.
+ */
+public interface EntityData {
+
+ /**
+ * Returns a {@link GeyserEntity} to e.g. make them play an emote.
+ *
+ * @param javaId the Java entity ID to look up
+ * @return a {@link GeyserEntity} if present in this connection's entity tracker
+ */
+ @NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId);
+
+ /**
+ * Displays a player entity as emoting to this client.
+ *
+ * @param emoter the player entity emoting
+ * @param emoteId the emote ID to send to this client
+ */
+ void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId);
+
+ /**
+ * Gets the {@link GeyserPlayerEntity} of this connection.
+ *
+ * @return the {@link GeyserPlayerEntity} of this connection
+ */
+ @NonNull GeyserPlayerEntity playerEntity();
+
+ /**
+ * (Un)locks the client's movement inputs, so that they cannot move.
+ * To ensure that movement is only unlocked when all locks are released, you must supply
+ * a UUID with this method, and use the same UUID to unlock the camera.
+ *
+ * @param lock whether to lock the movement
+ * @param owner the owner of the lock
+ * @return if the movement is locked after this method call
+ */
+ boolean lockMovement(boolean lock, @NonNull UUID owner);
+
+ /**
+ * Returns whether the client's movement is currently locked.
+ *
+ * @return whether the movement is locked
+ */
+ boolean isMovementLocked();
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java b/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java
index da2e28609..bba4dbf3e 100644
--- a/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java
+++ b/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java
@@ -25,7 +25,15 @@
package org.geysermc.geyser.api.entity.type.player;
+import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
public interface GeyserPlayerEntity extends GeyserEntity {
+
+ /**
+ * Gets the position of the player.
+ *
+ * @return the position of the player.
+ */
+ Vector3f position();
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoginEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoginEvent.java
index c3c8198c1..86a5ec6f8 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoginEvent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionLoginEvent.java
@@ -32,18 +32,27 @@ import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
import org.geysermc.geyser.api.network.RemoteServer;
+import java.util.Map;
+import java.util.Objects;
+
/**
- * Called when a session has logged in, and is about to connect to a remote java server.
+ * Called when a session has logged in, and is about to connect to a remote Java server.
* This event is cancellable, and can be used to prevent the player from connecting to the remote server.
*/
public final class SessionLoginEvent extends ConnectionEvent implements Cancellable {
private RemoteServer remoteServer;
private boolean cancelled;
private String disconnectReason;
+ private Map cookies;
+ private boolean transferring;
- public SessionLoginEvent(@NonNull GeyserConnection connection, @NonNull RemoteServer remoteServer) {
+ public SessionLoginEvent(@NonNull GeyserConnection connection,
+ @NonNull RemoteServer remoteServer,
+ @NonNull Map cookies) {
super(connection);
this.remoteServer = remoteServer;
+ this.cookies = cookies;
+ this.transferring = false;
}
/**
@@ -90,9 +99,9 @@ public final class SessionLoginEvent extends ConnectionEvent implements Cancella
}
/**
- * Gets the {@link RemoteServer} the section will attempt to connect to.
+ * Gets the {@link RemoteServer} the session will attempt to connect to.
*
- * @return the {@link RemoteServer} the section will attempt to connect to.
+ * @return the {@link RemoteServer} the session will attempt to connect to.
*/
public @NonNull RemoteServer remoteServer() {
return this.remoteServer;
@@ -106,4 +115,36 @@ public final class SessionLoginEvent extends ConnectionEvent implements Cancella
public void remoteServer(@NonNull RemoteServer remoteServer) {
this.remoteServer = remoteServer;
}
+
+ /**
+ * Sets a map of cookies from a possible previous session. The Java server can send and request these
+ * to store information on the client across server transfers.
+ */
+ public void cookies(@NonNull Map cookies) {
+ Objects.requireNonNull(cookies);
+ this.cookies = cookies;
+ }
+
+ /**
+ * Gets a map of the sessions cookies, if set.
+ * @return the connections cookies
+ */
+ public @NonNull Map cookies() {
+ return cookies;
+ }
+
+ /**
+ * Determines the connection intent of the connection
+ */
+ public void transferring(boolean transferring) {
+ this.transferring = transferring;
+ }
+
+ /**
+ * Gets whether this login attempt to the Java server
+ * has the transfer intent
+ */
+ public boolean transferring() {
+ return this.transferring;
+ }
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java
new file mode 100644
index 000000000..f22241e41
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.event.bedrock;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.api.connection.GeyserConnection;
+import org.geysermc.geyser.api.event.connection.ConnectionEvent;
+import org.geysermc.geyser.api.skin.Cape;
+import org.geysermc.geyser.api.skin.Skin;
+import org.geysermc.geyser.api.skin.SkinData;
+import org.geysermc.geyser.api.skin.SkinGeometry;
+
+import java.util.UUID;
+
+/**
+ * Called when a skin is applied to a player.
+ *
+ * Won't be called when a fake player is spawned for a player skull.
+ */
+public abstract class SessionSkinApplyEvent extends ConnectionEvent {
+
+ private final String username;
+ private final UUID uuid;
+ private final boolean slim;
+ private final boolean bedrock;
+ private final SkinData originalSkinData;
+
+ public SessionSkinApplyEvent(@NonNull GeyserConnection connection, String username, UUID uuid, boolean slim, boolean bedrock, SkinData skinData) {
+ super(connection);
+ this.username = username;
+ this.uuid = uuid;
+ this.slim = slim;
+ this.bedrock = bedrock;
+ this.originalSkinData = skinData;
+ }
+
+ /**
+ * The username of the player.
+ *
+ * @return the username of the player
+ */
+ public @NonNull String username() {
+ return username;
+ }
+
+ /**
+ * The UUID of the player.
+ *
+ * @return the UUID of the player
+ */
+ public @NonNull UUID uuid() {
+ return uuid;
+ }
+
+ /**
+ * If the player is using a slim model.
+ *
+ * @return if the player is using a slim model
+ */
+ public boolean slim() {
+ return slim;
+ }
+
+ /**
+ * If the player is a Bedrock player.
+ *
+ * @return if the player is a Bedrock player
+ */
+ public boolean bedrock() {
+ return bedrock;
+ }
+
+ /**
+ * The original skin data of the player.
+ *
+ * @return the original skin data of the player
+ */
+ public @NonNull SkinData originalSkin() {
+ return originalSkinData;
+ }
+
+ /**
+ * The skin data of the player.
+ *
+ * @return the current skin data of the player
+ */
+ public abstract @NonNull SkinData skinData();
+
+ /**
+ * Change the skin of the player.
+ *
+ * @param newSkin the new skin
+ */
+ public abstract void skin(@NonNull Skin newSkin);
+
+ /**
+ * Change the cape of the player.
+ *
+ * @param newCape the new cape
+ */
+ public abstract void cape(@NonNull Cape newCape);
+
+ /**
+ * Change the geometry of the player.
+ *
+ * @param newGeometry the new geometry
+ */
+ public abstract void geometry(@NonNull SkinGeometry newGeometry);
+
+ /**
+ * Change the geometry of the player.
+ *
+ * Constructs a generic {@link SkinGeometry} object with the given data.
+ *
+ * @param geometryName the name of the geometry
+ * @param geometryData the data of the geometry
+ */
+ public void geometry(@NonNull String geometryName, @NonNull String geometryData) {
+ geometry(new SkinGeometry("{\"geometry\" :{\"default\" :\"" + geometryName + "\"}}", geometryData));
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionRequestEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionRequestEvent.java
new file mode 100644
index 000000000..b36ee8bfb
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/event/connection/ConnectionRequestEvent.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.event.connection;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.event.Cancellable;
+import org.geysermc.event.Event;
+
+import java.net.InetSocketAddress;
+
+/**
+ * Called whenever a client attempts to connect to the server, before the connection is accepted.
+ */
+public final class ConnectionRequestEvent implements Event, Cancellable {
+
+ private boolean cancelled;
+ private final InetSocketAddress ip;
+ private final InetSocketAddress proxyIp;
+
+ public ConnectionRequestEvent(@NonNull InetSocketAddress ip, @Nullable InetSocketAddress proxyIp) {
+ this.ip = ip;
+ this.proxyIp = proxyIp;
+ }
+
+ /**
+ * The IP address of the client attempting to connect
+ *
+ * @return the IP address of the client attempting to connect
+ * @deprecated Use {@link #inetSocketAddress()} instead
+ */
+ @NonNull @Deprecated(forRemoval = true)
+ public InetSocketAddress getInetSocketAddress() {
+ return ip;
+ }
+
+ /**
+ * The IP address of the proxy handling the connection. It will return null if there is no proxy.
+ *
+ * @return the IP address of the proxy handling the connection
+ * @deprecated Use {@link #proxyIp()} instead
+ */
+ @Nullable @Deprecated(forRemoval = true)
+ public InetSocketAddress getProxyIp() {
+ return proxyIp;
+ }
+
+ /**
+ * The IP address of the client attempting to connect
+ *
+ * @return the IP address of the client attempting to connect
+ */
+ @NonNull
+ public InetSocketAddress inetSocketAddress() {
+ return ip;
+ }
+
+ /**
+ * The IP address of the proxy handling the connection. It will return null if there is no proxy.
+ *
+ * @return the IP address of the proxy handling the connection
+ */
+ @Nullable
+ public InetSocketAddress proxyIp() {
+ return proxyIp;
+ }
+
+ /**
+ * The cancel status of this event. If this event is cancelled, the connection will be rejected.
+ *
+ * @return the cancel status of this event
+ */
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ /**
+ * Sets the cancel status of this event. If this event is canceled, the connection will be rejected.
+ *
+ * @param cancelled the cancel status of this event.
+ */
+ @Override
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/java/ServerTransferEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerTransferEvent.java
new file mode 100644
index 000000000..594e28ef0
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerTransferEvent.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.event.java;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.value.qual.IntRange;
+import org.geysermc.geyser.api.connection.GeyserConnection;
+import org.geysermc.geyser.api.event.connection.ConnectionEvent;
+
+import java.util.Map;
+
+/**
+ * Fired when the Java server sends a transfer request to a different Java server.
+ * Geyser Extensions can listen to this event and set a target server ip/port for Bedrock players to be transferred to.
+ */
+public class ServerTransferEvent extends ConnectionEvent {
+
+ private final String host;
+ private final int port;
+ private String bedrockHost;
+ private int bedrockPort;
+ private final Map cookies;
+
+ public ServerTransferEvent(@NonNull GeyserConnection connection,
+ @NonNull String host, int port, @NonNull Map cookies) {
+ super(connection);
+ this.host = host;
+ this.port = port;
+ this.cookies = cookies;
+ this.bedrockHost = null;
+ this.bedrockPort = -1;
+ }
+
+ /**
+ * The host that the Java server requests a transfer to.
+ *
+ * @return the host
+ */
+ public @NonNull String host() {
+ return this.host;
+ }
+
+ /**
+ * The port that the Java server requests a transfer to.
+ *
+ * @return the port
+ */
+ public int port() {
+ return this.port;
+ }
+
+ /**
+ * The host that the Bedrock player should try and connect to.
+ * If this is not set, the Bedrock player will just be disconnected.
+ *
+ * @return the host where the Bedrock client will be transferred to, or null if not set.
+ */
+ public @Nullable String bedrockHost() {
+ return this.bedrockHost;
+ }
+
+ /**
+ * The port that the Bedrock player should try and connect to.
+ * If this is not set, the Bedrock player will just be disconnected.
+ *
+ * @return the port where the Bedrock client will be transferred to, or -1 if not set.
+ */
+ public int bedrockPort() {
+ return this.bedrockPort;
+ }
+
+ /**
+ * Sets the host for the Bedrock player to be transferred to
+ */
+ public void bedrockHost(@NonNull String host) {
+ if (host == null || host.isBlank()) {
+ throw new IllegalArgumentException("Server address cannot be null or blank");
+ }
+ this.bedrockHost = host;
+ }
+
+ /**
+ * Sets the port for the Bedrock player to be transferred to
+ */
+ public void bedrockPort(@IntRange(from = 0, to = 65535) int port) {
+ if (port < 0 || port > 65535) {
+ throw new IllegalArgumentException("Server port must be between 0 and 65535, was " + port);
+ }
+ this.bedrockPort = port;
+ }
+
+ /**
+ * Gets a map of the sessions current cookies.
+ *
+ * @return the connections cookies
+ */
+ public @NonNull Map cookies() {
+ return cookies;
+ }
+
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostReloadEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostReloadEvent.java
new file mode 100644
index 000000000..c421cda37
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPostReloadEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.event.lifecycle;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.event.Event;
+import org.geysermc.geyser.api.event.EventBus;
+import org.geysermc.geyser.api.event.EventRegistrar;
+import org.geysermc.geyser.api.extension.ExtensionManager;
+
+/**
+ * Called when Geyser finished reloading and is accepting Bedrock connections again.
+ * Equivalent to the {@link GeyserPostInitializeEvent}
+ *
+ * @param extensionManager the extension manager
+ * @param eventBus the event bus
+ */
+public record GeyserPostReloadEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event {
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreReloadEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreReloadEvent.java
new file mode 100644
index 000000000..16d5058da
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserPreReloadEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.event.lifecycle;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.event.Event;
+import org.geysermc.geyser.api.event.EventBus;
+import org.geysermc.geyser.api.event.EventRegistrar;
+import org.geysermc.geyser.api.extension.ExtensionManager;
+
+/**
+ * Called when Geyser is about to reload. Primarily aimed at extensions, so they can decide on their own what to reload.
+ * After this event is fired, some lifecycle events can be fired again - such as the {@link GeyserLoadResourcePacksEvent}.
+ *
+ * @param extensionManager the extension manager
+ * @param eventBus the event bus
+ */
+public record GeyserPreReloadEvent(@NonNull ExtensionManager extensionManager, @NonNull EventBus eventBus) implements Event {
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java
index 404679e60..3b871cd74 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java
@@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
+import java.util.OptionalInt;
import java.util.Set;
/**
@@ -77,6 +78,20 @@ public interface CustomItemData {
*/
boolean displayHandheld();
+ /**
+ * Gets the item's creative category, or tab id.
+ *
+ * @return the item's creative category
+ */
+ @NonNull OptionalInt creativeCategory();
+
+ /**
+ * Gets the item's creative group.
+ *
+ * @return the item's creative group
+ */
+ @Nullable String creativeGroup();
+
/**
* Gets the item's texture size. This is to resize the item if the texture is not 16x16.
*
@@ -119,6 +134,10 @@ public interface CustomItemData {
Builder displayHandheld(boolean displayHandheld);
+ Builder creativeCategory(int creativeCategory);
+
+ Builder creativeGroup(@Nullable String creativeGroup);
+
Builder textureSize(int textureSize);
Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets);
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java
index 616a5bba6..2c283780c 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -30,7 +30,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
-import java.util.OptionalInt;
import java.util.Set;
/**
@@ -65,6 +64,14 @@ public interface NonVanillaCustomItemData extends CustomItemData {
*/
int maxDamage();
+ /**
+ * Gets the attack damage of the item.
+ * This is purely visual, and only applied to tools
+ *
+ * @return the attack damage of the item
+ */
+ int attackDamage();
+
/**
* Gets the tool type of the item.
*
@@ -107,20 +114,6 @@ public interface NonVanillaCustomItemData extends CustomItemData {
*/
@Nullable Set repairMaterials();
- /**
- * Gets the item's creative category, or tab id.
- *
- * @return the item's creative category
- */
- @NonNull OptionalInt creativeCategory();
-
- /**
- * Gets the item's creative group.
- *
- * @return the item's creative group
- */
- @Nullable String creativeGroup();
-
/**
* Gets if the item is a hat. This is used to determine if the item should be rendered on the player's head, and
* normally allow the player to equip it. This is not meant for armor.
@@ -168,6 +161,13 @@ public interface NonVanillaCustomItemData extends CustomItemData {
return displayHandheld();
}
+ /**
+ * Gets the block the item places.
+ *
+ * @return the block the item places
+ */
+ String block();
+
static NonVanillaCustomItemData.Builder builder() {
return GeyserApi.api().provider(NonVanillaCustomItemData.Builder.class);
}
@@ -184,6 +184,8 @@ public interface NonVanillaCustomItemData extends CustomItemData {
Builder maxDamage(int maxDamage);
+ Builder attackDamage(int attackDamage);
+
Builder toolType(@Nullable String toolType);
Builder toolTier(@Nullable String toolTier);
@@ -196,10 +198,6 @@ public interface NonVanillaCustomItemData extends CustomItemData {
Builder repairMaterials(@Nullable Set repairMaterials);
- Builder creativeCategory(int creativeCategory);
-
- Builder creativeGroup(@Nullable String creativeGroup);
-
Builder hat(boolean isHat);
Builder foil(boolean isFoil);
@@ -210,6 +208,8 @@ public interface NonVanillaCustomItemData extends CustomItemData {
Builder chargeable(boolean isChargeable);
+ Builder block(String block);
+
/**
* @deprecated Use {@link #displayHandheld(boolean)} instead.
*/
@@ -218,6 +218,12 @@ public interface NonVanillaCustomItemData extends CustomItemData {
return displayHandheld(isTool);
}
+ @Override
+ Builder creativeCategory(int creativeCategory);
+
+ @Override
+ Builder creativeGroup(@Nullable String creativeGroup);
+
@Override
Builder customItemOptions(@NonNull CustomItemOptions customItemOptions);
diff --git a/api/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java b/api/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java
index 61fe286aa..af35d7ad1 100644
--- a/api/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java
+++ b/api/src/main/java/org/geysermc/geyser/api/network/BedrockListener.java
@@ -50,6 +50,14 @@ public interface BedrockListener {
*/
int port();
+ /**
+ * Gets the broadcast port that's sent to Bedrock clients with the motd.
+ * This is the port that Bedrock clients will connect with. It usually does not differ from the listening port.
+ *
+ * @return the broadcast port
+ */
+ int broadcastPort();
+
/**
* Gets the primary MOTD shown to Bedrock players if a ping passthrough setting is not enabled.
*
diff --git a/api/src/main/java/org/geysermc/geyser/api/pack/PathPackCodec.java b/api/src/main/java/org/geysermc/geyser/api/pack/PathPackCodec.java
index ee5db8242..a3770451a 100644
--- a/api/src/main/java/org/geysermc/geyser/api/pack/PathPackCodec.java
+++ b/api/src/main/java/org/geysermc/geyser/api/pack/PathPackCodec.java
@@ -42,4 +42,4 @@ public abstract class PathPackCodec extends PackCodec {
*/
@NonNull
public abstract Path path();
-}
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/level/block/DoubleChestValue.java b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java
similarity index 65%
rename from core/src/main/java/org/geysermc/geyser/level/block/DoubleChestValue.java
rename to api/src/main/java/org/geysermc/geyser/api/skin/Cape.java
index 97c861df7..1e7341ae4 100644
--- a/core/src/main/java/org/geysermc/geyser/level/block/DoubleChestValue.java
+++ b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
+ * Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,18 +23,18 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.level.block;
+package org.geysermc.geyser.api.skin;
/**
- * This stores all values of double chests that are part of the Java block state.
+ * Represents a cape.
*
- * @param isFacingEast If true, then chest is facing east/west; if false, south/north
- * @param isDirectionPositive If true, direction is positive (east/south); if false, direction is negative (west/north)
- * @param isLeft If true, chest is the left of a pair; if false, chest is the right of a pair.
+ * @param textureUrl The URL of the cape texture
+ * @param capeId The ID of the cape
+ * @param capeData The raw cape image data in ARGB format
+ * @param failed If the cape failed to load, this is for things like fallback capes
*/
-public record DoubleChestValue(
- boolean isFacingEast,
- boolean isDirectionPositive,
- boolean isLeft) {
-
+public record Cape(String textureUrl, String capeId, byte[] capeData, boolean failed) {
+ public Cape(String textureUrl, String capeId, byte[] capeData) {
+ this(textureUrl, capeId, capeData, false);
+ }
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java
new file mode 100644
index 000000000..9b39ddfe8
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.skin;
+
+/**
+ * Represents a skin.
+ *
+ * @param textureUrl The URL/ID of the skin texture
+ * @param skinData The raw skin image data in ARGB
+ * @param failed If the skin failed to load, this is for things like fallback skins
+ */
+public record Skin(String textureUrl, byte[] skinData, boolean failed) {
+ public Skin(String textureUrl, byte[] skinData) {
+ this(textureUrl, skinData, false);
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java b/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java
new file mode 100644
index 000000000..9de4a3534
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.skin;
+
+/**
+ * Represents a full package of {@link Skin}, {@link Cape}, and {@link SkinGeometry}.
+ */
+public record SkinData(Skin skin, Cape cape, SkinGeometry geometry) {
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java b/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java
new file mode 100644
index 000000000..5b40d2022
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.skin;
+
+/**
+ * Represents geometry of a skin.
+ *
+ * @param geometryName The name of the geometry (JSON)
+ * @param geometryData The geometry data (JSON)
+ */
+public record SkinGeometry(String geometryName, String geometryData) {
+
+ public static SkinGeometry WIDE = getLegacy(false);
+ public static SkinGeometry SLIM = getLegacy(true);
+
+ /**
+ * Generate generic geometry
+ *
+ * @param isSlim if true, it will be the slimmer alex model
+ * @return The generic geometry object
+ */
+ private static SkinGeometry getLegacy(boolean isSlim) {
+ return new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.custom" + (isSlim ? "Slim" : "") + "\"}}", "");
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java b/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java
index 207320b1e..245eb9bc2 100644
--- a/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java
+++ b/api/src/main/java/org/geysermc/geyser/api/util/CreativeCategory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -31,11 +31,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
* Represents the creative menu categories or tabs.
*/
public enum CreativeCategory {
- COMMANDS("commands", 1),
- CONSTRUCTION("construction", 2),
+ CONSTRUCTION("construction", 1),
+ NATURE("nature", 2),
EQUIPMENT("equipment", 3),
ITEMS("items", 4),
- NATURE("nature", 5),
NONE("none", 6);
private final String internalName;
diff --git a/api/src/main/java/org/geysermc/geyser/api/util/MinecraftVersion.java b/api/src/main/java/org/geysermc/geyser/api/util/MinecraftVersion.java
new file mode 100644
index 000000000..34a4b59af
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/util/MinecraftVersion.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.util;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+/**
+ * Represents a Minecraft version.
+ */
+public interface MinecraftVersion {
+
+ /**
+ * Gets the Minecraft version as a String.
+ * Example: "1.20.2", or "1.20.40/1.20.41"
+ *
+ * @return the version string
+ */
+ @NonNull String versionString();
+
+ /**
+ * Gets the protocol version of this Minecraft version.
+ *
+ * @return the protocol version
+ */
+ int protocolVersion();
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java b/api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java
index 815381d6b..cda5e06e4 100644
--- a/api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java
+++ b/api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java
@@ -34,10 +34,12 @@ public record PlatformType(String platformName) {
public static final PlatformType ANDROID = new PlatformType("Android");
public static final PlatformType BUNGEECORD = new PlatformType("BungeeCord");
public static final PlatformType FABRIC = new PlatformType("Fabric");
+ public static final PlatformType NEOFORGE = new PlatformType("NeoForge");
public static final PlatformType SPIGOT = new PlatformType("Spigot");
@Deprecated
public static final PlatformType SPONGE = new PlatformType("Sponge");
public static final PlatformType STANDALONE = new PlatformType("Standalone");
public static final PlatformType VELOCITY = new PlatformType("Velocity");
+ public static final PlatformType VIAPROXY = new PlatformType("ViaProxy");
}
diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts
index 4025569dd..910e50723 100644
--- a/bootstrap/bungeecord/build.gradle.kts
+++ b/bootstrap/bungeecord/build.gradle.kts
@@ -1,7 +1,7 @@
dependencies {
api(projects.core)
-
implementation(libs.adventure.text.serializer.bungeecord)
+ compileOnlyApi(libs.bungeecord.proxy)
}
platformRelocate("net.md_5.bungee.jni")
@@ -22,6 +22,7 @@ tasks.withType {
dependencies {
exclude(dependency("com.google.*:.*"))
+ exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
exclude(dependency("io.netty:netty-handler:.*"))
@@ -33,3 +34,8 @@ tasks.withType {
exclude(dependency("io.netty:netty-resolver-dns:.*"))
}
}
+
+modrinth {
+ uploadFile.set(tasks.getByPath("shadowJar"))
+ loaders.add("bungeecord")
+}
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeLogger.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeLogger.java
index daeb20102..e8cf7ee39 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeLogger.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeLogger.java
@@ -26,22 +26,19 @@
package org.geysermc.geyser.platform.bungeecord;
import lombok.Getter;
+import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.geysermc.geyser.GeyserLogger;
import java.util.logging.Level;
import java.util.logging.Logger;
+@RequiredArgsConstructor
public class GeyserBungeeLogger implements GeyserLogger {
private final Logger logger;
@Getter @Setter
private boolean debug;
- public GeyserBungeeLogger(Logger logger, boolean debug) {
- this.logger = logger;
- this.debug = debug;
- }
-
@Override
public void severe(String message) {
logger.severe(message);
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
index c5d5cdacc..cd6b59f64 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java
@@ -32,11 +32,11 @@ import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.ProtocolConstants;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
-import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
+import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
@@ -58,62 +58,96 @@ import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
private GeyserCommandManager geyserCommandManager;
private GeyserBungeeConfiguration geyserConfig;
private GeyserBungeeInjector geyserInjector;
- private GeyserBungeeLogger geyserLogger;
+ private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger());
private IGeyserPingPassthrough geyserBungeePingPassthrough;
private GeyserImpl geyser;
- private static boolean INITIALIZED = false;
-
- @SuppressWarnings({"JavaReflectionMemberAccess", "ResultOfMethodCallIgnored"})
@Override
public void onLoad() {
+ onGeyserInitialize();
+ }
+
+ @Override
+ public void onGeyserInitialize() {
GeyserLocale.init(this);
// Copied from ViaVersion.
// https://github.com/ViaVersion/ViaVersion/blob/b8072aad86695cc8ec6f5e4103e43baf3abf6cc5/bungee/src/main/java/us/myles/ViaVersion/BungeePlugin.java#L43
try {
- ProtocolConstants.class.getField("MINECRAFT_1_20_3");
+ ProtocolConstants.class.getField("MINECRAFT_1_21");
} catch (NoSuchFieldException e) {
- getLogger().warning(" / \\");
- getLogger().warning(" / \\");
- getLogger().warning(" / | \\");
- getLogger().warning(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName()));
- getLogger().warning(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
- getLogger().warning(" / o \\");
- getLogger().warning("/_____________\\");
+ geyserLogger.error(" / \\");
+ geyserLogger.error(" / \\");
+ geyserLogger.error(" / | \\");
+ geyserLogger.error(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName()));
+ geyserLogger.error(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
+ geyserLogger.error(" / o \\");
+ geyserLogger.error("/_____________\\");
}
- if (!getDataFolder().exists())
- getDataFolder().mkdir();
-
- try {
- if (!getDataFolder().exists())
- getDataFolder().mkdir();
- File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"),
- "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
- this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class);
- } catch (IOException ex) {
- getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
- ex.printStackTrace();
+ if (!this.loadConfig()) {
return;
}
-
- this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode());
+ this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
-
this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this);
+ this.geyserInjector = new GeyserBungeeInjector(this);
}
@Override
public void onEnable() {
+ // Big hack - Bungee does not provide us an event to listen to, so schedule a repeating
+ // task that waits for a field to be filled which is set after the plugin enable
+ // process is complete
+ this.awaitStartupCompletion(0);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void awaitStartupCompletion(int tries) {
+ // After 20 tries give up waiting. This will happen just after 3 minutes approximately
+ if (tries >= 20) {
+ this.geyserLogger.warning("BungeeCord plugin startup is taking abnormally long, so Geyser is starting now. " +
+ "If all your plugins are loaded properly, this is a bug! " +
+ "If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times.");
+ this.onGeyserEnable();
+ return;
+ }
+
+ try {
+ Field listenersField = BungeeCord.getInstance().getClass().getDeclaredField("listeners");
+ listenersField.setAccessible(true);
+
+ Collection listeners = (Collection) listenersField.get(BungeeCord.getInstance());
+ if (listeners.isEmpty()) {
+ this.getProxy().getScheduler().schedule(this, this::onGeyserEnable, tries, TimeUnit.SECONDS);
+ } else {
+ this.awaitStartupCompletion(++tries);
+ }
+ } catch (NoSuchFieldException | IllegalAccessException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ public void onGeyserEnable() {
+ if (GeyserImpl.getInstance().isReloading()) {
+ if (!loadConfig()) {
+ return;
+ }
+ this.geyserLogger.setDebug(geyserConfig.isDebugMode());
+ GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
+ } else {
+ // For consistency with other platforms - create command manager before GeyserImpl#start()
+ // This ensures the command events are called before the item/block ones are
+ this.geyserCommandManager = new GeyserCommandManager(geyser);
+ this.geyserCommandManager.init();
+ }
// Force-disable query if enabled, or else Geyser won't enable
for (ListenerInfo info : getProxy().getConfig().getListeners()) {
@@ -133,54 +167,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
}
- // Big hack - Bungee does not provide us an event to listen to, so schedule a repeating
- // task that waits for a field to be filled which is set after the plugin enable
- // process is complete
- if (!INITIALIZED) {
- this.awaitStartupCompletion(0);
- } else {
- // No need to "wait" for startup completion, just start Geyser - we're reloading.
- this.postStartup();
- }
- }
+ GeyserImpl.start();
- @SuppressWarnings("unchecked")
- private void awaitStartupCompletion(int tries) {
- // After 20 tries give up waiting. This will happen
- // just after 3 minutes approximately
- if (tries >= 20) {
- this.geyserLogger.warning("BungeeCord plugin startup is taking abnormally long, so Geyser is starting now. " +
- "If all your plugins are loaded properly, this is a bug! " +
- "If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times.");
- this.postStartup();
+ if (geyserConfig.isLegacyPingPassthrough()) {
+ this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
+ } else {
+ this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
+ }
+
+ // No need to re-register commands or re-init injector when reloading
+ if (GeyserImpl.getInstance().isReloading()) {
return;
}
- try {
- Field listenersField = BungeeCord.getInstance().getClass().getDeclaredField("listeners");
- listenersField.setAccessible(true);
-
- Collection listeners = (Collection) listenersField.get(BungeeCord.getInstance());
- if (listeners.isEmpty()) {
- this.getProxy().getScheduler().schedule(this, this::postStartup, tries, TimeUnit.SECONDS);
- } else {
- this.awaitStartupCompletion(++tries);
- }
- } catch (NoSuchFieldException | IllegalAccessException ex) {
- ex.printStackTrace();
- }
- }
-
- private void postStartup() {
- GeyserImpl.start();
-
- if (!INITIALIZED) {
- this.geyserInjector = new GeyserBungeeInjector(this);
- this.geyserInjector.initializeLocalChannel(this);
- }
-
- this.geyserCommandManager = new GeyserCommandManager(geyser);
- this.geyserCommandManager.init();
+ this.geyserInjector.initializeLocalChannel(this);
this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands()));
for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
@@ -191,18 +191,17 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands));
}
-
- if (geyserConfig.isLegacyPingPassthrough()) {
- this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
- } else {
- this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
- }
-
- INITIALIZED = true;
}
@Override
- public void onDisable() {
+ public void onGeyserDisable() {
+ if (geyser != null) {
+ geyser.disable();
+ }
+ }
+
+ @Override
+ public void onGeyserShutdown() {
if (geyser != null) {
geyser.shutdown();
}
@@ -211,6 +210,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
}
+ @Override
+ public void onDisable() {
+ this.onGeyserShutdown();
+ }
+
@Override
public GeyserBungeeConfiguration getGeyserConfig() {
return geyserConfig;
@@ -278,4 +282,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
.map(info -> (InetSocketAddress) info.getSocketAddress())
.findFirst();
}
+
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ private boolean loadConfig() {
+ try {
+ if (!getDataFolder().exists()) //noinspection ResultOfMethodCallIgnored
+ getDataFolder().mkdir();
+ File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"),
+ "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
+ this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class);
+ } catch (IOException ex) {
+ geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
+ ex.printStackTrace();
+ return false;
+ }
+ return true;
+ }
}
diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts
deleted file mode 100644
index 66af130b3..000000000
--- a/bootstrap/fabric/build.gradle.kts
+++ /dev/null
@@ -1,124 +0,0 @@
-import net.fabricmc.loom.task.RemapJarTask
-
-plugins {
- id("fabric-loom") version "1.0-SNAPSHOT"
- id("com.modrinth.minotaur") version "2.+"
-}
-
-dependencies {
- //to change the versions see the gradle.properties file
- minecraft(libs.fabric.minecraft)
- mappings(loom.officialMojangMappings())
- modImplementation(libs.fabric.loader)
-
- // Fabric API. This is technically optional, but you probably want it anyway.
- modImplementation(libs.fabric.api)
-
- // This should be in the libs TOML, but something about modImplementation AND include just doesn't work
- include(modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT"))
-
- // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
- // You may need to force-disable transitiveness on them.
-
- api(projects.core)
- shadow(projects.core) {
- exclude(group = "com.google.guava", module = "guava")
- exclude(group = "com.google.code.gson", module = "gson")
- exclude(group = "org.slf4j")
- exclude(group = "com.nukkitx.fastutil")
- exclude(group = "io.netty.incubator")
- }
-}
-
-loom {
- mixin.defaultRefmapName.set("geyser-fabric-refmap.json")
-}
-
-repositories {
- mavenLocal()
- maven("https://repo.opencollab.dev/maven-releases/")
- maven("https://repo.opencollab.dev/maven-snapshots/")
- maven("https://jitpack.io")
- maven("https://oss.sonatype.org/content/repositories/snapshots/")
- maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
-}
-
-application {
- mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain")
-}
-
-tasks {
- // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
- // if it is present.
- // If you remove this task, sources will not be generated.
- sourcesJar {
- archiveClassifier.set("sources")
- from(sourceSets.main.get().allSource)
- }
-
- shadowJar {
- // Mirrors the example fabric project, otherwise tons of dependencies are shaded that shouldn't be
- configurations = listOf(project.configurations.shadow.get())
- // The remapped shadowJar is the final desired Geyser-Fabric.jar
- archiveVersion.set(project.version.toString())
- archiveClassifier.set("shaded")
-
- relocate("org.objectweb.asm", "org.geysermc.relocate.asm")
- relocate("org.yaml", "org.geysermc.relocate.yaml") // https://github.com/CardboardPowered/cardboard/issues/139
- relocate("com.fasterxml.jackson", "org.geysermc.relocate.jackson")
- relocate("net.kyori", "org.geysermc.relocate.kyori")
-
- dependencies {
- // Exclude everything EXCEPT some DNS stuff required for HAProxy
- exclude(dependency("io.netty:netty-transport-classes-epoll:.*"))
- exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
- exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
- exclude(dependency("io.netty:netty-transport-classes-kqueue:.*"))
- exclude(dependency("io.netty:netty-transport-native-kqueue:.*"))
- exclude(dependency("io.netty:netty-handler:.*"))
- exclude(dependency("io.netty:netty-common:.*"))
- exclude(dependency("io.netty:netty-buffer:.*"))
- exclude(dependency("io.netty:netty-resolver:.*"))
- exclude(dependency("io.netty:netty-transport:.*"))
- exclude(dependency("io.netty:netty-codec:.*"))
- exclude(dependency("io.netty:netty-resolver-dns:.*"))
- exclude(dependency("io.netty:netty-resolver-dns-native-macos:.*"))
- }
- }
-
- remapJar {
- dependsOn(shadowJar)
- inputFile.set(shadowJar.get().archiveFile)
- archiveBaseName.set("Geyser-Fabric")
- archiveVersion.set("")
- archiveClassifier.set("")
- }
-
- register("remapModrinthJar", RemapJarTask::class) {
- dependsOn(shadowJar)
- inputFile.set(shadowJar.get().archiveFile)
- archiveBaseName.set("geyser-fabric")
- archiveVersion.set(project.version.toString() + "+build." + System.getenv("GITHUB_RUN_NUMBER"))
- archiveClassifier.set("")
- }
-}
-
-modrinth {
- token.set(System.getenv("MODRINTH_TOKEN")) // Even though this is the default value, apparently this prevents GitHub Actions caching the token?
- projectId.set("wKkoqHrH")
- versionNumber.set(project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER"))
- versionType.set("beta")
- changelog.set("A changelog can be found at https://github.com/GeyserMC/Geyser/commits")
-
- syncBodyFrom.set(rootProject.file("README.md").readText())
-
- uploadFile.set(tasks.getByPath("remapModrinthJar"))
- gameVersions.addAll("1.20.4")
-
- loaders.add("fabric")
- failSilently.set(true)
-
- dependencies {
- required.project("fabric-api")
- }
-}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java
deleted file mode 100644
index 9d7b81831..000000000
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * @author GeyserMC
- * @link https://github.com/GeyserMC/Geyser
- */
-
-package org.geysermc.geyser.platform.fabric.world;
-
-import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
-import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
-import me.lucko.fabric.api.permissions.v0.Permissions;
-import net.minecraft.core.BlockPos;
-import net.minecraft.nbt.*;
-import net.minecraft.server.MinecraftServer;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.WritableBookItem;
-import net.minecraft.world.item.WrittenBookItem;
-import net.minecraft.world.level.block.entity.BannerBlockEntity;
-import net.minecraft.world.level.block.entity.BlockEntity;
-import net.minecraft.world.level.block.entity.LecternBlockEntity;
-import net.minecraft.world.level.chunk.LevelChunk;
-import org.checkerframework.checker.nullness.qual.NonNull;
-import org.cloudburstmc.math.vector.Vector3i;
-import org.cloudburstmc.nbt.NbtMap;
-import org.cloudburstmc.nbt.NbtMapBuilder;
-import org.cloudburstmc.nbt.NbtType;
-import org.geysermc.erosion.util.LecternUtils;
-import org.geysermc.geyser.level.GeyserWorldManager;
-import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.util.BlockEntityUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-
-public class GeyserFabricWorldManager extends GeyserWorldManager {
- private final MinecraftServer server;
-
- public GeyserFabricWorldManager(MinecraftServer server) {
- this.server = server;
- }
-
- @Override
- public boolean shouldExpectLecternHandled(GeyserSession session) {
- return true;
- }
-
- @Override
- public void sendLecternData(GeyserSession session, int x, int z, List blockEntityInfos) {
- server.execute(() -> {
- ServerPlayer player = getPlayer(session);
- if (player == null) {
- return;
- }
-
- //noinspection resource - level() is just a getter
- LevelChunk chunk = player.level().getChunk(x, z);
- final int chunkBlockX = x << 4;
- final int chunkBlockZ = z << 4;
- //noinspection ForLoopReplaceableByForEach - avoid constructing iterator
- for (int i = 0; i < blockEntityInfos.size(); i++) {
- BlockEntityInfo blockEntityInfo = blockEntityInfos.get(i);
- BlockEntity blockEntity = chunk.getBlockEntity(new BlockPos(chunkBlockX + blockEntityInfo.getX(),
- blockEntityInfo.getY(), chunkBlockZ + blockEntityInfo.getZ()));
- sendLecternData(session, blockEntity, true);
- }
- });
- }
-
- @Override
- public void sendLecternData(GeyserSession session, int x, int y, int z) {
- server.execute(() -> {
- ServerPlayer player = getPlayer(session);
- if (player == null) {
- return;
- }
- //noinspection resource - level() is just a getter
- BlockEntity blockEntity = player.level().getBlockEntity(new BlockPos(x, y, z));
- sendLecternData(session, blockEntity, false);
- });
- }
-
- private void sendLecternData(GeyserSession session, BlockEntity blockEntity, boolean isChunkLoad) {
- if (!(blockEntity instanceof LecternBlockEntity lectern)) {
- return;
- }
-
- int x = blockEntity.getBlockPos().getX();
- int y = blockEntity.getBlockPos().getY();
- int z = blockEntity.getBlockPos().getZ();
-
- if (!lectern.hasBook()) {
- if (!isChunkLoad) {
- BlockEntityUtils.updateBlockEntity(session, LecternUtils.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z));
- }
- return;
- }
-
- ItemStack book = lectern.getBook();
- int pageCount = WrittenBookItem.getPageCount(book);
- boolean hasBookPages = pageCount > 0;
- NbtMapBuilder lecternTag = LecternUtils.getBaseLecternTag(x, y, z, hasBookPages ? pageCount : 1);
- lecternTag.putInt("page", lectern.getPage() / 2);
- NbtMapBuilder bookTag = NbtMap.builder()
- .putByte("Count", (byte) book.getCount())
- .putShort("Damage", (short) 0)
- .putString("Name", "minecraft:writable_book");
- List pages = new ArrayList<>(hasBookPages ? pageCount : 1);
- if (hasBookPages && WritableBookItem.makeSureTagIsValid(book.getTag())) {
- ListTag listTag = book.getTag().getList("pages", 8);
-
- for (int i = 0; i < listTag.size(); i++) {
- String page = listTag.getString(i);
- NbtMapBuilder pageBuilder = NbtMap.builder()
- .putString("photoname", "")
- .putString("text", page);
- pages.add(pageBuilder.build());
- }
- } else {
- // Empty page
- NbtMapBuilder pageBuilder = NbtMap.builder()
- .putString("photoname", "")
- .putString("text", "");
- pages.add(pageBuilder.build());
- }
-
- bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build());
- lecternTag.putCompound("book", bookTag.build());
- NbtMap blockEntityTag = lecternTag.build();
- BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z));
- }
-
- @Override
- public boolean hasPermission(GeyserSession session, String permission) {
- ServerPlayer player = getPlayer(session);
- return Permissions.check(player, permission);
- }
-
- @Override
- public GameMode getDefaultGameMode(GeyserSession session) {
- return GameMode.byId(server.getDefaultGameType().getId());
- }
-
- @NonNull
- @Override
- public CompletableFuture getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {
- CompletableFuture future = new CompletableFuture<>();
- server.execute(() -> {
- ServerPlayer player = getPlayer(session);
- if (player == null) {
- future.complete(null);
- return;
- }
-
- BlockPos pos = new BlockPos(x, y, z);
- // Don't create a new block entity if invalid
- //noinspection resource - level() is just a getter
- BlockEntity blockEntity = player.level().getChunkAt(pos).getBlockEntity(pos);
- if (blockEntity instanceof BannerBlockEntity banner) {
- // Potentially exposes other NBT data? But we need to get the NBT data for the banner patterns *and*
- // the banner might have a custom name, both of which a Java client knows and caches
- ItemStack itemStack = banner.getItem();
- var tag = OpenNbtTagVisitor.convert("", itemStack.getOrCreateTag());
-
- future.complete(tag);
- return;
- }
- future.complete(null);
- });
- return future;
- }
-
- private ServerPlayer getPlayer(GeyserSession session) {
- return server.getPlayerList().getPlayer(session.getPlayerEntity().getUuid());
- }
-
- // Future considerations: option to clone; would affect arrays
- private static class OpenNbtTagVisitor implements TagVisitor {
- private String currentKey;
- private final com.github.steveice10.opennbt.tag.builtin.CompoundTag root;
- private com.github.steveice10.opennbt.tag.builtin.Tag currentTag;
-
- OpenNbtTagVisitor(String key) {
- root = new com.github.steveice10.opennbt.tag.builtin.CompoundTag(key);
- }
-
- @Override
- public void visitString(StringTag stringTag) {
- currentTag = new com.github.steveice10.opennbt.tag.builtin.StringTag(currentKey, stringTag.getAsString());
- }
-
- @Override
- public void visitByte(ByteTag byteTag) {
- currentTag = new com.github.steveice10.opennbt.tag.builtin.ByteTag(currentKey, byteTag.getAsByte());
- }
-
- @Override
- public void visitShort(ShortTag shortTag) {
- currentTag = new com.github.steveice10.opennbt.tag.builtin.ShortTag(currentKey, shortTag.getAsShort());
- }
-
- @Override
- public void visitInt(IntTag intTag) {
- currentTag = new com.github.steveice10.opennbt.tag.builtin.IntTag(currentKey, intTag.getAsInt());
- }
-
- @Override
- public void visitLong(LongTag longTag) {
- currentTag = new com.github.steveice10.opennbt.tag.builtin.LongTag(currentKey, longTag.getAsLong());
- }
-
- @Override
- public void visitFloat(FloatTag floatTag) {
- currentTag = new com.github.steveice10.opennbt.tag.builtin.FloatTag(currentKey, floatTag.getAsFloat());
- }
-
- @Override
- public void visitDouble(DoubleTag doubleTag) {
- currentTag = new com.github.steveice10.opennbt.tag.builtin.DoubleTag(currentKey, doubleTag.getAsDouble());
- }
-
- @Override
- public void visitByteArray(ByteArrayTag byteArrayTag) {
- currentTag = new com.github.steveice10.opennbt.tag.builtin.ByteArrayTag(currentKey, byteArrayTag.getAsByteArray());
- }
-
- @Override
- public void visitIntArray(IntArrayTag intArrayTag) {
- currentTag = new com.github.steveice10.opennbt.tag.builtin.IntArrayTag(currentKey, intArrayTag.getAsIntArray());
- }
-
- @Override
- public void visitLongArray(LongArrayTag longArrayTag) {
- currentTag = new com.github.steveice10.opennbt.tag.builtin.LongArrayTag(currentKey, longArrayTag.getAsLongArray());
- }
-
- @Override
- public void visitList(ListTag listTag) {
- var newList = new com.github.steveice10.opennbt.tag.builtin.ListTag(currentKey);
- for (Tag tag : listTag) {
- currentKey = "";
- tag.accept(this);
- newList.add(currentTag);
- }
- currentTag = newList;
- }
-
- @Override
- public void visitCompound(@NonNull CompoundTag compoundTag) {
- currentTag = convert(currentKey, compoundTag);
- }
-
- private static com.github.steveice10.opennbt.tag.builtin.CompoundTag convert(String name, CompoundTag compoundTag) {
- OpenNbtTagVisitor visitor = new OpenNbtTagVisitor(name);
- for (String key : compoundTag.getAllKeys()) {
- visitor.currentKey = key;
- Tag tag = Objects.requireNonNull(compoundTag.get(key));
- tag.accept(visitor);
- visitor.root.put(visitor.currentTag);
- }
- return visitor.root;
- }
-
- @Override
- public void visitEnd(@NonNull EndTag endTag) {
- }
- }
-}
diff --git a/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json b/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json
deleted file mode 100644
index aeb051809..000000000
--- a/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "required": true,
- "package": "org.geysermc.geyser.platform.fabric.mixin",
- "compatibilityLevel": "JAVA_16",
- "refmap": "geyser-fabric-refmap.json",
- "client": [
- "client.IntegratedServerMixin"
- ],
- "server": [
- "server.MinecraftDedicatedServerMixin"
- ],
- "injectors": {
- "defaultRequire": 1
- }
-}
diff --git a/bootstrap/mod/build.gradle.kts b/bootstrap/mod/build.gradle.kts
new file mode 100644
index 000000000..32224d00b
--- /dev/null
+++ b/bootstrap/mod/build.gradle.kts
@@ -0,0 +1,22 @@
+architectury {
+ common("neoforge", "fabric")
+}
+
+loom {
+ mixin.defaultRefmapName.set("geyser-refmap.json")
+}
+
+afterEvaluate {
+ // We don't need these
+ tasks.named("remapModrinthJar").configure {
+ enabled = false
+ }
+}
+
+dependencies {
+ api(projects.core)
+ compileOnly(libs.mixin)
+
+ // Only here to suppress "unknown enum constant EnvType.CLIENT" warnings. DO NOT USE!
+ compileOnly(libs.fabric.loader)
+}
\ No newline at end of file
diff --git a/bootstrap/mod/fabric/build.gradle.kts b/bootstrap/mod/fabric/build.gradle.kts
new file mode 100644
index 000000000..25bd0af9d
--- /dev/null
+++ b/bootstrap/mod/fabric/build.gradle.kts
@@ -0,0 +1,70 @@
+plugins {
+ application
+}
+
+architectury {
+ platformSetupLoomIde()
+ fabric()
+}
+
+val includeTransitive: Configuration = configurations.getByName("includeTransitive")
+
+dependencies {
+ modImplementation(libs.fabric.loader)
+ modApi(libs.fabric.api)
+
+ api(project(":mod", configuration = "namedElements"))
+ shadow(project(path = ":mod", configuration = "transformProductionFabric")) {
+ isTransitive = false
+ }
+ shadow(projects.core) { isTransitive = false }
+ includeTransitive(projects.core)
+
+ // These are NOT transitively included, and instead shadowed + relocated.
+ // Avoids fabric complaining about non-SemVer versioning
+ shadow(libs.protocol.connection) { isTransitive = false }
+ shadow(libs.protocol.common) { isTransitive = false }
+ shadow(libs.protocol.codec) { isTransitive = false }
+ shadow(libs.mcauthlib) { isTransitive = false }
+ shadow(libs.raknet) { isTransitive = false }
+
+ // Consequences of shading + relocating mcauthlib: shadow/relocate mcpl!
+ shadow(libs.mcprotocollib) { isTransitive = false }
+
+ // Since we also relocate cloudburst protocol: shade erosion common
+ shadow(libs.erosion.common) { isTransitive = false }
+
+ // Let's shade in our own api/common module
+ shadow(projects.api) { isTransitive = false }
+ shadow(projects.common) { isTransitive = false }
+
+ // Permissions
+ modImplementation(libs.fabric.permissions)
+ include(libs.fabric.permissions)
+}
+
+application {
+ mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain")
+}
+
+relocate("org.cloudburstmc.netty")
+relocate("org.cloudburstmc.protocol")
+relocate("com.github.steveice10.mc.auth")
+
+tasks {
+ remapJar {
+ archiveBaseName.set("Geyser-Fabric")
+ }
+
+ remapModrinthJar {
+ archiveBaseName.set("geyser-fabric")
+ }
+}
+
+modrinth {
+ loaders.add("fabric")
+ uploadFile.set(tasks.getByPath("remapModrinthJar"))
+ dependencies {
+ required.project("fabric-api")
+ }
+}
\ No newline at end of file
diff --git a/bootstrap/mod/fabric/gradle.properties b/bootstrap/mod/fabric/gradle.properties
new file mode 100644
index 000000000..90ee7a259
--- /dev/null
+++ b/bootstrap/mod/fabric/gradle.properties
@@ -0,0 +1 @@
+loom.platform=fabric
\ No newline at end of file
diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java
new file mode 100644
index 000000000..c363ade8f
--- /dev/null
+++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.fabric;
+
+import me.lucko.fabric.api.permissions.v0.Permissions;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
+import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
+import net.fabricmc.loader.api.FabricLoader;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.world.entity.player.Player;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
+import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
+
+public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInitializer {
+
+ public GeyserFabricBootstrap() {
+ super(new GeyserFabricPlatform());
+ }
+
+ @Override
+ public void onInitialize() {
+ if (isServer()) {
+ // Set as an event, so we can get the proper IP and port if needed
+ ServerLifecycleEvents.SERVER_STARTED.register((server) -> {
+ this.setServer(server);
+ onGeyserEnable();
+ });
+ } else {
+ ClientLifecycleEvents.CLIENT_STOPPING.register(($)-> {
+ onGeyserShutdown();
+ });
+ }
+
+ // These are only registered once
+ ServerLifecycleEvents.SERVER_STOPPING.register((server) -> {
+ if (isServer()) {
+ onGeyserShutdown();
+ } else {
+ onGeyserDisable();
+ }
+ });
+
+ ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserModUpdateListener.onPlayReady(handler.getPlayer()));
+
+ this.onGeyserInitialize();
+ }
+
+ @Override
+ public boolean isServer() {
+ return FabricLoader.getInstance().getEnvironmentType().equals(EnvType.SERVER);
+ }
+
+ @Override
+ public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
+ return Permissions.check(source, permissionNode);
+ }
+
+ @Override
+ public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
+ return Permissions.check(source, permissionNode, permissionLevel);
+ }
+}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java
similarity index 100%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java
rename to bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java
similarity index 100%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java
rename to bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java
diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPlatform.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPlatform.java
new file mode 100644
index 000000000..4631ab493
--- /dev/null
+++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPlatform.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.fabric;
+
+import net.fabricmc.loader.api.FabricLoader;
+import net.fabricmc.loader.api.ModContainer;
+import net.minecraft.server.MinecraftServer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.api.util.PlatformType;
+import org.geysermc.geyser.dump.BootstrapDumpInfo;
+import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
+import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.Optional;
+
+public class GeyserFabricPlatform implements GeyserModPlatform {
+
+ private final ModContainer mod;
+
+ public GeyserFabricPlatform() {
+ this.mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow();
+ }
+
+ @Override
+ public @NonNull PlatformType platformType() {
+ return PlatformType.FABRIC;
+ }
+
+ @Override
+ public @NonNull String configPath() {
+ return "Geyser-Fabric";
+ }
+
+ @Override
+ public @NonNull Path dataFolder(@NonNull String modId) {
+ return FabricLoader.getInstance().getConfigDir().resolve(modId);
+ }
+
+ @Override
+ public @NonNull BootstrapDumpInfo dumpInfo(@NonNull MinecraftServer server) {
+ return new GeyserFabricDumpInfo(server);
+ }
+
+ @Override
+ public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap) {
+ Optional floodgate = FabricLoader.getInstance().getModContainer("floodgate");
+ if (floodgate.isPresent()) {
+ Path floodgateDataFolder = FabricLoader.getInstance().getConfigDir().resolve("floodgate");
+ bootstrap.getGeyserConfig().loadFloodgate(bootstrap, floodgateDataFolder);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public @Nullable InputStream resolveResource(@NonNull String resource) {
+ // We need to handle this differently, because Fabric shares the classloader across multiple mods
+ Path path = this.mod.findPath(resource).orElse(null);
+ if (path == null) {
+ return null;
+ }
+
+ try {
+ return path.getFileSystem()
+ .provider()
+ .newInputStream(path);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+}
diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/mod/fabric/src/main/resources/fabric.mod.json
similarity index 61%
rename from bootstrap/fabric/src/main/resources/fabric.mod.json
rename to bootstrap/mod/fabric/src/main/resources/fabric.mod.json
index a192109e2..262f9833a 100644
--- a/bootstrap/fabric/src/main/resources/fabric.mod.json
+++ b/bootstrap/mod/fabric/src/main/resources/fabric.mod.json
@@ -9,23 +9,22 @@
],
"contact": {
"website": "${url}",
- "repo": "https://github.com/GeyserMC/Geyser-Fabric"
+ "repo": "https://github.com/GeyserMC/Geyser"
},
"license": "MIT",
- "icon": "assets/geyser-fabric/icon.png",
+ "icon": "assets/geyser/icon.png",
"environment": "*",
"entrypoints": {
"main": [
- "org.geysermc.geyser.platform.fabric.GeyserFabricMod"
+ "org.geysermc.geyser.platform.fabric.GeyserFabricBootstrap"
]
},
"mixins": [
- "geyser-fabric.mixins.json"
+ "geyser.mixins.json"
],
"depends": {
- "fabricloader": ">=0.15.2",
+ "fabricloader": ">=0.15.11",
"fabric": "*",
- "minecraft": ">=1.20.4",
- "fabric-permissions-api-v0": "*"
+ "minecraft": ">=1.21"
}
}
diff --git a/bootstrap/mod/neoforge/build.gradle.kts b/bootstrap/mod/neoforge/build.gradle.kts
new file mode 100644
index 000000000..e0e7c2dfa
--- /dev/null
+++ b/bootstrap/mod/neoforge/build.gradle.kts
@@ -0,0 +1,59 @@
+plugins {
+ application
+}
+
+// This is provided by "org.cloudburstmc.math.mutable" too, so yeet.
+// NeoForge's class loader is *really* annoying.
+provided("org.cloudburstmc.math", "api")
+
+architectury {
+ platformSetupLoomIde()
+ neoForge()
+}
+
+val includeTransitive: Configuration = configurations.getByName("includeTransitive")
+
+dependencies {
+ // See https://github.com/google/guava/issues/6618
+ modules {
+ module("com.google.guava:listenablefuture") {
+ replacedBy("com.google.guava:guava", "listenablefuture is part of guava")
+ }
+ }
+
+ neoForge(libs.neoforge.minecraft)
+
+ api(project(":mod", configuration = "namedElements"))
+ shadow(project(path = ":mod", configuration = "transformProductionNeoForge")) {
+ isTransitive = false
+ }
+ shadow(projects.core) { isTransitive = false }
+
+ // Let's shade in our own api
+ shadow(projects.api) { isTransitive = false }
+
+ // cannot be shaded, since neoforge will complain if floodgate-neoforge tries to provide this
+ include(projects.common)
+
+ // Include all transitive deps of core via JiJ
+ includeTransitive(projects.core)
+}
+
+application {
+ mainClass.set("org.geysermc.geyser.platform.forge.GeyserNeoForgeMain")
+}
+
+tasks {
+ remapJar {
+ archiveBaseName.set("Geyser-NeoForge")
+ }
+
+ remapModrinthJar {
+ archiveBaseName.set("geyser-neoforge")
+ }
+}
+
+modrinth {
+ loaders.add("neoforge")
+ uploadFile.set(tasks.getByPath("remapModrinthJar"))
+}
\ No newline at end of file
diff --git a/bootstrap/mod/neoforge/gradle.properties b/bootstrap/mod/neoforge/gradle.properties
new file mode 100644
index 000000000..2914393db
--- /dev/null
+++ b/bootstrap/mod/neoforge/gradle.properties
@@ -0,0 +1 @@
+loom.platform=neoforge
\ No newline at end of file
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java
new file mode 100644
index 000000000..b97e42389
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.neoforge;
+
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.world.entity.player.Player;
+import net.neoforged.fml.ModContainer;
+import net.neoforged.fml.common.Mod;
+import net.neoforged.fml.loading.FMLLoader;
+import net.neoforged.neoforge.common.NeoForge;
+import net.neoforged.neoforge.event.GameShuttingDownEvent;
+import net.neoforged.neoforge.event.entity.player.PlayerEvent;
+import net.neoforged.neoforge.event.server.ServerStartedEvent;
+import net.neoforged.neoforge.event.server.ServerStoppingEvent;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
+import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
+
+@Mod(ModConstants.MOD_ID)
+public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
+
+ private final GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler();
+
+ public GeyserNeoForgeBootstrap(ModContainer container) {
+ super(new GeyserNeoForgePlatform(container));
+
+ if (isServer()) {
+ // Set as an event so we can get the proper IP and port if needed
+ NeoForge.EVENT_BUS.addListener(this::onServerStarted);
+ } else {
+ NeoForge.EVENT_BUS.addListener(this::onClientStopping);
+ }
+
+ NeoForge.EVENT_BUS.addListener(this::onServerStopping);
+ NeoForge.EVENT_BUS.addListener(this::onPlayerJoin);
+ NeoForge.EVENT_BUS.addListener(this.permissionHandler::onPermissionGather);
+
+ this.onGeyserInitialize();
+ }
+
+ private void onServerStarted(ServerStartedEvent event) {
+ this.setServer(event.getServer());
+ this.onGeyserEnable();
+ }
+
+ private void onServerStopping(ServerStoppingEvent event) {
+ if (isServer()) {
+ this.onGeyserShutdown();
+ } else {
+ this.onGeyserDisable();
+ }
+ }
+
+ private void onClientStopping(GameShuttingDownEvent ignored) {
+ this.onGeyserShutdown();
+ }
+
+ private void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) {
+ GeyserModUpdateListener.onPlayReady(event.getEntity());
+ }
+
+ @Override
+ public boolean isServer() {
+ return FMLLoader.getDist().isDedicatedServer();
+ }
+
+ @Override
+ public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
+ return this.permissionHandler.hasPermission(source, permissionNode);
+ }
+
+ @Override
+ public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
+ return this.permissionHandler.hasPermission(source, permissionNode, permissionLevel);
+ }
+}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java
new file mode 100644
index 000000000..623f68d3a
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.neoforge;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import net.minecraft.server.MinecraftServer;
+import net.neoforged.api.distmarker.Dist;
+import net.neoforged.fml.ModList;
+import net.neoforged.fml.loading.FMLLoader;
+import net.neoforged.neoforgespi.language.IModInfo;
+import org.geysermc.geyser.dump.BootstrapDumpInfo;
+import org.geysermc.geyser.text.AsteriskSerializer;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+@Getter
+public class GeyserNeoForgeDumpInfo extends BootstrapDumpInfo {
+
+ private final String platformName;
+ private final String platformVersion;
+ private final String minecraftVersion;
+ private final Dist dist;
+
+ @AsteriskSerializer.Asterisk(isIp = true)
+ private final String serverIP;
+ private final int serverPort;
+ private final boolean onlineMode;
+ private final List mods;
+
+ public GeyserNeoForgeDumpInfo(MinecraftServer server) {
+ this.platformName = FMLLoader.launcherHandlerName();
+ this.platformVersion = FMLLoader.versionInfo().neoForgeVersion();
+ this.minecraftVersion = FMLLoader.versionInfo().mcVersion();
+ this.dist = FMLLoader.getDist();
+ this.serverIP = server.getLocalIp() == null ? "unknown" : server.getLocalIp();
+ this.serverPort = server.getPort();
+ this.onlineMode = server.usesAuthentication();
+ this.mods = new ArrayList<>();
+
+ for (IModInfo mod : ModList.get().getMods()) {
+ this.mods.add(new ModInfo(
+ ModList.get().isLoaded(mod.getModId()),
+ mod.getModId(),
+ mod.getVersion().toString(),
+ mod.getModURL().map(URL::toString).orElse("")
+ ));
+ }
+ }
+
+ @Getter
+ @AllArgsConstructor
+ public static class ModInfo {
+ public boolean enabled;
+ public String name;
+ public String version;
+ public String url;
+ }
+}
\ No newline at end of file
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeMain.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeMain.java
new file mode 100644
index 000000000..70bac2a40
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeMain.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.neoforge;
+
+import org.geysermc.geyser.GeyserMain;
+
+public class GeyserNeoForgeMain extends GeyserMain {
+
+ public static void main(String[] args) {
+ new GeyserNeoForgeMain().displayMessage();
+ }
+
+ @Override
+ public String getPluginType() {
+ return "NeoForge";
+ }
+
+ @Override
+ public String getPluginFolder() {
+ return "mods";
+ }
+}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java
new file mode 100644
index 000000000..0a5f8f052
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.neoforge;
+
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.player.Player;
+import net.neoforged.neoforge.server.permission.PermissionAPI;
+import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
+import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey;
+import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
+import net.neoforged.neoforge.server.permission.nodes.PermissionType;
+import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.Constants;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.api.command.Command;
+import org.geysermc.geyser.command.GeyserCommandManager;
+
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.Map;
+
+public class GeyserNeoForgePermissionHandler {
+
+ private static final Constructor> PERMISSION_NODE_CONSTRUCTOR;
+
+ static {
+ try {
+ @SuppressWarnings("rawtypes")
+ Constructor constructor = PermissionNode.class.getDeclaredConstructor(
+ String.class,
+ PermissionType.class,
+ PermissionNode.PermissionResolver.class,
+ PermissionDynamicContextKey[].class
+ );
+ constructor.setAccessible(true);
+ PERMISSION_NODE_CONSTRUCTOR = constructor;
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Unable to construct PermissionNode!", e);
+ }
+ }
+
+ private final Map> permissionNodes = new HashMap<>();
+
+ public void onPermissionGather(PermissionGatherEvent.Nodes event) {
+ this.registerNode(Constants.UPDATE_PERMISSION, event);
+
+ GeyserCommandManager commandManager = GeyserImpl.getInstance().commandManager();
+ for (Map.Entry entry : commandManager.commands().entrySet()) {
+ Command command = entry.getValue();
+
+ // Don't register aliases
+ if (!command.name().equals(entry.getKey())) {
+ continue;
+ }
+
+ this.registerNode(command.permission(), event);
+ }
+
+ for (Map commands : commandManager.extensionCommands().values()) {
+ for (Map.Entry entry : commands.entrySet()) {
+ Command command = entry.getValue();
+
+ // Don't register aliases
+ if (!command.name().equals(entry.getKey())) {
+ continue;
+ }
+
+ this.registerNode(command.permission(), event);
+ }
+ }
+ }
+
+ public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) {
+ PermissionNode node = this.permissionNodes.get(permissionNode);
+ if (node == null) {
+ GeyserImpl.getInstance().getLogger().warning("Unable to find permission node " + permissionNode);
+ return false;
+ }
+
+ return PermissionAPI.getPermission((ServerPlayer) source, node);
+ }
+
+ public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) {
+ if (!source.isPlayer()) {
+ return true;
+ }
+ assert source.getPlayer() != null;
+ boolean permission = this.hasPermission(source.getPlayer(), permissionNode);
+ if (!permission) {
+ return source.getPlayer().hasPermissions(permissionLevel);
+ }
+
+ return true;
+ }
+
+ private void registerNode(String node, PermissionGatherEvent.Nodes event) {
+ PermissionNode permissionNode = this.createNode(node);
+
+ // NeoForge likes to crash if you try and register a duplicate node
+ if (!event.getNodes().contains(permissionNode)) {
+ event.addNodes(permissionNode);
+ this.permissionNodes.put(node, permissionNode);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private PermissionNode createNode(String node) {
+ // The typical constructors in PermissionNode require a
+ // mod id, which means our permission nodes end up becoming
+ // geyser_neoforge. instead of just . We work around
+ // this by using reflection to access the constructor that
+ // doesn't require a mod id or ResourceLocation.
+ try {
+ return (PermissionNode) PERMISSION_NODE_CONSTRUCTOR.newInstance(
+ node,
+ PermissionTypes.BOOLEAN,
+ (PermissionNode.PermissionResolver) (player, playerUUID, context) -> false,
+ new PermissionDynamicContextKey[0]
+ );
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to create permission node " + node, e);
+ }
+ }
+}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java
new file mode 100644
index 000000000..41562baf3
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.neoforge;
+
+import net.minecraft.server.MinecraftServer;
+import net.neoforged.fml.ModContainer;
+import net.neoforged.fml.ModList;
+import net.neoforged.fml.loading.FMLPaths;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.api.util.PlatformType;
+import org.geysermc.geyser.dump.BootstrapDumpInfo;
+import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
+import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class GeyserNeoForgePlatform implements GeyserModPlatform {
+
+ private final ModContainer container;
+
+ public GeyserNeoForgePlatform(ModContainer container) {
+ this.container = container;
+ }
+
+ @Override
+ public @NonNull PlatformType platformType() {
+ return PlatformType.NEOFORGE;
+ }
+
+ @Override
+ public @NonNull String configPath() {
+ return "Geyser-NeoForge";
+ }
+
+ @Override
+ public @NonNull Path dataFolder(@NonNull String modId) {
+ return FMLPaths.CONFIGDIR.get().resolve(modId);
+ }
+
+ @Override
+ public @NonNull BootstrapDumpInfo dumpInfo(@NonNull MinecraftServer server) {
+ return new GeyserNeoForgeDumpInfo(server);
+ }
+
+ @Override
+ public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap) {
+ if (ModList.get().isLoaded("floodgate")) {
+ Path floodgateDataFolder = FMLPaths.CONFIGDIR.get().resolve("floodgate");
+ bootstrap.getGeyserConfig().loadFloodgate(bootstrap, floodgateDataFolder);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public @Nullable InputStream resolveResource(@NonNull String resource) {
+ try {
+ Path path = container.getModInfo().getOwningFile().getFile().findResource(resource);
+ return Files.newInputStream(path);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/item/type/FlowerItem.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/ModConstants.java
similarity index 82%
rename from core/src/main/java/org/geysermc/geyser/item/type/FlowerItem.java
rename to bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/ModConstants.java
index c65eec1d2..aa72bb2a0 100644
--- a/core/src/main/java/org/geysermc/geyser/item/type/FlowerItem.java
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/ModConstants.java
@@ -23,11 +23,8 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.item.type;
+package org.geysermc.geyser.platform.neoforge;
-// If blocks are implemented, then this class is not needed.
-public class FlowerItem extends BlockItem {
- public FlowerItem(String javaIdentifier, Builder builder) {
- super(javaIdentifier, builder);
- }
+public class ModConstants {
+ public static final String MOD_ID = "geyser_neoforge";
}
diff --git a/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml
new file mode 100644
index 000000000..fa01bb6ec
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml
@@ -0,0 +1,25 @@
+modLoader="javafml"
+loaderVersion="[1,)"
+license="MIT"
+[[mods]]
+modId="geyser_neoforge"
+version="${version}"
+displayName="Geyser"
+displayURL="https://geysermc.org/"
+logoFile= "../assets/geyser/icon.png"
+authors="GeyserMC"
+description="${description}"
+[[mixins]]
+config = "geyser.mixins.json"
+[[dependencies.geyser_neoforge]]
+ modId="neoforge"
+ type="required"
+ versionRange="[21.0.0-beta,)"
+ ordering="NONE"
+ side="BOTH"
+[[dependencies.geyser_neoforge]]
+ modId="minecraft"
+ type="required"
+ versionRange="[1.21,)"
+ ordering="NONE"
+ side="BOTH"
\ No newline at end of file
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserChannelGetter.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserChannelGetter.java
new file mode 100644
index 000000000..8dc0026bf
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserChannelGetter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.mod;
+
+import io.netty.channel.ChannelFuture;
+
+import java.util.List;
+
+/**
+ * Represents a getter to the server channels in the connection listener class.
+ */
+public interface GeyserChannelGetter {
+
+ /**
+ * Returns the channels.
+ *
+ * @return The channels.
+ */
+ List geyser$getChannels();
+}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
similarity index 64%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java
rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
index 071409046..d7373f0a9 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
@@ -23,20 +23,17 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.platform.fabric;
+package org.geysermc.geyser.platform.mod;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.ModInitializer;
-import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
-import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
-import net.fabricmc.loader.api.FabricLoader;
-import net.fabricmc.loader.api.ModContainer;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.MinecraftServer;
-import org.apache.logging.log4j.LogManager;
+import net.minecraft.world.entity.player.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserBootstrap;
@@ -44,7 +41,6 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
-import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
@@ -52,95 +48,66 @@ import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
-import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandExecutor;
-import org.geysermc.geyser.platform.fabric.world.GeyserFabricWorldManager;
+import org.geysermc.geyser.platform.mod.command.GeyserModCommandExecutor;
+import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
+import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.net.SocketAddress;
import java.nio.file.Path;
import java.util.Map;
-import java.util.Optional;
import java.util.UUID;
-public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
- private static GeyserFabricMod instance;
+@RequiredArgsConstructor
+public abstract class GeyserModBootstrap implements GeyserBootstrap {
- private boolean reloading;
+ @Getter
+ private static GeyserModBootstrap instance;
+
+ private final GeyserModPlatform platform;
private GeyserImpl geyser;
- private ModContainer mod;
private Path dataFolder;
+
+ @Setter
private MinecraftServer server;
private GeyserCommandManager geyserCommandManager;
- private GeyserFabricConfiguration geyserConfig;
- private GeyserFabricLogger geyserLogger;
+ private GeyserModConfiguration geyserConfig;
+ private GeyserModInjector geyserInjector;
+ private final GeyserModLogger geyserLogger = new GeyserModLogger();
private IGeyserPingPassthrough geyserPingPassthrough;
private WorldManager geyserWorldManager;
@Override
- public void onInitialize() {
+ public void onGeyserInitialize() {
instance = this;
- mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow();
-
- this.onEnable();
- if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) {
- // Set as an event so we can get the proper IP and port if needed
- ServerLifecycleEvents.SERVER_STARTED.register(this::startGeyser);
- }
- }
-
- @Override
- public void onEnable() {
- dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric");
- if (!dataFolder.toFile().exists()) {
- //noinspection ResultOfMethodCallIgnored
- dataFolder.toFile().mkdir();
- }
-
- // Init dataFolder first as local language overrides call getConfigFolder()
+ dataFolder = this.platform.dataFolder(this.platform.configPath());
GeyserLocale.init(this);
-
- try {
- File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml",
- (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
- this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class);
- } catch (IOException ex) {
- LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
- ex.printStackTrace();
+ if (!loadConfig()) {
return;
}
-
- this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode());
-
+ this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
+ this.geyser = GeyserImpl.load(this.platform.platformType(), this);
- this.geyser = GeyserImpl.load(PlatformType.FABRIC, this);
-
- if (server == null) {
- // Server has yet to start
- // Register onDisable so players are properly kicked
- ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable());
-
- ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler));
- } else {
- // Server has started and this is a reload
- startGeyser(this.server);
- reloading = false;
- }
+ // Create command manager here, since the permission handler on neo needs it
+ this.geyserCommandManager = new GeyserCommandManager(geyser);
+ this.geyserCommandManager.init();
}
- /**
- * Initialize core Geyser.
- * A function, as it needs to be called in different places depending on if Geyser is being reloaded or not.
- *
- * @param server The minecraft server.
- */
- public void startGeyser(MinecraftServer server) {
- this.server = server;
+ public void onGeyserEnable() {
+ if (GeyserImpl.getInstance().isReloading()) {
+ if (!loadConfig()) {
+ return;
+ }
+ this.geyserLogger.setDebug(geyserConfig.isDebugMode());
+ GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
+ }
GeyserImpl.start();
@@ -150,20 +117,29 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger);
}
- this.geyserCommandManager = new GeyserCommandManager(geyser);
- this.geyserCommandManager.init();
+ // No need to re-register commands, or try to re-inject
+ if (GeyserImpl.getInstance().isReloading()) {
+ return;
+ }
- this.geyserWorldManager = new GeyserFabricWorldManager(server);
+ this.geyserWorldManager = new GeyserModWorldManager(server);
+
+ // We want to do this late in the server startup process to allow other mods
+ // To do their job injecting, then connect into *that*
+ this.geyserInjector = new GeyserModInjector(server, this.platform);
+ if (isServer()) {
+ this.geyserInjector.initializeLocalChannel(this);
+ }
// Start command building
// Set just "geyser" as the help command
- GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(geyser,
+ GeyserModCommandExecutor helpExecutor = new GeyserModCommandExecutor(geyser,
(GeyserCommand) geyser.commandManager().getCommands().get("help"));
LiteralArgumentBuilder builder = Commands.literal("geyser").executes(helpExecutor);
// Register all subcommands as valid
for (Map.Entry command : geyser.commandManager().getCommands().entrySet()) {
- GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(geyser, (GeyserCommand) command.getValue());
+ GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
builder.then(Commands.literal(command.getKey())
.executes(executor)
// Could also test for Bedrock but depending on when this is called it may backfire
@@ -183,12 +159,12 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
}
// Register help command for just "/"
- GeyserFabricCommandExecutor extensionHelpExecutor = new GeyserFabricCommandExecutor(geyser,
+ GeyserModCommandExecutor extensionHelpExecutor = new GeyserModCommandExecutor(geyser,
(GeyserCommand) extensionCommands.get("help"));
LiteralArgumentBuilder extCmdBuilder = Commands.literal(extensionMapEntry.getKey().description().id()).executes(extensionHelpExecutor);
for (Map.Entry command : extensionCommands.entrySet()) {
- GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(geyser, (GeyserCommand) command.getValue());
+ GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
extCmdBuilder.then(Commands.literal(command.getKey())
.executes(executor)
.requires(executor::testPermission)
@@ -201,18 +177,26 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
}
@Override
- public void onDisable() {
+ public void onGeyserDisable() {
+ if (geyser != null) {
+ geyser.disable();
+ }
+ }
+
+ @Override
+ public void onGeyserShutdown() {
if (geyser != null) {
geyser.shutdown();
geyser = null;
}
- if (!reloading) {
+ if (geyserInjector != null) {
+ geyserInjector.shutdown();
this.server = null;
}
}
@Override
- public GeyserConfiguration getGeyserConfig() {
+ public GeyserModConfiguration getGeyserConfig() {
return geyserConfig;
}
@@ -243,7 +227,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
@Override
public BootstrapDumpInfo getDumpInfo() {
- return new GeyserFabricDumpInfo(server);
+ return this.platform.dumpInfo(this.server);
}
@Override
@@ -259,43 +243,53 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
}
@Override
- public int getServerPort() {
- return ((GeyserServerPortGetter) server).geyser$getServerPort();
+ public SocketAddress getSocketAddress() {
+ return this.geyserInjector.getServerSocketAddress();
}
@Override
- public boolean testFloodgatePluginPresent() {
- Optional floodgate = FabricLoader.getInstance().getModContainer("floodgate");
- if (floodgate.isPresent()) {
- geyserConfig.loadFloodgate(this, floodgate.orElse(null));
- return true;
+ public int getServerPort() {
+ if (isServer()) {
+ return ((GeyserServerPortGetter) server).geyser$getServerPort();
+ } else {
+ // Set in the IntegratedServerMixin
+ return geyserConfig.getRemote().port();
}
- return false;
+ }
+
+ public abstract boolean isServer();
+
+ @Override
+ public boolean testFloodgatePluginPresent() {
+ return this.platform.testFloodgatePluginPresent(this);
}
@Nullable
@Override
public InputStream getResourceOrNull(String resource) {
- // We need to handle this differently, because Fabric shares the classloader across multiple mods
- Path path = this.mod.findPath(resource).orElse(null);
- if (path == null) {
- return null;
- }
+ return this.platform.resolveResource(resource);
+ }
+ public abstract boolean hasPermission(@NonNull Player source, @NonNull String permissionNode);
+
+ public abstract boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel);
+
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ private boolean loadConfig() {
try {
- return path.getFileSystem()
- .provider()
- .newInputStream(path);
- } catch (IOException e) {
- return null;
+ if (!dataFolder.toFile().exists()) {
+ //noinspection ResultOfMethodCallIgnored
+ dataFolder.toFile().mkdir();
+ }
+
+ File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml",
+ (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
+ this.geyserConfig = FileUtils.loadConfig(configFile, GeyserModConfiguration.class);
+ return true;
+ } catch (IOException ex) {
+ geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
+ ex.printStackTrace();
+ return false;
}
}
-
- public void setReloading(boolean reloading) {
- this.reloading = reloading;
- }
-
- public static GeyserFabricMod getInstance() {
- return instance;
- }
}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModCompressionDisabler.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModCompressionDisabler.java
new file mode 100644
index 000000000..631a21510
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModCompressionDisabler.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.mod;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelOutboundHandlerAdapter;
+import io.netty.channel.ChannelPromise;
+import net.minecraft.network.protocol.login.ClientboundGameProfilePacket;
+import net.minecraft.network.protocol.login.ClientboundLoginCompressionPacket;
+
+/**
+ * Disables the compression packet (and the compression handlers from being added to the pipeline) for Geyser clients
+ * that won't be receiving the data over the network.
+ *
+ * As of 1.8 - 1.17.1, compression is enabled in the Netty pipeline by adding a listener after a packet is written.
+ * If we simply "cancel" or don't forward the packet, then the listener is never called.
+ */
+public class GeyserModCompressionDisabler extends ChannelOutboundHandlerAdapter {
+
+ @Override
+ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
+ Class> msgClass = msg.getClass();
+ // Don't let any compression packet get through
+ if (!ClientboundLoginCompressionPacket.class.isAssignableFrom(msgClass)) {
+ if (ClientboundGameProfilePacket.class.isAssignableFrom(msgClass)) {
+
+ // We're past the point that a compression packet can be sent, so we can safely yeet ourselves away
+ ctx.channel().pipeline().remove(this);
+ }
+ super.write(ctx, msg, promise);
+ }
+ }
+}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModConfiguration.java
similarity index 80%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java
rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModConfiguration.java
index f557d16c0..a24380bd6 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModConfiguration.java
@@ -23,23 +23,20 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.platform.fabric;
+package org.geysermc.geyser.platform.mod;
import com.fasterxml.jackson.annotation.JsonIgnore;
-import net.fabricmc.loader.api.FabricLoader;
-import net.fabricmc.loader.api.ModContainer;
import org.geysermc.geyser.FloodgateKeyLoader;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
import java.nio.file.Path;
-public class GeyserFabricConfiguration extends GeyserJacksonConfiguration {
+public class GeyserModConfiguration extends GeyserJacksonConfiguration {
@JsonIgnore
private Path floodgateKeyPath;
- public void loadFloodgate(GeyserFabricMod geyser, ModContainer floodgate) {
+ public void loadFloodgate(GeyserModBootstrap geyser, Path floodgateDataFolder) {
Path geyserDataFolder = geyser.getConfigFolder();
- Path floodgateDataFolder = floodgate != null ? FabricLoader.getInstance().getConfigDir().resolve("floodgate") : null;
floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, geyser.getGeyserLogger());
}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModInjector.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModInjector.java
new file mode 100644
index 000000000..624eccb3f
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModInjector.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.mod;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.DefaultEventLoopGroup;
+import io.netty.channel.local.LocalAddress;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.network.ServerConnectionListener;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.GeyserBootstrap;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.network.netty.GeyserInjector;
+import org.geysermc.geyser.network.netty.LocalServerChannelWrapper;
+import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class GeyserModInjector extends GeyserInjector {
+
+ private final MinecraftServer server;
+ private final GeyserModPlatform platform;
+ private DefaultEventLoopGroup eventLoopGroup;
+
+ /**
+ * Used to uninject ourselves on shutdown.
+ */
+ private List allServerChannels;
+
+ public GeyserModInjector(MinecraftServer server, GeyserModPlatform platform) {
+ this.server = server;
+ this.platform = platform;
+ }
+
+ @Override
+ protected void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception {
+ ServerConnectionListener connection = this.server.getConnection();
+
+ // Find the channel that Minecraft uses to listen to connections
+ ChannelFuture listeningChannel = null;
+ this.allServerChannels = ((GeyserChannelGetter) connection).geyser$getChannels();
+ for (ChannelFuture o : allServerChannels) {
+ listeningChannel = o;
+ break;
+ }
+
+ if (listeningChannel == null) {
+ throw new RuntimeException("Unable to find listening channel!");
+ }
+
+ // Making this a function prevents childHandler from being treated as a non-final variable
+ ChannelInitializer childHandler = getChildHandler(bootstrap, listeningChannel);
+ // This method is what initializes the connection in Java Edition, after Netty is all set.
+ Method initChannel = childHandler.getClass().getDeclaredMethod("initChannel", Channel.class);
+ initChannel.setAccessible(true);
+
+ // Separate variable so we can shut it down later
+ eventLoopGroup = new DefaultEventLoopGroup(0, new DefaultThreadFactory("Geyser " + this.platform.platformType().platformName() + " connection thread", Thread.MAX_PRIORITY));
+ ChannelFuture channelFuture = (new ServerBootstrap()
+ .channel(LocalServerChannelWrapper.class)
+ .childHandler(new ChannelInitializer<>() {
+ @Override
+ protected void initChannel(@NonNull Channel ch) throws Exception {
+ initChannel.invoke(childHandler, ch);
+
+ int index = ch.pipeline().names().indexOf("encoder");
+ String baseName = index != -1 ? "encoder" : "outbound_config";
+
+ if (bootstrap.getGeyserConfig().isDisableCompression()) {
+ ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserModCompressionDisabler());
+ }
+ }
+ })
+ // Set to MAX_PRIORITY as MultithreadEventLoopGroup#newDefaultThreadFactory which DefaultEventLoopGroup implements does by default
+ .group(eventLoopGroup)
+ .localAddress(LocalAddress.ANY))
+ .bind()
+ .syncUninterruptibly();
+ // We don't need to add to the list, but plugins like ProtocolSupport and ProtocolLib that add to the main pipeline
+ // will work when we add to the list.
+ allServerChannels.add(channelFuture);
+ this.localChannel = channelFuture;
+ this.serverSocketAddress = channelFuture.channel().localAddress();
+ }
+
+ @SuppressWarnings("unchecked")
+ private ChannelInitializer getChildHandler(GeyserBootstrap bootstrap, ChannelFuture listeningChannel) {
+ List names = listeningChannel.channel().pipeline().names();
+ ChannelInitializer childHandler = null;
+ for (String name : names) {
+ ChannelHandler handler = listeningChannel.channel().pipeline().get(name);
+ try {
+ Field childHandlerField = handler.getClass().getDeclaredField("childHandler");
+ childHandlerField.setAccessible(true);
+ childHandler = (ChannelInitializer) childHandlerField.get(handler);
+ break;
+ } catch (Exception e) {
+ if (bootstrap.getGeyserConfig().isDebugMode()) {
+ bootstrap.getGeyserLogger().debug("The handler " + name + " isn't a ChannelInitializer. THIS ERROR IS SAFE TO IGNORE!");
+ e.printStackTrace();
+ }
+ }
+ }
+ if (childHandler == null) {
+ throw new RuntimeException();
+ }
+ return childHandler;
+ }
+
+ @Override
+ public void shutdown() {
+ if (this.allServerChannels != null) {
+ this.allServerChannels.remove(this.localChannel);
+ this.allServerChannels = null;
+ }
+
+ if (eventLoopGroup != null) {
+ try {
+ eventLoopGroup.shutdownGracefully().sync();
+ eventLoopGroup = null;
+ } catch (Exception e) {
+ GeyserImpl.getInstance().getLogger().error("Unable to shut down injector! " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ super.shutdown();
+ }
+}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java
similarity index 89%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java
rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java
index 180197f2d..9260288d7 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java
@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.platform.fabric;
+package org.geysermc.geyser.platform.mod;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@@ -32,15 +32,11 @@ import org.apache.logging.log4j.Logger;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.text.ChatColor;
-public class GeyserFabricLogger implements GeyserLogger {
- private final Logger logger = LogManager.getLogger("geyser-fabric");
+public class GeyserModLogger implements GeyserLogger {
+ private final Logger logger = LogManager.getLogger("geyser");
private boolean debug;
- public GeyserFabricLogger(boolean isDebug) {
- debug = isDebug;
- }
-
@Override
public void severe(String message) {
logger.fatal(message);
@@ -73,7 +69,7 @@ public class GeyserFabricLogger implements GeyserLogger {
@Override
public void sendMessage(Component message) {
- // As of Java Edition 1.19.2, Fabric's console doesn't natively support legacy format
+ // As of Java Edition 1.19.2, Minecraft's console doesn't natively support legacy format
String flattened = LegacyComponentSerializer.legacySection().serialize(message);
// Add the reset at the end, or else format will persist... forever.
// https://cdn.discordapp.com/attachments/573909525132738590/1033904509170225242/unknown.png
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java
similarity index 68%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java
rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java
index 1ea69cbe2..11ca0bc4f 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java
@@ -23,21 +23,22 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.platform.fabric;
+package org.geysermc.geyser.platform.mod;
-import me.lucko.fabric.api.permissions.v0.Permissions;
-import net.minecraft.server.network.ServerGamePacketListenerImpl;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.world.entity.player.Player;
import org.geysermc.geyser.Constants;
-import org.geysermc.geyser.platform.fabric.command.FabricCommandSender;
+import org.geysermc.geyser.platform.mod.command.ModCommandSender;
import org.geysermc.geyser.util.VersionCheckUtils;
-public final class GeyserFabricUpdateListener {
- public static void onPlayReady(ServerGamePacketListenerImpl handler) {
- if (Permissions.check(handler.player, Constants.UPDATE_PERMISSION, 2)) {
- VersionCheckUtils.checkForGeyserUpdate(() -> new FabricCommandSender(handler.player.createCommandSourceStack()));
+public final class GeyserModUpdateListener {
+ public static void onPlayReady(Player player) {
+ CommandSourceStack stack = player.createCommandSourceStack();
+ if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION, 2)) {
+ VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSender(stack));
}
}
- private GeyserFabricUpdateListener() {
+ private GeyserModUpdateListener() {
}
}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserServerPortGetter.java
similarity index 97%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java
rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserServerPortGetter.java
index 4f1c8b638..fad0d1678 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserServerPortGetter.java
@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.platform.fabric;
+package org.geysermc.geyser.platform.mod;
import net.minecraft.server.MinecraftServer;
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModPingPassthrough.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/ModPingPassthrough.java
similarity index 90%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModPingPassthrough.java
rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/ModPingPassthrough.java
index e74be7fb7..a2bbfa379 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModPingPassthrough.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/ModPingPassthrough.java
@@ -23,12 +23,13 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.platform.fabric;
+package org.geysermc.geyser.platform.mod;
import lombok.AllArgsConstructor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+import net.minecraft.core.RegistryAccess;
import net.minecraft.network.Connection;
import net.minecraft.network.PacketSendListener;
import net.minecraft.network.protocol.Packet;
@@ -39,10 +40,11 @@ import net.minecraft.network.protocol.status.ServerStatusPacketListener;
import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerStatusPacketListenerImpl;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.ping.GeyserPingInfo;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
-import org.jetbrains.annotations.Nullable;
import java.net.InetSocketAddress;
import java.util.Objects;
@@ -68,7 +70,7 @@ public class ModPingPassthrough implements IGeyserPingPassthrough {
StatusInterceptor connection = new StatusInterceptor();
ServerStatusPacketListener statusPacketListener = new ServerStatusPacketListenerImpl(status, connection);
- statusPacketListener.handleStatusRequest(new ServerboundStatusRequestPacket());
+ statusPacketListener.handleStatusRequest(ServerboundStatusRequestPacket.INSTANCE);
// mods like MiniMOTD (that inject into the above method) have now processed the response
status = Objects.requireNonNull(connection.status, "status response");
} catch (Exception e) {
@@ -78,7 +80,7 @@ public class ModPingPassthrough implements IGeyserPingPassthrough {
}
}
- String jsonDescription = net.minecraft.network.chat.Component.Serializer.toJson(status.description());
+ String jsonDescription = net.minecraft.network.chat.Component.Serializer.toJson(status.description(), RegistryAccess.EMPTY);
String legacyDescription = LEGACY_SERIALIZER.serialize(GSON_SERIALIZER.deserializeOr(jsonDescription, Component.empty()));
return new GeyserPingInfo(
@@ -100,7 +102,7 @@ public class ModPingPassthrough implements IGeyserPingPassthrough {
}
@Override
- public void send(Packet> packet, @Nullable PacketSendListener packetSendListener, boolean bl) {
+ public void send(@NonNull Packet> packet, @Nullable PacketSendListener packetSendListener, boolean bl) {
if (packet instanceof ClientboundStatusResponsePacket statusResponse) {
status = statusResponse.status();
}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java
similarity index 78%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java
rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java
index 732b28ca7..694dc732e 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java
@@ -23,32 +23,31 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.platform.fabric.command;
+package org.geysermc.geyser.platform.mod.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
-import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.commands.CommandSourceStack;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandExecutor;
-import org.geysermc.geyser.platform.fabric.GeyserFabricMod;
+import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Collections;
-public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implements Command {
+public class GeyserModCommandExecutor extends GeyserCommandExecutor implements Command {
private final GeyserCommand command;
- public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command) {
- super(connector, Collections.singletonMap(command.name(), command));
+ public GeyserModCommandExecutor(GeyserImpl geyser, GeyserCommand command) {
+ super(geyser, Collections.singletonMap(command.name(), command));
this.command = command;
}
public boolean testPermission(CommandSourceStack source) {
- return Permissions.check(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0);
+ return GeyserModBootstrap.getInstance().hasPermission(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0);
}
@Override
@@ -58,15 +57,12 @@ public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implement
public int runWithArgs(CommandContext context, String args) {
CommandSourceStack source = context.getSource();
- FabricCommandSender sender = new FabricCommandSender(source);
+ ModCommandSender sender = new ModCommandSender(source);
GeyserSession session = getGeyserSession(sender);
if (!testPermission(source)) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
return 0;
}
- if (this.command.name().equals("reload")) {
- GeyserFabricMod.getInstance().setReloading(true);
- }
if (command.isBedrockOnly() && session == null) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java
similarity index 85%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java
rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java
index 28875ec6e..5bebfae93 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java
@@ -23,25 +23,26 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.platform.fabric.command;
+package org.geysermc.geyser.platform.mod.command;
-import me.lucko.fabric.api.permissions.v0.Permissions;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandSource;
+import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.text.ChatColor;
import java.util.Objects;
-public class FabricCommandSender implements GeyserCommandSource {
+public class ModCommandSender implements GeyserCommandSource {
private final CommandSourceStack source;
- public FabricCommandSender(CommandSourceStack source) {
+ public ModCommandSender(CommandSourceStack source) {
this.source = source;
}
@@ -63,7 +64,7 @@ public class FabricCommandSender implements GeyserCommandSource {
public void sendMessage(net.kyori.adventure.text.Component message) {
if (source.getEntity() instanceof ServerPlayer player) {
String decoded = GsonComponentSerializer.gson().serialize(message);
- player.displayClientMessage(Objects.requireNonNull(Component.Serializer.fromJson(decoded)), false);
+ player.displayClientMessage(Objects.requireNonNull(Component.Serializer.fromJson(decoded, RegistryAccess.EMPTY)), false);
return;
}
GeyserCommandSource.super.sendMessage(message);
@@ -76,6 +77,6 @@ public class FabricCommandSender implements GeyserCommandSource {
@Override
public boolean hasPermission(String permission) {
- return Permissions.check(source, permission, source.getServer().getOperatorUserPermissionLevel());
+ return GeyserModBootstrap.getInstance().hasPermission(source, permission, source.getServer().getOperatorUserPermissionLevel());
}
}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/client/IntegratedServerMixin.java
similarity index 85%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java
rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/client/IntegratedServerMixin.java
index af11174dc..ece2f730a 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/client/IntegratedServerMixin.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,18 +23,16 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.platform.fabric.mixin.client;
+package org.geysermc.geyser.platform.mod.mixin.client;
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft;
import net.minecraft.client.server.IntegratedServer;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.GameType;
import org.geysermc.geyser.GeyserImpl;
-import org.geysermc.geyser.platform.fabric.GeyserFabricMod;
-import org.geysermc.geyser.platform.fabric.GeyserServerPortGetter;
+import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
+import org.geysermc.geyser.platform.mod.GeyserServerPortGetter;
import org.geysermc.geyser.text.GeyserLocale;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -45,7 +43,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Objects;
-@Environment(EnvType.CLIENT)
@Mixin(IntegratedServer.class)
public class IntegratedServerMixin implements GeyserServerPortGetter {
@Shadow
@@ -57,7 +54,10 @@ public class IntegratedServerMixin implements GeyserServerPortGetter {
private void onOpenToLan(GameType gameType, boolean cheatsAllowed, int port, CallbackInfoReturnable cir) {
if (cir.getReturnValueZ()) {
// If the LAN is opened, starts Geyser.
- GeyserFabricMod.getInstance().startGeyser((MinecraftServer) (Object) this);
+ GeyserModBootstrap instance = GeyserModBootstrap.getInstance();
+ instance.setServer((MinecraftServer) (Object) this);
+ instance.getGeyserConfig().getRemote().setPort(port);
+ instance.onGeyserEnable();
// Ensure player locale has been loaded, in case it's different from Java system language
GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode);
// Give indication that Geyser is loaded
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/DedicatedServerMixin.java
similarity index 76%
rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java
rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/DedicatedServerMixin.java
index 23e148775..3b809d321 100644
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/DedicatedServerMixin.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.platform.fabric.mixin.server;
+package org.geysermc.geyser.platform.mod.mixin.server;
import com.mojang.datafixers.DataFixer;
import net.minecraft.server.MinecraftServer;
@@ -33,14 +33,14 @@ import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.world.level.storage.LevelStorageSource;
-import org.geysermc.geyser.platform.fabric.GeyserServerPortGetter;
+import org.geysermc.geyser.platform.mod.GeyserServerPortGetter;
import org.spongepowered.asm.mixin.Mixin;
import java.net.Proxy;
@Mixin(DedicatedServer.class)
-public abstract class MinecraftDedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter {
- public MinecraftDedicatedServerMixin(Thread thread, LevelStorageSource.LevelStorageAccess levelStorageAccess, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer dataFixer, Services services, ChunkProgressListenerFactory chunkProgressListenerFactory) {
+public abstract class DedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter {
+ public DedicatedServerMixin(Thread thread, LevelStorageSource.LevelStorageAccess levelStorageAccess, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer dataFixer, Services services, ChunkProgressListenerFactory chunkProgressListenerFactory) {
super(thread, levelStorageAccess, packRepository, worldStem, proxy, dataFixer, services, chunkProgressListenerFactory);
}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/ServerConnectionListenerMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/ServerConnectionListenerMixin.java
new file mode 100644
index 000000000..52941d631
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/ServerConnectionListenerMixin.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.mod.mixin.server;
+
+import io.netty.channel.ChannelFuture;
+import net.minecraft.server.network.ServerConnectionListener;
+import org.geysermc.geyser.platform.mod.GeyserChannelGetter;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+import java.util.List;
+
+@Mixin(ServerConnectionListener.class)
+public abstract class ServerConnectionListenerMixin implements GeyserChannelGetter {
+
+ @Shadow @Final private List channels;
+
+ @Override
+ public List geyser$getChannels() {
+ return this.channels;
+ }
+}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/platform/GeyserModPlatform.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/platform/GeyserModPlatform.java
new file mode 100644
index 000000000..2f615591b
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/platform/GeyserModPlatform.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.mod.platform;
+
+import net.minecraft.server.MinecraftServer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.api.util.PlatformType;
+import org.geysermc.geyser.dump.BootstrapDumpInfo;
+import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
+
+import java.io.InputStream;
+import java.nio.file.Path;
+
+/**
+ * An interface which holds common methods that have different
+ * APIs on their respective mod platforms.
+ */
+public interface GeyserModPlatform {
+
+ /**
+ * Gets the {@link PlatformType} of the mod platform.
+ *
+ * @return the platform type of the mod platform
+ */
+ @NonNull
+ PlatformType platformType();
+
+ /**
+ * Gets the config path of the mod platform.
+ *
+ * @return the config path of the mod platform
+ */
+ @NonNull
+ String configPath();
+
+ /**
+ * Gets the data folder of the mod platform.
+ *
+ * @return the data folder of the mod platform
+ */
+ @NonNull
+ Path dataFolder(@NonNull String modId);
+
+ /**
+ * Gets the dump info of the mod platform.
+ *
+ * @param server the server to get the dump info from
+ * @return the dump info of the mod platform
+ */
+ @NonNull
+ BootstrapDumpInfo dumpInfo(@NonNull MinecraftServer server);
+
+ /**
+ * Tests if the Floodgate plugin is present on the mod platform.
+ *
+ * @return {@code true} if the Floodgate plugin is present on the mod platform, {@code false} otherwise
+ */
+ boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap);
+
+ /**
+ * Resolves a resource from the mod jar.
+ *
+ * @param resource the name of the resource
+ * @return the input stream of the resource
+ */
+ @Nullable
+ InputStream resolveResource(@NonNull String resource);
+}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java
new file mode 100644
index 000000000..db1768737
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.mod.world;
+
+import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
+import net.minecraft.SharedConstants;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.network.chat.Component;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.entity.BannerBlockEntity;
+import net.minecraft.world.level.block.entity.BannerPatternLayers;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.entity.DecoratedPotBlockEntity;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.status.ChunkStatus;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.cloudburstmc.math.vector.Vector3i;
+import org.geysermc.geyser.level.GeyserWorldManager;
+import org.geysermc.geyser.network.GameProtocol;
+import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
+import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.util.MinecraftKey;
+import org.geysermc.mcprotocollib.protocol.data.game.Holder;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
+import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer;
+import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+public class GeyserModWorldManager extends GeyserWorldManager {
+
+ private static final GsonComponentSerializer GSON_SERIALIZER = GsonComponentSerializer.gson();
+ private final MinecraftServer server;
+
+ public GeyserModWorldManager(MinecraftServer server) {
+ this.server = server;
+ }
+
+ @Override
+ public int getBlockAt(GeyserSession session, int x, int y, int z) {
+ // If the protocol version of Geyser and the server are not the
+ // same, fallback to the chunk cache. May be able to update this
+ // in the future to use ViaVersion however, like Spigot does.
+ if (SharedConstants.getCurrentVersion().getProtocolVersion() != GameProtocol.getJavaProtocolVersion()) {
+ return super.getBlockAt(session, x, y, z);
+ }
+
+ ServerPlayer player = this.getPlayer(session);
+ if (player == null) {
+ return 0;
+ }
+
+ Level level = player.level();
+ if (y < level.getMinBuildHeight()) {
+ return 0;
+ }
+
+ ChunkAccess chunk = level.getChunkSource().getChunk(x >> 4, z >> 4, ChunkStatus.FULL, false);
+ if (chunk == null) {
+ return 0;
+ }
+
+ int worldOffset = level.getMinBuildHeight() >> 4;
+ int chunkOffset = (y >> 4) - worldOffset;
+ if (chunkOffset < chunk.getSections().length) {
+ LevelChunkSection section = chunk.getSections()[chunkOffset];
+ if (section != null && !section.hasOnlyAir()) {
+ return Block.getId(section.getBlockState(x & 15, y & 15, z & 15));
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public boolean hasOwnChunkCache() {
+ return SharedConstants.getCurrentVersion().getProtocolVersion() == GameProtocol.getJavaProtocolVersion();
+ }
+
+ @Override
+ public boolean hasPermission(GeyserSession session, String permission) {
+ ServerPlayer player = getPlayer(session);
+ return GeyserModBootstrap.getInstance().hasPermission(player, permission);
+ }
+
+ @Override
+ public GameMode getDefaultGameMode(GeyserSession session) {
+ return GameMode.byId(server.getDefaultGameType().getId());
+ }
+
+ @NonNull
+ @Override
+ public CompletableFuture getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) {
+ CompletableFuture future = new CompletableFuture<>();
+ server.execute(() -> {
+ ServerPlayer player = getPlayer(session);
+ if (player == null) {
+ future.complete(null);
+ return;
+ }
+
+ BlockPos pos = new BlockPos(x, y, z);
+ // Don't create a new block entity if invalid
+ //noinspection resource - level() is just a getter
+ BlockEntity blockEntity = player.level().getChunkAt(pos).getBlockEntity(pos);
+ if (blockEntity instanceof BannerBlockEntity banner) {
+ // Potentially exposes other NBT data? But we need to get the NBT data for the banner patterns *and*
+ // the banner might have a custom name, both of which a Java client knows and caches
+ ItemStack itemStack = banner.getItem();
+
+ org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents components =
+ new org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents(new HashMap<>());
+
+ components.put(DataComponentType.DAMAGE, itemStack.getDamageValue());
+
+ Component customName = itemStack.getComponents().get(DataComponents.CUSTOM_NAME);
+ if (customName != null) {
+ components.put(DataComponentType.CUSTOM_NAME, toKyoriComponent(customName));
+ }
+
+ BannerPatternLayers pattern = itemStack.get(DataComponents.BANNER_PATTERNS);
+ if (pattern != null) {
+ components.put(DataComponentType.BANNER_PATTERNS, toPatternList(pattern));
+ }
+
+ future.complete(components);
+ return;
+ }
+ future.complete(null);
+ });
+ return future;
+ }
+
+ @Override
+ public void getDecoratedPotData(GeyserSession session, Vector3i pos, Consumer> apply) {
+ server.execute(() -> {
+ ServerPlayer player = getPlayer(session);
+ if (player == null) {
+ return;
+ }
+
+ BlockPos blockPos = new BlockPos(pos.getX(), pos.getY(), pos.getZ());
+ // Don't create a new block entity if invalid
+ //noinspection resource - level() is just a getter
+ BlockEntity blockEntity = player.level().getChunkAt(blockPos).getBlockEntity(blockPos);
+ if (blockEntity instanceof DecoratedPotBlockEntity pot) {
+ List sherds = pot.getDecorations().ordered()
+ .stream().map(item -> BuiltInRegistries.ITEM.getKey(item).toString())
+ .toList();
+ apply.accept(sherds);
+ }
+ });
+ }
+
+ private ServerPlayer getPlayer(GeyserSession session) {
+ return server.getPlayerList().getPlayer(session.getPlayerEntity().getUuid());
+ }
+
+ private static net.kyori.adventure.text.Component toKyoriComponent(Component component) {
+ String json = Component.Serializer.toJson(component, RegistryAccess.EMPTY);
+ return GSON_SERIALIZER.deserializeOr(json, net.kyori.adventure.text.Component.empty());
+ }
+
+ private static List toPatternList(BannerPatternLayers patternLayers) {
+ return patternLayers.layers().stream()
+ .map(layer -> {
+ BannerPatternLayer.BannerPattern pattern = new BannerPatternLayer.BannerPattern(
+ MinecraftKey.key(layer.pattern().value().assetId().toString()), layer.pattern().value().translationKey()
+ );
+ return new BannerPatternLayer(Holder.ofCustom(pattern), layer.color().getId());
+ })
+ .toList();
+ }
+}
diff --git a/bootstrap/mod/src/main/resources/geyser.mixins.json b/bootstrap/mod/src/main/resources/geyser.mixins.json
new file mode 100644
index 000000000..47b2f60f3
--- /dev/null
+++ b/bootstrap/mod/src/main/resources/geyser.mixins.json
@@ -0,0 +1,18 @@
+{
+ "required": true,
+ "minVersion": "0.8",
+ "package": "org.geysermc.geyser.platform.mod.mixin",
+ "compatibilityLevel": "JAVA_17",
+ "mixins": [
+ "server.ServerConnectionListenerMixin"
+ ],
+ "server": [
+ "server.DedicatedServerMixin"
+ ],
+ "client": [
+ "client.IntegratedServerMixin"
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts
index 129064cd4..fcb85f100 100644
--- a/bootstrap/spigot/build.gradle.kts
+++ b/bootstrap/spigot/build.gradle.kts
@@ -4,25 +4,27 @@ dependencies {
isTransitive = false
}
+ implementation(libs.erosion.bukkit.nms) {
+ attributes {
+ attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 21)
+ }
+ }
+
implementation(variantOf(libs.adapters.spigot) {
classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
})
+ implementation(variantOf(libs.adapters.paper) {
+ classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
+ })
implementation(libs.commodore)
implementation(libs.adventure.text.serializer.bungeecord)
-
- // Both folia-api and paper-mojangapi only provide Java 17 versions for 1.19
- compileOnly(libs.folia.api) {
- attributes {
- attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
- }
- }
- compileOnly(libs.paper.mojangapi) {
- attributes {
- attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
- }
- }
+
+ compileOnly(libs.folia.api)
+ compileOnly(libs.paper.mojangapi)
+
+ compileOnlyApi(libs.viaversion)
}
platformRelocate("it.unimi.dsi.fastutil")
@@ -41,6 +43,12 @@ application {
}
tasks.withType {
+
+ // Prevents Paper 1.20.5+ from remapping Geyser
+ manifest {
+ attributes["paperweight-mappings-namespace"] = "mojang"
+ }
+
archiveBaseName.set("Geyser-Spigot")
dependencies {
@@ -48,6 +56,7 @@ tasks.withType {
// We cannot shade Netty, or else native libraries will not load
// Needed because older Spigot builds do not provide the haproxy module
+ exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("io.netty:netty-transport-classes-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
@@ -67,3 +76,8 @@ tasks.withType {
exclude(dependency("com.mojang:.*"))
}
}
+
+modrinth {
+ uploadFile.set(tasks.getByPath("shadowJar"))
+ loaders.addAll("spigot", "paper")
+}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperLogger.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperLogger.java
index 930f84cec..9ebd6519a 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperLogger.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserPaperLogger.java
@@ -34,8 +34,8 @@ import java.util.logging.Logger;
public final class GeyserPaperLogger extends GeyserSpigotLogger {
private final ComponentLogger componentLogger;
- public GeyserPaperLogger(Plugin plugin, Logger logger, boolean debug) {
- super(logger, debug);
+ public GeyserPaperLogger(Plugin plugin, Logger logger) {
+ super(logger);
componentLogger = plugin.getComponentLogger();
}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java
index ad31131bd..5dcfbd0f8 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java
@@ -25,7 +25,7 @@
package org.geysermc.geyser.platform.spigot;
-import com.github.steveice10.mc.protocol.MinecraftProtocol;
+import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
@@ -119,8 +119,11 @@ public class GeyserSpigotInjector extends GeyserInjector {
protected void initChannel(@NonNull Channel ch) throws Exception {
initChannel.invoke(childHandler, ch);
+ int index = ch.pipeline().names().indexOf("encoder");
+ String baseName = index != -1 ? "encoder" : "outbound_config";
+
if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserSpigotCompressionDisabler.ENABLED) {
- ch.pipeline().addAfter("encoder", "geyser-compression-disabler", new GeyserSpigotCompressionDisabler());
+ ch.pipeline().addAfter(baseName, "geyser-compression-disabler", new GeyserSpigotCompressionDisabler());
}
}
})
@@ -177,6 +180,7 @@ public class GeyserSpigotInjector extends GeyserInjector {
bootstrap.getGeyserConfig().getRemote().port(), this.serverSocketAddress,
InetAddress.getLoopbackAddress().getHostAddress(), protocol, protocol.createHelper());
session.connect();
+ session.disconnect("");
}
@Override
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotLogger.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotLogger.java
index fe56cba1c..5c6101eae 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotLogger.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotLogger.java
@@ -25,15 +25,15 @@
package org.geysermc.geyser.platform.spigot;
-import lombok.AllArgsConstructor;
import lombok.Getter;
+import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.geysermc.geyser.GeyserLogger;
import java.util.logging.Level;
import java.util.logging.Logger;
-@AllArgsConstructor
+@RequiredArgsConstructor
public class GeyserSpigotLogger implements GeyserLogger {
private final Logger logger;
@Getter @Setter
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
index a2a08c3bf..2d13155f2 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java
@@ -46,6 +46,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.adapters.paper.PaperAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
@@ -78,18 +79,14 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
-import java.util.logging.Level;
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
- /**
- * Determines if the plugin has been ran once before, including before /geyser reload.
- */
- private static boolean INITIALIZED = false;
private GeyserSpigotCommandManager geyserCommandManager;
private GeyserSpigotConfiguration geyserConfig;
private GeyserSpigotInjector geyserInjector;
- private GeyserSpigotLogger geyserLogger;
+ private final GeyserSpigotLogger geyserLogger = GeyserPaperLogger.supported() ?
+ new GeyserPaperLogger(this, getLogger()) : new GeyserSpigotLogger(getLogger());
private IGeyserPingPassthrough geyserSpigotPingPassthrough;
private GeyserSpigotWorldManager geyserWorldManager;
@@ -102,6 +99,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
@Override
public void onLoad() {
+ onGeyserInitialize();
+ }
+
+ @Override
+ public void onGeyserInitialize() {
GeyserLocale.init(this);
try {
@@ -112,12 +114,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
// We depend on this as a fallback in certain scenarios
BlockData.class.getMethod("getAsString");
} catch (ClassNotFoundException | NoSuchMethodException e) {
- getLogger().severe("*********************************************");
- getLogger().severe("");
- getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.header"));
- getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
- getLogger().severe("");
- getLogger().severe("*********************************************");
+ geyserLogger.error("*********************************************");
+ geyserLogger.error("");
+ geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.header"));
+ geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
+ geyserLogger.error("");
+ geyserLogger.error("*********************************************");
+ Bukkit.getPluginManager().disablePlugin(this);
return;
}
@@ -125,12 +128,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
Class.forName("net.md_5.bungee.chat.ComponentSerializer");
} catch (ClassNotFoundException e) {
if (!PaperAdventure.canSendMessageUsingComponent()) { // Prepare for Paper eventually removing Bungee chat
- getLogger().severe("*********************************************");
- getLogger().severe("");
- getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.header", getServer().getName()));
- getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
- getLogger().severe("");
- getLogger().severe("*********************************************");
+ geyserLogger.error("*********************************************");
+ geyserLogger.error("");
+ geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.header", getServer().getName()));
+ geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
+ geyserLogger.error("");
+ geyserLogger.error("*********************************************");
+ Bukkit.getPluginManager().disablePlugin(this);
return;
}
}
@@ -138,91 +142,76 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
try {
Class.forName("io.netty.util.internal.ObjectPool$ObjectCreator");
} catch (ClassNotFoundException e) {
- getLogger().severe("*********************************************");
- getLogger().severe("");
- getLogger().severe("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
- getLogger().severe("");
- getLogger().severe("*********************************************");
- return;
- }
-
- // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
- try {
- if (!getDataFolder().exists()) {
- //noinspection ResultOfMethodCallIgnored
- getDataFolder().mkdir();
- }
- File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml",
- (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
- this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
- } catch (IOException ex) {
- getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
- ex.printStackTrace();
+ geyserLogger.error("*********************************************");
+ geyserLogger.error("");
+ geyserLogger.error("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
+ geyserLogger.error("");
+ geyserLogger.error("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
- this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode())
- : new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
-
+ if (!loadConfig()) {
+ return;
+ }
+ this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
+ // Turn "(MC: 1.16.4)" into 1.16.4.
+ this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
+
this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this);
}
@Override
public void onEnable() {
- if (this.geyserConfig == null) {
- // We failed to initialize correctly
- Bukkit.getPluginManager().disablePlugin(this);
- return;
- }
-
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
this.geyserCommandManager.init();
- if (!INITIALIZED) {
- // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
- Bukkit.getPluginManager().registerEvents(new Listener() {
+ // Because Bukkit locks its command map upon startup, we need to
+ // add our plugin commands in onEnable, but populating the executor
+ // can happen at any time (later in #onGeyserEnable())
+ CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
+ for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
+ // Thanks again, Bukkit
+ try {
+ Constructor constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
+ constructor.setAccessible(true);
- @EventHandler
- public void onServerLoaded(ServerLoadEvent event) {
- // Wait until all plugins have loaded so Geyser can start
- postStartup();
- }
- }, this);
+ PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
+ pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
- // Because Bukkit locks its command map upon startup, we need to
- // add our plugin commands in onEnable, but populating the executor
- // can happen at any time
- CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
- for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
- // Thanks again, Bukkit
- try {
- Constructor constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
- constructor.setAccessible(true);
-
- PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
- pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
-
- commandMap.register(extension.description().id(), "geyserext", pluginCommand);
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
- this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
- }
+ commandMap.register(extension.description().id(), "geyserext", pluginCommand);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
+ this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
}
}
- if (INITIALIZED) {
- // Reload; continue with post startup
- postStartup();
- }
+ // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
+ Bukkit.getPluginManager().registerEvents(new Listener() {
+
+ @EventHandler
+ public void onServerLoaded(ServerLoadEvent event) {
+ if (event.getType() == ServerLoadEvent.LoadType.RELOAD) {
+ geyser.setShuttingDown(false);
+ }
+ onGeyserEnable();
+ }
+ }, this);
}
- private void postStartup() {
- GeyserImpl.start();
+ public void onGeyserEnable() {
+ // Configs are loaded once early - so we can create the logger, then load extensions and finally register
+ // extension commands in #onEnable. To ensure reloading geyser also reloads the geyser config, this exists
+ if (GeyserImpl.getInstance().isReloading()) {
+ if (!loadConfig()) {
+ return;
+ }
+ this.geyserLogger.setDebug(this.geyserConfig.isDebugMode());
+ GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
+ }
- // Turn "(MC: 1.16.4)" into 1.16.4.
- this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
+ GeyserImpl.start();
if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
@@ -238,20 +227,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
}
geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass()));
- boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null;
- if (isViaVersion) {
- try {
- // Ensure that we have the latest 4.0.0 changes and not an older ViaVersion version
- Class.forName("com.viaversion.viaversion.api.ViaManager");
- } catch (ClassNotFoundException e) {
- GeyserSpigotVersionChecker.sendOutdatedViaVersionMessage(geyserLogger);
- isViaVersion = false;
- if (this.geyserConfig.isDebugMode()) {
- e.printStackTrace();
- }
- }
+ // Don't need to re-create the world manager/re-register commands/reinject when reloading
+ if (GeyserImpl.getInstance().isReloading()) {
+ return;
}
+ boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null;
+
+ // Check to ensure the current setup can support the protocol version Geyser uses
+ GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
+
// We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib
// To do their job injecting, then connect into *that*
this.geyserInjector = new GeyserSpigotInjector(isViaVersion);
@@ -259,17 +244,29 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
if (Boolean.parseBoolean(System.getProperty("Geyser.UseDirectAdapters", "true"))) {
try {
- String name = Bukkit.getServer().getClass().getPackage().getName();
- String nmsVersion = name.substring(name.lastIndexOf('.') + 1);
- SpigotAdapters.registerWorldAdapter(nmsVersion);
+ boolean isPaper = false;
+ try {
+ String name = Bukkit.getServer().getClass().getPackage().getName();
+ String nmsVersion = name.substring(name.lastIndexOf('.') + 1);
+ SpigotAdapters.registerWorldAdapter(nmsVersion);
+ geyserLogger.debug("Using spigot NMS adapter for nms version: " + nmsVersion);
+ } catch (Exception e) { // Likely running on Paper 1.20.5+
+ geyserLogger.debug("Unable to find spigot world manager: " + e.getMessage());
+ //noinspection deprecation
+ int protocolVersion = Bukkit.getUnsafe().getProtocolVersion();
+ PaperAdapters.registerClosestWorldAdapter(protocolVersion);
+ isPaper = true;
+ geyserLogger.debug("Using paper world adapter for protocol version: " + protocolVersion);
+ }
+
if (isViaVersion && isViaVersionNeeded()) {
- this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this);
+ this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, isPaper);
} else {
// No ViaVersion
- this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this);
+ this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this, isPaper);
}
- geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion);
- } catch (Exception e) {
+ geyserLogger.debug("Using world manager of type: " + this.geyserWorldManager.getClass().getSimpleName());
+ } catch (Throwable e) {
if (geyserConfig.isDebugMode()) {
geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)");
e.printStackTrace();
@@ -278,6 +275,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
} else {
geyserLogger.debug("Not using NMS adapter as it is disabled via system property.");
}
+
if (this.geyserWorldManager == null) {
// No NMS adapter
this.geyserWorldManager = new GeyserSpigotWorldManager(this);
@@ -302,72 +300,72 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands));
}
- if (!INITIALIZED) {
- // Register permissions so they appear in, for example, LuckPerms' UI
- // Re-registering permissions throws an error
- for (Map.Entry entry : geyserCommandManager.commands().entrySet()) {
+ // Register permissions so they appear in, for example, LuckPerms' UI
+ // Re-registering permissions throws an error
+ for (Map.Entry entry : geyserCommandManager.commands().entrySet()) {
+ Command command = entry.getValue();
+ if (command.aliases().contains(entry.getKey())) {
+ // Don't register aliases
+ continue;
+ }
+
+ Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
+ GeyserLocale.getLocaleStringLog(command.description()),
+ command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
+ }
+
+ // Register permissions for extension commands
+ for (Map.Entry> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
+ for (Map.Entry entry : commandEntry.getValue().entrySet()) {
Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) {
// Don't register aliases
continue;
}
+ if (command.permission().isBlank()) {
+ continue;
+ }
+
+ // Avoid registering the same permission twice, e.g. for the extension help commands
+ if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
+ GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
+ continue;
+ }
+
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
-
- // Register permissions for extension commands
- for (Map.Entry> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
- for (Map.Entry entry : commandEntry.getValue().entrySet()) {
- Command command = entry.getValue();
- if (command.aliases().contains(entry.getKey())) {
- // Don't register aliases
- continue;
- }
-
- if (command.permission().isBlank()) {
- continue;
- }
-
- // Avoid registering the same permission twice, e.g. for the extension help commands
- if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
- GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
- continue;
- }
-
- Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
- GeyserLocale.getLocaleStringLog(command.description()),
- command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
- }
- }
-
- Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
- "Whether update notifications can be seen", PermissionDefault.OP));
-
- // Events cannot be unregistered - re-registering results in duplicate firings
- GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
- Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
-
- Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
-
- Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
}
+ Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
+ "Whether update notifications can be seen", PermissionDefault.OP));
+
+ // Events cannot be unregistered - re-registering results in duplicate firings
+ GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
+ Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
+
+ Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
+
+ Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
+
boolean brigadierSupported = CommodoreProvider.isSupported();
geyserLogger.debug("Brigadier supported? " + brigadierSupported);
if (brigadierSupported) {
GeyserBrigadierSupport.loadBrigadier(this, geyserCommand);
}
-
- // Check to ensure the current setup can support the protocol version Geyser uses
- GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
-
- INITIALIZED = true;
}
@Override
- public void onDisable() {
+ public void onGeyserDisable() {
+ if (geyser != null) {
+ geyser.disable();
+ }
+ }
+
+ @Override
+ public void onGeyserShutdown() {
if (geyser != null) {
geyser.shutdown();
}
@@ -376,6 +374,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
}
}
+ @Override
+ public void onDisable() {
+ this.onGeyserShutdown();
+ }
+
@Override
public GeyserSpigotConfiguration getGeyserConfig() {
return geyserConfig;
@@ -442,7 +445,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
return false;
}
for (int i = protocolList.size() - 1; i >= 0; i--) {
- MappingData mappingData = protocolList.get(i).getProtocol().getMappingData();
+ MappingData mappingData = protocolList.get(i).protocol().getMappingData();
if (mappingData != null) {
return true;
}
@@ -470,4 +473,25 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
}
return false;
}
+
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ private boolean loadConfig() {
+ // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
+ try {
+ if (!getDataFolder().exists()) {
+ //noinspection ResultOfMethodCallIgnored
+ getDataFolder().mkdir();
+ }
+ File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml",
+ (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
+ this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
+ } catch (IOException ex) {
+ geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
+ ex.printStackTrace();
+ Bukkit.getPluginManager().disablePlugin(this);
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java
index 9e0b14b11..fa7555ac6 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/PaperAdventure.java
@@ -25,7 +25,7 @@
package org.geysermc.geyser.platform.spigot;
-import com.github.steveice10.mc.protocol.data.DefaultComponentSerializer;
+import org.geysermc.mcprotocollib.protocol.data.DefaultComponentSerializer;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.checkerframework.checker.nullness.qual.Nullable;
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java
index 01b0be9f2..963f5bac3 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserPistonListener.java
@@ -25,10 +25,8 @@
package org.geysermc.geyser.platform.spigot.world;
-import com.github.steveice10.mc.protocol.data.game.level.block.value.PistonValueType;
-import org.cloudburstmc.math.vector.Vector3i;
-import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
@@ -40,13 +38,17 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPistonEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
+import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.block.BlockStateValues;
+import org.geysermc.geyser.level.block.property.Properties;
+import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.PistonCache;
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
+import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
import java.util.List;
import java.util.Map;
@@ -85,7 +87,7 @@ public class GeyserPistonListener implements Listener {
PistonValueType type = isExtend ? PistonValueType.PUSHING : PistonValueType.PULLING;
boolean sticky = event.isSticky();
- Object2IntMap attachedBlocks = new Object2IntArrayMap<>();
+ Object2ObjectMap attachedBlocks = new Object2ObjectArrayMap<>();
boolean blocksFilled = false;
for (Map.Entry entry : geyser.getSessionManager().getSessions().entrySet()) {
@@ -108,10 +110,10 @@ public class GeyserPistonListener implements Listener {
List blocks = isExtend ? ((BlockPistonExtendEvent) event).getBlocks() : ((BlockPistonRetractEvent) event).getBlocks();
for (Block block : blocks) {
Location attachedLocation = block.getLocation();
- int blockId = worldManager.getBlockNetworkId(block);
+ BlockState state = BlockState.of(worldManager.getBlockNetworkId(block));
// Ignore blocks that will be destroyed
- if (BlockStateValues.canPistonMoveBlock(blockId, isExtend)) {
- attachedBlocks.put(getVector(attachedLocation), blockId);
+ if (BlockStateValues.canPistonMoveBlock(state, isExtend)) {
+ attachedBlocks.put(getVector(attachedLocation), state);
}
}
blocksFilled = true;
@@ -119,7 +121,7 @@ public class GeyserPistonListener implements Listener {
int pistonBlockId = worldManager.getBlockNetworkId(event.getBlock());
// event.getDirection() is unreliable
- Direction orientation = BlockStateValues.getPistonOrientation(pistonBlockId);
+ Direction orientation = BlockState.of(pistonBlockId).getValue(Properties.FACING);
session.executeInEventLoop(() -> {
PistonCache pistonCache = session.getPistonCache();
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigotBlockPlaceListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigotBlockPlaceListener.java
index 71aba11f9..1cdd77c64 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigotBlockPlaceListener.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/GeyserSpigotBlockPlaceListener.java
@@ -33,7 +33,7 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import org.geysermc.geyser.GeyserImpl;
-import org.geysermc.geyser.level.block.BlockStateValues;
+import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
@@ -59,11 +59,11 @@ public class GeyserSpigotBlockPlaceListener implements Listener {
event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())));
} else {
String javaBlockId = event.getBlockPlaced().getBlockData().getAsString();
- placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().getOrDefault(javaBlockId, BlockStateValues.JAVA_AIR_ID)));
+ placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().getOrDefault(javaBlockId, Block.JAVA_AIR_ID)));
}
placeBlockSoundPacket.setIdentifier(":");
session.sendUpstreamPacket(placeBlockSoundPacket);
session.setLastBlockPlacePosition(null);
- session.setLastBlockPlacedId(null);
+ session.setLastBlockPlaced(null);
}
}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java
index 021db5ec1..fe2dda053 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java
@@ -46,8 +46,8 @@ public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorl
private final Int2IntMap oldToNewBlockId;
- public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin) {
- super(plugin);
+ public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean isPaper) {
+ super(plugin, isPaper);
IntList allBlockStates = adapter.getAllBlockStates();
oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size());
ProtocolVersion serverVersion = plugin.getServerProtocolVersion();
@@ -58,7 +58,7 @@ public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorl
int newBlockId = oldBlockId;
// protocolList should *not* be null; we checked for that before initializing this class
for (int i = protocolList.size() - 1; i >= 0; i--) {
- MappingData mappingData = protocolList.get(i).getProtocol().getMappingData();
+ MappingData mappingData = protocolList.get(i).protocol().getMappingData();
if (mappingData != null) {
newBlockId = mappingData.getNewBlockStateId(newBlockId);
}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java
index 00212663c..c99ca4e78 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java
@@ -26,27 +26,34 @@
package org.geysermc.geyser.platform.spigot.world.manager;
import org.bukkit.Bukkit;
+import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.adapters.WorldAdapter;
+import org.geysermc.geyser.adapters.paper.PaperAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
-import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter;
import org.geysermc.geyser.level.block.BlockStateValues;
+import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession;
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
- protected final SpigotWorldAdapter adapter;
+ protected final WorldAdapter adapter;
- public GeyserSpigotNativeWorldManager(Plugin plugin) {
+ public GeyserSpigotNativeWorldManager(Plugin plugin, boolean isPaper) {
super(plugin);
- adapter = SpigotAdapters.getWorldAdapter();
+ if (isPaper) {
+ adapter = PaperAdapters.getWorldAdapter();
+ } else {
+ adapter = SpigotAdapters.getWorldAdapter();
+ }
}
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername());
if (player == null) {
- return BlockStateValues.JAVA_AIR_ID;
+ return Block.JAVA_AIR_ID;
}
return adapter.getBlockAt(player.getWorld(), x, y, z);
}
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java
index 42f0d17f4..73356c4e7 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java
@@ -25,56 +25,52 @@
package org.geysermc.geyser.platform.spigot.world.manager;
-import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
-import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
-import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import org.bukkit.Bukkit;
-import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.block.Block;
+import org.bukkit.block.DecoratedPot;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
-import org.cloudburstmc.nbt.NbtMap;
-import org.geysermc.erosion.bukkit.BukkitLecterns;
+import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.erosion.bukkit.BukkitUtils;
import org.geysermc.erosion.bukkit.PickBlockUtils;
import org.geysermc.erosion.bukkit.SchedulerUtils;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.level.GameRule;
import org.geysermc.geyser.level.WorldManager;
-import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.util.BlockEntityUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
+import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
/**
* The base world manager to use when there is no supported NMS revision
*/
public class GeyserSpigotWorldManager extends WorldManager {
private final Plugin plugin;
- private final BukkitLecterns lecterns;
public GeyserSpigotWorldManager(Plugin plugin) {
this.plugin = plugin;
- this.lecterns = new BukkitLecterns(plugin);
}
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
- return BlockStateValues.JAVA_AIR_ID;
+ return org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID;
}
World world = bukkitPlayer.getWorld();
if (!world.isChunkLoaded(x >> 4, z >> 4)) {
// If the chunk isn't loaded, how could we even be here?
- return BlockStateValues.JAVA_AIR_ID;
+ return org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID;
}
return getBlockNetworkId(world.getBlockAt(x, y, z));
@@ -85,9 +81,9 @@ public class GeyserSpigotWorldManager extends WorldManager {
// Terrible behavior, but this is basically what's always been happening behind the scenes anyway.
CompletableFuture blockData = new CompletableFuture<>();
Bukkit.getRegionScheduler().execute(this.plugin, block.getLocation(), () -> blockData.complete(block.getBlockData().getAsString()));
- return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(blockData.join(), BlockStateValues.JAVA_AIR_ID);
+ return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(blockData.join(), org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID);
}
- return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(block.getBlockData().getAsString(), BlockStateValues.JAVA_AIR_ID);
+ return BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(block.getBlockData().getAsString(), org.geysermc.geyser.level.block.type.Block.JAVA_AIR_ID); // TODO could just make this a BlockState lookup?
}
@Override
@@ -95,69 +91,6 @@ public class GeyserSpigotWorldManager extends WorldManager {
return true;
}
- @Override
- public void sendLecternData(GeyserSession session, int x, int y, int z) {
- Player bukkitPlayer;
- if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
- return;
- }
-
- Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
- // Run as a task to prevent async issues
- SchedulerUtils.runTask(this.plugin, () -> sendLecternData(session, block, false), block);
- }
-
- public void sendLecternData(GeyserSession session, int x, int z, List blockEntityInfos) {
- Player bukkitPlayer;
- if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
- return;
- }
- if (SchedulerUtils.FOLIA) {
- Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
- if (chunk == null) {
- return;
- }
- Bukkit.getRegionScheduler().execute(this.plugin, bukkitPlayer.getWorld(), x, z, () ->
- sendLecternData(session, chunk, blockEntityInfos));
- } else {
- Bukkit.getScheduler().runTask(this.plugin, () -> {
- Chunk chunk = getChunk(bukkitPlayer.getWorld(), x, z);
- if (chunk == null) {
- return;
- }
- sendLecternData(session, chunk, blockEntityInfos);
- });
- }
- }
-
- private @Nullable Chunk getChunk(World world, int x, int z) {
- if (!world.isChunkLoaded(x, z)) {
- return null;
- }
- return world.getChunkAt(x, z);
- }
-
- private void sendLecternData(GeyserSession session, Chunk chunk, List blockEntityInfos) {
- //noinspection ForLoopReplaceableByForEach - avoid constructing Iterator
- for (int i = 0; i < blockEntityInfos.size(); i++) {
- BlockEntityInfo info = blockEntityInfos.get(i);
- Block block = chunk.getBlock(info.getX(), info.getY(), info.getZ());
- sendLecternData(session, block, true);
- }
- }
-
- private void sendLecternData(GeyserSession session, Block block, boolean isChunkLoad) {
- NbtMap blockEntityTag = this.lecterns.getLecternData(block, isChunkLoad);
- if (blockEntityTag != null) {
- BlockEntityUtils.updateBlockEntity(session, blockEntityTag, BukkitUtils.getVector(block.getLocation()));
- }
- }
-
- @Override
- public boolean shouldExpectLecternHandled(GeyserSession session) {
- return true;
- }
-
public boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
org.bukkit.GameRule> bukkitGameRule = org.bukkit.GameRule.getByName(gameRule.getJavaID());
if (bukkitGameRule == null) {
@@ -205,18 +138,32 @@ public class GeyserSpigotWorldManager extends WorldManager {
}
@Override
- public @NonNull CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {
- CompletableFuture<@Nullable CompoundTag> future = new CompletableFuture<>();
+ public @NonNull CompletableFuture<@Nullable DataComponents> getPickItemComponents(GeyserSession session, int x, int y, int z, boolean addNbtData) {
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
- future.complete(null);
- return future;
+ return CompletableFuture.completedFuture(null);
}
+ CompletableFuture> future = new CompletableFuture<>();
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
// Paper 1.19.3 complains about async access otherwise.
// java.lang.IllegalStateException: Tile is null, asynchronous access?
SchedulerUtils.runTask(this.plugin, () -> future.complete(PickBlockUtils.pickBlock(block)), block);
- return future;
+ return future.thenApply(RAW_TRANSFORMER);
+ }
+
+ public void getDecoratedPotData(GeyserSession session, Vector3i pos, Consumer> apply) {
+ Player bukkitPlayer;
+ if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUuid())) == null) {
+ return;
+ }
+ Block block = bukkitPlayer.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
+ SchedulerUtils.runTask(this.plugin, () -> {
+ var state = BukkitUtils.getBlockState(block);
+ if (!(state instanceof DecoratedPot pot)) {
+ return;
+ }
+ apply.accept(pot.getShards().stream().map(material -> material.getKey().toString()).toList());
+ }, block);
}
/**
diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java
index 9f2208ea8..f289fa2ba 100644
--- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java
+++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java
@@ -39,9 +39,9 @@ import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.checkerframework.checker.nullness.qual.NonNull;
-import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
@@ -59,20 +59,24 @@ import java.lang.reflect.Method;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
-import java.util.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
import java.util.stream.Collectors;
public class GeyserStandaloneBootstrap implements GeyserBootstrap {
private GeyserCommandManager geyserCommandManager;
private GeyserStandaloneConfiguration geyserConfig;
- private GeyserStandaloneLogger geyserLogger;
+ private final GeyserStandaloneLogger geyserLogger = new GeyserStandaloneLogger();
private IGeyserPingPassthrough geyserPingPassthrough;
-
private GeyserStandaloneGUI gui;
-
@Getter
private boolean useGui = System.console() == null && !isHeadless();
+ private Logger log4jLogger;
private String configFilename = "config.yml";
private GeyserImpl geyser;
@@ -161,28 +165,22 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
}
}
}
- bootstrap.onEnable(useGuiOpts, configFilenameOpt);
- }
-
- public void onEnable(boolean useGui, String configFilename) {
- this.configFilename = configFilename;
- this.useGui = useGui;
- this.onEnable();
+ bootstrap.useGui = useGuiOpts;
+ bootstrap.configFilename = configFilenameOpt;
+ bootstrap.onGeyserInitialize();
}
@Override
- public void onEnable() {
- Logger logger = (Logger) LogManager.getRootLogger();
- for (Appender appender : logger.getAppenders().values()) {
+ public void onGeyserInitialize() {
+ log4jLogger = (Logger) LogManager.getRootLogger();
+ for (Appender appender : log4jLogger.getAppenders().values()) {
// Remove the appender that is not in use
// Prevents multiple appenders/double logging and removes harmless errors
if ((useGui && appender instanceof TerminalConsoleAppender) || (!useGui && appender instanceof ConsoleAppender)) {
- logger.removeAppender(appender);
+ log4jLogger.removeAppender(appender);
}
}
- this.geyserLogger = new GeyserStandaloneLogger();
-
if (useGui && gui == null) {
gui = new GeyserStandaloneGUI(geyserLogger);
gui.redirectSystemStreams();
@@ -190,7 +188,12 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
}
LoopbackUtil.checkAndApplyLoopback(geyserLogger);
-
+
+ this.onGeyserEnable();
+ }
+
+ @Override
+ public void onGeyserEnable() {
try {
File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml",
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
@@ -215,14 +218,15 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Allow libraries like Protocol to have their debug information passthrough
- logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO);
+ log4jLogger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO);
geyser = GeyserImpl.load(PlatformType.STANDALONE, this);
- GeyserImpl.start();
geyserCommandManager = new GeyserCommandManager(geyser);
geyserCommandManager.init();
+ GeyserImpl.start();
+
if (gui != null) {
gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager);
}
@@ -250,7 +254,14 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
}
@Override
- public void onDisable() {
+ public void onGeyserDisable() {
+ // We can re-register commands on standalone, so why not
+ GeyserImpl.getInstance().commandManager().getCommands().clear();
+ geyser.disable();
+ }
+
+ @Override
+ public void onGeyserShutdown() {
geyser.shutdown();
System.exit(0);
}
diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java
index 3c29bc648..3a34920ce 100644
--- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java
+++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java
@@ -49,7 +49,7 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
@Override
protected void shutdown() {
- GeyserImpl.getInstance().getBootstrap().onDisable();
+ GeyserImpl.getInstance().getBootstrap().onGeyserShutdown();
}
@Override
diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java
index c3e2e10e8..b82d8cc94 100644
--- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java
+++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java
@@ -100,7 +100,7 @@ public class GeyserStandaloneGUI {
Container cp = frame.getContentPane();
// Fetch and set the icon for the frame
- URL image = getClass().getClassLoader().getResource("icon.png");
+ URL image = getClass().getClassLoader().getResource("assets/geyser/icon.png");
if (image != null) {
ImageIcon icon = new ImageIcon(image);
frame.setIconImage(icon.getImage());
diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts
index 8908b2afd..4daad9784 100644
--- a/bootstrap/velocity/build.gradle.kts
+++ b/bootstrap/velocity/build.gradle.kts
@@ -1,15 +1,19 @@
dependencies {
annotationProcessor(libs.velocity.api)
api(projects.core)
+
+ compileOnlyApi(libs.velocity.api)
}
platformRelocate("com.fasterxml.jackson")
platformRelocate("it.unimi.dsi.fastutil")
platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl")
+platformRelocate("org.yaml")
exclude("com.google.*:*")
-// Needed because Velocity provides every dependency except netty-resolver-dns
+// Needed because Velocity provides every dependency except netty-resolver-dns
+exclude("io.netty.incubator:.*")
exclude("io.netty:netty-transport-native-epoll:*")
exclude("io.netty:netty-transport-native-unix-common:*")
exclude("io.netty:netty-transport-native-kqueue:*")
@@ -54,6 +58,7 @@ tasks.withType {
exclude(dependency("io.netty:netty-transport:.*"))
exclude(dependency("io.netty:netty-codec:.*"))
exclude(dependency("io.netty:netty-codec-haproxy:.*"))
+ exclude(dependency("io.netty.incubator:.*"))
exclude(dependency("org.slf4j:.*"))
exclude(dependency("org.ow2.asm:.*"))
// Exclude all Kyori dependencies except the legacy NBT serializer
@@ -64,4 +69,9 @@ tasks.withType {
exclude(dependency("net.kyori:adventure-text-serializer-legacy:.*"))
exclude(dependency("net.kyori:adventure-nbt:.*"))
}
+}
+
+modrinth {
+ uploadFile.set(tasks.getByPath("shadowJar"))
+ loaders.addAll("velocity")
}
\ No newline at end of file
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java
index e787e7355..99c759767 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityCompressionDisabler.java
@@ -47,8 +47,14 @@ public class GeyserVelocityCompressionDisabler extends ChannelDuplexHandler {
Method setCompressionMethod = null;
try {
- compressionPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.SetCompression");
- loginSuccessPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess");
+ try {
+ compressionPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.SetCompressionPacket");
+ loginSuccessPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket");
+ } catch (Exception ignored) {
+ // Velocity renamed packet classes in https://github.com/PaperMC/Velocity/commit/2ac8751337befd04f4663575f5d752c748384110
+ compressionPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.SetCompression");
+ loginSuccessPacketClass = Class.forName("com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess");
+ }
compressionEnabledEvent = Class.forName("com.velocitypowered.proxy.protocol.VelocityConnectionEvent")
.getDeclaredField("COMPRESSION_ENABLED").get(null);
setCompressionMethod = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection")
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityLogger.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityLogger.java
index 567870e7f..4d10e4daf 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityLogger.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityLogger.java
@@ -25,13 +25,13 @@
package org.geysermc.geyser.platform.velocity;
-import lombok.AllArgsConstructor;
import lombok.Getter;
+import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.geysermc.geyser.GeyserLogger;
import org.slf4j.Logger;
-@AllArgsConstructor
+@RequiredArgsConstructor
public class GeyserVelocityLogger implements GeyserLogger {
private final Logger logger;
@Getter @Setter
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPingPassthrough.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPingPassthrough.java
index 8944ea134..b2258d3a3 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPingPassthrough.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPingPassthrough.java
@@ -26,6 +26,7 @@
package org.geysermc.geyser.platform.velocity;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
+import com.velocitypowered.api.network.ProtocolState;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.ProxyServer;
@@ -88,6 +89,11 @@ public class GeyserVelocityPingPassthrough implements IGeyserPingPassthrough {
public ProtocolVersion getProtocolVersion() {
return ProtocolVersion.MAXIMUM_VERSION;
}
+
+ @Override
+ public ProtocolState getProtocolState() {
+ return ProtocolState.STATUS;
+ }
}
}
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java
index bd3d6085a..539bdadbf 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java
@@ -32,10 +32,10 @@ import com.velocitypowered.api.event.proxy.ListenerBoundEvent;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.network.ListenerType;
+import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.Getter;
-import net.kyori.adventure.util.Codec;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserBootstrap;
@@ -46,6 +46,7 @@ import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
+import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandExecutor;
@@ -64,78 +65,74 @@ import java.util.UUID;
@Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC")
public class GeyserVelocityPlugin implements GeyserBootstrap {
- /**
- * Determines if the plugin has been ran once before, including before /geyser reload.
- */
- private static boolean INITIALIZED = false;
-
- @Inject
- private Logger logger;
-
- @Inject
- private ProxyServer proxyServer;
-
- @Inject
- private CommandManager commandManager;
-
+ private final ProxyServer proxyServer;
+ private final CommandManager commandManager;
+ private final GeyserVelocityLogger geyserLogger;
private GeyserCommandManager geyserCommandManager;
private GeyserVelocityConfiguration geyserConfig;
private GeyserVelocityInjector geyserInjector;
- private GeyserVelocityLogger geyserLogger;
private IGeyserPingPassthrough geyserPingPassthrough;
-
private GeyserImpl geyser;
@Getter
private final Path configFolder = Paths.get("plugins/" + GeyserImpl.NAME + "-Velocity/");
- @Override
- public void onEnable() {
- try {
- Codec.class.getMethod("codec", Codec.Decoder.class, Codec.Encoder.class);
- } catch (NoSuchMethodException e) {
- // velocitypowered.com has a build that is very outdated
- logger.error("Please download Velocity from https://papermc.io/downloads#Velocity - the 'stable' Velocity version " +
- "that has likely been downloaded is very outdated and does not support 1.19.");
- return;
- }
+ @Inject
+ public GeyserVelocityPlugin(ProxyServer server, Logger logger, CommandManager manager) {
+ this.geyserLogger = new GeyserVelocityLogger(logger);
+ this.proxyServer = server;
+ this.commandManager = manager;
+ }
+ @Override
+ public void onGeyserInitialize() {
GeyserLocale.init(this);
- try {
- if (!configFolder.toFile().exists())
- //noinspection ResultOfMethodCallIgnored
- configFolder.toFile().mkdirs();
- File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(),
- "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
- this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class);
- } catch (IOException ex) {
- logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
- ex.printStackTrace();
- return;
+ if (!ProtocolVersion.isSupported(GameProtocol.getJavaProtocolVersion())) {
+ geyserLogger.error(" / \\");
+ geyserLogger.error(" / \\");
+ geyserLogger.error(" / | \\");
+ geyserLogger.error(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", proxyServer.getVersion().getName()));
+ geyserLogger.error(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
+ geyserLogger.error(" / o \\");
+ geyserLogger.error("/_____________\\");
}
- this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode());
+ if (!loadConfig()) {
+ return;
+ }
+ this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this);
-
- // Hack: Normally triggered by ListenerBoundEvent, but that doesn't fire on /geyser reload
- if (INITIALIZED) {
- this.postStartup();
- }
+ this.geyserInjector = new GeyserVelocityInjector(proxyServer);
}
- private void postStartup() {
- GeyserImpl.start();
-
- if (!INITIALIZED) {
- this.geyserInjector = new GeyserVelocityInjector(proxyServer);
- // Will be initialized after the proxy has been bound
+ @Override
+ public void onGeyserEnable() {
+ if (GeyserImpl.getInstance().isReloading()) {
+ if (!loadConfig()) {
+ return;
+ }
+ this.geyserLogger.setDebug(geyserConfig.isDebugMode());
+ GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
+ } else {
+ this.geyserCommandManager = new GeyserCommandManager(geyser);
+ this.geyserCommandManager.init();
}
- this.geyserCommandManager = new GeyserCommandManager(geyser);
- this.geyserCommandManager.init();
+ GeyserImpl.start();
+
+ if (geyserConfig.isLegacyPingPassthrough()) {
+ this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
+ } else {
+ this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer);
+ }
+
+ // No need to re-register commands when reloading
+ if (GeyserImpl.getInstance().isReloading()) {
+ return;
+ }
this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands()));
for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
@@ -147,17 +144,18 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
this.commandManager.register(entry.getKey().description().id(), new GeyserVelocityCommandExecutor(this.geyser, commands));
}
- if (geyserConfig.isLegacyPingPassthrough()) {
- this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
- } else {
- this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer);
- }
-
proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener());
}
@Override
- public void onDisable() {
+ public void onGeyserDisable() {
+ if (geyser != null) {
+ geyser.disable();
+ }
+ }
+
+ @Override
+ public void onGeyserShutdown() {
if (geyser != null) {
geyser.shutdown();
}
@@ -188,26 +186,24 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
@Subscribe
public void onInit(ProxyInitializeEvent event) {
- onEnable();
+ this.onGeyserInitialize();
}
@Subscribe
public void onShutdown(ProxyShutdownEvent event) {
- onDisable();
+ this.onGeyserShutdown();
}
@Subscribe
public void onProxyBound(ListenerBoundEvent event) {
if (event.getListenerType() == ListenerType.MINECRAFT) {
// Once listener is bound, do our startup process
- this.postStartup();
+ this.onGeyserEnable();
if (geyserInjector != null) {
// After this bound, we know that the channel initializer cannot change without it being ineffective for Velocity, too
geyserInjector.initializeLocalChannel(this);
}
-
- INITIALIZED = true;
}
}
@@ -242,4 +238,21 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
}
return false;
}
+
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ private boolean loadConfig() {
+ try {
+ if (!configFolder.toFile().exists())
+ //noinspection ResultOfMethodCallIgnored
+ configFolder.toFile().mkdirs();
+ File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(),
+ "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
+ this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class);
+ } catch (IOException ex) {
+ geyserLogger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
+ ex.printStackTrace();
+ return false;
+ }
+ return true;
+ }
}
diff --git a/bootstrap/viaproxy/build.gradle.kts b/bootstrap/viaproxy/build.gradle.kts
new file mode 100644
index 000000000..6eadc790f
--- /dev/null
+++ b/bootstrap/viaproxy/build.gradle.kts
@@ -0,0 +1,29 @@
+dependencies {
+ api(projects.core)
+
+ compileOnlyApi(libs.viaproxy)
+}
+
+platformRelocate("net.kyori")
+platformRelocate("org.yaml")
+platformRelocate("it.unimi.dsi.fastutil")
+platformRelocate("org.cloudburstmc.netty")
+
+// These dependencies are already present on the platform
+provided(libs.viaproxy)
+
+application {
+ mainClass.set("org.geysermc.geyser.platform.viaproxy.GeyserViaProxyMain")
+}
+
+tasks.withType {
+ archiveBaseName.set("Geyser-ViaProxy")
+
+ dependencies {
+ exclude(dependency("com.google.*:.*"))
+ exclude(dependency("io.netty:.*"))
+ exclude(dependency("io.netty.incubator:.*"))
+ exclude(dependency("org.slf4j:.*"))
+ exclude(dependency("org.ow2.asm:.*"))
+ }
+}
diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyConfiguration.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyConfiguration.java
new file mode 100644
index 000000000..afc46fa6a
--- /dev/null
+++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyConfiguration.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+package org.geysermc.geyser.platform.viaproxy;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import net.raphimc.vialegacy.api.LegacyProtocolVersion;
+import net.raphimc.viaproxy.ViaProxy;
+import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyConfig;
+import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
+
+import java.io.File;
+import java.nio.file.Path;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
+public class GeyserViaProxyConfiguration extends GeyserJacksonConfiguration {
+
+ private RemoteConfiguration remote = new RemoteConfiguration() {
+ @Override
+ public boolean isForwardHost() {
+ return super.isForwardHost() || !ViaProxy.getConfig().getWildcardDomainHandling().equals(ViaProxyConfig.WildcardDomainHandling.NONE);
+ }
+ };
+
+ @Override
+ public Path getFloodgateKeyPath() {
+ return new File(GeyserViaProxyPlugin.ROOT_FOLDER, this.getFloodgateKeyFile()).toPath();
+ }
+
+ @Override
+ public int getPingPassthroughInterval() {
+ int interval = super.getPingPassthroughInterval();
+ if (interval < 15 && ViaProxy.getConfig().getTargetVersion() != null && ViaProxy.getConfig().getTargetVersion().olderThanOrEqualTo(LegacyProtocolVersion.r1_6_4)) {
+ // <= 1.6.4 servers sometimes block incoming connections from an IP address if too many connections are made
+ interval = 15;
+ }
+ return interval;
+ }
+
+ @Override
+ public RemoteConfiguration getRemote() {
+ return this.remote;
+ }
+
+}
diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java
new file mode 100644
index 000000000..0bfc9d022
--- /dev/null
+++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+package org.geysermc.geyser.platform.viaproxy;
+
+import lombok.Getter;
+import net.raphimc.viaproxy.ViaProxy;
+import net.raphimc.viaproxy.plugins.ViaProxyPlugin;
+import org.geysermc.geyser.dump.BootstrapDumpInfo;
+import org.geysermc.geyser.text.AsteriskSerializer;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Getter
+public class GeyserViaProxyDumpInfo extends BootstrapDumpInfo {
+
+ private final String platformVersion;
+ private final boolean onlineMode;
+
+ @AsteriskSerializer.Asterisk(isIp = true)
+ private final String serverIP;
+ private final int serverPort;
+ private final List plugins;
+
+ public GeyserViaProxyDumpInfo() {
+ this.platformVersion = ViaProxy.VERSION;
+ this.onlineMode = ViaProxy.getConfig().isProxyOnlineMode();
+ if (ViaProxy.getConfig().getBindAddress() instanceof InetSocketAddress inetSocketAddress) {
+ this.serverIP = inetSocketAddress.getHostString();
+ this.serverPort = inetSocketAddress.getPort();
+ } else {
+ this.serverIP = "unsupported";
+ this.serverPort = 0;
+ }
+ this.plugins = new ArrayList<>();
+
+ for (ViaProxyPlugin plugin : ViaProxy.getPluginManager().getPlugins()) {
+ this.plugins.add(new PluginInfo(true, plugin.getName(), plugin.getVersion(), "unknown", Collections.singletonList(plugin.getAuthor())));
+ }
+ }
+
+}
diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyLogger.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyLogger.java
new file mode 100644
index 000000000..10f414b51
--- /dev/null
+++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyLogger.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+package org.geysermc.geyser.platform.viaproxy;
+
+import net.raphimc.viaproxy.cli.ConsoleFormatter;
+import org.apache.logging.log4j.Logger;
+import org.geysermc.geyser.GeyserLogger;
+import org.geysermc.geyser.command.GeyserCommandSource;
+
+public class GeyserViaProxyLogger implements GeyserLogger, GeyserCommandSource {
+
+ private final Logger logger;
+ private boolean debug;
+
+ public GeyserViaProxyLogger(Logger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ public void severe(String message) {
+ this.logger.fatal(ConsoleFormatter.convert(message));
+ }
+
+ @Override
+ public void severe(String message, Throwable error) {
+ this.logger.fatal(ConsoleFormatter.convert(message), error);
+ }
+
+ @Override
+ public void error(String message) {
+ this.logger.error(ConsoleFormatter.convert(message));
+ }
+
+ @Override
+ public void error(String message, Throwable error) {
+ this.logger.error(ConsoleFormatter.convert(message), error);
+ }
+
+ @Override
+ public void warning(String message) {
+ this.logger.warn(ConsoleFormatter.convert(message));
+ }
+
+ @Override
+ public void info(String message) {
+ this.logger.info(ConsoleFormatter.convert(message));
+ }
+
+ @Override
+ public void debug(String message) {
+ if (this.debug) {
+ this.logger.debug(ConsoleFormatter.convert(message));
+ }
+ }
+
+ @Override
+ public void setDebug(boolean debug) {
+ this.debug = debug;
+ }
+
+ @Override
+ public boolean isDebug() {
+ return this.debug;
+ }
+
+}
diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyMain.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyMain.java
new file mode 100644
index 000000000..582a97d78
--- /dev/null
+++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyMain.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.viaproxy;
+
+import org.geysermc.geyser.GeyserMain;
+
+public class GeyserViaProxyMain extends GeyserMain {
+
+ public static void main(String[] args) {
+ new GeyserViaProxyMain().displayMessage();
+ }
+
+ public String getPluginType() {
+ return "ViaProxy";
+ }
+
+ public String getPluginFolder() {
+ return "plugins";
+ }
+
+}
diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java
new file mode 100644
index 000000000..bdc80335a
--- /dev/null
+++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+package org.geysermc.geyser.platform.viaproxy;
+
+import net.lenni0451.lambdaevents.EventHandler;
+import net.raphimc.vialegacy.api.LegacyProtocolVersion;
+import net.raphimc.viaproxy.ViaProxy;
+import net.raphimc.viaproxy.plugins.PluginManager;
+import net.raphimc.viaproxy.plugins.ViaProxyPlugin;
+import net.raphimc.viaproxy.plugins.events.ConsoleCommandEvent;
+import net.raphimc.viaproxy.plugins.events.ProxyStartEvent;
+import net.raphimc.viaproxy.plugins.events.ProxyStopEvent;
+import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent;
+import org.apache.logging.log4j.LogManager;
+import org.geysermc.geyser.GeyserBootstrap;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.GeyserLogger;
+import org.geysermc.geyser.api.event.EventRegistrar;
+import org.geysermc.geyser.api.network.AuthType;
+import org.geysermc.geyser.api.util.PlatformType;
+import org.geysermc.geyser.command.GeyserCommandManager;
+import org.geysermc.geyser.configuration.GeyserConfiguration;
+import org.geysermc.geyser.dump.BootstrapDumpInfo;
+import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
+import org.geysermc.geyser.ping.IGeyserPingPassthrough;
+import org.geysermc.geyser.platform.viaproxy.listener.GeyserServerTransferListener;
+import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.text.GeyserLocale;
+import org.geysermc.geyser.util.FileUtils;
+import org.geysermc.geyser.util.LoopbackUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.UUID;
+
+public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootstrap, EventRegistrar {
+
+ public static final File ROOT_FOLDER = new File(PluginManager.PLUGINS_DIR, "Geyser");
+
+ private final GeyserViaProxyLogger logger = new GeyserViaProxyLogger(LogManager.getLogger("Geyser"));
+ private GeyserViaProxyConfiguration config;
+ private GeyserImpl geyser;
+ private GeyserCommandManager commandManager;
+ private IGeyserPingPassthrough pingPassthrough;
+
+ @Override
+ public void onEnable() {
+ ROOT_FOLDER.mkdirs();
+
+ GeyserLocale.init(this);
+ this.onGeyserInitialize();
+
+ ViaProxy.EVENT_MANAGER.register(this);
+ }
+
+ @Override
+ public void onDisable() {
+ this.onGeyserShutdown();
+ }
+
+ @EventHandler
+ private void onConsoleCommand(final ConsoleCommandEvent event) {
+ final String command = event.getCommand().startsWith("/") ? event.getCommand().substring(1) : event.getCommand();
+ if (this.getGeyserCommandManager().runCommand(this.getGeyserLogger(), command + " " + String.join(" ", event.getArgs()))) {
+ event.setCancelled(true);
+ }
+ }
+
+ @EventHandler
+ private void onShouldVerifyOnlineModeEvent(final ShouldVerifyOnlineModeEvent event) {
+ final UUID uuid = event.getProxyConnection().getGameProfile().getId();
+ if (uuid == null) return;
+
+ final GeyserSession connection = GeyserImpl.getInstance().onlineConnections().stream().filter(s -> s.javaUuid().equals(uuid)).findAny().orElse(null);
+ if (connection == null) return;
+
+ if (connection.javaUsername().equals(event.getProxyConnection().getGameProfile().getName())) {
+ event.setCancelled(true);
+ }
+ }
+
+ @EventHandler
+ private void onProxyStart(final ProxyStartEvent event) {
+ this.onGeyserEnable();
+ }
+
+ @EventHandler
+ private void onProxyStop(final ProxyStopEvent event) {
+ this.onGeyserDisable();
+ }
+
+ @Override
+ public void onGeyserInitialize() {
+ if (!this.loadConfig()) {
+ return;
+ }
+
+ this.geyser = GeyserImpl.load(PlatformType.VIAPROXY, this);
+ this.geyser.eventBus().register(this, new GeyserServerTransferListener());
+ LoopbackUtil.checkAndApplyLoopback(this.logger);
+ }
+
+ @Override
+ public void onGeyserEnable() {
+ if (GeyserImpl.getInstance().isReloading()) {
+ if (!this.loadConfig()) {
+ return;
+ }
+ }
+
+ this.commandManager = new GeyserCommandManager(this.geyser);
+ this.commandManager.init();
+
+ GeyserImpl.start();
+
+ if (ViaProxy.getConfig().getTargetVersion() != null && ViaProxy.getConfig().getTargetVersion().newerThanOrEqualTo(LegacyProtocolVersion.b1_8tob1_8_1)) {
+ // Only initialize the ping passthrough if the protocol version is above beta 1.7.3, as that's when the status protocol was added
+ this.pingPassthrough = GeyserLegacyPingPassthrough.init(this.geyser);
+ }
+ }
+
+ @Override
+ public void onGeyserDisable() {
+ this.geyser.disable();
+ }
+
+ @Override
+ public void onGeyserShutdown() {
+ this.geyser.shutdown();
+ }
+
+ @Override
+ public GeyserConfiguration getGeyserConfig() {
+ return this.config;
+ }
+
+ @Override
+ public GeyserLogger getGeyserLogger() {
+ return this.logger;
+ }
+
+ @Override
+ public GeyserCommandManager getGeyserCommandManager() {
+ return this.commandManager;
+ }
+
+ @Override
+ public IGeyserPingPassthrough getGeyserPingPassthrough() {
+ return this.pingPassthrough;
+ }
+
+ @Override
+ public Path getConfigFolder() {
+ return ROOT_FOLDER.toPath();
+ }
+
+ @Override
+ public BootstrapDumpInfo getDumpInfo() {
+ return new GeyserViaProxyDumpInfo();
+ }
+
+ @NotNull
+ @Override
+ public String getServerBindAddress() {
+ if (ViaProxy.getConfig().getBindAddress() instanceof InetSocketAddress socketAddress) {
+ return socketAddress.getHostString();
+ } else {
+ throw new IllegalStateException("Unsupported bind address type: " + ViaProxy.getConfig().getBindAddress().getClass().getName());
+ }
+ }
+
+ @Override
+ public int getServerPort() {
+ if (ViaProxy.getConfig().getBindAddress() instanceof InetSocketAddress socketAddress) {
+ return socketAddress.getPort();
+ } else {
+ throw new IllegalStateException("Unsupported bind address type: " + ViaProxy.getConfig().getBindAddress().getClass().getName());
+ }
+ }
+
+ @Override
+ public boolean testFloodgatePluginPresent() {
+ return false;
+ }
+
+ private boolean loadConfig() {
+ try {
+ final File configFile = FileUtils.fileOrCopiedFromResource(new File(ROOT_FOLDER, "config.yml"), "config.yml", s -> s.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
+ this.config = FileUtils.loadConfig(configFile, GeyserViaProxyConfiguration.class);
+ } catch (IOException e) {
+ this.logger.severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), e);
+ return false;
+ }
+ this.config.getRemote().setAuthType(Files.isRegularFile(this.config.getFloodgateKeyPath()) ? AuthType.FLOODGATE : AuthType.OFFLINE);
+ this.logger.setDebug(this.config.isDebugMode());
+ GeyserConfiguration.checkGeyserConfiguration(this.config, this.logger);
+ return true;
+ }
+
+}
diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/listener/GeyserServerTransferListener.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/listener/GeyserServerTransferListener.java
new file mode 100644
index 000000000..64b3cc56e
--- /dev/null
+++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/listener/GeyserServerTransferListener.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.platform.viaproxy.listener;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.net.HostAndPort;
+import org.geysermc.event.PostOrder;
+import org.geysermc.event.subscribe.Subscribe;
+import org.geysermc.geyser.api.event.bedrock.SessionLoginEvent;
+import org.geysermc.geyser.api.event.java.ServerTransferEvent;
+import org.geysermc.geyser.session.GeyserSession;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class GeyserServerTransferListener {
+
+ private final Cache> cookieStorages = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
+
+ @Subscribe(postOrder = PostOrder.FIRST)
+ private void onServerTransfer(final ServerTransferEvent event) {
+ this.cookieStorages.put(event.connection().xuid(), event.cookies());
+ final GeyserSession geyserSession = (GeyserSession) event.connection();
+ final HostAndPort hostAndPort = HostAndPort.fromString(geyserSession.getClientData().getServerAddress()).withDefaultPort(19132);
+ event.bedrockHost(hostAndPort.getHost());
+ event.bedrockPort(hostAndPort.getPort());
+ }
+
+ @Subscribe(postOrder = PostOrder.FIRST)
+ private void onSessionLogin(final SessionLoginEvent event) {
+ final Map cookies = this.cookieStorages.asMap().remove(event.connection().xuid());
+ if (cookies != null) {
+ event.cookies(cookies);
+ event.transferring(true);
+ }
+ }
+
+}
diff --git a/bootstrap/viaproxy/src/main/resources/viaproxy.yml b/bootstrap/viaproxy/src/main/resources/viaproxy.yml
new file mode 100644
index 000000000..66fbdb932
--- /dev/null
+++ b/bootstrap/viaproxy/src/main/resources/viaproxy.yml
@@ -0,0 +1,5 @@
+name: "${name}-ViaProxy"
+version: "${version}"
+author: "${author}"
+main: "org.geysermc.geyser.platform.viaproxy.GeyserViaProxyPlugin"
+min-version: "3.2.1"
diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts
index c0c683920..190386667 100644
--- a/build-logic/build.gradle.kts
+++ b/build-logic/build.gradle.kts
@@ -4,14 +4,20 @@ plugins {
repositories {
gradlePluginPortal()
- maven("https://repo.opencollab.dev/maven-snapshots")
+
+ maven("https://repo.opencollab.dev/maven-snapshots/")
+ maven("https://maven.fabricmc.net/")
+ maven("https://maven.neoforged.net/releases")
+ maven("https://maven.architectury.dev/")
}
dependencies {
- implementation("net.kyori", "indra-common", "3.1.1")
- implementation("com.github.johnrengelman", "shadow", "7.1.3-SNAPSHOT")
-
- // Within the gradle plugin classpath, there is a version conflict between loom and some other
- // plugin for databind. This fixes it: minimum 2.13.2 is required by loom.
- implementation("com.fasterxml.jackson.core:jackson-databind:2.14.0")
+ // this is OK as long as the same version catalog is used in the main build and build-logic
+ // see https://github.com/gradle/gradle/issues/15383#issuecomment-779893192
+ implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
+ implementation(libs.indra)
+ implementation(libs.shadow)
+ implementation(libs.architectury.plugin)
+ implementation(libs.architectury.loom)
+ implementation(libs.minotaur)
}
diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts
new file mode 100644
index 000000000..63bde189b
--- /dev/null
+++ b/build-logic/settings.gradle.kts
@@ -0,0 +1,11 @@
+@file:Suppress("UnstableApiUsage")
+
+dependencyResolutionManagement {
+ versionCatalogs {
+ create("libs") {
+ from(files("../gradle/libs.versions.toml"))
+ }
+ }
+}
+
+rootProject.name = "build-logic"
\ No newline at end of file
diff --git a/build-logic/src/main/kotlin/LibsAccessor.kt b/build-logic/src/main/kotlin/LibsAccessor.kt
new file mode 100644
index 000000000..2a0c09eb6
--- /dev/null
+++ b/build-logic/src/main/kotlin/LibsAccessor.kt
@@ -0,0 +1,6 @@
+import org.gradle.accessors.dm.LibrariesForLibs
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.getByType
+
+val Project.libs: LibrariesForLibs
+ get() = rootProject.extensions.getByType()
\ No newline at end of file
diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt
index 1e1732852..41e11344b 100644
--- a/build-logic/src/main/kotlin/extensions.kt
+++ b/build-logic/src/main/kotlin/extensions.kt
@@ -24,11 +24,17 @@
*/
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.options.Option
+import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.named
+import java.io.File
+import java.net.URL
fun Project.relocate(pattern: String) {
tasks.named("shadowJar") {
@@ -52,22 +58,63 @@ fun Project.platformRelocate(pattern: String, exclusion: String = "") {
val providedDependencies = mutableMapOf>()
-fun Project.provided(pattern: String, name: String, version: String, excludedOn: Int = 0b110) {
+fun getProvidedDependenciesForProject(projectName: String): MutableSet {
+ return providedDependencies.getOrDefault(projectName, emptySet()).toMutableSet()
+}
+
+fun Project.provided(pattern: String, name: String, excludedOn: Int = 0b110) {
providedDependencies.getOrPut(project.name) { mutableSetOf() }
- .add("${calcExclusion(pattern, 0b100, excludedOn)}:" +
- "${calcExclusion(name, 0b10, excludedOn)}:" +
- calcExclusion(version, 0b1, excludedOn))
- dependencies.add("compileOnlyApi", "$pattern:$name:$version")
+ .add("${calcExclusion(pattern, 0b100, excludedOn)}:${calcExclusion(name, 0b10, excludedOn)}")
}
fun Project.provided(dependency: ProjectDependency) =
- provided(dependency.group!!, dependency.name, dependency.version!!)
+ provided(dependency.group!!, dependency.name)
fun Project.provided(dependency: MinimalExternalModuleDependency) =
- provided(dependency.module.group, dependency.module.name, dependency.versionConstraint.requiredVersion)
+ provided(dependency.module.group, dependency.module.name)
fun Project.provided(provider: Provider) =
provided(provider.get())
+open class DownloadFilesTask : DefaultTask() {
+ @Input
+ var urls: List = listOf()
+
+ @Input
+ var destinationDir: String = ""
+
+ @Option(option="suffix", description="suffix")
+ @Input
+ var suffix: String = ""
+
+ @Input
+ var suffixedFiles: List = listOf()
+
+ @TaskAction
+ fun downloadAndAddSuffix() {
+ urls.forEach { fileUrl ->
+ val fileName = fileUrl.substringAfterLast("/")
+ val baseName = fileName.substringBeforeLast(".")
+ val extension = fileName.substringAfterLast(".", "")
+ val shouldSuffix = fileName in suffixedFiles
+ val suffixedFileName = if (shouldSuffix && extension.isNotEmpty()) "$baseName.$suffix.$extension" else fileName
+ val outputFile = File(destinationDir, suffixedFileName)
+
+ if (!outputFile.parentFile.exists()) {
+ outputFile.parentFile.mkdirs()
+ }
+
+ URL(fileUrl).openStream().use { input ->
+ outputFile.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+
+ println("Downloaded: $suffixedFileName")
+ }
+ }
+}
+
private fun calcExclusion(section: String, bit: Int, excludedOn: Int): String =
- if (excludedOn and bit > 0) section else ""
\ No newline at end of file
+ if (excludedOn and bit > 0) section else ""
+
diff --git a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts
index 01c769733..950c0184b 100644
--- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts
+++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts
@@ -22,8 +22,8 @@ indra {
tasks {
processResources {
- // Spigot, BungeeCord, Velocity, Fabric
- filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json")) {
+ // Spigot, BungeeCord, Velocity, Fabric, ViaProxy, NeoForge
+ filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json", "viaproxy.yml", "META-INF/neoforge.mods.toml")) {
expand(
"id" to "geyser",
"name" to "Geyser",
diff --git a/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts
new file mode 100644
index 000000000..7952bcf14
--- /dev/null
+++ b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts
@@ -0,0 +1,123 @@
+@file:Suppress("UnstableApiUsage")
+
+import net.fabricmc.loom.task.RemapJarTask
+import org.gradle.kotlin.dsl.dependencies
+import org.gradle.kotlin.dsl.maven
+
+plugins {
+ id("geyser.publish-conventions")
+ id("architectury-plugin")
+ id("dev.architectury.loom")
+}
+
+// These are provided by Minecraft/modded platforms already, no need to include them
+provided("com.google.code.gson", "gson")
+provided("com.google.guava", ".*")
+provided("org.slf4j", "slf4j-api")
+provided("com.nukkitx.fastutil", ".*")
+provided("org.cloudburstmc.fastutil.maps", ".*")
+provided("org.cloudburstmc.fastutil.sets", ".*")
+provided("org.cloudburstmc.fastutil.commons", ".*")
+provided("org.cloudburstmc.fastutil", ".*")
+provided("org.checkerframework", "checker-qual")
+provided("io.netty", "netty-transport-classes-epoll")
+provided("io.netty", "netty-transport-native-epoll")
+provided("io.netty", "netty-transport-native-unix-common")
+provided("io.netty", "netty-transport-classes-kqueue")
+provided("io.netty", "netty-transport-native-kqueue")
+provided("io.netty.incubator", "netty-incubator-transport-native-io_uring")
+provided("io.netty.incubator", "netty-incubator-transport-classes-io_uring")
+provided("io.netty", "netty-handler")
+provided("io.netty", "netty-common")
+provided("io.netty", "netty-buffer")
+provided("io.netty", "netty-resolver")
+provided("io.netty", "netty-transport")
+provided("io.netty", "netty-codec")
+provided("io.netty", "netty-resolver-dns")
+provided("io.netty", "netty-resolver-dns-native-macos")
+provided("org.ow2.asm", "asm")
+
+architectury {
+ minecraft = libs.minecraft.get().version as String
+}
+
+loom {
+ silentMojangMappingsLicense()
+}
+
+indra {
+ javaVersions {
+ target(21)
+ }
+}
+
+configurations {
+ create("includeTransitive").isTransitive = true
+}
+
+tasks {
+ // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
+ // if it is present.
+ // If you remove this task, sources will not be generated.
+ sourcesJar {
+ archiveClassifier.set("sources")
+ from(sourceSets.main.get().allSource)
+ }
+
+ shadowJar {
+ // Mirrors the example fabric project, otherwise tons of dependencies are shaded that shouldn't be
+ configurations = listOf(project.configurations.shadow.get())
+ // The remapped shadowJar is the final desired mod jar
+ archiveVersion.set(project.version.toString())
+ archiveClassifier.set("shaded")
+ }
+
+ remapJar {
+ dependsOn(shadowJar)
+ inputFile.set(shadowJar.get().archiveFile)
+ archiveClassifier.set("")
+ archiveVersion.set("")
+ }
+
+ register("remapModrinthJar", RemapJarTask::class) {
+ dependsOn(shadowJar)
+ inputFile.set(shadowJar.get().archiveFile)
+ archiveVersion.set(project.version.toString() + "+build." + System.getenv("BUILD_NUMBER"))
+ archiveClassifier.set("")
+ }
+}
+
+afterEvaluate {
+ val providedDependencies = getProvidedDependenciesForProject(project.name)
+
+ // These are shaded, no need to JiJ them
+ configurations["shadow"].dependencies.forEach {shadowed ->
+ //println("Not including shadowed dependency: ${shadowed.group}:${shadowed.name}")
+ providedDependencies.add("${shadowed.group}:${shadowed.name}")
+ }
+
+ // Now: Include all transitive dependencies that aren't excluded
+ configurations["includeTransitive"].resolvedConfiguration.resolvedArtifacts.forEach { dep ->
+ if (!providedDependencies.contains("${dep.moduleVersion.id.group}:${dep.moduleVersion.id.name}")
+ and !providedDependencies.contains("${dep.moduleVersion.id.group}:.*")) {
+ //println("Including dependency via JiJ: ${dep.id}")
+ dependencies.add("include", dep.moduleVersion.id.toString())
+ } else {
+ //println("Not including ${dep.id} for ${project.name}!")
+ }
+ }
+}
+
+dependencies {
+ minecraft(libs.minecraft)
+ mappings(loom.officialMojangMappings())
+}
+
+repositories {
+ // mavenLocal()
+ maven("https://repo.opencollab.dev/main")
+ maven("https://jitpack.io")
+ maven("https://oss.sonatype.org/content/repositories/snapshots/")
+ maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
+ maven("https://maven.neoforged.net/releases")
+}
\ No newline at end of file
diff --git a/build-logic/src/main/kotlin/geyser.modrinth-uploading-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.modrinth-uploading-conventions.gradle.kts
new file mode 100644
index 000000000..d710ae1a2
--- /dev/null
+++ b/build-logic/src/main/kotlin/geyser.modrinth-uploading-conventions.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ id("com.modrinth.minotaur")
+}
+
+// Ensure that the readme is synched
+tasks.modrinth.get().dependsOn(tasks.modrinthSyncBody)
+
+modrinth {
+ token.set(System.getenv("MODRINTH_TOKEN") ?: "") // Even though this is the default value, apparently this prevents GitHub Actions caching the token?
+ projectId.set("geyser")
+ versionNumber.set(project.version as String + "-" + System.getenv("BUILD_NUMBER"))
+ versionType.set("beta")
+ changelog.set(System.getenv("CHANGELOG") ?: "")
+ gameVersions.add(libs.minecraft.get().version as String)
+ failSilently.set(true)
+
+ syncBodyFrom.set(rootProject.file("README.md").readText())
+}
\ No newline at end of file
diff --git a/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts
index 036ee803c..eca587721 100644
--- a/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts
+++ b/build-logic/src/main/kotlin/geyser.publish-conventions.gradle.kts
@@ -7,3 +7,9 @@ indra {
publishSnapshotsTo("geysermc", "https://repo.opencollab.dev/maven-snapshots")
publishReleasesTo("geysermc", "https://repo.opencollab.dev/maven-releases")
}
+
+publishing {
+ // skip shadow jar from publishing. Workaround for https://github.com/johnrengelman/shadow/issues/651
+ val javaComponent = project.components["java"] as AdhocComponentWithVariants
+ javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) { skip() }
+}
\ No newline at end of file
diff --git a/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts
index dde85c33a..c160e5ec6 100644
--- a/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts
+++ b/build-logic/src/main/kotlin/geyser.shadow-conventions.gradle.kts
@@ -7,7 +7,6 @@ plugins {
tasks {
named("jar") {
- archiveClassifier.set("unshaded")
from(project.rootProject.file("LICENSE"))
}
val shadowJar = named("shadowJar") {
diff --git a/build.gradle.kts b/build.gradle.kts
index a72b8a484..dfbf9837f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,9 +1,9 @@
plugins {
`java-library`
// Ensure AP works in eclipse (no effect on other IDEs)
- `eclipse`
+ eclipse
id("geyser.build-logic")
- id("io.freefair.lombok") version "6.3.0" apply false
+ alias(libs.plugins.lombok) apply false
}
allprojects {
@@ -12,17 +12,25 @@ allprojects {
description = properties["description"] as String
}
-java {
- toolchain {
- languageVersion.set(JavaLanguageVersion.of(17))
- }
-}
-
-val platforms = setOf(
- projects.fabric,
+val basePlatforms = setOf(
projects.bungeecord,
projects.spigot,
projects.standalone,
+ projects.velocity,
+ projects.viaproxy
+).map { it.dependencyProject }
+
+val moddedPlatforms = setOf(
+ projects.fabric,
+ projects.neoforge,
+ projects.mod
+).map { it.dependencyProject }
+
+val modrinthPlatforms = setOf(
+ projects.bungeecord,
+ projects.fabric,
+ projects.neoforge,
+ projects.spigot,
projects.velocity
).map { it.dependencyProject }
@@ -34,7 +42,14 @@ subprojects {
}
when (this) {
- in platforms -> plugins.apply("geyser.platform-conventions")
+ in basePlatforms -> plugins.apply("geyser.platform-conventions")
+ in moddedPlatforms -> plugins.apply("geyser.modded-conventions")
else -> plugins.apply("geyser.base-conventions")
}
-}
+
+ // Not combined with platform-conventions as that also contains
+ // platforms which we cant publish to modrinth
+ if (modrinthPlatforms.contains(this)) {
+ plugins.apply("geyser.modrinth-uploading-conventions")
+ }
+}
\ No newline at end of file
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 8f79a3b8a..3e18d1ec2 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -1,12 +1,15 @@
-import net.kyori.blossom.BlossomExtension
-
plugins {
- id("net.kyori.blossom")
- id("net.kyori.indra.git")
+ // Allow blossom to mark sources root of templates
+ idea
+ alias(libs.plugins.blossom)
id("geyser.publish-conventions")
}
dependencies {
+ constraints {
+ implementation(libs.raknet) // Ensure protocol does not override the RakNet version
+ }
+
api(projects.common)
api(projects.api)
@@ -21,7 +24,6 @@ dependencies {
implementation(libs.websocket)
api(libs.bundles.protocol)
- implementation(libs.blockstateupdater)
api(libs.mcauthlib)
api(libs.mcprotocollib) {
@@ -44,6 +46,8 @@ dependencies {
implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-x86_64" } }
implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-aarch_64" } }
implementation(libs.netty.transport.native.kqueue) { artifact { classifier = "osx-x86_64" } }
+ implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-x86_64" } }
+ implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-aarch_64" } }
// Adventure text serialization
api(libs.bundles.adventure)
@@ -63,11 +67,6 @@ dependencies {
api(libs.events)
}
-configurations.api {
- // This is still experimental - additionally, it could only really benefit standalone
- exclude(group = "io.netty.incubator", module = "netty-incubator-transport-native-io_uring")
-}
-
tasks.processResources {
// This is solely for backwards compatibility for other programs that used this file before the switch to gradle.
// It used to be generated by the maven Git-Commit-Id-Plugin
@@ -85,27 +84,30 @@ tasks.processResources {
}
}
-configure {
- val mainFile = "src/main/java/org/geysermc/geyser/GeyserImpl.java"
- val info = GitInfo()
-
- replaceToken("\${version}", "${info.version} (${info.gitVersion})", mainFile)
- replaceToken("\${gitVersion}", info.gitVersion, mainFile)
- replaceToken("\${buildNumber}", info.buildNumber, mainFile)
- replaceToken("\${branch}", info.branch, mainFile)
- replaceToken("\${commit}", info.commit, mainFile)
- replaceToken("\${repository}", info.repository, mainFile)
- replaceToken("\${dev}", info.isDev)
+sourceSets {
+ main {
+ blossom {
+ val info = GitInfo()
+ javaSources {
+ property("version", "${info.version} (${info.gitVersion})")
+ property("gitVersion", info.gitVersion)
+ property("buildNumber", info.buildNumber.toString())
+ property("branch", info.branch)
+ property("commit", info.commit)
+ property("repository", info.repository)
+ property("devVersion", info.isDev)
+ }
+ }
+ }
}
-// -1 as a fallback for local builds
-fun buildNumber(): Int = System.getenv("GITHUB_RUN_NUMBER")?.let { Integer.parseInt(it) } ?: -1
+fun Project.buildNumber(): Int =
+ (System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1
fun isDevBuild(branch: String, repository: String): Boolean {
return branch != "master" || repository.equals("https://github.com/GeyserMC/Geyser", ignoreCase = true).not()
}
-
inner class GitInfo {
val branch: String
val commit: String
@@ -139,3 +141,19 @@ inner class GitInfo {
version = "$projectVersion ($gitVersion)"
}
}
+
+// Manual task to download the bedrock data files from the CloudburstMC/Data repository
+// Invoke with ./gradlew :core:downloadBedrockData --suffix=1_20_70
+// Set suffix to the current Bedrock version
+tasks.register("downloadBedrockData") {
+ urls = listOf(
+ "https://raw.githubusercontent.com/CloudburstMC/Data/master/entity_identifiers.dat",
+ "https://raw.githubusercontent.com/CloudburstMC/Data/master/biome_definitions.dat",
+ "https://raw.githubusercontent.com/CloudburstMC/Data/master/block_palette.nbt",
+ "https://raw.githubusercontent.com/CloudburstMC/Data/master/creative_items.json",
+ "https://raw.githubusercontent.com/CloudburstMC/Data/master/runtime_item_states.json"
+ )
+ suffixedFiles = listOf("block_palette.nbt", "creative_items.json", "runtime_item_states.json")
+
+ destinationDir = "$projectDir/src/main/resources/bedrock"
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/util/collection/package-info.java b/core/src/main/java-templates/org/geysermc/geyser/BuildData.java
similarity index 69%
rename from core/src/main/java/org/geysermc/geyser/util/collection/package-info.java
rename to core/src/main/java-templates/org/geysermc/geyser/BuildData.java
index 46fa5df11..d489d8d17 100644
--- a/core/src/main/java/org/geysermc/geyser/util/collection/package-info.java
+++ b/core/src/main/java-templates/org/geysermc/geyser/BuildData.java
@@ -23,12 +23,15 @@
* @link https://github.com/GeyserMC/Geyser
*/
-/**
- * Contains useful collections for use in Geyser.
- *
- * Of note are the fixed int maps. Designed for use with block states that are positive and sequential, they do not allow keys to be
- * added that are not greater by one versus the previous key. Because of this, speedy operations of {@link java.util.Map#get(java.lang.Object)}
- * and {@link java.util.Map#containsKey(java.lang.Object)} can be performed by simply checking the bounds of the map
- * size and its "start" integer.
- */
-package org.geysermc.geyser.util.collection;
\ No newline at end of file
+package org.geysermc.geyser;
+
+// The constants are replaced before compilation
+public class BuildData {
+ public static final String GIT_VERSION = "{{ gitVersion }}";
+ public static final String VERSION = "{{ version }}";
+
+ public static final String BUILD_NUMBER = "{{ buildNumber }}";
+ public static final String BRANCH = "{{ branch }}";
+ public static final String COMMIT = "{{ commit }}";
+ public static final String REPOSITORY = "{{ repository }}";
+}
diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java
index 5de8e6e6b..588b25172 100644
--- a/core/src/main/java/org/geysermc/geyser/Constants.java
+++ b/core/src/main/java/org/geysermc/geyser/Constants.java
@@ -34,7 +34,7 @@ public final class Constants {
public static final String NEWS_OVERVIEW_URL = "https://api.geysermc.org/v2/news/";
public static final String NEWS_PROJECT_NAME = "geyser";
- public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/";
+ public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://geysermc.org/download#floodgate";
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
public static final String UPDATE_PERMISSION = "geyser.update";
diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java
index 4dbc1dca3..a9414d9d0 100644
--- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java
+++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java
@@ -44,14 +44,28 @@ public interface GeyserBootstrap {
GeyserWorldManager DEFAULT_CHUNK_MANAGER = new GeyserWorldManager();
/**
- * Called when the GeyserBootstrap is enabled
+ * Called when the GeyserBootstrap is initialized.
+ * This will only be called once, when Geyser is loading. Calling this must
+ * happen before {@link #onGeyserEnable()}, since this "sets up" Geyser.
*/
- void onEnable();
+ void onGeyserInitialize();
/**
- * Called when the GeyserBootstrap is disabled
+ * Called when the GeyserBootstrap is enabled/reloaded.
+ * This starts Geyser, after which, Geyser is in a player-accepting state.
*/
- void onDisable();
+ void onGeyserEnable();
+
+ /**
+ * Called when the GeyserBootstrap is disabled - either before a reload,
+ * of before fully shutting down.
+ */
+ void onGeyserDisable();
+
+ /**
+ * Called when the GeyserBootstrap is shutting down.
+ */
+ void onGeyserShutdown();
/**
* Returns the current GeyserConfiguration
diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java
index 99951dbdb..8416ed51c 100644
--- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java
+++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java
@@ -29,7 +29,6 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.github.steveice10.packetlib.tcp.TcpSession;
import io.netty.channel.epoll.Epoll;
import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.DefaultThreadFactory;
@@ -42,6 +41,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
+import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.geysermc.api.Geyser;
import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder;
@@ -52,14 +52,14 @@ import org.geysermc.floodgate.crypto.Base64Topping;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.news.NewsItemAction;
import org.geysermc.geyser.api.GeyserApi;
+import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.event.EventBus;
import org.geysermc.geyser.api.event.EventRegistrar;
-import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent;
-import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent;
-import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent;
+import org.geysermc.geyser.api.event.lifecycle.*;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.BedrockListener;
import org.geysermc.geyser.api.network.RemoteServer;
+import org.geysermc.geyser.api.util.MinecraftVersion;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
@@ -67,7 +67,9 @@ import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.erosion.UnixSocketClientListener;
import org.geysermc.geyser.event.GeyserEventBus;
import org.geysermc.geyser.extension.GeyserExtensionManager;
+import org.geysermc.geyser.impl.MinecraftVersionImpl;
import org.geysermc.geyser.level.WorldManager;
+import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.network.netty.GeyserServer;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries;
@@ -76,6 +78,7 @@ import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
import org.geysermc.geyser.session.SessionManager;
+import org.geysermc.geyser.session.cache.RegistryCache;
import org.geysermc.geyser.skin.FloodgateSkinUploader;
import org.geysermc.geyser.skin.ProvidedSkins;
import org.geysermc.geyser.skin.SkinProvider;
@@ -83,6 +86,7 @@ import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.*;
+import org.geysermc.mcprotocollib.network.tcp.TcpSession;
import java.io.File;
import java.io.FileWriter;
@@ -111,13 +115,13 @@ public class GeyserImpl implements GeyserApi {
.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
public static final String NAME = "Geyser";
- public static final String GIT_VERSION = "${gitVersion}"; // A fallback for running in IDEs
- public static final String VERSION = "${version}"; // A fallback for running in IDEs
+ public static final String GIT_VERSION = BuildData.GIT_VERSION;
+ public static final String VERSION = BuildData.VERSION;
- public static final String BUILD_NUMBER = "${buildNumber}";
- public static final String BRANCH = "${branch}";
- public static final String COMMIT = "${commit}";
- public static final String REPOSITORY = "${repository}";
+ public static final String BUILD_NUMBER = BuildData.BUILD_NUMBER;
+ public static final String BRANCH = BuildData.BRANCH;
+ public static final String COMMIT = BuildData.COMMIT;
+ public static final String REPOSITORY = BuildData.REPOSITORY;
@SuppressWarnings("ConstantValue")
public static final boolean IS_DEV = Boolean.parseBoolean("${dev}");
@@ -143,6 +147,7 @@ public class GeyserImpl implements GeyserApi {
private UnixSocketClientListener erosionUnixListener;
+ @Setter
private volatile boolean shuttingDown = false;
private ScheduledExecutorService scheduledThread;
@@ -160,8 +165,20 @@ public class GeyserImpl implements GeyserApi {
@Getter(AccessLevel.NONE)
private Map savedRefreshTokens;
+ @Getter
private static GeyserImpl instance;
+ /**
+ * Determines if we're currently reloading. Replaces per-bootstrap reload checks
+ */
+ private volatile boolean isReloading;
+
+ /**
+ * Determines if Geyser is currently enabled. This is used to determine if {@link #disable()} should be called during {@link #shutdown()}.
+ */
+ @Setter
+ private boolean isEnabled;
+
private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) {
instance = this;
@@ -170,13 +187,16 @@ public class GeyserImpl implements GeyserApi {
this.platformType = platformType;
this.bootstrap = bootstrap;
- GeyserLocale.finalizeDefaultLocale(this);
-
/* Initialize event bus */
this.eventBus = new GeyserEventBus();
- /* Load Extensions */
+ /* Create Extension Manager */
this.extensionManager = new GeyserExtensionManager();
+
+ /* Finalize locale loading now that we know the default locale from the config */
+ GeyserLocale.finalizeDefaultLocale(this);
+
+ /* Load Extensions */
this.extensionManager.init();
this.eventBus.fire(new GeyserPreInitializeEvent(this.extensionManager, this.eventBus));
}
@@ -200,6 +220,8 @@ public class GeyserImpl implements GeyserApi {
Registries.init();
BlockRegistries.init();
+ RegistryCache.init();
+
/* Initialize translators */
EntityDefinitions.init();
MessageTranslator.init();
@@ -209,6 +231,7 @@ public class GeyserImpl implements GeyserApi {
if (ex != null) {
return;
}
+
MinecraftLocale.ensureEN_US();
String locale = GeyserLocale.getDefaultLocale();
if (!"en_us".equals(locale)) {
@@ -238,11 +261,17 @@ public class GeyserImpl implements GeyserApi {
} else if (config.getRemote().authType() == AuthType.FLOODGATE) {
VersionCheckUtils.checkForOutdatedFloodgate(logger);
}
+
+ VersionCheckUtils.checkForOutdatedJava(logger);
}
private void startInstance() {
this.scheduledThread = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("Geyser Scheduled Thread"));
+ if (isReloading) {
+ // If we're reloading, the default locale in the config might have changed.
+ GeyserLocale.finalizeDefaultLocale(this);
+ }
GeyserLogger logger = bootstrap.getGeyserLogger();
GeyserConfiguration config = bootstrap.getGeyserConfig();
@@ -316,15 +345,33 @@ public class GeyserImpl implements GeyserApi {
}
}
- boolean floodgatePresent = bootstrap.testFloodgatePluginPresent();
- if (config.getRemote().authType() == AuthType.FLOODGATE && !floodgatePresent) {
- logger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " "
- + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
- return;
- } else if (config.isAutoconfiguredRemote() && floodgatePresent) {
- // Floodgate installed means that the user wants Floodgate authentication
- logger.debug("Auto-setting to Floodgate authentication.");
- config.getRemote().setAuthType(AuthType.FLOODGATE);
+ String broadcastPort = System.getProperty("geyserBroadcastPort", "");
+ if (!broadcastPort.isEmpty()) {
+ int parsedPort;
+ try {
+ parsedPort = Integer.parseInt(broadcastPort);
+ if (parsedPort < 1 || parsedPort > 65535) {
+ throw new NumberFormatException("The broadcast port must be between 1 and 65535 inclusive!");
+ }
+ } catch (NumberFormatException e) {
+ logger.error(String.format("Invalid broadcast port: %s! Defaulting to configured port.", broadcastPort + " (" + e.getMessage() + ")"));
+ parsedPort = config.getBedrock().port();
+ }
+ config.getBedrock().setBroadcastPort(parsedPort);
+ logger.info("Broadcast port set from system property: " + parsedPort);
+ }
+
+ if (platformType != PlatformType.VIAPROXY) {
+ boolean floodgatePresent = bootstrap.testFloodgatePluginPresent();
+ if (config.getRemote().authType() == AuthType.FLOODGATE && !floodgatePresent) {
+ logger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " "
+ + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
+ return;
+ } else if (config.isAutoconfiguredRemote() && floodgatePresent) {
+ // Floodgate installed means that the user wants Floodgate authentication
+ logger.debug("Auto-setting to Floodgate authentication.");
+ config.getRemote().setAuthType(AuthType.FLOODGATE);
+ }
}
}
@@ -522,12 +569,15 @@ public class GeyserImpl implements GeyserApi {
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
- this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus));
+ if (isReloading) {
+ this.eventBus.fire(new GeyserPostReloadEvent(this.extensionManager, this.eventBus));
+ } else {
+ this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus));
+ }
+
if (config.isNotifyOnNewBedrockUpdate()) {
VersionCheckUtils.checkForGeyserUpdate(this::getLogger);
}
-
- VersionCheckUtils.checkForOutdatedJava(logger);
}
@Override
@@ -586,9 +636,8 @@ public class GeyserImpl implements GeyserApi {
return session.transfer(address, port);
}
- public void shutdown() {
+ public void disable() {
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown"));
- shuttingDown = true;
if (sessionManager.size() >= 1) {
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.log", sessionManager.size()));
@@ -602,7 +651,6 @@ public class GeyserImpl implements GeyserApi {
skinUploader.close();
}
newsHandler.shutdown();
- this.commandManager().getCommands().clear();
if (this.erosionUnixListener != null) {
this.erosionUnixListener.close();
@@ -610,16 +658,31 @@ public class GeyserImpl implements GeyserApi {
Registries.RESOURCE_PACKS.get().clear();
+ this.setEnabled(false);
+ }
+
+ public void shutdown() {
+ shuttingDown = true;
+ if (isEnabled) {
+ this.disable();
+ }
+ this.commandManager().getCommands().clear();
+
+ // Disable extensions, fire the shutdown event
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
this.extensionManager.disableExtensions();
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done"));
}
- public void reload() {
- shutdown();
- this.extensionManager.enableExtensions();
- bootstrap.onEnable();
+ public void reloadGeyser() {
+ isReloading = true;
+ this.eventBus.fire(new GeyserPreReloadEvent(this.extensionManager, this.eventBus));
+
+ bootstrap.onGeyserDisable();
+ bootstrap.onGeyserEnable();
+
+ isReloading = false;
}
/**
@@ -691,11 +754,31 @@ public class GeyserImpl implements GeyserApi {
return platformType;
}
+ @Override
+ public @NonNull MinecraftVersion supportedJavaVersion() {
+ return new MinecraftVersionImpl(GameProtocol.getJavaMinecraftVersion(), GameProtocol.getJavaProtocolVersion());
+ }
+
+ @Override
+ public @NonNull List supportedBedrockVersions() {
+ ArrayList versions = new ArrayList<>();
+ for (BedrockCodec codec : GameProtocol.SUPPORTED_BEDROCK_CODECS) {
+ versions.add(new MinecraftVersionImpl(codec.getMinecraftVersion(), codec.getProtocolVersion()));
+ }
+ return Collections.unmodifiableList(versions);
+ }
+
+ @Override
+ public @NonNull CommandSource consoleCommandSource() {
+ return getLogger();
+ }
+
public int buildNumber() {
if (!this.isProductionEnvironment()) {
return 0;
}
+ //noinspection DataFlowIssue
return Integer.parseInt(BUILD_NUMBER);
}
@@ -712,13 +795,12 @@ public class GeyserImpl implements GeyserApi {
throw new RuntimeException("Geyser has not been loaded yet!");
}
- // We've been reloaded
- if (instance.isShuttingDown()) {
- instance.shuttingDown = false;
+ if (getInstance().isReloading()) {
instance.startInstance();
} else {
instance.initialize();
}
+ instance.setEnabled(true);
}
public GeyserLogger getLogger() {
@@ -765,8 +847,4 @@ public class GeyserImpl implements GeyserApi {
}
});
}
-
- public static GeyserImpl getInstance() {
- return instance;
- }
}
diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java
index d646845c7..72ed22381 100644
--- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java
+++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java
@@ -86,7 +86,7 @@ public class GeyserCommandManager {
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
}
- if (this.geyser.extensionManager().extensions().size() > 0) {
+ if (!this.geyser.extensionManager().extensions().isEmpty()) {
registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions"));
}
diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java
index ad51826c3..981c97595 100644
--- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java
+++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java
@@ -31,6 +31,7 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
+import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.LoopbackUtil;
@@ -84,7 +85,7 @@ public class ConnectionTestCommand extends GeyserCommand {
return;
}
} else {
- port = 19132;
+ port = geyser.getConfig().getBedrock().broadcastPort();
}
String ip = fullAddress[0];
@@ -112,30 +113,41 @@ public class ConnectionTestCommand extends GeyserCommand {
return;
}
- // Issue: do the ports not line up?
- if (port != geyser.getConfig().getBedrock().port()) {
- if (fullAddress.length == 2) {
- sender.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration ("
- + geyser.getConfig().getBedrock().port() + ")");
- sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config.");
- if (geyser.getConfig().getBedrock().isCloneRemotePort()) {
- sender.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead.");
+ GeyserConfiguration config = geyser.getConfig();
+
+ // Issue: do the ports not line up? We only check this if players don't override the broadcast port - if they do, they (hopefully) know what they're doing
+ if (config.getBedrock().broadcastPort() == config.getBedrock().port()) {
+ if (port != config.getBedrock().port()) {
+ if (fullAddress.length == 2) {
+ sender.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration ("
+ + config.getBedrock().port() + ")");
+ sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config.");
+ if (config.getBedrock().isCloneRemotePort()) {
+ sender.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead.");
+ }
+ } else {
+ sender.sendMessage("You did not specify the port to check (add it with \":\"), " +
+ "and the default port 19132 does not match the port in your Geyser configuration ("
+ + config.getBedrock().port() + ")!");
+ sender.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`.");
}
- } else {
- sender.sendMessage("You did not specify the port to check (add it with \":\"), " +
- "and the default port 19132 does not match the port in your Geyser configuration ("
- + geyser.getConfig().getBedrock().port() + ")!");
- sender.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`.");
+ }
+ } else {
+ if (config.getBedrock().broadcastPort() != port) {
+ sender.sendMessage("The port you are testing with (" + port + ") is not the same as the broadcast port set in your Geyser configuration ("
+ + config.getBedrock().broadcastPort() + "). ");
+ sender.sendMessage("You ONLY need to change the broadcast port if clients connects with a port different from the port Geyser is running on.");
+ sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `broadcast-port` in the config.");
}
}
// Issue: is the `bedrock` `address` in the config different?
- if (!geyser.getConfig().getBedrock().address().equals("0.0.0.0")) {
+ if (!config.getBedrock().address().equals("0.0.0.0")) {
sender.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional.");
}
// Issue: did someone turn on enable-proxy-protocol, and they didn't mean it?
- if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
+ if (config.getBedrock().isEnableProxyProtocol()) {
sender.sendMessage("You have the `enable-proxy-protocol` setting enabled. " +
"Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled.");
}
@@ -166,7 +178,7 @@ public class ConnectionTestCommand extends GeyserCommand {
String connectionTestMotd = "Geyser Connection Test " + randomStr;
CONNECTION_TEST_MOTD = connectionTestMotd;
- sender.sendMessage("Testing server connection now. Please wait...");
+ sender.sendMessage("Testing server connection to " + ip + " with port: " + port + " now. Please wait...");
JsonNode output;
try {
String hostname = URLEncoder.encode(ip, StandardCharsets.UTF_8);
diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java
index a3cd8fa4c..987860238 100644
--- a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java
+++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java
@@ -55,7 +55,7 @@ public class ReloadCommand extends GeyserCommand {
geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick");
//FIXME Without the tiny wait, players do not get kicked - same happens when Geyser tries to disconnect all sessions on shutdown
- geyser.getScheduledThread().schedule(geyser::reload, 10, TimeUnit.MILLISECONDS);
+ geyser.getScheduledThread().schedule(geyser::reloadGeyser, 10, TimeUnit.MILLISECONDS);
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java
index 1ff12dea3..5952ea00d 100644
--- a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java
+++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java
@@ -25,12 +25,12 @@
package org.geysermc.geyser.command.defaults;
-import com.github.steveice10.mc.protocol.data.game.ClientCommand;
-import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.ClientCommand;
+import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket;
public class StatisticsCommand extends GeyserCommand {
diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java
index 7db539cc5..1cd3050c9 100644
--- a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java
+++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java
@@ -52,7 +52,7 @@ public class StopCommand extends GeyserCommand {
return;
}
- geyser.getBootstrap().onDisable();
+ geyser.getBootstrap().onGeyserShutdown();
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java
index f9bb15b32..88bb98171 100644
--- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java
+++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -72,8 +72,10 @@ public interface GeyserConfiguration {
boolean isDebugMode();
+ @Deprecated
boolean isAllowThirdPartyCapes();
+ @Deprecated
boolean isAllowThirdPartyEars();
String getShowCooldown();
@@ -122,6 +124,8 @@ public interface GeyserConfiguration {
void setPort(int port);
+ void setBroadcastPort(int broadcastPort);
+
boolean isCloneRemotePort();
int getCompressionLevel();
diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java
index 0874daa07..81ac824e4 100644
--- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java
+++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java
@@ -40,9 +40,11 @@ import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.network.CIDRMatcher;
import org.geysermc.geyser.text.AsteriskSerializer;
import org.geysermc.geyser.text.GeyserLocale;
+import org.geysermc.geyser.util.WebUtils;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@@ -92,7 +94,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
private boolean debugMode = false;
@JsonProperty("allow-third-party-capes")
- private boolean allowThirdPartyCapes = true;
+ private boolean allowThirdPartyCapes = false;
@JsonProperty("show-cooldown")
private String showCooldown = "title";
@@ -172,6 +174,15 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
return port;
}
+ @Setter
+ @JsonProperty("broadcast-port")
+ private int broadcastPort = 0;
+
+ @Override
+ public int broadcastPort() {
+ return broadcastPort;
+ }
+
@Getter
@JsonProperty("clone-remote-port")
private boolean cloneRemotePort = false;
@@ -224,7 +235,18 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
List matchers = this.whitelistedIPsMatchers;
if (matchers == null) {
synchronized (this) {
- this.whitelistedIPsMatchers = matchers = proxyProtocolWhitelistedIPs.stream()
+ // Check if proxyProtocolWhitelistedIPs contains URLs we need to fetch and parse by line
+ List whitelistedCIDRs = new ArrayList<>();
+ for (String ip: proxyProtocolWhitelistedIPs) {
+ if (!ip.startsWith("http")) {
+ whitelistedCIDRs.add(ip);
+ continue;
+ }
+
+ WebUtils.getLineStream(ip).forEach(whitelistedCIDRs::add);
+ }
+
+ this.whitelistedIPsMatchers = matchers = whitelistedCIDRs.stream()
.map(CIDRMatcher::new)
.collect(Collectors.toList());
}
diff --git a/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java
index 6a56c536a..7851fadfd 100644
--- a/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java
+++ b/core/src/main/java/org/geysermc/geyser/dump/BootstrapDumpInfo.java
@@ -27,8 +27,8 @@ package org.geysermc.geyser.dump;
import lombok.AllArgsConstructor;
import lombok.Getter;
-import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.text.AsteriskSerializer;
import java.util.List;
diff --git a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java
index 818607314..6989dc10a 100644
--- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java
+++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java
@@ -56,12 +56,7 @@ import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
+import java.util.*;
import java.util.stream.Collectors;
@Getter
@@ -78,6 +73,7 @@ public class DumpInfo {
private final GeyserConfiguration config;
private final Floodgate floodgate;
private final Object2IntMap userPlatforms;
+ private final int connectionAttempts;
private final HashInfo hashInfo;
private final RamInfo ramInfo;
private LogsInfo logsInfo;
@@ -129,6 +125,8 @@ public class DumpInfo {
userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1);
}
+ this.connectionAttempts = GeyserImpl.getInstance().getGeyserServer().getConnectionAttempts();
+
this.bootstrapInfo = GeyserImpl.getInstance().getBootstrap().getDumpInfo();
this.flagsInfo = new FlagsInfo();
diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java
index 8b430d559..31aa7cc73 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java
@@ -25,14 +25,15 @@
package org.geysermc.geyser.entity;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType;
-import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.entity.factory.EntityFactory;
+import org.geysermc.geyser.entity.properties.GeyserEntityProperties;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.translator.entity.EntityMetadataTranslator;
@@ -49,10 +50,10 @@ import java.util.function.BiConsumer;
* @param the entity type this definition represents
*/
public record EntityDefinition(EntityFactory factory, EntityType entityType, String identifier,
- float width, float height, float offset, List> translators) {
+ float width, float height, float offset, GeyserEntityProperties registeredProperties, List> translators) {
public static Builder inherited(EntityFactory factory, EntityDefinition super T> parent) {
- return new Builder<>(factory, parent.entityType, parent.identifier, parent.width, parent.height, parent.offset, new ObjectArrayList<>(parent.translators));
+ return new Builder<>(factory, parent.entityType, parent.identifier, parent.width, parent.height, parent.offset, parent.registeredProperties, new ObjectArrayList<>(parent.translators));
}
public static Builder builder(EntityFactory factory) {
@@ -87,6 +88,7 @@ public record EntityDefinition(EntityFactory factory, Entit
private float width;
private float height;
private float offset = 0.00001f;
+ private GeyserEntityProperties registeredProperties;
private final List> translators;
private Builder(EntityFactory factory) {
@@ -94,13 +96,14 @@ public record EntityDefinition(EntityFactory factory, Entit
translators = new ObjectArrayList<>();
}
- public Builder(EntityFactory factory, EntityType type, String identifier, float width, float height, float offset, List> translators) {
+ public Builder(EntityFactory factory, EntityType type, String identifier, float width, float height, float offset, GeyserEntityProperties registeredProperties, List> translators) {
this.factory = factory;
this.type = type;
this.identifier = identifier;
this.width = width;
this.height = height;
this.offset = offset;
+ this.registeredProperties = registeredProperties;
this.translators = translators;
}
@@ -127,6 +130,11 @@ public record EntityDefinition(EntityFactory factory, Entit
return this;
}
+ public Builder properties(GeyserEntityProperties registeredProperties) {
+ this.registeredProperties = registeredProperties;
+ return this;
+ }
+
public >> Builder addTranslator(MetadataType type, BiConsumer translateFunction) {
translators.add(new EntityMetadataTranslator<>(type, translateFunction));
return this;
@@ -149,10 +157,13 @@ public record EntityDefinition(EntityFactory factory, Entit
if (identifier == null && type != null) {
identifier = "minecraft:" + type.name().toLowerCase(Locale.ROOT);
}
- EntityDefinition definition = new EntityDefinition<>(factory, type, identifier, width, height, offset, translators);
+ EntityDefinition definition = new EntityDefinition<>(factory, type, identifier, width, height, offset, registeredProperties, translators);
if (register && definition.entityType() != null) {
Registries.ENTITY_DEFINITIONS.get().putIfAbsent(definition.entityType(), definition);
Registries.JAVA_ENTITY_IDENTIFIERS.get().putIfAbsent("minecraft:" + type.name().toLowerCase(Locale.ROOT), definition);
+ if (definition.registeredProperties() != null) {
+ Registries.BEDROCK_ENTITY_PROPERTIES.get().add(definition.registeredProperties().toNbtMap(identifier));
+ }
}
return definition;
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java
index e9d49fbd8..1496f8a82 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java
@@ -25,12 +25,16 @@
package org.geysermc.geyser.entity;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
+import org.geysermc.geyser.entity.type.AbstractWindChargeEntity;
+import org.geysermc.geyser.entity.factory.EntityFactory;
+import org.geysermc.geyser.entity.type.living.monster.raid.RavagerEntity;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
+import org.geysermc.geyser.entity.properties.GeyserEntityProperties;
import org.geysermc.geyser.entity.type.*;
import org.geysermc.geyser.entity.type.living.*;
import org.geysermc.geyser.entity.type.living.animal.*;
@@ -53,13 +57,17 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
public final class EntityDefinitions {
public static final EntityDefinition ALLAY;
public static final EntityDefinition AREA_EFFECT_CLOUD;
+ public static final EntityDefinition ARMADILLO;
public static final EntityDefinition ARMOR_STAND;
- public static final EntityDefinition ARROW;
+ public static final EntityDefinition ARROW;
public static final EntityDefinition AXOLOTL;
public static final EntityDefinition BAT;
public static final EntityDefinition BEE;
public static final EntityDefinition BLAZE;
public static final EntityDefinition BOAT;
+ public static final EntityDefinition BOGGED;
+ public static final EntityDefinition BREEZE;
+ public static final EntityDefinition BREEZE_WIND_CHARGE;
public static final EntityDefinition CAMEL;
public static final EntityDefinition CAT;
public static final EntityDefinition CAVE_SPIDER;
@@ -130,7 +138,7 @@ public final class EntityDefinitions {
public static final EntityDefinition POTION;
public static final EntityDefinition PUFFERFISH;
public static final EntityDefinition RABBIT;
- public static final EntityDefinition RAVAGER;
+ public static final EntityDefinition RAVAGER;
public static final EntityDefinition SALMON;
public static final EntityDefinition SHEEP;
public static final EntityDefinition SHULKER;
@@ -162,6 +170,7 @@ public final class EntityDefinitions {
public static final EntityDefinition VINDICATOR;
public static final EntityDefinition WANDERING_TRADER;
public static final EntityDefinition WARDEN;
+ public static final EntityDefinition WIND_CHARGE;
public static final EntityDefinition WITCH;
public static final EntityDefinition WITHER;
public static final EntityDefinition WITHER_SKELETON;
@@ -200,7 +209,6 @@ public final class EntityDefinitions {
.type(EntityType.AREA_EFFECT_CLOUD)
.height(0.5f).width(1.0f)
.addTranslator(MetadataType.FLOAT, AreaEffectCloudEntity::setRadius)
- .addTranslator(MetadataType.INT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.EFFECT_COLOR, entityMetadata.getValue()))
.addTranslator(null) // Waiting
.addTranslator(MetadataType.PARTICLE, AreaEffectCloudEntity::setParticle)
.build();
@@ -233,7 +241,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataType.BOOLEAN,
(enderCrystalEntity, entityMetadata) -> enderCrystalEntity.setFlag(EntityFlag.SHOW_BOTTOM, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue())) // There is a base located on the ender crystal
.build();
- EXPERIENCE_ORB = EntityDefinition.inherited(null, entityBase)
+ EXPERIENCE_ORB = EntityDefinition.inherited(ExpOrbEntity::new, entityBase)
.type(EntityType.EXPERIENCE_ORB)
.identifier("minecraft:xp_orb")
.build();
@@ -295,6 +303,7 @@ public final class EntityDefinitions {
TNT = EntityDefinition.inherited(TNTEntity::new, entityBase)
.type(EntityType.TNT)
.heightAndWidth(0.98f)
+ .offset(0.49f)
.addTranslator(MetadataType.INT, TNTEntity::setFuseLength)
.build();
@@ -372,14 +381,26 @@ public final class EntityDefinitions {
.heightAndWidth(0.25f)
.build();
+ EntityFactory windChargeSupplier = AbstractWindChargeEntity::new;
+ BREEZE_WIND_CHARGE = EntityDefinition.inherited(windChargeSupplier, entityBase)
+ .type(EntityType.BREEZE_WIND_CHARGE)
+ .identifier("minecraft:breeze_wind_charge_projectile")
+ .heightAndWidth(0.3125f)
+ .build();
+ WIND_CHARGE = EntityDefinition.inherited(windChargeSupplier, entityBase)
+ .type(EntityType.WIND_CHARGE)
+ .identifier("minecraft:wind_charge_projectile")
+ .heightAndWidth(0.3125f)
+ .build();
+
EntityDefinition abstractArrowBase = EntityDefinition.inherited(AbstractArrowEntity::new, entityBase)
.addTranslator(MetadataType.BYTE, AbstractArrowEntity::setArrowFlags)
.addTranslator(null) // "Piercing level"
.build();
- ARROW = EntityDefinition.inherited(TippedArrowEntity::new, abstractArrowBase)
+ ARROW = EntityDefinition.inherited(ArrowEntity::new, abstractArrowBase)
.type(EntityType.ARROW)
.heightAndWidth(0.25f)
- .addTranslator(MetadataType.INT, TippedArrowEntity::setPotionEffectColor)
+ .addTranslator(MetadataType.INT, ArrowEntity::setPotionEffectColor)
.build();
SPECTRAL_ARROW = EntityDefinition.inherited(abstractArrowBase.factory(), abstractArrowBase)
.type(EntityType.SPECTRAL_ARROW)
@@ -452,8 +473,7 @@ public final class EntityDefinitions {
EntityDefinition livingEntityBase = EntityDefinition.inherited(LivingEntity::new, entityBase)
.addTranslator(MetadataType.BYTE, LivingEntity::setLivingEntityFlags)
.addTranslator(MetadataType.FLOAT, LivingEntity::setHealth)
- .addTranslator(MetadataType.INT,
- (livingEntity, entityMetadata) -> livingEntity.getDirtyMetadata().put(EntityDataTypes.EFFECT_COLOR, entityMetadata.getValue()))
+ .addTranslator(MetadataType.PARTICLES, LivingEntity::setParticles)
.addTranslator(MetadataType.BOOLEAN,
(livingEntity, entityMetadata) -> livingEntity.getDirtyMetadata().put(EntityDataTypes.EFFECT_AMBIENCE, (byte) (((BooleanEntityMetadata) entityMetadata).getPrimitiveValue() ? 1 : 0)))
.addTranslator(null) // Arrow count
@@ -501,11 +521,20 @@ public final class EntityDefinitions {
.height(0.9f).width(0.5f)
.addTranslator(MetadataType.BYTE, BatEntity::setBatFlags)
.build();
+ BOGGED = EntityDefinition.inherited(BoggedEntity::new, mobEntityBase)
+ .type(EntityType.BOGGED)
+ .height(1.99f).width(0.6f)
+ .addTranslator(MetadataType.BOOLEAN, BoggedEntity::setSheared)
+ .build();
BLAZE = EntityDefinition.inherited(BlazeEntity::new, mobEntityBase)
.type(EntityType.BLAZE)
.height(1.8f).width(0.6f)
.addTranslator(MetadataType.BYTE, BlazeEntity::setBlazeFlags)
.build();
+ BREEZE = EntityDefinition.inherited(BreezeEntity::new, mobEntityBase)
+ .type(EntityType.BREEZE)
+ .height(1.77f).width(0.6f)
+ .build();
CREEPER = EntityDefinition.inherited(CreeperEntity::new, mobEntityBase)
.type(EntityType.CREEPER)
.height(1.7f).width(0.6f)
@@ -672,7 +701,7 @@ public final class EntityDefinitions {
SLIME = EntityDefinition.inherited(SlimeEntity::new, mobEntityBase)
.type(EntityType.SLIME)
.heightAndWidth(0.51f)
- .addTranslator(MetadataType.INT, SlimeEntity::setScale)
+ .addTranslator(MetadataType.INT, SlimeEntity::setSlimeScale)
.build();
MAGMA_CUBE = EntityDefinition.inherited(MagmaCubeEntity::new, SLIME)
.type(EntityType.MAGMA_CUBE)
@@ -745,9 +774,9 @@ public final class EntityDefinitions {
.type(EntityType.PILLAGER)
.height(1.8f).width(0.6f)
.offset(1.62f)
- .addTranslator(null) // Charging; doesn't have an equivalent on Bedrock //TODO check
+ .addTranslator(MetadataType.BOOLEAN, PillagerEntity::setChargingCrossbow)
.build();
- RAVAGER = EntityDefinition.inherited(raidParticipantEntityBase.factory(), raidParticipantEntityBase)
+ RAVAGER = EntityDefinition.inherited(RavagerEntity::new, raidParticipantEntityBase)
.type(EntityType.RAVAGER)
.height(1.9f).width(1.2f)
.build();
@@ -770,6 +799,20 @@ public final class EntityDefinitions {
// Extends ageable
{
+ ARMADILLO = EntityDefinition.inherited(ArmadilloEntity::new, ageableEntityBase)
+ .type(EntityType.ARMADILLO)
+ .height(0.65f).width(0.7f)
+ .properties(new GeyserEntityProperties.Builder()
+ .addEnum(
+ "minecraft:armadillo_state",
+ "unrolled",
+ "rolled_up",
+ "rolled_up_peeking",
+ "rolled_up_relaxing",
+ "rolled_up_unrolling")
+ .build())
+ .addTranslator(MetadataType.ARMADILLO_STATE, ArmadilloEntity::setArmadilloState)
+ .build();
AXOLOTL = EntityDefinition.inherited(AxolotlEntity::new, ageableEntityBase)
.type(EntityType.AXOLOTL)
.height(0.42f).width(0.7f)
@@ -780,6 +823,9 @@ public final class EntityDefinitions {
BEE = EntityDefinition.inherited(BeeEntity::new, ageableEntityBase)
.type(EntityType.BEE)
.heightAndWidth(0.6f)
+ .properties(new GeyserEntityProperties.Builder()
+ .addBoolean("minecraft:has_nectar")
+ .build())
.addTranslator(MetadataType.BYTE, BeeEntity::setBeeFlags)
.addTranslator(MetadataType.INT, BeeEntity::setAngerTime)
.build();
@@ -937,8 +983,7 @@ public final class EntityDefinitions {
LLAMA = EntityDefinition.inherited(LlamaEntity::new, chestedHorseEntityBase)
.type(EntityType.LLAMA)
.height(1.87f).width(0.9f)
- .addTranslator(MetadataType.INT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.STRENGTH, entityMetadata.getValue()))
- .addTranslator(MetadataType.INT, LlamaEntity::setCarpetedColor)
+ .addTranslator(MetadataType.INT, LlamaEntity::setStrength)
.addTranslator(MetadataType.INT, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityDataTypes.VARIANT, entityMetadata.getValue()))
.build();
TRADER_LLAMA = EntityDefinition.inherited(TraderLlamaEntity::new, LLAMA)
@@ -947,7 +992,7 @@ public final class EntityDefinitions {
.build();
}
- EntityDefinition tameableEntityBase = EntityDefinition.inherited(TameableEntity::new, ageableEntityBase)
+ EntityDefinition tameableEntityBase = EntityDefinition.inherited(null, ageableEntityBase) // No factory, is abstract
.addTranslator(MetadataType.BYTE, TameableEntity::setTameableFlags)
.addTranslator(MetadataType.OPTIONAL_UUID, TameableEntity::setOwner)
.build();
@@ -971,6 +1016,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataType.BOOLEAN, (wolfEntity, entityMetadata) -> wolfEntity.setFlag(EntityFlag.INTERESTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataType.INT, WolfEntity::setCollarColor)
.addTranslator(MetadataType.INT, WolfEntity::setWolfAngerTime)
+ .addTranslator(MetadataType.WOLF_VARIANT, WolfEntity::setWolfVariant)
.build();
// As of 1.18 these don't track entity data at all
diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java
new file mode 100644
index 000000000..c9ef7a2dd
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity;
+
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.cloudburstmc.protocol.bedrock.packet.EmotePacket;
+import org.geysermc.geyser.api.entity.EntityData;
+import org.geysermc.geyser.api.entity.type.GeyserEntity;
+import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
+import org.geysermc.geyser.entity.type.Entity;
+import org.geysermc.geyser.session.GeyserSession;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+public class GeyserEntityData implements EntityData {
+
+ private final GeyserSession session;
+
+ private final Set movementLockOwners = new HashSet<>();
+
+ public GeyserEntityData(GeyserSession session) {
+ this.session = session;
+ }
+
+ @Override
+ public @NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId) {
+ CompletableFuture future = new CompletableFuture<>();
+ session.ensureInEventLoop(() -> future.complete(session.getEntityCache().getEntityByJavaId(javaId)));
+ return future;
+ }
+
+ @Override
+ public void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId) {
+ Objects.requireNonNull(emoter, "emoter must not be null!");
+ Entity entity = (Entity) emoter;
+ if (entity.getSession() != session) {
+ throw new IllegalStateException("Given entity must be from this session!");
+ }
+
+ EmotePacket packet = new EmotePacket();
+ packet.setRuntimeEntityId(entity.getGeyserId());
+ packet.setXuid("");
+ packet.setPlatformId(""); // BDS sends empty
+ packet.setEmoteId(emoteId);
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public @NonNull GeyserPlayerEntity playerEntity() {
+ return session.getPlayerEntity();
+ }
+
+ @Override
+ public boolean lockMovement(boolean lock, @NonNull UUID owner) {
+ Objects.requireNonNull(owner, "owner must not be null!");
+ if (lock) {
+ movementLockOwners.add(owner);
+ } else {
+ movementLockOwners.remove(owner);
+ }
+
+ session.lockInputs(session.camera().isCameraLocked(), isMovementLocked());
+ return isMovementLocked();
+ }
+
+ @Override
+ public boolean isMovementLocked() {
+ return !movementLockOwners.isEmpty();
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java
index 234c1afe9..f19912a8c 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java
@@ -49,9 +49,10 @@ public enum GeyserAttributeType {
ATTACK_KNOCKBACK("minecraft:generic.attack_knockback", null, 1.5f, Float.MAX_VALUE, 0f),
ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f),
MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f),
+ SCALE("minecraft:generic.scale", null, 0.0625f, 16f, 1f), // Unused. Do we need this?
// Bedrock Attributes
- ABSORPTION(null, "minecraft:absorption", 0f, Float.MAX_VALUE, 0f),
+ ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f),
EXHAUSTION(null, "minecraft:player.exhaustion", 0f, 5f, 0f),
EXPERIENCE(null, "minecraft:player.experience", 0f, 1f, 0f),
EXPERIENCE_LEVEL(null, "minecraft:player.level", 0f, 24791.00f, 0f),
@@ -66,6 +67,10 @@ public enum GeyserAttributeType {
private final float maximum;
private final float defaultValue;
+ public AttributeData getAttribute() {
+ return getAttribute(defaultValue);
+ }
+
public AttributeData getAttribute(float value) {
return getAttribute(value, maximum);
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/properties/GeyserEntityProperties.java b/core/src/main/java/org/geysermc/geyser/entity/properties/GeyserEntityProperties.java
new file mode 100644
index 000000000..1729b0583
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/properties/GeyserEntityProperties.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.properties;
+
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.cloudburstmc.nbt.NbtMap;
+import org.cloudburstmc.nbt.NbtMapBuilder;
+import org.cloudburstmc.nbt.NbtType;
+import org.geysermc.geyser.entity.properties.type.BooleanProperty;
+import org.geysermc.geyser.entity.properties.type.EnumProperty;
+import org.geysermc.geyser.entity.properties.type.FloatProperty;
+import org.geysermc.geyser.entity.properties.type.IntProperty;
+import org.geysermc.geyser.entity.properties.type.PropertyType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@EqualsAndHashCode
+@ToString
+public class GeyserEntityProperties {
+ private final ObjectArrayList properties;
+ private final Object2IntMap propertyIndices;
+
+ private GeyserEntityProperties(ObjectArrayList properties,
+ Object2IntMap propertyIndices) {
+ this.properties = properties;
+ this.propertyIndices = propertyIndices;
+ }
+
+ public NbtMap toNbtMap(String entityType) {
+ NbtMapBuilder mapBuilder = NbtMap.builder();
+ List nbtProperties = new ArrayList<>();
+
+ for (PropertyType property : properties) {
+ nbtProperties.add(property.nbtMap());
+ }
+ mapBuilder.putList("properties", NbtType.COMPOUND, nbtProperties);
+
+ return mapBuilder.putString("type", entityType).build();
+ }
+
+ public @NonNull List getProperties() {
+ return properties;
+ }
+
+ public int getPropertyIndex(String name) {
+ return propertyIndices.getOrDefault(name, -1);
+ }
+
+ public static class Builder {
+ private final ObjectArrayList properties = new ObjectArrayList<>();
+ private final Object2IntMap propertyIndices = new Object2IntOpenHashMap<>();
+
+ public Builder addInt(@NonNull String name, int min, int max) {
+ if (propertyIndices.containsKey(name)) {
+ throw new IllegalArgumentException(
+ "Property with name " + name + " already exists on builder!");
+ }
+ PropertyType property = new IntProperty(name, min, max);
+ this.properties.add(property);
+ propertyIndices.put(name, properties.size() - 1);
+ return this;
+ }
+
+ public Builder addInt(@NonNull String name) {
+ if (propertyIndices.containsKey(name)) {
+ throw new IllegalArgumentException(
+ "Property with name " + name + " already exists on builder!");
+ }
+ PropertyType property = new IntProperty(name, Integer.MIN_VALUE, Integer.MAX_VALUE);
+ this.properties.add(property);
+ propertyIndices.put(name, properties.size() - 1);
+ return this;
+ }
+
+ public Builder addFloat(@NonNull String name, float min, float max) {
+ if (propertyIndices.containsKey(name)) {
+ throw new IllegalArgumentException(
+ "Property with name " + name + " already exists on builder!");
+ }
+ PropertyType property = new FloatProperty(name, min, max);
+ this.properties.add(property);
+ propertyIndices.put(name, properties.size() - 1);
+ return this;
+ }
+
+ public Builder addFloat(@NonNull String name) {
+ if (propertyIndices.containsKey(name)) {
+ throw new IllegalArgumentException(
+ "Property with name " + name + " already exists on builder!");
+ }
+ PropertyType property = new FloatProperty(name, Float.MIN_NORMAL, Float.MAX_VALUE);
+ this.properties.add(property);
+ propertyIndices.put(name, properties.size() - 1);
+ return this;
+ }
+
+ public Builder addBoolean(@NonNull String name) {
+ if (propertyIndices.containsKey(name)) {
+ throw new IllegalArgumentException(
+ "Property with name " + name + " already exists on builder!");
+ }
+ PropertyType property = new BooleanProperty(name);
+ this.properties.add(property);
+ propertyIndices.put(name, properties.size() - 1);
+ return this;
+ }
+
+ public Builder addEnum(@NonNull String name, List values) {
+ if (propertyIndices.containsKey(name)) {
+ throw new IllegalArgumentException(
+ "Property with name " + name + " already exists on builder!");
+ }
+ PropertyType property = new EnumProperty(name, values);
+ this.properties.add(property);
+ propertyIndices.put(name, properties.size() - 1);
+ return this;
+ }
+
+ public Builder addEnum(@NonNull String name, String... values) {
+ if (propertyIndices.containsKey(name)) {
+ throw new IllegalArgumentException(
+ "Property with name " + name + " already exists on builder!");
+ }
+ List valuesList = Arrays.asList(values); // Convert array to list
+ PropertyType property = new EnumProperty(name, valuesList);
+ this.properties.add(property);
+ propertyIndices.put(name, properties.size() - 1);
+ return this;
+ }
+
+ public GeyserEntityProperties build() {
+ return new GeyserEntityProperties(properties, propertyIndices);
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/entity/properties/GeyserEntityPropertyManager.java b/core/src/main/java/org/geysermc/geyser/entity/properties/GeyserEntityPropertyManager.java
new file mode 100644
index 000000000..29026b172
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/properties/GeyserEntityPropertyManager.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.properties;
+
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import org.cloudburstmc.protocol.bedrock.data.entity.FloatEntityProperty;
+import org.cloudburstmc.protocol.bedrock.data.entity.IntEntityProperty;
+import org.geysermc.geyser.entity.properties.type.EnumProperty;
+import org.geysermc.geyser.entity.properties.type.PropertyType;
+
+import java.util.List;
+
+public class GeyserEntityPropertyManager {
+
+ private final GeyserEntityProperties properties;
+
+ private final ObjectArrayList intEntityProperties = new ObjectArrayList<>();
+ private final ObjectArrayList floatEntityProperties = new ObjectArrayList<>();
+
+ public GeyserEntityPropertyManager(GeyserEntityProperties properties) {
+ this.properties = properties;
+ }
+
+ public void add(String propertyName, int value) {
+ int index = properties.getPropertyIndex(propertyName);
+ intEntityProperties.add(new IntEntityProperty(index, value));
+ }
+
+ public void add(String propertyName, boolean value) {
+ int index = properties.getPropertyIndex(propertyName);
+ intEntityProperties.add(new IntEntityProperty(index, value ? 1 : 0));
+ }
+
+ public void add(String propertyName, String value) {
+ int index = properties.getPropertyIndex(propertyName);
+ PropertyType property = properties.getProperties().get(index);
+ int enumIndex = ((EnumProperty) property).getIndex(value);
+ intEntityProperties.add(new IntEntityProperty(index, enumIndex));
+ }
+
+ public void add(String propertyName, float value) {
+ int index = properties.getPropertyIndex(propertyName);
+ floatEntityProperties.add(new FloatEntityProperty(index, value));
+ }
+
+ public boolean hasFloatProperties() {
+ return !this.floatEntityProperties.isEmpty();
+ }
+
+ public boolean hasIntProperties() {
+ return !this.intEntityProperties.isEmpty();
+ }
+
+ public boolean hasProperties() {
+ return hasFloatProperties() || hasIntProperties();
+ }
+
+ public ObjectArrayList intProperties() {
+ return this.intEntityProperties;
+ }
+
+ public void applyIntProperties(List properties) {
+ properties.addAll(intEntityProperties);
+ intEntityProperties.clear();
+ }
+
+ public ObjectArrayList floatProperties() {
+ return this.floatEntityProperties;
+ }
+
+ public void applyFloatProperties(List properties) {
+ properties.addAll(floatEntityProperties);
+ floatEntityProperties.clear();
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/entity/properties/type/BooleanProperty.java b/core/src/main/java/org/geysermc/geyser/entity/properties/type/BooleanProperty.java
new file mode 100644
index 000000000..6fc64ad4b
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/properties/type/BooleanProperty.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.properties.type;
+
+import org.cloudburstmc.nbt.NbtMap;
+
+public class BooleanProperty implements PropertyType {
+ private final String name;
+
+ public BooleanProperty(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public NbtMap nbtMap() {
+ return NbtMap.builder()
+ .putString("name", name)
+ .putInt("type", 2)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/entity/properties/type/EnumProperty.java b/core/src/main/java/org/geysermc/geyser/entity/properties/type/EnumProperty.java
new file mode 100644
index 000000000..05e12ba61
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/properties/type/EnumProperty.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.properties.type;
+
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import org.cloudburstmc.nbt.NbtMap;
+import org.cloudburstmc.nbt.NbtType;
+
+import java.util.List;
+
+public class EnumProperty implements PropertyType {
+ private final String name;
+ private final List values;
+ private final Object2IntMap valueIndexMap;
+
+ public EnumProperty(String name, List values) {
+ this.name = name;
+ this.values = values;
+ this.valueIndexMap = new Object2IntOpenHashMap<>(values.size());
+ for (int i = 0; i < values.size(); i++) {
+ valueIndexMap.put(values.get(i), i);
+ }
+ }
+
+ @Override
+ public NbtMap nbtMap() {
+ return NbtMap.builder()
+ .putString("name", name)
+ .putList("enum", NbtType.STRING, values)
+ .putInt("type", 3)
+ .build();
+ }
+
+ public int getIndex(String value) {
+ return valueIndexMap.getOrDefault(value, -1);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/entity/properties/type/FloatProperty.java b/core/src/main/java/org/geysermc/geyser/entity/properties/type/FloatProperty.java
new file mode 100644
index 000000000..8b808ebc3
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/properties/type/FloatProperty.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.properties.type;
+
+import org.cloudburstmc.nbt.NbtMap;
+
+public class FloatProperty implements PropertyType {
+ private final String name;
+ private final float max;
+ private final float min;
+
+ public FloatProperty(String name, float min, float max) {
+ this.name = name;
+ this.max = max;
+ this.min = min;
+ }
+
+ @Override
+ public NbtMap nbtMap() {
+ return NbtMap.builder()
+ .putString("name", name)
+ .putFloat("max", max)
+ .putFloat("min", min)
+ .putInt("type", 1)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/entity/properties/type/IntProperty.java b/core/src/main/java/org/geysermc/geyser/entity/properties/type/IntProperty.java
new file mode 100644
index 000000000..9e38db7c7
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/properties/type/IntProperty.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.properties.type;
+
+import org.cloudburstmc.nbt.NbtMap;
+
+public class IntProperty implements PropertyType {
+ private final String name;
+ private final int max;
+ private final int min;
+
+ public IntProperty(String name, int min, int max) {
+ this.name = name;
+ this.max = max;
+ this.min = min;
+ }
+
+ @Override
+ public NbtMap nbtMap() {
+ return NbtMap.builder()
+ .putString("name", name)
+ .putInt("max", max)
+ .putInt("min", min)
+ .putInt("type", 0)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/entity/properties/type/PropertyType.java b/core/src/main/java/org/geysermc/geyser/entity/properties/type/PropertyType.java
new file mode 100644
index 000000000..a64d7246a
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/properties/type/PropertyType.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.properties.type;
+
+import org.cloudburstmc.nbt.NbtMap;
+
+public interface PropertyType {
+ NbtMap nbtMap();
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java
index 6828d1020..1cc746d7a 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java
@@ -25,8 +25,8 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/AbstractWindChargeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractWindChargeEntity.java
new file mode 100644
index 000000000..5678c3af4
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/AbstractWindChargeEntity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.type;
+
+import org.cloudburstmc.math.vector.Vector3f;
+import org.geysermc.geyser.entity.EntityDefinition;
+import org.geysermc.geyser.session.GeyserSession;
+
+import java.util.UUID;
+
+/**
+ * Note that, as of 1.21, a wind charge entity does not actually implement the thrown item. We're just reusing
+ * the "hide until far away" aspect.
+ */
+public class AbstractWindChargeEntity extends ThrowableItemEntity {
+ public AbstractWindChargeEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
+ super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ }
+
+ @Override
+ protected float getDrag() {
+ // Always, even in water. As of 1.21.
+ return 1f;
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/AreaEffectCloudEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/AreaEffectCloudEntity.java
index 84dfae468..165495506 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/AreaEffectCloudEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/AreaEffectCloudEntity.java
@@ -25,9 +25,6 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.level.particle.Particle;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.ParticleType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
@@ -35,6 +32,11 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.util.MathUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.level.particle.EntityEffectParticleData;
+import org.geysermc.mcprotocollib.protocol.data.game.level.particle.Particle;
import java.util.UUID;
@@ -51,7 +53,7 @@ public class AreaEffectCloudEntity extends Entity {
dirtyMetadata.put(EntityDataTypes.AREA_EFFECT_CLOUD_DURATION, Integer.MAX_VALUE);
// This disabled client side shrink of the cloud
- dirtyMetadata.put(EntityDataTypes.AREA_EFFECT_CLOUD_RADIUS, 0.0f);
+ dirtyMetadata.put(EntityDataTypes.AREA_EFFECT_CLOUD_RADIUS, 3.0f);
dirtyMetadata.put(EntityDataTypes.AREA_EFFECT_CLOUD_CHANGE_RATE, Float.MIN_VALUE);
dirtyMetadata.put(EntityDataTypes.AREA_EFFECT_CLOUD_CHANGE_ON_PICKUP, Float.MIN_VALUE);
@@ -60,7 +62,7 @@ public class AreaEffectCloudEntity extends Entity {
public void setRadius(FloatEntityMetadata entityMetadata) {
// Anything less than 0.5 will cause the cloud to despawn
- float value = Math.max(entityMetadata.getPrimitiveValue(), 0.5f);
+ float value = MathUtils.clamp(entityMetadata.getPrimitiveValue(), 0.5f, 32.0f);
dirtyMetadata.put(EntityDataTypes.AREA_EFFECT_CLOUD_RADIUS, value);
dirtyMetadata.put(EntityDataTypes.WIDTH, 2.0f * value);
}
@@ -69,5 +71,9 @@ public class AreaEffectCloudEntity extends Entity {
Particle particle = entityMetadata.getValue();
Registries.PARTICLES.map(particle.getType(), p -> p.levelEventType() instanceof ParticleType particleType ? particleType : null).ifPresent(type ->
dirtyMetadata.put(EntityDataTypes.AREA_EFFECT_CLOUD_PARTICLE, type));
+
+ if (particle.getData() instanceof EntityEffectParticleData effectParticleData) {
+ dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, effectParticleData.getColor());
+ }
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ArrowEntity.java
similarity index 68%
rename from core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java
rename to core/src/main/java/org/geysermc/geyser/entity/type/ArrowEntity.java
index 856c4cc66..ba1241434 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/TippedArrowEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/ArrowEntity.java
@@ -25,21 +25,18 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.inventory.item.TippedArrowPotion;
+import org.geysermc.geyser.inventory.item.Potion;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
-/**
- * Internally this is known as TippedArrowEntity but is used with tipped arrows and normal arrows
- */
-public class TippedArrowEntity extends AbstractArrowEntity {
+public class ArrowEntity extends AbstractArrowEntity {
- public TippedArrowEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
+ public ArrowEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
@@ -49,12 +46,7 @@ public class TippedArrowEntity extends AbstractArrowEntity {
if (potionColor == -1) {
dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) 0);
} else {
- TippedArrowPotion potion = TippedArrowPotion.getByJavaColor(potionColor);
- if (potion != null && potion.getJavaColor() != -1) {
- dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) potion.getBedrockId());
- } else {
- dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, (byte) 0);
- }
+ dirtyMetadata.put(EntityDataTypes.CUSTOM_DISPLAY, Potion.toTippedArrowId(potionColor));
}
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java
index 5527e773a..47ae6777a 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java
@@ -25,24 +25,23 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
+import lombok.Getter;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
import org.cloudburstmc.protocol.bedrock.packet.MoveEntityAbsolutePacket;
-import lombok.Getter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-public class BoatEntity extends Entity {
+public class BoatEntity extends Entity implements Leashable, Tickable {
/**
* Required when IS_BUOYANT is sent in order for boats to work in the water.
@@ -58,6 +57,7 @@ public class BoatEntity extends Entity {
private float paddleTimeLeft;
private boolean isPaddlingRight;
private float paddleTimeRight;
+ private boolean doTick;
/**
* Saved for using the "pick" functionality on a boat.
@@ -65,6 +65,8 @@ public class BoatEntity extends Entity {
@Getter
private int variant;
+ private long leashHolderBedrockId = -1;
+
// Looks too fast and too choppy with 0.1f, which is how I believe the Microsoftian client handles it
private final float ROWING_SPEED = 0.1f;
@@ -133,40 +135,32 @@ public class BoatEntity extends Entity {
public void setPaddlingLeft(BooleanEntityMetadata entityMetadata) {
isPaddlingLeft = entityMetadata.getPrimitiveValue();
- if (isPaddlingLeft) {
- // Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing
- // This is an asynchronous method that emulates Bedrock rowing until "false" is sent.
- paddleTimeLeft = 0f;
- if (!this.passengers.isEmpty()) {
- // Get the entity by the first stored passenger and convey motion in this manner
- Entity entity = this.passengers.get(0);
- if (entity != null) {
- updateLeftPaddle(session, entity);
- }
- }
- } else {
- // Indicate that the row position should be reset
+ if (!isPaddlingLeft) {
+ paddleTimeLeft = 0.0f;
dirtyMetadata.put(EntityDataTypes.ROW_TIME_LEFT, 0.0f);
}
}
public void setPaddlingRight(BooleanEntityMetadata entityMetadata) {
isPaddlingRight = entityMetadata.getPrimitiveValue();
- if (isPaddlingRight) {
- paddleTimeRight = 0f;
- if (!this.passengers.isEmpty()) {
- Entity entity = this.passengers.get(0);
- if (entity != null) {
- updateRightPaddle(session, entity);
- }
- }
- } else {
+ if (!isPaddlingRight) {
+ paddleTimeRight = 0.0f;
dirtyMetadata.put(EntityDataTypes.ROW_TIME_RIGHT, 0.0f);
}
}
+ @Override
+ public void setLeashHolderBedrockId(long bedrockId) {
+ this.leashHolderBedrockId = bedrockId;
+ dirtyMetadata.put(EntityDataTypes.LEASH_HOLDER, bedrockId);
+ }
+
@Override
protected InteractiveTag testInteraction(Hand hand) {
+ InteractiveTag tag = super.testInteraction(hand);
+ if (tag != InteractiveTag.NONE) {
+ return tag;
+ }
if (session.isSneaking()) {
return InteractiveTag.NONE;
} else if (passengers.size() < 2) {
@@ -178,6 +172,10 @@ public class BoatEntity extends Entity {
@Override
public InteractionResult interact(Hand hand) {
+ InteractionResult result = super.interact(hand);
+ if (result != InteractionResult.PASS) {
+ return result;
+ }
if (session.isSneaking()) {
return InteractionResult.PASS;
} else {
@@ -186,32 +184,34 @@ public class BoatEntity extends Entity {
}
}
- private void updateLeftPaddle(GeyserSession session, Entity rower) {
+ @Override
+ public void tick() {
+ // Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing
+ doTick = !doTick; // Run every 100 ms
+ if (!doTick || passengers.isEmpty()) {
+ return;
+ }
+
+ Entity rower = passengers.get(0);
+ if (rower == null) {
+ return;
+ }
+
if (isPaddlingLeft) {
paddleTimeLeft += ROWING_SPEED;
sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_LEFT, paddleTimeLeft);
-
- session.scheduleInEventLoop(() ->
- updateLeftPaddle(session, rower),
- 100,
- TimeUnit.MILLISECONDS
- );
}
- }
-
- private void updateRightPaddle(GeyserSession session, Entity rower) {
if (isPaddlingRight) {
paddleTimeRight += ROWING_SPEED;
sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_RIGHT, paddleTimeRight);
-
- session.scheduleInEventLoop(() ->
- updateRightPaddle(session, rower),
- 100,
- TimeUnit.MILLISECONDS
- );
}
}
+ @Override
+ public long leashHolderBedrockId() {
+ return leashHolderBedrockId;
+ }
+
private void sendAnimationPacket(GeyserSession session, Entity rower, AnimatePacket.Action action, float rowTime) {
AnimatePacket packet = new AnimatePacket();
packet.setRuntimeEntityId(rower.getGeyserId());
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ChestBoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ChestBoatEntity.java
index 675e9517d..479b4d80d 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/ChestBoatEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/ChestBoatEntity.java
@@ -25,12 +25,12 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java
index 9c7a28f6e..2d0835286 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/CommandBlockMinecartEntity.java
@@ -25,7 +25,7 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/DefaultBlockMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/DefaultBlockMinecartEntity.java
index 63b5ff2ab..fd6f17eb8 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/DefaultBlockMinecartEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/DefaultBlockMinecartEntity.java
@@ -25,8 +25,8 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/EnderCrystalEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/EnderCrystalEntity.java
index 86436f82b..932864bf7 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/EnderCrystalEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/EnderCrystalEntity.java
@@ -25,7 +25,7 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java
index a2b7cc6ec..08e87dc03 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java
@@ -25,13 +25,6 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
-import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@@ -42,31 +35,34 @@ import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
-import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
-import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
-import org.cloudburstmc.protocol.bedrock.packet.MoveEntityAbsolutePacket;
-import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket;
-import org.cloudburstmc.protocol.bedrock.packet.RemoveEntityPacket;
-import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
+import org.cloudburstmc.protocol.bedrock.packet.*;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.GeyserDirtyMetadata;
+import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager;
+import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.geyser.util.MathUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
+import java.util.*;
@Getter
@Setter
public class Entity implements GeyserEntity {
+
+ private static final boolean PRINT_ENTITY_SPAWN_DEBUG = Boolean.parseBoolean(System.getProperty("Geyser.PrintEntitySpawnDebug", "false"));
+
protected final GeyserSession session;
protected int entityId;
@@ -126,6 +122,8 @@ public class Entity implements GeyserEntity {
@Setter(AccessLevel.PROTECTED) // For players
private boolean flagsDirty = false;
+ protected final GeyserEntityPropertyManager propertyManager;
+
public Entity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
this.session = session;
@@ -140,6 +138,8 @@ public class Entity implements GeyserEntity {
this.valid = false;
+ this.propertyManager = definition.registeredProperties() == null ? null : new GeyserEntityPropertyManager(definition.registeredProperties());
+
setPosition(position);
setAirSupply(getMaxAir());
@@ -185,7 +185,7 @@ public class Entity implements GeyserEntity {
flagsDirty = false;
- if (session.getGeyser().getConfig().isDebugMode()) {
+ if (session.getGeyser().getConfig().isDebugMode() && PRINT_ENTITY_SPAWN_DEBUG) {
EntityType type = definition.entityType();
String name = type != null ? type.name() : getClass().getSimpleName();
session.getGeyser().getLogger().debug("Spawned entity " + name + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
@@ -200,11 +200,9 @@ public class Entity implements GeyserEntity {
/**
* Despawns the entity
- *
- * @return can be deleted
*/
- public boolean despawnEntity() {
- if (!valid) return true;
+ public void despawnEntity() {
+ if (!valid) return;
for (Entity passenger : passengers) { // Make sure all passengers on the despawned entity are updated
if (passenger == null) continue;
@@ -218,7 +216,6 @@ public class Entity implements GeyserEntity {
session.sendUpstreamPacket(removeEntityPacket);
valid = false;
- return true;
}
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) {
@@ -360,6 +357,23 @@ public class Entity implements GeyserEntity {
}
}
+ /**
+ * Sends the Bedrock entity properties to the client
+ */
+ public void updateBedrockEntityProperties() {
+ if (!valid) {
+ return;
+ }
+
+ if (propertyManager != null && propertyManager.hasProperties()) {
+ SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
+ entityDataPacket.setRuntimeEntityId(geyserId);
+ propertyManager.applyIntProperties(entityDataPacket.getProperties().getIntProperties());
+ propertyManager.applyFloatProperties(entityDataPacket.getProperties().getFloatProperties());
+ session.sendUpstreamPacket(entityDataPacket);
+ }
+ }
+
public void setFlags(ByteEntityMetadata entityMetadata) {
byte xd = entityMetadata.getPrimitiveValue();
setFlag(EntityFlag.ON_FIRE, ((xd & 0x01) == 0x01) && !getFlag(EntityFlag.FIRE_IMMUNE)); // Otherwise immune entities sometimes flicker onfire
@@ -544,6 +558,17 @@ public class Entity implements GeyserEntity {
* Should usually mirror {@link #interact(Hand)} without any side effects.
*/
protected InteractiveTag testInteraction(Hand hand) {
+ if (isAlive() && this instanceof Leashable leashable) {
+ if (leashable.leashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) {
+ // Note this might be client side. Has yet to be an issue though, as of Java 1.21.
+ return InteractiveTag.REMOVE_LEASH;
+ }
+ if (session.getPlayerInventory().getItemInHand(hand).asItem() == Items.LEAD && leashable.canBeLeashed()) {
+ // We shall leash
+ return InteractiveTag.LEASH;
+ }
+ }
+
return InteractiveTag.NONE;
}
@@ -552,6 +577,18 @@ public class Entity implements GeyserEntity {
* to ensure packet parity as well as functionality parity (such as sound effect responses).
*/
public InteractionResult interact(Hand hand) {
+ if (isAlive() && this instanceof Leashable leashable) {
+ if (leashable.leashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) {
+ // Note this might also update client side (a theoretical Geyser/client desync and Java parity issue).
+ // Has yet to be an issue though, as of Java 1.21.
+ return InteractionResult.SUCCESS;
+ }
+ if (session.getPlayerInventory().getItemInHand(hand).asItem() == Items.LEAD && leashable.canBeLeashed()) {
+ // We shall leash
+ return InteractionResult.SUCCESS;
+ }
+ }
+
return InteractionResult.PASS;
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ExpOrbEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ExpOrbEntity.java
index 5a79a98b3..9f61bc961 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/ExpOrbEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/ExpOrbEntity.java
@@ -27,11 +27,18 @@ package org.geysermc.geyser.entity.type;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
+import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
+import java.util.UUID;
+
public class ExpOrbEntity extends Entity {
+ public ExpOrbEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> entityDefinition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
+ this(session, 1, entityId, geyserId, position);
+ }
+
public ExpOrbEntity(GeyserSession session, int amount, int entityId, long geyserId, Vector3f position) {
super(session, entityId, geyserId, null, EntityDefinitions.EXPERIENCE_ORB, position, Vector3f.ZERO, 0, 0, 0);
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FallingBlockEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FallingBlockEntity.java
index e6d3a5783..4fcec7a63 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/FallingBlockEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/FallingBlockEntity.java
@@ -25,12 +25,12 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java
index 3db032f0f..904596b3a 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java
@@ -72,6 +72,9 @@ public class FireballEntity extends ThrowableEntity {
@Override
public void tick() {
+ if (removedInVoid()) {
+ return;
+ }
moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false);
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java
index 7a544f23c..f0739abb3 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java
@@ -25,26 +25,18 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
-import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
-import com.github.steveice10.opennbt.tag.builtin.ListTag;
-import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.cloudburstmc.math.vector.Vector3f;
-import org.cloudburstmc.nbt.NbtMap;
-import org.cloudburstmc.nbt.NbtMapBuilder;
-import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket;
-import org.geysermc.floodgate.util.DeviceOs;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
-import org.geysermc.geyser.level.FireworkColor;
+import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.util.MathUtils;
+import org.geysermc.geyser.translator.item.BedrockItemBuilder;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
+import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
-import java.util.ArrayList;
-import java.util.List;
import java.util.OptionalInt;
import java.util.UUID;
@@ -59,80 +51,16 @@ public class FireworkEntity extends Entity {
if (item == null) {
return;
}
- CompoundTag tag = item.getNbt();
-
- if (tag == null) {
+ DataComponents components = item.getDataComponents();
+ if (components == null) {
return;
}
- // TODO: Remove once Mojang fixes bugs with fireworks crashing clients on these specific devices.
- // https://bugs.mojang.com/browse/MCPE-89115
- if (session.getClientData().getDeviceOs() == DeviceOs.XBOX
- || session.getClientData().getDeviceOs() == DeviceOs.PS4) {
- return;
- }
-
- CompoundTag fireworks = tag.get("Fireworks");
- if (fireworks == null) {
- // Thank you Mineplex very cool
- return;
- }
-
- NbtMapBuilder fireworksBuilder = NbtMap.builder();
- if (fireworks.get("Flight") != null) {
- fireworksBuilder.putByte("Flight", MathUtils.getNbtByte(fireworks.get("Flight").getValue()));
- }
-
- List explosions = new ArrayList<>();
- if (fireworks.get("Explosions") != null) {
- for (Tag effect : ((ListTag) fireworks.get("Explosions")).getValue()) {
- CompoundTag effectData = (CompoundTag) effect;
- NbtMapBuilder effectBuilder = NbtMap.builder();
-
- if (effectData.get("Type") != null) {
- effectBuilder.putByte("FireworkType", MathUtils.getNbtByte(effectData.get("Type").getValue()));
- }
-
- if (effectData.get("Colors") != null) {
- int[] oldColors = (int[]) effectData.get("Colors").getValue();
- byte[] colors = new byte[oldColors.length];
-
- int i = 0;
- for (int color : oldColors) {
- colors[i++] = FireworkColor.fromJavaRGB(color);
- }
-
- effectBuilder.putByteArray("FireworkColor", colors);
- }
-
- if (effectData.get("FadeColors") != null) {
- int[] oldColors = (int[]) effectData.get("FadeColors").getValue();
- byte[] colors = new byte[oldColors.length];
-
- int i = 0;
- for (int color : oldColors) {
- colors[i++] = FireworkColor.fromJavaRGB(color);
- }
-
- effectBuilder.putByteArray("FireworkFade", colors);
- }
-
- if (effectData.get("Trail") != null) {
- effectBuilder.putByte("FireworkTrail", MathUtils.getNbtByte(effectData.get("Trail").getValue()));
- }
-
- if (effectData.get("Flicker") != null) {
- effectBuilder.putByte("FireworkFlicker", MathUtils.getNbtByte(effectData.get("Flicker").getValue()));
- }
-
- explosions.add(effectBuilder.build());
- }
- }
-
- fireworksBuilder.putList("Explosions", NbtType.COMPOUND, explosions);
-
- NbtMapBuilder builder = NbtMap.builder();
- builder.put("Fireworks", fireworksBuilder.build());
+ // TODO this looked the same, so I'm going to assume it is and (keep below comment if true)
+ // Translate using item methods to get firework NBT for Bedrock
+ BedrockItemBuilder builder = new BedrockItemBuilder();
+ Items.FIREWORK_ROCKET.translateComponentsToBedrock(session, components, builder);
+
dirtyMetadata.put(EntityDataTypes.DISPLAY_FIREWORK, builder.build());
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java
index bcbff16ce..26a64bcae 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java
@@ -25,19 +25,20 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import lombok.Getter;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket;
-import lombok.Getter;
import org.geysermc.erosion.util.BlockPositionIterator;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.level.block.BlockStateValues;
+import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.physics.BoundingBox;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.collision.BlockCollision;
import org.geysermc.geyser.util.BlockUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
@@ -133,6 +134,9 @@ public class FishingHookEntity extends ThrowableEntity {
@Override
public void tick() {
+ if (removedInVoid()) {
+ return;
+ }
if (hooked || !isInAir() && !isInWater() || isOnGround()) {
motion = Vector3f.ZERO;
return;
@@ -159,7 +163,7 @@ public class FishingHookEntity extends ThrowableEntity {
*/
protected boolean isInAir() {
int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt());
- return block == BlockStateValues.JAVA_AIR_ID;
+ return block == Block.JAVA_AIR_ID;
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java
index a7a117fff..e33e6d7b6 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/FurnaceMinecartEntity.java
@@ -25,14 +25,16 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.level.block.BlockStateValues;
+import org.geysermc.geyser.level.block.Blocks;
+import org.geysermc.geyser.level.block.property.Properties;
+import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -51,7 +53,8 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity {
@Override
public void updateDefaultBlockMetadata() {
- dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(hasFuel ? BlockStateValues.JAVA_FURNACE_LIT_ID : BlockStateValues.JAVA_FURNACE_ID));
+ BlockState furnace = Blocks.FURNACE.defaultBlockState().withValue(Properties.LIT, hasFuel);
+ dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(furnace));
dirtyMetadata.put(EntityDataTypes.DISPLAY_OFFSET, 6);
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java
index 0917465d4..06035a47c 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java
@@ -25,16 +25,16 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
-import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
+import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
import java.util.UUID;
@@ -80,7 +80,10 @@ public class InteractionEntity extends Entity {
}
public void setHeight(FloatEntityMetadata height) {
- setBoundingBoxHeight(height.getPrimitiveValue());
+ // Bedrock does *not* like high values being placed here
+ // https://gist.github.com/Owen1212055/f5d59169d3a6a5c32f0c173d57eb199d recommend(s/ed) using the tactic
+ // https://github.com/GeyserMC/Geyser/issues/4688
+ setBoundingBoxHeight(Math.min(height.getPrimitiveValue(), 64f));
}
public void setResponse(BooleanEntityMetadata response) {
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java
index bb67a60f6..49eb9ddc4 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java
@@ -25,8 +25,6 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
@@ -36,8 +34,11 @@ import org.cloudburstmc.protocol.bedrock.packet.AddItemEntityPacket;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.level.block.BlockStateValues;
+import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
+import org.geysermc.geyser.translator.item.ItemTranslator;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@@ -74,7 +75,7 @@ public class ItemEntity extends ThrowableEntity {
@Override
public void tick() {
- if (isInWater()) {
+ if (removedInVoid() || isInWater()) {
return;
}
if (!isOnGround() || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) {
@@ -137,7 +138,7 @@ public class ItemEntity extends ThrowableEntity {
protected float getDrag() {
if (isOnGround()) {
Vector3i groundBlockPos = position.toInt().down(1);
- int blockState = session.getGeyser().getWorldManager().getBlockAt(session, groundBlockPos);
+ BlockState blockState = session.getGeyser().getWorldManager().blockAt(session, groundBlockPos);
return BlockStateValues.getSlipperiness(blockState) * 0.98f;
}
return 0.98f;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java
index 295972200..f38e727c0 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java
@@ -25,12 +25,7 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.object.Direction;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
-import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
+import lombok.Getter;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
@@ -39,12 +34,17 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
-import lombok.Getter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
+import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InventoryUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
+import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import java.util.UUID;
@@ -148,7 +148,7 @@ public class ItemFrameEntity extends Entity {
}
@Override
- public boolean despawnEntity() {
+ public void despawnEntity() {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(bedrockPosition);
@@ -161,7 +161,6 @@ public class ItemFrameEntity extends Entity {
session.getItemFrameCache().remove(bedrockPosition, this);
valid = false;
- return true;
}
private NbtMap getDefaultTag() {
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java
index 3f0d2ee68..af739297c 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java
@@ -25,11 +25,11 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Leashable.java b/core/src/main/java/org/geysermc/geyser/entity/type/Leashable.java
new file mode 100644
index 000000000..64d95ba3c
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/Leashable.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.type;
+
+/**
+ * I can haz lead
+ * (The item, not the mineral)
+ */
+public interface Leashable {
+ void setLeashHolderBedrockId(long bedrockId);
+
+ long leashHolderBedrockId();
+
+ default boolean canBeLeashed() {
+ return isNotLeashed();
+ }
+
+ default boolean isNotLeashed() {
+ return leashHolderBedrockId() == -1L;
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java
index 245b99cef..499084555 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java
@@ -25,16 +25,6 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute;
-import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
-import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
-import com.github.steveice10.opennbt.tag.builtin.StringTag;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@@ -55,8 +45,23 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.geyser.util.AttributeUtils;
import org.geysermc.geyser.util.InteractionResult;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
+import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
+import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
+import org.geysermc.mcprotocollib.protocol.data.game.level.particle.EntityEffectParticleData;
+import org.geysermc.mcprotocollib.protocol.data.game.level.particle.Particle;
+import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType;
import java.util.*;
@@ -69,7 +74,7 @@ public class LivingEntity extends Entity {
protected ItemData leggings = ItemData.AIR;
protected ItemData boots = ItemData.AIR;
protected ItemData hand = ItemData.AIR;
- protected ItemData offHand = ItemData.AIR;
+ protected ItemData offhand = ItemData.AIR;
@Getter(value = AccessLevel.NONE)
protected float health = 1f; // The default value in Java Edition before any entity metadata is sent
@@ -81,12 +86,58 @@ public class LivingEntity extends Entity {
*/
private boolean isMaxFrozenState = false;
+ /**
+ * The base scale entity data, without attributes applied. Used for such cases as baby variants.
+ */
+ @Getter(AccessLevel.NONE)
+ @Setter(AccessLevel.NONE)
+ private float scale;
+ /**
+ * The scale sent through the Java attributes packet
+ */
+ @Getter(AccessLevel.NONE)
+ @Setter(AccessLevel.NONE)
+ private float attributeScale;
+
public LivingEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
+ public void setHelmet(ItemStack stack) {
+ this.helmet = ItemTranslator.translateToBedrock(session, stack);
+ }
+
+ public void setChestplate(ItemStack stack) {
+ this.chestplate = ItemTranslator.translateToBedrock(session, stack);
+ }
+
+ public void setLeggings(ItemStack stack) {
+ this.leggings = ItemTranslator.translateToBedrock(session, stack);
+ }
+
+ public void setBoots(ItemStack stack) {
+ this.boots = ItemTranslator.translateToBedrock(session, stack);
+ }
+
+ public void setHand(ItemStack stack) {
+ this.hand = ItemTranslator.translateToBedrock(session, stack);
+ }
+
+ public void setOffhand(ItemStack stack) {
+ this.offhand = ItemTranslator.translateToBedrock(session, stack);
+ }
+
+ public void switchHands() {
+ ItemData offhand = this.offhand;
+ this.offhand = this.hand;
+ this.hand = offhand;
+ }
+
@Override
protected void initializeMetadata() {
+ // Initialize here so overriding classes don't have 0 values
+ this.scale = 1f;
+ this.attributeScale = 1f;
super.initializeMetadata();
// Matches Bedrock behavior; is always set to this
dirtyMetadata.put(EntityDataTypes.STRUCTURAL_INTEGRITY, 1);
@@ -121,6 +172,37 @@ public class LivingEntity extends Entity {
session.sendUpstreamPacket(attributesPacket);
}
+ // TODO: support all particle types
+ public void setParticles(ObjectEntityMetadata> entityMetadata) {
+ List particles = entityMetadata.getValue();
+ float r = 0f;
+ float g = 0f;
+ float b = 0f;
+
+ int count = 0;
+ for (Particle particle : particles) {
+ if (particle.getType() != ParticleType.ENTITY_EFFECT) {
+ continue;
+ }
+
+ int color = ((EntityEffectParticleData) particle.getData()).getColor();
+ r += ((color >> 16) & 0xFF) / 255f;
+ g += ((color >> 8) & 0xFF) / 255f;
+ b += ((color) & 0xFF) / 255f;
+ count++;
+ }
+
+ int result = 0;
+ if (count > 0) {
+ r = r / count * 255f;
+ g = g / count * 255f;
+ b = b / count * 255f;
+ result = (int) r << 16 | (int) g << 8 | (int) b;
+ }
+
+ dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, result);
+ }
+
public @Nullable Vector3i setBedPosition(EntityMetadata, ?> entityMetadata) {
Optional optionalPos = entityMetadata.getValue();
if (optionalPos.isPresent()) {
@@ -135,7 +217,7 @@ public class LivingEntity extends Entity {
protected boolean hasShield(boolean offhand) {
ItemMapping shieldMapping = session.getItemMappings().getStoredItems().shield();
if (offhand) {
- return offHand.getDefinition().equals(shieldMapping.getBedrockDefinition());
+ return this.offhand.getDefinition().equals(shieldMapping.getBedrockDefinition());
} else {
return hand.getDefinition().equals(shieldMapping.getBedrockDefinition());
}
@@ -164,6 +246,21 @@ public class LivingEntity extends Entity {
return freezingPercentage;
}
+ protected void setScale(float scale) {
+ this.scale = scale;
+ applyScale();
+ }
+
+ private void setAttributeScale(float scale) {
+ this.attributeScale = scale;
+ applyScale();
+ }
+
+ private void applyScale() {
+ // Take any adjustments Bedrock requires, and compute it alongside the attribute's additional changes
+ this.dirtyMetadata.put(EntityDataTypes.SCALE, scale * attributeScale);
+ }
+
/**
* @return a Bedrock health attribute constructed from the data sent from the server
*/
@@ -194,9 +291,9 @@ public class LivingEntity extends Entity {
/**
* Checks to see if a nametag interaction would go through.
*/
+ // Implementation note for 1.20.5: this code was moved to the NameTag item.
protected final InteractionResult checkInteractWithNameTag(GeyserItemStack itemStack) {
- CompoundTag nbt = itemStack.getNbt();
- if (nbt != null && nbt.get("display") instanceof CompoundTag displayTag && displayTag.get("Name") instanceof StringTag) {
+ if (itemStack.getComponent(DataComponentType.CUSTOM_NAME) != null) {
// The mob shall be named
return InteractionResult.SUCCESS;
}
@@ -247,7 +344,7 @@ public class LivingEntity extends Entity {
MobEquipmentPacket offHandPacket = new MobEquipmentPacket();
offHandPacket.setRuntimeEntityId(geyserId);
- offHandPacket.setItem(offHand);
+ offHandPacket.setItem(offhand);
offHandPacket.setHotbarSlot(-1);
offHandPacket.setInventorySlot(0);
offHandPacket.setContainerId(ContainerId.OFFHAND);
@@ -255,6 +352,15 @@ public class LivingEntity extends Entity {
session.sendUpstreamPacket(offHandPacket);
}
+ /**
+ * Called when a SWING_ARM animation packet is received
+ *
+ * @return true if an ATTACK_START event should be used instead
+ */
+ public boolean useArmSwingAttack() {
+ return false;
+ }
+
/**
* Attributes are properties of an entity that are generally more runtime-based instead of permanent properties.
* Movement speed, current attack damage with a weapon, current knockback resistance.
@@ -299,7 +405,12 @@ public class LivingEntity extends Entity {
case GENERIC_MOVEMENT_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.MOVEMENT_SPEED));
case GENERIC_FOLLOW_RANGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE));
case GENERIC_KNOCKBACK_RESISTANCE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE));
- case HORSE_JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH));
+ case GENERIC_JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH));
+ case GENERIC_SCALE -> {
+ // Attribute on Java, entity data on Bedrock
+ setAttributeScale((float) AttributeUtils.calculateValue(javaAttribute));
+ updateBedrockMetadata();
+ }
}
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java
index ecf67052b..9096d8bd6 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java
@@ -25,9 +25,6 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
@@ -35,6 +32,9 @@ import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java
index 4e5fe9d59..6d0294783 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java
@@ -25,13 +25,15 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.object.Direction;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.packet.AddPaintingPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.level.PaintingType;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.Holder;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.PaintingVariant;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
import java.util.UUID;
@@ -49,8 +51,14 @@ public class PaintingEntity extends Entity {
// Wait until we get the metadata needed
}
- public void setPaintingType(ObjectEntityMetadata entityMetadata) {
- PaintingType type = PaintingType.getByPaintingType(entityMetadata.getValue());
+ public void setPaintingType(ObjectEntityMetadata> entityMetadata) {
+ if (!entityMetadata.getValue().isId()) {
+ return;
+ }
+ PaintingType type = session.getRegistryCache().paintings().byId(entityMetadata.getValue().id());
+ if (type == null) {
+ return;
+ }
AddPaintingPacket addPaintingPacket = new AddPaintingPacket();
addPaintingPacket.setUniqueEntityId(geyserId);
addPaintingPacket.setRuntimeEntityId(geyserId);
@@ -79,7 +87,7 @@ public class PaintingEntity extends Entity {
private Vector3f fixOffset(PaintingType paintingName) {
Vector3f position = super.position;
position = position.add(0.5, 0.5, 0.5);
- double widthOffset = paintingName.getWidth() > 1 ? 0.5 : 0;
+ double widthOffset = paintingName.getWidth() > 1 && paintingName.getWidth() != 3 ? 0.5 : 0;
double heightOffset = paintingName.getHeight() > 1 && paintingName.getHeight() != 3 ? 0.5 : 0;
return switch (direction) {
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/SpawnerMinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/SpawnerMinecartEntity.java
index 49cfc0081..4d69c8a1e 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/SpawnerMinecartEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/SpawnerMinecartEntity.java
@@ -28,7 +28,7 @@ package org.geysermc.geyser.entity.type;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.level.block.BlockStateValues;
+import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
import java.util.UUID;
@@ -41,7 +41,7 @@ public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity {
@Override
public void updateDefaultBlockMetadata() {
- dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(BlockStateValues.JAVA_SPAWNER_ID));
+ dirtyMetadata.put(EntityDataTypes.DISPLAY_BLOCK_STATE, session.getBlockMappings().getBedrockBlock(Blocks.SPAWNER.defaultBlockState()));
dirtyMetadata.put(EntityDataTypes.DISPLAY_OFFSET, 6);
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java
index 98c2edd00..47d97b8f7 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java
@@ -25,13 +25,13 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
@@ -39,7 +39,17 @@ public class TNTEntity extends Entity implements Tickable {
private int currentTick;
public TNTEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
- super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
+ super(session, entityId, geyserId, uuid, definition, position.add(0, definition.offset(), 0), motion, yaw, pitch, headYaw);
+ }
+
+ @Override
+ public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) {
+ super.moveRelative(relX, relY + definition.offset(), relZ, yaw, pitch, isOnGround);
+ }
+
+ @Override
+ public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
+ super.moveAbsolute(position.add(Vector3f.from(0, definition.offset(), 0)), yaw, pitch, headYaw, isOnGround, teleported);
}
public void setFuseLength(IntEntityMetadata entityMetadata) {
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java
index 0b6160401..28f38f919 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java
@@ -25,14 +25,14 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import net.kyori.adventure.text.Component;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.text.MessageTranslator;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.Optional;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java
index 60840e65b..25bbdbd3c 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java
@@ -25,7 +25,6 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
@@ -34,6 +33,7 @@ import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import java.util.UUID;
@@ -55,6 +55,9 @@ public class ThrowableEntity extends Entity implements Tickable {
*/
@Override
public void tick() {
+ if (removedInVoid()) {
+ return;
+ }
moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false);
float drag = getDrag();
float gravity = getGravity();
@@ -170,14 +173,14 @@ public class ThrowableEntity extends Entity implements Tickable {
}
@Override
- public boolean despawnEntity() {
+ public void despawnEntity() {
if (definition.entityType() == EntityType.ENDER_PEARL) {
LevelEventPacket particlePacket = new LevelEventPacket();
particlePacket.setType(LevelEvent.PARTICLE_TELEPORT);
particlePacket.setPosition(position);
session.sendUpstreamPacket(particlePacket);
}
- return super.despawnEntity();
+ super.despawnEntity();
}
@Override
@@ -191,4 +194,17 @@ public class ThrowableEntity extends Entity implements Tickable {
moveAbsoluteImmediate(position, yaw, pitch, headYaw, isOnGround, teleported);
lastJavaPosition = position;
}
+
+ /**
+ * Removes the entity if it is 64 blocks below the world.
+ *
+ * @return true if the entity was removed
+ */
+ public boolean removedInVoid() {
+ if (position.getY() < session.getDimensionType().minY() - 64) {
+ session.getEntityCache().removeEntity(this);
+ return true;
+ }
+ return false;
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java
index 39c8386bd..55334010f 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java
@@ -25,8 +25,8 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java
index 1b5c1d2d0..88cf4f8b9 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrownPotionEntity.java
@@ -25,10 +25,6 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
-import com.github.steveice10.opennbt.tag.builtin.StringTag;
-import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
@@ -38,6 +34,11 @@ import org.geysermc.geyser.inventory.item.Potion;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
+import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
+import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
+import org.geysermc.mcprotocollib.protocol.data.game.item.component.PotionContents;
import java.util.EnumSet;
import java.util.UUID;
@@ -53,21 +54,22 @@ public class ThrownPotionEntity extends ThrowableItemEntity {
public void setItem(EntityMetadata entityMetadata) {
ItemStack itemStack = entityMetadata.getValue();
if (itemStack == null) {
- dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, 0);
+ dirtyMetadata.put(EntityDataTypes.AUX_VALUE_DATA, (short) 0);
setFlag(EntityFlag.ENCHANTED, false);
setFlag(EntityFlag.LINGERING, false);
} else {
// As of Java 1.19.3, the server/client doesn't seem to care of the item is actually a potion?
- if (itemStack.getNbt() != null) {
- Tag potionTag = itemStack.getNbt().get("Potion");
- if (potionTag instanceof StringTag) {
- Potion potion = Potion.getByJavaIdentifier(((StringTag) potionTag).getValue());
+ DataComponents components = itemStack.getDataComponents();
+ if (components != null) {
+ PotionContents potionContents = components.get(DataComponentType.POTION_CONTENTS);
+ if (potionContents != null) {
+ Potion potion = Potion.getByJavaId(potionContents.getPotionId());
if (potion != null) {
- dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, (int) potion.getBedrockId());
+ dirtyMetadata.put(EntityDataTypes.AUX_VALUE_DATA, potion.getBedrockId());
setFlag(EntityFlag.ENCHANTED, !NON_ENCHANTED_POTIONS.contains(potion));
} else {
- dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, 0);
- GeyserImpl.getInstance().getLogger().debug("Unknown java potion: " + potionTag.getValue());
+ dirtyMetadata.put(EntityDataTypes.AUX_VALUE_DATA, (short) 0);
+ GeyserImpl.getInstance().getLogger().debug("Unknown java potion: " + potionContents.getPotionId());
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java
index 637ca4139..8427a8e10 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java
@@ -25,7 +25,7 @@
package org.geysermc.geyser.entity.type;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java
index 9cc3a006e..6ecfa4978 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java
@@ -25,7 +25,6 @@
package org.geysermc.geyser.entity.type.living;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
@@ -34,6 +33,7 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java
index 6e2e7a407..8f84e051b 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java
@@ -25,12 +25,11 @@
package org.geysermc.geyser.entity.type.living;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
-import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.UUID;
@@ -44,12 +43,12 @@ public class AgeableEntity extends CreatureEntity {
protected void initializeMetadata() {
super.initializeMetadata();
// Required as of 1.19.3 Java
- dirtyMetadata.put(EntityDataTypes.SCALE, getAdultSize());
+ setScale(getAdultSize());
}
public void setBaby(BooleanEntityMetadata entityMetadata) {
boolean isBaby = entityMetadata.getPrimitiveValue();
- dirtyMetadata.put(EntityDataTypes.SCALE, isBaby ? getBabySize() : getAdultSize());
+ setScale(isBaby ? getBabySize() : getAdultSize());
setFlag(EntityFlag.BABY, isBaby);
setBoundingBoxHeight(definition.height() * (isBaby ? getBabySize() : getAdultSize()));
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AllayEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AllayEntity.java
index 48d633e5d..01a42e527 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AllayEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AllayEntity.java
@@ -25,8 +25,6 @@
package org.geysermc.geyser.entity.type.living;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
@@ -36,6 +34,8 @@ import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java
index 8f81125d0..f4b80edf1 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/AmbientEntity.java
@@ -38,7 +38,7 @@ public class AmbientEntity extends MobEntity {
}
@Override
- protected boolean canBeLeashed() {
+ public boolean canBeLeashed() {
return false;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java
index c64776f18..d057f09c7 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java
@@ -25,10 +25,6 @@
package org.geysermc.geyser.entity.type.living;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.cloudburstmc.math.vector.Vector3f;
@@ -43,6 +39,11 @@ import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.MathUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
+import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import java.util.Optional;
import java.util.UUID;
@@ -99,11 +100,11 @@ public class ArmorStandEntity extends LivingEntity {
}
@Override
- public boolean despawnEntity() {
+ public void despawnEntity() {
if (secondEntity != null) {
secondEntity.despawnEntity();
}
- return super.despawnEntity();
+ super.despawnEntity();
}
@Override
@@ -257,38 +258,38 @@ public class ArmorStandEntity extends LivingEntity {
}
@Override
- public void setHelmet(ItemData helmet) {
+ public void setHelmet(ItemStack helmet) {
super.setHelmet(helmet);
updateSecondEntityStatus(true);
}
@Override
- public void setChestplate(ItemData chestplate) {
+ public void setChestplate(ItemStack chestplate) {
super.setChestplate(chestplate);
updateSecondEntityStatus(true);
}
@Override
- public void setLeggings(ItemData leggings) {
+ public void setLeggings(ItemStack leggings) {
super.setLeggings(leggings);
updateSecondEntityStatus(true);
}
@Override
- public void setBoots(ItemData boots) {
+ public void setBoots(ItemStack boots) {
super.setBoots(boots);
updateSecondEntityStatus(true);
}
@Override
- public void setHand(ItemData hand) {
+ public void setHand(ItemStack hand) {
super.setHand(hand);
updateSecondEntityStatus(true);
}
@Override
- public void setOffHand(ItemData offHand) {
- super.setOffHand(offHand);
+ public void setOffhand(ItemStack offHand) {
+ super.setOffhand(offHand);
updateSecondEntityStatus(true);
}
@@ -310,7 +311,7 @@ public class ArmorStandEntity extends LivingEntity {
if (!isInvisible) {
// The armor stand isn't invisible. We good.
setFlag(EntityFlag.INVISIBLE, false);
- dirtyMetadata.put(EntityDataTypes.SCALE, getScale());
+ setScale(getScale());
updateOffsetRequirement(false);
if (secondEntity != null) {
@@ -324,9 +325,9 @@ public class ArmorStandEntity extends LivingEntity {
}
boolean isNametagEmpty = nametag.isEmpty();
if (!isNametagEmpty && (!helmet.equals(ItemData.AIR) || !chestplate.equals(ItemData.AIR) || !leggings.equals(ItemData.AIR)
- || !boots.equals(ItemData.AIR) || !hand.equals(ItemData.AIR) || !offHand.equals(ItemData.AIR))) {
+ || !boots.equals(ItemData.AIR) || !hand.equals(ItemData.AIR) || !offhand.equals(ItemData.AIR))) {
// Reset scale of the proper armor stand
- this.dirtyMetadata.put(EntityDataTypes.SCALE, getScale());
+ setScale(getScale());
// Set the proper armor stand to invisible to show armor
setFlag(EntityFlag.INVISIBLE, true);
// Update the position of the armor stand
@@ -349,7 +350,7 @@ public class ArmorStandEntity extends LivingEntity {
// Guarantee this copy is NOT invisible
secondEntity.setFlag(EntityFlag.INVISIBLE, false);
// Scale to 0 to show nametag
- secondEntity.getDirtyMetadata().put(EntityDataTypes.SCALE, 0.0f);
+ secondEntity.setScale(0f);
// No bounding box as we don't want to interact with this entity
secondEntity.getDirtyMetadata().put(EntityDataTypes.WIDTH, 0.0f);
secondEntity.getDirtyMetadata().put(EntityDataTypes.HEIGHT, 0.0f);
@@ -359,7 +360,7 @@ public class ArmorStandEntity extends LivingEntity {
} else if (isNametagEmpty) {
// We can just make an invisible entity
// Reset scale of the proper armor stand
- dirtyMetadata.put(EntityDataTypes.SCALE, getScale());
+ setScale(getScale());
// Set the proper armor stand to invisible to show armor
setFlag(EntityFlag.INVISIBLE, true);
// Update offset
@@ -373,7 +374,7 @@ public class ArmorStandEntity extends LivingEntity {
// Nametag is not empty and there is no armor
// We don't need to make a new entity
setFlag(EntityFlag.INVISIBLE, false);
- dirtyMetadata.put(EntityDataTypes.SCALE, 0.0f);
+ setScale(0f);
// As the above is applied, we need an offset
updateOffsetRequirement(!isMarker);
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/BatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/BatEntity.java
index 644054e72..bdfc20c88 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/BatEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/BatEntity.java
@@ -25,11 +25,11 @@
package org.geysermc.geyser.entity.type.living;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java
index f0348bcaf..a0ea79d67 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/DolphinEntity.java
@@ -25,14 +25,15 @@
package org.geysermc.geyser.entity.type.living;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -42,14 +43,14 @@ public class DolphinEntity extends WaterEntity {
}
@Override
- protected boolean canBeLeashed() {
+ public boolean canBeLeashed() {
return true;
}
@NonNull
@Override
protected InteractiveTag testMobInteraction(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) {
- if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) {
+ if (!itemInHand.isEmpty() && session.getTagCache().is(ItemTag.FISHES, itemInHand)) {
return InteractiveTag.FEED;
}
return super.testMobInteraction(hand, itemInHand);
@@ -58,7 +59,7 @@ public class DolphinEntity extends WaterEntity {
@NonNull
@Override
protected InteractionResult mobInteract(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) {
- if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) {
+ if (!itemInHand.isEmpty() && session.getTagCache().is(ItemTag.FISHES, itemInHand)) {
// Feed
return InteractionResult.SUCCESS;
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java
index 7afa4b436..58a349cc9 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/IronGolemEntity.java
@@ -25,7 +25,6 @@
package org.geysermc.geyser.entity.type.living;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
@@ -35,6 +34,7 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java
index 0ac81c957..9accf178f 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/MobEntity.java
@@ -25,14 +25,12 @@
package org.geysermc.geyser.entity.type.living;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
-import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
+import org.geysermc.geyser.entity.type.Leashable;
import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
@@ -40,14 +38,15 @@ import org.geysermc.geyser.item.type.SpawnEggItem;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
-public class MobEntity extends LivingEntity {
+public class MobEntity extends LivingEntity implements Leashable {
/**
* If another mob is holding this mob by a leash, this variable tracks their Bedrock entity ID.
*/
- @Getter
private long leashHolderBedrockId;
public MobEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
@@ -65,6 +64,7 @@ public class MobEntity extends LivingEntity {
setFlag(EntityFlag.NO_AI, (xd & 0x01) == 0x01);
}
+ @Override
public void setLeashHolderBedrockId(long bedrockId) {
this.leashHolderBedrockId = bedrockId;
dirtyMetadata.put(EntityDataTypes.LEASH_HOLDER, bedrockId);
@@ -79,10 +79,7 @@ public class MobEntity extends LivingEntity {
return InteractiveTag.REMOVE_LEASH;
} else {
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand);
- if (itemStack.asItem() == Items.LEAD && canBeLeashed()) {
- // We shall leash
- return InteractiveTag.LEASH;
- } else if (itemStack.asItem() == Items.NAME_TAG) {
+ if (itemStack.asItem() == Items.NAME_TAG) {
InteractionResult result = checkInteractWithNameTag(itemStack);
if (result.consumesAction()) {
return InteractiveTag.NAME;
@@ -99,9 +96,6 @@ public class MobEntity extends LivingEntity {
if (!isAlive()) {
// dead lol
return InteractionResult.PASS;
- } else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) {
- // TODO looks like the client assumes it will go through and removes the attachment itself?
- return InteractionResult.SUCCESS;
} else {
GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand(hand);
InteractionResult result = checkPriorityInteractions(itemInHand);
@@ -115,10 +109,7 @@ public class MobEntity extends LivingEntity {
}
private InteractionResult checkPriorityInteractions(GeyserItemStack itemInHand) {
- if (itemInHand.asItem() == Items.LEAD && canBeLeashed()) {
- // We shall leash
- return InteractionResult.SUCCESS;
- } else if (itemInHand.asItem() == Items.NAME_TAG) {
+ if (itemInHand.asItem() == Items.NAME_TAG) {
InteractionResult result = checkInteractWithNameTag(itemInHand);
if (result.consumesAction()) {
return result;
@@ -143,12 +134,14 @@ public class MobEntity extends LivingEntity {
return InteractionResult.PASS;
}
- protected boolean canBeLeashed() {
+ @Override
+ public boolean canBeLeashed() {
return isNotLeashed() && !isEnemy();
}
- protected final boolean isNotLeashed() {
- return leashHolderBedrockId == -1L;
+ @Override
+ public long leashHolderBedrockId() {
+ return leashHolderBedrockId;
}
/**
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java
index 1d2eb95bc..3be2db1db 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SlimeEntity.java
@@ -25,11 +25,10 @@
package org.geysermc.geyser.entity.type.living;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
-import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
@@ -39,8 +38,8 @@ public class SlimeEntity extends MobEntity {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
- public void setScale(IntEntityMetadata entityMetadata) {
- dirtyMetadata.put(EntityDataTypes.SCALE, 0.10f + entityMetadata.getPrimitiveValue());
+ public void setSlimeScale(IntEntityMetadata entityMetadata) {
+ setScale(0.10f + entityMetadata.getPrimitiveValue());
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java
index 7f0699415..50aa7b90e 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SnowGolemEntity.java
@@ -25,8 +25,6 @@
package org.geysermc.geyser.entity.type.living;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
@@ -36,6 +34,8 @@ import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java
index 80a5af442..6285bd9a4 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java
@@ -122,7 +122,7 @@ public class SquidEntity extends WaterEntity implements Tickable {
}
@Override
- protected boolean canBeLeashed() {
+ public boolean canBeLeashed() {
return isNotLeashed();
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/TadpoleEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/TadpoleEntity.java
index 7094c431e..68cf763c3 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/TadpoleEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/TadpoleEntity.java
@@ -25,15 +25,15 @@
package org.geysermc.geyser.entity.type.living;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
-import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -62,6 +62,6 @@ public class TadpoleEntity extends AbstractFishEntity {
}
private boolean isFood(GeyserItemStack itemStack) {
- return itemStack.asItem() == Items.SLIME_BALL;
+ return session.getTagCache().is(ItemTag.FROG_FOOD, itemStack);
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java
index a847c4cd7..ae9d0d659 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/WaterEntity.java
@@ -38,7 +38,7 @@ public class WaterEntity extends CreatureEntity {
}
@Override
- protected boolean canBeLeashed() {
+ public boolean canBeLeashed() {
return false;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java
index 7278709ce..2e627b461 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AnimalEntity.java
@@ -25,39 +25,40 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.living.AgeableEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
-public class AnimalEntity extends AgeableEntity {
+public abstract class AnimalEntity extends AgeableEntity {
public AnimalEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
- public final boolean canEat(GeyserItemStack itemStack) {
- return canEat(itemStack.asItem());
+ protected final boolean canEat(GeyserItemStack itemStack) {
+ ItemTag tag = getFoodTag();
+ if (tag == null) {
+ return false;
+ }
+ return session.getTagCache().is(tag, itemStack);
}
/**
- * @return true if this is a valid item to breed with for this animal.
+ * @return the tag associated with this animal for eating food. Null for nothing or different behavior.
*/
- public boolean canEat(Item item) {
- // This is what it defaults to. OK.
- return item == Items.WHEAT;
- }
+ protected abstract @Nullable ItemTag getFoodTag();
@NonNull
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ArmadilloEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ArmadilloEntity.java
new file mode 100644
index 000000000..968520bb6
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ArmadilloEntity.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.type.living.animal;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.cloudburstmc.math.vector.Vector3f;
+import org.geysermc.geyser.entity.EntityDefinition;
+import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.ArmadilloState;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+public class ArmadilloEntity extends AnimalEntity {
+ private ArmadilloState armadilloState = ArmadilloState.IDLE;
+
+ public ArmadilloEntity(GeyserSession session, int entityId, long geyserId, UUID uuid,
+ EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
+ super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
+ }
+
+ public void setArmadilloState(ObjectEntityMetadata entityMetadata) {
+ armadilloState = entityMetadata.getValue();
+
+ switch (armadilloState) {
+ case IDLE -> propertyManager.add("minecraft:armadillo_state", "unrolled");
+ case ROLLING -> propertyManager.add("minecraft:armadillo_state", "rolled_up");
+ case SCARED -> propertyManager.add("minecraft:armadillo_state", "rolled_up_relaxing");
+ case UNROLLING -> propertyManager.add("minecraft:armadillo_state", "rolled_up_unrolling");
+ }
+
+ updateBedrockEntityProperties();
+ }
+
+ public void onPeeking() {
+ // Technically we should wait if not currently scared
+ if (armadilloState == ArmadilloState.SCARED) {
+ propertyManager.add("minecraft:armadillo_state", "rolled_up_peeking");
+ updateBedrockEntityProperties();
+
+ // Needed for consecutive peeks
+ session.scheduleInEventLoop(() -> {
+ if (armadilloState == ArmadilloState.SCARED) {
+ propertyManager.add("minecraft:armadillo_state", "rolled_up_relaxing");
+ updateBedrockEntityProperties();
+ }
+ }, 250, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.ARMADILLO_FOOD;
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java
index 85b2afc14..a0ab56ead 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/AxolotlEntity.java
@@ -25,19 +25,20 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -60,8 +61,9 @@ public class AxolotlEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return session.getTagCache().isAxolotlTemptItem(item);
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.AXOLOTL_FOOD;
}
@Override
@@ -70,7 +72,7 @@ public class AxolotlEntity extends AnimalEntity {
}
@Override
- protected boolean canBeLeashed() {
+ public boolean canBeLeashed() {
return true;
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/BeeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/BeeEntity.java
index e05b44cf0..4fcf0e178 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/BeeEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/BeeEntity.java
@@ -25,16 +25,17 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
@@ -57,7 +58,8 @@ public class BeeEntity extends AnimalEntity {
// If the bee has stung
dirtyMetadata.put(EntityDataTypes.MARK_VARIANT, (xd & 0x04) == 0x04 ? 1 : 0);
// If the bee has nectar or not
- setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08);
+ propertyManager.add("minecraft:has_nectar", (xd & 0x08) == 0x08);
+ updateBedrockEntityProperties();
}
public void setAngerTime(IntEntityMetadata entityMetadata) {
@@ -66,7 +68,8 @@ public class BeeEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return session.getTagCache().isFlower(item);
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.BEE_FOOD;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ChickenEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ChickenEntity.java
index 164fb1b6c..075a49923 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ChickenEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/ChickenEntity.java
@@ -25,24 +25,23 @@
package org.geysermc.geyser.entity.type.living.animal;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
-import java.util.Set;
import java.util.UUID;
public class ChickenEntity extends AnimalEntity {
- private static final Set- VALID_FOOD = Set.of(Items.WHEAT_SEEDS, Items.MELON_SEEDS, Items.PUMPKIN_SEEDS, Items.BEETROOT_SEEDS);
public ChickenEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
@Override
- public boolean canEat(Item item) {
- return VALID_FOOD.contains(item);
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.CHICKEN_FOOD;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/CowEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/CowEntity.java
index cdcf534a3..64e7de193 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/CowEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/CowEntity.java
@@ -25,8 +25,8 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
@@ -34,8 +34,10 @@ import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -64,4 +66,10 @@ public class CowEntity extends AnimalEntity {
session.playSoundEvent(SoundEvent.MILK, position);
return InteractionResult.SUCCESS;
}
+
+ @Override
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.COW_FOOD;
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FoxEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FoxEntity.java
index 98c73cbce..e20031baa 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FoxEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FoxEntity.java
@@ -25,14 +25,15 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
@@ -55,7 +56,8 @@ public class FoxEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return session.getTagCache().isFoxFood(item);
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.FOX_FOOD;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FrogEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FrogEntity.java
index 039ef5bf9..120bfcdd4 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FrogEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/FrogEntity.java
@@ -25,17 +25,17 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.Entity;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
import java.util.OptionalInt;
import java.util.UUID;
@@ -76,7 +76,8 @@ public class FrogEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return item == Items.SLIME_BALL;
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.FROG_FOOD;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java
index 9ed94f96f..4e919b81c 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java
@@ -25,10 +25,8 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
@@ -37,7 +35,11 @@ import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -94,4 +96,10 @@ public class GoatEntity extends AnimalEntity {
private void setHornCount() {
dirtyMetadata.put(EntityDataTypes.GOAT_HORN_COUNT, (hasLeftHorn ? 1 : 0) + (hasRightHorn ? 1 : 0));
}
+
+ @Override
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.GOAT_FOOD;
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java
index 154c2f688..cc23fc607 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HoglinEntity.java
@@ -25,13 +25,14 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
+import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.UUID;
@@ -40,6 +41,8 @@ public class HoglinEntity extends AnimalEntity {
public HoglinEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
+ dirtyMetadata.put(EntityDataTypes.TARGET_EID, session.getPlayerEntity().getGeyserId());
+ setFlag(EntityFlag.SHAKING, isShaking());
}
public void setImmuneToZombification(BooleanEntityMetadata entityMetadata) {
@@ -54,12 +57,13 @@ public class HoglinEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return item == Items.CRIMSON_FUNGUS;
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.HOGLIN_FOOD;
}
@Override
- protected boolean canBeLeashed() {
+ public boolean canBeLeashed() {
return isNotLeashed();
}
@@ -67,4 +71,9 @@ public class HoglinEntity extends AnimalEntity {
protected boolean isEnemy() {
return true;
}
+
+ @Override
+ public boolean useArmSwingAttack() {
+ return true;
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java
index 1c347bf31..2c9040b53 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/MooshroomEntity.java
@@ -25,22 +25,22 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.FlowerItem;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
-public class MooshroomEntity extends AnimalEntity {
+public class MooshroomEntity extends CowEntity {
private boolean isBrown = false;
public MooshroomEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
@@ -77,7 +77,7 @@ public class MooshroomEntity extends AnimalEntity {
} else if (!isBaby && isAlive() && itemInHand.asItem() == Items.SHEARS) {
// Shear items
return InteractionResult.SUCCESS;
- } else if (isBrown && session.getTagCache().isSmallFlower(itemInHand) && itemInHand.asItem() instanceof FlowerItem) {
+ } else if (isBrown && session.getTagCache().is(ItemTag.SMALL_FLOWERS, itemInHand)) {
// ?
return InteractionResult.SUCCESS;
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java
index c115ebcdc..9d6d33227 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java
@@ -25,17 +25,17 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -46,8 +46,9 @@ public class OcelotEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return item == Items.COD || item == Items.SALMON;
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.OCELOT_FOOD;
}
@NonNull
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java
index d2ef36932..aaa7c2d7e 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PandaEntity.java
@@ -25,9 +25,6 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
@@ -37,11 +34,13 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -62,7 +61,8 @@ public class PandaEntity extends AnimalEntity {
EntityEventPacket packet = new EntityEventPacket();
packet.setRuntimeEntityId(geyserId);
packet.setType(EntityEventType.EATING_ITEM);
- packet.setData(session.getItemMappings().getStoredItems().bamboo().getBedrockDefinition().getRuntimeId() << 16);
+ // As of 1.20.5 - pandas can eat cake
+ packet.setData(this.hand.getDefinition().getRuntimeId() << 16);
session.sendUpstreamPacket(packet);
}
}
@@ -89,8 +89,9 @@ public class PandaEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return item == Items.BAMBOO;
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.PANDA_FOOD;
}
@NonNull
@@ -122,7 +123,7 @@ public class PandaEntity extends AnimalEntity {
}
@Override
- protected boolean canBeLeashed() {
+ public boolean canBeLeashed() {
return false;
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java
index 2bc02cd55..446e3e109 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PigEntity.java
@@ -25,18 +25,18 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -47,8 +47,9 @@ public class PigEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return item == Items.CARROT || item == Items.POTATO || item == Items.BEETROOT;
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.PIG_FOOD;
}
@NonNull
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PolarBearEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PolarBearEntity.java
index 1d7777cdb..0e83615f7 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PolarBearEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PolarBearEntity.java
@@ -25,10 +25,11 @@
package org.geysermc.geyser.entity.type.living.animal;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import java.util.UUID;
@@ -39,7 +40,8 @@ public class PolarBearEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return false;
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return null;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PufferFishEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PufferFishEntity.java
index d0d119593..6f0063474 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PufferFishEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/PufferFishEntity.java
@@ -25,12 +25,12 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.living.AbstractFishEntity;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java
index 1efa87ec8..0a108be73 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java
@@ -25,14 +25,14 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
@@ -67,7 +67,8 @@ public class RabbitEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return item == Items.DANDELION || item == Items.CARROT || item == Items.GOLDEN_CARROT;
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.RABBIT_FOOD;
}
}
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java
index 13059244a..155ddf00c 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SheepEntity.java
@@ -25,9 +25,8 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
@@ -36,8 +35,11 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.DyeItem;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -55,6 +57,12 @@ public class SheepEntity extends AnimalEntity {
dirtyMetadata.put(EntityDataTypes.COLOR, (byte) color);
}
+ @Override
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.SHEEP_FOOD;
+ }
+
@NonNull
@Override
protected InteractiveTag testMobInteraction(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) {
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java
index a97756e39..11fee5bbf 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java
@@ -25,9 +25,7 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.SnifferState;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
@@ -37,8 +35,11 @@ import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.Tickable;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.SnifferState;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
import java.util.UUID;
@@ -71,8 +72,9 @@ public class SnifferEntity extends AnimalEntity implements Tickable {
}
@Override
- public boolean canEat(Item item) {
- return session.getTagCache().isSnifferFood(item);
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.SNIFFER_FOOD;
}
public void setSnifferState(ObjectEntityMetadata entityMetadata) {
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java
index 39a55fa1e..0291f75d9 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/StriderEntity.java
@@ -25,20 +25,20 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.inventory.GeyserItemStack;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -94,8 +94,9 @@ public class StriderEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return item == Items.WARPED_FUNGUS;
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.STRIDER_FOOD;
}
@NonNull
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TropicalFishEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TropicalFishEntity.java
index b18e55a48..b6751bc3f 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TropicalFishEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TropicalFishEntity.java
@@ -25,14 +25,14 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.IntList;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
-import it.unimi.dsi.fastutil.ints.IntList;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.living.AbstractFishEntity;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.List;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java
index 870ded193..16901a844 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/TurtleEntity.java
@@ -25,13 +25,13 @@
package org.geysermc.geyser.entity.type.living.animal;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.UUID;
@@ -50,12 +50,13 @@ public class TurtleEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return item == Items.SEAGRASS;
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.TURTLE_FOOD;
}
@Override
- protected boolean canBeLeashed() {
+ public boolean canBeLeashed() {
return false;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java
index faa495487..ddc212053 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java
@@ -25,9 +25,8 @@
package org.geysermc.geyser.entity.type.living.animal.horse;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
@@ -40,21 +39,16 @@ import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
-import java.util.Set;
import java.util.UUID;
public class AbstractHorseEntity extends AnimalEntity {
- /**
- * A list of all foods a horse/donkey can eat on Java Edition.
- * Used to display interactive tag if needed.
- */
- private static final Set
- DONKEY_AND_HORSE_FOODS = Set.of(Items.GOLDEN_APPLE, Items.ENCHANTED_GOLDEN_APPLE,
- Items.GOLDEN_CARROT, Items.SUGAR, Items.APPLE, Items.WHEAT, Items.HAY_BLOCK);
public AbstractHorseEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
@@ -124,8 +118,9 @@ public class AbstractHorseEntity extends AnimalEntity {
}
@Override
- public boolean canEat(Item item) {
- return DONKEY_AND_HORSE_FOODS.contains(item);
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.HORSE_FOOD;
}
@NonNull
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java
index 8106b096d..ee3b2be70 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java
@@ -25,9 +25,7 @@
package org.geysermc.geyser.entity.type.living.animal.horse;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
@@ -35,9 +33,11 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import java.util.UUID;
@@ -90,8 +90,8 @@ public class CamelEntity extends AbstractHorseEntity {
}
@Override
- public boolean canEat(Item item) {
- return item == Items.CACTUS;
+ protected @Nullable ItemTag getFoodTag() {
+ return ItemTag.CAMEL_FOOD;
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/HorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/HorseEntity.java
index dfa6ef30a..b8a9a8f28 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/HorseEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/HorseEntity.java
@@ -25,11 +25,11 @@
package org.geysermc.geyser.entity.type.living.animal.horse;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java
index a32d7b1b5..76939ceb9 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/LlamaEntity.java
@@ -25,19 +25,24 @@
package org.geysermc.geyser.entity.type.living.animal.horse;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import lombok.Getter;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
-import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
-import org.cloudburstmc.protocol.bedrock.packet.MobArmorEquipmentPacket;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
+import org.geysermc.geyser.util.MathUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
public class LlamaEntity extends ChestedHorseEntity {
+ /**
+ * Used to calculate inventory size
+ */
+ @Getter
+ private int strength = 1;
public LlamaEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
@@ -45,32 +50,13 @@ public class LlamaEntity extends ChestedHorseEntity {
dirtyMetadata.put(EntityDataTypes.CONTAINER_STRENGTH_MODIFIER, 3); // Presumably 3 slots for every 1 strength
}
- /**
- * Color equipped on the llama
- */
- public void setCarpetedColor(IntEntityMetadata entityMetadata) {
- // Bedrock treats llama decoration as armor
- MobArmorEquipmentPacket equipmentPacket = new MobArmorEquipmentPacket();
- equipmentPacket.setRuntimeEntityId(geyserId);
- // -1 means no armor
- int carpetIndex = entityMetadata.getPrimitiveValue();
- if (carpetIndex > -1 && carpetIndex <= 15) {
- // The damage value is the dye color that Java sends us, for pre-1.16.220
- // The item is always going to be a carpet
- equipmentPacket.setChestplate(session.getItemMappings().getCarpets().get(carpetIndex));
- } else {
- equipmentPacket.setChestplate(ItemData.AIR);
- }
- // Required to fill out the rest of the equipment or Bedrock ignores it, including above else statement if removing armor
- equipmentPacket.setBoots(ItemData.AIR);
- equipmentPacket.setHelmet(ItemData.AIR);
- equipmentPacket.setLeggings(ItemData.AIR);
-
- session.sendUpstreamPacket(equipmentPacket);
+ public void setStrength(IntEntityMetadata entityMetadata) {
+ strength = MathUtils.constrain(entityMetadata.getPrimitiveValue(), 1, 5);
+ this.dirtyMetadata.put(EntityDataTypes.STRENGTH, strength);
}
@Override
- public boolean canEat(Item item) {
- return item == Items.WHEAT || item == Items.HAY_BLOCK;
+ protected @Nullable ItemTag getFoodTag() {
+ return ItemTag.LLAMA_FOOD;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/SkeletonHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/SkeletonHorseEntity.java
index 7080f9f75..d74913c31 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/SkeletonHorseEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/SkeletonHorseEntity.java
@@ -25,7 +25,6 @@
package org.geysermc.geyser.entity.type.living.animal.horse;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
@@ -33,6 +32,7 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ZombieHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ZombieHorseEntity.java
index 3275712fc..9e77daebc 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ZombieHorseEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/ZombieHorseEntity.java
@@ -25,7 +25,6 @@
package org.geysermc.geyser.entity.type.living.animal.horse;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
@@ -33,6 +32,7 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java
index 412157b5d..bf1555e9d 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java
@@ -25,27 +25,27 @@
package org.geysermc.geyser.entity.type.living.animal.tameable;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
-import org.geysermc.geyser.item.Items;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
public class CatEntity extends TameableEntity {
- private byte collarColor;
+ private byte collarColor = 14; // Red - default
public CatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
@@ -76,10 +76,7 @@ public class CatEntity extends TameableEntity {
@Override
public void setTameableFlags(ByteEntityMetadata entityMetadata) {
super.setTameableFlags(entityMetadata);
- // Update collar color if tamed
- if (getFlag(EntityFlag.TAMED)) {
- dirtyMetadata.put(EntityDataTypes.COLOR, collarColor);
- }
+ updateCollarColor();
}
public void setCatVariant(IntEntityMetadata entityMetadata) {
@@ -101,6 +98,10 @@ public class CatEntity extends TameableEntity {
public void setCollarColor(IntEntityMetadata entityMetadata) {
collarColor = (byte) entityMetadata.getPrimitiveValue();
+ updateCollarColor();
+ }
+
+ private void updateCollarColor() {
// Needed or else wild cats are a red color
if (getFlag(EntityFlag.TAMED)) {
dirtyMetadata.put(EntityDataTypes.COLOR, collarColor);
@@ -108,8 +109,8 @@ public class CatEntity extends TameableEntity {
}
@Override
- public boolean canEat(Item item) {
- return item == Items.COD || item == Items.SALMON;
+ protected @Nullable ItemTag getFoodTag() {
+ return ItemTag.CAT_FOOD;
}
@NonNull
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java
index 4c4b6a222..8baba6f00 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/ParrotEntity.java
@@ -25,8 +25,8 @@
package org.geysermc.geyser.entity.type.living.animal.tameable;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
@@ -34,8 +34,10 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.Set;
import java.util.UUID;
@@ -49,16 +51,17 @@ public class ParrotEntity extends TameableEntity {
}
@Override
- public boolean canEat(Item item) {
- return false;
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return null;
}
private boolean isTameFood(Item item) {
- return TAMING_FOOD.contains(item);
+ return session.getTagCache().is(ItemTag.PARROT_FOOD, item);
}
private boolean isPoisonousFood(Item item) {
- return item == Items.COOKIE;
+ return session.getTagCache().is(ItemTag.PARROT_POISONOUS_FOOD, item);
}
@NonNull
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java
index 5fc8c459d..ea347d193 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/TameableEntity.java
@@ -25,21 +25,21 @@
package org.geysermc.geyser.entity.type.living.animal.tameable;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import lombok.Getter;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
-import lombok.Getter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import java.util.Optional;
import java.util.UUID;
-public class TameableEntity extends AnimalEntity {
+public abstract class TameableEntity extends AnimalEntity {
/**
* Used in the interactive tag manager to track if the session player owns this entity
*/
@@ -84,7 +84,7 @@ public class TameableEntity extends AnimalEntity {
}
@Override
- protected boolean canBeLeashed() {
+ public boolean canBeLeashed() {
return isNotLeashed();
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java
index 0f5b36ec3..e7fde2be8 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java
@@ -25,36 +25,39 @@
package org.geysermc.geyser.entity.type.living.animal.tameable;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
+import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
+import org.geysermc.geyser.item.enchantment.EnchantmentComponent;
import org.geysermc.geyser.item.type.DyeItem;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.geyser.util.ItemUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.Holder;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.WolfVariant;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
+import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
-import java.util.Set;
+import java.util.Collections;
+import java.util.Locale;
import java.util.UUID;
public class WolfEntity extends TameableEntity {
- /**
- * A list of all foods a wolf can eat on Java Edition.
- * Used to display interactive tag or particles if needed.
- * TODO generate
- */
- private static final Set
- WOLF_FOODS = Set.of(Items.PUFFERFISH, Items.TROPICAL_FISH, Items.CHICKEN, Items.COOKED_CHICKEN,
- Items.PORKCHOP, Items.BEEF, Items.RABBIT, Items.COOKED_PORKCHOP, Items.COOKED_BEEF, Items.ROTTEN_FLESH, Items.MUTTON, Items.COOKED_MUTTON,
- Items.COOKED_RABBIT);
+ private byte collarColor = 14; // Red - default
- private byte collarColor;
+ private boolean isCurseOfBinding = false;
public WolfEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
@@ -64,19 +67,27 @@ public class WolfEntity extends TameableEntity {
public void setTameableFlags(ByteEntityMetadata entityMetadata) {
super.setTameableFlags(entityMetadata);
// Reset wolf color
- byte xd = entityMetadata.getPrimitiveValue();
- boolean angry = (xd & 0x02) == 0x02;
- if (angry) {
+ if (getFlag(EntityFlag.ANGRY)) {
dirtyMetadata.put(EntityDataTypes.COLOR, (byte) 0);
+ } else if (getFlag(EntityFlag.TAMED)) {
+ updateCollarColor();
+
+ // This fixes tail angle when taming
+ UpdateAttributesPacket packet = new UpdateAttributesPacket();
+ packet.setRuntimeEntityId(geyserId);
+ packet.setAttributes(Collections.singletonList(createHealthAttribute()));
+ session.sendUpstreamPacket(packet);
}
}
public void setCollarColor(IntEntityMetadata entityMetadata) {
collarColor = (byte) entityMetadata.getPrimitiveValue();
- if (getFlag(EntityFlag.ANGRY)) {
- return;
+ if (!getFlag(EntityFlag.ANGRY) && getFlag(EntityFlag.TAMED)) {
+ updateCollarColor();
}
+ }
+ private void updateCollarColor() {
dirtyMetadata.put(EntityDataTypes.COLOR, collarColor);
if (ownerBedrockId == 0) {
// If a color is set and there is no owner entity ID, set one.
@@ -92,14 +103,31 @@ public class WolfEntity extends TameableEntity {
dirtyMetadata.put(EntityDataTypes.COLOR, time != 0 ? (byte) 0 : collarColor);
}
- @Override
- public boolean canEat(Item item) {
- // Cannot be a baby to eat these foods
- return WOLF_FOODS.contains(item) && !isBaby();
+ // 1.20.5+
+ public void setWolfVariant(ObjectEntityMetadata> entityMetadata) {
+ entityMetadata.getValue().ifId(id -> {
+ BuiltInWolfVariant wolfVariant = session.getRegistryCache().wolfVariants().byId(id);
+ if (wolfVariant == null) {
+ wolfVariant = BuiltInWolfVariant.PALE;
+ }
+ dirtyMetadata.put(EntityDataTypes.VARIANT, wolfVariant.ordinal());
+ });
}
@Override
- protected boolean canBeLeashed() {
+ @Nullable
+ protected ItemTag getFoodTag() {
+ return ItemTag.WOLF_FOOD;
+ }
+
+ @Override
+ public void setChestplate(ItemStack stack) {
+ super.setChestplate(stack);
+ isCurseOfBinding = ItemUtils.hasEffect(session, stack, EnchantmentComponent.PREVENT_ARMOR_CHANGE); // TODO test
+ }
+
+ @Override
+ public boolean canBeLeashed() {
return !getFlag(EntityFlag.ANGRY) && super.canBeLeashed();
}
@@ -112,16 +140,30 @@ public class WolfEntity extends TameableEntity {
if (itemInHand.asItem() == Items.BONE && !getFlag(EntityFlag.TAMED)) {
// Bone and untamed - can tame
return InteractiveTag.TAME;
- } else {
- if (itemInHand.asItem() instanceof DyeItem item) {
+ }
+ if (getFlag(EntityFlag.TAMED) && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
+ if (itemInHand.asItem() instanceof DyeItem dyeItem) {
// If this fails, as of Java Edition 1.18.1, you cannot toggle sit/stand
- if (item.dyeColor() != this.collarColor) {
+ if (dyeItem.dyeColor() != this.collarColor) {
return InteractiveTag.DYE;
+ } else {
+ return super.testMobInteraction(hand, itemInHand);
}
- } else if (getFlag(EntityFlag.TAMED) && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
- // Tamed and owned by player - can sit/stand
- return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
}
+ if (itemInHand.asItem() == Items.WOLF_ARMOR && !this.chestplate.isValid() && !getFlag(EntityFlag.BABY)) {
+ return InteractiveTag.EQUIP_WOLF_ARMOR;
+ }
+ if (itemInHand.asItem() == Items.SHEARS && this.chestplate.isValid()
+ && (!isCurseOfBinding || session.getGameMode().equals(GameMode.CREATIVE))) {
+ return InteractiveTag.REMOVE_WOLF_ARMOR;
+ }
+ if (Items.WOLF_ARMOR.isValidRepairItem(itemInHand.asItem()) && getFlag(EntityFlag.SITTING) &&
+ this.chestplate.isValid() && this.chestplate.getTag() != null &&
+ this.chestplate.getTag().getInt("Damage") > 0) {
+ return InteractiveTag.REPAIR_WOLF_ARMOR;
+ }
+ // Tamed and owned by player - can sit/stand
+ return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
}
return super.testMobInteraction(hand, itemInHand);
}
@@ -137,4 +179,34 @@ public class WolfEntity extends TameableEntity {
return InteractionResult.PASS;
}
}
+
+ // Ordered by bedrock id
+ public enum BuiltInWolfVariant {
+ PALE,
+ ASHEN,
+ BLACK,
+ CHESTNUT,
+ RUSTY,
+ SNOWY,
+ SPOTTED,
+ STRIPED,
+ WOODS;
+
+ private static final BuiltInWolfVariant[] VALUES = values();
+
+ private final String javaIdentifier;
+
+ BuiltInWolfVariant() {
+ this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ROOT);
+ }
+
+ public static @Nullable BuiltInWolfVariant getByJavaIdentifier(String javaIdentifier) {
+ for (BuiltInWolfVariant wolfVariant : VALUES) {
+ if (wolfVariant.javaIdentifier.equals(javaIdentifier)) {
+ return wolfVariant;
+ }
+ }
+ return null;
+ }
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java
index c7b29130f..2492aabd7 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java
@@ -25,7 +25,6 @@
package org.geysermc.geyser.entity.type.living.merchant;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
@@ -37,6 +36,7 @@ import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -47,7 +47,7 @@ public class AbstractMerchantEntity extends AgeableEntity {
}
@Override
- protected boolean canBeLeashed() {
+ public boolean canBeLeashed() {
return false;
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java
index 9b0f50050..d7efa9f1d 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java
@@ -25,8 +25,6 @@
package org.geysermc.geyser.entity.type.living.merchant;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
@@ -35,9 +33,12 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import org.geysermc.geyser.entity.EntityDefinition;
-import org.geysermc.geyser.registry.BlockRegistries;
-import org.geysermc.geyser.registry.type.BlockMapping;
+import org.geysermc.geyser.level.block.property.Properties;
+import org.geysermc.geyser.level.block.type.BedBlock;
+import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.VillagerData;
import java.util.Optional;
import java.util.UUID;
@@ -119,28 +120,31 @@ public class VillagerEntity extends AbstractMerchantEntity {
}
// The bed block
- int blockId = session.getGeyser().getWorldManager().getBlockAt(session, bedPosition);
- String fullIdentifier = BlockRegistries.JAVA_BLOCKS.getOrDefault(blockId, BlockMapping.DEFAULT).getJavaIdentifier();
+ BlockState state = session.getGeyser().getWorldManager().blockAt(session, bedPosition);
// Set the correct position offset and rotation when sleeping
int bedRotation = 0;
float xOffset = 0;
float zOffset = 0;
- if (fullIdentifier.contains("facing=south")) {
- // bed is facing south
- bedRotation = 180;
- zOffset = -.5f;
- } else if (fullIdentifier.contains("facing=east")) {
- // bed is facing east
- bedRotation = 90;
- xOffset = -.5f;
- } else if (fullIdentifier.contains("facing=west")) {
- // bed is facing west
- bedRotation = 270;
- xOffset = .5f;
- } else if (fullIdentifier.contains("facing=north")) {
- // rotation does not change because north is 0
- zOffset = .5f;
+ if (state.block() instanceof BedBlock) {
+ switch (state.getValue(Properties.HORIZONTAL_FACING)) {
+ case SOUTH -> {
+ bedRotation = 180;
+ zOffset = -.5f;
+ }
+ case EAST -> {
+ bedRotation = 90;
+ xOffset = -.5f;
+ }
+ case WEST -> {
+ bedRotation = 270;
+ xOffset = .5f;
+ }
+ case NORTH -> {
+ // rotation does not change because north is 0
+ zOffset = .5f;
+ }
+ }
}
setYaw(yaw);
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/AbstractSkeletonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/AbstractSkeletonEntity.java
index 04b3bba1b..d08fff06a 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/AbstractSkeletonEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/AbstractSkeletonEntity.java
@@ -25,11 +25,13 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
+import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
+import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import java.util.UUID;
@@ -45,5 +47,17 @@ public class AbstractSkeletonEntity extends MonsterEntity {
byte xd = entityMetadata.getPrimitiveValue();
// A bit of a loophole so the hands get raised - set the target ID to its own ID
dirtyMetadata.put(EntityDataTypes.TARGET_EID, ((xd & 4) == 4) ? geyserId : 0);
+
+ if ((xd & 4) == 4) {
+ ItemDefinition bow = session.getItemMappings().getStoredItems().bow().getBedrockDefinition();
+ setFlag(EntityFlag.FACING_TARGET_TO_RANGE_ATTACK, this.hand.getDefinition() == bow || this.offhand.getDefinition() == bow);
+ } else {
+ setFlag(EntityFlag.FACING_TARGET_TO_RANGE_ATTACK, false);
+ }
+ }
+
+ @Override
+ public boolean useArmSwingAttack() {
+ return true;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BasePiglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BasePiglinEntity.java
index 5f2647b7a..9258cd3b8 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BasePiglinEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BasePiglinEntity.java
@@ -25,11 +25,14 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
+import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import java.util.UUID;
@@ -38,6 +41,16 @@ public class BasePiglinEntity extends MonsterEntity {
public BasePiglinEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
+ // Both TARGET_EID and BLOCK are needed for melee attack animation
+ dirtyMetadata.put(EntityDataTypes.BLOCK, session.getBlockMappings().getDefinition(1));
+ setFlag(EntityFlag.SHAKING, isShaking());
+ }
+
+ @Override
+ public void setMobFlags(ByteEntityMetadata entityMetadata) {
+ super.setMobFlags(entityMetadata);
+ byte xd = entityMetadata.getPrimitiveValue();
+ dirtyMetadata.put(EntityDataTypes.TARGET_EID, (xd & 4) == 4 ? session.getPlayerEntity().getGeyserId() : 0);
}
public void setImmuneToZombification(BooleanEntityMetadata entityMetadata) {
@@ -50,4 +63,9 @@ public class BasePiglinEntity extends MonsterEntity {
protected boolean isShaking() {
return (!isImmuneToZombification && !session.getDimensionType().piglinSafe()) || super.isShaking();
}
+
+ @Override
+ public boolean useArmSwingAttack() {
+ return true;
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BlazeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BlazeEntity.java
index 43d78f468..5b26d7bd1 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BlazeEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BlazeEntity.java
@@ -25,11 +25,11 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BoggedEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BoggedEntity.java
new file mode 100644
index 000000000..806d58ed1
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BoggedEntity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.type.living.monster;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.cloudburstmc.math.vector.Vector3f;
+import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
+import org.geysermc.geyser.entity.EntityDefinition;
+import org.geysermc.geyser.inventory.GeyserItemStack;
+import org.geysermc.geyser.item.Items;
+import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.util.InteractionResult;
+import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
+
+import java.util.UUID;
+
+public class BoggedEntity extends AbstractSkeletonEntity {
+ private boolean sheared = false;
+
+ public BoggedEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
+ super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
+ }
+
+ public void setSheared(BooleanEntityMetadata entityMetadata) {
+ this.sheared = entityMetadata.getPrimitiveValue();
+ setFlag(EntityFlag.SHEARED, this.sheared);
+ }
+
+ @Override
+ protected @NonNull InteractiveTag testMobInteraction(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) {
+ if (itemInHand.asItem() == Items.SHEARS && readyForShearing()) {
+ return InteractiveTag.SHEAR;
+ }
+ return super.testMobInteraction(hand, itemInHand);
+ }
+
+ @Override
+ protected @NonNull InteractionResult mobInteract(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) {
+ if (itemInHand.asItem() == Items.SHEARS && readyForShearing()) {
+ return InteractionResult.SUCCESS;
+ }
+ return super.mobInteract(hand, itemInHand);
+ }
+
+ private boolean readyForShearing() {
+ return !this.sheared && this.isAlive();
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BreezeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BreezeEntity.java
new file mode 100644
index 000000000..251a77fb9
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/BreezeEntity.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.type.living.monster;
+
+import org.cloudburstmc.math.vector.Vector3f;
+import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
+import org.geysermc.geyser.entity.EntityDefinition;
+import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
+
+import java.util.UUID;
+
+public class BreezeEntity extends MonsterEntity {
+ public BreezeEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
+ super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
+ }
+
+ @Override
+ public void setPose(Pose pose) {
+ // TODO Test
+ setFlag(EntityFlag.FACING_TARGET_TO_RANGE_ATTACK, pose == Pose.SHOOTING);
+ setFlag(EntityFlag.JUMP_GOAL_JUMP, pose == Pose.INHALING);
+ super.setPose(pose);
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java
index 33ea4e544..5f54d2942 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreeperEntity.java
@@ -25,9 +25,6 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
@@ -35,8 +32,12 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
@@ -65,7 +66,7 @@ public class CreeperEntity extends MonsterEntity {
@NonNull
@Override
protected InteractiveTag testMobInteraction(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) {
- if (session.getTagCache().isCreeperIgniter(itemInHand.asItem())) {
+ if (session.getTagCache().is(ItemTag.CREEPER_IGNITERS, itemInHand)) {
return InteractiveTag.IGNITE_CREEPER;
} else {
return super.testMobInteraction(hand, itemInHand);
@@ -75,7 +76,7 @@ public class CreeperEntity extends MonsterEntity {
@NonNull
@Override
protected InteractionResult mobInteract(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) {
- if (session.getTagCache().isCreeperIgniter(itemInHand.asItem())) {
+ if (session.getTagCache().is(ItemTag.CREEPER_IGNITERS, itemInHand)) {
// Ignite creeper - as of 1.19.3
session.playSoundEvent(SoundEvent.IGNITE, position);
return InteractionResult.SUCCESS;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java
index bb09a23f4..0162d498e 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java
@@ -25,23 +25,19 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import lombok.Data;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.ParticleType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
-import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
-import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
-import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
-import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket;
-import org.cloudburstmc.protocol.bedrock.packet.SpawnParticleEffectPacket;
+import org.cloudburstmc.protocol.bedrock.packet.*;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.Tickable;
import org.geysermc.geyser.entity.type.living.MobEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.DimensionUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.Optional;
import java.util.Random;
@@ -148,11 +144,11 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
}
@Override
- public boolean despawnEntity() {
+ public void despawnEntity() {
for (EnderDragonPartEntity part : allParts) {
part.despawnEntity();
}
- return super.despawnEntity();
+ super.despawnEntity();
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java
index 5b8e23f8b..586ba5cd9 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java
@@ -25,8 +25,6 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
@@ -35,6 +33,8 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java
index f7b9d17b8..984aab642 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GhastEntity.java
@@ -25,12 +25,12 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.living.FlyingEntity;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GiantEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GiantEntity.java
index e98c8f120..6bef3ae3e 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GiantEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GiantEntity.java
@@ -26,7 +26,6 @@
package org.geysermc.geyser.entity.type.living.monster;
import org.cloudburstmc.math.vector.Vector3f;
-import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
@@ -36,7 +35,11 @@ public class GiantEntity extends MonsterEntity {
public GiantEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
+ }
- dirtyMetadata.put(EntityDataTypes.SCALE, 6f);
+ @Override
+ protected void initializeMetadata() {
+ super.initializeMetadata();
+ setScale(6f);
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GuardianEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GuardianEntity.java
index 92e50d207..40793522e 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GuardianEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/GuardianEntity.java
@@ -25,12 +25,12 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java
index 915e34e79..18b7f6ae1 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PhantomEntity.java
@@ -25,12 +25,11 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
-import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.living.FlyingEntity;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
@@ -46,7 +45,7 @@ public class PhantomEntity extends FlyingEntity {
setBoundingBoxWidth(boundsScale * definition.width());
setBoundingBoxHeight(boundsScale * definition.height());
- dirtyMetadata.put(EntityDataTypes.SCALE, modelScale);
+ setScale(modelScale);
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java
index 450c546ec..19b6d8e69 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/PiglinEntity.java
@@ -25,18 +25,24 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
+import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;
+import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
+import org.cloudburstmc.protocol.bedrock.packet.MobEquipmentPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items;
+import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
+import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import java.util.UUID;
@@ -48,24 +54,72 @@ public class PiglinEntity extends BasePiglinEntity {
public void setBaby(BooleanEntityMetadata entityMetadata) {
boolean isBaby = entityMetadata.getPrimitiveValue();
- dirtyMetadata.put(EntityDataTypes.SCALE, isBaby? .55f : 1f);
+ setScale(isBaby? .55f : 1f);
setFlag(EntityFlag.BABY, isBaby);
updateMountOffset();
}
public void setChargingCrossbow(BooleanEntityMetadata entityMetadata) {
- setFlag(EntityFlag.CHARGING, entityMetadata.getPrimitiveValue());
+ boolean charging = entityMetadata.getPrimitiveValue();
+ setFlag(EntityFlag.CHARGING, charging);
+ dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, charging ? (byte) 64 : (byte) 0); // TODO: gradually increase
}
public void setDancing(BooleanEntityMetadata entityMetadata) {
setFlag(EntityFlag.DANCING, entityMetadata.getPrimitiveValue());
}
+ @Override
+ public void setHand(ItemStack stack) {
+ ItemMapping crossbow = session.getItemMappings().getStoredItems().crossbow();
+ boolean toCrossbow = stack != null && stack.getId() == crossbow.getJavaItem().javaId();
+
+ if (toCrossbow ^ this.hand.getDefinition() == crossbow.getBedrockDefinition()) { // If switching to/from crossbow
+ dirtyMetadata.put(EntityDataTypes.BLOCK, session.getBlockMappings().getDefinition(toCrossbow ? 0 : 1));
+ dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, (byte) 0);
+ setFlag(EntityFlag.CHARGED, false);
+ setFlag(EntityFlag.USING_ITEM, false);
+ updateBedrockMetadata();
+
+ if (this.hand.isValid()) {
+ MobEquipmentPacket mobEquipmentPacket = new MobEquipmentPacket();
+ mobEquipmentPacket.setRuntimeEntityId(geyserId);
+ mobEquipmentPacket.setContainerId(ContainerId.INVENTORY);
+ mobEquipmentPacket.setInventorySlot(0);
+ mobEquipmentPacket.setHotbarSlot(-1);
+ mobEquipmentPacket.setItem(ItemData.AIR);
+ session.sendUpstreamPacket(mobEquipmentPacket);
+ }
+ }
+
+ super.setHand(stack);
+ }
+
+ @Override
+ public void updateMainHand(GeyserSession session) {
+ super.updateMainHand(session);
+
+ if (this.hand.getDefinition() == session.getItemMappings().getStoredItems().crossbow().getBedrockDefinition()) {
+ if (this.hand.getTag() != null && this.hand.getTag().containsKey("chargedItem")) {
+ dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, Byte.MAX_VALUE);
+ setFlag(EntityFlag.CHARGING, false);
+ setFlag(EntityFlag.CHARGED, true);
+ setFlag(EntityFlag.USING_ITEM, true);
+ } else if (getFlag(EntityFlag.CHARGED)) {
+ dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, (byte) 0);
+ setFlag(EntityFlag.CHARGED, false);
+ setFlag(EntityFlag.USING_ITEM, false);
+ }
+ }
+
+ updateBedrockMetadata();
+ }
+
@Override
public void updateOffHand(GeyserSession session) {
// Check if the Piglin is holding Gold and set the ADMIRING flag accordingly so its pose updates
- setFlag(EntityFlag.ADMIRING, session.getTagCache().shouldPiglinAdmire(session.getItemMappings().getMapping(this.offHand).getJavaItem()));
+ setFlag(EntityFlag.ADMIRING, session.getTagCache().is(ItemTag.PIGLIN_LOVED, session.getItemMappings().getMapping(this.offhand).getJavaItem()));
super.updateBedrockMetadata();
super.updateOffHand(session);
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java
index 27dd45f40..aecb4a915 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java
@@ -25,15 +25,15 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.object.Direction;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.living.GolemEntity;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SkeletonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SkeletonEntity.java
index da11b2759..a6343e256 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SkeletonEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SkeletonEntity.java
@@ -25,11 +25,11 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SpiderEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SpiderEntity.java
index 03e234911..4a4527cef 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SpiderEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/SpiderEntity.java
@@ -25,11 +25,11 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/VexEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/VexEntity.java
index 56a0975ae..840f5b3b4 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/VexEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/VexEntity.java
@@ -25,11 +25,11 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java
index 7a0c5e040..2341b8c32 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java
@@ -25,8 +25,6 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.GenericMath;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
@@ -36,6 +34,8 @@ import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.Tickable;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.MathUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WitherEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WitherEntity.java
index 3abb7f122..19c1a457b 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WitherEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WitherEntity.java
@@ -25,13 +25,13 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataType;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java
index 6e40573ba..3d6e381c7 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZoglinEntity.java
@@ -25,12 +25,12 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.UUID;
@@ -38,12 +38,13 @@ public class ZoglinEntity extends MonsterEntity {
public ZoglinEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
+ dirtyMetadata.put(EntityDataTypes.TARGET_EID, session.getPlayerEntity().getGeyserId());
}
public void setBaby(BooleanEntityMetadata entityMetadata) {
boolean isBaby = entityMetadata.getPrimitiveValue();
if (isBaby != getFlag(EntityFlag.BABY)) {
- dirtyMetadata.put(EntityDataTypes.SCALE, isBaby ? .55f : 1f);
+ setScale(isBaby ? .55f : 1f);
setFlag(EntityFlag.BABY, isBaby);
updatePassengerOffsets();
@@ -57,7 +58,7 @@ public class ZoglinEntity extends MonsterEntity {
}
@Override
- protected boolean canBeLeashed() {
+ public boolean canBeLeashed() {
return isNotLeashed();
}
@@ -65,4 +66,9 @@ public class ZoglinEntity extends MonsterEntity {
protected boolean isEnemy() {
return true;
}
+
+ @Override
+ public boolean useArmSwingAttack() {
+ return true;
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieEntity.java
index af6a30a10..b07afd742 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieEntity.java
@@ -25,12 +25,11 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
-import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.UUID;
@@ -43,7 +42,7 @@ public class ZombieEntity extends MonsterEntity {
public void setZombieBaby(BooleanEntityMetadata entityMetadata) {
boolean isBaby = entityMetadata.getPrimitiveValue();
- dirtyMetadata.put(EntityDataTypes.SCALE, isBaby ? .55f : 1.0f);
+ setScale(isBaby ? .55f : 1.0f);
setFlag(EntityFlag.BABY, isBaby);
updateMountOffset();
@@ -58,4 +57,9 @@ public class ZombieEntity extends MonsterEntity {
protected boolean isShaking() {
return convertingToDrowned || super.isShaking();
}
+
+ @Override
+ public boolean useArmSwingAttack() {
+ return true;
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java
index 32e45507a..6e03e4f98 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ZombieVillagerEntity.java
@@ -25,10 +25,6 @@
package org.geysermc.geyser.entity.type.living.monster;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
@@ -40,6 +36,10 @@ import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.VillagerData;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/PillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/PillagerEntity.java
index d2f8377d3..fd7448e29 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/PillagerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/PillagerEntity.java
@@ -26,10 +26,13 @@
package org.geysermc.geyser.entity.type.living.monster.raid;
import org.cloudburstmc.math.vector.Vector3f;
+import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
+import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import java.util.UUID;
@@ -39,16 +42,22 @@ public class PillagerEntity extends AbstractIllagerEntity {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
+ public void setChargingCrossbow(BooleanEntityMetadata entityMetadata) {
+ boolean charging = entityMetadata.getPrimitiveValue();
+ setFlag(EntityFlag.CHARGING, charging);
+ dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, charging ? (byte) 64 : (byte) 0); // TODO: gradually increase
+ }
+
@Override
- public void updateMainHand(GeyserSession session) { //TODO
- checkForCrossbow();
+ public void updateMainHand(GeyserSession session) {
+ updateCrossbow();
super.updateMainHand(session);
}
@Override
public void updateOffHand(GeyserSession session) {
- checkForCrossbow();
+ updateCrossbow();
super.updateOffHand(session);
}
@@ -56,12 +65,27 @@ public class PillagerEntity extends AbstractIllagerEntity {
/**
* Check for a crossbow in either the mainhand or offhand. If one exists, indicate that the pillager should be posing
*/
- protected void checkForCrossbow() {
+ protected void updateCrossbow() {
ItemMapping crossbow = session.getItemMappings().getStoredItems().crossbow();
- boolean hasCrossbow = this.hand.getDefinition() == crossbow.getBedrockDefinition()
- || this.offHand.getDefinition() == crossbow.getBedrockDefinition();
- setFlag(EntityFlag.USING_ITEM, hasCrossbow);
- setFlag(EntityFlag.CHARGED, hasCrossbow);
+ ItemData activeCrossbow = null;
+ if (this.hand.getDefinition() == crossbow.getBedrockDefinition()) {
+ activeCrossbow = this.hand;
+ } else if (this.offhand.getDefinition() == crossbow.getBedrockDefinition()) {
+ activeCrossbow = this.offhand;
+ }
+
+ if (activeCrossbow != null) {
+ if (activeCrossbow.getTag() != null && activeCrossbow.getTag().containsKey("chargedItem")) {
+ dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, Byte.MAX_VALUE);
+ setFlag(EntityFlag.CHARGING, false);
+ setFlag(EntityFlag.CHARGED, true);
+ setFlag(EntityFlag.USING_ITEM, true);
+ } else if (getFlag(EntityFlag.CHARGED)) {
+ dirtyMetadata.put(EntityDataTypes.CHARGE_AMOUNT, (byte) 0);
+ setFlag(EntityFlag.CHARGED, false);
+ setFlag(EntityFlag.USING_ITEM, false);
+ }
+ }
updateBedrockMetadata();
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/RavagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/RavagerEntity.java
new file mode 100644
index 000000000..6190bae10
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/RavagerEntity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.entity.type.living.monster.raid;
+
+import org.cloudburstmc.math.vector.Vector3f;
+import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
+import org.geysermc.geyser.entity.EntityDefinition;
+import org.geysermc.geyser.session.GeyserSession;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+public class RavagerEntity extends RaidParticipantEntity {
+
+ public RavagerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
+ super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
+ }
+
+ @Override
+ public boolean useArmSwingAttack() {
+ setFlag(EntityFlag.DELAYED_ATTACK, false);
+ updateBedrockMetadata();
+
+ session.scheduleInEventLoop(() -> {
+ setFlag(EntityFlag.DELAYED_ATTACK, true);
+ updateBedrockMetadata();
+ }, 75, TimeUnit.MILLISECONDS);
+
+ return true;
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/SpellcasterIllagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/SpellcasterIllagerEntity.java
index f083437ae..8d4b3c44e 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/SpellcasterIllagerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/SpellcasterIllagerEntity.java
@@ -25,13 +25,13 @@
package org.geysermc.geyser.entity.type.living.monster.raid;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import java.util.UUID;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/VindicatorEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/VindicatorEntity.java
index ad99dda50..a2557e75a 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/VindicatorEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/VindicatorEntity.java
@@ -25,11 +25,12 @@
package org.geysermc.geyser.entity.type.living.monster.raid;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
+import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import java.util.UUID;
@@ -37,6 +38,7 @@ public class VindicatorEntity extends AbstractIllagerEntity {
public VindicatorEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
+ dirtyMetadata.put(EntityDataTypes.TARGET_EID, session.getPlayerEntity().getGeyserId());
}
@Override
@@ -46,4 +48,9 @@ public class VindicatorEntity extends AbstractIllagerEntity {
byte xd = entityMetadata.getPrimitiveValue();
setFlag(EntityFlag.ANGRY, (xd & 4) == 4);
}
+
+ @Override
+ public boolean useArmSwingAttack() {
+ return true;
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java
index 7504db1b1..4c67b882f 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java
@@ -25,28 +25,27 @@
package org.geysermc.geyser.entity.type.player;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition;
-import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
-import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
-import org.cloudburstmc.protocol.bedrock.data.*;
+import org.cloudburstmc.nbt.NbtMap;
+import org.cloudburstmc.nbt.NbtMapBuilder;
+import org.cloudburstmc.protocol.bedrock.data.Ability;
+import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
+import org.cloudburstmc.protocol.bedrock.data.GameType;
+import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
+import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.packet.*;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
import org.geysermc.geyser.entity.EntityDefinitions;
+import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity;
@@ -55,8 +54,21 @@ import org.geysermc.geyser.scoreboard.Score;
import org.geysermc.geyser.scoreboard.Team;
import org.geysermc.geyser.scoreboard.UpdateType;
import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.ChunkUtils;
+import org.geysermc.mcprotocollib.protocol.codec.NbtComponentSerializer;
+import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.BlankFormat;
+import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.FixedFormat;
+import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.NumberFormat;
+import org.geysermc.mcprotocollib.protocol.data.game.chat.numbers.StyledFormat;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
+import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor;
import java.util.Collections;
import java.util.List;
@@ -143,6 +155,10 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
addPlayerPacket.setGameType(GameType.SURVIVAL); //TODO
addPlayerPacket.setAbilityLayers(BASE_ABILITY_LAYER); // Recommended to be added since 1.19.10, but only needed here for permissions viewing
addPlayerPacket.getMetadata().putFlags(flags);
+
+ // Since 1.20.60, the nametag does not show properly if this is not set :/
+ // The nametag does disappear properly when the player is invisible though.
+ dirtyMetadata.put(EntityDataTypes.NAMETAG_ALWAYS_SHOW, (byte) 1);
dirtyMetadata.apply(addPlayerPacket.getMetadata());
setFlagsDirty(false);
@@ -151,6 +167,31 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
session.sendUpstreamPacket(addPlayerPacket);
}
+ @Override
+ public void despawnEntity() {
+ super.despawnEntity();
+
+ // Since we re-use player entities: Clear flags, held item, etc
+ this.resetMetadata();
+ this.hand = ItemData.AIR;
+ this.offhand = ItemData.AIR;
+ this.boots = ItemData.AIR;
+ this.leggings = ItemData.AIR;
+ this.chestplate = ItemData.AIR;
+ this.helmet = ItemData.AIR;
+ }
+
+ public void resetMetadata() {
+ // Reset all metadata to their default values
+ // This is used when a player respawns
+ this.flags.clear();
+ this.initializeMetadata();
+
+ // Explicitly reset all metadata not handled by initializeMetadata
+ setParrot(null, true);
+ setParrot(null, false);
+ }
+
public void sendPlayer() {
if (session.getEntityCache().getPlayerEntity(uuid) == null)
return;
@@ -271,7 +312,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
attributesPacket.setRuntimeEntityId(geyserId);
// Setting to a higher maximum since plugins/datapacks can probably extend the Bedrock soft limit
attributesPacket.setAttributes(Collections.singletonList(
- new AttributeData("minecraft:absorption", 0.0f, 1024f, entityMetadata.getPrimitiveValue(), 0.0f)));
+ GeyserAttributeType.ABSORPTION.getAttribute(entityMetadata.getPrimitiveValue())));
session.sendUpstreamPacket(attributesPacket);
}
@@ -283,11 +324,11 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
dirtyMetadata.put(EntityDataTypes.MARK_VARIANT, ~entityMetadata.getPrimitiveValue() & 0xff);
}
- public void setLeftParrot(EntityMetadata entityMetadata) {
+ public void setLeftParrot(EntityMetadata entityMetadata) {
setParrot(entityMetadata.getValue(), true);
}
- public void setRightParrot(EntityMetadata entityMetadata) {
+ public void setRightParrot(EntityMetadata entityMetadata) {
setParrot(entityMetadata.getValue(), false);
}
@@ -295,7 +336,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
* Sets the parrot occupying the shoulder. Bedrock Edition requires a full entity whereas Java Edition just
* spawns it from the NBT data provided
*/
- private void setParrot(CompoundTag tag, boolean isLeft) {
+ protected void setParrot(NbtMap tag, boolean isLeft) {
if (tag != null && !tag.isEmpty()) {
if ((isLeft && leftParrot != null) || (!isLeft && rightParrot != null)) {
// No need to update a parrot's data when it already exists
@@ -305,7 +346,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
ParrotEntity parrot = new ParrotEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(),
null, EntityDefinitions.PARROT, position, motion, getYaw(), getPitch(), getHeadYaw());
parrot.spawnEntity();
- parrot.getDirtyMetadata().put(EntityDataTypes.VARIANT, (Integer) tag.get("Variant").getValue());
+ parrot.getDirtyMetadata().put(EntityDataTypes.VARIANT, (Integer) tag.get("Variant"));
// Different position whether the parrot is left or right
float offset = isLeft ? 0.4f : -0.4f;
parrot.getDirtyMetadata().put(EntityDataTypes.SEAT_OFFSET, Vector3f.from(offset, -0.22, -0.1));
@@ -402,14 +443,36 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
public void setBelowNameText(Objective objective) {
if (objective != null && objective.getUpdateType() != UpdateType.REMOVE) {
- int amount;
Score score = objective.getScores().get(username);
+ String numberString;
+ NumberFormat numberFormat;
+ int amount;
if (score != null) {
- amount = score.getCurrentData().getScore();
+ amount = score.getScore();
+ numberFormat = score.getNumberFormat();
+ if (numberFormat == null) {
+ numberFormat = objective.getNumberFormat();
+ }
} else {
amount = 0;
+ numberFormat = objective.getNumberFormat();
}
- String displayString = amount + " " + objective.getDisplayName();
+
+ if (numberFormat instanceof BlankFormat) {
+ numberString = "";
+ } else if (numberFormat instanceof FixedFormat fixedFormat) {
+ numberString = MessageTranslator.convertMessage(fixedFormat.getValue());
+ } else if (numberFormat instanceof StyledFormat styledFormat) {
+ NbtMapBuilder styledAmount = styledFormat.getStyle().toBuilder();
+ styledAmount.putString("text", String.valueOf(amount));
+
+ numberString = MessageTranslator.convertJsonMessage(
+ NbtComponentSerializer.tagComponentToJson(styledAmount.build()).toString(), session.locale());
+ } else {
+ numberString = String.valueOf(amount);
+ }
+
+ String displayString = numberString + " " + ChatColor.RESET + objective.getDisplayName();
if (valid) {
// Already spawned - we still need to run the rest of this code because the spawn packet will be
@@ -418,13 +481,22 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
packet.setRuntimeEntityId(geyserId);
packet.getMetadata().put(EntityDataTypes.SCORE, displayString);
session.sendUpstreamPacket(packet);
+ } else {
+ // Not spawned yet, store score value in dirtyMetadata to be picked up by #spawnEntity
+ dirtyMetadata.put(EntityDataTypes.SCORE, displayString);
+ }
+ } else {
+ if (valid) {
+ SetEntityDataPacket packet = new SetEntityDataPacket();
+ packet.setRuntimeEntityId(geyserId);
+ packet.getMetadata().put(EntityDataTypes.SCORE, "");
+ session.sendUpstreamPacket(packet);
+ } else {
+ // Not spawned yet, store score value in dirtyMetadata to be picked up by #spawnEntity
+ dirtyMetadata.put(EntityDataTypes.SCORE, "");
}
- } else if (valid) {
- SetEntityDataPacket packet = new SetEntityDataPacket();
- packet.setRuntimeEntityId(geyserId);
- packet.getMetadata().put(EntityDataTypes.SCORE, "");
- session.sendUpstreamPacket(packet);
}
+
}
/**
@@ -433,4 +505,9 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
public UUID getTabListUuid() {
return getUuid();
}
+
+ @Override
+ public Vector3f position() {
+ return this.position.clone();
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
index 751a24871..31eb02984 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
@@ -25,12 +25,6 @@
package org.geysermc.geyser.entity.type.player;
-import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute;
-import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.GlobalPos;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
-import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -41,9 +35,17 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.item.Items;
+import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.AttributeUtils;
import org.geysermc.geyser.util.DimensionUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.GlobalPos;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import java.util.Collections;
import java.util.List;
@@ -59,16 +61,14 @@ public class SessionPlayerEntity extends PlayerEntity {
*/
@Getter
protected final Map attributes = new Object2ObjectOpenHashMap<>();
- /**
- * Whether to check for updated speed after all entity metadata has been processed
- */
- private boolean refreshSpeed = false;
/**
* Used in PlayerInputTranslator for movement checks.
*/
@Getter
private boolean isRidingInFront;
+ private int lastAirSupply = getMaxAir();
+
public SessionPlayerEntity(GeyserSession session) {
super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null);
@@ -119,9 +119,7 @@ public class SessionPlayerEntity extends PlayerEntity {
// TODO: proper fix, BDS somehow does it? https://paste.gg/p/anonymous/3adfb7612f1540be80fa03a2281f93dc (BDS 1.20.13)
if (!this.session.getGameMode().equals(GameMode.SPECTATOR)) {
super.setFlags(entityMetadata);
- session.setSwimmingInWater((entityMetadata.getPrimitiveValue() & 0x10) == 0x10 && getFlag(EntityFlag.SPRINTING));
}
- refreshSpeed = true;
}
/**
@@ -149,7 +147,6 @@ public class SessionPlayerEntity extends PlayerEntity {
public void setPose(Pose pose) {
super.setPose(pose);
session.setPose(pose);
- refreshSpeed = true;
}
public float getMaxHealth() {
@@ -166,7 +163,13 @@ public class SessionPlayerEntity extends PlayerEntity {
@Override
protected void setAirSupply(int amount) {
- if (amount == getMaxAir()) {
+ // Seemingly required to be sent as of Bedrock 1.21. Otherwise, bubbles will appear as empty
+ // Also, this changes how the air bubble graphics/sounds are presented. Breathing on means sound effects and
+ // the bubbles visually pop
+ setFlag(EntityFlag.BREATHING, amount >= this.lastAirSupply);
+ this.lastAirSupply = amount;
+
+ if (amount == getMaxAir() && GameProtocol.isPre1_21_0(session)) {
super.setAirSupply(0); // Hide the bubble counter from the UI for the player
} else {
super.setAirSupply(amount);
@@ -198,21 +201,6 @@ public class SessionPlayerEntity extends PlayerEntity {
}
}
- @Override
- public void updateBedrockMetadata() {
- super.updateBedrockMetadata();
- if (refreshSpeed) {
- AttributeData speedAttribute = session.adjustSpeed();
- if (speedAttribute != null) {
- UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
- attributesPacket.setRuntimeEntityId(geyserId);
- attributesPacket.setAttributes(Collections.singletonList(speedAttribute));
- session.sendUpstreamPacket(attributesPacket);
- }
- refreshSpeed = false;
- }
- }
-
@Override
protected void updateAttribute(Attribute javaAttribute, List newAttributes) {
if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_ATTACK_SPEED) {
@@ -225,17 +213,6 @@ public class SessionPlayerEntity extends PlayerEntity {
@Override
protected AttributeData calculateAttribute(Attribute javaAttribute, GeyserAttributeType type) {
AttributeData attributeData = super.calculateAttribute(javaAttribute, type);
-
- if (javaAttribute.getType() == AttributeType.Builtin.GENERIC_MOVEMENT_SPEED) {
- session.setOriginalSpeedAttribute(attributeData.getValue());
- AttributeData speedAttribute = session.adjustSpeed();
- if (speedAttribute != null) {
- // Overwrite the attribute with our own
- this.attributes.put(type, speedAttribute);
- return speedAttribute;
- }
- }
-
this.attributes.put(type, attributeData);
return attributeData;
}
@@ -243,7 +220,7 @@ public class SessionPlayerEntity extends PlayerEntity {
public void setLastDeathPosition(@Nullable GlobalPos pos) {
if (pos != null) {
dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_POS, pos.getPosition());
- dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_DIMENSION, DimensionUtils.javaToBedrock(pos.getDimension()));
+ dirtyMetadata.put(EntityDataTypes.PLAYER_LAST_DEATH_DIMENSION, DimensionUtils.javaToBedrock(pos.getDimension().asString()));
dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, true);
} else {
dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, false);
@@ -255,13 +232,45 @@ public class SessionPlayerEntity extends PlayerEntity {
return session.getAuthData().uuid();
}
+ @Override
+ public void setAbsorptionHearts(FloatEntityMetadata entityMetadata) {
+ // The bedrock client can glitch when sending a health and absorption attribute in the same tick
+ // This can happen when switching servers. Resending the absorption attribute fixes the issue
+ attributes.put(GeyserAttributeType.ABSORPTION, GeyserAttributeType.ABSORPTION.getAttribute(entityMetadata.getPrimitiveValue()));
+ super.setAbsorptionHearts(entityMetadata);
+ }
+
+ @Override
public void resetMetadata() {
- // Reset all metadata to their default values
- // This is used when a player respawns
- this.initializeMetadata();
+ super.resetMetadata();
// Reset air
this.resetAir();
+
+ // Absorption is metadata in java edition
+ attributes.remove(GeyserAttributeType.ABSORPTION);
+ UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
+ attributesPacket.setRuntimeEntityId(geyserId);
+ attributesPacket.setAttributes(Collections.singletonList(
+ GeyserAttributeType.ABSORPTION.getAttribute(0f)));
+ session.sendUpstreamPacket(attributesPacket);
+
+ dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, 0);
+ dirtyMetadata.put(EntityDataTypes.EFFECT_AMBIENCE, (byte) 0);
+ dirtyMetadata.put(EntityDataTypes.FREEZING_EFFECT_STRENGTH, 0f);
+
+ silent = false;
+ }
+
+ public void resetAttributes() {
+ attributes.clear();
+ maxHealth = GeyserAttributeType.MAX_HEALTH.getDefaultValue();
+
+ UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
+ attributesPacket.setRuntimeEntityId(geyserId);
+ attributesPacket.setAttributes(Collections.singletonList(
+ GeyserAttributeType.MOVEMENT_SPEED.getAttribute()));
+ session.sendUpstreamPacket(attributesPacket);
}
public void resetAir() {
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java
index 939e4721d..1c5060de0 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java
@@ -25,6 +25,7 @@
package org.geysermc.geyser.entity.type.player;
+import lombok.Getter;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.GameType;
@@ -33,8 +34,10 @@ import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.AddPlayerPacket;
-import lombok.Getter;
-import org.geysermc.geyser.level.block.BlockStateValues;
+import org.geysermc.geyser.level.block.property.Properties;
+import org.geysermc.geyser.level.block.type.BlockState;
+import org.geysermc.geyser.level.block.type.WallSkullBlock;
+import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache;
import org.geysermc.geyser.skin.SkullSkinManager;
@@ -137,20 +140,19 @@ public class SkullPlayerEntity extends PlayerEntity {
float z = skull.getPosition().getZ() + .5f;
float rotation;
- int blockState = skull.getBlockState();
- byte floorRotation = BlockStateValues.getSkullRotation(blockState);
- if (floorRotation == -1) {
- // Wall skull
+ BlockState blockState = skull.getBlockState();
+ if (blockState.block() instanceof WallSkullBlock) {
y += 0.25f;
- rotation = BlockStateValues.getSkullWallDirections().get(blockState);
- switch ((int) rotation) {
- case 180 -> z += 0.24f; // North
- case 0 -> z -= 0.24f; // South
- case 90 -> x += 0.24f; // West
- case 270 -> x -= 0.24f; // East
+ Direction direction = blockState.getValue(Properties.HORIZONTAL_FACING);
+ rotation = WallSkullBlock.getDegrees(direction);
+ switch (direction) {
+ case NORTH -> z += 0.24f;
+ case SOUTH -> z -= 0.24f;
+ case WEST -> x += 0.24f;
+ case EAST -> x -= 0.24f;
}
} else {
- rotation = (180f + (floorRotation * 22.5f)) % 360;
+ rotation = (180f + blockState.getValue(Properties.ROTATION_16, 0) * 22.5f) % 360;
}
moveAbsolute(Vector3f.from(x, y, z), rotation, 0, rotation, true, true);
diff --git a/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundPacketHandlerImpl.java b/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundPacketHandlerImpl.java
index 3ae458f63..c8cbe384b 100644
--- a/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundPacketHandlerImpl.java
+++ b/core/src/main/java/org/geysermc/geyser/erosion/GeyserboundPacketHandlerImpl.java
@@ -25,14 +25,13 @@
package org.geysermc.geyser.erosion;
-import com.github.steveice10.mc.protocol.data.game.level.block.value.PistonValueType;
-import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import io.netty.channel.Channel;
+import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import lombok.Getter;
import lombok.Setter;
import org.cloudburstmc.math.vector.Vector3i;
@@ -45,12 +44,16 @@ import org.geysermc.erosion.packet.backendbound.BackendboundInitializePacket;
import org.geysermc.erosion.packet.backendbound.BackendboundPacket;
import org.geysermc.erosion.packet.geyserbound.*;
import org.geysermc.geyser.level.block.BlockStateValues;
+import org.geysermc.geyser.level.block.property.Properties;
+import org.geysermc.geyser.level.block.type.Block;
+import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.physics.Direction;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.PistonCache;
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
import org.geysermc.geyser.util.BlockEntityUtils;
+import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
@@ -64,7 +67,7 @@ public final class GeyserboundPacketHandlerImpl extends AbstractGeyserboundPacke
@Setter
private CompletableFuture pendingBatchLookup = null;
@Setter
- private CompletableFuture pickBlockLookup = null;
+ private CompletableFuture> pickBlockLookup = null;
private final AtomicInteger nextTransactionId = new AtomicInteger(1);
@@ -120,7 +123,7 @@ public final class GeyserboundPacketHandlerImpl extends AbstractGeyserboundPacke
}
CompletableFuture future = this.asyncPendingLookups.remove(transactionId);
if (future != null) {
- future.complete(BlockStateValues.JAVA_AIR_ID);
+ future.complete(Block.JAVA_AIR_ID);
}
}
@@ -134,28 +137,29 @@ public final class GeyserboundPacketHandlerImpl extends AbstractGeyserboundPacke
placeBlockSoundPacket.setIdentifier(":");
session.sendUpstreamPacket(placeBlockSoundPacket);
session.setLastBlockPlacePosition(null);
- session.setLastBlockPlacedId(null);
+ session.setLastBlockPlaced(null);
}
@Override
public void handlePickBlock(GeyserboundPickBlockPacket packet) {
if (this.pickBlockLookup != null) {
- this.pickBlockLookup.complete(packet.getTag());
+ this.pickBlockLookup.complete(packet.getComponents());
}
}
@Override
public void handlePistonEvent(GeyserboundPistonEventPacket packet) {
- Direction orientation = BlockStateValues.getPistonOrientation(packet.getBlockId());
+ Direction orientation = BlockState.of(packet.getBlockId()).getValue(Properties.FACING);
Vector3i position = packet.getPos();
boolean isExtend = packet.isExtend();
var stream = packet.getAttachedBlocks()
.object2IntEntrySet()
.stream()
- .filter(entry -> BlockStateValues.canPistonMoveBlock(entry.getIntValue(), isExtend));
- Object2IntMap attachedBlocks = new Object2IntArrayMap<>();
- stream.forEach(entry -> attachedBlocks.put(entry.getKey(), entry.getIntValue()));
+ .map(entry -> Pair.of(entry.getKey(), BlockState.of(entry.getIntValue())))
+ .filter(pair -> BlockStateValues.canPistonMoveBlock(pair.value(), isExtend));
+ Object2ObjectMap attachedBlocks = new Object2ObjectArrayMap<>();
+ stream.forEach(pair -> attachedBlocks.put(pair.key(), pair.value()));
session.executeInEventLoop(() -> {
PistonCache pistonCache = session.getPistonCache();
diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java
index 716b763f5..239ffc450 100644
--- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java
+++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java
@@ -31,6 +31,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.extension.ExtensionDescription;
import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException;
import org.geysermc.geyser.text.GeyserLocale;
+import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor;
@@ -48,7 +49,7 @@ public record GeyserExtensionDescription(@NonNull String id,
@NonNull String version,
@NonNull List authors) implements ExtensionDescription {
- private static final Yaml YAML = new Yaml(new CustomClassLoaderConstructor(Source.class.getClassLoader()));
+ private static final Yaml YAML = new Yaml(new CustomClassLoaderConstructor(Source.class.getClassLoader(), new LoaderOptions()));
public static final Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}");
public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z_.-]+$");
diff --git a/core/src/main/java/org/geysermc/geyser/impl/MinecraftVersionImpl.java b/core/src/main/java/org/geysermc/geyser/impl/MinecraftVersionImpl.java
new file mode 100644
index 000000000..121d33c64
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/impl/MinecraftVersionImpl.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.impl;
+
+import org.geysermc.geyser.api.util.MinecraftVersion;
+
+public record MinecraftVersionImpl(String versionString, int protocolVersion) implements MinecraftVersion {
+}
diff --git a/core/src/main/java/org/geysermc/geyser/impl/camera/CameraDefinitions.java b/core/src/main/java/org/geysermc/geyser/impl/camera/CameraDefinitions.java
new file mode 100644
index 000000000..80564bdf3
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/impl/camera/CameraDefinitions.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.impl.camera;
+
+import org.cloudburstmc.protocol.bedrock.data.camera.CameraAudioListener;
+import org.cloudburstmc.protocol.bedrock.data.camera.CameraPreset;
+import org.cloudburstmc.protocol.common.DefinitionRegistry;
+import org.cloudburstmc.protocol.common.NamedDefinition;
+import org.cloudburstmc.protocol.common.SimpleDefinitionRegistry;
+import org.cloudburstmc.protocol.common.util.OptionalBoolean;
+import org.geysermc.geyser.api.bedrock.camera.CameraPerspective;
+
+import java.util.List;
+
+public class CameraDefinitions {
+
+ public static final DefinitionRegistry CAMERA_DEFINITIONS;
+
+ public static final List CAMERA_PRESETS;
+
+ static {
+ CAMERA_PRESETS = List.of(
+ new CameraPreset(CameraPerspective.FIRST_PERSON.id(), "", null, null, null, null, OptionalBoolean.empty()),
+ new CameraPreset(CameraPerspective.FREE.id(), "", null, null, null, null, OptionalBoolean.empty()),
+ new CameraPreset(CameraPerspective.THIRD_PERSON.id(), "", null, null, null, null, OptionalBoolean.empty()),
+ new CameraPreset(CameraPerspective.THIRD_PERSON_FRONT.id(), "", null, null, null, null, OptionalBoolean.empty()),
+ new CameraPreset("geyser:free_audio", "minecraft:free", null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(false)),
+ new CameraPreset("geyser:free_effects", "minecraft:free", null, null, null, CameraAudioListener.CAMERA, OptionalBoolean.of(true)),
+ new CameraPreset("geyser:free_audio_effects", "minecraft:free", null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(true)));
+
+ SimpleDefinitionRegistry.Builder builder = SimpleDefinitionRegistry.builder();
+ for (int i = 0; i < CAMERA_PRESETS.size(); i++) {
+ builder.add(CameraDefinition.of(CAMERA_PRESETS.get(i).getIdentifier(), i));
+ }
+ CAMERA_DEFINITIONS = builder.build();
+ }
+
+ public static NamedDefinition getById(int id) {
+ return CAMERA_DEFINITIONS.getDefinition(id);
+ }
+
+ public static NamedDefinition getByFunctionality(boolean audio, boolean effects) {
+ if (!audio && !effects) {
+ return getById(1); // FREE
+ }
+ if (audio) {
+ if (effects) {
+ return getById(6); // FREE_AUDIO_EFFECTS
+ } else {
+ return getById(4); // FREE_AUDIO
+ }
+ } else {
+ return getById(5); // FREE_EFFECTS
+ }
+ }
+
+ public record CameraDefinition(String identifier, int runtimeId) implements NamedDefinition {
+
+ @Override
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public int getRuntimeId() {
+ return runtimeId;
+ }
+
+ public static CameraDefinition of(String identifier, int runtimeId) {
+ return new CameraDefinition(identifier, runtimeId);
+ }
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraData.java b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraData.java
new file mode 100644
index 000000000..7582502b3
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraData.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.impl.camera;
+
+import lombok.Getter;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.cloudburstmc.math.vector.Vector2f;
+import org.cloudburstmc.math.vector.Vector3f;
+import org.cloudburstmc.protocol.bedrock.data.CameraShakeAction;
+import org.cloudburstmc.protocol.bedrock.data.CameraShakeType;
+import org.cloudburstmc.protocol.bedrock.data.HudElement;
+import org.cloudburstmc.protocol.bedrock.data.HudVisibility;
+import org.cloudburstmc.protocol.bedrock.data.camera.CameraEase;
+import org.cloudburstmc.protocol.bedrock.data.camera.CameraFadeInstruction;
+import org.cloudburstmc.protocol.bedrock.data.camera.CameraSetInstruction;
+import org.cloudburstmc.protocol.bedrock.packet.CameraInstructionPacket;
+import org.cloudburstmc.protocol.bedrock.packet.CameraShakePacket;
+import org.cloudburstmc.protocol.bedrock.packet.PlayerFogPacket;
+import org.cloudburstmc.protocol.bedrock.packet.SetHudPacket;
+import org.geysermc.geyser.api.bedrock.camera.CameraData;
+import org.geysermc.geyser.api.bedrock.camera.CameraEaseType;
+import org.geysermc.geyser.api.bedrock.camera.CameraFade;
+import org.geysermc.geyser.api.bedrock.camera.CameraPerspective;
+import org.geysermc.geyser.api.bedrock.camera.CameraPosition;
+import org.geysermc.geyser.api.bedrock.camera.CameraShake;
+import org.geysermc.geyser.api.bedrock.camera.GuiElement;
+import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+public class GeyserCameraData implements CameraData {
+ private static final HudElement[] HUD_ELEMENT_VALUES = HudElement.values();
+ private static final Set ALL_HUD_ELEMENTS = Set.of(HUD_ELEMENT_VALUES);
+
+ /**
+ * An array of elements to hide when the player is in spectator mode.
+ * Helps with tidying up the GUI; Java-style.
+ */
+ private static final GuiElement[] SPECTATOR_HIDDEN_ELEMENTS = {
+ GuiElement.AIR_BUBBLES_BAR,
+ GuiElement.ARMOR,
+ GuiElement.HEALTH,
+ GuiElement.FOOD_BAR,
+ GuiElement.PROGRESS_BAR,
+ GuiElement.TOOL_TIPS
+ };
+
+ private final GeyserSession session;
+
+ /**
+ * All fog effects that are currently applied to the client.
+ */
+ private final Set appliedFog = new HashSet<>();
+
+ private final Set cameraLockOwners = new HashSet<>();
+
+ /**
+ * All currently hidden HUD elements
+ */
+ private final Set hiddenHudElements = new HashSet<>();
+
+ @Getter
+ private CameraPerspective cameraPerspective;
+
+ public GeyserCameraData(GeyserSession session) {
+ this.session = session;
+ }
+
+ @Override
+ public void clearCameraInstructions() {
+ this.cameraPerspective = null;
+ CameraInstructionPacket packet = new CameraInstructionPacket();
+ packet.setClear(true);
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public void forceCameraPerspective(@NonNull CameraPerspective perspective) {
+ Objects.requireNonNull(perspective, "perspective cannot be null!");
+
+ if (perspective == cameraPerspective) {
+ return; // nothing to do
+ }
+
+ this.cameraPerspective = perspective;
+ CameraInstructionPacket packet = new CameraInstructionPacket();
+ CameraSetInstruction setInstruction = new CameraSetInstruction();
+
+ if (perspective == CameraPerspective.FREE) {
+ throw new IllegalArgumentException("Cannot force a stationary camera (CameraPerspective#FREE) on the player!" +
+ "Send a CameraPosition with an exact position instead");
+ }
+
+ setInstruction.setPreset(CameraDefinitions.getById(perspective.ordinal()));
+ packet.setSetInstruction(setInstruction);
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public @Nullable CameraPerspective forcedCameraPerspective() {
+ return this.cameraPerspective;
+ }
+
+ @Override
+ public void sendCameraFade(@NonNull CameraFade fade) {
+ Objects.requireNonNull(fade, "fade cannot be null!");
+ CameraFadeInstruction fadeInstruction = new CameraFadeInstruction();
+ fadeInstruction.setColor(fade.color());
+ fadeInstruction.setTimeData(
+ new CameraFadeInstruction.TimeData(
+ fade.fadeInSeconds(),
+ fade.fadeHoldSeconds(),
+ fade.fadeOutSeconds()
+ )
+ );
+
+ CameraInstructionPacket packet = new CameraInstructionPacket();
+ packet.setFadeInstruction(fadeInstruction);
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public void sendCameraPosition(@NonNull CameraPosition movement) {
+ Objects.requireNonNull(movement, "movement cannot be null!");
+ this.cameraPerspective = CameraPerspective.FREE; // Movements only work with the free preset
+ CameraSetInstruction setInstruction = new CameraSetInstruction();
+
+ CameraEaseType easeType = movement.easeType();
+ if (easeType != null) {
+ setInstruction.setEase(new CameraSetInstruction.EaseData(
+ CameraEase.fromName(easeType.id()),
+ movement.easeSeconds()
+ ));
+ }
+
+ Vector3f facingPosition = movement.facingPosition();
+ if (facingPosition != null) {
+ setInstruction.setFacing(facingPosition);
+ }
+
+ setInstruction.setPos(movement.position());
+ setInstruction.setRot(Vector2f.from(movement.rotationX(), movement.rotationY()));
+ setInstruction.setPreset(CameraDefinitions.getByFunctionality(movement.playerPositionForAudio(), movement.renderPlayerEffects()));
+
+ CameraInstructionPacket packet = new CameraInstructionPacket();
+ packet.setSetInstruction(setInstruction);
+
+ // If present, also send the fade
+ CameraFade fade = movement.cameraFade();
+ if (fade != null) {
+ CameraFadeInstruction fadeInstruction = new CameraFadeInstruction();
+ fadeInstruction.setColor(fade.color());
+ fadeInstruction.setTimeData(
+ new CameraFadeInstruction.TimeData(
+ fade.fadeInSeconds(),
+ fade.fadeHoldSeconds(),
+ fade.fadeOutSeconds()
+ )
+ );
+ packet.setFadeInstruction(fadeInstruction);
+ }
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public void shakeCamera(float intensity, float duration, @NonNull CameraShake type) {
+ Objects.requireNonNull(type, "camera shake type must be non null!");
+ CameraShakePacket packet = new CameraShakePacket();
+ packet.setIntensity(intensity);
+ packet.setDuration(duration);
+ packet.setShakeType(type == CameraShake.POSITIONAL ? CameraShakeType.POSITIONAL : CameraShakeType.ROTATIONAL);
+ packet.setShakeAction(CameraShakeAction.ADD);
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public void stopCameraShake() {
+ CameraShakePacket packet = new CameraShakePacket();
+ // CameraShakeAction.STOP removes all types regardless of the given type, but regardless it can't be null
+ packet.setShakeType(CameraShakeType.POSITIONAL);
+ packet.setShakeAction(CameraShakeAction.STOP);
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public void sendFog(String... fogNameSpaces) {
+ Collections.addAll(this.appliedFog, fogNameSpaces);
+
+ PlayerFogPacket packet = new PlayerFogPacket();
+ packet.getFogStack().addAll(this.appliedFog);
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public void removeFog(String... fogNameSpaces) {
+ if (fogNameSpaces.length == 0) {
+ this.appliedFog.clear();
+ } else {
+ for (String id : fogNameSpaces) {
+ this.appliedFog.remove(id);
+ }
+ }
+ PlayerFogPacket packet = new PlayerFogPacket();
+ packet.getFogStack().addAll(this.appliedFog);
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public @NonNull Set fogEffects() {
+ // Use a copy so that sendFog/removeFog can be called while iterating the returned set (avoid CME)
+ return Set.copyOf(this.appliedFog);
+ }
+
+ @Override
+ public boolean lockCamera(boolean lock, @NonNull UUID owner) {
+ Objects.requireNonNull(owner, "owner cannot be null!");
+ if (lock) {
+ this.cameraLockOwners.add(owner);
+ } else {
+ this.cameraLockOwners.remove(owner);
+ }
+
+ session.lockInputs(isCameraLocked(), session.entities().isMovementLocked());
+ return isCameraLocked();
+ }
+
+ @Override
+ public boolean isCameraLocked() {
+ return !this.cameraLockOwners.isEmpty();
+ }
+
+ @Override
+ public void hideElement(GuiElement... elements) {
+ Objects.requireNonNull(elements);
+ SetHudPacket packet = new SetHudPacket();
+ packet.setVisibility(HudVisibility.HIDE);
+ Set elementSet = packet.getElements();
+
+ for (GuiElement element : elements) {
+ this.hiddenHudElements.add(element);
+ elementSet.add(HUD_ELEMENT_VALUES[element.id()]);
+ }
+
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public void resetElement(GuiElement... elements) {
+ SetHudPacket packet = new SetHudPacket();
+ packet.setVisibility(HudVisibility.RESET);
+ Set elementSet = packet.getElements();
+
+ if (elements != null && elements.length != 0) {
+ for (GuiElement element : elements) {
+ this.hiddenHudElements.remove(element);
+ elementSet.add(HUD_ELEMENT_VALUES[element.id()]);
+ }
+ } else {
+ this.hiddenHudElements.clear();
+ elementSet.addAll(ALL_HUD_ELEMENTS);
+ }
+
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public boolean isHudElementHidden(@NonNull GuiElement element) {
+ Objects.requireNonNull(element);
+ return this.hiddenHudElements.contains(element);
+ }
+
+ @Override
+ public @NonNull Set hiddenElements() {
+ return Collections.unmodifiableSet(hiddenHudElements);
+ }
+
+ /**
+ * Deals with hiding hud elements while in spectator.
+ *
+ * @param currentlySpectator whether the player is currently in spectator mode
+ * @param newGameMode the new GameMode to switch to
+ */
+ public void handleGameModeChange(boolean currentlySpectator, GameMode newGameMode) {
+ if (newGameMode == GameMode.SPECTATOR) {
+ if (!currentlySpectator) {
+ hideElement(SPECTATOR_HIDDEN_ELEMENTS);
+ }
+ } else {
+ if (currentlySpectator) {
+ resetElement(SPECTATOR_HIDDEN_ELEMENTS);
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraFade.java b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraFade.java
new file mode 100644
index 000000000..f69504545
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraFade.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.impl.camera;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.value.qual.IntRange;
+import org.geysermc.geyser.api.bedrock.camera.CameraFade;
+
+import java.awt.*;
+import java.util.Objects;
+
+public record GeyserCameraFade(
+ Color color,
+ float fadeInSeconds,
+ float fadeHoldSeconds,
+ float fadeOutSeconds
+
+) implements CameraFade {
+ public static class Builder implements CameraFade.Builder {
+ private Color color;
+ private float fadeInSeconds;
+ private float fadeHoldSeconds;
+ private float fadeOutSeconds;
+
+ @Override
+ public CameraFade.Builder color(@NonNull Color color) {
+ Objects.requireNonNull(color, "color cannot be null!");
+ this.color = color;
+ return this;
+ }
+
+ @Override
+ public CameraFade.Builder fadeInSeconds(@IntRange(from = 0, to = 10) float fadeInSeconds) {
+ if (fadeInSeconds < 0f) {
+ throw new IllegalArgumentException("Fade in seconds must be at least 0 seconds");
+ }
+
+ if (fadeInSeconds > 10f) {
+ throw new IllegalArgumentException("Fade in seconds must be at most 10 seconds");
+ }
+ this.fadeInSeconds = fadeInSeconds;
+ return this;
+ }
+
+ @Override
+ public CameraFade.Builder fadeHoldSeconds(@IntRange(from = 0, to = 10) float fadeHoldSeconds) {
+ if (fadeHoldSeconds < 0f) {
+ throw new IllegalArgumentException("Fade hold seconds must be at least 0 seconds");
+ }
+
+ if (fadeHoldSeconds > 10f) {
+ throw new IllegalArgumentException("Fade hold seconds must be at most 10 seconds");
+ }
+ this.fadeHoldSeconds = fadeHoldSeconds;
+ return this;
+ }
+
+ @Override
+ public CameraFade.Builder fadeOutSeconds(@IntRange(from = 0, to = 10) float fadeOutSeconds) {
+ if (fadeOutSeconds < 0f) {
+ throw new IllegalArgumentException("Fade out seconds must be at least 0 seconds");
+ }
+
+ if (fadeOutSeconds > 10f) {
+ throw new IllegalArgumentException("Fade out seconds must be at most 10 seconds");
+ }
+ this.fadeOutSeconds = fadeOutSeconds;
+ return this;
+ }
+
+ @Override
+ public CameraFade build() {
+ Objects.requireNonNull(color, "color must be non null!");
+ if (fadeInSeconds + fadeHoldSeconds + fadeOutSeconds < 0.5f) {
+ throw new IllegalArgumentException("Total fade time (in, hold, out) must be at least 0.5 seconds");
+ }
+
+ return new GeyserCameraFade(color, fadeInSeconds, fadeHoldSeconds, fadeOutSeconds);
+ }
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraPosition.java b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraPosition.java
new file mode 100644
index 000000000..13b382465
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraPosition.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.impl.camera;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.value.qual.IntRange;
+import org.cloudburstmc.math.vector.Vector3f;
+import org.geysermc.geyser.api.bedrock.camera.CameraEaseType;
+import org.geysermc.geyser.api.bedrock.camera.CameraFade;
+import org.geysermc.geyser.api.bedrock.camera.CameraPosition;
+
+import java.util.Objects;
+
+public record GeyserCameraPosition(CameraFade cameraFade,
+ boolean renderPlayerEffects,
+ boolean playerPositionForAudio,
+ CameraEaseType easeType,
+ float easeSeconds,
+ Vector3f position,
+ @IntRange(from = -90, to = 90) int rotationX,
+ int rotationY,
+ Vector3f facingPosition
+) implements CameraPosition {
+
+ public static class Builder implements CameraPosition.Builder {
+ private CameraFade cameraFade;
+ private boolean renderPlayerEffects;
+ private boolean playerPositionForAudio;
+ private CameraEaseType easeType;
+ private float easeSeconds;
+ private Vector3f position;
+ private @IntRange(from = -90, to = 90) int rotationX;
+ private int rotationY;
+ private Vector3f facingPosition;
+
+ @Override
+ public CameraPosition.Builder cameraFade(@Nullable CameraFade cameraFade) {
+ this.cameraFade = cameraFade;
+ return this;
+ }
+
+ @Override
+ public CameraPosition.Builder renderPlayerEffects(boolean renderPlayerEffects) {
+ this.renderPlayerEffects = renderPlayerEffects;
+ return this;
+ }
+
+ @Override
+ public CameraPosition.Builder playerPositionForAudio(boolean playerPositionForAudio) {
+ this.playerPositionForAudio = playerPositionForAudio;
+ return this;
+ }
+
+ @Override
+ public CameraPosition.Builder easeType(@Nullable CameraEaseType easeType) {
+ this.easeType = easeType;
+ return this;
+ }
+
+ @Override
+ public CameraPosition.Builder easeSeconds(float easeSeconds) {
+ if (easeSeconds < 0) {
+ throw new IllegalArgumentException("Camera ease duration cannot be negative!");
+ }
+ this.easeSeconds = easeSeconds;
+ return this;
+ }
+
+ @Override
+ public CameraPosition.Builder position(@NonNull Vector3f position) {
+ Objects.requireNonNull(position, "camera position cannot be null!");
+ this.position = position;
+ return this;
+ }
+
+ @Override
+ public CameraPosition.Builder rotationX(int rotationX) {
+ if (rotationX < -90 || rotationX > 90) {
+ throw new IllegalArgumentException("x-axis rotation needs to be between -90 and 90 degrees.");
+ }
+ this.rotationX = rotationX;
+ return this;
+ }
+
+ @Override
+ public CameraPosition.Builder rotationY(int rotationY) {
+ this.rotationY = rotationY;
+ return this;
+ }
+
+ @Override
+ public CameraPosition.Builder facingPosition(@Nullable Vector3f facingPosition) {
+ this.facingPosition = facingPosition;
+ return this;
+ }
+
+ @Override
+ public CameraPosition build() {
+ if (easeSeconds > 0 && easeType == null) {
+ throw new IllegalArgumentException("Camera ease type cannot be null if ease duration is greater than 0");
+ }
+
+ Objects.requireNonNull(position, "camera position must be non null!");
+ return new GeyserCameraPosition(cameraFade, renderPlayerEffects, playerPositionForAudio, easeType, easeSeconds, position, rotationX, rotationY, facingPosition);
+ }
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java
index 9e0b83768..45a062468 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java
@@ -25,8 +25,9 @@
package org.geysermc.geyser.inventory;
-import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
-import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket;
+import net.kyori.adventure.text.Component;
+import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
+import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket;
import lombok.Getter;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -72,9 +73,9 @@ public class AnvilContainer extends Container {
String correctRename;
newName = rename;
- String originalName = ItemUtils.getCustomName(getInput().getNbt());
+ Component originalName = ItemUtils.getCustomName(getInput().getComponents());
- String plainOriginalName = MessageTranslator.convertToPlainTextLenient(originalName, session.locale());
+ String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.locale());
String plainNewName = MessageTranslator.convertToPlainText(rename);
if (!plainOriginalName.equals(plainNewName)) {
// Strip out formatting since Java Edition does not allow it
@@ -84,7 +85,7 @@ public class AnvilContainer extends Container {
session.sendDownstreamGamePacket(renameItemPacket);
} else {
// Restore formatting for item since we're not renaming
- correctRename = MessageTranslator.convertMessageLenient(originalName);
+ correctRename = originalName != null ? MessageTranslator.convertMessage(originalName, session.locale()) : "";
// Java Edition sends the original custom name when not renaming,
// if there isn't a custom name an empty string is sent
ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainOriginalName);
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/BeaconContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/BeaconContainer.java
index 7644ada73..1b59772fa 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/BeaconContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/BeaconContainer.java
@@ -25,7 +25,7 @@
package org.geysermc.geyser.inventory;
-import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
+import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import lombok.Getter;
import lombok.Setter;
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/CartographyContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/CartographyContainer.java
index 72f1088c3..ace3f93ad 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/CartographyContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/CartographyContainer.java
@@ -25,7 +25,7 @@
package org.geysermc.geyser.inventory;
-import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
+import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
public class CartographyContainer extends Container {
public CartographyContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Container.java b/core/src/main/java/org/geysermc/geyser/inventory/Container.java
index 79fa67da1..e78a4d2c6 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/Container.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/Container.java
@@ -25,7 +25,8 @@
package org.geysermc.geyser.inventory;
-import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
+import org.geysermc.geyser.level.block.type.Block;
+import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.session.GeyserSession;
@@ -37,7 +38,7 @@ import org.jetbrains.annotations.Range;
*/
@Getter
public class Container extends Inventory {
- private final PlayerInventory playerInventory;
+ protected final PlayerInventory playerInventory;
private final int containerSize;
/**
@@ -83,9 +84,9 @@ public class Container extends Inventory {
* Will be overwritten for droppers.
*
* @param usingRealBlock whether this container is using a real container or not
- * @param javaBlockId the Java block string of the block, if real
+ * @param block the Java block, if real
*/
- public void setUsingRealBlock(boolean usingRealBlock, String javaBlockId) {
+ public void setUsingRealBlock(boolean usingRealBlock, Block block) {
isUsingRealBlock = usingRealBlock;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java
index bcacd3587..fb118252d 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java
@@ -25,13 +25,19 @@
package org.geysermc.geyser.inventory;
-import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.translator.inventory.CrafterInventoryTranslator;
+import org.geysermc.geyser.translator.inventory.InventoryTranslator;
+import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.GeyserImpl;
+import org.jetbrains.annotations.Range;
@Getter
public class CrafterContainer extends Container {
+ private GeyserItemStack resultItem = GeyserItemStack.EMPTY;
@Setter
private boolean triggered = false;
@@ -46,8 +52,36 @@ public class CrafterContainer extends Container {
super(title, id, size, containerType, playerInventory);
}
+ @Override
+ public GeyserItemStack getItem(int slot) {
+ if (slot == CrafterInventoryTranslator.JAVA_RESULT_SLOT) {
+ return this.resultItem;
+ } else if (isCraftingGrid(slot)) {
+ return super.getItem(slot);
+ } else {
+ return playerInventory.getItem(slot - CrafterInventoryTranslator.GRID_SIZE + InventoryTranslator.PLAYER_INVENTORY_OFFSET);
+ }
+ }
+
+ @Override
+ public int getOffsetForHotbar(@Range(from = 0, to = 8) int slot) {
+ return playerInventory.getOffsetForHotbar(slot) - InventoryTranslator.PLAYER_INVENTORY_OFFSET + CrafterInventoryTranslator.GRID_SIZE;
+ }
+
+ @Override
+ public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
+ if (slot == CrafterInventoryTranslator.JAVA_RESULT_SLOT) {
+ // Result item probably won't be an item that needs to worry about net ID or lodestone compasses
+ this.resultItem = newItem;
+ } else if (isCraftingGrid(slot)) {
+ super.setItem(slot, newItem, session);
+ } else {
+ playerInventory.setItem(slot - CrafterInventoryTranslator.GRID_SIZE + InventoryTranslator.PLAYER_INVENTORY_OFFSET, newItem, session);
+ }
+ }
+
public void setSlot(int slot, boolean enabled) {
- if (slot < 0 || slot > 8) {
+ if (!isCraftingGrid(slot)) {
GeyserImpl.getInstance().getLogger().warning("Crafter slot out of bounds: " + slot);
return;
}
@@ -58,4 +92,8 @@ public class CrafterContainer extends Container {
disabledSlotsMask = (short) (disabledSlotsMask | (1 << slot));
}
}
+
+ private static boolean isCraftingGrid(int slot) {
+ return slot >= 0 && slot <= 8;
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/EnchantingContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/EnchantingContainer.java
index ac55aae60..08397ab44 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/EnchantingContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/EnchantingContainer.java
@@ -25,7 +25,7 @@
package org.geysermc.geyser.inventory;
-import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
+import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.data.inventory.EnchantOptionData;
import lombok.Getter;
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Generic3X3Container.java b/core/src/main/java/org/geysermc/geyser/inventory/Generic3X3Container.java
index 6518dce7c..0b14d1105 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/Generic3X3Container.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/Generic3X3Container.java
@@ -25,10 +25,12 @@
package org.geysermc.geyser.inventory;
-import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
import lombok.Getter;
+import org.geysermc.geyser.level.block.Blocks;
+import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.Generic3X3InventoryTranslator;
+import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
public class Generic3X3Container extends Container {
/**
@@ -44,10 +46,10 @@ public class Generic3X3Container extends Container {
}
@Override
- public void setUsingRealBlock(boolean usingRealBlock, String javaBlockId) {
- super.setUsingRealBlock(usingRealBlock, javaBlockId);
+ public void setUsingRealBlock(boolean usingRealBlock, Block block) {
+ super.setUsingRealBlock(usingRealBlock, block);
if (usingRealBlock) {
- isDropper = javaBlockId.startsWith("minecraft:dropper");
+ isDropper = block == Blocks.DROPPER;
}
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/GeyserEnchantOption.java b/core/src/main/java/org/geysermc/geyser/inventory/GeyserEnchantOption.java
index 23365e392..de0bd7300 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/GeyserEnchantOption.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/GeyserEnchantOption.java
@@ -25,12 +25,11 @@
package org.geysermc.geyser.inventory;
+import lombok.Getter;
import org.cloudburstmc.protocol.bedrock.data.inventory.EnchantData;
import org.cloudburstmc.protocol.bedrock.data.inventory.EnchantOptionData;
-import lombok.Getter;
import org.geysermc.geyser.session.GeyserSession;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -44,13 +43,13 @@ public class GeyserEnchantOption {
* is controlled by the server.
* So, of course, we have to throw in some easter eggs. ;)
*/
- private static final List ENCHANT_NAMES = Arrays.asList("tougher armor", "lukeeey", "fall better",
- "explode less", "camo toy", "breathe better", "rtm five one six", "armor stab", "water walk", "you are elsa",
- "tim two zero three", "fast walk nether", "davchoo", "oof ouch owie", "enemy on fire", "spider sad", "aj ferguson", "redned",
- "more items thx", "long sword reach", "fast tool", "give me block", "less breaky break", "cube craft",
- "strong arrow", "fist arrow", "spicy arrow", "many many arrows", "geyser", "come here fish", "i like this",
- "stabby stab", "supreme mortal", "avatar i guess", "more arrows", "fly finder seventeen", "in and out",
- "xp heals tools", "dragon proxy waz here");
+ private static final List ENCHANT_NAMES = List.of("tougher armor", "lukeeey", "fall better",
+ "explode less", "camo toy", "armor stab", "breathe better", "water walk", "rtm five one six", "oof ouch owie",
+ "enemy on fire", "spider sad", "aj ferguson", "redned", "more items thx", "fast tool", "give me block",
+ "less breaky break", "cube craft", "strong arrow", "fist arrow", "spicy arrow", "many many arrows", "geyser",
+ "come here fish", "you are elsa", "xp heals tools", "tim two zero three", "dragon proxy waz here",
+ "stabby stab", "supreme mortal", "i like this", "avatar i guess", "more arrows", "in and out",
+ "fly finder seventeen", "fast walk nether", "davchoo", "onechris", "death bringer thirteen", "kastle");
@Getter
private final int javaIndex;
@@ -62,7 +61,6 @@ public class GeyserEnchantOption {
private boolean hasChanged;
private int xpCost = 0;
- private int javaEnchantIndex = -1;
private int bedrockEnchantIndex = -1;
private int enchantLevel = -1;
@@ -74,7 +72,7 @@ public class GeyserEnchantOption {
this.hasChanged = false;
return new EnchantOptionData(xpCost, javaIndex + 16, EMPTY,
enchantLevel == -1 ? EMPTY : Collections.singletonList(new EnchantData(bedrockEnchantIndex, enchantLevel)), EMPTY,
- javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), enchantLevel == -1 ? 0 : session.getNextItemNetId());
+ bedrockEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(bedrockEnchantIndex), enchantLevel == -1 ? 0 : session.getNextItemNetId());
}
public boolean hasChanged() {
@@ -88,10 +86,9 @@ public class GeyserEnchantOption {
}
}
- public void setEnchantIndex(int javaEnchantIndex, int bedrockEnchantIndex) {
- if (this.javaEnchantIndex != javaEnchantIndex) {
+ public void setEnchantIndex(int bedrockEnchantIndex) {
+ if (this.bedrockEnchantIndex != bedrockEnchantIndex) {
hasChanged = true;
- this.javaEnchantIndex = javaEnchantIndex;
this.bedrockEnchantIndex = bedrockEnchantIndex;
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java
index 4ff8db9f0..744ad70b6 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java
@@ -25,12 +25,7 @@
package org.geysermc.geyser.inventory;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
-import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
-import lombok.AccessLevel;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
+import lombok.*;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
@@ -39,7 +34,12 @@ import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
+import org.geysermc.geyser.translator.item.ItemTranslator;
+import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
+import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
+import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
+
+import java.util.HashMap;
@Data
public class GeyserItemStack {
@@ -47,26 +47,34 @@ public class GeyserItemStack {
private final int javaId;
private int amount;
- private CompoundTag nbt;
+ private DataComponents components;
private int netId;
- @Getter(AccessLevel.NONE)
+ @Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
private Item item;
- private GeyserItemStack(int javaId, int amount, CompoundTag nbt) {
- this(javaId, amount, nbt, 1);
+ private GeyserItemStack(int javaId, int amount, DataComponents components) {
+ this(javaId, amount, components, 1);
}
- private GeyserItemStack(int javaId, int amount, CompoundTag nbt, int netId) {
+ private GeyserItemStack(int javaId, int amount, DataComponents components, int netId) {
this.javaId = javaId;
this.amount = amount;
- this.nbt = nbt;
+ this.components = components;
this.netId = netId;
}
+ public static @NonNull GeyserItemStack of(int javaId, int amount) {
+ return of(javaId, amount, null);
+ }
+
+ public static @NonNull GeyserItemStack of(int javaId, int amount, @Nullable DataComponents components) {
+ return new GeyserItemStack(javaId, amount, components);
+ }
+
public static @NonNull GeyserItemStack from(@Nullable ItemStack itemStack) {
- return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getNbt());
+ return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getDataComponents());
}
public int getJavaId() {
@@ -77,8 +85,48 @@ public class GeyserItemStack {
return isEmpty() ? 0 : amount;
}
- public @Nullable CompoundTag getNbt() {
- return isEmpty() ? null : nbt;
+ public @Nullable DataComponents getComponents() {
+ return isEmpty() ? null : components;
+ }
+
+ @NonNull
+ public DataComponents getOrCreateComponents() {
+ if (components == null) {
+ return components = new DataComponents(new HashMap<>());
+ }
+ return components;
+ }
+
+ @Nullable
+ public T getComponent(@NonNull DataComponentType type) {
+ if (components == null) {
+ return null;
+ }
+ return components.get(type);
+ }
+
+ public boolean getComponent(@NonNull DataComponentType type, boolean def) {
+ if (components == null) {
+ return def;
+ }
+
+ Boolean result = components.get(type);
+ if (result != null) {
+ return result;
+ }
+ return def;
+ }
+
+ public int getComponent(@NonNull DataComponentType