3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-11-20 15:00:11 +01:00

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
Dieser Commit ist enthalten in:
onebeastchris 2024-02-16 17:19:23 +01:00
Commit a4fa2e611c
70 geänderte Dateien mit 14264 neuen und 587 gelöschten Zeilen

Datei anzeigen

@ -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! 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 ## Setting Up
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -25,6 +25,9 @@
package org.geysermc.geyser.api.bedrock.camera; 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 { public enum CameraShake {
POSITIONAL, POSITIONAL,
ROTATIONAL ROTATIONAL

Datei anzeigen

@ -29,8 +29,10 @@ import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.connection.Connection; 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.bedrock.camera.CameraShake;
import org.geysermc.geyser.api.command.CommandSource; 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.GeyserEntity;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; 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. * Represents a player connection used in Geyser.
*/ */
public interface GeyserConnection extends Connection, CommandSource { 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. * @param javaId the Java entity ID to look up.
* @return a {@link GeyserEntity} if present in this connection's entity tracker. * @return a {@link GeyserEntity} if present in this connection's entity tracker.
* @deprecated Use {@link EntityData#entityByJavaId(int)} instead
*/ */
@Deprecated
@NonNull @NonNull
CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId); CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId);
@ -53,11 +74,14 @@ public interface GeyserConnection extends Connection, CommandSource {
* *
* @param emoter the player entity emoting. * @param emoter the player entity emoting.
* @param emoteId the emote ID to send to this client. * @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); 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 * 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> * 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 * 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 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 duration the time in seconds that the shake will occur for
* @param type the type of shake * @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); void shakeCamera(float intensity, float duration, @NonNull CameraShake type);
/** /**
* Stops all camera shake of any type. * Stops all camera shake of any type.
*
* @deprecated Use {@link CameraData#stopCameraShake()} instead.
*/ */
@Deprecated
void stopCameraShake(); 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> * 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. * @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); void sendFog(String... fogNameSpaces);
/** /**
* Removes the given fog IDs from the fog cache, then sends all fog IDs in the cache to the client. * 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. * @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); void removeFog(String... fogNameSpaces);
/** /**
* Returns an immutable copy of all fog affects currently applied to this client. * Returns an immutable copy of all fog affects currently applied to this client.
*
* @deprecated Use {@link CameraData#fogEffects()} instead.
*/ */
@Deprecated
@NonNull @NonNull
Set<String> fogEffects(); Set<String> fogEffects();
} }

Datei anzeigen

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

Datei anzeigen

@ -25,7 +25,15 @@
package org.geysermc.geyser.api.entity.type.player; package org.geysermc.geyser.api.entity.type.player;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.api.entity.type.GeyserEntity;
public interface GeyserPlayerEntity extends GeyserEntity { public interface GeyserPlayerEntity extends GeyserEntity {
/**
* Gets the position of the player.
*
* @return the position of the player.
*/
Vector3f position();
} }

Datei anzeigen

@ -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 {
}

Datei anzeigen

@ -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 {
}

Datei anzeigen

@ -32,11 +32,11 @@ import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.ProtocolConstants; import net.md_5.bungee.protocol.ProtocolConstants;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.dump.BootstrapDumpInfo;
@ -70,11 +70,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
private GeyserImpl geyser; private GeyserImpl geyser;
private static boolean INITIALIZED = false;
@SuppressWarnings({"JavaReflectionMemberAccess", "ResultOfMethodCallIgnored"})
@Override @Override
public void onLoad() { public void onLoad() {
onGeyserInitialize();
}
@Override
public void onGeyserInitialize() {
GeyserLocale.init(this); GeyserLocale.init(this);
// Copied from ViaVersion. // Copied from ViaVersion.
@ -91,29 +93,62 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
getLogger().warning("/_____________\\"); getLogger().warning("/_____________\\");
} }
if (!getDataFolder().exists()) if (!this.loadConfig()) {
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();
return; return;
} }
this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this); this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this);
this.geyserInjector = new GeyserBungeeInjector(this);
} }
@Override @Override
public void onEnable() { 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 // Force-disable query if enabled, or else Geyser won't enable
for (ListenerInfo info : getProxy().getConfig().getListeners()) { 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 GeyserImpl.start();
// 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();
}
}
@SuppressWarnings("unchecked") if (geyserConfig.isLegacyPingPassthrough()) {
private void awaitStartupCompletion(int tries) { this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
// After 20 tries give up waiting. This will happen } else {
// just after 3 minutes approximately this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
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! " + // No need to re-register commands or re-init injector when reloading
"If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times."); if (GeyserImpl.getInstance().isReloading()) {
this.postStartup();
return; return;
} }
try { this.geyserInjector.initializeLocalChannel(this);
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.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands())); 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()) { 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)); 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 @Override
public void onDisable() { public void onGeyserDisable() {
if (geyser != null) {
geyser.disable();
}
}
@Override
public void onGeyserShutdown() {
if (geyser != null) { if (geyser != null) {
geyser.shutdown(); geyser.shutdown();
} }
@ -211,6 +211,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
} }
} }
@Override
public void onDisable() {
this.onGeyserShutdown();
}
@Override @Override
public GeyserBungeeConfiguration getGeyserConfig() { public GeyserBungeeConfiguration getGeyserConfig() {
return geyserConfig; return geyserConfig;
@ -278,4 +283,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
.map(info -> (InetSocketAddress) info.getSocketAddress()) .map(info -> (InetSocketAddress) info.getSocketAddress())
.findFirst(); .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;
}
} }

Datei anzeigen

@ -27,6 +27,8 @@ package org.geysermc.geyser.platform.fabric;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import lombok.Getter;
import lombok.Setter;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
@ -66,13 +68,14 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
@Getter
private static GeyserFabricMod instance; private static GeyserFabricMod instance;
private boolean reloading;
private GeyserImpl geyser; private GeyserImpl geyser;
private ModContainer mod; private ModContainer mod;
private Path dataFolder; private Path dataFolder;
@Setter
private MinecraftServer server; private MinecraftServer server;
private GeyserCommandManager geyserCommandManager; private GeyserCommandManager geyserCommandManager;
@ -85,64 +88,45 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
public void onInitialize() { public void onInitialize() {
instance = this; instance = this;
mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow(); mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow();
onGeyserInitialize();
this.onEnable();
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) {
// Set as an event so we can get the proper IP and port if needed
ServerLifecycleEvents.SERVER_STARTED.register(this::startGeyser);
}
} }
@Override @Override
public void onEnable() { public void onGeyserInitialize() {
dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric"); if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) {
if (!dataFolder.toFile().exists()) { // Set as an event, so we can get the proper IP and port if needed
//noinspection ResultOfMethodCallIgnored ServerLifecycleEvents.SERVER_STARTED.register((server) -> {
dataFolder.toFile().mkdir(); this.server = server;
onGeyserEnable();
});
} }
// Init dataFolder first as local language overrides call getConfigFolder() // These are only registered once
GeyserLocale.init(this); ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onGeyserShutdown());
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler));
try { dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric");
File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml", GeyserLocale.init(this);
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); if (!loadConfig()) {
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class);
} catch (IOException ex) {
LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
ex.printStackTrace();
return; return;
} }
this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode()); this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(PlatformType.FABRIC, this); this.geyser = GeyserImpl.load(PlatformType.FABRIC, this);
if (server == null) {
// Server has yet to start
// Register onDisable so players are properly kicked
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable());
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler));
} else {
// Server has started and this is a reload
startGeyser(this.server);
reloading = false;
}
} }
/** @Override
* Initialize core Geyser. public void onGeyserEnable() {
* A function, as it needs to be called in different places depending on if Geyser is being reloaded or not. if (GeyserImpl.getInstance().isReloading()) {
* if (!loadConfig()) {
* @param server The minecraft server. return;
*/ }
public void startGeyser(MinecraftServer server) { this.geyserLogger.setDebug(geyserConfig.isDebugMode());
this.server = server; GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
} else {
GeyserImpl.start(); this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
}
if (geyserConfig.isLegacyPingPassthrough()) { if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
@ -150,8 +134,12 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger); this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger);
} }
this.geyserCommandManager = new GeyserCommandManager(geyser); GeyserImpl.start();
this.geyserCommandManager.init();
// No need to re-register commands, or re-recreate the world manager when reloading
if (GeyserImpl.getInstance().isReloading()) {
return;
}
this.geyserWorldManager = new GeyserFabricWorldManager(server); this.geyserWorldManager = new GeyserFabricWorldManager(server);
@ -201,14 +189,19 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
} }
@Override @Override
public void onDisable() { public void onGeyserDisable() {
if (geyser != null) {
geyser.disable();
}
}
@Override
public void onGeyserShutdown() {
if (geyser != null) { if (geyser != null) {
geyser.shutdown(); geyser.shutdown();
geyser = null; geyser = null;
} }
if (!reloading) { this.server = null;
this.server = null;
}
} }
@Override @Override
@ -291,11 +284,22 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
} }
} }
public void setReloading(boolean reloading) { @SuppressWarnings("BooleanMethodIsAlwaysInverted")
this.reloading = reloading; private boolean loadConfig() {
} try {
if (!dataFolder.toFile().exists()) {
//noinspection ResultOfMethodCallIgnored
dataFolder.toFile().mkdir();
}
public static GeyserFabricMod getInstance() { File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml",
return instance; (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;
}
} }
} }

Datei anzeigen

@ -32,7 +32,6 @@ import net.minecraft.commands.CommandSourceStack;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandExecutor; import org.geysermc.geyser.command.GeyserCommandExecutor;
import org.geysermc.geyser.platform.fabric.GeyserFabricMod;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale; 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())); sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
return 0; return 0;
} }
if (this.command.name().equals("reload")) {
GeyserFabricMod.getInstance().setReloading(true);
}
if (command.isBedrockOnly() && session == null) { if (command.isBedrockOnly() && session == null) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale())); sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));

Datei anzeigen

@ -57,7 +57,8 @@ public class IntegratedServerMixin implements GeyserServerPortGetter {
private void onOpenToLan(GameType gameType, boolean cheatsAllowed, int port, CallbackInfoReturnable<Boolean> cir) { private void onOpenToLan(GameType gameType, boolean cheatsAllowed, int port, CallbackInfoReturnable<Boolean> cir) {
if (cir.getReturnValueZ()) { if (cir.getReturnValueZ()) {
// If the LAN is opened, starts Geyser. // 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 // Ensure player locale has been loaded, in case it's different from Java system language
GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode); GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode);
// Give indication that Geyser is loaded // Give indication that Geyser is loaded

Datei anzeigen

@ -81,10 +81,6 @@ import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { 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 GeyserSpigotCommandManager geyserCommandManager;
private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotConfiguration geyserConfig;
@ -102,6 +98,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
@Override @Override
public void onLoad() { public void onLoad() {
onGeyserInitialize();
}
@Override
public void onGeyserInitialize() {
GeyserLocale.init(this); GeyserLocale.init(this);
try { 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(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
getLogger().severe(""); getLogger().severe("");
getLogger().severe("*********************************************"); getLogger().severe("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return; 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(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
getLogger().severe(""); getLogger().severe("");
getLogger().severe("*********************************************"); getLogger().severe("*********************************************");
Bukkit.getPluginManager().disablePlugin(this);
return; 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("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
getLogger().severe(""); getLogger().severe("");
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); Bukkit.getPluginManager().disablePlugin(this);
return; return;
} }
if (!loadConfig()) {
return;
}
this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode()) this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode())
: new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); : new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); 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); this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this);
} }
@Override @Override
public void onEnable() { public void onEnable() {
if (this.geyserConfig == null) {
// We failed to initialize correctly
Bukkit.getPluginManager().disablePlugin(this);
return;
}
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
this.geyserCommandManager.init(); this.geyserCommandManager.init();
if (!INITIALIZED) { // Because Bukkit locks its command map upon startup, we need to
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes // add our plugin commands in onEnable, but populating the executor
Bukkit.getPluginManager().registerEvents(new Listener() { // 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 PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
public void onServerLoaded(ServerLoadEvent event) { pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
// Wait until all plugins have loaded so Geyser can start
postStartup();
}
}, this);
// Because Bukkit locks its command map upon startup, we need to commandMap.register(extension.description().id(), "geyserext", pluginCommand);
// add our plugin commands in onEnable, but populating the executor } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
// can happen at any time this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
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);
}
} }
} }
if (INITIALIZED) { // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
// Reload; continue with post startup Bukkit.getPluginManager().registerEvents(new Listener() {
postStartup();
} @EventHandler
public void onServerLoaded(ServerLoadEvent event) {
if (event.getType() == ServerLoadEvent.LoadType.RELOAD) {
geyser.setShuttingDown(false);
}
onGeyserEnable();
}
}, this);
} }
private void postStartup() { public void onGeyserEnable() {
GeyserImpl.start(); // 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. GeyserImpl.start();
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
if (geyserConfig.isLegacyPingPassthrough()) { if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); 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())); geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass()));
boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; // Don't need to re-create the world manager/re-register commands/reinject when reloading
if (isViaVersion) { if (GeyserImpl.getInstance().isReloading()) {
try { return;
// 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();
}
}
} }
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 // 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* // To do their job injecting, then connect into *that*
this.geyserInjector = new GeyserSpigotInjector(isViaVersion); this.geyserInjector = new GeyserSpigotInjector(isViaVersion);
@ -278,6 +263,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
} else { } else {
geyserLogger.debug("Not using NMS adapter as it is disabled via system property."); geyserLogger.debug("Not using NMS adapter as it is disabled via system property.");
} }
if (this.geyserWorldManager == null) { if (this.geyserWorldManager == null) {
// No NMS adapter // No NMS adapter
this.geyserWorldManager = new GeyserSpigotWorldManager(this); this.geyserWorldManager = new GeyserSpigotWorldManager(this);
@ -302,72 +288,72 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands)); command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands));
} }
if (!INITIALIZED) { // Register permissions so they appear in, for example, LuckPerms' UI
// Register permissions so they appear in, for example, LuckPerms' UI // Re-registering permissions throws an error
// Re-registering permissions throws an error for (Map.Entry<String, Command> entry : geyserCommandManager.commands().entrySet()) {
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(); Command command = entry.getValue();
if (command.aliases().contains(entry.getKey())) { if (command.aliases().contains(entry.getKey())) {
// Don't register aliases // Don't register aliases
continue; 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(), Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()), GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); 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(); boolean brigadierSupported = CommodoreProvider.isSupported();
geyserLogger.debug("Brigadier supported? " + brigadierSupported); geyserLogger.debug("Brigadier supported? " + brigadierSupported);
if (brigadierSupported) { if (brigadierSupported) {
GeyserBrigadierSupport.loadBrigadier(this, geyserCommand); GeyserBrigadierSupport.loadBrigadier(this, geyserCommand);
} }
// Check to ensure the current setup can support the protocol version Geyser uses
GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
INITIALIZED = true;
} }
@Override @Override
public void onDisable() { public void onGeyserDisable() {
if (geyser != null) {
geyser.disable();
}
}
@Override
public void onGeyserShutdown() {
if (geyser != null) { if (geyser != null) {
geyser.shutdown(); geyser.shutdown();
} }
@ -376,6 +362,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
} }
} }
@Override
public void onDisable() {
this.onGeyserShutdown();
}
@Override @Override
public GeyserSpigotConfiguration getGeyserConfig() { public GeyserSpigotConfiguration getGeyserConfig() {
return geyserConfig; return geyserConfig;
@ -470,4 +461,25 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
} }
return false; 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;
}
} }

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -44,14 +44,28 @@ public interface GeyserBootstrap {
GeyserWorldManager DEFAULT_CHUNK_MANAGER = new GeyserWorldManager(); 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 * Returns the current GeyserConfiguration

Datei anzeigen

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

Datei anzeigen

@ -86,7 +86,7 @@ public class GeyserCommandManager {
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); 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")); registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions"));
} }

Datei anzeigen

@ -55,7 +55,7 @@ public class ReloadCommand extends GeyserCommand {
geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick"); 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 //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 @Override

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -39,12 +39,20 @@ import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i; 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.command.CommandPermission;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData; 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.api.entity.type.player.GeyserPlayerEntity;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.Entity;
@ -143,6 +151,10 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
addPlayerPacket.setGameType(GameType.SURVIVAL); //TODO 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.setAbilityLayers(BASE_ABILITY_LAYER); // Recommended to be added since 1.19.10, but only needed here for permissions viewing
addPlayerPacket.getMetadata().putFlags(flags); 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()); dirtyMetadata.apply(addPlayerPacket.getMetadata());
setFlagsDirty(false); setFlagsDirty(false);
@ -433,4 +445,9 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
public UUID getTabListUuid() { public UUID getTabListUuid() {
return getUuid(); return getUuid();
} }
@Override
public Vector3f position() {
return this.position.clone();
}
} }

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -31,6 +31,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622; import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630; 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.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
import org.geysermc.geyser.session.GeyserSession; 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 * Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports. * 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 * 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() SUPPORTED_BEDROCK_CODECS.add(Bedrock_v622.CODEC.toBuilder()
.minecraftVersion("1.20.40/1.20.41") .minecraftVersion("1.20.40/1.20.41")
.build()); .build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() SUPPORTED_BEDROCK_CODECS.add(Bedrock_v630.CODEC.toBuilder()
.minecraftVersion("1.20.50/1.20.51") .minecraftVersion("1.20.50/1.20.51")
.build()); .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(); 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. * Gets the {@link PacketCodec} for Minecraft: Java Edition.
* *

Datei anzeigen

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

Datei anzeigen

@ -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.ExperimentData;
import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm; import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm;
import org.cloudburstmc.protocol.bedrock.data.ResourcePackType; 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.PacketSignal;
import org.cloudburstmc.protocol.common.util.Zlib;
import org.geysermc.geyser.Constants; import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.AuthType;
@ -57,18 +74,29 @@ import org.geysermc.geyser.util.VersionCheckUtils;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel; 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 { public class UpstreamPacketHandler extends LoggingPacketHandler {
private boolean networkSettingsRequested = false; private boolean networkSettingsRequested = false;
private final Deque<String> packsToSent = new ArrayDeque<>(); private final Deque<String> packsToSent = new ArrayDeque<>();
private final Set<UUID> brokenResourcePacks = new HashSet<>(); private final Set<UUID> brokenResourcePacks = new HashSet<>();
private final CompressionStrategy compressionStrategy;
private SessionLoadResourcePacksEventImpl resourcePackLoadEvent; private SessionLoadResourcePacksEventImpl resourcePackLoadEvent;
public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) { public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) {
super(geyser, 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) { private PacketSignal translateAndDefault(BedrockPacket packet) {
@ -136,16 +164,15 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
responsePacket.setCompressionAlgorithm(algorithm); responsePacket.setCompressionAlgorithm(algorithm);
responsePacket.setCompressionThreshold(512); responsePacket.setCompressionThreshold(512);
session.sendUpstreamPacketImmediately(responsePacket); 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; networkSettingsRequested = true;
return PacketSignal.HANDLED; return PacketSignal.HANDLED;
} }
@Override @Override
public PacketSignal handle(LoginPacket loginPacket) { 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 // Don't allow new players in if we're no longer operating
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.message")); session.disconnect(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.message"));
return PacketSignal.HANDLED; return PacketSignal.HANDLED;
@ -331,6 +358,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]); ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]);
PackCodec codec = pack.codec(); PackCodec codec = pack.codec();
ResourcePackManifest.Header header = pack.manifest().header(); ResourcePackManifest.Header header = pack.manifest().header();
data.setPackId(header.uuid()); data.setPackId(header.uuid());
int chunkCount = (int) Math.ceil(codec.size() / (double) GeyserResourcePack.CHUNK_SIZE); int chunkCount = (int) Math.ceil(codec.size() / (double) GeyserResourcePack.CHUNK_SIZE);
data.setChunkCount(chunkCount); data.setChunkCount(chunkCount);

Datei anzeigen

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

Datei anzeigen

@ -94,13 +94,16 @@ public final class GeyserServer {
private final GeyserImpl geyser; private final GeyserImpl geyser;
private EventLoopGroup group; private EventLoopGroup group;
// Split childGroup may improve IO
private EventLoopGroup childGroup;
private final ServerBootstrap bootstrap; private final ServerBootstrap bootstrap;
private EventLoopGroup playerGroup; private EventLoopGroup playerGroup;
@Getter @Getter
private final ExpiringMap<InetSocketAddress, InetSocketAddress> proxiedAddresses; 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. * 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) { public GeyserServer(GeyserImpl geyser, int threadCount) {
this.geyser = geyser; 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()) { if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
this.proxiedAddresses = ExpiringMap.builder() this.proxiedAddresses = ExpiringMap.builder()
@ -130,46 +138,51 @@ public final class GeyserServer {
} }
public CompletableFuture<Void> bind(InetSocketAddress address) { public CompletableFuture<Void> bind(InetSocketAddress address) {
CompletableFuture<Void> future = new CompletableFuture<>(); bootstrapFutures = new ChannelFuture[listenCount];
this.bootstrapFuture = this.bootstrap.bind(address).addListener(bindResult -> { for (int i = 0; i < listenCount; i++) {
if (bindResult.cause() != null) { ChannelFuture future = bootstrap.bind(address);
future.completeExceptionally(bindResult.cause()); addHandlers(future);
return; bootstrapFutures[i] = future;
} }
future.complete(null);
});
Channel channel = this.bootstrapFuture.channel(); return Bootstraps.allOf(bootstrapFutures);
}
private void addHandlers(ChannelFuture future) {
Channel channel = future.channel();
// Add our ping handler // Add our ping handler
channel.pipeline() channel.pipeline()
.addFirst(RakConnectionRequestHandler.NAME, new RakConnectionRequestHandler(this)) .addFirst(RakConnectionRequestHandler.NAME, new RakConnectionRequestHandler(this))
.addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this)); .addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this));
// Add proxy handler
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) { if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
channel.pipeline().addFirst("proxy-protocol-decoder", new ProxyServerHandler()); channel.pipeline().addFirst("proxy-protocol-decoder", new ProxyServerHandler());
} }
return future;
} }
public void shutdown() { public void shutdown() {
try { 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; 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; this.playerGroup = null;
future1.sync();
future2.sync(); futureChildGroup.sync();
futureGroup.sync();
futurePlayerGroup.sync();
SkinProvider.shutdown(); SkinProvider.shutdown();
} catch (InterruptedException e) { } catch (InterruptedException e) {
GeyserImpl.getInstance().getLogger().severe("Exception in shutdown process", 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()) { if (this.geyser.getConfig().isDebugMode()) {
this.geyser.getLogger().debug("EventLoop type: " + TRANSPORT.datagramChannel()); this.geyser.getLogger().debug("EventLoop type: " + TRANSPORT.datagramChannel());
if (TRANSPORT.datagramChannel() == NioDatagramChannel.class) { if (TRANSPORT.datagramChannel() == NioDatagramChannel.class) {
@ -188,7 +201,7 @@ public final class GeyserServer {
this.geyser.getLogger().debug("Setting MTU to " + this.geyser.getConfig().getMtu()); this.geyser.getLogger().debug("Setting MTU to " + this.geyser.getConfig().getMtu());
return new ServerBootstrap() return new ServerBootstrap()
.channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannel())) .channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannel()))
.group(group) .group(group, childGroup)
.option(RakChannelOption.RAK_HANDLE_PING, true) .option(RakChannelOption.RAK_HANDLE_PING, true)
.option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu()) .option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu())
.childHandler(serverInitializer); .childHandler(serverInitializer);
@ -224,7 +237,7 @@ public final class GeyserServer {
return true; return true;
} }
public BedrockPong onQuery(InetSocketAddress inetSocketAddress) { public BedrockPong onQuery(Channel channel, InetSocketAddress inetSocketAddress) {
if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) { if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) {
String ip; String ip;
if (geyser.getConfig().isLogPlayerIpAddresses()) { 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. .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) .ipv4Port(this.broadcastPort)
.ipv6Port(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) { if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");

Datei anzeigen

@ -45,7 +45,7 @@ public class RakPingHandler extends SimpleChannelInboundHandler<RakPing> {
protected void channelRead0(ChannelHandlerContext ctx, RakPing msg) { protected void channelRead0(ChannelHandlerContext ctx, RakPing msg) {
long guid = ctx.channel().config().getOption(RakChannelOption.RAK_GUID); 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); ctx.writeAndFlush(pong);
} }
} }

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -41,6 +41,7 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType; import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622; import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630; 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.BlockPropertyData;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
@ -118,6 +119,8 @@ public final class BlockRegistryPopulator {
var blockMappers = ImmutableMap.<ObjectIntPair<String>, Remapper>builder() 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_40", Bedrock_v622.CODEC.getProtocolVersion()), Conversion630_622::remapBlock)
.put(ObjectIntPair.of("1_20_50", Bedrock_v630.CODEC.getProtocolVersion()), tag -> tag) .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(); .build();
// We can keep this strong as nothing should be garbage collected // 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("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("name_hash"); // Quick workaround - was added in 1.19.20
builder.remove("network_id"); // Added in 1.19.80 - ???? 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 //noinspection UnstableApiUsage
builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states"))); builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
vanillaBlockStates.set(i, builder.build()); vanillaBlockStates.set(i, builder.build());
@ -154,6 +158,7 @@ public final class BlockRegistryPopulator {
List<CustomBlockState> customExtBlockStates = new ArrayList<>(); List<CustomBlockState> customExtBlockStates = new ArrayList<>();
int[] remappedVanillaIds = new int[0]; int[] remappedVanillaIds = new int[0];
if (BlockRegistries.CUSTOM_BLOCKS.get().length != 0) { if (BlockRegistries.CUSTOM_BLOCKS.get().length != 0) {
CustomBlockRegistryPopulator.BLOCK_ID.set(CustomBlockRegistryPopulator.START_OFFSET);
for (CustomBlockData customBlock : BlockRegistries.CUSTOM_BLOCKS.get()) { for (CustomBlockData customBlock : BlockRegistries.CUSTOM_BLOCKS.get()) {
customBlockProperties.add(CustomBlockRegistryPopulator.generateBlockPropertyData(customBlock, protocolVersion)); customBlockProperties.add(CustomBlockRegistryPopulator.generateBlockPropertyData(customBlock, protocolVersion));
CustomBlockRegistryPopulator.generateCustomBlockStates(customBlock, customBlockStates, customExtBlockStates); CustomBlockRegistryPopulator.generateCustomBlockStates(customBlock, customBlockStates, customExtBlockStates);

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -44,6 +44,7 @@ import org.geysermc.geyser.item.GeyserCustomMappingData;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.components.WearableSlot; import org.geysermc.geyser.item.components.WearableSlot;
import org.geysermc.geyser.item.type.Item; 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.mappings.MappingsConfigReader;
import org.geysermc.geyser.registry.type.GeyserMappingItem; import org.geysermc.geyser.registry.type.GeyserMappingItem;
import org.geysermc.geyser.registry.type.ItemMapping; 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); 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()); ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build());
return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId); return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId);
@ -124,7 +125,7 @@ public class CustomItemRegistryPopulator {
return true; return true;
} }
public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId) { public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) {
String customIdentifier = customItemData.identifier(); String customIdentifier = customItemData.identifier();
Set<String> repairMaterials = customItemData.repairMaterials(); Set<String> repairMaterials = customItemData.repairMaterials();
@ -152,14 +153,14 @@ public class CustomItemRegistryPopulator {
.build(); .build();
NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId, 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()); ComponentItemData componentItemData = new ComponentItemData(customIdentifier, builder.build());
return new NonVanillaItemRegistration(componentItemData, item, customItemMapping); return new NonVanillaItemRegistration(componentItemData, item, customItemMapping);
} }
private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, Item javaItem, GeyserMappingItem mapping, private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, Item javaItem, GeyserMappingItem mapping,
String customItemName, int customItemId) { String customItemName, int customItemId, int protocolVersion) {
NbtMapBuilder builder = NbtMap.builder(); NbtMapBuilder builder = NbtMap.builder();
builder.putString("name", customItemName) builder.putString("name", customItemName)
.putInt("id", customItemId); .putInt("id", customItemId);
@ -167,7 +168,7 @@ public class CustomItemRegistryPopulator {
NbtMapBuilder itemProperties = NbtMap.builder(); NbtMapBuilder itemProperties = NbtMap.builder();
NbtMapBuilder componentBuilder = 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; boolean canDestroyInCreative = true;
if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here. 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); itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
if (mapping.getArmorType() != null) { if (mapping.getArmorType() != null) {
computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), componentBuilder); computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), itemProperties, componentBuilder);
} }
if (mapping.getFirstBlockRuntimeId() != null) { if (mapping.getFirstBlockRuntimeId() != null) {
@ -193,7 +194,7 @@ public class CustomItemRegistryPopulator {
switch (mapping.getBedrockIdentifier()) { switch (mapping.getBedrockIdentifier()) {
case "minecraft:fire_charge", "minecraft:flint_and_steel" -> computeBlockItemProperties("minecraft:fire", componentBuilder); 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: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" -> case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" ->
computeThrowableProperties(componentBuilder); computeThrowableProperties(componentBuilder);
@ -210,7 +211,7 @@ public class CustomItemRegistryPopulator {
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName, private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName,
int customItemId, OptionalInt creativeCategory, int customItemId, OptionalInt creativeCategory,
String creativeGroup, boolean isHat, boolean displayHandheld) { String creativeGroup, boolean isHat, boolean displayHandheld, int protocolVersion) {
NbtMapBuilder builder = NbtMap.builder(); NbtMapBuilder builder = NbtMap.builder();
builder.putString("name", customItemName) builder.putString("name", customItemName)
.putInt("id", customItemId); .putInt("id", customItemId);
@ -218,17 +219,17 @@ public class CustomItemRegistryPopulator {
NbtMapBuilder itemProperties = NbtMap.builder(); NbtMapBuilder itemProperties = NbtMap.builder();
NbtMapBuilder componentBuilder = 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; boolean canDestroyInCreative = true;
if (customItemData.toolType() != null) { // This is not using the isTool boolean because it is not just a render type here. 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); itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
String armorType = customItemData.armorType(); String armorType = customItemData.armorType();
if (armorType != null) { if (armorType != null) {
computeArmorProperties(armorType, customItemData.protectionValue(), componentBuilder); computeArmorProperties(armorType, customItemData.protectionValue(), itemProperties, componentBuilder);
} }
if (customItemData.isEdible()) { if (customItemData.isEdible()) {
@ -236,7 +237,11 @@ public class CustomItemRegistryPopulator {
} }
if (customItemData.isChargeable()) { 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); computeRenderOffsets(isHat, customItemData, componentBuilder);
@ -258,10 +263,21 @@ public class CustomItemRegistryPopulator {
return builder; return builder;
} }
private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean displayHandheld, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean displayHandheld, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int protocolVersion) {
itemProperties.putCompound("minecraft:icon", NbtMap.builder() NbtMap iconMap;
.putString("texture", customItemData.icon()) if (GameProtocol.is1_20_60orHigher(protocolVersion)) {
.build()); 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()); componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", customItemData.displayName()).build());
// Add a Geyser tag to the item, allowing Molang queries // Add a Geyser tag to the item, allowing Molang queries
@ -336,16 +352,19 @@ public class CustomItemRegistryPopulator {
if (toolType.equals("sword")) { if (toolType.equals("sword")) {
miningSpeed = 1.5f; miningSpeed = 1.5f;
canDestroyInCreative = false; canDestroyInCreative = false;
componentBuilder.putCompound("minecraft:weapon", NbtMap.EMPTY);
} }
itemProperties.putBoolean("hand_equipped", true); itemProperties.putBoolean("hand_equipped", true);
itemProperties.putFloat("mining_speed", miningSpeed); 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; 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) { switch (armorType) {
case "boots" -> { case "boots" -> {
componentBuilder.putString("minecraft:render_offsets", "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()); 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 // setting high use_duration prevents the consume animation from playing
itemProperties.putInt("use_duration", Integer.MAX_VALUE); itemProperties.putInt("use_duration", Integer.MAX_VALUE);
// display item as tool (mainly for crossbow and bow) // display item as tool (mainly for crossbow and bow)
itemProperties.putBoolean("hand_equipped", true); 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) // Make bows, tridents, and crossbows enchantable
componentBuilder.putCompound("minecraft:chargeable", NbtMap.builder().putFloat("movement_modifier", 0.35F).build()); 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) { private static void computeConsumableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int useAnimation, boolean canAlwaysEat) {

Datei anzeigen

@ -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."); GeyserImpl.getInstance().getLogger().info("Registered " + BlockRegistries.CUSTOM_SKULLS.get().size() + " custom skulls as custom blocks.");
} }
} }

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -266,6 +266,7 @@ public final class EntityUtils {
* Convert Java GameMode to Bedrock GameType * Convert Java GameMode to Bedrock GameType
* Needed to account for ordinal differences (spectator is 3 in Java, 6 in Bedrock) * 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) { public static GameType toBedrockGamemode(GameMode gamemode) {
return switch (gamemode) { return switch (gamemode) {
case CREATIVE -> GameType.CREATIVE; case CREATIVE -> GameType.CREATIVE;

Binäre Datei nicht angezeigt.

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@ -7,5 +7,5 @@ org.gradle.vfs.watch=false
group=org.geysermc group=org.geysermc
id=geyser 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. description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers.

Datei anzeigen

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