Merge remote-tracking branch 'upstream/master' into rp

# Conflicts:
#	core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java
#	core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java
This commit is contained in:
onebeastchris 2024-02-16 17:19:23 +01:00
commit a4fa2e611c
70 changed files with 14264 additions and 587 deletions

View file

@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
### Currently supporting Minecraft Bedrock 1.20.40 - 1.20.51 and Minecraft Java 1.20.4
### Currently supporting Minecraft Bedrock 1.20.40 - 1.20.61 and Minecraft Java 1.20.4
## Setting Up
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.

View file

@ -4,4 +4,5 @@ plugins {
dependencies {
api(libs.base.api)
api(libs.math)
}

View file

@ -0,0 +1,148 @@
/*
* 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()}.
* <p>
* 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.
* <p>
* 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.<br>
* 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.
* <p>
* Fog IDs can be found <a href="https://wiki.bedrock.dev/documentation/fog-ids.html">here</a>
*
* @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<String> 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();
}

View file

@ -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.
* <p>
* See <a href="https://easings.net/">https://easings.net/</a> 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;
}
}

View file

@ -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.
* <p>
* 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();
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.bedrock.camera;
/**
* Represents a camera perspective that a player's camera can take.
* All perspectives except for {@link #FREE} are locked to the player's head,
* and are therefore relative to the player's position and rotation.
*/
public enum CameraPerspective {
FIRST_PERSON("minecraft:first_person"),
FREE("minecraft:free"),
THIRD_PERSON("minecraft:third_person"),
THIRD_PERSON_FRONT("minecraft:third_person_front");
private final String id;
CameraPerspective(String id) {
this.id = id;
}
public String id() {
return this.id;
}
}

View file

@ -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()}.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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();
}
}

View file

@ -25,6 +25,9 @@
package org.geysermc.geyser.api.bedrock.camera;
/**
* Represents a camera shake instruction. Can be sent in {@link CameraData#shakeCamera(float, float, CameraShake)}
*/
public enum CameraShake {
POSITIONAL,
ROTATIONAL

View file

@ -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.<br><br>
* Shakes the client's camera.
* <p>
* 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.<br>
* If the existing shake type is different and the new intensity/duration are not positive, the existing shake only
@ -66,12 +90,18 @@ public interface GeyserConnection extends Connection, CommandSource {
* @param intensity the intensity of the shake. The client has a maximum total intensity of 4.
* @param duration the time in seconds that the shake will occur for
* @param type the type of shake
*
* @deprecated Use {@link CameraData#shakeCamera(float, float, CameraShake)} instead.
*/
@Deprecated
void shakeCamera(float intensity, float duration, @NonNull CameraShake type);
/**
* Stops all camera shake of any type.
*
* @deprecated Use {@link CameraData#stopCameraShake()} instead.
*/
@Deprecated
void stopCameraShake();
/**
@ -80,19 +110,26 @@ public interface GeyserConnection extends Connection, CommandSource {
* Fog IDs can be found <a href="https://wiki.bedrock.dev/documentation/fog-ids.html">here</a>
*
* @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<String> fogEffects();
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.api.entity;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* This class holds all the methods that relate to entities.
* Can be accessed through {@link GeyserConnection#entities()}.
*/
public interface EntityData {
/**
* Returns a {@link GeyserEntity} to e.g. make them play an emote.
*
* @param javaId the Java entity ID to look up
* @return a {@link GeyserEntity} if present in this connection's entity tracker
*/
@NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId);
/**
* Displays a player entity as emoting to this client.
*
* @param emoter the player entity emoting
* @param emoteId the emote ID to send to this client
*/
void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId);
/**
* Gets the {@link GeyserPlayerEntity} of this connection.
*
* @return the {@link GeyserPlayerEntity} of this connection
*/
@NonNull GeyserPlayerEntity playerEntity();
/**
* (Un)locks the client's movement inputs, so that they cannot move.
* To ensure that movement is only unlocked when all locks are released, you must supply
* a UUID with this method, and use the same UUID to unlock the camera.
*
* @param lock whether to lock the movement
* @param owner the owner of the lock
* @return if the movement is locked after this method call
*/
boolean lockMovement(boolean lock, @NonNull UUID owner);
/**
* Returns whether the client's movement is currently locked.
*
* @return whether the movement is locked
*/
boolean isMovementLocked();
}

View file

@ -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();
}

View file

@ -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<EventRegistrar> eventBus) implements Event {
}

View file

@ -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<EventRegistrar> eventBus) implements Event {
}

View file

@ -32,11 +32,11 @@ import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.ProtocolConstants;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
@ -70,11 +70,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
private GeyserImpl geyser;
private static boolean INITIALIZED = false;
@SuppressWarnings({"JavaReflectionMemberAccess", "ResultOfMethodCallIgnored"})
@Override
public void onLoad() {
onGeyserInitialize();
}
@Override
public void onGeyserInitialize() {
GeyserLocale.init(this);
// Copied from ViaVersion.
@ -91,29 +93,62 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
getLogger().warning("/_____________\\");
}
if (!getDataFolder().exists())
getDataFolder().mkdir();
try {
if (!getDataFolder().exists())
getDataFolder().mkdir();
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"),
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class);
} catch (IOException ex) {
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
if (!this.loadConfig()) {
return;
}
this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this);
this.geyserInjector = new GeyserBungeeInjector(this);
}
@Override
public void onEnable() {
// Big hack - Bungee does not provide us an event to listen to, so schedule a repeating
// task that waits for a field to be filled which is set after the plugin enable
// process is complete
this.awaitStartupCompletion(0);
}
@SuppressWarnings("unchecked")
private void awaitStartupCompletion(int tries) {
// After 20 tries give up waiting. This will happen just after 3 minutes approximately
if (tries >= 20) {
this.geyserLogger.warning("BungeeCord plugin startup is taking abnormally long, so Geyser is starting now. " +
"If all your plugins are loaded properly, this is a bug! " +
"If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times.");
this.onGeyserEnable();
return;
}
try {
Field listenersField = BungeeCord.getInstance().getClass().getDeclaredField("listeners");
listenersField.setAccessible(true);
Collection<Channel> listeners = (Collection<Channel>) listenersField.get(BungeeCord.getInstance());
if (listeners.isEmpty()) {
this.getProxy().getScheduler().schedule(this, this::onGeyserEnable, tries, TimeUnit.SECONDS);
} else {
this.awaitStartupCompletion(++tries);
}
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
}
public void onGeyserEnable() {
if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) {
return;
}
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
} else {
// For consistency with other platforms - create command manager before GeyserImpl#start()
// This ensures the command events are called before the item/block ones are
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
}
// Force-disable query if enabled, or else Geyser won't enable
for (ListenerInfo info : getProxy().getConfig().getListeners()) {
@ -133,54 +168,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
}
// Big hack - Bungee does not provide us an event to listen to, so schedule a repeating
// task that waits for a field to be filled which is set after the plugin enable
// process is complete
if (!INITIALIZED) {
this.awaitStartupCompletion(0);
} else {
// No need to "wait" for startup completion, just start Geyser - we're reloading.
this.postStartup();
}
}
GeyserImpl.start();
@SuppressWarnings("unchecked")
private void awaitStartupCompletion(int tries) {
// After 20 tries give up waiting. This will happen
// just after 3 minutes approximately
if (tries >= 20) {
this.geyserLogger.warning("BungeeCord plugin startup is taking abnormally long, so Geyser is starting now. " +
"If all your plugins are loaded properly, this is a bug! " +
"If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times.");
this.postStartup();
if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else {
this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
}
// No need to re-register commands or re-init injector when reloading
if (GeyserImpl.getInstance().isReloading()) {
return;
}
try {
Field listenersField = BungeeCord.getInstance().getClass().getDeclaredField("listeners");
listenersField.setAccessible(true);
Collection<Channel> listeners = (Collection<Channel>) listenersField.get(BungeeCord.getInstance());
if (listeners.isEmpty()) {
this.getProxy().getScheduler().schedule(this, this::postStartup, tries, TimeUnit.SECONDS);
} else {
this.awaitStartupCompletion(++tries);
}
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
}
private void postStartup() {
GeyserImpl.start();
if (!INITIALIZED) {
this.geyserInjector = new GeyserBungeeInjector(this);
this.geyserInjector.initializeLocalChannel(this);
}
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
this.geyserInjector.initializeLocalChannel(this);
this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands()));
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
@ -191,18 +192,17 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands));
}
if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else {
this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
}
INITIALIZED = true;
}
@Override
public void onDisable() {
public void onGeyserDisable() {
if (geyser != null) {
geyser.disable();
}
}
@Override
public void onGeyserShutdown() {
if (geyser != null) {
geyser.shutdown();
}
@ -211,6 +211,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
}
}
@Override
public void onDisable() {
this.onGeyserShutdown();
}
@Override
public GeyserBungeeConfiguration getGeyserConfig() {
return geyserConfig;
@ -278,4 +283,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) {
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
return false;
}
return true;
}
}

View file

@ -27,6 +27,8 @@ package org.geysermc.geyser.platform.fabric;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import lombok.Getter;
import lombok.Setter;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
@ -66,13 +68,14 @@ import java.util.Optional;
import java.util.UUID;
public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
@Getter
private static GeyserFabricMod instance;
private boolean reloading;
private GeyserImpl geyser;
private ModContainer mod;
private Path dataFolder;
@Setter
private MinecraftServer server;
private GeyserCommandManager geyserCommandManager;
@ -85,64 +88,45 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
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);
}
onGeyserInitialize();
}
@Override
public void onEnable() {
dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric");
if (!dataFolder.toFile().exists()) {
//noinspection ResultOfMethodCallIgnored
dataFolder.toFile().mkdir();
public void onGeyserInitialize() {
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((server) -> {
this.server = server;
onGeyserEnable();
});
}
// Init dataFolder first as local language overrides call getConfigFolder()
GeyserLocale.init(this);
// These are only registered once
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onGeyserShutdown());
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler));
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();
dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric");
GeyserLocale.init(this);
if (!loadConfig()) {
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();
@Override
public void onGeyserEnable() {
if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) {
return;
}
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
} else {
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
}
if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
@ -150,8 +134,12 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger);
}
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
GeyserImpl.start();
// No need to re-register commands, or re-recreate the world manager when reloading
if (GeyserImpl.getInstance().isReloading()) {
return;
}
this.geyserWorldManager = new GeyserFabricWorldManager(server);
@ -201,14 +189,19 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
}
@Override
public void onDisable() {
public void onGeyserDisable() {
if (geyser != null) {
geyser.disable();
}
}
@Override
public void onGeyserShutdown() {
if (geyser != null) {
geyser.shutdown();
geyser = null;
}
if (!reloading) {
this.server = null;
}
this.server = null;
}
@Override
@ -291,11 +284,22 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
}
}
public void setReloading(boolean reloading) {
this.reloading = reloading;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean loadConfig() {
try {
if (!dataFolder.toFile().exists()) {
//noinspection ResultOfMethodCallIgnored
dataFolder.toFile().mkdir();
}
public static GeyserFabricMod getInstance() {
return instance;
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);
return true;
} catch (IOException ex) {
LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
return false;
}
}
}

View file

@ -32,7 +32,6 @@ 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;
@ -64,9 +63,6 @@ public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implement
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()));

View file

@ -57,7 +57,8 @@ public class IntegratedServerMixin implements GeyserServerPortGetter {
private void onOpenToLan(GameType gameType, boolean cheatsAllowed, int port, CallbackInfoReturnable<Boolean> cir) {
if (cir.getReturnValueZ()) {
// If the LAN is opened, starts Geyser.
GeyserFabricMod.getInstance().startGeyser((MinecraftServer) (Object) this);
GeyserFabricMod.getInstance().setServer((MinecraftServer) (Object) this);
GeyserFabricMod.getInstance().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

View file

@ -81,10 +81,6 @@ import java.util.UUID;
import java.util.logging.Level;
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
/**
* Determines if the plugin has been ran once before, including before /geyser reload.
*/
private static boolean INITIALIZED = false;
private GeyserSpigotCommandManager geyserCommandManager;
private GeyserSpigotConfiguration geyserConfig;
@ -102,6 +98,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
@Override
public void onLoad() {
onGeyserInitialize();
}
@Override
public void onGeyserInitialize() {
GeyserLocale.init(this);
try {
@ -118,6 +119,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
getLogger().severe("");
getLogger().severe("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
@ -131,6 +133,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
getLogger().severe("");
getLogger().severe("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
}
@ -143,86 +146,72 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
getLogger().severe("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
getLogger().severe("");
getLogger().severe("*********************************************");
return;
}
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
try {
if (!getDataFolder().exists()) {
//noinspection ResultOfMethodCallIgnored
getDataFolder().mkdir();
}
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml",
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
} catch (IOException ex) {
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
Bukkit.getPluginManager().disablePlugin(this);
return;
}
if (!loadConfig()) {
return;
}
this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode())
: new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Turn "(MC: 1.16.4)" into 1.16.4.
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this);
}
@Override
public void onEnable() {
if (this.geyserConfig == null) {
// We failed to initialize correctly
Bukkit.getPluginManager().disablePlugin(this);
return;
}
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
this.geyserCommandManager.init();
if (!INITIALIZED) {
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
Bukkit.getPluginManager().registerEvents(new Listener() {
// Because Bukkit locks its command map upon startup, we need to
// add our plugin commands in onEnable, but populating the executor
// can happen at any time (later in #onGeyserEnable())
CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
// Thanks again, Bukkit
try {
Constructor<PluginCommand> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
constructor.setAccessible(true);
@EventHandler
public void onServerLoaded(ServerLoadEvent event) {
// Wait until all plugins have loaded so Geyser can start
postStartup();
}
}, this);
PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
// Because Bukkit locks its command map upon startup, we need to
// add our plugin commands in onEnable, but populating the executor
// can happen at any time
CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
// Thanks again, Bukkit
try {
Constructor<PluginCommand> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
constructor.setAccessible(true);
PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
commandMap.register(extension.description().id(), "geyserext", pluginCommand);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
}
commandMap.register(extension.description().id(), "geyserext", pluginCommand);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
}
}
if (INITIALIZED) {
// Reload; continue with post startup
postStartup();
}
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
Bukkit.getPluginManager().registerEvents(new Listener() {
@EventHandler
public void onServerLoaded(ServerLoadEvent event) {
if (event.getType() == ServerLoadEvent.LoadType.RELOAD) {
geyser.setShuttingDown(false);
}
onGeyserEnable();
}
}, this);
}
private void postStartup() {
GeyserImpl.start();
public void onGeyserEnable() {
// Configs are loaded once early - so we can create the logger, then load extensions and finally register
// extension commands in #onEnable. To ensure reloading geyser also reloads the geyser config, this exists
if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) {
return;
}
this.geyserLogger.setDebug(this.geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
}
// Turn "(MC: 1.16.4)" into 1.16.4.
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
GeyserImpl.start();
if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
@ -238,20 +227,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
}
geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass()));
boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null;
if (isViaVersion) {
try {
// Ensure that we have the latest 4.0.0 changes and not an older ViaVersion version
Class.forName("com.viaversion.viaversion.api.ViaManager");
} catch (ClassNotFoundException e) {
GeyserSpigotVersionChecker.sendOutdatedViaVersionMessage(geyserLogger);
isViaVersion = false;
if (this.geyserConfig.isDebugMode()) {
e.printStackTrace();
}
}
// Don't need to re-create the world manager/re-register commands/reinject when reloading
if (GeyserImpl.getInstance().isReloading()) {
return;
}
boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null;
// Check to ensure the current setup can support the protocol version Geyser uses
GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
// We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib
// To do their job injecting, then connect into *that*
this.geyserInjector = new GeyserSpigotInjector(isViaVersion);
@ -278,6 +263,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
} else {
geyserLogger.debug("Not using NMS adapter as it is disabled via system property.");
}
if (this.geyserWorldManager == null) {
// No NMS adapter
this.geyserWorldManager = new GeyserSpigotWorldManager(this);
@ -302,72 +288,72 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands));
}
if (!INITIALIZED) {
// Register permissions so they appear in, for example, LuckPerms' UI
// Re-registering permissions throws an error
for (Map.Entry<String, Command> entry : geyserCommandManager.commands().entrySet()) {
// Register permissions so they appear in, for example, LuckPerms' UI
// Re-registering permissions throws an error
for (Map.Entry<String, Command> entry : geyserCommandManager.commands().entrySet()) {
Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) {
// Don't register aliases
continue;
}
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
// Register permissions for extension commands
for (Map.Entry<Extension, Map<String, Command>> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
for (Map.Entry<String, Command> entry : commandEntry.getValue().entrySet()) {
Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) {
// Don't register aliases
continue;
}
if (command.permission().isBlank()) {
continue;
}
// Avoid registering the same permission twice, e.g. for the extension help commands
if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
continue;
}
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
// Register permissions for extension commands
for (Map.Entry<Extension, Map<String, Command>> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
for (Map.Entry<String, Command> entry : commandEntry.getValue().entrySet()) {
Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) {
// Don't register aliases
continue;
}
if (command.permission().isBlank()) {
continue;
}
// Avoid registering the same permission twice, e.g. for the extension help commands
if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
continue;
}
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
}
Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
"Whether update notifications can be seen", PermissionDefault.OP));
// Events cannot be unregistered - re-registering results in duplicate firings
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
}
Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
"Whether update notifications can be seen", PermissionDefault.OP));
// Events cannot be unregistered - re-registering results in duplicate firings
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
boolean brigadierSupported = CommodoreProvider.isSupported();
geyserLogger.debug("Brigadier supported? " + brigadierSupported);
if (brigadierSupported) {
GeyserBrigadierSupport.loadBrigadier(this, geyserCommand);
}
// Check to ensure the current setup can support the protocol version Geyser uses
GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
INITIALIZED = true;
}
@Override
public void onDisable() {
public void onGeyserDisable() {
if (geyser != null) {
geyser.disable();
}
}
@Override
public void onGeyserShutdown() {
if (geyser != null) {
geyser.shutdown();
}
@ -376,6 +362,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
}
}
@Override
public void onDisable() {
this.onGeyserShutdown();
}
@Override
public GeyserSpigotConfiguration getGeyserConfig() {
return geyserConfig;
@ -470,4 +461,25 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
}
return false;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean loadConfig() {
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
try {
if (!getDataFolder().exists()) {
//noinspection ResultOfMethodCallIgnored
getDataFolder().mkdir();
}
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml",
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
} catch (IOException ex) {
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
Bukkit.getPluginManager().disablePlugin(this);
return false;
}
return true;
}
}

View file

@ -39,9 +39,9 @@ import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
@ -59,7 +59,12 @@ import java.lang.reflect.Method;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public class GeyserStandaloneBootstrap implements GeyserBootstrap {
@ -68,11 +73,10 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
private GeyserStandaloneConfiguration geyserConfig;
private GeyserStandaloneLogger geyserLogger;
private IGeyserPingPassthrough geyserPingPassthrough;
private GeyserStandaloneGUI gui;
@Getter
private boolean useGui = System.console() == null && !isHeadless();
private Logger log4jLogger;
private String configFilename = "config.yml";
private GeyserImpl geyser;
@ -161,23 +165,19 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
}
}
}
bootstrap.onEnable(useGuiOpts, configFilenameOpt);
}
public void onEnable(boolean useGui, String configFilename) {
this.configFilename = configFilename;
this.useGui = useGui;
this.onEnable();
bootstrap.useGui = useGuiOpts;
bootstrap.configFilename = configFilenameOpt;
bootstrap.onGeyserInitialize();
}
@Override
public void onEnable() {
Logger logger = (Logger) LogManager.getRootLogger();
for (Appender appender : logger.getAppenders().values()) {
public void onGeyserInitialize() {
log4jLogger = (Logger) LogManager.getRootLogger();
for (Appender appender : log4jLogger.getAppenders().values()) {
// Remove the appender that is not in use
// Prevents multiple appenders/double logging and removes harmless errors
if ((useGui && appender instanceof TerminalConsoleAppender) || (!useGui && appender instanceof ConsoleAppender)) {
logger.removeAppender(appender);
log4jLogger.removeAppender(appender);
}
}
@ -190,7 +190,12 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
}
LoopbackUtil.checkAndApplyLoopback(geyserLogger);
this.onGeyserEnable();
}
@Override
public void onGeyserEnable() {
try {
File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml",
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
@ -215,14 +220,15 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Allow libraries like Protocol to have their debug information passthrough
logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO);
log4jLogger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO);
geyser = GeyserImpl.load(PlatformType.STANDALONE, this);
GeyserImpl.start();
geyserCommandManager = new GeyserCommandManager(geyser);
geyserCommandManager.init();
GeyserImpl.start();
if (gui != null) {
gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager);
}
@ -250,7 +256,14 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
}
@Override
public void onDisable() {
public void onGeyserDisable() {
// We can re-register commands on standalone, so why not
GeyserImpl.getInstance().commandManager().getCommands().clear();
geyser.disable();
}
@Override
public void onGeyserShutdown() {
geyser.shutdown();
System.exit(0);
}

View file

@ -49,7 +49,7 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
@Override
protected void shutdown() {
GeyserImpl.getInstance().getBootstrap().onDisable();
GeyserImpl.getInstance().getBootstrap().onGeyserShutdown();
}
@Override

View file

@ -32,10 +32,10 @@ import com.velocitypowered.api.event.proxy.ListenerBoundEvent;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.network.ListenerType;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.Getter;
import net.kyori.adventure.util.Codec;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserBootstrap;
@ -46,6 +46,7 @@ import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandExecutor;
@ -63,12 +64,6 @@ import java.util.UUID;
@Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC")
public class GeyserVelocityPlugin implements GeyserBootstrap {
/**
* Determines if the plugin has been ran once before, including before /geyser reload.
*/
private static boolean INITIALIZED = false;
@Inject
private Logger logger;
@ -90,52 +85,54 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
private final Path configFolder = Paths.get("plugins/" + GeyserImpl.NAME + "-Velocity/");
@Override
public void onEnable() {
try {
Codec.class.getMethod("codec", Codec.Decoder.class, Codec.Encoder.class);
} catch (NoSuchMethodException e) {
// velocitypowered.com has a build that is very outdated
logger.error("Please download Velocity from https://papermc.io/downloads#Velocity - the 'stable' Velocity version " +
"that has likely been downloaded is very outdated and does not support 1.19.");
return;
}
public void onGeyserInitialize() {
GeyserLocale.init(this);
try {
if (!configFolder.toFile().exists())
//noinspection ResultOfMethodCallIgnored
configFolder.toFile().mkdirs();
File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(),
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class);
} catch (IOException ex) {
logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
return;
if (!ProtocolVersion.isSupported(GameProtocol.getJavaProtocolVersion())) {
logger.error(" / \\");
logger.error(" / \\");
logger.error(" / | \\");
logger.error(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", proxyServer.getVersion().getName()));
logger.error(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
logger.error(" / o \\");
logger.error("/_____________\\");
}
if (!loadConfig()) {
return;
}
this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this);
// Hack: Normally triggered by ListenerBoundEvent, but that doesn't fire on /geyser reload
if (INITIALIZED) {
this.postStartup();
}
this.geyserInjector = new GeyserVelocityInjector(proxyServer);
}
private void postStartup() {
GeyserImpl.start();
if (!INITIALIZED) {
this.geyserInjector = new GeyserVelocityInjector(proxyServer);
// Will be initialized after the proxy has been bound
@Override
public void onGeyserEnable() {
if (GeyserImpl.getInstance().isReloading()) {
if (!loadConfig()) {
return;
}
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
} else {
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
}
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
GeyserImpl.start();
if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else {
this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer);
}
// No need to re-register commands when reloading
if (GeyserImpl.getInstance().isReloading()) {
return;
}
this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands()));
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
@ -147,17 +144,18 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
this.commandManager.register(entry.getKey().description().id(), new GeyserVelocityCommandExecutor(this.geyser, commands));
}
if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else {
this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer);
}
proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener());
}
@Override
public void onDisable() {
public void onGeyserDisable() {
if (geyser != null) {
geyser.disable();
}
}
@Override
public void onGeyserShutdown() {
if (geyser != null) {
geyser.shutdown();
}
@ -188,26 +186,24 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
@Subscribe
public void onInit(ProxyInitializeEvent event) {
onEnable();
this.onGeyserInitialize();
}
@Subscribe
public void onShutdown(ProxyShutdownEvent event) {
onDisable();
this.onGeyserShutdown();
}
@Subscribe
public void onProxyBound(ListenerBoundEvent event) {
if (event.getListenerType() == ListenerType.MINECRAFT) {
// Once listener is bound, do our startup process
this.postStartup();
this.onGeyserEnable();
if (geyserInjector != null) {
// After this bound, we know that the channel initializer cannot change without it being ineffective for Velocity, too
geyserInjector.initializeLocalChannel(this);
}
INITIALIZED = true;
}
}
@ -242,4 +238,21 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
}
return false;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean loadConfig() {
try {
if (!configFolder.toFile().exists())
//noinspection ResultOfMethodCallIgnored
configFolder.toFile().mkdirs();
File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(),
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class);
} catch (IOException ex) {
logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
return false;
}
return true;
}
}

View file

@ -44,14 +44,28 @@ public interface GeyserBootstrap {
GeyserWorldManager DEFAULT_CHUNK_MANAGER = new GeyserWorldManager();
/**
* Called when the GeyserBootstrap is enabled
* Called when the GeyserBootstrap is initialized.
* This will only be called once, when Geyser is loading. Calling this must
* happen before {@link #onGeyserEnable()}, since this "sets up" Geyser.
*/
void onEnable();
void onGeyserInitialize();
/**
* Called when the GeyserBootstrap is disabled
* Called when the GeyserBootstrap is enabled/reloaded.
* This starts Geyser, after which, Geyser is in a player-accepting state.
*/
void onDisable();
void onGeyserEnable();
/**
* Called when the GeyserBootstrap is disabled - either before a reload,
* of before fully shutting down.
*/
void onGeyserDisable();
/**
* Called when the GeyserBootstrap is shutting down.
*/
void onGeyserShutdown();
/**
* Returns the current GeyserConfiguration

View file

@ -58,9 +58,7 @@ import org.geysermc.floodgate.news.NewsItemAction;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.event.EventBus;
import org.geysermc.geyser.api.event.EventRegistrar;
import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent;
import org.geysermc.geyser.api.event.lifecycle.*;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.BedrockListener;
import org.geysermc.geyser.api.network.RemoteServer;
@ -145,6 +143,7 @@ public class GeyserImpl implements GeyserApi {
private UnixSocketClientListener erosionUnixListener;
@Setter
private volatile boolean shuttingDown = false;
private ScheduledExecutorService scheduledThread;
@ -162,8 +161,14 @@ public class GeyserImpl implements GeyserApi {
@Getter(AccessLevel.NONE)
private Map<String, String> savedRefreshTokens;
@Getter
private static GeyserImpl instance;
/**
* Determines if we're currently reloading. Replaces per-bootstrap reload checks
*/
private volatile boolean isReloading;
private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) {
instance = this;
@ -172,13 +177,16 @@ public class GeyserImpl implements GeyserApi {
this.platformType = platformType;
this.bootstrap = bootstrap;
GeyserLocale.finalizeDefaultLocale(this);
/* Initialize event bus */
this.eventBus = new GeyserEventBus();
/* Load Extensions */
/* Create Extension Manager */
this.extensionManager = new GeyserExtensionManager();
/* Finalize locale loading now that we know the default locale from the config */
GeyserLocale.finalizeDefaultLocale(this);
/* Load Extensions */
this.extensionManager.init();
this.eventBus.fire(new GeyserPreInitializeEvent(this.extensionManager, this.eventBus));
}
@ -236,11 +244,17 @@ public class GeyserImpl implements GeyserApi {
} else if (config.getRemote().authType() == AuthType.FLOODGATE) {
VersionCheckUtils.checkForOutdatedFloodgate(logger);
}
VersionCheckUtils.checkForOutdatedJava(logger);
}
private void startInstance() {
this.scheduledThread = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("Geyser Scheduled Thread"));
if (isReloading) {
// If we're reloading, the default locale in the config might have changed.
GeyserLocale.finalizeDefaultLocale(this);
}
GeyserLogger logger = bootstrap.getGeyserLogger();
GeyserConfiguration config = bootstrap.getGeyserConfig();
@ -536,12 +550,15 @@ public class GeyserImpl implements GeyserApi {
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus));
if (isReloading) {
this.eventBus.fire(new GeyserPostReloadEvent(this.extensionManager, this.eventBus));
} else {
this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus));
}
if (config.isNotifyOnNewBedrockUpdate()) {
VersionCheckUtils.checkForGeyserUpdate(this::getLogger);
}
VersionCheckUtils.checkForOutdatedJava(logger);
}
@Override
@ -600,9 +617,8 @@ public class GeyserImpl implements GeyserApi {
return session.transfer(address, port);
}
public void shutdown() {
public void disable() {
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown"));
shuttingDown = true;
if (sessionManager.size() >= 1) {
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.log", sessionManager.size()));
@ -616,7 +632,6 @@ public class GeyserImpl implements GeyserApi {
skinUploader.close();
}
newsHandler.shutdown();
this.commandManager().getCommands().clear();
if (this.erosionUnixListener != null) {
this.erosionUnixListener.close();
@ -624,16 +639,29 @@ public class GeyserImpl implements GeyserApi {
Registries.RESOURCE_PACKS.get().clear();
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done"));
}
public void shutdown() {
shuttingDown = true;
this.disable();
this.commandManager().getCommands().clear();
// Disable extensions, fire the shutdown event
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
this.extensionManager.disableExtensions();
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done"));
}
public void reload() {
shutdown();
this.extensionManager.enableExtensions();
bootstrap.onEnable();
public void reloadGeyser() {
isReloading = true;
this.eventBus.fire(new GeyserPreReloadEvent(this.extensionManager, this.eventBus));
bootstrap.onGeyserDisable();
bootstrap.onGeyserEnable();
isReloading = false;
}
/**
@ -744,9 +772,7 @@ public class GeyserImpl implements GeyserApi {
throw new RuntimeException("Geyser has not been loaded yet!");
}
// We've been reloaded
if (instance.isShuttingDown()) {
instance.shuttingDown = false;
if (getInstance().isReloading()) {
instance.startInstance();
} else {
instance.initialize();
@ -797,8 +823,4 @@ public class GeyserImpl implements GeyserApi {
}
});
}
public static GeyserImpl getInstance() {
return instance;
}
}

View file

@ -86,7 +86,7 @@ public class GeyserCommandManager {
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
}
if (this.geyser.extensionManager().extensions().size() > 0) {
if (!this.geyser.extensionManager().extensions().isEmpty()) {
registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions"));
}

View file

@ -55,7 +55,7 @@ public class ReloadCommand extends GeyserCommand {
geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick");
//FIXME Without the tiny wait, players do not get kicked - same happens when Geyser tries to disconnect all sessions on shutdown
geyser.getScheduledThread().schedule(geyser::reload, 10, TimeUnit.MILLISECONDS);
geyser.getScheduledThread().schedule(geyser::reloadGeyser, 10, TimeUnit.MILLISECONDS);
}
@Override

View file

@ -52,7 +52,7 @@ public class StopCommand extends GeyserCommand {
return;
}
geyser.getBootstrap().onDisable();
geyser.getBootstrap().onGeyserShutdown();
}
@Override

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.entity;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.packet.EmotePacket;
import org.geysermc.geyser.api.entity.EntityData;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.session.GeyserSession;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class GeyserEntityData implements EntityData {
private final GeyserSession session;
private final Set<UUID> movementLockOwners = new HashSet<>();
public GeyserEntityData(GeyserSession session) {
this.session = session;
}
@Override
public @NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId) {
CompletableFuture<GeyserEntity> future = new CompletableFuture<>();
session.ensureInEventLoop(() -> future.complete(session.getEntityCache().getEntityByJavaId(javaId)));
return future;
}
@Override
public void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId) {
Objects.requireNonNull(emoter, "emoter must not be null!");
Entity entity = (Entity) emoter;
if (entity.getSession() != session) {
throw new IllegalStateException("Given entity must be from this session!");
}
EmotePacket packet = new EmotePacket();
packet.setRuntimeEntityId(entity.getGeyserId());
packet.setXuid("");
packet.setPlatformId(""); // BDS sends empty
packet.setEmoteId(emoteId);
session.sendUpstreamPacket(packet);
}
@Override
public @NonNull GeyserPlayerEntity playerEntity() {
return session.getPlayerEntity();
}
@Override
public boolean lockMovement(boolean lock, @NonNull UUID owner) {
Objects.requireNonNull(owner, "owner must not be null!");
if (lock) {
movementLockOwners.add(owner);
} else {
movementLockOwners.remove(owner);
}
session.lockInputs(session.camera().isCameraLocked(), isMovementLocked());
return isMovementLocked();
}
@Override
public boolean isMovementLocked() {
return !movementLockOwners.isEmpty();
}
}

View file

@ -39,12 +39,20 @@ import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.*;
import org.cloudburstmc.protocol.bedrock.data.Ability;
import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.data.GameType;
import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
import org.cloudburstmc.protocol.bedrock.packet.*;
import org.cloudburstmc.protocol.bedrock.packet.AddPlayerPacket;
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.Entity;
@ -143,6 +151,10 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
addPlayerPacket.setGameType(GameType.SURVIVAL); //TODO
addPlayerPacket.setAbilityLayers(BASE_ABILITY_LAYER); // Recommended to be added since 1.19.10, but only needed here for permissions viewing
addPlayerPacket.getMetadata().putFlags(flags);
// Since 1.20.60, the nametag does not show properly if this is not set :/
// The nametag does disappear properly when the player is invisible though.
dirtyMetadata.put(EntityDataTypes.NAMETAG_ALWAYS_SHOW, (byte) 1);
dirtyMetadata.apply(addPlayerPacket.getMetadata());
setFlagsDirty(false);
@ -433,4 +445,9 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
public UUID getTabListUuid() {
return getUuid();
}
@Override
public Vector3f position() {
return this.position.clone();
}
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.impl.camera;
import org.cloudburstmc.protocol.bedrock.data.camera.CameraAudioListener;
import org.cloudburstmc.protocol.bedrock.data.camera.CameraPreset;
import org.cloudburstmc.protocol.common.DefinitionRegistry;
import org.cloudburstmc.protocol.common.NamedDefinition;
import org.cloudburstmc.protocol.common.SimpleDefinitionRegistry;
import org.cloudburstmc.protocol.common.util.OptionalBoolean;
import org.geysermc.geyser.api.bedrock.camera.CameraPerspective;
import java.util.List;
public class CameraDefinitions {
public static final DefinitionRegistry<NamedDefinition> CAMERA_DEFINITIONS;
public static final List<CameraPreset> CAMERA_PRESETS;
static {
CAMERA_PRESETS = List.of(
new CameraPreset(CameraPerspective.FIRST_PERSON.id(), "", null, null, null, null, OptionalBoolean.empty()),
new CameraPreset(CameraPerspective.FREE.id(), "", null, null, null, null, OptionalBoolean.empty()),
new CameraPreset(CameraPerspective.THIRD_PERSON.id(), "", null, null, null, null, OptionalBoolean.empty()),
new CameraPreset(CameraPerspective.THIRD_PERSON_FRONT.id(), "", null, null, null, null, OptionalBoolean.empty()),
new CameraPreset("geyser:free_audio", "minecraft:free", null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(false)),
new CameraPreset("geyser:free_effects", "minecraft:free", null, null, null, CameraAudioListener.CAMERA, OptionalBoolean.of(true)),
new CameraPreset("geyser:free_audio_effects", "minecraft:free", null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(true)));
SimpleDefinitionRegistry.Builder<NamedDefinition> builder = SimpleDefinitionRegistry.builder();
for (int i = 0; i < CAMERA_PRESETS.size(); i++) {
builder.add(CameraDefinition.of(CAMERA_PRESETS.get(i).getIdentifier(), i));
}
CAMERA_DEFINITIONS = builder.build();
}
public static NamedDefinition getById(int id) {
return CAMERA_DEFINITIONS.getDefinition(id);
}
public static NamedDefinition getByFunctionality(boolean audio, boolean effects) {
if (!audio && !effects) {
return getById(1); // FREE
}
if (audio) {
if (effects) {
return getById(6); // FREE_AUDIO_EFFECTS
} else {
return getById(4); // FREE_AUDIO
}
} else {
return getById(5); // FREE_EFFECTS
}
}
public record CameraDefinition(String identifier, int runtimeId) implements NamedDefinition {
@Override
public String getIdentifier() {
return identifier;
}
@Override
public int getRuntimeId() {
return runtimeId;
}
public static CameraDefinition of(String identifier, int runtimeId) {
return new CameraDefinition(identifier, runtimeId);
}
}
}

View file

@ -0,0 +1,235 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.impl.camera;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector2f;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.CameraShakeAction;
import org.cloudburstmc.protocol.bedrock.data.CameraShakeType;
import org.cloudburstmc.protocol.bedrock.data.camera.CameraEase;
import org.cloudburstmc.protocol.bedrock.data.camera.CameraFadeInstruction;
import org.cloudburstmc.protocol.bedrock.data.camera.CameraSetInstruction;
import org.cloudburstmc.protocol.bedrock.packet.CameraInstructionPacket;
import org.cloudburstmc.protocol.bedrock.packet.CameraShakePacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerFogPacket;
import org.geysermc.geyser.api.bedrock.camera.CameraEaseType;
import org.geysermc.geyser.api.bedrock.camera.CameraData;
import org.geysermc.geyser.api.bedrock.camera.CameraFade;
import org.geysermc.geyser.api.bedrock.camera.CameraPerspective;
import org.geysermc.geyser.api.bedrock.camera.CameraPosition;
import org.geysermc.geyser.api.bedrock.camera.CameraShake;
import org.geysermc.geyser.session.GeyserSession;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
public class GeyserCameraData implements CameraData {
private final GeyserSession session;
@Getter
private CameraPerspective cameraPerspective;
/**
* All fog effects that are currently applied to the client.
*/
private final Set<String> appliedFog = new HashSet<>();
private final Set<UUID> cameraLockOwners = new HashSet<>();
public GeyserCameraData(GeyserSession session) {
this.session = session;
}
@Override
public void clearCameraInstructions() {
this.cameraPerspective = null;
CameraInstructionPacket packet = new CameraInstructionPacket();
packet.setClear(true);
session.sendUpstreamPacket(packet);
}
@Override
public void forceCameraPerspective(@NonNull CameraPerspective perspective) {
Objects.requireNonNull(perspective, "perspective cannot be null!");
if (perspective == cameraPerspective) {
return; // nothing to do
}
this.cameraPerspective = perspective;
CameraInstructionPacket packet = new CameraInstructionPacket();
CameraSetInstruction setInstruction = new CameraSetInstruction();
if (perspective == CameraPerspective.FREE) {
throw new IllegalArgumentException("Cannot force a stationary camera (CameraPerspective#FREE) on the player!" +
"Send a CameraPosition with an exact position instead");
}
setInstruction.setPreset(CameraDefinitions.getById(perspective.ordinal()));
packet.setSetInstruction(setInstruction);
session.sendUpstreamPacket(packet);
}
@Override
public @Nullable CameraPerspective forcedCameraPerspective() {
return this.cameraPerspective;
}
@Override
public void sendCameraFade(@NonNull CameraFade fade) {
Objects.requireNonNull(fade, "fade cannot be null!");
CameraFadeInstruction fadeInstruction = new CameraFadeInstruction();
fadeInstruction.setColor(fade.color());
fadeInstruction.setTimeData(
new CameraFadeInstruction.TimeData(
fade.fadeInSeconds(),
fade.fadeHoldSeconds(),
fade.fadeOutSeconds()
)
);
CameraInstructionPacket packet = new CameraInstructionPacket();
packet.setFadeInstruction(fadeInstruction);
session.sendUpstreamPacket(packet);
}
@Override
public void sendCameraPosition(@NonNull CameraPosition movement) {
Objects.requireNonNull(movement, "movement cannot be null!");
this.cameraPerspective = CameraPerspective.FREE; // Movements only work with the free preset
CameraSetInstruction setInstruction = new CameraSetInstruction();
CameraEaseType easeType = movement.easeType();
if (easeType != null) {
setInstruction.setEase(new CameraSetInstruction.EaseData(
CameraEase.fromName(easeType.id()),
movement.easeSeconds()
));
}
Vector3f facingPosition = movement.facingPosition();
if (facingPosition != null) {
setInstruction.setFacing(facingPosition);
}
setInstruction.setPos(movement.position());
setInstruction.setRot(Vector2f.from(movement.rotationX(), movement.rotationY()));
setInstruction.setPreset(CameraDefinitions.getByFunctionality(movement.playerPositionForAudio(), movement.renderPlayerEffects()));
CameraInstructionPacket packet = new CameraInstructionPacket();
packet.setSetInstruction(setInstruction);
// If present, also send the fade
CameraFade fade = movement.cameraFade();
if (fade != null) {
CameraFadeInstruction fadeInstruction = new CameraFadeInstruction();
fadeInstruction.setColor(fade.color());
fadeInstruction.setTimeData(
new CameraFadeInstruction.TimeData(
fade.fadeInSeconds(),
fade.fadeHoldSeconds(),
fade.fadeOutSeconds()
)
);
packet.setFadeInstruction(fadeInstruction);
}
session.sendUpstreamPacket(packet);
}
@Override
public void shakeCamera(float intensity, float duration, @NonNull CameraShake type) {
Objects.requireNonNull(type, "camera shake type must be non null!");
CameraShakePacket packet = new CameraShakePacket();
packet.setIntensity(intensity);
packet.setDuration(duration);
packet.setShakeType(type == CameraShake.POSITIONAL ? CameraShakeType.POSITIONAL : CameraShakeType.ROTATIONAL);
packet.setShakeAction(CameraShakeAction.ADD);
session.sendUpstreamPacket(packet);
}
@Override
public void stopCameraShake() {
CameraShakePacket packet = new CameraShakePacket();
// CameraShakeAction.STOP removes all types regardless of the given type, but regardless it can't be null
packet.setShakeType(CameraShakeType.POSITIONAL);
packet.setShakeAction(CameraShakeAction.STOP);
session.sendUpstreamPacket(packet);
}
@Override
public void sendFog(String... fogNameSpaces) {
Collections.addAll(this.appliedFog, fogNameSpaces);
PlayerFogPacket packet = new PlayerFogPacket();
packet.getFogStack().addAll(this.appliedFog);
session.sendUpstreamPacket(packet);
}
@Override
public void removeFog(String... fogNameSpaces) {
if (fogNameSpaces.length == 0) {
this.appliedFog.clear();
} else {
for (String id : fogNameSpaces) {
this.appliedFog.remove(id);
}
}
PlayerFogPacket packet = new PlayerFogPacket();
packet.getFogStack().addAll(this.appliedFog);
session.sendUpstreamPacket(packet);
}
@Override
public @NonNull Set<String> fogEffects() {
// Use a copy so that sendFog/removeFog can be called while iterating the returned set (avoid CME)
return Set.copyOf(this.appliedFog);
}
@Override
public boolean lockCamera(boolean lock, @NonNull UUID owner) {
Objects.requireNonNull(owner, "owner cannot be null!");
if (lock) {
this.cameraLockOwners.add(owner);
} else {
this.cameraLockOwners.remove(owner);
}
session.lockInputs(isCameraLocked(), session.entities().isMovementLocked());
return isCameraLocked();
}
@Override
public boolean isCameraLocked() {
return !this.cameraLockOwners.isEmpty();
}
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.impl.camera;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.common.value.qual.IntRange;
import org.geysermc.geyser.api.bedrock.camera.CameraFade;
import java.awt.Color;
import java.util.Objects;
public record GeyserCameraFade(
Color color,
float fadeInSeconds,
float fadeHoldSeconds,
float fadeOutSeconds
) implements CameraFade {
public static class Builder implements CameraFade.Builder {
private Color color;
private float fadeInSeconds;
private float fadeHoldSeconds;
private float fadeOutSeconds;
@Override
public CameraFade.Builder color(@NonNull Color color) {
Objects.requireNonNull(color, "color cannot be null!");
this.color = color;
return this;
}
@Override
public CameraFade.Builder fadeInSeconds(@IntRange(from = 0, to = 10) float fadeInSeconds) {
if (fadeInSeconds < 0f) {
throw new IllegalArgumentException("Fade in seconds must be at least 0 seconds");
}
if (fadeInSeconds > 10f) {
throw new IllegalArgumentException("Fade in seconds must be at most 10 seconds");
}
this.fadeInSeconds = fadeInSeconds;
return this;
}
@Override
public CameraFade.Builder fadeHoldSeconds(@IntRange(from = 0, to = 10) float fadeHoldSeconds) {
if (fadeHoldSeconds < 0f) {
throw new IllegalArgumentException("Fade hold seconds must be at least 0 seconds");
}
if (fadeHoldSeconds > 10f) {
throw new IllegalArgumentException("Fade hold seconds must be at most 10 seconds");
}
this.fadeHoldSeconds = fadeHoldSeconds;
return this;
}
@Override
public CameraFade.Builder fadeOutSeconds(@IntRange(from = 0, to = 10) float fadeOutSeconds) {
if (fadeOutSeconds < 0f) {
throw new IllegalArgumentException("Fade out seconds must be at least 0 seconds");
}
if (fadeOutSeconds > 10f) {
throw new IllegalArgumentException("Fade out seconds must be at most 10 seconds");
}
this.fadeOutSeconds = fadeOutSeconds;
return this;
}
@Override
public CameraFade build() {
Objects.requireNonNull(color, "color must be non null!");
if (fadeInSeconds + fadeHoldSeconds + fadeOutSeconds < 0.5f) {
throw new IllegalArgumentException("Total fade time (in, hold, out) must be at least 0.5 seconds");
}
return new GeyserCameraFade(color, fadeInSeconds, fadeHoldSeconds, fadeOutSeconds);
}
}
}

View file

@ -0,0 +1,131 @@
/*
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.impl.camera;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.value.qual.IntRange;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.api.bedrock.camera.CameraEaseType;
import org.geysermc.geyser.api.bedrock.camera.CameraFade;
import org.geysermc.geyser.api.bedrock.camera.CameraPosition;
import java.util.Objects;
public record GeyserCameraPosition(CameraFade cameraFade,
boolean renderPlayerEffects,
boolean playerPositionForAudio,
CameraEaseType easeType,
float easeSeconds,
Vector3f position,
@IntRange(from = -90, to = 90) int rotationX,
int rotationY,
Vector3f facingPosition
) implements CameraPosition {
public static class Builder implements CameraPosition.Builder {
private CameraFade cameraFade;
private boolean renderPlayerEffects;
private boolean playerPositionForAudio;
private CameraEaseType easeType;
private float easeSeconds;
private Vector3f position;
private @IntRange(from = -90, to = 90) int rotationX;
private int rotationY;
private Vector3f facingPosition;
@Override
public CameraPosition.Builder cameraFade(@Nullable CameraFade cameraFade) {
this.cameraFade = cameraFade;
return this;
}
@Override
public CameraPosition.Builder renderPlayerEffects(boolean renderPlayerEffects) {
this.renderPlayerEffects = renderPlayerEffects;
return this;
}
@Override
public CameraPosition.Builder playerPositionForAudio(boolean playerPositionForAudio) {
this.playerPositionForAudio = playerPositionForAudio;
return this;
}
@Override
public CameraPosition.Builder easeType(@Nullable CameraEaseType easeType) {
this.easeType = easeType;
return this;
}
@Override
public CameraPosition.Builder easeSeconds(float easeSeconds) {
if (easeSeconds < 0) {
throw new IllegalArgumentException("Camera ease duration cannot be negative!");
}
this.easeSeconds = easeSeconds;
return this;
}
@Override
public CameraPosition.Builder position(@NonNull Vector3f position) {
Objects.requireNonNull(position, "camera position cannot be null!");
this.position = position;
return this;
}
@Override
public CameraPosition.Builder rotationX(int rotationX) {
if (rotationX < -90 || rotationX > 90) {
throw new IllegalArgumentException("x-axis rotation needs to be between -90 and 90 degrees.");
}
this.rotationX = rotationX;
return this;
}
@Override
public CameraPosition.Builder rotationY(int rotationY) {
this.rotationY = rotationY;
return this;
}
@Override
public CameraPosition.Builder facingPosition(@Nullable Vector3f facingPosition) {
this.facingPosition = facingPosition;
return this;
}
@Override
public CameraPosition build() {
if (easeSeconds > 0 && easeType == null) {
throw new IllegalArgumentException("Camera ease type cannot be null if ease duration is greater than 0");
}
Objects.requireNonNull(position, "camera position must be non null!");
return new GeyserCameraPosition(cameraFade, renderPlayerEffects, playerPositionForAudio, easeType, easeSeconds, position, rotationX, rotationY, facingPosition);
}
}
}

View file

@ -115,7 +115,7 @@ public class GeyserCustomItemData implements CustomItemData {
return tags;
}
public static class CustomItemDataBuilder implements Builder {
public static class Builder implements CustomItemData.Builder {
protected String name = null;
protected CustomItemOptions customItemOptions = null;

View file

@ -37,7 +37,7 @@ public record GeyserCustomItemOptions(TriState unbreakable,
boolean defaultItem) implements CustomItemOptions {
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public static class CustomItemOptionsBuilder implements CustomItemOptions.Builder {
public static class Builder implements CustomItemOptions.Builder {
private TriState unbreakable = TriState.NOT_SET;
private OptionalInt customModelData = OptionalInt.empty();
private OptionalInt damagePredicate = OptionalInt.empty();

View file

@ -59,7 +59,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
private final boolean canAlwaysEat;
private final boolean isChargeable;
public GeyserNonVanillaCustomItemData(NonVanillaCustomItemDataBuilder builder) {
public GeyserNonVanillaCustomItemData(Builder builder) {
super(builder.name, builder.customItemOptions, builder.displayName, builder.icon, builder.allowOffhand,
builder.displayHandheld, builder.textureSize, builder.renderOffsets, builder.tags);
@ -168,7 +168,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
return isChargeable;
}
public static class NonVanillaCustomItemDataBuilder extends GeyserCustomItemData.CustomItemDataBuilder implements NonVanillaCustomItemData.Builder {
public static class Builder extends GeyserCustomItemData.Builder implements NonVanillaCustomItemData.Builder {
private String identifier = null;
private int javaId = -1;
@ -197,49 +197,49 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
private boolean chargeable = false;
@Override
public NonVanillaCustomItemData.Builder name(@NonNull String name) {
return (NonVanillaCustomItemData.Builder) super.name(name);
public Builder name(@NonNull String name) {
return (Builder) super.name(name);
}
@Override
public NonVanillaCustomItemData.Builder customItemOptions(@NonNull CustomItemOptions customItemOptions) {
public Builder customItemOptions(@NonNull CustomItemOptions customItemOptions) {
//Do nothing, as that value won't be read
return this;
}
@Override
public NonVanillaCustomItemData.Builder allowOffhand(boolean allowOffhand) {
return (NonVanillaCustomItemData.Builder) super.allowOffhand(allowOffhand);
public Builder allowOffhand(boolean allowOffhand) {
return (Builder) super.allowOffhand(allowOffhand);
}
@Override
public NonVanillaCustomItemData.Builder displayHandheld(boolean displayHandheld) {
return (NonVanillaCustomItemData.Builder) super.displayHandheld(displayHandheld);
public Builder displayHandheld(boolean displayHandheld) {
return (Builder) super.displayHandheld(displayHandheld);
}
@Override
public NonVanillaCustomItemData.Builder displayName(@NonNull String displayName) {
return (NonVanillaCustomItemData.Builder) super.displayName(displayName);
public Builder displayName(@NonNull String displayName) {
return (Builder) super.displayName(displayName);
}
@Override
public NonVanillaCustomItemData.Builder icon(@NonNull String icon) {
return (NonVanillaCustomItemData.Builder) super.icon(icon);
public Builder icon(@NonNull String icon) {
return (Builder) super.icon(icon);
}
@Override
public NonVanillaCustomItemData.Builder textureSize(int textureSize) {
return (NonVanillaCustomItemData.Builder) super.textureSize(textureSize);
public Builder textureSize(int textureSize) {
return (Builder) super.textureSize(textureSize);
}
@Override
public NonVanillaCustomItemData.Builder renderOffsets(CustomRenderOffsets renderOffsets) {
return (NonVanillaCustomItemData.Builder) super.renderOffsets(renderOffsets);
public Builder renderOffsets(CustomRenderOffsets renderOffsets) {
return (Builder) super.renderOffsets(renderOffsets);
}
@Override
public NonVanillaCustomItemData.Builder tags(@Nullable Set<String> tags) {
return (NonVanillaCustomItemData.Builder) super.tags(tags);
public Builder tags(@Nullable Set<String> tags) {
return (Builder) super.tags(tags);
}
@Override

View file

@ -62,7 +62,7 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents {
boolean placeAir;
Set<String> tags;
private GeyserCustomBlockComponents(CustomBlockComponentsBuilder builder) {
private GeyserCustomBlockComponents(Builder builder) {
this.selectionBox = builder.selectionBox;
this.collisionBox = builder.collisionBox;
this.displayName = builder.displayName;
@ -157,7 +157,7 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents {
return tags;
}
public static class CustomBlockComponentsBuilder implements Builder {
public static class Builder implements CustomBlockComponents.Builder {
protected BoxComponent selectionBox;
protected BoxComponent collisionBox;
protected String displayName;

View file

@ -54,7 +54,7 @@ public class GeyserCustomBlockData implements CustomBlockData {
private final Map<String, Object> defaultProperties;
GeyserCustomBlockData(CustomBlockDataBuilder builder) {
GeyserCustomBlockData(Builder builder) {
this.name = builder.name;
if (name == null) {
throw new IllegalStateException("Name must be set");
@ -141,10 +141,10 @@ public class GeyserCustomBlockData implements CustomBlockData {
@Override
public CustomBlockState.@NonNull Builder blockStateBuilder() {
return new GeyserCustomBlockState.CustomBlockStateBuilder(this);
return new GeyserCustomBlockState.Builder(this);
}
public static class CustomBlockDataBuilder implements Builder {
public static class Builder implements CustomBlockData.Builder {
private String name;
private boolean includedInCreativeInventory;
private CreativeCategory creativeCategory;

View file

@ -64,7 +64,7 @@ public class GeyserCustomBlockState implements CustomBlockState {
}
@RequiredArgsConstructor
public static class CustomBlockStateBuilder implements CustomBlockState.Builder {
public static class Builder implements CustomBlockState.Builder {
private final CustomBlockData blockData;
private final Object2ObjectMap<String, Object> properties = new Object2ObjectOpenHashMap<>();

View file

@ -37,7 +37,7 @@ public class GeyserGeometryComponent implements GeometryComponent {
private final String identifier;
private final Map<String, String> boneVisibility;
GeyserGeometryComponent(GeometryComponentBuilder builder) {
GeyserGeometryComponent(Builder builder) {
this.identifier = builder.identifier;
this.boneVisibility = builder.boneVisibility;
}
@ -52,18 +52,18 @@ public class GeyserGeometryComponent implements GeometryComponent {
return boneVisibility;
}
public static class GeometryComponentBuilder implements Builder {
public static class Builder implements GeometryComponent.Builder {
private String identifier;
private Map<String, String> boneVisibility;
@Override
public GeometryComponent.Builder identifier(@NonNull String identifier) {
public Builder identifier(@NonNull String identifier) {
this.identifier = identifier;
return this;
}
@Override
public GeometryComponent.Builder boneVisibility(@Nullable Map<String, String> boneVisibility) {
public Builder boneVisibility(@Nullable Map<String, String> boneVisibility) {
this.boneVisibility = boneVisibility;
return this;
}

View file

@ -18,7 +18,7 @@ public class GeyserJavaBlockState implements JavaBlockState {
String pistonBehavior;
boolean hasBlockEntity;
private GeyserJavaBlockState(JavaBlockStateBuilder builder) {
private GeyserJavaBlockState(Builder builder) {
this.identifier = builder.identifier;
this.javaId = builder.javaId;
this.stateGroupId = builder.stateGroupId;
@ -81,7 +81,7 @@ public class GeyserJavaBlockState implements JavaBlockState {
return hasBlockEntity;
}
public static class JavaBlockStateBuilder implements Builder {
public static class Builder implements JavaBlockState.Builder {
private String identifier;
private int javaId;
private int stateGroupId;

View file

@ -36,7 +36,7 @@ public class GeyserMaterialInstance implements MaterialInstance {
private final boolean faceDimming;
private final boolean ambientOcclusion;
GeyserMaterialInstance(MaterialInstanceBuilder builder) {
GeyserMaterialInstance(Builder builder) {
this.texture = builder.texture;
this.renderMethod = builder.renderMethod;
this.faceDimming = builder.faceDimming;
@ -63,7 +63,7 @@ public class GeyserMaterialInstance implements MaterialInstance {
return ambientOcclusion;
}
public static class MaterialInstanceBuilder implements Builder {
public static class Builder implements MaterialInstance.Builder {
private String texture;
private String renderMethod;
private boolean faceDimming;

View file

@ -37,7 +37,7 @@ import java.util.List;
public class GeyserNonVanillaCustomBlockData extends GeyserCustomBlockData implements NonVanillaCustomBlockData {
private final String namespace;
GeyserNonVanillaCustomBlockData(NonVanillaCustomBlockDataBuilder builder) {
GeyserNonVanillaCustomBlockData(Builder builder) {
super(builder);
this.namespace = builder.namespace;
@ -56,58 +56,58 @@ public class GeyserNonVanillaCustomBlockData extends GeyserCustomBlockData imple
return this.namespace;
}
public static class NonVanillaCustomBlockDataBuilder extends CustomBlockDataBuilder implements NonVanillaCustomBlockData.Builder {
public static class Builder extends GeyserCustomBlockData.Builder implements NonVanillaCustomBlockData.Builder {
private String namespace;
@Override
public NonVanillaCustomBlockDataBuilder namespace(@NonNull String namespace) {
public Builder namespace(@NonNull String namespace) {
this.namespace = namespace;
return this;
}
@Override
public NonVanillaCustomBlockDataBuilder name(@NonNull String name) {
return (NonVanillaCustomBlockDataBuilder) super.name(name);
public Builder name(@NonNull String name) {
return (Builder) super.name(name);
}
@Override
public NonVanillaCustomBlockDataBuilder includedInCreativeInventory(boolean includedInCreativeInventory) {
return (NonVanillaCustomBlockDataBuilder) super.includedInCreativeInventory(includedInCreativeInventory);
public Builder includedInCreativeInventory(boolean includedInCreativeInventory) {
return (Builder) super.includedInCreativeInventory(includedInCreativeInventory);
}
@Override
public NonVanillaCustomBlockDataBuilder creativeCategory(@Nullable CreativeCategory creativeCategories) {
return (NonVanillaCustomBlockDataBuilder) super.creativeCategory(creativeCategories);
public Builder creativeCategory(@Nullable CreativeCategory creativeCategories) {
return (Builder) super.creativeCategory(creativeCategories);
}
@Override
public NonVanillaCustomBlockDataBuilder creativeGroup(@Nullable String creativeGroup) {
return (NonVanillaCustomBlockDataBuilder) super.creativeGroup(creativeGroup);
public Builder creativeGroup(@Nullable String creativeGroup) {
return (Builder) super.creativeGroup(creativeGroup);
}
@Override
public NonVanillaCustomBlockDataBuilder components(@NonNull CustomBlockComponents components) {
return (NonVanillaCustomBlockDataBuilder) super.components(components);
public Builder components(@NonNull CustomBlockComponents components) {
return (Builder) super.components(components);
}
@Override
public NonVanillaCustomBlockDataBuilder booleanProperty(@NonNull String propertyName) {
return (NonVanillaCustomBlockDataBuilder) super.booleanProperty(propertyName);
public Builder booleanProperty(@NonNull String propertyName) {
return (Builder) super.booleanProperty(propertyName);
}
@Override
public NonVanillaCustomBlockDataBuilder intProperty(@NonNull String propertyName, List<Integer> values) {
return (NonVanillaCustomBlockDataBuilder) super.intProperty(propertyName, values);
public Builder intProperty(@NonNull String propertyName, List<Integer> values) {
return (Builder) super.intProperty(propertyName, values);
}
@Override
public NonVanillaCustomBlockDataBuilder stringProperty(@NonNull String propertyName, List<String> values) {
return (NonVanillaCustomBlockDataBuilder) super.stringProperty(propertyName, values);
public Builder stringProperty(@NonNull String propertyName, List<String> values) {
return (Builder) super.stringProperty(propertyName, values);
}
@Override
public NonVanillaCustomBlockDataBuilder permutations(@NonNull List<CustomBlockPermutation> permutations) {
return (NonVanillaCustomBlockDataBuilder) super.permutations(permutations);
public Builder permutations(@NonNull List<CustomBlockPermutation> permutations) {
return (Builder) super.permutations(permutations);
}
@Override

View file

@ -31,6 +31,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630;
import org.cloudburstmc.protocol.bedrock.codec.v649.Bedrock_v649;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
import org.geysermc.geyser.session.GeyserSession;
@ -46,7 +47,7 @@ public final class GameProtocol {
* Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports.
*/
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v630.CODEC;
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v649.CODEC;
/**
* A list of all supported Bedrock versions that can join Geyser
@ -63,9 +64,12 @@ public final class GameProtocol {
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v622.CODEC.toBuilder()
.minecraftVersion("1.20.40/1.20.41")
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v630.CODEC.toBuilder()
.minecraftVersion("1.20.50/1.20.51")
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
.minecraftVersion("1.20.60/1.20.61")
.build());
}
/**
@ -88,6 +92,10 @@ public final class GameProtocol {
return session.getUpstream().getProtocolVersion() < Bedrock_v630.CODEC.getProtocolVersion();
}
public static boolean is1_20_60orHigher(int protocolVersion) {
return protocolVersion >= Bedrock_v649.CODEC.getProtocolVersion();
}
/**
* Gets the {@link PacketCodec} for Minecraft: Java Edition.
*

View file

@ -100,6 +100,16 @@ public class LoggingPacketHandler implements BedrockPacketHandler {
return defaultHandler(packet);
}
@Override
public PacketSignal handle(CameraPresetsPacket packet) {
return defaultHandler(packet);
}
@Override
public PacketSignal handle(CameraInstructionPacket packet) {
return defaultHandler(packet);
}
@Override
public PacketSignal handle(CommandBlockUpdatePacket packet) {
return defaultHandler(packet);

View file

@ -33,8 +33,25 @@ import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm;
import org.cloudburstmc.protocol.bedrock.data.ResourcePackType;
import org.cloudburstmc.protocol.bedrock.packet.*;
import org.cloudburstmc.protocol.bedrock.netty.codec.compression.CompressionStrategy;
import org.cloudburstmc.protocol.bedrock.netty.codec.compression.SimpleCompressionStrategy;
import org.cloudburstmc.protocol.bedrock.netty.codec.compression.ZlibCompression;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.cloudburstmc.protocol.bedrock.packet.LoginPacket;
import org.cloudburstmc.protocol.bedrock.packet.ModalFormResponsePacket;
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
import org.cloudburstmc.protocol.bedrock.packet.NetworkSettingsPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
import org.cloudburstmc.protocol.bedrock.packet.RequestNetworkSettingsPacket;
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackChunkDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackChunkRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackClientResponsePacket;
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackDataInfoPacket;
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackStackPacket;
import org.cloudburstmc.protocol.bedrock.packet.ResourcePacksInfoPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetTitlePacket;
import org.cloudburstmc.protocol.common.PacketSignal;
import org.cloudburstmc.protocol.common.util.Zlib;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType;
@ -57,18 +74,29 @@ import org.geysermc.geyser.util.VersionCheckUtils;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.util.*;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.OptionalInt;
import java.util.Set;
import java.util.UUID;
public class UpstreamPacketHandler extends LoggingPacketHandler {
private boolean networkSettingsRequested = false;
private final Deque<String> packsToSent = new ArrayDeque<>();
private final Set<UUID> brokenResourcePacks = new HashSet<>();
private final CompressionStrategy compressionStrategy;
private SessionLoadResourcePacksEventImpl resourcePackLoadEvent;
public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) {
super(geyser, session);
ZlibCompression compression = new ZlibCompression(Zlib.RAW);
compression.setLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
this.compressionStrategy = new SimpleCompressionStrategy(compression);
}
private PacketSignal translateAndDefault(BedrockPacket packet) {
@ -136,16 +164,15 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
responsePacket.setCompressionAlgorithm(algorithm);
responsePacket.setCompressionThreshold(512);
session.sendUpstreamPacketImmediately(responsePacket);
session.getUpstream().getSession().getPeer().setCompression(compressionStrategy);
session.getUpstream().getSession().setCompression(algorithm);
session.getUpstream().getSession().setCompressionLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
networkSettingsRequested = true;
return PacketSignal.HANDLED;
}
@Override
public PacketSignal handle(LoginPacket loginPacket) {
if (geyser.isShuttingDown()) {
if (geyser.isShuttingDown() || geyser.isReloading()) {
// Don't allow new players in if we're no longer operating
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.message"));
return PacketSignal.HANDLED;
@ -331,6 +358,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]);
PackCodec codec = pack.codec();
ResourcePackManifest.Header header = pack.manifest().header();
data.setPackId(header.uuid());
int chunkCount = (int) Math.ceil(codec.size() / (double) GeyserResourcePack.CHUNK_SIZE);
data.setChunkCount(chunkCount);

View file

@ -0,0 +1,130 @@
/*
* 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.network.netty;
import io.netty.bootstrap.AbstractBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.epoll.Native;
import io.netty.channel.unix.UnixChannelOption;
import lombok.experimental.UtilityClass;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@UtilityClass
public final class Bootstraps {
private static final Optional<int[]> KERNEL_VERSION;
// The REUSEPORT_AVAILABLE socket option is available starting from kernel version 3.9.
// This option allows multiple sockets to listen on the same IP address and port without conflict.
private static final int[] REUSEPORT_VERSION = new int[]{3, 9, 0};
private static final boolean REUSEPORT_AVAILABLE;
static {
String kernelVersion;
try {
kernelVersion = Native.KERNEL_VERSION;
} catch (Throwable e) {
kernelVersion = null;
}
if (kernelVersion != null && kernelVersion.contains("-")) {
int index = kernelVersion.indexOf('-');
if (index > -1) {
kernelVersion = kernelVersion.substring(0, index);
}
int[] kernelVer = fromString(kernelVersion);
KERNEL_VERSION = Optional.of(kernelVer);
REUSEPORT_AVAILABLE = checkVersion(kernelVer, 0);
} else {
KERNEL_VERSION = Optional.empty();
REUSEPORT_AVAILABLE = false;
}
}
public static Optional<int[]> getKernelVersion() {
return KERNEL_VERSION;
}
public static boolean isReusePortAvailable() {
return REUSEPORT_AVAILABLE;
}
@SuppressWarnings({"rawtypes, unchecked"})
public static void setupBootstrap(AbstractBootstrap bootstrap) {
if (REUSEPORT_AVAILABLE) {
bootstrap.option(UnixChannelOption.SO_REUSEPORT, true);
}
}
private static int[] fromString(String ver) {
String[] parts = ver.split("\\.");
if (parts.length < 2) {
throw new IllegalArgumentException("At least 2 version numbers required");
}
return new int[]{
Integer.parseInt(parts[0]),
Integer.parseInt(parts[1]),
parts.length == 2 ? 0 : Integer.parseInt(parts[2])
};
}
private static boolean checkVersion(int[] ver, int i) {
if (ver[i] > REUSEPORT_VERSION[i]) {
return true;
} else if (ver[i] == REUSEPORT_VERSION[i]) {
if (ver.length == (i + 1)) {
return true;
} else {
return checkVersion(ver, i + 1);
}
}
return false;
}
public static CompletableFuture<Void> allOf(ChannelFuture... futures) {
if (futures == null || futures.length == 0) {
return CompletableFuture.completedFuture(null);
}
@SuppressWarnings("unchecked")
CompletableFuture<Channel>[] completableFutures = new CompletableFuture[futures.length];
for (int i = 0; i < futures.length; i++) {
ChannelFuture channelFuture = futures[i];
CompletableFuture<Channel> completableFuture = new CompletableFuture<>();
channelFuture.addListener(future -> {
if (future.cause() != null) {
completableFuture.completeExceptionally(future.cause());
}
completableFuture.complete(channelFuture.channel());
});
completableFutures[i] = completableFuture;
}
return CompletableFuture.allOf(completableFutures);
}
}

View file

@ -94,13 +94,16 @@ public final class GeyserServer {
private final GeyserImpl geyser;
private EventLoopGroup group;
// Split childGroup may improve IO
private EventLoopGroup childGroup;
private final ServerBootstrap bootstrap;
private EventLoopGroup playerGroup;
@Getter
private final ExpiringMap<InetSocketAddress, InetSocketAddress> proxiedAddresses;
private final int listenCount;
private ChannelFuture bootstrapFuture;
private ChannelFuture[] bootstrapFutures;
/**
* The port to broadcast in the pong. This can be different from the port the server is bound to, e.g. due to port forwarding.
@ -109,9 +112,14 @@ public final class GeyserServer {
public GeyserServer(GeyserImpl geyser, int threadCount) {
this.geyser = geyser;
this.group = TRANSPORT.eventLoopGroupFactory().apply(threadCount);
this.listenCount = Bootstraps.isReusePortAvailable() ? Integer.getInteger("Geyser.ListenCount", 2) : 1;
GeyserImpl.getInstance().getLogger().debug("Listen thread count: " + listenCount);
this.group = TRANSPORT.eventLoopGroupFactory().apply(listenCount);
this.childGroup = TRANSPORT.eventLoopGroupFactory().apply(threadCount);
this.bootstrap = this.createBootstrap(this.group);
this.bootstrap = this.createBootstrap();
// setup SO_REUSEPORT if exists
Bootstraps.setupBootstrap(this.bootstrap);
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
this.proxiedAddresses = ExpiringMap.builder()
@ -130,46 +138,51 @@ public final class GeyserServer {
}
public CompletableFuture<Void> bind(InetSocketAddress address) {
CompletableFuture<Void> future = new CompletableFuture<>();
this.bootstrapFuture = this.bootstrap.bind(address).addListener(bindResult -> {
if (bindResult.cause() != null) {
future.completeExceptionally(bindResult.cause());
return;
}
future.complete(null);
});
bootstrapFutures = new ChannelFuture[listenCount];
for (int i = 0; i < listenCount; i++) {
ChannelFuture future = bootstrap.bind(address);
addHandlers(future);
bootstrapFutures[i] = future;
}
Channel channel = this.bootstrapFuture.channel();
return Bootstraps.allOf(bootstrapFutures);
}
private void addHandlers(ChannelFuture future) {
Channel channel = future.channel();
// Add our ping handler
channel.pipeline()
.addFirst(RakConnectionRequestHandler.NAME, new RakConnectionRequestHandler(this))
.addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this));
// Add proxy handler
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
channel.pipeline().addFirst("proxy-protocol-decoder", new ProxyServerHandler());
}
return future;
}
public void shutdown() {
try {
Future<?> future1 = this.group.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Future<?> futureChildGroup = this.childGroup.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
this.childGroup = null;
Future<?> futureGroup = this.group.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
this.group = null;
Future<?> future2 = this.playerGroup.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Future<?> futurePlayerGroup = this.playerGroup.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
this.playerGroup = null;
future1.sync();
future2.sync();
futureChildGroup.sync();
futureGroup.sync();
futurePlayerGroup.sync();
SkinProvider.shutdown();
} catch (InterruptedException e) {
GeyserImpl.getInstance().getLogger().severe("Exception in shutdown process", e);
}
this.bootstrapFuture.channel().closeFuture().syncUninterruptibly();
for (ChannelFuture f : bootstrapFutures) {
f.channel().closeFuture().syncUninterruptibly();
}
}
private ServerBootstrap createBootstrap(EventLoopGroup group) {
private ServerBootstrap createBootstrap() {
if (this.geyser.getConfig().isDebugMode()) {
this.geyser.getLogger().debug("EventLoop type: " + TRANSPORT.datagramChannel());
if (TRANSPORT.datagramChannel() == NioDatagramChannel.class) {
@ -188,7 +201,7 @@ public final class GeyserServer {
this.geyser.getLogger().debug("Setting MTU to " + this.geyser.getConfig().getMtu());
return new ServerBootstrap()
.channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannel()))
.group(group)
.group(group, childGroup)
.option(RakChannelOption.RAK_HANDLE_PING, true)
.option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu())
.childHandler(serverInitializer);
@ -224,7 +237,7 @@ public final class GeyserServer {
return true;
}
public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
public BedrockPong onQuery(Channel channel, InetSocketAddress inetSocketAddress) {
if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) {
String ip;
if (geyser.getConfig().isLogPlayerIpAddresses()) {
@ -257,7 +270,7 @@ public final class GeyserServer {
.version(GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()) // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
.ipv4Port(this.broadcastPort)
.ipv6Port(this.broadcastPort)
.serverId(bootstrapFuture.channel().config().getOption(RakChannelOption.RAK_GUID));
.serverId(channel.config().getOption(RakChannelOption.RAK_GUID));
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");

View file

@ -45,7 +45,7 @@ public class RakPingHandler extends SimpleChannelInboundHandler<RakPing> {
protected void channelRead0(ChannelHandlerContext ctx, RakPing msg) {
long guid = ctx.channel().config().getOption(RakChannelOption.RAK_GUID);
RakPong pong = msg.reply(guid, this.server.onQuery(msg.getSender()).toByteBuf());
RakPong pong = msg.reply(guid, this.server.onQuery(ctx.channel(), msg.getSender()).toByteBuf());
ctx.writeAndFlush(pong);
}
}

View file

@ -25,6 +25,8 @@
package org.geysermc.geyser.registry.loader;
import org.geysermc.geyser.api.bedrock.camera.CameraFade;
import org.geysermc.geyser.api.bedrock.camera.CameraPosition;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.api.block.custom.NonVanillaCustomBlockData;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
@ -39,6 +41,8 @@ import org.geysermc.geyser.api.item.custom.CustomItemOptions;
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
import org.geysermc.geyser.api.pack.PathPackCodec;
import org.geysermc.geyser.api.pack.UrlPackCodec;
import org.geysermc.geyser.impl.camera.GeyserCameraFade;
import org.geysermc.geyser.impl.camera.GeyserCameraPosition;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.event.GeyserEventRegistrar;
import org.geysermc.geyser.item.GeyserCustomItemData;
@ -67,21 +71,25 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
// misc
providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0]));
providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.CustomBlockComponentsBuilder());
providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.CustomBlockDataBuilder());
providers.put(JavaBlockState.Builder.class, args -> new GeyserJavaBlockState.JavaBlockStateBuilder());
providers.put(NonVanillaCustomBlockData.Builder.class, args -> new GeyserNonVanillaCustomBlockData.NonVanillaCustomBlockDataBuilder());
providers.put(MaterialInstance.Builder.class, args -> new GeyserMaterialInstance.MaterialInstanceBuilder());
providers.put(GeometryComponent.Builder.class, args -> new GeyserGeometryComponent.GeometryComponentBuilder());
providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.Builder());
providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.Builder());
providers.put(JavaBlockState.Builder.class, args -> new GeyserJavaBlockState.Builder());
providers.put(NonVanillaCustomBlockData.Builder.class, args -> new GeyserNonVanillaCustomBlockData.Builder());
providers.put(MaterialInstance.Builder.class, args -> new GeyserMaterialInstance.Builder());
providers.put(GeometryComponent.Builder.class, args -> new GeyserGeometryComponent.Builder());
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0]));
providers.put(UrlPackCodec.class, args -> new GeyserUrlPackCodec((String) args[0], (String) args[1]));
// items
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder());
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder());
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder());
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.Builder());
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.Builder());
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.Builder());
// cameras
providers.put(CameraFade.Builder.class, args -> new GeyserCameraFade.Builder());
providers.put(CameraPosition.Builder.class, args -> new GeyserCameraPosition.Builder());
return providers;
}

View file

@ -35,17 +35,22 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
import org.geysermc.geyser.api.block.custom.CustomBlockState;
import org.geysermc.geyser.api.block.custom.component.*;
import org.geysermc.geyser.api.block.custom.component.BoxComponent;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
import org.geysermc.geyser.api.block.custom.component.GeometryComponent;
import org.geysermc.geyser.api.block.custom.component.MaterialInstance;
import org.geysermc.geyser.api.block.custom.component.PlacementConditions;
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.BlockFilterType;
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.Face;
import org.geysermc.geyser.api.block.custom.component.TransformationComponent;
import org.geysermc.geyser.api.item.custom.CustomItemData;
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
import org.geysermc.geyser.api.util.CreativeCategory;
import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException;
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents.CustomBlockComponentsBuilder;
import org.geysermc.geyser.level.block.GeyserCustomBlockData.CustomBlockDataBuilder;
import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder;
import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder;
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents;
import org.geysermc.geyser.level.block.GeyserCustomBlockData;
import org.geysermc.geyser.level.block.GeyserGeometryComponent;
import org.geysermc.geyser.level.block.GeyserMaterialInstance;
import org.geysermc.geyser.level.physics.BoundingBox;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.mappings.util.CustomBlockComponentsMapping;
@ -57,7 +62,14 @@ import org.geysermc.geyser.util.BlockUtils;
import org.geysermc.geyser.util.MathUtils;
import java.nio.file.Path;
import java.util.*;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
@ -248,7 +260,7 @@ public class MappingsReader_v1 extends MappingsReader {
boolean onlyOverrideStates = node.has("only_override_states") && node.get("only_override_states").asBoolean();
// Create the data for the overall block
CustomBlockData.Builder customBlockDataBuilder = new CustomBlockDataBuilder()
CustomBlockData.Builder customBlockDataBuilder = new GeyserCustomBlockData.Builder()
.name(name)
.includedInCreativeInventory(includedInCreativeInventory)
.creativeCategory(creativeCategory)
@ -360,7 +372,7 @@ public class MappingsReader_v1 extends MappingsReader {
int id = BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(stateKey, -1);
BoxComponent boxComponent = createBoxComponent(id);
BoxComponent extendedBoxComponent = createExtendedBoxComponent(id);
CustomBlockComponents.Builder builder = new CustomBlockComponentsBuilder()
CustomBlockComponents.Builder builder = new GeyserCustomBlockComponents.Builder()
.collisionBox(boxComponent)
.selectionBox(boxComponent);
@ -392,12 +404,12 @@ public class MappingsReader_v1 extends MappingsReader {
if (node.has("geometry")) {
if (node.get("geometry").isTextual()) {
builder.geometry(new GeometryComponentBuilder()
builder.geometry(new GeyserGeometryComponent.Builder()
.identifier(node.get("geometry").asText())
.build());
} else {
JsonNode geometry = node.get("geometry");
GeometryComponentBuilder geometryBuilder = new GeometryComponentBuilder();
GeometryComponent.Builder geometryBuilder = new GeyserGeometryComponent.Builder();
if (geometry.has("identifier")) {
geometryBuilder.identifier(geometry.get("identifier").asText());
}
@ -654,7 +666,7 @@ public class MappingsReader_v1 extends MappingsReader {
ambientOcclusion = node.get("ambient_occlusion").asBoolean();
}
return new MaterialInstanceBuilder()
return new GeyserMaterialInstance.Builder()
.texture(texture)
.renderMethod(renderMethod)
.faceDimming(faceDimming)

View file

@ -41,6 +41,7 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630;
import org.cloudburstmc.protocol.bedrock.codec.v649.Bedrock_v649;
import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.geysermc.geyser.GeyserImpl;
@ -118,6 +119,8 @@ public final class BlockRegistryPopulator {
var blockMappers = ImmutableMap.<ObjectIntPair<String>, Remapper>builder()
.put(ObjectIntPair.of("1_20_40", Bedrock_v622.CODEC.getProtocolVersion()), Conversion630_622::remapBlock)
.put(ObjectIntPair.of("1_20_50", Bedrock_v630.CODEC.getProtocolVersion()), tag -> tag)
// Only changes in 1.20.60 are hard_stained_glass (an EDU only block)
.put(ObjectIntPair.of("1_20_60", Bedrock_v649.CODEC.getProtocolVersion()), tag -> tag)
.build();
// We can keep this strong as nothing should be garbage collected
@ -139,6 +142,7 @@ public final class BlockRegistryPopulator {
builder.remove("version"); // Remove all nbt tags which are not needed for differentiating states
builder.remove("name_hash"); // Quick workaround - was added in 1.19.20
builder.remove("network_id"); // Added in 1.19.80 - ????
builder.remove("block_id"); // Added in 1.20.60 //TODO verify this can be just removed
//noinspection UnstableApiUsage
builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
vanillaBlockStates.set(i, builder.build());
@ -154,6 +158,7 @@ public final class BlockRegistryPopulator {
List<CustomBlockState> customExtBlockStates = new ArrayList<>();
int[] remappedVanillaIds = new int[0];
if (BlockRegistries.CUSTOM_BLOCKS.get().length != 0) {
CustomBlockRegistryPopulator.BLOCK_ID.set(CustomBlockRegistryPopulator.START_OFFSET);
for (CustomBlockData customBlock : BlockRegistries.CUSTOM_BLOCKS.get()) {
customBlockProperties.add(CustomBlockRegistryPopulator.generateBlockPropertyData(customBlock, protocolVersion));
CustomBlockRegistryPopulator.generateCustomBlockStates(customBlock, customBlockStates, customExtBlockStates);

View file

@ -0,0 +1,40 @@
/*
* 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.registry.populator;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.registry.type.GeyserMappingItem;
public class Conversion630_649 {
static GeyserMappingItem remapItem(@SuppressWarnings("unused") Item item, GeyserMappingItem mapping) {
if (mapping.getBedrockIdentifier().equalsIgnoreCase("minecraft:scute")) {
return mapping.withBedrockIdentifier("minecraft:turtle_scute");
}
return mapping;
}
}

View file

@ -124,6 +124,7 @@ public class CreativeItemRegistryPopulator {
builder.remove("name_hash");
builder.remove("network_id");
builder.remove("version");
builder.remove("block_id");
blockDefinition = blockMappings.getDefinition(builder.build());
} catch (IOException e) {

View file

@ -23,17 +23,19 @@ import org.geysermc.geyser.api.block.custom.property.CustomBlockProperty;
import org.geysermc.geyser.api.block.custom.property.PropertyType;
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomBlocksEvent;
import org.geysermc.geyser.api.util.CreativeCategory;
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents;
import org.geysermc.geyser.level.block.GeyserCustomBlockData;
import org.geysermc.geyser.level.block.GeyserCustomBlockState;
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents.CustomBlockComponentsBuilder;
import org.geysermc.geyser.level.block.GeyserCustomBlockData.CustomBlockDataBuilder;
import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder;
import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder;
import org.geysermc.geyser.level.block.GeyserGeometryComponent;
import org.geysermc.geyser.level.block.GeyserMaterialInstance;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.mappings.MappingsConfigReader;
import org.geysermc.geyser.registry.type.CustomSkull;
import org.geysermc.geyser.util.MathUtils;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -41,6 +43,13 @@ import java.util.Map;
import java.util.Set;
public class CustomBlockRegistryPopulator {
// Since 1.20.60, custom blocks need a block_id in their nbt tag
public static AtomicInteger BLOCK_ID = new AtomicInteger();
// Custom block id's start at 10000, and count up
public static final int START_OFFSET = 10000;
/**
* The stage of population
*/
@ -89,7 +98,7 @@ public class CustomBlockRegistryPopulator {
GeyserImpl.getInstance().getEventBus().fire(new GeyserDefineCustomBlocksEvent() {
@Override
public void register(@NonNull CustomBlockData customBlockData) {
if (customBlockData.name().length() == 0) {
if (customBlockData.name().isEmpty()) {
throw new IllegalArgumentException("Custom block name must have at least 1 character.");
}
if (!CUSTOM_BLOCK_NAMES.add(customBlockData.name())) {
@ -179,17 +188,17 @@ public class CustomBlockRegistryPopulator {
});
BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.set(blockStateOverrides);
if (blockStateOverrides.size() != 0) {
if (!blockStateOverrides.isEmpty()) {
GeyserImpl.getInstance().getLogger().info("Registered " + blockStateOverrides.size() + " custom block overrides.");
}
BlockRegistries.CUSTOM_BLOCK_ITEM_OVERRIDES.set(CUSTOM_BLOCK_ITEM_OVERRIDES);
if (CUSTOM_BLOCK_ITEM_OVERRIDES.size() != 0) {
if (!CUSTOM_BLOCK_ITEM_OVERRIDES.isEmpty()) {
GeyserImpl.getInstance().getLogger().info("Registered " + CUSTOM_BLOCK_ITEM_OVERRIDES.size() + " custom block item overrides.");
}
BlockRegistries.EXTENDED_COLLISION_BOXES.set(extendedCollisionBoxes);
if (extendedCollisionBoxes.size() != 0) {
if (!extendedCollisionBoxes.isEmpty()) {
GeyserImpl.getInstance().getLogger().info("Registered " + extendedCollisionBoxes.size() + " custom block extended collision boxes.");
}
}
@ -199,7 +208,7 @@ public class CustomBlockRegistryPopulator {
*/
private static void populateNonVanilla() {
BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.set(NON_VANILLA_BLOCK_STATE_OVERRIDES);
if (NON_VANILLA_BLOCK_STATE_OVERRIDES.size() != 0) {
if (!NON_VANILLA_BLOCK_STATE_OVERRIDES.isEmpty()) {
GeyserImpl.getInstance().getLogger().info("Registered " + NON_VANILLA_BLOCK_STATE_OVERRIDES.size() + " non-vanilla block overrides.");
}
}
@ -209,7 +218,7 @@ public class CustomBlockRegistryPopulator {
*/
private static void registration() {
BlockRegistries.CUSTOM_BLOCKS.set(CUSTOM_BLOCKS.toArray(new CustomBlockData[0]));
if (CUSTOM_BLOCKS.size() != 0) {
if (!CUSTOM_BLOCKS.isEmpty()) {
GeyserImpl.getInstance().getLogger().info("Registered " + CUSTOM_BLOCKS.size() + " custom blocks.");
}
}
@ -279,7 +288,7 @@ public class CustomBlockRegistryPopulator {
CreativeCategory creativeCategory = customBlock.creativeCategory() != null ? customBlock.creativeCategory() : CreativeCategory.NONE;
String creativeGroup = customBlock.creativeGroup() != null ? customBlock.creativeGroup() : "";
NbtMap propertyTag = NbtMap.builder()
NbtMapBuilder propertyTag = NbtMap.builder()
.putCompound("components", CustomBlockRegistryPopulator.convertComponents(customBlock.components(), protocolVersion))
// this is required or the client will crash
// in the future, this can be used to replace items in the creative inventory
@ -292,9 +301,14 @@ public class CustomBlockRegistryPopulator {
// meaning of this version is unknown, but it's required for tags to work and should probably be checked periodically
.putInt("molangVersion", 1)
.putList("permutations", NbtType.COMPOUND, permutations)
.putList("properties", NbtType.COMPOUND, properties)
.build();
return new BlockPropertyData(customBlock.identifier(), propertyTag);
.putList("properties", NbtType.COMPOUND, properties);
if (GameProtocol.is1_20_60orHigher(protocolVersion)) {
propertyTag.putCompound("vanilla_block_data", NbtMap.builder()
.putInt("block_id", BLOCK_ID.getAndIncrement())
.build());
}
return new BlockPropertyData(customBlock.identifier(), propertyTag.build());
}
/**
@ -479,20 +493,20 @@ public class CustomBlockRegistryPopulator {
}
private static CustomBlockData createExtendedCollisionBlock(BoxComponent boxComponent, int extendedCollisionBlock) {
return new CustomBlockDataBuilder()
return new GeyserCustomBlockData.Builder()
.name("extended_collision_" + extendedCollisionBlock)
.components(
new CustomBlockComponentsBuilder()
new GeyserCustomBlockComponents.Builder()
.collisionBox(boxComponent)
.selectionBox(BoxComponent.emptyBox())
.materialInstance("*", new MaterialInstanceBuilder()
.materialInstance("*", new GeyserMaterialInstance.Builder()
.texture("glass")
.renderMethod("alpha_test")
.faceDimming(false)
.ambientOcclusion(false)
.build())
.lightDampening(0)
.geometry(new GeometryComponentBuilder()
.geometry(new GeyserGeometryComponent.Builder()
.identifier("geometry.invisible")
.build())
.build())

View file

@ -44,6 +44,7 @@ import org.geysermc.geyser.item.GeyserCustomMappingData;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.components.WearableSlot;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.registry.mappings.MappingsConfigReader;
import org.geysermc.geyser.registry.type.GeyserMappingItem;
import org.geysermc.geyser.registry.type.ItemMapping;
@ -97,10 +98,10 @@ public class CustomItemRegistryPopulator {
}
}
public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, CustomItemData customItemData, int bedrockId) {
public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, CustomItemData customItemData, int bedrockId, int protocolVersion) {
ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true);
NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, mapping, customItemName, bedrockId);
NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, mapping, customItemName, bedrockId, protocolVersion);
ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build());
return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId);
@ -124,7 +125,7 @@ public class CustomItemRegistryPopulator {
return true;
}
public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId) {
public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) {
String customIdentifier = customItemData.identifier();
Set<String> repairMaterials = customItemData.repairMaterials();
@ -152,14 +153,14 @@ public class CustomItemRegistryPopulator {
.build();
NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId,
customItemData.creativeCategory(), customItemData.creativeGroup(), customItemData.isHat(), customItemData.displayHandheld());
customItemData.creativeCategory(), customItemData.creativeGroup(), customItemData.isHat(), customItemData.displayHandheld(), protocolVersion);
ComponentItemData componentItemData = new ComponentItemData(customIdentifier, builder.build());
return new NonVanillaItemRegistration(componentItemData, item, customItemMapping);
}
private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, Item javaItem, GeyserMappingItem mapping,
String customItemName, int customItemId) {
String customItemName, int customItemId, int protocolVersion) {
NbtMapBuilder builder = NbtMap.builder();
builder.putString("name", customItemName)
.putInt("id", customItemId);
@ -167,7 +168,7 @@ public class CustomItemRegistryPopulator {
NbtMapBuilder itemProperties = NbtMap.builder();
NbtMapBuilder componentBuilder = NbtMap.builder();
setupBasicItemInfo(javaItem.maxDamage(), javaItem.maxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder);
setupBasicItemInfo(javaItem.maxDamage(), javaItem.maxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder, protocolVersion);
boolean canDestroyInCreative = true;
if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
@ -176,7 +177,7 @@ public class CustomItemRegistryPopulator {
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
if (mapping.getArmorType() != null) {
computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), componentBuilder);
computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), itemProperties, componentBuilder);
}
if (mapping.getFirstBlockRuntimeId() != null) {
@ -193,7 +194,7 @@ public class CustomItemRegistryPopulator {
switch (mapping.getBedrockIdentifier()) {
case "minecraft:fire_charge", "minecraft:flint_and_steel" -> computeBlockItemProperties("minecraft:fire", componentBuilder);
case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder);
case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder, mapping.getBedrockIdentifier(), protocolVersion);
case "minecraft:honey_bottle", "minecraft:milk_bucket", "minecraft:potion" -> computeConsumableProperties(itemProperties, componentBuilder, 2, true);
case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" ->
computeThrowableProperties(componentBuilder);
@ -210,7 +211,7 @@ public class CustomItemRegistryPopulator {
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName,
int customItemId, OptionalInt creativeCategory,
String creativeGroup, boolean isHat, boolean displayHandheld) {
String creativeGroup, boolean isHat, boolean displayHandheld, int protocolVersion) {
NbtMapBuilder builder = NbtMap.builder();
builder.putString("name", customItemName)
.putInt("id", customItemId);
@ -218,17 +219,17 @@ public class CustomItemRegistryPopulator {
NbtMapBuilder itemProperties = NbtMap.builder();
NbtMapBuilder componentBuilder = NbtMap.builder();
setupBasicItemInfo(customItemData.maxDamage(), customItemData.stackSize(), displayHandheld, customItemData, itemProperties, componentBuilder);
setupBasicItemInfo(customItemData.maxDamage(), customItemData.stackSize(), displayHandheld, customItemData, itemProperties, componentBuilder, protocolVersion);
boolean canDestroyInCreative = true;
if (customItemData.toolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
canDestroyInCreative = computeToolProperties(customItemData.toolType(), itemProperties, componentBuilder);
canDestroyInCreative = computeToolProperties(Objects.requireNonNull(customItemData.toolType()), itemProperties, componentBuilder);
}
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
String armorType = customItemData.armorType();
if (armorType != null) {
computeArmorProperties(armorType, customItemData.protectionValue(), componentBuilder);
computeArmorProperties(armorType, customItemData.protectionValue(), itemProperties, componentBuilder);
}
if (customItemData.isEdible()) {
@ -236,7 +237,11 @@ public class CustomItemRegistryPopulator {
}
if (customItemData.isChargeable()) {
computeChargeableProperties(itemProperties, componentBuilder);
String tooltype = customItemData.toolType();
if (tooltype == null) {
throw new IllegalArgumentException("tool type must be set if the custom item is chargeable!");
}
computeChargeableProperties(itemProperties, componentBuilder, "minecraft:" + tooltype, protocolVersion);
}
computeRenderOffsets(isHat, customItemData, componentBuilder);
@ -258,10 +263,21 @@ public class CustomItemRegistryPopulator {
return builder;
}
private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean displayHandheld, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
itemProperties.putCompound("minecraft:icon", NbtMap.builder()
.putString("texture", customItemData.icon())
.build());
private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean displayHandheld, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int protocolVersion) {
NbtMap iconMap;
if (GameProtocol.is1_20_60orHigher(protocolVersion)) {
iconMap = NbtMap.builder()
.putCompound("textures", NbtMap.builder()
.putString("default", customItemData.icon())
.build())
.build();
} else {
iconMap = NbtMap.builder()
.putString("texture", customItemData.icon())
.build();
}
itemProperties.putCompound("minecraft:icon", iconMap);
componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", customItemData.displayName()).build());
// Add a Geyser tag to the item, allowing Molang queries
@ -336,16 +352,19 @@ public class CustomItemRegistryPopulator {
if (toolType.equals("sword")) {
miningSpeed = 1.5f;
canDestroyInCreative = false;
componentBuilder.putCompound("minecraft:weapon", NbtMap.EMPTY);
}
itemProperties.putBoolean("hand_equipped", true);
itemProperties.putFloat("mining_speed", miningSpeed);
// This allows custom tools - shears, swords, shovels, axes etc to be enchanted or combined in the anvil
itemProperties.putInt("enchantable_value", 1);
itemProperties.putString("enchantable_slot", toolType);
return canDestroyInCreative;
}
private static void computeArmorProperties(String armorType, int protectionValue, NbtMapBuilder componentBuilder) {
private static void computeArmorProperties(String armorType, int protectionValue, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
switch (armorType) {
case "boots" -> {
componentBuilder.putString("minecraft:render_offsets", "boots");
@ -379,13 +398,73 @@ public class CustomItemRegistryPopulator {
componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build());
}
private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, String mapping, int protocolVersion) {
// setting high use_duration prevents the consume animation from playing
itemProperties.putInt("use_duration", Integer.MAX_VALUE);
// display item as tool (mainly for crossbow and bow)
itemProperties.putBoolean("hand_equipped", true);
// ensure client moves at slow speed while charging (note: this was calculated by hand as the movement modifer value does not seem to scale linearly)
componentBuilder.putCompound("minecraft:chargeable", NbtMap.builder().putFloat("movement_modifier", 0.35F).build());
// Make bows, tridents, and crossbows enchantable
itemProperties.putInt("enchantable_value", 1);
if (GameProtocol.is1_20_60orHigher(protocolVersion)) {
componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder()
.putFloat("use_duration", 100F)
.putFloat("movement_modifier", 0.35F)
.build());
switch (mapping) {
case "minecraft:bow" -> {
itemProperties.putString("enchantable_slot", "bow");
itemProperties.putInt("frame_count", 3);
componentBuilder.putCompound("minecraft:shooter", NbtMap.builder()
.putList("ammunition", NbtType.COMPOUND, List.of(
NbtMap.builder()
.putCompound("item", NbtMap.builder()
.putString("name", "minecraft:arrow")
.build())
.putBoolean("use_offhand", true)
.putBoolean("search_inventory", true)
.build()
))
.putFloat("max_draw_duration", 0f)
.putBoolean("charge_on_draw", true)
.putBoolean("scale_power_by_draw_duration", true)
.build());
componentBuilder.putInt("minecraft:use_duration", 999);
}
case "minecraft:trident" -> {
itemProperties.putString("enchantable_slot", "trident");
componentBuilder.putInt("minecraft:use_duration", 999);
}
case "minecraft:crossbow" -> {
itemProperties.putString("enchantable_slot", "crossbow");
itemProperties.putInt("frame_count", 10);
componentBuilder.putCompound("minecraft:shooter", NbtMap.builder()
.putList("ammunition", NbtType.COMPOUND, List.of(
NbtMap.builder()
.putCompound("item", NbtMap.builder()
.putString("name", "minecraft:arrow")
.build())
.putBoolean("use_offhand", true)
.putBoolean("search_inventory", true)
.build()
))
.putFloat("max_draw_duration", 1f)
.putBoolean("charge_on_draw", true)
.putBoolean("scale_power_by_draw_duration", true)
.build());
componentBuilder.putInt("minecraft:use_duration", 999);
}
}
} else {
// ensure client moves at slow speed while charging (note: this was calculated by hand as the movement modifer value does not seem to scale linearly)
componentBuilder.putCompound("minecraft:chargeable", NbtMap.builder().putFloat("movement_modifier", 0.35F).build());
// keep item enchantable; also works on 1.20.50
itemProperties.putString("enchantable_slot", mapping.replace("minecraft:", ""));
}
}
private static void computeConsumableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int useAnimation, boolean canAlwaysEat) {

View file

@ -130,7 +130,7 @@ public class CustomSkullRegistryPopulator {
}
});
if (BlockRegistries.CUSTOM_SKULLS.get().size() != 0) {
if (!BlockRegistries.CUSTOM_SKULLS.get().isEmpty()) {
GeyserImpl.getInstance().getLogger().info("Registered " + BlockRegistries.CUSTOM_SKULLS.get().size() + " custom skulls as custom blocks.");
}
}

View file

@ -40,6 +40,7 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630;
import org.cloudburstmc.protocol.bedrock.codec.v649.Bedrock_v649;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
@ -59,6 +60,7 @@ import org.geysermc.geyser.inventory.item.StoredItemMappings;
import org.geysermc.geyser.item.GeyserCustomMappingData;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.*;
@ -89,6 +91,7 @@ public class ItemRegistryPopulator {
List<PaletteVersion> paletteVersions = new ArrayList<>(3);
paletteVersions.add(new PaletteVersion("1_20_40", Bedrock_v622.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion630_622::remapItem));
paletteVersions.add(new PaletteVersion("1_20_50", Bedrock_v630.CODEC.getProtocolVersion()));
paletteVersions.add(new PaletteVersion("1_20_60", Bedrock_v649.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion630_649::remapItem));
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
@ -272,7 +275,7 @@ public class ItemRegistryPopulator {
if (firstPass) {
firstPass = false;
if (states.size() == 0) {
if (states.isEmpty()) {
// No need to iterate and find all block states - this is the one, as there can't be any others
bedrockBlock = bedrockBlockRuntimeId;
break;
@ -288,7 +291,7 @@ public class ItemRegistryPopulator {
requiredBlockStatesBuilder.remove(nbtEntry.getKey());
}
}
if (requiredBlockStatesBuilder.size() == 0) {
if (requiredBlockStatesBuilder.isEmpty()) {
// There are no required block states
// E.G. there was only a direction property that is no longer in play
// (States that are important include color for glass)
@ -420,7 +423,7 @@ public class ItemRegistryPopulator {
}
GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem(
customItemName, javaItem, mappingItem, customItem, customProtocolId
customItemName, javaItem, mappingItem, customItem, customProtocolId, palette.protocolVersion
);
// ComponentItemData - used to register some custom properties
componentItemData.add(customMapping.componentItemData());
@ -495,7 +498,7 @@ public class ItemRegistryPopulator {
.count(1)
.build());
registerFurnaceMinecart(nextFreeBedrockId++, componentItemData);
registerFurnaceMinecart(nextFreeBedrockId++, componentItemData, palette.protocolVersion);
// Register any completely custom items given to us
IntSet registeredJavaIds = new IntOpenHashSet(); // Used to check for duplicate item java ids
@ -508,7 +511,7 @@ public class ItemRegistryPopulator {
}
int customItemId = nextFreeBedrockId++;
NonVanillaItemRegistration registration = CustomItemRegistryPopulator.registerCustomItem(customItem, customItemId);
NonVanillaItemRegistration registration = CustomItemRegistryPopulator.registerCustomItem(customItem, customItemId, palette.protocolVersion);
componentItemData.add(registration.componentItemData());
ItemMapping mapping = registration.mapping();
@ -585,7 +588,7 @@ public class ItemRegistryPopulator {
}
}
private static void registerFurnaceMinecart(int nextFreeBedrockId, List<ComponentItemData> componentItemData) {
private static void registerFurnaceMinecart(int nextFreeBedrockId, List<ComponentItemData> componentItemData, int protocolVersion) {
NbtMapBuilder builder = NbtMap.builder();
builder.putString("name", "geysermc:furnace_minecart")
.putInt("id", nextFreeBedrockId);
@ -594,11 +597,20 @@ public class ItemRegistryPopulator {
NbtMapBuilder componentBuilder = NbtMap.builder();
// Conveniently, as of 1.16.200, the furnace minecart has a texture AND translation string already.
itemProperties.putCompound("minecraft:icon", NbtMap.builder()
.putString("texture", "minecart_furnace")
.putString("frame", "0.000000")
.putInt("frame_version", 1)
.putString("legacy_id", "").build());
// Not so conveniently, the way to set an icon changed in 1.20.60
NbtMap iconMap;
if (GameProtocol.is1_20_60orHigher(protocolVersion)) {
iconMap = NbtMap.builder()
.putCompound("textures", NbtMap.builder()
.putString("default", "minecart_furnace")
.build())
.build();
} else {
iconMap = NbtMap.builder()
.putString("texture", "minecart_furnace")
.build();
}
itemProperties.putCompound("minecraft:icon", iconMap);
componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", "item.minecartFurnace.name").build());
// Indicate that the arm animation should play on rails

View file

@ -34,8 +34,8 @@ import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
import org.geysermc.geyser.api.block.custom.component.TransformationComponent;
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents;
import org.geysermc.geyser.level.block.GeyserCustomBlockData;
import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder;
import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder;
import org.geysermc.geyser.level.block.GeyserGeometryComponent;
import org.geysermc.geyser.level.block.GeyserMaterialInstance;
import java.util.ArrayList;
import java.util.List;
@ -65,9 +65,9 @@ public class CustomSkull {
public CustomSkull(String skinHash) {
this.skinHash = skinHash;
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
CustomBlockComponents components = new GeyserCustomBlockComponents.Builder()
.destructibleByMining(1.5f)
.materialInstance("*", new MaterialInstanceBuilder()
.materialInstance("*", new GeyserMaterialInstance.Builder()
.texture("geyser." + skinHash + "_player_skin")
.renderMethod("alpha_test")
.faceDimming(true)
@ -82,7 +82,7 @@ public class CustomSkull {
addFloorPermutations(permutations);
addWallPermutations(permutations);
customBlockData = new GeyserCustomBlockData.CustomBlockDataBuilder()
customBlockData = new GeyserCustomBlockData.Builder()
.name("player_skull_" + skinHash)
.components(components)
.intProperty(BITS_A_PROPERTY, IntStream.rangeClosed(0, 6).boxed().toList()) // This gives us exactly 21 block states
@ -114,8 +114,8 @@ public class CustomSkull {
}
private void addDefaultPermutation(List<CustomBlockPermutation> permutations) {
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
.geometry(new GeometryComponentBuilder()
CustomBlockComponents components = new GeyserCustomBlockComponents.Builder()
.geometry(new GeyserGeometryComponent.Builder()
.identifier("geometry.geyser.player_skull_hand")
.build())
.transformation(new TransformationComponent(0, 180, 0))
@ -131,10 +131,10 @@ public class CustomSkull {
for (int quadrant = 0; quadrant < 4; quadrant++) {
for (int i = 0; i < 4; i++) {
int floorRotation = 4 * quadrant + i;
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
CustomBlockComponents components = new GeyserCustomBlockComponents.Builder()
.selectionBox(FLOOR_BOX)
.collisionBox(FLOOR_BOX)
.geometry(new GeometryComponentBuilder()
.geometry(new GeyserGeometryComponent.Builder()
.identifier("geometry.geyser.player_skull_floor_" + quadrantNames[i])
.build())
.transformation(new TransformationComponent(0, ROTATIONS[quadrant], 0))
@ -150,10 +150,10 @@ public class CustomSkull {
private void addWallPermutations(List<CustomBlockPermutation> permutations) {
for (int i = 0; i < 4; i++) {
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
CustomBlockComponents components = new GeyserCustomBlockComponents.Builder()
.selectionBox(WALL_BOX)
.collisionBox(WALL_BOX)
.geometry(new GeometryComponentBuilder()
.geometry(new GeyserGeometryComponent.Builder()
.identifier("geometry.geyser.player_skull_wall")
.build())
.transformation(new TransformationComponent(0, ROTATIONS[i], 0))

View file

@ -62,7 +62,7 @@ public final class ScoreboardUpdater extends Thread {
@Override
public void run() {
while (!geyser.isShuttingDown()) {
while (!geyser.isShuttingDown() && !geyser.isReloading()) {
try {
long timeTillAction = getTimeTillNextAction();
if (timeTillAction > 0) {

View file

@ -101,8 +101,10 @@ import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.bedrock.camera.CameraData;
import org.geysermc.geyser.api.bedrock.camera.CameraShake;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.entity.EntityData;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
import org.geysermc.geyser.api.event.bedrock.SessionDisconnectEvent;
@ -110,10 +112,13 @@ import org.geysermc.geyser.api.event.bedrock.SessionLoginEvent;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.RemoteServer;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.impl.camera.CameraDefinitions;
import org.geysermc.geyser.impl.camera.GeyserCameraData;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.GeyserEntityData;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
@ -151,7 +156,16 @@ import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;
@ -550,11 +564,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter
private boolean waitingForStatistics = false;
/**
* All fog effects that are currently applied to the client.
*/
private final Set<String> appliedFog = new HashSet<>();
private final Set<UUID> emotes;
/**
@ -586,6 +595,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
*/
private final Queue<Long> keepAliveCache = new ConcurrentLinkedQueue<>();
private final GeyserCameraData cameraData;
private final GeyserEntityData entityData;
private MinecraftProtocol protocol;
public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop) {
@ -607,6 +620,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.skullCache = new SkullCache(this);
this.tagCache = new TagCache();
this.worldCache = new WorldCache(this);
this.cameraData = new GeyserCameraData(this);
this.entityData = new GeyserEntityData(this);
this.worldBorder = new WorldBorder(this);
@ -667,6 +682,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
entityPacket.setIdentifiers(Registries.BEDROCK_ENTITY_IDENTIFIERS.get());
upstream.sendPacket(entityPacket);
CameraPresetsPacket cameraPresetsPacket = new CameraPresetsPacket();
cameraPresetsPacket.getPresets().addAll(CameraDefinitions.CAMERA_PRESETS);
upstream.sendPacket(cameraPresetsPacket);
CreativeContentPacket creativePacket = new CreativeContentPacket();
creativePacket.setContents(this.itemMappings.getCreativeItems());
upstream.sendPacket(creativePacket);
@ -935,7 +954,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
).toString());
} catch (Exception e) {
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode()));
disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.floodgate.encrypt_fail", getClientData().getLanguageCode()));
return;
}
@ -1182,12 +1201,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
// Set the mood
if (shouldShowFog && !isInWorldBorderWarningArea) {
isInWorldBorderWarningArea = true;
sendFog("minecraft:fog_crimson_forest");
camera().sendFog("minecraft:fog_crimson_forest");
}
}
if (!shouldShowFog && isInWorldBorderWarningArea) {
// Clear fog as we are outside the world border now
removeFog("minecraft:fog_crimson_forest");
camera().removeFog("minecraft:fog_crimson_forest");
isInWorldBorderWarningArea = false;
}
@ -1480,6 +1499,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private void startGame() {
this.upstream.getCodecHelper().setItemDefinitions(this.itemMappings);
this.upstream.getCodecHelper().setBlockDefinitions((DefinitionRegistry) this.blockMappings); //FIXME
this.upstream.getCodecHelper().setCameraPresetDefinitions(CameraDefinitions.CAMERA_DEFINITIONS);
StartGamePacket startGamePacket = new StartGamePacket();
startGamePacket.setUniqueEntityId(playerEntity.getGeyserId());
@ -1980,72 +2000,66 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Override
public @NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId) {
CompletableFuture<GeyserEntity> future = new CompletableFuture<>();
ensureInEventLoop(() -> future.complete(this.entityCache.getEntityByJavaId(javaId)));
return future;
return entities().entityByJavaId(javaId);
}
@Override
public void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId) {
Entity entity = (Entity) emoter;
if (entity.getSession() != this) {
throw new IllegalStateException("Given entity must be from this session!");
entities().showEmote(emoter, emoteId);
}
public void lockInputs(boolean camera, boolean movement) {
UpdateClientInputLocksPacket packet = new UpdateClientInputLocksPacket();
final int cameraOffset = 1 << 1;
final int movementOffset = 1 << 2;
int result = 0;
if (camera) {
result |= cameraOffset;
}
if (movement) {
result |= movementOffset;
}
EmotePacket packet = new EmotePacket();
packet.setRuntimeEntityId(entity.getGeyserId());
packet.setXuid("");
packet.setPlatformId(""); // BDS sends empty
packet.setEmoteId(emoteId);
packet.setLockComponentData(result);
packet.setServerPosition(this.playerEntity.getPosition());
sendUpstreamPacket(packet);
}
@Override
public @NonNull CameraData camera() {
return this.cameraData;
}
@Override
public @NonNull EntityData entities() {
return this.entityData;
}
@Override
public void shakeCamera(float intensity, float duration, @NonNull CameraShake type) {
CameraShakePacket packet = new CameraShakePacket();
packet.setIntensity(intensity);
packet.setDuration(duration);
packet.setShakeType(type == CameraShake.POSITIONAL ? CameraShakeType.POSITIONAL : CameraShakeType.ROTATIONAL);
packet.setShakeAction(CameraShakeAction.ADD);
sendUpstreamPacket(packet);
this.cameraData.shakeCamera(intensity, duration, type);
}
@Override
public void stopCameraShake() {
CameraShakePacket packet = new CameraShakePacket();
// CameraShakeAction.STOP removes all types regardless of the given type, but regardless it can't be null
packet.setShakeType(CameraShakeType.POSITIONAL);
packet.setShakeAction(CameraShakeAction.STOP);
sendUpstreamPacket(packet);
this.cameraData.stopCameraShake();
}
@Override
public void sendFog(String... fogNameSpaces) {
Collections.addAll(this.appliedFog, fogNameSpaces);
PlayerFogPacket packet = new PlayerFogPacket();
packet.getFogStack().addAll(this.appliedFog);
sendUpstreamPacket(packet);
this.cameraData.sendFog(fogNameSpaces);
}
@Override
public void removeFog(String... fogNameSpaces) {
if (fogNameSpaces.length == 0) {
this.appliedFog.clear();
} else {
for (String id : fogNameSpaces) {
this.appliedFog.remove(id);
}
}
PlayerFogPacket packet = new PlayerFogPacket();
packet.getFogStack().addAll(this.appliedFog);
sendUpstreamPacket(packet);
this.cameraData.removeFog(fogNameSpaces);
}
@Override
public @NonNull Set<String> fogEffects() {
// Use a copy so that sendFog/removeFog can be called while iterating the returned set (avoid CME)
return Set.copyOf(this.appliedFog);
return this.cameraData.fogEffects();
}
public void addCommandEnum(String name, String enums) {

View file

@ -139,7 +139,7 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
DimensionUtils.switchDimension(session, newDimension);
} else if (DimensionUtils.isCustomBedrockNetherId() && newDimension.equalsIgnoreCase(DimensionUtils.NETHER)) {
// If the player is spawning into the "fake" nether, send them some fog
session.sendFog(DimensionUtils.BEDROCK_FOG_HELL);
session.camera().sendFog(DimensionUtils.BEDROCK_FOG_HELL);
}
ChunkUtils.loadDimension(session);

View file

@ -72,6 +72,7 @@ import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.BlockEntityUtils;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.DimensionUtils;
import java.io.IOException;
import java.util.BitSet;
@ -522,6 +523,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
levelChunkPacket.setChunkX(packet.getX());
levelChunkPacket.setChunkZ(packet.getZ());
levelChunkPacket.setData(Unpooled.wrappedBuffer(payload));
levelChunkPacket.setDimension(DimensionUtils.javaToBedrock(session.getChunkCache().getBedrockDimension()));
session.sendUpstreamPacket(levelChunkPacket);
if (!lecterns.isEmpty()) {

View file

@ -244,6 +244,7 @@ public class ChunkUtils {
byteBuf.readBytes(payload);
LevelChunkPacket data = new LevelChunkPacket();
data.setDimension(DimensionUtils.javaToBedrock(session.getChunkCache().getBedrockDimension()));
data.setChunkX(chunkX);
data.setChunkZ(chunkZ);
data.setSubChunksLength(0);

View file

@ -143,9 +143,9 @@ public class DimensionUtils {
// thinks they are in the end dimension.
if (isCustomBedrockNetherId()) {
if (NETHER.equals(javaDimension)) {
session.sendFog(BEDROCK_FOG_HELL);
session.camera().sendFog(BEDROCK_FOG_HELL);
} else if (NETHER.equals(previousDimension)) {
session.removeFog(BEDROCK_FOG_HELL);
session.camera().removeFog(BEDROCK_FOG_HELL);
}
}
}

View file

@ -266,6 +266,7 @@ public final class EntityUtils {
* Convert Java GameMode to Bedrock GameType
* Needed to account for ordinal differences (spectator is 3 in Java, 6 in Bedrock)
*/
@SuppressWarnings("deprecation") // Must use survival_viewer due to limitations on Bedrock's spectator gamemode
public static GameType toBedrockGamemode(GameMode gamemode) {
return switch (gamemode) {
case CREATIVE -> GameType.CREATIVE;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -7,5 +7,5 @@ org.gradle.vfs.watch=false
group=org.geysermc
id=geyser
version=2.2.1-SNAPSHOT
version=2.2.2-SNAPSHOT
description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers.

View file

@ -9,10 +9,10 @@ netty = "4.1.103.Final"
guava = "29.0-jre"
gson = "2.3.1" # Provided by Spigot 1.8.8
websocket = "1.5.1"
protocol = "3.0.0.Beta1-20231206.150507-114"
protocol-connection = "3.0.0.Beta1-20231206.150507-113"
protocol = "3.0.0.Beta1-20240204.134050-120"
protocol-connection = "3.0.0.Beta1-20240204.134050-119"
raknet = "1.0.0.CR1-20231206.145325-12"
blockstateupdater="1.20.50-20231106.161340-1"
blockstateupdater="1.20.60-20240129.140535-1"
mcauthlib = "d9d773e"
mcprotocollib = "1.20.4-2-20240116.220521-7"
adventure = "4.14.0"
@ -98,6 +98,8 @@ protocol-common = { group = "org.cloudburstmc.protocol", name = "common", versio
protocol-codec = { group = "org.cloudburstmc.protocol", name = "bedrock-codec", version.ref = "protocol" }
protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-connection", version.ref = "protocol-connection" }
math = { group = "org.cloudburstmc.math", name = "immutable", version = "2.0" }
blockstateupdater = { group = "org.cloudburstmc", name = "block-state-updater", version.ref = "blockstateupdater"}
[bundles]