supportedBedrockVersions();
+
+ /**
+ * Gets the {@link CommandSource} for the console.
+ *
+ * @return the console command source
+ */
+ @NonNull
+ CommandSource consoleCommandSource();
+
/**
* Gets the current {@link GeyserApiBase} instance.
*
@@ -142,4 +170,14 @@ public interface GeyserApi extends GeyserApiBase {
static GeyserApi api() {
return Geyser.api(GeyserApi.class);
}
+
+ /**
+ * Returns the {@link ApiVersion} representing the current Geyser api version.
+ * See the Geyser version outline)
+ *
+ * @return the current geyser api version
+ */
+ default ApiVersion geyserApiVersion() {
+ return BuildData.API_VERSION;
+ }
}
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/core/src/main/java/org/geysermc/geyser/level/block/DoubleChestValue.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPerspective.java
similarity index 63%
rename from core/src/main/java/org/geysermc/geyser/level/block/DoubleChestValue.java
rename to api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPerspective.java
index 8024af650..4167f2a34 100644
--- a/core/src/main/java/org/geysermc/geyser/level/block/DoubleChestValue.java
+++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPerspective.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,23 +23,26 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.level.block;
+package org.geysermc.geyser.api.bedrock.camera;
/**
- * This stores all values of double chests that are part of the Java block state.
+ * 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 record DoubleChestValue(
- /**
- * If true, then chest is facing east/west; if false, south/north
- */
- boolean isFacingEast,
- /**
- * If true, direction is positive (east/south); if false, direction is negative (west/north)
- */
- boolean isDirectionPositive,
- /**
- * If true, chest is the left of a pair; if false, chest is the right of a pair.
- */
- boolean isLeft) {
+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 18bafb428..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,7 +25,10 @@
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;
+ 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 036723092..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,11 +184,15 @@ 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);
- Builder tags(Set tags);
+ Builder tags(@Nullable Set tags);
CustomBlockComponents build();
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/block/custom/component/PlacementConditions.java b/api/src/main/java/org/geysermc/geyser/api/block/custom/component/PlacementConditions.java
index d06d9a967..e274fd8bd 100644
--- a/api/src/main/java/org/geysermc/geyser/api/block/custom/component/PlacementConditions.java
+++ b/api/src/main/java/org/geysermc/geyser/api/block/custom/component/PlacementConditions.java
@@ -25,11 +25,11 @@
package org.geysermc.geyser.api.block.custom.component;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
import java.util.LinkedHashMap;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.NonNull;
-
/**
* This class is used to store conditions for a placement filter for a custom block.
*
@@ -43,7 +43,7 @@ public record PlacementConditions(@NonNull Set allowedFaces, @NonNull Link
NORTH,
SOUTH,
WEST,
- EAST;
+ EAST
}
public enum BlockFilterType {
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 6293506a8..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
@@ -39,7 +39,7 @@ public interface JavaBlockState {
*
* @return whether the block state is waterlogged
*/
- @NonNull boolean waterlogged();
+ boolean waterlogged();
/**
* Gets the collision of the block state
@@ -53,7 +53,7 @@ public interface JavaBlockState {
*
* @return whether the block state can be broken with hand
*/
- @NonNull boolean canBreakWithHand();
+ boolean canBreakWithHand();
/**
* Gets the pick item of the block state
@@ -73,8 +73,11 @@ 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.
*/
- @Nullable boolean hasBlockEntity();
+ @Deprecated(forRemoval = true)
+ boolean hasBlockEntity();
/**
* Creates a new {@link JavaBlockState.Builder} instance
@@ -94,17 +97,22 @@ public interface JavaBlockState {
Builder blockHardness(@NonNegative float blockHardness);
- Builder waterlogged(@NonNull boolean waterlogged);
+ Builder waterlogged(boolean waterlogged);
Builder collision(@NonNull JavaBoundingBox[] collision);
- Builder canBreakWithHand(@NonNull boolean canBreakWithHand);
+ Builder canBreakWithHand(boolean canBreakWithHand);
Builder pickItem(@Nullable String pickItem);
Builder pistonBehavior(@Nullable String pistonBehavior);
- Builder hasBlockEntity(@Nullable boolean hasBlockEntity);
+ /**
+ * @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/block/custom/nonvanilla/JavaBoundingBox.java b/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBoundingBox.java
index 56a4ca3da..9065e8711 100644
--- a/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBoundingBox.java
+++ b/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBoundingBox.java
@@ -1,6 +1,4 @@
package org.geysermc.geyser.api.block.custom.nonvanilla;
-import org.checkerframework.checker.nullness.qual.NonNull;
-
-public record JavaBoundingBox(@NonNull double middleX, @NonNull double middleY, @NonNull double middleZ, @NonNull double sizeX, @NonNull double sizeY, @NonNull double sizeZ) {
+public record JavaBoundingBox(double middleX, double middleY, double middleZ, double sizeX, double sizeY, double sizeZ) {
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java
index 2f1f2b24d..29922ae1e 100644
--- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java
+++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java
@@ -28,7 +28,9 @@ package org.geysermc.geyser.api.command;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.connection.GeyserConnection;
+import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
import org.geysermc.geyser.api.extension.Extension;
+import org.geysermc.geyser.api.util.TriState;
import java.util.Collections;
import java.util.List;
@@ -58,15 +60,15 @@ public interface Command {
* Gets the permission node associated with
* this command.
*
- * @return the permission node for this command
+ * @return the permission node for this command if defined, otherwise an empty string
*/
@NonNull
String permission();
/**
- * Gets the aliases for this command.
+ * Gets the aliases for this command, as an unmodifiable list
*
- * @return the aliases for this command
+ * @return the aliases for this command as an unmodifiable list
*/
@NonNull
List aliases();
@@ -75,35 +77,39 @@ public interface Command {
* Gets if this command is designed to be used only by server operators.
*
* @return if this command is designated to be used only by server operators.
+ * @deprecated this method is not guaranteed to provide meaningful or expected results.
*/
- boolean isSuggestedOpOnly();
-
- /**
- * Gets if this command is executable on console.
- *
- * @return if this command is executable on console
- */
- boolean isExecutableOnConsole();
-
- /**
- * Gets the subcommands associated with this
- * command. Mainly used within the Geyser Standalone
- * GUI to know what subcommands are supported.
- *
- * @return the subcommands associated with this command
- */
- @NonNull
- default List subCommands() {
- return Collections.emptyList();
+ @Deprecated(forRemoval = true)
+ default boolean isSuggestedOpOnly() {
+ return false;
}
/**
- * Used to send a deny message to Java players if this command can only be used by Bedrock players.
- *
- * @return true if this command can only be used by Bedrock players.
+ * @return true if this command is executable on console
+ * @deprecated use {@link #isPlayerOnly()} instead (inverted)
*/
- default boolean isBedrockOnly() {
- return false;
+ @Deprecated(forRemoval = true)
+ default boolean isExecutableOnConsole() {
+ return !isPlayerOnly();
+ }
+
+ /**
+ * @return true if this command can only be used by players
+ */
+ boolean isPlayerOnly();
+
+ /**
+ * @return true if this command can only be used by Bedrock players
+ */
+ boolean isBedrockOnly();
+
+ /**
+ * @deprecated this method will always return an empty immutable list
+ */
+ @Deprecated(forRemoval = true)
+ @NonNull
+ default List subCommands() {
+ return Collections.emptyList();
}
/**
@@ -128,7 +134,7 @@ public interface Command {
* is an instance of this source.
*
* @param sourceType the source type
- * @return the builder
+ * @return this builder
*/
Builder source(@NonNull Class extends T> sourceType);
@@ -136,7 +142,7 @@ public interface Command {
* Sets the command name.
*
* @param name the command name
- * @return the builder
+ * @return this builder
*/
Builder name(@NonNull String name);
@@ -144,23 +150,40 @@ public interface Command {
* Sets the command description.
*
* @param description the command description
- * @return the builder
+ * @return this builder
*/
Builder description(@NonNull String description);
/**
- * Sets the permission node.
+ * Sets the permission node required to run this command.
+ * It will not be registered with any permission registries, such as an underlying server,
+ * or a permissions Extension (unlike {@link #permission(String, TriState)}).
*
* @param permission the permission node
- * @return the builder
+ * @return this builder
*/
Builder permission(@NonNull String permission);
+ /**
+ * Sets the permission node and its default value. The usage of the default value is platform dependant
+ * and may or may not be used. For example, it may be registered to an underlying server.
+ *
+ * Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} to register permissions,
+ * especially if the same permission is required by multiple commands. Also see this event for TriState meanings.
+ *
+ * @param permission the permission node
+ * @param defaultValue the node's default value
+ * @return this builder
+ * @deprecated this method is experimental and may be removed in the future
+ */
+ @Deprecated
+ Builder permission(@NonNull String permission, @NonNull TriState defaultValue);
+
/**
* Sets the aliases.
*
* @param aliases the aliases
- * @return the builder
+ * @return this builder
*/
Builder aliases(@NonNull List aliases);
@@ -168,46 +191,62 @@ public interface Command {
* Sets if this command is designed to be used only by server operators.
*
* @param suggestedOpOnly if this command is designed to be used only by server operators
- * @return the builder
+ * @return this builder
+ * @deprecated this method is not guaranteed to produce meaningful or expected results
*/
+ @Deprecated(forRemoval = true)
Builder suggestedOpOnly(boolean suggestedOpOnly);
/**
* Sets if this command is executable on console.
*
* @param executableOnConsole if this command is executable on console
- * @return the builder
+ * @return this builder
+ * @deprecated use {@link #isPlayerOnly()} instead (inverted)
*/
+ @Deprecated(forRemoval = true)
Builder executableOnConsole(boolean executableOnConsole);
+ /**
+ * Sets if this command can only be executed by players.
+ *
+ * @param playerOnly if this command is player only
+ * @return this builder
+ */
+ Builder playerOnly(boolean playerOnly);
+
+ /**
+ * Sets if this command can only be executed by bedrock players.
+ *
+ * @param bedrockOnly if this command is bedrock only
+ * @return this builder
+ */
+ Builder bedrockOnly(boolean bedrockOnly);
+
/**
* Sets the subcommands.
*
* @param subCommands the subcommands
- * @return the builder
+ * @return this builder
+ * @deprecated this method has no effect
*/
- Builder subCommands(@NonNull List subCommands);
-
- /**
- * Sets if this command is bedrock only.
- *
- * @param bedrockOnly if this command is bedrock only
- * @return the builder
- */
- Builder bedrockOnly(boolean bedrockOnly);
+ @Deprecated(forRemoval = true)
+ default Builder subCommands(@NonNull List subCommands) {
+ return this;
+ }
/**
* Sets the {@link CommandExecutor} for this command.
*
* @param executor the command executor
- * @return the builder
+ * @return this builder
*/
Builder executor(@NonNull CommandExecutor executor);
/**
* Builds the command.
*
- * @return the command
+ * @return a new command from this builder
*/
@NonNull
Command build();
diff --git a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java
index 45276e2c4..c1453f579 100644
--- a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java
+++ b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java
@@ -26,6 +26,10 @@
package org.geysermc.geyser.api.command;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.api.connection.GeyserConnection;
+
+import java.util.UUID;
/**
* Represents an instance capable of sending commands.
@@ -64,6 +68,17 @@ public interface CommandSource {
*/
boolean isConsole();
+ /**
+ * @return a Java UUID if this source represents a player, otherwise null
+ */
+ @Nullable UUID playerUuid();
+
+ /**
+ * @return a GeyserConnection if this source represents a Bedrock player that is connected
+ * to this Geyser instance, otherwise null
+ */
+ @Nullable GeyserConnection connection();
+
/**
* Returns the locale of the command source.
*
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..ba559a462 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,31 @@ 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();
+
+ /**
+ * Returns the current ping of the connection.
+ */
+ int ping();
}
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..48c717089
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java
@@ -0,0 +1,90 @@
+/*
+ * 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();
+
+ /**
+ * Sends a request to the Java server to switch the items in the main and offhand.
+ * There is no guarantee of the server accepting the request.
+ */
+ void switchHands();
+}
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/EventBus.java b/api/src/main/java/org/geysermc/geyser/api/event/EventBus.java
index 801bfa45f..3344e38f4 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/EventBus.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/EventBus.java
@@ -28,7 +28,6 @@ package org.geysermc.geyser.api.event;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.Event;
import org.geysermc.event.bus.OwnedEventBus;
-import org.geysermc.geyser.api.extension.Extension;
import java.util.Set;
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java b/api/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java
index 7f91d09a3..258ef9164 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/EventSubscriber.java
@@ -27,7 +27,6 @@ package org.geysermc.geyser.api.event;
import org.geysermc.event.Event;
import org.geysermc.event.subscribe.OwnedSubscriber;
-import org.geysermc.geyser.api.extension.Extension;
/**
* Represents a subscribed listener to a {@link Event}. Wraps around
diff --git a/core/src/main/java/org/geysermc/geyser/item/type/ChestItem.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDisconnectEvent.java
similarity index 56%
rename from core/src/main/java/org/geysermc/geyser/item/type/ChestItem.java
rename to api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDisconnectEvent.java
index 99857006c..05e3415a0 100644
--- a/core/src/main/java/org/geysermc/geyser/item/type/ChestItem.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDisconnectEvent.java
@@ -23,31 +23,39 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.item.type;
+package org.geysermc.geyser.api.event.bedrock;
-import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.checkerframework.checker.nullness.qual.NonNull;
-import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.api.connection.GeyserConnection;
+import org.geysermc.geyser.api.event.connection.ConnectionEvent;
-public class ChestItem extends BlockItem {
+/**
+ * Called when a Geyser session disconnects.
+ */
+public class SessionDisconnectEvent extends ConnectionEvent {
+ private String disconnectReason;
- public ChestItem(String javaIdentifier, Builder builder) {
- super(javaIdentifier, builder);
+ public SessionDisconnectEvent(@NonNull GeyserConnection connection, @NonNull String reason) {
+ super(connection);
+ this.disconnectReason = reason;
}
- @Override
- public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
- super.translateNbtToBedrock(session, tag);
+ /**
+ * Gets the disconnect reason.
+ *
+ * @return the reason for the disconnect
+ */
+ public @NonNull String disconnectReason() {
+ return disconnectReason;
+ }
- // Strip the BlockEntityTag from the chests contents
- // sent to the client. The client does not parse this
- // or use it for anything, as this tag is fully
- // server-side, so we remove it to reduce bandwidth and
- // solve potential issues with very large tags.
-
- // There was a problem in the past where this would strip
- // NBT data in creative mode, however with the new server
- // authoritative inventories, this is no longer a concern.
- tag.remove("BlockEntityTag");
+ /**
+ * Sets the disconnect reason, thereby overriding th original reason.
+ *
+ * @param disconnectReason the reason for the disconnect
+ */
+ public void disconnectReason(@NonNull String disconnectReason) {
+ this.disconnectReason = disconnectReason;
}
}
+
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/connection/GeyserBedrockPingEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/connection/GeyserBedrockPingEvent.java
index 67a81ac58..10ccb93d5 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/connection/GeyserBedrockPingEvent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/connection/GeyserBedrockPingEvent.java
@@ -34,7 +34,7 @@ import java.net.InetSocketAddress;
/**
* Called whenever Geyser gets pinged
- *
+ *
* This event allows you to modify/obtain the MOTD, maximum player count, and current number of players online,
* Geyser will reply to the client with what was given.
*/
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/GeyserDefineCommandsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java
index 77d5efa65..d136202bd 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java
@@ -33,7 +33,7 @@ import java.util.Map;
/**
* Called when commands are defined within Geyser.
- *
+ *
* This event allows you to register new commands using the {@link #register(Command)}
* method and retrieve the default commands defined.
*/
@@ -50,7 +50,7 @@ public interface GeyserDefineCommandsEvent extends Event {
/**
* Gets all the registered built-in {@link Command}s.
*
- * @return all the registered built-in commands
+ * @return all the registered built-in commands as an unmodifiable map
*/
@NonNull
Map commands();
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java
index b1a01d7e6..a105578d9 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java
@@ -28,13 +28,12 @@ package org.geysermc.geyser.api.event.lifecycle;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.api.block.custom.CustomBlockState;
-import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockItem;
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
import org.geysermc.event.Event;
/**
* Called on Geyser's startup when looking for custom blocks. Custom blocks must be registered through this event.
- *
+ *
* This event will not be called if the "add-non-bedrock-items" setting is disabled in the Geyser config.
*/
public abstract class GeyserDefineCustomBlocksEvent implements Event {
@@ -48,8 +47,8 @@ public abstract class GeyserDefineCustomBlocksEvent implements Event {
/**
* Registers the given {@link CustomBlockState} as an override for the
* given java state identifier
- * Java state identifiers are listed in
- * https://raw.githubusercontent.com/GeyserMC/mappings/master/blocks.json
+ * Java state identifiers are listed
+ * here
*
* @param javaIdentifier the java state identifier to override
* @param customBlockState the custom block state with which to override java state identifier
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java
index 0957b8551..bd14aaf43 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java
@@ -36,7 +36,7 @@ import java.util.Map;
/**
* Called on Geyser's startup when looking for custom items. Custom items must be registered through this event.
- *
+ *
* This event will not be called if the "add non-Bedrock items" setting is disabled in the Geyser config.
*/
public interface GeyserDefineCustomItemsEvent extends Event {
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomSkullsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomSkullsEvent.java
index 17f7b599a..6443bbeb3 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomSkullsEvent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomSkullsEvent.java
@@ -5,7 +5,7 @@ import org.geysermc.event.Event;
/**
* Called on Geyser's startup when looking for custom skulls. Custom skulls must be registered through this event.
- *
+ *
* This event will not be called if the "add-non-bedrock-items" setting is disabled in the Geyser config.
*/
public abstract class GeyserDefineCustomSkullsEvent implements Event {
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/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java
new file mode 100644
index 000000000..43ebc2c50
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java
@@ -0,0 +1,42 @@
+/*
+ * 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.event.lifecycle;
+
+import org.geysermc.event.Event;
+import org.geysermc.event.PostOrder;
+import org.geysermc.geyser.api.permission.PermissionChecker;
+
+/**
+ * Fired by any permission manager implementations that wish to add support for custom permission checking.
+ * This event is not guaranteed to be fired - it is currently only fired on Geyser-Standalone and ViaProxy.
+ *
+ * Subscribing to this event with an earlier {@link PostOrder} and registering a {@link PermissionChecker}
+ * will result in that checker having a higher priority than others.
+ */
+public interface GeyserRegisterPermissionCheckersEvent extends Event {
+
+ void register(PermissionChecker checker);
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java
new file mode 100644
index 000000000..4f06c4e5f
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java
@@ -0,0 +1,51 @@
+/*
+ * 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.event.lifecycle;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.event.Event;
+import org.geysermc.geyser.api.util.TriState;
+
+/**
+ * Fired by anything that wishes to gather permission nodes and defaults.
+ *
+ * This event is not guaranteed to be fired, as certain Geyser platforms do not have a native permission system.
+ * It can be expected to fire on Geyser-Spigot, Geyser-NeoForge, Geyser-Standalone, and Geyser-ViaProxy
+ * It may be fired by a 3rd party regardless of the platform.
+ */
+public interface GeyserRegisterPermissionsEvent extends Event {
+
+ /**
+ * Registers a permission node and its default value with the firer.
+ * {@link TriState#TRUE} corresponds to all players having the permission by default.
+ * {@link TriState#NOT_SET} corresponds to only server operators having the permission by default (if such a concept exists on the platform).
+ * {@link TriState#FALSE} corresponds to no players having the permission by default.
+ *
+ * @param permission the permission node to register
+ * @param defaultValue the default value of the node
+ */
+ void register(@NonNull String permission, @NonNull TriState defaultValue);
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java
index 993bdee44..1eacfea9a 100644
--- a/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java
+++ b/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java
@@ -107,6 +107,15 @@ public interface Extension extends EventRegistrar {
return this.extensionLoader().description(this);
}
+ /**
+ * @return the root command that all of this extension's commands will stem from.
+ * By default, this is the extension's id.
+ */
+ @NonNull
+ default String rootCommand() {
+ return this.description().id();
+ }
+
/**
* Gets the extension's logger
*
diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java
index 2df3ee815..25daf450f 100644
--- a/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java
+++ b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java
@@ -59,33 +59,46 @@ public interface ExtensionDescription {
String main();
/**
- * Gets the extension's major api version
+ * Represents the human api version that the extension requires.
+ * See the Geyser version outline)
+ * for more details on the Geyser API version.
*
- * @return the extension's major api version
+ * @return the extension's requested human api version
+ */
+ int humanApiVersion();
+
+ /**
+ * Represents the major api version that the extension requires.
+ * See the Geyser version outline)
+ * for more details on the Geyser API version.
+ *
+ * @return the extension's requested major api version
*/
int majorApiVersion();
/**
- * Gets the extension's minor api version
+ * Represents the minor api version that the extension requires.
+ * See the Geyser version outline)
+ * for more details on the Geyser API version.
*
- * @return the extension's minor api version
+ * @return the extension's requested minor api version
*/
int minorApiVersion();
/**
- * Gets the extension's patch api version
- *
- * @return the extension's patch api version
+ * No longer in use. Geyser is now using an adaption of the romantic versioning scheme.
+ * See here for details.
*/
- int patchApiVersion();
+ @Deprecated(forRemoval = true)
+ default int patchApiVersion() {
+ return minorApiVersion();
+ }
/**
- * Gets the extension's api version.
- *
- * @return the extension's api version
+ * Returns the extension's requested Geyser Api version.
*/
default String apiVersion() {
- return majorApiVersion() + "." + minorApiVersion() + "." + patchApiVersion();
+ return humanApiVersion() + "." + majorApiVersion() + "." + minorApiVersion();
}
/**
diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java
index a9d0d7376..5226221df 100644
--- a/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java
+++ b/api/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java
@@ -36,13 +36,13 @@ import java.util.Collection;
public abstract class ExtensionManager {
/**
- * Gets an extension with the given name.
+ * Gets an extension by the given ID.
*
- * @param name the name of the extension
- * @return an extension with the given name
+ * @param id the ID of the extension
+ * @return an extension with the given ID
*/
@Nullable
- public abstract Extension extension(@NonNull String name);
+ public abstract Extension extension(@NonNull String id);
/**
* Enables the given {@link Extension}.
diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java b/api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java
index 1fe88e9e9..5313c1f40 100644
--- a/api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java
+++ b/api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidDescriptionException.java
@@ -25,10 +25,16 @@
package org.geysermc.geyser.api.extension.exception;
+import java.io.Serial;
+
/**
* Thrown when an extension's description is invalid.
*/
public class InvalidDescriptionException extends Exception {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
public InvalidDescriptionException(Throwable cause) {
super(cause);
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java b/api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java
index 7fb6b6922..89780fe46 100644
--- a/api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java
+++ b/api/src/main/java/org/geysermc/geyser/api/extension/exception/InvalidExtensionException.java
@@ -25,10 +25,16 @@
package org.geysermc.geyser.api.extension.exception;
+import java.io.Serial;
+
/**
* Thrown when an extension is invalid.
*/
public class InvalidExtensionException extends Exception {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
public InvalidExtensionException(Throwable cause) {
super(cause);
}
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 d256b9ac0..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,9 @@ 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;
+
/**
* This is used to store data for a custom item.
*/
@@ -75,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.
*
@@ -89,6 +106,14 @@ public interface CustomItemData {
*/
@Nullable CustomRenderOffsets renderOffsets();
+ /**
+ * Gets the item's set of tags that can be used in Molang.
+ * Equivalent to "tag:some_tag"
+ *
+ * @return the item's tags, if they exist
+ */
+ @NonNull Set tags();
+
static CustomItemData.Builder builder() {
return GeyserApi.api().provider(CustomItemData.Builder.class);
}
@@ -109,10 +134,16 @@ 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);
+ Builder tags(@Nullable Set tags);
+
CustomItemData build();
}
}
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 0a09f6958..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);
@@ -239,6 +245,9 @@ public interface NonVanillaCustomItemData extends CustomItemData {
@Override
Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets);
+ @Override
+ Builder tags(@Nullable Set tags);
+
NonVanillaCustomItemData build();
}
}
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/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java
new file mode 100644
index 000000000..c0d4af2f4
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.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.permission;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.api.command.CommandSource;
+import org.geysermc.geyser.api.util.TriState;
+
+/**
+ * Something capable of checking if a {@link CommandSource} has a permission
+ */
+@FunctionalInterface
+public interface PermissionChecker {
+
+ /**
+ * Checks if the given source has a permission
+ *
+ * @param source the {@link CommandSource} whose permissions should be queried
+ * @param permission the permission node to check
+ * @return a {@link TriState} as the value of the node. {@link TriState#NOT_SET} generally means that the permission
+ * node itself was not found, and the source does not have such permission.
+ * {@link TriState#TRUE} and {@link TriState#FALSE} represent explicitly set values.
+ */
+ @NonNull
+ TriState hasPermission(@NonNull CommandSource source, @NonNull String permission);
+}
diff --git a/core/src/main/java/org/geysermc/geyser/util/collection/package-info.java b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java
similarity index 66%
rename from core/src/main/java/org/geysermc/geyser/util/collection/package-info.java
rename to api/src/main/java/org/geysermc/geyser/api/skin/Cape.java
index e17a38877..1e7341ae4 100644
--- a/core/src/main/java/org/geysermc/geyser/util/collection/package-info.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,12 +23,18 @@
* @link https://github.com/GeyserMC/Geyser
*/
+package org.geysermc.geyser.api.skin;
+
/**
- * Contains useful collections for use in Geyser.
+ * Represents a cape.
*
- * 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.
+ * @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
*/
-package org.geysermc.geyser.util.collection;
\ No newline at end of file
+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 7519ff157..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;
@@ -51,7 +50,7 @@ public enum CreativeCategory {
*
* @return the name of the category
*/
- @NonNull public String internalName() {
+ public @NonNull String internalName() {
return 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/api/src/main/java/org/geysermc/geyser/api/util/TriState.java b/api/src/main/java/org/geysermc/geyser/api/util/TriState.java
index 457a38e32..a8bb723d6 100644
--- a/api/src/main/java/org/geysermc/geyser/api/util/TriState.java
+++ b/api/src/main/java/org/geysermc/geyser/api/util/TriState.java
@@ -30,7 +30,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
/**
* This is a way to represent a boolean, but with a non set value added.
- * This class was inspired by adventure's version https://github.com/KyoriPowered/adventure/blob/main/4/api/src/main/java/net/kyori/adventure/util/TriState.java
+ * This class was inspired by adventure's TriState
*/
public enum TriState {
/**
diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts
index 4025569dd..5fe7ea3d1 100644
--- a/bootstrap/bungeecord/build.gradle.kts
+++ b/bootstrap/bungeecord/build.gradle.kts
@@ -1,20 +1,24 @@
dependencies {
api(projects.core)
+ implementation(libs.cloud.bungee)
implementation(libs.adventure.text.serializer.bungeecord)
+ compileOnlyApi(libs.bungeecord.proxy)
}
platformRelocate("net.md_5.bungee.jni")
platformRelocate("com.fasterxml.jackson")
platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound
platformRelocate("net.kyori")
+platformRelocate("org.incendo")
+platformRelocate("io.leangen.geantyref") // provided by cloud, should also be relocated
platformRelocate("org.yaml") // Broken as of 1.20
// These dependencies are already present on the platform
provided(libs.bungeecord.proxy)
-application {
- mainClass.set("org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain")
+tasks.withType {
+ manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain"
}
tasks.withType {
@@ -22,6 +26,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 +38,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/GeyserBungeeCompressionDisabler.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeCompressionDisabler.java
index 084e1d2dc..485079a05 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeCompressionDisabler.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeCompressionDisabler.java
@@ -32,18 +32,26 @@ import net.md_5.bungee.protocol.packet.LoginSuccess;
import net.md_5.bungee.protocol.packet.SetCompression;
public class GeyserBungeeCompressionDisabler extends ChannelOutboundHandlerAdapter {
+ private boolean compressionDisabled = false;
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (!(msg instanceof SetCompression)) {
- if (msg instanceof LoginSuccess) {
- // We're past the point that compression can be enabled
+ // Fixes https://github.com/GeyserMC/Geyser/issues/4281
+ // The server may send a LoginDisconnect packet after compression is set.
+ if (!compressionDisabled) {
if (ctx.pipeline().get("compress") != null) {
ctx.pipeline().remove("compress");
+ compressionDisabled = true;
}
if (ctx.pipeline().get("decompress") != null) {
ctx.pipeline().remove("decompress");
+ compressionDisabled = true;
}
+ }
+
+ if (msg instanceof LoginSuccess) {
+ // We're past the point that compression can be enabled
ctx.pipeline().remove(this);
}
super.write(ctx, msg, promise);
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeDumpInfo.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeDumpInfo.java
index ba7bc464f..85abd285b 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeDumpInfo.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeDumpInfo.java
@@ -30,6 +30,7 @@ import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
+import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -51,7 +52,8 @@ public class GeyserBungeeDumpInfo extends BootstrapDumpInfo {
this.plugins = new ArrayList<>();
for (net.md_5.bungee.api.config.ListenerInfo listener : proxy.getConfig().getListeners()) {
- this.listeners.add(new ListenerInfo(listener.getHost().getHostString(), listener.getHost().getPort()));
+ InetSocketAddress address = (InetSocketAddress) listener.getSocketAddress();
+ this.listeners.add(new ListenerInfo(address.getHostString(), address.getPort()));
}
for (Plugin plugin : proxy.getPluginManager().getPlugins()) {
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java
index e10b3ce6f..7c60ba95d 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeInjector.java
@@ -39,6 +39,7 @@ import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.netty.PipelineUtils;
+import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.network.netty.GeyserInjector;
@@ -125,7 +126,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
.channel(LocalServerChannelWrapper.class)
.childHandler(new ChannelInitializer<>() {
@Override
- protected void initChannel(Channel ch) throws Exception {
+ protected void initChannel(@NonNull Channel ch) throws Exception {
if (proxy.getConfig().getServers() == null) {
// Proxy hasn't finished loading all plugins - it loads the config after all plugins
// Probably doesn't need to be translatable?
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/GeyserBungeePingPassthrough.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java
index 39fb9e134..1193a52b3 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePingPassthrough.java
@@ -35,14 +35,16 @@ import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.protocol.ProtocolConstants;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.ping.GeyserPingInfo;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
-import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
@AllArgsConstructor
public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, Listener {
@@ -59,18 +61,23 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
future.complete(event);
}
}));
- ProxyPingEvent event = future.join();
- ServerPing response = event.getResponse();
- GeyserPingInfo geyserPingInfo = new GeyserPingInfo(
- response.getDescriptionComponent().toLegacyText(),
- new GeyserPingInfo.Players(response.getPlayers().getMax(), response.getPlayers().getOnline()),
- new GeyserPingInfo.Version(response.getVersion().getName(), response.getVersion().getProtocol())
- );
- if (event.getResponse().getPlayers().getSample() != null) {
- Arrays.stream(event.getResponse().getPlayers().getSample()).forEach(proxiedPlayer ->
- geyserPingInfo.getPlayerList().add(proxiedPlayer.getName()));
+
+ ProxyPingEvent event;
+
+ try {
+ event = future.get(100, TimeUnit.MILLISECONDS);
+ } catch (Throwable cause) {
+ String address = GeyserImpl.getInstance().getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "";
+ GeyserImpl.getInstance().getLogger().error("Failed to get ping information for " + address, cause);
+ return null;
}
- return geyserPingInfo;
+
+ ServerPing response = event.getResponse();
+ return new GeyserPingInfo(
+ response.getDescriptionComponent().toLegacyText(),
+ response.getPlayers().getMax(),
+ response.getPlayers().getOnline()
+ );
}
// This is static so pending connection can use it
@@ -110,7 +117,7 @@ public class GeyserBungeePingPassthrough implements IGeyserPingPassthrough, List
}
@Override
- public InetSocketAddress getVirtualHost() {
+ public @Nullable InetSocketAddress getVirtualHost() {
return null;
}
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 538e00a05..1c0049231 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
@@ -27,24 +27,28 @@ package org.geysermc.geyser.platform.bungeecord;
import io.netty.channel.Channel;
import net.md_5.bungee.BungeeCord;
+import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.config.ListenerInfo;
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.command.GeyserCommandManager;
+import org.geysermc.geyser.api.util.PlatformType;
+import org.geysermc.geyser.command.CommandRegistry;
+import org.geysermc.geyser.command.CommandSourceConverter;
+import org.geysermc.geyser.command.GeyserCommandSource;
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.bungeecord.command.GeyserBungeeCommandExecutor;
+import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
-import org.jetbrains.annotations.NotNull;
+import org.incendo.cloud.CommandManager;
+import org.incendo.cloud.bungee.BungeeCommandManager;
+import org.incendo.cloud.execution.ExecutionCoordinator;
import java.io.File;
import java.io.IOException;
@@ -54,69 +58,117 @@ import java.net.SocketAddress;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
-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 CommandRegistry commandRegistry;
private GeyserBungeeConfiguration geyserConfig;
private GeyserBungeeInjector geyserInjector;
- private GeyserBungeeLogger geyserLogger;
+ private final GeyserBungeeLogger geyserLogger = new GeyserBungeeLogger(getLogger());
private IGeyserPingPassthrough geyserBungeePingPassthrough;
-
private GeyserImpl geyser;
+ // We can't disable the plugin; hence we need to keep track of it manually
+ private boolean disabled;
+
@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_19_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()) {
+ disabled = true;
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);
+
+ // Registration of listeners occurs only once
+ this.getProxy().getPluginManager().registerListener(this, new GeyserBungeeUpdateListener());
}
@Override
public void onEnable() {
- // Remove this in like a year
- if (getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) {
- geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/"));
+ if (disabled) {
+ return; // Config did not load properly!
+ }
+ // 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 {
+ var sourceConverter = new CommandSourceConverter<>(
+ CommandSender.class,
+ id -> getProxy().getPlayer(id),
+ () -> getProxy().getConsole(),
+ BungeeCommandSource::new
+ );
+ CommandManager cloud = new BungeeCommandManager<>(
+ this,
+ ExecutionCoordinator.simpleCoordinator(),
+ sourceConverter
+ );
+ this.commandRegistry = new CommandRegistry(geyser, cloud, false); // applying root permission would be a breaking change because we can't register permission defaults
+ }
+
// Force-disable query if enabled, or else Geyser won't enable
for (ListenerInfo info : getProxy().getConfig().getListeners()) {
if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.getBedrock().port()) {
@@ -135,67 +187,31 @@ 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
- 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.postStartup();
- 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();
- this.geyserInjector = new GeyserBungeeInjector(this);
- this.geyserInjector.initializeLocalChannel(this);
-
- this.geyserCommandManager = new GeyserCommandManager(geyser);
- this.geyserCommandManager.init();
-
- this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands()));
- for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
- Map commands = entry.getValue();
- if (commands.isEmpty()) {
- continue;
- }
-
- 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());
}
+
+ // No need to re-register commands or re-init injector when reloading
+ if (GeyserImpl.getInstance().isReloading()) {
+ return;
+ }
+
+ this.geyserInjector.initializeLocalChannel(this);
}
@Override
- public void onDisable() {
+ public void onGeyserDisable() {
+ if (geyser != null) {
+ geyser.disable();
+ }
+ }
+
+ @Override
+ public void onGeyserShutdown() {
if (geyser != null) {
geyser.shutdown();
}
@@ -204,6 +220,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
}
+ @Override
+ public void onDisable() {
+ this.onGeyserShutdown();
+ }
+
@Override
public GeyserBungeeConfiguration getGeyserConfig() {
return geyserConfig;
@@ -215,8 +236,8 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
@Override
- public GeyserCommandManager getGeyserCommandManager() {
- return this.geyserCommandManager;
+ public CommandRegistry getCommandRegistry() {
+ return this.commandRegistry;
}
@Override
@@ -245,7 +266,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
return this.geyserInjector.getServerSocketAddress();
}
- @NotNull
+ @NonNull
@Override
public String getServerBindAddress() {
return findCompatibleListener().map(InetSocketAddress::getHostString).orElse("");
@@ -271,4 +292,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/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java
index c68839b20..0a89b5421 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeUpdateListener.java
@@ -29,8 +29,8 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
-import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.Permissions;
import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource;
import org.geysermc.geyser.util.VersionCheckUtils;
@@ -40,7 +40,7 @@ public final class GeyserBungeeUpdateListener implements Listener {
public void onPlayerJoin(final PostLoginEvent event) {
if (GeyserImpl.getInstance().getConfig().isNotifyOnNewBedrockUpdate()) {
final ProxiedPlayer player = event.getPlayer();
- if (player.hasPermission(Constants.UPDATE_PERMISSION)) {
+ if (player.hasPermission(Permissions.CHECK_UPDATE)) {
VersionCheckUtils.checkForGeyserUpdate(() -> new BungeeCommandSource(player));
}
}
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java
index f65377643..10ccc5bac 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java
@@ -27,18 +27,22 @@ package org.geysermc.geyser.platform.bungeecord.command;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
+import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Locale;
+import java.util.UUID;
public class BungeeCommandSource implements GeyserCommandSource {
- private final net.md_5.bungee.api.CommandSender handle;
+ private final CommandSender handle;
- public BungeeCommandSource(net.md_5.bungee.api.CommandSender handle) {
+ public BungeeCommandSource(CommandSender handle) {
this.handle = handle;
// Ensure even Java players' languages are loaded
GeyserLocale.loadGeyserLocale(this.locale());
@@ -50,7 +54,7 @@ public class BungeeCommandSource implements GeyserCommandSource {
}
@Override
- public void sendMessage(String message) {
+ public void sendMessage(@NonNull String message) {
handle.sendMessage(TextComponent.fromLegacyText(message));
}
@@ -71,12 +75,20 @@ public class BungeeCommandSource implements GeyserCommandSource {
return !(handle instanceof ProxiedPlayer);
}
+ @Override
+ public @Nullable UUID playerUuid() {
+ if (handle instanceof ProxiedPlayer player) {
+ return player.getUniqueId();
+ }
+ return null;
+ }
+
@Override
public String locale() {
if (handle instanceof ProxiedPlayer player) {
Locale locale = player.getLocale();
if (locale != null) {
- // Locale can be null early on in the conneciton
+ // Locale can be null early on in the connection
return GeyserLocale.formatLocale(locale.getLanguage() + "_" + locale.getCountry());
}
}
@@ -85,6 +97,12 @@ public class BungeeCommandSource implements GeyserCommandSource {
@Override
public boolean hasPermission(String permission) {
- return handle.hasPermission(permission);
+ // Handle blank permissions ourselves, as bungeecord only handles empty ones
+ return permission.isBlank() || handle.hasPermission(permission);
+ }
+
+ @Override
+ public Object handle() {
+ return handle;
}
}
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java
deleted file mode 100644
index 2d02c9950..000000000
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java
+++ /dev/null
@@ -1,89 +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.bungeecord.command;
-
-import net.md_5.bungee.api.ChatColor;
-import net.md_5.bungee.api.CommandSender;
-import net.md_5.bungee.api.plugin.Command;
-import net.md_5.bungee.api.plugin.TabExecutor;
-import org.geysermc.geyser.GeyserImpl;
-import org.geysermc.geyser.command.GeyserCommand;
-import org.geysermc.geyser.command.GeyserCommandExecutor;
-import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.text.GeyserLocale;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Map;
-
-public class GeyserBungeeCommandExecutor extends Command implements TabExecutor {
- private final GeyserCommandExecutor commandExecutor;
-
- public GeyserBungeeCommandExecutor(String name, GeyserImpl geyser, Map commands) {
- super(name);
-
- this.commandExecutor = new GeyserCommandExecutor(geyser, commands);
- }
-
- @Override
- public void execute(CommandSender sender, String[] args) {
- BungeeCommandSource commandSender = new BungeeCommandSource(sender);
- GeyserSession session = this.commandExecutor.getGeyserSession(commandSender);
-
- if (args.length > 0) {
- GeyserCommand command = this.commandExecutor.getCommand(args[0]);
- if (command != null) {
- if (!sender.hasPermission(command.permission())) {
- String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale());
-
- commandSender.sendMessage(ChatColor.RED + message);
- return;
- }
- if (command.isBedrockOnly() && session == null) {
- String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale());
-
- commandSender.sendMessage(ChatColor.RED + message);
- return;
- }
- command.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
- } else {
- String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale());
- commandSender.sendMessage(ChatColor.RED + message);
- }
- } else {
- this.commandExecutor.getCommand("help").execute(session, commandSender, new String[0]);
- }
- }
-
- @Override
- public Iterable onTabComplete(CommandSender sender, String[] args) {
- if (args.length == 1) {
- return commandExecutor.tabComplete(new BungeeCommandSource(sender));
- } else {
- return Collections.emptyList();
- }
- }
-}
diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts
deleted file mode 100644
index c260703a5..000000000
--- a/bootstrap/fabric/build.gradle.kts
+++ /dev/null
@@ -1,129 +0,0 @@
-import net.fabricmc.loom.task.RemapJarTask
-
-plugins {
- id("fabric-loom") version "1.0-SNAPSHOT"
- id("com.modrinth.minotaur") version "2.+"
-}
-
-java {
- targetCompatibility = JavaVersion.VERSION_17
- sourceCompatibility = JavaVersion.VERSION_17
-}
-
-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", "1.20.1")
-
- loaders.add("fabric")
- failSilently.set(true)
-
- dependencies {
- required.project("fabric-api")
- }
-}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java
deleted file mode 100644
index 0bbe73f16..000000000
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java
+++ /dev/null
@@ -1,297 +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;
-
-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 net.minecraft.commands.CommandSourceStack;
-import net.minecraft.commands.Commands;
-import net.minecraft.server.MinecraftServer;
-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.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;
-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.text.GeyserLocale;
-import org.geysermc.geyser.util.FileUtils;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-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;
-
- private boolean reloading;
-
- private GeyserImpl geyser;
- private ModContainer mod;
- private Path dataFolder;
- private MinecraftServer server;
-
- private GeyserCommandManager geyserCommandManager;
- private GeyserFabricConfiguration geyserConfig;
- private GeyserFabricLogger geyserLogger;
- private IGeyserPingPassthrough geyserPingPassthrough;
- private WorldManager geyserWorldManager;
-
- @Override
- public void onInitialize() {
- 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()
- 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();
- return;
- }
-
- this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode());
-
- GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
-
- 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;
- }
- }
-
- /**
- * 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;
-
- GeyserImpl.start();
-
- this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
-
- this.geyserCommandManager = new GeyserCommandManager(geyser);
- this.geyserCommandManager.init();
-
- this.geyserWorldManager = new GeyserFabricWorldManager(server);
-
- // Start command building
- // Set just "geyser" as the help command
- GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(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());
- builder.then(Commands.literal(command.getKey())
- .executes(executor)
- // Could also test for Bedrock but depending on when this is called it may backfire
- .requires(executor::testPermission)
- // Allows parsing of arguments; e.g. for /geyser dump logs or the connectiontest command
- .then(Commands.argument("args", StringArgumentType.greedyString())
- .executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
- .requires(executor::testPermission)));
- }
- server.getCommands().getDispatcher().register(builder);
-
- // Register extension commands
- for (Map.Entry> extensionMapEntry : geyser.commandManager().extensionCommands().entrySet()) {
- Map extensionCommands = extensionMapEntry.getValue();
- if (extensionCommands.isEmpty()) {
- continue;
- }
-
- // Register help command for just "/"
- GeyserFabricCommandExecutor extensionHelpExecutor = new GeyserFabricCommandExecutor(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());
- extCmdBuilder.then(Commands.literal(command.getKey())
- .executes(executor)
- .requires(executor::testPermission)
- .then(Commands.argument("args", StringArgumentType.greedyString())
- .executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
- .requires(executor::testPermission)));
- }
- server.getCommands().getDispatcher().register(extCmdBuilder);
- }
- }
-
- @Override
- public void onDisable() {
- if (geyser != null) {
- geyser.shutdown();
- geyser = null;
- }
- if (!reloading) {
- this.server = null;
- }
- }
-
- @Override
- public GeyserConfiguration getGeyserConfig() {
- return geyserConfig;
- }
-
- @Override
- public GeyserLogger getGeyserLogger() {
- return geyserLogger;
- }
-
- @Override
- public GeyserCommandManager getGeyserCommandManager() {
- return geyserCommandManager;
- }
-
- @Override
- public IGeyserPingPassthrough getGeyserPingPassthrough() {
- return geyserPingPassthrough;
- }
-
- @Override
- public WorldManager getWorldManager() {
- return geyserWorldManager;
- }
-
- @Override
- public Path getConfigFolder() {
- return dataFolder;
- }
-
- @Override
- public BootstrapDumpInfo getDumpInfo() {
- return new GeyserFabricDumpInfo(server);
- }
-
- @Override
- public String getMinecraftServerVersion() {
- return this.server.getServerVersion();
- }
-
- @NotNull
- @Override
- public String getServerBindAddress() {
- String ip = this.server.getLocalIp();
- return ip != null ? ip : ""; // See issue #3812
- }
-
- @Override
- public int getServerPort() {
- return ((GeyserServerPortGetter) server).geyser$getServerPort();
- }
-
- @Override
- public boolean testFloodgatePluginPresent() {
- Optional floodgate = FabricLoader.getInstance().getModContainer("floodgate");
- if (floodgate.isPresent()) {
- geyserConfig.loadFloodgate(this, floodgate.orElse(null));
- return true;
- }
- return false;
- }
-
- @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;
- }
-
- try {
- return path.getFileSystem()
- .provider()
- .newInputStream(path);
- } catch (IOException e) {
- return null;
- }
- }
-
- public void setReloading(boolean reloading) {
- this.reloading = reloading;
- }
-
- public static GeyserFabricMod getInstance() {
- return instance;
- }
-}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java
deleted file mode 100644
index 8da7c8512..000000000
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java
+++ /dev/null
@@ -1,79 +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.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.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 {
- private final GeyserCommand command;
-
- public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command) {
- super(connector, Collections.singletonMap(command.name(), command));
- this.command = command;
- }
-
- public boolean testPermission(CommandSourceStack source) {
- return Permissions.check(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0);
- }
-
- @Override
- public int run(CommandContext context) {
- return runWithArgs(context, "");
- }
-
- public int runWithArgs(CommandContext context, String args) {
- CommandSourceStack source = (CommandSourceStack) context.getSource();
- FabricCommandSender sender = new FabricCommandSender(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()));
- return 0;
- }
-
- command.execute(session, sender, args.split(" "));
- return 0;
- }
-}
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 923db9b25..000000000
--- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java
+++ /dev/null
@@ -1,285 +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.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 javax.annotation.Nonnull;
-import java.util.ArrayList;
-import java.util.List;
-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;
- }
-
- LevelChunk chunk = player.level().getChunk(x, z);
- final int chunkBlockX = x << 4;
- final int chunkBlockZ = z << 4;
- 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;
- }
-
- 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
- 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(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 = compoundTag.get(key);
- tag.accept(visitor);
- visitor.root.put(visitor.currentTag);
- }
- return visitor.root;
- }
-
- @Override
- public void visitEnd(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..57f11b2c7
--- /dev/null
+++ b/bootstrap/mod/build.gradle.kts
@@ -0,0 +1,23 @@
+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)
+ compileOnly(libs.mixinextras)
+
+ // Only here to suppress "unknown enum constant EnvType.CLIENT" warnings. DO NOT USE!
+ compileOnly(libs.fabric.loader)
+}
diff --git a/bootstrap/mod/fabric/build.gradle.kts b/bootstrap/mod/fabric/build.gradle.kts
new file mode 100644
index 000000000..fd9d7e99d
--- /dev/null
+++ b/bootstrap/mod/fabric/build.gradle.kts
@@ -0,0 +1,62 @@
+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.raknet) { isTransitive = false }
+ 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 }
+
+ modImplementation(libs.cloud.fabric)
+ include(libs.cloud.fabric)
+}
+
+tasks.withType {
+ manifest.attributes["Main-Class"] = "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")
+ }
+}
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..149246d59
--- /dev/null
+++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.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.platform.fabric;
+
+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.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.command.CommandRegistry;
+import org.geysermc.geyser.command.CommandSourceConverter;
+import org.geysermc.geyser.command.GeyserCommandSource;
+import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
+import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
+import org.geysermc.geyser.platform.mod.command.ModCommandSource;
+import org.incendo.cloud.CommandManager;
+import org.incendo.cloud.execution.ExecutionCoordinator;
+import org.incendo.cloud.fabric.FabricServerCommandManager;
+
+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();
+
+ var sourceConverter = CommandSourceConverter.layered(
+ CommandSourceStack.class,
+ id -> getServer().getPlayerList().getPlayer(id),
+ Player::createCommandSourceStack,
+ () -> getServer().createCommandSourceStack(), // NPE if method reference is used, since server is not available yet
+ ModCommandSource::new
+ );
+ CommandManager cloud = new FabricServerCommandManager<>(
+ ExecutionCoordinator.simpleCoordinator(),
+ sourceConverter
+ );
+ this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud, false)); // applying root permission would be a breaking change because we can't register permission defaults
+ }
+
+ @Override
+ public boolean isServer() {
+ return FabricLoader.getInstance().getEnvironmentType().equals(EnvType.SERVER);
+ }
+}
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 90%
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
index ee986ee62..75da9125f 100644
--- 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
@@ -43,21 +43,27 @@ import java.util.stream.Collectors;
@Getter
public class GeyserFabricDumpInfo extends BootstrapDumpInfo {
- private String platformVersion = null;
+ private final String platformName;
+ private String platformVersion;
+ private final String minecraftVersion;
private final EnvType environmentType;
@AsteriskSerializer.Asterisk(isIp = true)
private final String serverIP;
private final int serverPort;
+ private final boolean onlineMode;
private final List mods;
public GeyserFabricDumpInfo(MinecraftServer server) {
+ this.platformName = server.getServerModName();
FabricLoader.getInstance().getModContainer("fabricloader").ifPresent(mod ->
this.platformVersion = mod.getMetadata().getVersion().getFriendlyString());
+ this.minecraftVersion = server.getServerVersion();
this.environmentType = FabricLoader.getInstance().getEnvironmentType();
this.serverIP = server.getLocalIp() == null ? "unknown" : server.getLocalIp();
this.serverPort = server.getPort();
+ this.onlineMode = server.usesAuthentication();
this.mods = new ArrayList<>();
for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {
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 4c442017a..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.14.21",
+ "fabricloader": ">=0.15.11",
"fabric": "*",
- "minecraft": ">=1.20",
- "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..81a35a58b
--- /dev/null
+++ b/bootstrap/mod/neoforge/build.gradle.kts
@@ -0,0 +1,59 @@
+// This is provided by "org.cloudburstmc.math.mutable" too, so yeet.
+// NeoForge's class loader is *really* annoying.
+provided("org.cloudburstmc.math", "api")
+provided("com.google.errorprone", "error_prone_annotations")
+
+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)
+
+ modImplementation(libs.cloud.neoforge)
+ include(libs.cloud.neoforge)
+}
+
+tasks.withType {
+ manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.neoforge.GeyserNeoForgeMain"
+}
+
+tasks {
+ remapJar {
+ archiveBaseName.set("Geyser-NeoForge")
+ }
+
+ remapModrinthJar {
+ archiveBaseName.set("geyser-neoforge")
+ }
+}
+
+modrinth {
+ loaders.add("neoforge")
+ uploadFile.set(tasks.getByPath("remapModrinthJar"))
+}
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..7d3b9dc5f
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java
@@ -0,0 +1,127 @@
+/*
+ * 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.bus.api.EventPriority;
+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 net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
+import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
+import org.geysermc.geyser.command.CommandSourceConverter;
+import org.geysermc.geyser.command.GeyserCommandSource;
+import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
+import org.geysermc.geyser.platform.mod.GeyserModUpdateListener;
+import org.geysermc.geyser.platform.mod.command.ModCommandSource;
+import org.incendo.cloud.CommandManager;
+import org.incendo.cloud.execution.ExecutionCoordinator;
+import org.incendo.cloud.neoforge.NeoForgeServerCommandManager;
+
+import java.util.Objects;
+
+@Mod(ModConstants.MOD_ID)
+public class GeyserNeoForgeBootstrap extends GeyserModBootstrap {
+
+ 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(EventPriority.HIGHEST, this::onPermissionGather);
+
+ this.onGeyserInitialize();
+
+ var sourceConverter = CommandSourceConverter.layered(
+ CommandSourceStack.class,
+ id -> getServer().getPlayerList().getPlayer(id),
+ Player::createCommandSourceStack,
+ () -> getServer().createCommandSourceStack(),
+ ModCommandSource::new
+ );
+ CommandManager cloud = new NeoForgeServerCommandManager<>(
+ ExecutionCoordinator.simpleCoordinator(),
+ sourceConverter
+ );
+ GeyserNeoForgeCommandRegistry registry = new GeyserNeoForgeCommandRegistry(getGeyser(), cloud);
+ this.setCommandRegistry(registry);
+ NeoForge.EVENT_BUS.addListener(EventPriority.LOWEST, registry::onPermissionGatherForUndefined);
+ }
+
+ 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();
+ }
+
+ private void onPermissionGather(PermissionGatherEvent.Nodes event) {
+ getGeyser().eventBus().fire(
+ (GeyserRegisterPermissionsEvent) (permission, defaultValue) -> {
+ Objects.requireNonNull(permission, "permission");
+ Objects.requireNonNull(defaultValue, "permission default for " + permission);
+
+ if (permission.isBlank()) {
+ return;
+ }
+ PermissionUtils.register(permission, defaultValue, event);
+ }
+ );
+ }
+}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java
new file mode 100644
index 000000000..a8854d5d9
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java
@@ -0,0 +1,101 @@
+/*
+ * 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.platform.neoforge;
+
+import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
+import org.geysermc.geyser.api.util.TriState;
+import org.geysermc.geyser.command.CommandRegistry;
+import org.geysermc.geyser.command.GeyserCommand;
+import org.geysermc.geyser.command.GeyserCommandSource;
+import org.incendo.cloud.CommandManager;
+import org.incendo.cloud.neoforge.PermissionNotRegisteredException;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class GeyserNeoForgeCommandRegistry extends CommandRegistry {
+
+ /**
+ * Permissions with an undefined permission default. Use Set to not register the same fallback more than once.
+ * NeoForge requires that all permissions are registered, and cloud-neoforge follows that.
+ * This is unlike most platforms, on which we wouldn't register a permission if no default was provided.
+ */
+ private final Set undefinedPermissions = new HashSet<>();
+
+ public GeyserNeoForgeCommandRegistry(GeyserImpl geyser, CommandManager cloud) {
+ super(geyser, cloud);
+ }
+
+ @Override
+ protected void register(GeyserCommand command, Map commands) {
+ super.register(command, commands);
+
+ // FIRST STAGE: Collect all permissions that may have undefined defaults.
+ if (!command.permission().isBlank() && command.permissionDefault() == null) {
+ // Permission requirement exists but no default value specified.
+ undefinedPermissions.add(command.permission());
+ }
+ }
+
+ @Override
+ protected void onRegisterPermissions(GeyserRegisterPermissionsEvent event) {
+ super.onRegisterPermissions(event);
+
+ // SECOND STAGE
+ // Now that we are aware of all commands, we can eliminate some incorrect assumptions.
+ // Example: two commands may have the same permission, but only of them defines a permission default.
+ undefinedPermissions.removeAll(permissionDefaults.keySet());
+ }
+
+ /**
+ * Registers permissions with possibly undefined defaults.
+ * Should be subscribed late to allow extensions and mods to register a desired permission default first.
+ */
+ void onPermissionGatherForUndefined(PermissionGatherEvent.Nodes event) {
+ // THIRD STAGE
+ for (String permission : undefinedPermissions) {
+ if (PermissionUtils.register(permission, TriState.NOT_SET, event)) {
+ // The permission was not already registered
+ geyser.getLogger().debug("Registered permission " + permission + " with fallback default value of NOT_SET");
+ }
+ }
+ }
+
+ @Override
+ public boolean hasPermission(GeyserCommandSource source, String permission) {
+ // NeoForgeServerCommandManager will throw this exception if the permission is not registered to the server.
+ // We can't realistically ensure that every permission is registered (calls by API users), so we catch this.
+ // This works for our calls, but not for cloud's internal usage. For that case, see above.
+ try {
+ return super.hasPermission(source, permission);
+ } catch (PermissionNotRegisteredException e) {
+ return false;
+ }
+ }
+}
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/core/src/main/java/org/geysermc/geyser/translator/collision/SpawnerCollision.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeMain.java
similarity index 71%
rename from core/src/main/java/org/geysermc/geyser/translator/collision/SpawnerCollision.java
rename to bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeMain.java
index 7d4dfedc2..70bac2a40 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/collision/SpawnerCollision.java
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeMain.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,16 +23,23 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.translator.collision;
+package org.geysermc.geyser.platform.neoforge;
-import lombok.EqualsAndHashCode;
+import org.geysermc.geyser.GeyserMain;
-@EqualsAndHashCode(callSuper = true)
-@CollisionRemapper(regex = "^spawner$")
-public class SpawnerCollision extends SolidCollision {
- public SpawnerCollision(String params) {
- super(params);
- // Increase pushAwayTolerance to work around https://bugs.mojang.com/browse/MCPE-41996
- pushAwayTolerance = 0.0002;
+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/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/licenseheader.txt b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/ModConstants.java
similarity index 85%
rename from licenseheader.txt
rename to bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/ModConstants.java
index 9bfe117f9..aa72bb2a0 100644
--- a/licenseheader.txt
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/ModConstants.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
@@ -21,4 +21,10 @@
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
- */
\ No newline at end of file
+ */
+
+package org.geysermc.geyser.platform.neoforge;
+
+public class ModConstants {
+ public static final String MOD_ID = "geyser_neoforge";
+}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/PermissionUtils.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/PermissionUtils.java
new file mode 100644
index 000000000..c57dc9a6c
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/PermissionUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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.neoforge;
+
+import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent;
+import net.neoforged.neoforge.server.permission.nodes.PermissionNode;
+import net.neoforged.neoforge.server.permission.nodes.PermissionTypes;
+import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent;
+import org.geysermc.geyser.api.util.TriState;
+import org.geysermc.geyser.platform.neoforge.mixin.PermissionNodeMixin;
+
+/**
+ * Common logic for handling the more complicated way we have to register permission on NeoForge
+ */
+public class PermissionUtils {
+
+ private PermissionUtils() {
+ //no
+ }
+
+ /**
+ * Registers the given permission and its default value to the event. If the permission has the same name as one
+ * that has already been registered to the event, it will not be registered. In other words, it will not override.
+ *
+ * @param permission the permission to register
+ * @param permissionDefault the permission's default value. See {@link GeyserRegisterPermissionsEvent#register(String, TriState)} for TriState meanings.
+ * @param event the registration event
+ * @return true if the permission was registered
+ */
+ public static boolean register(String permission, TriState permissionDefault, PermissionGatherEvent.Nodes event) {
+ // NeoForge likes to crash if you try and register a duplicate node
+ if (event.getNodes().stream().noneMatch(n -> n.getNodeName().equals(permission))) {
+ PermissionNode node = createNode(permission, permissionDefault);
+ event.addNodes(node);
+ return true;
+ }
+ return false;
+ }
+
+ private static PermissionNode createNode(String node, TriState permissionDefault) {
+ return PermissionNodeMixin.geyser$construct(
+ node,
+ PermissionTypes.BOOLEAN,
+ (player, playerUUID, context) -> switch (permissionDefault) {
+ case TRUE -> true;
+ case FALSE -> false;
+ case NOT_SET -> {
+ if (player != null) {
+ yield player.createCommandSourceStack().hasPermission(player.server.getOperatorUserPermissionLevel());
+ }
+ yield false; // NeoForge javadocs say player is null in the case of an offline player.
+ }
+ }
+ );
+ }
+}
diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/mixin/PermissionNodeMixin.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/mixin/PermissionNodeMixin.java
new file mode 100644
index 000000000..a43acd58a
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/mixin/PermissionNodeMixin.java
@@ -0,0 +1,48 @@
+/*
+ * 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.platform.neoforge.mixin;
+
+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 org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Invoker;
+
+@Mixin(value = PermissionNode.class, remap = false) // this is API - do not remap
+public interface PermissionNodeMixin {
+
+ /**
+ * Invokes the matching private constructor in {@link PermissionNode}.
+ *
+ * The typical constructors in PermissionNode require a mod id, which means our permission nodes
+ * would end up becoming {@code geyser_neoforge.} instead of just {@code }.
+ */
+ @SuppressWarnings("rawtypes") // the varargs
+ @Invoker("")
+ static PermissionNode geyser$construct(String nodeName, PermissionType type, PermissionNode.PermissionResolver defaultResolver, PermissionDynamicContextKey... dynamics) {
+ throw new IllegalStateException();
+ }
+}
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..56b7d68e1
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/resources/META-INF/neoforge.mods.toml
@@ -0,0 +1,27 @@
+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"
+[[mixins]]
+config = "geyser_neoforge.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/neoforge/src/main/resources/geyser_neoforge.mixins.json b/bootstrap/mod/neoforge/src/main/resources/geyser_neoforge.mixins.json
new file mode 100644
index 000000000..f1653051c
--- /dev/null
+++ b/bootstrap/mod/neoforge/src/main/resources/geyser_neoforge.mixins.json
@@ -0,0 +1,12 @@
+{
+ "required": true,
+ "minVersion": "0.8",
+ "package": "org.geysermc.geyser.platform.neoforge.mixin",
+ "compatibilityLevel": "JAVA_17",
+ "mixins": [
+ "PermissionNodeMixin"
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
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/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
new file mode 100644
index 000000000..f11b5fbd6
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java
@@ -0,0 +1,236 @@
+/*
+ * 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;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import net.minecraft.server.MinecraftServer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.GeyserBootstrap;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.GeyserLogger;
+import org.geysermc.geyser.command.CommandRegistry;
+import org.geysermc.geyser.configuration.GeyserConfiguration;
+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.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.UUID;
+
+@RequiredArgsConstructor
+public abstract class GeyserModBootstrap implements GeyserBootstrap {
+
+ @Getter
+ private static GeyserModBootstrap instance;
+
+ private final GeyserModPlatform platform;
+
+ @Getter
+ private GeyserImpl geyser;
+ private Path dataFolder;
+
+ @Setter @Getter
+ private MinecraftServer server;
+
+ @Setter
+ private CommandRegistry commandRegistry;
+ private GeyserModConfiguration geyserConfig;
+ private GeyserModInjector geyserInjector;
+ private final GeyserModLogger geyserLogger = new GeyserModLogger();
+ private IGeyserPingPassthrough geyserPingPassthrough;
+ private WorldManager geyserWorldManager;
+
+ @Override
+ public void onGeyserInitialize() {
+ instance = this;
+ dataFolder = this.platform.dataFolder(this.platform.configPath());
+ GeyserLocale.init(this);
+ if (!loadConfig()) {
+ return;
+ }
+ this.geyserLogger.setDebug(geyserConfig.isDebugMode());
+ GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
+ this.geyser = GeyserImpl.load(this.platform.platformType(), this);
+ }
+
+ public void onGeyserEnable() {
+ if (GeyserImpl.getInstance().isReloading()) {
+ if (!loadConfig()) {
+ return;
+ }
+ this.geyserLogger.setDebug(geyserConfig.isDebugMode());
+ GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
+ }
+
+ GeyserImpl.start();
+
+ if (geyserConfig.isLegacyPingPassthrough()) {
+ this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
+ } else {
+ this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger);
+ }
+
+ // No need to re-register commands, or try to re-inject
+ if (GeyserImpl.getInstance().isReloading()) {
+ return;
+ }
+
+ 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);
+ }
+ }
+
+ @Override
+ public void onGeyserDisable() {
+ if (geyser != null) {
+ geyser.disable();
+ }
+ }
+
+ @Override
+ public void onGeyserShutdown() {
+ if (geyser != null) {
+ geyser.shutdown();
+ geyser = null;
+ }
+ if (geyserInjector != null) {
+ geyserInjector.shutdown();
+ this.server = null;
+ }
+ }
+
+ @Override
+ public GeyserModConfiguration getGeyserConfig() {
+ return geyserConfig;
+ }
+
+ @Override
+ public GeyserLogger getGeyserLogger() {
+ return geyserLogger;
+ }
+
+ @Override
+ public CommandRegistry getCommandRegistry() {
+ return commandRegistry;
+ }
+
+ @Override
+ public IGeyserPingPassthrough getGeyserPingPassthrough() {
+ return geyserPingPassthrough;
+ }
+
+ @Override
+ public WorldManager getWorldManager() {
+ return geyserWorldManager;
+ }
+
+ @Override
+ public Path getConfigFolder() {
+ return dataFolder;
+ }
+
+ @Override
+ public BootstrapDumpInfo getDumpInfo() {
+ return this.platform.dumpInfo(this.server);
+ }
+
+ @Override
+ public String getMinecraftServerVersion() {
+ return this.server.getServerVersion();
+ }
+
+ @SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null
+ @NonNull
+ @Override
+ public String getServerBindAddress() {
+ String ip = this.server.getLocalIp();
+ return ip != null ? ip : ""; // See issue #3812
+ }
+
+ @Override
+ public SocketAddress getSocketAddress() {
+ return this.geyserInjector.getServerSocketAddress();
+ }
+
+ @Override
+ public int getServerPort() {
+ if (isServer()) {
+ return ((GeyserServerPortGetter) server).geyser$getServerPort();
+ } else {
+ // Set in the IntegratedServerMixin
+ return geyserConfig.getRemote().port();
+ }
+ }
+
+ public abstract boolean isServer();
+
+ @Override
+ public boolean testFloodgatePluginPresent() {
+ return this.platform.testFloodgatePluginPresent(this);
+ }
+
+ @Nullable
+ @Override
+ public InputStream getResourceOrNull(String resource) {
+ return this.platform.resolveResource(resource);
+ }
+
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ private boolean loadConfig() {
+ try {
+ 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;
+ }
+ }
+}
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 62%
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..6a724155f 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,23 @@
* @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 org.geysermc.geyser.Constants;
-import org.geysermc.geyser.platform.fabric.command.FabricCommandSender;
+import net.minecraft.world.entity.player.Player;
+import org.geysermc.geyser.Permissions;
+import org.geysermc.geyser.platform.mod.command.ModCommandSource;
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) {
+ // Should be creating this in the supplier, but we need it for the permission check.
+ // Not a big deal currently because ModCommandSource doesn't load locale, so don't need to try to wait for it.
+ ModCommandSource source = new ModCommandSource(player.createCommandSourceStack());
+ if (source.hasPermission(Permissions.CHECK_UPDATE)) {
+ VersionCheckUtils.checkForGeyserUpdate(() -> source);
}
}
- 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/mod/src/main/java/org/geysermc/geyser/platform/mod/ModPingPassthrough.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/ModPingPassthrough.java
new file mode 100644
index 000000000..a2bbfa379
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/ModPingPassthrough.java
@@ -0,0 +1,112 @@
+/*
+ * 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 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;
+import net.minecraft.network.protocol.PacketFlow;
+import net.minecraft.network.protocol.status.ClientboundStatusResponsePacket;
+import net.minecraft.network.protocol.status.ServerStatus;
+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 java.net.InetSocketAddress;
+import java.util.Objects;
+
+@AllArgsConstructor
+public class ModPingPassthrough implements IGeyserPingPassthrough {
+
+ private static final GsonComponentSerializer GSON_SERIALIZER = GsonComponentSerializer.gson();
+ private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.legacySection();
+
+ private final MinecraftServer server;
+ private final GeyserLogger logger;
+
+ @Nullable
+ @Override
+ public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) {
+ ServerStatus status = server.getStatus();
+ if (status == null) {
+ return null;
+ }
+
+ try {
+ StatusInterceptor connection = new StatusInterceptor();
+ ServerStatusPacketListener statusPacketListener = new ServerStatusPacketListenerImpl(status, connection);
+
+ 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) {
+ if (logger.isDebug()) {
+ logger.debug("Failed to listen for modified ServerStatus: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ 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(
+ legacyDescription,
+ status.players().map(ServerStatus.Players::max).orElse(1),
+ status.players().map(ServerStatus.Players::online).orElse(0)
+ );
+ }
+
+ /**
+ * Custom Connection that intercepts the status response right before it is sent
+ */
+ private static class StatusInterceptor extends Connection {
+
+ ServerStatus status;
+
+ StatusInterceptor() {
+ super(PacketFlow.SERVERBOUND); // we are the server.
+ }
+
+ @Override
+ public void send(@NonNull Packet> packet, @Nullable PacketSendListener packetSendListener, boolean bl) {
+ if (packet instanceof ClientboundStatusResponsePacket statusResponse) {
+ status = statusResponse.status();
+ }
+ super.send(packet, packetSendListener, bl);
+ }
+ }
+}
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/ModCommandSource.java
similarity index 67%
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/ModCommandSource.java
index 6517ac133..af1f368b3 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/ModCommandSource.java
@@ -23,25 +23,29 @@
* @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.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.text.ChatColor;
-import javax.annotation.Nonnull;
+import java.util.Objects;
+import java.util.UUID;
-public class FabricCommandSender implements GeyserCommandSource {
+public class ModCommandSource implements GeyserCommandSource {
private final CommandSourceStack source;
- public FabricCommandSender(CommandSourceStack source) {
+ public ModCommandSource(CommandSourceStack source) {
this.source = source;
+ // todo find locale?
}
@Override
@@ -50,7 +54,7 @@ public class FabricCommandSender implements GeyserCommandSource {
}
@Override
- public void sendMessage(@Nonnull String message) {
+ public void sendMessage(@NonNull String message) {
if (source.getEntity() instanceof ServerPlayer) {
((ServerPlayer) source.getEntity()).displayClientMessage(Component.literal(message), false);
} else {
@@ -62,7 +66,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(Component.Serializer.fromJson(decoded), false);
+ player.displayClientMessage(Objects.requireNonNull(Component.Serializer.fromJson(decoded, RegistryAccess.EMPTY)), false);
return;
}
GeyserCommandSource.super.sendMessage(message);
@@ -73,8 +77,24 @@ public class FabricCommandSender implements GeyserCommandSource {
return !(source.getEntity() instanceof ServerPlayer);
}
+ @Override
+ public @Nullable UUID playerUuid() {
+ if (source.getEntity() instanceof ServerPlayer player) {
+ return player.getUUID();
+ }
+ return null;
+ }
+
@Override
public boolean hasPermission(String permission) {
- return Permissions.check(source, permission, source.getServer().getOperatorUserPermissionLevel());
+ // Unlike other bootstraps; we delegate to cloud here too:
+ // On NeoForge; we'd have to keep track of all PermissionNodes - cloud already does that
+ // For Fabric, we won't need to include the Fabric Permissions API anymore - cloud already does that too :p
+ return GeyserImpl.getInstance().commandRegistry().hasPermission(this, permission);
+ }
+
+ @Override
+ public Object handle() {
+ return source;
}
}
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 79%
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 942909068..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,17 +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.platform.fabric.GeyserFabricMod;
-import org.geysermc.geyser.platform.fabric.GeyserServerPortGetter;
+import org.geysermc.geyser.GeyserImpl;
+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;
@@ -42,7 +41,8 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-@Environment(EnvType.CLIENT)
+import java.util.Objects;
+
@Mixin(IntegratedServer.class)
public class IntegratedServerMixin implements GeyserServerPortGetter {
@Shadow
@@ -54,12 +54,16 @@ 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
+ Objects.requireNonNull(this.minecraft.player);
this.minecraft.player.displayClientMessage(Component.literal(GeyserLocale.getPlayerLocaleString("geyser.core.start",
- this.minecraft.options.languageCode, "localhost", String.valueOf(this.publishedPort))), false);
+ this.minecraft.options.languageCode, "localhost", String.valueOf(GeyserImpl.getInstance().bedrockListener().port()))), false);
}
}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/BlockPlaceMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/BlockPlaceMixin.java
new file mode 100644
index 000000000..98620588e
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/BlockPlaceMixin.java
@@ -0,0 +1,79 @@
+/*
+ * 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.mod.mixin.server;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.BlockItem;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.context.BlockPlaceContext;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.SoundType;
+import net.minecraft.world.level.block.state.BlockState;
+import org.cloudburstmc.math.vector.Vector3f;
+import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
+import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.session.GeyserSession;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+@Mixin(BlockItem.class)
+public class BlockPlaceMixin {
+
+ @Inject(method = "place", locals = LocalCapture.CAPTURE_FAILSOFT, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/core/BlockPos;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V"))
+ private void geyser$hijackPlaySound(BlockPlaceContext blockPlaceContext, CallbackInfoReturnable cir, BlockPlaceContext blockPlaceContext2, BlockState blockState, BlockPos blockPos, Level level, Player player, ItemStack itemStack, BlockState blockState2, SoundType soundType) {
+ if (player == null) {
+ return;
+ }
+
+ GeyserSession session = GeyserImpl.getInstance().connectionByUuid(player.getUUID());
+ if (session == null) {
+ return;
+ }
+
+ Vector3f position = Vector3f.from(
+ blockPos.getX(),
+ blockPos.getY(),
+ blockPos.getZ()
+ );
+
+ LevelSoundEventPacket placeBlockSoundPacket = new LevelSoundEventPacket();
+ placeBlockSoundPacket.setSound(SoundEvent.PLACE);
+ placeBlockSoundPacket.setPosition(position);
+ placeBlockSoundPacket.setBabySound(false);
+ placeBlockSoundPacket.setExtraData(session.getBlockMappings().getBedrockBlockId(Block.BLOCK_STATE_REGISTRY.getId(blockState2)));
+ placeBlockSoundPacket.setIdentifier(":");
+ session.sendUpstreamPacket(placeBlockSoundPacket);
+ session.setLastBlockPlacePosition(null);
+ session.setLastBlockPlaced(null);
+ }
+}
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/PistonBaseBlockMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/PistonBaseBlockMixin.java
new file mode 100644
index 000000000..6ac51ba52
--- /dev/null
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/PistonBaseBlockMixin.java
@@ -0,0 +1,127 @@
+/*
+ * 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.mod.mixin.server;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.sugar.Share;
+import com.llamalad7.mixinextras.sugar.ref.LocalRef;
+import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.piston.PistonBaseBlock;
+import net.minecraft.world.level.block.state.BlockState;
+import org.cloudburstmc.math.vector.Vector3i;
+import org.geysermc.geyser.GeyserImpl;
+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 org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+@Mixin(PistonBaseBlock.class)
+public class PistonBaseBlockMixin {
+
+ @Shadow
+ @Final
+ private boolean isSticky;
+
+ @ModifyExpressionValue(method = "moveBlocks",
+ at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;")
+ )
+ private HashMap geyser$onMapCreate(HashMap original, @Share("pushBlocks") LocalRef