mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00: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
This commit is contained in:
commit
a4fa2e611c
70 changed files with 14264 additions and 587 deletions
|
@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
|||
|
||||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||
|
||||
### Currently supporting Minecraft Bedrock 1.20.40 - 1.20.51 and Minecraft Java 1.20.4
|
||||
### Currently supporting Minecraft Bedrock 1.20.40 - 1.20.61 and Minecraft Java 1.20.4
|
||||
|
||||
## Setting Up
|
||||
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
|
||||
|
|
|
@ -4,4 +4,5 @@ plugins {
|
|||
|
||||
dependencies {
|
||||
api(libs.base.api)
|
||||
api(libs.math)
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -25,6 +25,9 @@
|
|||
|
||||
package org.geysermc.geyser.api.bedrock.camera;
|
||||
|
||||
/**
|
||||
* Represents a camera shake instruction. Can be sent in {@link CameraData#shakeCamera(float, float, CameraShake)}
|
||||
*/
|
||||
public enum CameraShake {
|
||||
POSITIONAL,
|
||||
ROTATIONAL
|
||||
|
|
|
@ -29,8 +29,10 @@ import org.checkerframework.checker.index.qual.NonNegative;
|
|||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.api.connection.Connection;
|
||||
import org.geysermc.geyser.api.bedrock.camera.CameraData;
|
||||
import org.geysermc.geyser.api.bedrock.camera.CameraShake;
|
||||
import org.geysermc.geyser.api.command.CommandSource;
|
||||
import org.geysermc.geyser.api.entity.EntityData;
|
||||
import org.geysermc.geyser.api.entity.type.GeyserEntity;
|
||||
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
|
||||
|
||||
|
@ -41,10 +43,29 @@ import java.util.concurrent.CompletableFuture;
|
|||
* Represents a player connection used in Geyser.
|
||||
*/
|
||||
public interface GeyserConnection extends Connection, CommandSource {
|
||||
|
||||
/**
|
||||
* Exposes the {@link CameraData} for this connection.
|
||||
* It allows you to send fogs, camera shakes, force camera perspectives, and more.
|
||||
*
|
||||
* @return the CameraData for this connection.
|
||||
*/
|
||||
@NonNull CameraData camera();
|
||||
|
||||
/**
|
||||
* Exposes the {@link EntityData} for this connection.
|
||||
* It allows you to get entities by their Java entity ID, show emotes, and get the player entity.
|
||||
*
|
||||
* @return the EntityData for this connection.
|
||||
*/
|
||||
@NonNull EntityData entities();
|
||||
|
||||
/**
|
||||
* @param javaId the Java entity ID to look up.
|
||||
* @return a {@link GeyserEntity} if present in this connection's entity tracker.
|
||||
* @deprecated Use {@link EntityData#entityByJavaId(int)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
@NonNull
|
||||
CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId);
|
||||
|
||||
|
@ -53,11 +74,14 @@ public interface GeyserConnection extends Connection, CommandSource {
|
|||
*
|
||||
* @param emoter the player entity emoting.
|
||||
* @param emoteId the emote ID to send to this client.
|
||||
* @deprecated use {@link EntityData#showEmote(GeyserPlayerEntity, String)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId);
|
||||
|
||||
/**
|
||||
* Shakes the client's camera.<br><br>
|
||||
* Shakes the client's camera.
|
||||
* <p>
|
||||
* If the camera is already shaking with the same {@link CameraShake} type, then the additional intensity
|
||||
* will be layered on top of the existing intensity, with their own distinct durations.<br>
|
||||
* If the existing shake type is different and the new intensity/duration are not positive, the existing shake only
|
||||
|
@ -66,12 +90,18 @@ public interface GeyserConnection extends Connection, CommandSource {
|
|||
* @param intensity the intensity of the shake. The client has a maximum total intensity of 4.
|
||||
* @param duration the time in seconds that the shake will occur for
|
||||
* @param type the type of shake
|
||||
*
|
||||
* @deprecated Use {@link CameraData#shakeCamera(float, float, CameraShake)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void shakeCamera(float intensity, float duration, @NonNull CameraShake type);
|
||||
|
||||
/**
|
||||
* Stops all camera shake of any type.
|
||||
*
|
||||
* @deprecated Use {@link CameraData#stopCameraShake()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void stopCameraShake();
|
||||
|
||||
/**
|
||||
|
@ -80,19 +110,26 @@ public interface GeyserConnection extends Connection, CommandSource {
|
|||
* Fog IDs can be found <a href="https://wiki.bedrock.dev/documentation/fog-ids.html">here</a>
|
||||
*
|
||||
* @param fogNameSpaces the fog IDs to add. If empty, the existing cached IDs will still be sent.
|
||||
* @deprecated Use {@link CameraData#sendFog(String...)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void sendFog(String... fogNameSpaces);
|
||||
|
||||
/**
|
||||
* Removes the given fog IDs from the fog cache, then sends all fog IDs in the cache to the client.
|
||||
*
|
||||
* @param fogNameSpaces the fog IDs to remove. If empty, all fog IDs will be removed.
|
||||
* @deprecated Use {@link CameraData#removeFog(String...)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void removeFog(String... fogNameSpaces);
|
||||
|
||||
/**
|
||||
* Returns an immutable copy of all fog affects currently applied to this client.
|
||||
*
|
||||
* @deprecated Use {@link CameraData#fogEffects()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@NonNull
|
||||
Set<String> fogEffects();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -25,7 +25,15 @@
|
|||
|
||||
package org.geysermc.geyser.api.entity.type.player;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.geysermc.geyser.api.entity.type.GeyserEntity;
|
||||
|
||||
public interface GeyserPlayerEntity extends GeyserEntity {
|
||||
|
||||
/**
|
||||
* Gets the position of the player.
|
||||
*
|
||||
* @return the position of the player.
|
||||
*/
|
||||
Vector3f position();
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -32,11 +32,11 @@ import net.md_5.bungee.api.plugin.Plugin;
|
|||
import net.md_5.bungee.protocol.ProtocolConstants;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.api.extension.Extension;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
|
@ -70,11 +70,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
|||
|
||||
private GeyserImpl geyser;
|
||||
|
||||
private static boolean INITIALIZED = false;
|
||||
|
||||
@SuppressWarnings({"JavaReflectionMemberAccess", "ResultOfMethodCallIgnored"})
|
||||
@Override
|
||||
public void onLoad() {
|
||||
onGeyserInitialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserInitialize() {
|
||||
GeyserLocale.init(this);
|
||||
|
||||
// Copied from ViaVersion.
|
||||
|
@ -91,29 +93,62 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
|||
getLogger().warning("/_____________\\");
|
||||
}
|
||||
|
||||
if (!getDataFolder().exists())
|
||||
getDataFolder().mkdir();
|
||||
|
||||
try {
|
||||
if (!getDataFolder().exists())
|
||||
getDataFolder().mkdir();
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"),
|
||||
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
if (!this.loadConfig()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this);
|
||||
this.geyserInjector = new GeyserBungeeInjector(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// Big hack - Bungee does not provide us an event to listen to, so schedule a repeating
|
||||
// task that waits for a field to be filled which is set after the plugin enable
|
||||
// process is complete
|
||||
this.awaitStartupCompletion(0);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void awaitStartupCompletion(int tries) {
|
||||
// After 20 tries give up waiting. This will happen just after 3 minutes approximately
|
||||
if (tries >= 20) {
|
||||
this.geyserLogger.warning("BungeeCord plugin startup is taking abnormally long, so Geyser is starting now. " +
|
||||
"If all your plugins are loaded properly, this is a bug! " +
|
||||
"If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times.");
|
||||
this.onGeyserEnable();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Field listenersField = BungeeCord.getInstance().getClass().getDeclaredField("listeners");
|
||||
listenersField.setAccessible(true);
|
||||
|
||||
Collection<Channel> listeners = (Collection<Channel>) listenersField.get(BungeeCord.getInstance());
|
||||
if (listeners.isEmpty()) {
|
||||
this.getProxy().getScheduler().schedule(this, this::onGeyserEnable, tries, TimeUnit.SECONDS);
|
||||
} else {
|
||||
this.awaitStartupCompletion(++tries);
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void onGeyserEnable() {
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
} else {
|
||||
// For consistency with other platforms - create command manager before GeyserImpl#start()
|
||||
// This ensures the command events are called before the item/block ones are
|
||||
this.geyserCommandManager = new GeyserCommandManager(geyser);
|
||||
this.geyserCommandManager.init();
|
||||
}
|
||||
|
||||
// Force-disable query if enabled, or else Geyser won't enable
|
||||
for (ListenerInfo info : getProxy().getConfig().getListeners()) {
|
||||
|
@ -133,54 +168,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
|||
}
|
||||
}
|
||||
|
||||
// Big hack - Bungee does not provide us an event to listen to, so schedule a repeating
|
||||
// task that waits for a field to be filled which is set after the plugin enable
|
||||
// process is complete
|
||||
if (!INITIALIZED) {
|
||||
this.awaitStartupCompletion(0);
|
||||
} else {
|
||||
// No need to "wait" for startup completion, just start Geyser - we're reloading.
|
||||
this.postStartup();
|
||||
}
|
||||
}
|
||||
GeyserImpl.start();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void awaitStartupCompletion(int tries) {
|
||||
// After 20 tries give up waiting. This will happen
|
||||
// just after 3 minutes approximately
|
||||
if (tries >= 20) {
|
||||
this.geyserLogger.warning("BungeeCord plugin startup is taking abnormally long, so Geyser is starting now. " +
|
||||
"If all your plugins are loaded properly, this is a bug! " +
|
||||
"If not, consider cutting down the amount of plugins on your proxy as it is causing abnormally slow starting times.");
|
||||
this.postStartup();
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
} else {
|
||||
this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
|
||||
}
|
||||
|
||||
// No need to re-register commands or re-init injector when reloading
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Field listenersField = BungeeCord.getInstance().getClass().getDeclaredField("listeners");
|
||||
listenersField.setAccessible(true);
|
||||
|
||||
Collection<Channel> listeners = (Collection<Channel>) listenersField.get(BungeeCord.getInstance());
|
||||
if (listeners.isEmpty()) {
|
||||
this.getProxy().getScheduler().schedule(this, this::postStartup, tries, TimeUnit.SECONDS);
|
||||
} else {
|
||||
this.awaitStartupCompletion(++tries);
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void postStartup() {
|
||||
GeyserImpl.start();
|
||||
|
||||
if (!INITIALIZED) {
|
||||
this.geyserInjector = new GeyserBungeeInjector(this);
|
||||
this.geyserInjector.initializeLocalChannel(this);
|
||||
}
|
||||
|
||||
this.geyserCommandManager = new GeyserCommandManager(geyser);
|
||||
this.geyserCommandManager.init();
|
||||
this.geyserInjector.initializeLocalChannel(this);
|
||||
|
||||
this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands()));
|
||||
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
|
||||
|
@ -191,18 +192,17 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
|||
|
||||
this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands));
|
||||
}
|
||||
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
} else {
|
||||
this.geyserBungeePingPassthrough = new GeyserBungeePingPassthrough(getProxy());
|
||||
}
|
||||
|
||||
INITIALIZED = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
public void onGeyserDisable() {
|
||||
if (geyser != null) {
|
||||
geyser.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserShutdown() {
|
||||
if (geyser != null) {
|
||||
geyser.shutdown();
|
||||
}
|
||||
|
@ -211,6 +211,11 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
this.onGeyserShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserBungeeConfiguration getGeyserConfig() {
|
||||
return geyserConfig;
|
||||
|
@ -278,4 +283,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
|||
.map(info -> (InetSocketAddress) info.getSocketAddress())
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean loadConfig() {
|
||||
try {
|
||||
if (!getDataFolder().exists()) //noinspection ResultOfMethodCallIgnored
|
||||
getDataFolder().mkdir();
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"),
|
||||
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ package org.geysermc.geyser.platform.fabric;
|
|||
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
|
@ -66,13 +68,14 @@ import java.util.Optional;
|
|||
import java.util.UUID;
|
||||
|
||||
public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
|
||||
|
||||
@Getter
|
||||
private static GeyserFabricMod instance;
|
||||
|
||||
private boolean reloading;
|
||||
|
||||
private GeyserImpl geyser;
|
||||
private ModContainer mod;
|
||||
private Path dataFolder;
|
||||
|
||||
@Setter
|
||||
private MinecraftServer server;
|
||||
|
||||
private GeyserCommandManager geyserCommandManager;
|
||||
|
@ -85,64 +88,45 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
|
|||
public void onInitialize() {
|
||||
instance = this;
|
||||
mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow();
|
||||
|
||||
this.onEnable();
|
||||
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) {
|
||||
// Set as an event so we can get the proper IP and port if needed
|
||||
ServerLifecycleEvents.SERVER_STARTED.register(this::startGeyser);
|
||||
}
|
||||
onGeyserInitialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric");
|
||||
if (!dataFolder.toFile().exists()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dataFolder.toFile().mkdir();
|
||||
public void onGeyserInitialize() {
|
||||
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) {
|
||||
// Set as an event, so we can get the proper IP and port if needed
|
||||
ServerLifecycleEvents.SERVER_STARTED.register((server) -> {
|
||||
this.server = server;
|
||||
onGeyserEnable();
|
||||
});
|
||||
}
|
||||
|
||||
// Init dataFolder first as local language overrides call getConfigFolder()
|
||||
GeyserLocale.init(this);
|
||||
// These are only registered once
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onGeyserShutdown());
|
||||
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler));
|
||||
|
||||
try {
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml",
|
||||
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric");
|
||||
GeyserLocale.init(this);
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode());
|
||||
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
this.geyser = GeyserImpl.load(PlatformType.FABRIC, this);
|
||||
|
||||
if (server == null) {
|
||||
// Server has yet to start
|
||||
// Register onDisable so players are properly kicked
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable());
|
||||
|
||||
ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler));
|
||||
} else {
|
||||
// Server has started and this is a reload
|
||||
startGeyser(this.server);
|
||||
reloading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize core Geyser.
|
||||
* A function, as it needs to be called in different places depending on if Geyser is being reloaded or not.
|
||||
*
|
||||
* @param server The minecraft server.
|
||||
*/
|
||||
public void startGeyser(MinecraftServer server) {
|
||||
this.server = server;
|
||||
|
||||
GeyserImpl.start();
|
||||
@Override
|
||||
public void onGeyserEnable() {
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
} else {
|
||||
this.geyserCommandManager = new GeyserCommandManager(geyser);
|
||||
this.geyserCommandManager.init();
|
||||
}
|
||||
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
|
@ -150,8 +134,12 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
|
|||
this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger);
|
||||
}
|
||||
|
||||
this.geyserCommandManager = new GeyserCommandManager(geyser);
|
||||
this.geyserCommandManager.init();
|
||||
GeyserImpl.start();
|
||||
|
||||
// No need to re-register commands, or re-recreate the world manager when reloading
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.geyserWorldManager = new GeyserFabricWorldManager(server);
|
||||
|
||||
|
@ -201,14 +189,19 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
public void onGeyserDisable() {
|
||||
if (geyser != null) {
|
||||
geyser.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserShutdown() {
|
||||
if (geyser != null) {
|
||||
geyser.shutdown();
|
||||
geyser = null;
|
||||
}
|
||||
if (!reloading) {
|
||||
this.server = null;
|
||||
}
|
||||
this.server = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -291,11 +284,22 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
|
|||
}
|
||||
}
|
||||
|
||||
public void setReloading(boolean reloading) {
|
||||
this.reloading = reloading;
|
||||
}
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean loadConfig() {
|
||||
try {
|
||||
if (!dataFolder.toFile().exists()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dataFolder.toFile().mkdir();
|
||||
}
|
||||
|
||||
public static GeyserFabricMod getInstance() {
|
||||
return instance;
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml",
|
||||
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class);
|
||||
return true;
|
||||
} catch (IOException ex) {
|
||||
LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ import net.minecraft.commands.CommandSourceStack;
|
|||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandExecutor;
|
||||
import org.geysermc.geyser.platform.fabric.GeyserFabricMod;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
@ -64,9 +63,6 @@ public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implement
|
|||
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
|
||||
return 0;
|
||||
}
|
||||
if (this.command.name().equals("reload")) {
|
||||
GeyserFabricMod.getInstance().setReloading(true);
|
||||
}
|
||||
|
||||
if (command.isBedrockOnly() && session == null) {
|
||||
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));
|
||||
|
|
|
@ -57,7 +57,8 @@ public class IntegratedServerMixin implements GeyserServerPortGetter {
|
|||
private void onOpenToLan(GameType gameType, boolean cheatsAllowed, int port, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (cir.getReturnValueZ()) {
|
||||
// If the LAN is opened, starts Geyser.
|
||||
GeyserFabricMod.getInstance().startGeyser((MinecraftServer) (Object) this);
|
||||
GeyserFabricMod.getInstance().setServer((MinecraftServer) (Object) this);
|
||||
GeyserFabricMod.getInstance().onGeyserEnable();
|
||||
// Ensure player locale has been loaded, in case it's different from Java system language
|
||||
GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode);
|
||||
// Give indication that Geyser is loaded
|
||||
|
|
|
@ -81,10 +81,6 @@ import java.util.UUID;
|
|||
import java.util.logging.Level;
|
||||
|
||||
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
/**
|
||||
* Determines if the plugin has been ran once before, including before /geyser reload.
|
||||
*/
|
||||
private static boolean INITIALIZED = false;
|
||||
|
||||
private GeyserSpigotCommandManager geyserCommandManager;
|
||||
private GeyserSpigotConfiguration geyserConfig;
|
||||
|
@ -102,6 +98,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
onGeyserInitialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserInitialize() {
|
||||
GeyserLocale.init(this);
|
||||
|
||||
try {
|
||||
|
@ -118,6 +119,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.13.2"));
|
||||
getLogger().severe("");
|
||||
getLogger().severe("*********************************************");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -131,6 +133,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_server_type.message", "Paper"));
|
||||
getLogger().severe("");
|
||||
getLogger().severe("*********************************************");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -143,86 +146,72 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
getLogger().severe("This version of Spigot is using an outdated version of netty. Please use Paper instead!");
|
||||
getLogger().severe("");
|
||||
getLogger().severe("*********************************************");
|
||||
return;
|
||||
}
|
||||
|
||||
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
|
||||
try {
|
||||
if (!getDataFolder().exists()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml",
|
||||
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger = GeyserPaperLogger.supported() ? new GeyserPaperLogger(this, getLogger(), geyserConfig.isDebugMode())
|
||||
: new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
|
||||
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
// Turn "(MC: 1.16.4)" into 1.16.4.
|
||||
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
|
||||
|
||||
this.geyser = GeyserImpl.load(PlatformType.SPIGOT, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (this.geyserConfig == null) {
|
||||
// We failed to initialize correctly
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
|
||||
this.geyserCommandManager.init();
|
||||
|
||||
if (!INITIALIZED) {
|
||||
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
|
||||
Bukkit.getPluginManager().registerEvents(new Listener() {
|
||||
// Because Bukkit locks its command map upon startup, we need to
|
||||
// add our plugin commands in onEnable, but populating the executor
|
||||
// can happen at any time (later in #onGeyserEnable())
|
||||
CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
|
||||
for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
|
||||
// Thanks again, Bukkit
|
||||
try {
|
||||
Constructor<PluginCommand> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
|
||||
constructor.setAccessible(true);
|
||||
|
||||
@EventHandler
|
||||
public void onServerLoaded(ServerLoadEvent event) {
|
||||
// Wait until all plugins have loaded so Geyser can start
|
||||
postStartup();
|
||||
}
|
||||
}, this);
|
||||
PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
|
||||
pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
|
||||
|
||||
// Because Bukkit locks its command map upon startup, we need to
|
||||
// add our plugin commands in onEnable, but populating the executor
|
||||
// can happen at any time
|
||||
CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap();
|
||||
for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) {
|
||||
// Thanks again, Bukkit
|
||||
try {
|
||||
Constructor<PluginCommand> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
|
||||
constructor.setAccessible(true);
|
||||
|
||||
PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this);
|
||||
pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!");
|
||||
|
||||
commandMap.register(extension.description().id(), "geyserext", pluginCommand);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
|
||||
this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
|
||||
}
|
||||
commandMap.register(extension.description().id(), "geyserext", pluginCommand);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
|
||||
this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.name(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (INITIALIZED) {
|
||||
// Reload; continue with post startup
|
||||
postStartup();
|
||||
}
|
||||
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
|
||||
Bukkit.getPluginManager().registerEvents(new Listener() {
|
||||
|
||||
@EventHandler
|
||||
public void onServerLoaded(ServerLoadEvent event) {
|
||||
if (event.getType() == ServerLoadEvent.LoadType.RELOAD) {
|
||||
geyser.setShuttingDown(false);
|
||||
}
|
||||
onGeyserEnable();
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
private void postStartup() {
|
||||
GeyserImpl.start();
|
||||
public void onGeyserEnable() {
|
||||
// Configs are loaded once early - so we can create the logger, then load extensions and finally register
|
||||
// extension commands in #onEnable. To ensure reloading geyser also reloads the geyser config, this exists
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(this.geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
}
|
||||
|
||||
// Turn "(MC: 1.16.4)" into 1.16.4.
|
||||
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
|
||||
GeyserImpl.start();
|
||||
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
|
@ -238,20 +227,16 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
}
|
||||
geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass()));
|
||||
|
||||
boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null;
|
||||
if (isViaVersion) {
|
||||
try {
|
||||
// Ensure that we have the latest 4.0.0 changes and not an older ViaVersion version
|
||||
Class.forName("com.viaversion.viaversion.api.ViaManager");
|
||||
} catch (ClassNotFoundException e) {
|
||||
GeyserSpigotVersionChecker.sendOutdatedViaVersionMessage(geyserLogger);
|
||||
isViaVersion = false;
|
||||
if (this.geyserConfig.isDebugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
// Don't need to re-create the world manager/re-register commands/reinject when reloading
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null;
|
||||
|
||||
// Check to ensure the current setup can support the protocol version Geyser uses
|
||||
GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
|
||||
|
||||
// We want to do this late in the server startup process to allow plugins such as ViaVersion and ProtocolLib
|
||||
// To do their job injecting, then connect into *that*
|
||||
this.geyserInjector = new GeyserSpigotInjector(isViaVersion);
|
||||
|
@ -278,6 +263,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
} else {
|
||||
geyserLogger.debug("Not using NMS adapter as it is disabled via system property.");
|
||||
}
|
||||
|
||||
if (this.geyserWorldManager == null) {
|
||||
// No NMS adapter
|
||||
this.geyserWorldManager = new GeyserSpigotWorldManager(this);
|
||||
|
@ -302,72 +288,72 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands));
|
||||
}
|
||||
|
||||
if (!INITIALIZED) {
|
||||
// Register permissions so they appear in, for example, LuckPerms' UI
|
||||
// Re-registering permissions throws an error
|
||||
for (Map.Entry<String, Command> entry : geyserCommandManager.commands().entrySet()) {
|
||||
// Register permissions so they appear in, for example, LuckPerms' UI
|
||||
// Re-registering permissions throws an error
|
||||
for (Map.Entry<String, Command> entry : geyserCommandManager.commands().entrySet()) {
|
||||
Command command = entry.getValue();
|
||||
if (command.aliases().contains(entry.getKey())) {
|
||||
// Don't register aliases
|
||||
continue;
|
||||
}
|
||||
|
||||
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
|
||||
GeyserLocale.getLocaleStringLog(command.description()),
|
||||
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
|
||||
}
|
||||
|
||||
// Register permissions for extension commands
|
||||
for (Map.Entry<Extension, Map<String, Command>> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
|
||||
for (Map.Entry<String, Command> entry : commandEntry.getValue().entrySet()) {
|
||||
Command command = entry.getValue();
|
||||
if (command.aliases().contains(entry.getKey())) {
|
||||
// Don't register aliases
|
||||
continue;
|
||||
}
|
||||
|
||||
if (command.permission().isBlank()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid registering the same permission twice, e.g. for the extension help commands
|
||||
if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
|
||||
continue;
|
||||
}
|
||||
|
||||
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
|
||||
GeyserLocale.getLocaleStringLog(command.description()),
|
||||
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
|
||||
}
|
||||
|
||||
// Register permissions for extension commands
|
||||
for (Map.Entry<Extension, Map<String, Command>> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) {
|
||||
for (Map.Entry<String, Command> entry : commandEntry.getValue().entrySet()) {
|
||||
Command command = entry.getValue();
|
||||
if (command.aliases().contains(entry.getKey())) {
|
||||
// Don't register aliases
|
||||
continue;
|
||||
}
|
||||
|
||||
if (command.permission().isBlank()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid registering the same permission twice, e.g. for the extension help commands
|
||||
if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
|
||||
continue;
|
||||
}
|
||||
|
||||
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
|
||||
GeyserLocale.getLocaleStringLog(command.description()),
|
||||
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
|
||||
}
|
||||
}
|
||||
|
||||
Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
|
||||
"Whether update notifications can be seen", PermissionDefault.OP));
|
||||
|
||||
// Events cannot be unregistered - re-registering results in duplicate firings
|
||||
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
|
||||
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
|
||||
|
||||
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
|
||||
|
||||
Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
|
||||
}
|
||||
|
||||
Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION,
|
||||
"Whether update notifications can be seen", PermissionDefault.OP));
|
||||
|
||||
// Events cannot be unregistered - re-registering results in duplicate firings
|
||||
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
|
||||
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
|
||||
|
||||
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
|
||||
|
||||
Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this);
|
||||
|
||||
boolean brigadierSupported = CommodoreProvider.isSupported();
|
||||
geyserLogger.debug("Brigadier supported? " + brigadierSupported);
|
||||
if (brigadierSupported) {
|
||||
GeyserBrigadierSupport.loadBrigadier(this, geyserCommand);
|
||||
}
|
||||
|
||||
// Check to ensure the current setup can support the protocol version Geyser uses
|
||||
GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
|
||||
|
||||
INITIALIZED = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
public void onGeyserDisable() {
|
||||
if (geyser != null) {
|
||||
geyser.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserShutdown() {
|
||||
if (geyser != null) {
|
||||
geyser.shutdown();
|
||||
}
|
||||
|
@ -376,6 +362,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
this.onGeyserShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserSpigotConfiguration getGeyserConfig() {
|
||||
return geyserConfig;
|
||||
|
@ -470,4 +461,25 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean loadConfig() {
|
||||
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
|
||||
try {
|
||||
if (!getDataFolder().exists()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml",
|
||||
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,9 @@ import org.apache.logging.log4j.core.Appender;
|
|||
import org.apache.logging.log4j.core.Logger;
|
||||
import org.apache.logging.log4j.core.appender.ConsoleAppender;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
|
||||
|
@ -59,7 +59,12 @@ import java.lang.reflect.Method;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
|
@ -68,11 +73,10 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
|||
private GeyserStandaloneConfiguration geyserConfig;
|
||||
private GeyserStandaloneLogger geyserLogger;
|
||||
private IGeyserPingPassthrough geyserPingPassthrough;
|
||||
|
||||
private GeyserStandaloneGUI gui;
|
||||
|
||||
@Getter
|
||||
private boolean useGui = System.console() == null && !isHeadless();
|
||||
private Logger log4jLogger;
|
||||
private String configFilename = "config.yml";
|
||||
|
||||
private GeyserImpl geyser;
|
||||
|
@ -161,23 +165,19 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
|||
}
|
||||
}
|
||||
}
|
||||
bootstrap.onEnable(useGuiOpts, configFilenameOpt);
|
||||
}
|
||||
|
||||
public void onEnable(boolean useGui, String configFilename) {
|
||||
this.configFilename = configFilename;
|
||||
this.useGui = useGui;
|
||||
this.onEnable();
|
||||
bootstrap.useGui = useGuiOpts;
|
||||
bootstrap.configFilename = configFilenameOpt;
|
||||
bootstrap.onGeyserInitialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Logger logger = (Logger) LogManager.getRootLogger();
|
||||
for (Appender appender : logger.getAppenders().values()) {
|
||||
public void onGeyserInitialize() {
|
||||
log4jLogger = (Logger) LogManager.getRootLogger();
|
||||
for (Appender appender : log4jLogger.getAppenders().values()) {
|
||||
// Remove the appender that is not in use
|
||||
// Prevents multiple appenders/double logging and removes harmless errors
|
||||
if ((useGui && appender instanceof TerminalConsoleAppender) || (!useGui && appender instanceof ConsoleAppender)) {
|
||||
logger.removeAppender(appender);
|
||||
log4jLogger.removeAppender(appender);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,7 +190,12 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
|||
}
|
||||
|
||||
LoopbackUtil.checkAndApplyLoopback(geyserLogger);
|
||||
|
||||
|
||||
this.onGeyserEnable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserEnable() {
|
||||
try {
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml",
|
||||
(x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
|
@ -215,14 +220,15 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
|||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
// Allow libraries like Protocol to have their debug information passthrough
|
||||
logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO);
|
||||
log4jLogger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO);
|
||||
|
||||
geyser = GeyserImpl.load(PlatformType.STANDALONE, this);
|
||||
GeyserImpl.start();
|
||||
|
||||
geyserCommandManager = new GeyserCommandManager(geyser);
|
||||
geyserCommandManager.init();
|
||||
|
||||
GeyserImpl.start();
|
||||
|
||||
if (gui != null) {
|
||||
gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager);
|
||||
}
|
||||
|
@ -250,7 +256,14 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
public void onGeyserDisable() {
|
||||
// We can re-register commands on standalone, so why not
|
||||
GeyserImpl.getInstance().commandManager().getCommands().clear();
|
||||
geyser.disable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserShutdown() {
|
||||
geyser.shutdown();
|
||||
System.exit(0);
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
|
|||
|
||||
@Override
|
||||
protected void shutdown() {
|
||||
GeyserImpl.getInstance().getBootstrap().onDisable();
|
||||
GeyserImpl.getInstance().getBootstrap().onGeyserShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -32,10 +32,10 @@ import com.velocitypowered.api.event.proxy.ListenerBoundEvent;
|
|||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.network.ListenerType;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.util.Codec;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
|
@ -46,6 +46,7 @@ import org.geysermc.geyser.api.util.PlatformType;
|
|||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.dump.BootstrapDumpInfo;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
|
||||
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandExecutor;
|
||||
|
@ -63,12 +64,6 @@ import java.util.UUID;
|
|||
|
||||
@Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC")
|
||||
public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
|
||||
/**
|
||||
* Determines if the plugin has been ran once before, including before /geyser reload.
|
||||
*/
|
||||
private static boolean INITIALIZED = false;
|
||||
|
||||
@Inject
|
||||
private Logger logger;
|
||||
|
||||
|
@ -90,52 +85,54 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
|||
private final Path configFolder = Paths.get("plugins/" + GeyserImpl.NAME + "-Velocity/");
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
try {
|
||||
Codec.class.getMethod("codec", Codec.Decoder.class, Codec.Encoder.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// velocitypowered.com has a build that is very outdated
|
||||
logger.error("Please download Velocity from https://papermc.io/downloads#Velocity - the 'stable' Velocity version " +
|
||||
"that has likely been downloaded is very outdated and does not support 1.19.");
|
||||
return;
|
||||
}
|
||||
|
||||
public void onGeyserInitialize() {
|
||||
GeyserLocale.init(this);
|
||||
|
||||
try {
|
||||
if (!configFolder.toFile().exists())
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
configFolder.toFile().mkdirs();
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(),
|
||||
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
return;
|
||||
if (!ProtocolVersion.isSupported(GameProtocol.getJavaProtocolVersion())) {
|
||||
logger.error(" / \\");
|
||||
logger.error(" / \\");
|
||||
logger.error(" / | \\");
|
||||
logger.error(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", proxyServer.getVersion().getName()));
|
||||
logger.error(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
|
||||
logger.error(" / o \\");
|
||||
logger.error("/_____________\\");
|
||||
}
|
||||
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
|
||||
this.geyser = GeyserImpl.load(PlatformType.VELOCITY, this);
|
||||
|
||||
// Hack: Normally triggered by ListenerBoundEvent, but that doesn't fire on /geyser reload
|
||||
if (INITIALIZED) {
|
||||
this.postStartup();
|
||||
}
|
||||
this.geyserInjector = new GeyserVelocityInjector(proxyServer);
|
||||
}
|
||||
|
||||
private void postStartup() {
|
||||
GeyserImpl.start();
|
||||
|
||||
if (!INITIALIZED) {
|
||||
this.geyserInjector = new GeyserVelocityInjector(proxyServer);
|
||||
// Will be initialized after the proxy has been bound
|
||||
@Override
|
||||
public void onGeyserEnable() {
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
if (!loadConfig()) {
|
||||
return;
|
||||
}
|
||||
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
|
||||
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
|
||||
} else {
|
||||
this.geyserCommandManager = new GeyserCommandManager(geyser);
|
||||
this.geyserCommandManager.init();
|
||||
}
|
||||
|
||||
this.geyserCommandManager = new GeyserCommandManager(geyser);
|
||||
this.geyserCommandManager.init();
|
||||
GeyserImpl.start();
|
||||
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
} else {
|
||||
this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer);
|
||||
}
|
||||
|
||||
// No need to re-register commands when reloading
|
||||
if (GeyserImpl.getInstance().isReloading()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands()));
|
||||
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
|
||||
|
@ -147,17 +144,18 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
|||
this.commandManager.register(entry.getKey().description().id(), new GeyserVelocityCommandExecutor(this.geyser, commands));
|
||||
}
|
||||
|
||||
if (geyserConfig.isLegacyPingPassthrough()) {
|
||||
this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
} else {
|
||||
this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer);
|
||||
}
|
||||
|
||||
proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
public void onGeyserDisable() {
|
||||
if (geyser != null) {
|
||||
geyser.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeyserShutdown() {
|
||||
if (geyser != null) {
|
||||
geyser.shutdown();
|
||||
}
|
||||
|
@ -188,26 +186,24 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
|||
|
||||
@Subscribe
|
||||
public void onInit(ProxyInitializeEvent event) {
|
||||
onEnable();
|
||||
this.onGeyserInitialize();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onShutdown(ProxyShutdownEvent event) {
|
||||
onDisable();
|
||||
this.onGeyserShutdown();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onProxyBound(ListenerBoundEvent event) {
|
||||
if (event.getListenerType() == ListenerType.MINECRAFT) {
|
||||
// Once listener is bound, do our startup process
|
||||
this.postStartup();
|
||||
this.onGeyserEnable();
|
||||
|
||||
if (geyserInjector != null) {
|
||||
// After this bound, we know that the channel initializer cannot change without it being ineffective for Velocity, too
|
||||
geyserInjector.initializeLocalChannel(this);
|
||||
}
|
||||
|
||||
INITIALIZED = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,4 +238,21 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean loadConfig() {
|
||||
try {
|
||||
if (!configFolder.toFile().exists())
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
configFolder.toFile().mkdirs();
|
||||
File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(),
|
||||
"config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
|
||||
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class);
|
||||
} catch (IOException ex) {
|
||||
logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex);
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,14 +44,28 @@ public interface GeyserBootstrap {
|
|||
GeyserWorldManager DEFAULT_CHUNK_MANAGER = new GeyserWorldManager();
|
||||
|
||||
/**
|
||||
* Called when the GeyserBootstrap is enabled
|
||||
* Called when the GeyserBootstrap is initialized.
|
||||
* This will only be called once, when Geyser is loading. Calling this must
|
||||
* happen before {@link #onGeyserEnable()}, since this "sets up" Geyser.
|
||||
*/
|
||||
void onEnable();
|
||||
void onGeyserInitialize();
|
||||
|
||||
/**
|
||||
* Called when the GeyserBootstrap is disabled
|
||||
* Called when the GeyserBootstrap is enabled/reloaded.
|
||||
* This starts Geyser, after which, Geyser is in a player-accepting state.
|
||||
*/
|
||||
void onDisable();
|
||||
void onGeyserEnable();
|
||||
|
||||
/**
|
||||
* Called when the GeyserBootstrap is disabled - either before a reload,
|
||||
* of before fully shutting down.
|
||||
*/
|
||||
void onGeyserDisable();
|
||||
|
||||
/**
|
||||
* Called when the GeyserBootstrap is shutting down.
|
||||
*/
|
||||
void onGeyserShutdown();
|
||||
|
||||
/**
|
||||
* Returns the current GeyserConfiguration
|
||||
|
|
|
@ -58,9 +58,7 @@ import org.geysermc.floodgate.news.NewsItemAction;
|
|||
import org.geysermc.geyser.api.GeyserApi;
|
||||
import org.geysermc.geyser.api.event.EventBus;
|
||||
import org.geysermc.geyser.api.event.EventRegistrar;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserPreInitializeEvent;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent;
|
||||
import org.geysermc.geyser.api.event.lifecycle.*;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.network.BedrockListener;
|
||||
import org.geysermc.geyser.api.network.RemoteServer;
|
||||
|
@ -145,6 +143,7 @@ public class GeyserImpl implements GeyserApi {
|
|||
|
||||
private UnixSocketClientListener erosionUnixListener;
|
||||
|
||||
@Setter
|
||||
private volatile boolean shuttingDown = false;
|
||||
|
||||
private ScheduledExecutorService scheduledThread;
|
||||
|
@ -162,8 +161,14 @@ public class GeyserImpl implements GeyserApi {
|
|||
@Getter(AccessLevel.NONE)
|
||||
private Map<String, String> savedRefreshTokens;
|
||||
|
||||
@Getter
|
||||
private static GeyserImpl instance;
|
||||
|
||||
/**
|
||||
* Determines if we're currently reloading. Replaces per-bootstrap reload checks
|
||||
*/
|
||||
private volatile boolean isReloading;
|
||||
|
||||
private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) {
|
||||
instance = this;
|
||||
|
||||
|
@ -172,13 +177,16 @@ public class GeyserImpl implements GeyserApi {
|
|||
this.platformType = platformType;
|
||||
this.bootstrap = bootstrap;
|
||||
|
||||
GeyserLocale.finalizeDefaultLocale(this);
|
||||
|
||||
/* Initialize event bus */
|
||||
this.eventBus = new GeyserEventBus();
|
||||
|
||||
/* Load Extensions */
|
||||
/* Create Extension Manager */
|
||||
this.extensionManager = new GeyserExtensionManager();
|
||||
|
||||
/* Finalize locale loading now that we know the default locale from the config */
|
||||
GeyserLocale.finalizeDefaultLocale(this);
|
||||
|
||||
/* Load Extensions */
|
||||
this.extensionManager.init();
|
||||
this.eventBus.fire(new GeyserPreInitializeEvent(this.extensionManager, this.eventBus));
|
||||
}
|
||||
|
@ -236,11 +244,17 @@ public class GeyserImpl implements GeyserApi {
|
|||
} else if (config.getRemote().authType() == AuthType.FLOODGATE) {
|
||||
VersionCheckUtils.checkForOutdatedFloodgate(logger);
|
||||
}
|
||||
|
||||
VersionCheckUtils.checkForOutdatedJava(logger);
|
||||
}
|
||||
|
||||
private void startInstance() {
|
||||
this.scheduledThread = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("Geyser Scheduled Thread"));
|
||||
|
||||
if (isReloading) {
|
||||
// If we're reloading, the default locale in the config might have changed.
|
||||
GeyserLocale.finalizeDefaultLocale(this);
|
||||
}
|
||||
GeyserLogger logger = bootstrap.getGeyserLogger();
|
||||
GeyserConfiguration config = bootstrap.getGeyserConfig();
|
||||
|
||||
|
@ -536,12 +550,15 @@ public class GeyserImpl implements GeyserApi {
|
|||
|
||||
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
|
||||
|
||||
this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus));
|
||||
if (isReloading) {
|
||||
this.eventBus.fire(new GeyserPostReloadEvent(this.extensionManager, this.eventBus));
|
||||
} else {
|
||||
this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus));
|
||||
}
|
||||
|
||||
if (config.isNotifyOnNewBedrockUpdate()) {
|
||||
VersionCheckUtils.checkForGeyserUpdate(this::getLogger);
|
||||
}
|
||||
|
||||
VersionCheckUtils.checkForOutdatedJava(logger);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -600,9 +617,8 @@ public class GeyserImpl implements GeyserApi {
|
|||
return session.transfer(address, port);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
public void disable() {
|
||||
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown"));
|
||||
shuttingDown = true;
|
||||
|
||||
if (sessionManager.size() >= 1) {
|
||||
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.log", sessionManager.size()));
|
||||
|
@ -616,7 +632,6 @@ public class GeyserImpl implements GeyserApi {
|
|||
skinUploader.close();
|
||||
}
|
||||
newsHandler.shutdown();
|
||||
this.commandManager().getCommands().clear();
|
||||
|
||||
if (this.erosionUnixListener != null) {
|
||||
this.erosionUnixListener.close();
|
||||
|
@ -624,16 +639,29 @@ public class GeyserImpl implements GeyserApi {
|
|||
|
||||
Registries.RESOURCE_PACKS.get().clear();
|
||||
|
||||
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done"));
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
shuttingDown = true;
|
||||
this.disable();
|
||||
this.commandManager().getCommands().clear();
|
||||
|
||||
// Disable extensions, fire the shutdown event
|
||||
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
|
||||
this.extensionManager.disableExtensions();
|
||||
|
||||
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done"));
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
shutdown();
|
||||
this.extensionManager.enableExtensions();
|
||||
bootstrap.onEnable();
|
||||
public void reloadGeyser() {
|
||||
isReloading = true;
|
||||
this.eventBus.fire(new GeyserPreReloadEvent(this.extensionManager, this.eventBus));
|
||||
|
||||
bootstrap.onGeyserDisable();
|
||||
bootstrap.onGeyserEnable();
|
||||
|
||||
isReloading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -744,9 +772,7 @@ public class GeyserImpl implements GeyserApi {
|
|||
throw new RuntimeException("Geyser has not been loaded yet!");
|
||||
}
|
||||
|
||||
// We've been reloaded
|
||||
if (instance.isShuttingDown()) {
|
||||
instance.shuttingDown = false;
|
||||
if (getInstance().isReloading()) {
|
||||
instance.startInstance();
|
||||
} else {
|
||||
instance.initialize();
|
||||
|
@ -797,8 +823,4 @@ public class GeyserImpl implements GeyserApi {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static GeyserImpl getInstance() {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ public class GeyserCommandManager {
|
|||
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
|
||||
}
|
||||
|
||||
if (this.geyser.extensionManager().extensions().size() > 0) {
|
||||
if (!this.geyser.extensionManager().extensions().isEmpty()) {
|
||||
registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions"));
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ public class ReloadCommand extends GeyserCommand {
|
|||
|
||||
geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick");
|
||||
//FIXME Without the tiny wait, players do not get kicked - same happens when Geyser tries to disconnect all sessions on shutdown
|
||||
geyser.getScheduledThread().schedule(geyser::reload, 10, TimeUnit.MILLISECONDS);
|
||||
geyser.getScheduledThread().schedule(geyser::reloadGeyser, 10, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -52,7 +52,7 @@ public class StopCommand extends GeyserCommand {
|
|||
return;
|
||||
}
|
||||
|
||||
geyser.getBootstrap().onDisable();
|
||||
geyser.getBootstrap().onGeyserShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -39,12 +39,20 @@ import net.kyori.adventure.text.Component;
|
|||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.*;
|
||||
import org.cloudburstmc.protocol.bedrock.data.Ability;
|
||||
import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
|
||||
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.GameType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
|
||||
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.*;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddPlayerPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
|
@ -143,6 +151,10 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
|||
addPlayerPacket.setGameType(GameType.SURVIVAL); //TODO
|
||||
addPlayerPacket.setAbilityLayers(BASE_ABILITY_LAYER); // Recommended to be added since 1.19.10, but only needed here for permissions viewing
|
||||
addPlayerPacket.getMetadata().putFlags(flags);
|
||||
|
||||
// Since 1.20.60, the nametag does not show properly if this is not set :/
|
||||
// The nametag does disappear properly when the player is invisible though.
|
||||
dirtyMetadata.put(EntityDataTypes.NAMETAG_ALWAYS_SHOW, (byte) 1);
|
||||
dirtyMetadata.apply(addPlayerPacket.getMetadata());
|
||||
|
||||
setFlagsDirty(false);
|
||||
|
@ -433,4 +445,9 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
|||
public UUID getTabListUuid() {
|
||||
return getUuid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3f position() {
|
||||
return this.position.clone();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -115,7 +115,7 @@ public class GeyserCustomItemData implements CustomItemData {
|
|||
return tags;
|
||||
}
|
||||
|
||||
public static class CustomItemDataBuilder implements Builder {
|
||||
public static class Builder implements CustomItemData.Builder {
|
||||
protected String name = null;
|
||||
protected CustomItemOptions customItemOptions = null;
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ public record GeyserCustomItemOptions(TriState unbreakable,
|
|||
boolean defaultItem) implements CustomItemOptions {
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public static class CustomItemOptionsBuilder implements CustomItemOptions.Builder {
|
||||
public static class Builder implements CustomItemOptions.Builder {
|
||||
private TriState unbreakable = TriState.NOT_SET;
|
||||
private OptionalInt customModelData = OptionalInt.empty();
|
||||
private OptionalInt damagePredicate = OptionalInt.empty();
|
||||
|
|
|
@ -59,7 +59,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
|
|||
private final boolean canAlwaysEat;
|
||||
private final boolean isChargeable;
|
||||
|
||||
public GeyserNonVanillaCustomItemData(NonVanillaCustomItemDataBuilder builder) {
|
||||
public GeyserNonVanillaCustomItemData(Builder builder) {
|
||||
super(builder.name, builder.customItemOptions, builder.displayName, builder.icon, builder.allowOffhand,
|
||||
builder.displayHandheld, builder.textureSize, builder.renderOffsets, builder.tags);
|
||||
|
||||
|
@ -168,7 +168,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
|
|||
return isChargeable;
|
||||
}
|
||||
|
||||
public static class NonVanillaCustomItemDataBuilder extends GeyserCustomItemData.CustomItemDataBuilder implements NonVanillaCustomItemData.Builder {
|
||||
public static class Builder extends GeyserCustomItemData.Builder implements NonVanillaCustomItemData.Builder {
|
||||
private String identifier = null;
|
||||
private int javaId = -1;
|
||||
|
||||
|
@ -197,49 +197,49 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
|
|||
private boolean chargeable = false;
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomItemData.Builder name(@NonNull String name) {
|
||||
return (NonVanillaCustomItemData.Builder) super.name(name);
|
||||
public Builder name(@NonNull String name) {
|
||||
return (Builder) super.name(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomItemData.Builder customItemOptions(@NonNull CustomItemOptions customItemOptions) {
|
||||
public Builder customItemOptions(@NonNull CustomItemOptions customItemOptions) {
|
||||
//Do nothing, as that value won't be read
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomItemData.Builder allowOffhand(boolean allowOffhand) {
|
||||
return (NonVanillaCustomItemData.Builder) super.allowOffhand(allowOffhand);
|
||||
public Builder allowOffhand(boolean allowOffhand) {
|
||||
return (Builder) super.allowOffhand(allowOffhand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomItemData.Builder displayHandheld(boolean displayHandheld) {
|
||||
return (NonVanillaCustomItemData.Builder) super.displayHandheld(displayHandheld);
|
||||
public Builder displayHandheld(boolean displayHandheld) {
|
||||
return (Builder) super.displayHandheld(displayHandheld);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomItemData.Builder displayName(@NonNull String displayName) {
|
||||
return (NonVanillaCustomItemData.Builder) super.displayName(displayName);
|
||||
public Builder displayName(@NonNull String displayName) {
|
||||
return (Builder) super.displayName(displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomItemData.Builder icon(@NonNull String icon) {
|
||||
return (NonVanillaCustomItemData.Builder) super.icon(icon);
|
||||
public Builder icon(@NonNull String icon) {
|
||||
return (Builder) super.icon(icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomItemData.Builder textureSize(int textureSize) {
|
||||
return (NonVanillaCustomItemData.Builder) super.textureSize(textureSize);
|
||||
public Builder textureSize(int textureSize) {
|
||||
return (Builder) super.textureSize(textureSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomItemData.Builder renderOffsets(CustomRenderOffsets renderOffsets) {
|
||||
return (NonVanillaCustomItemData.Builder) super.renderOffsets(renderOffsets);
|
||||
public Builder renderOffsets(CustomRenderOffsets renderOffsets) {
|
||||
return (Builder) super.renderOffsets(renderOffsets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomItemData.Builder tags(@Nullable Set<String> tags) {
|
||||
return (NonVanillaCustomItemData.Builder) super.tags(tags);
|
||||
public Builder tags(@Nullable Set<String> tags) {
|
||||
return (Builder) super.tags(tags);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -62,7 +62,7 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents {
|
|||
boolean placeAir;
|
||||
Set<String> tags;
|
||||
|
||||
private GeyserCustomBlockComponents(CustomBlockComponentsBuilder builder) {
|
||||
private GeyserCustomBlockComponents(Builder builder) {
|
||||
this.selectionBox = builder.selectionBox;
|
||||
this.collisionBox = builder.collisionBox;
|
||||
this.displayName = builder.displayName;
|
||||
|
@ -157,7 +157,7 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents {
|
|||
return tags;
|
||||
}
|
||||
|
||||
public static class CustomBlockComponentsBuilder implements Builder {
|
||||
public static class Builder implements CustomBlockComponents.Builder {
|
||||
protected BoxComponent selectionBox;
|
||||
protected BoxComponent collisionBox;
|
||||
protected String displayName;
|
||||
|
|
|
@ -54,7 +54,7 @@ public class GeyserCustomBlockData implements CustomBlockData {
|
|||
|
||||
private final Map<String, Object> defaultProperties;
|
||||
|
||||
GeyserCustomBlockData(CustomBlockDataBuilder builder) {
|
||||
GeyserCustomBlockData(Builder builder) {
|
||||
this.name = builder.name;
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("Name must be set");
|
||||
|
@ -141,10 +141,10 @@ public class GeyserCustomBlockData implements CustomBlockData {
|
|||
|
||||
@Override
|
||||
public CustomBlockState.@NonNull Builder blockStateBuilder() {
|
||||
return new GeyserCustomBlockState.CustomBlockStateBuilder(this);
|
||||
return new GeyserCustomBlockState.Builder(this);
|
||||
}
|
||||
|
||||
public static class CustomBlockDataBuilder implements Builder {
|
||||
public static class Builder implements CustomBlockData.Builder {
|
||||
private String name;
|
||||
private boolean includedInCreativeInventory;
|
||||
private CreativeCategory creativeCategory;
|
||||
|
|
|
@ -64,7 +64,7 @@ public class GeyserCustomBlockState implements CustomBlockState {
|
|||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class CustomBlockStateBuilder implements CustomBlockState.Builder {
|
||||
public static class Builder implements CustomBlockState.Builder {
|
||||
private final CustomBlockData blockData;
|
||||
private final Object2ObjectMap<String, Object> properties = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ public class GeyserGeometryComponent implements GeometryComponent {
|
|||
private final String identifier;
|
||||
private final Map<String, String> boneVisibility;
|
||||
|
||||
GeyserGeometryComponent(GeometryComponentBuilder builder) {
|
||||
GeyserGeometryComponent(Builder builder) {
|
||||
this.identifier = builder.identifier;
|
||||
this.boneVisibility = builder.boneVisibility;
|
||||
}
|
||||
|
@ -52,18 +52,18 @@ public class GeyserGeometryComponent implements GeometryComponent {
|
|||
return boneVisibility;
|
||||
}
|
||||
|
||||
public static class GeometryComponentBuilder implements Builder {
|
||||
public static class Builder implements GeometryComponent.Builder {
|
||||
private String identifier;
|
||||
private Map<String, String> boneVisibility;
|
||||
|
||||
@Override
|
||||
public GeometryComponent.Builder identifier(@NonNull String identifier) {
|
||||
public Builder identifier(@NonNull String identifier) {
|
||||
this.identifier = identifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeometryComponent.Builder boneVisibility(@Nullable Map<String, String> boneVisibility) {
|
||||
public Builder boneVisibility(@Nullable Map<String, String> boneVisibility) {
|
||||
this.boneVisibility = boneVisibility;
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ public class GeyserJavaBlockState implements JavaBlockState {
|
|||
String pistonBehavior;
|
||||
boolean hasBlockEntity;
|
||||
|
||||
private GeyserJavaBlockState(JavaBlockStateBuilder builder) {
|
||||
private GeyserJavaBlockState(Builder builder) {
|
||||
this.identifier = builder.identifier;
|
||||
this.javaId = builder.javaId;
|
||||
this.stateGroupId = builder.stateGroupId;
|
||||
|
@ -81,7 +81,7 @@ public class GeyserJavaBlockState implements JavaBlockState {
|
|||
return hasBlockEntity;
|
||||
}
|
||||
|
||||
public static class JavaBlockStateBuilder implements Builder {
|
||||
public static class Builder implements JavaBlockState.Builder {
|
||||
private String identifier;
|
||||
private int javaId;
|
||||
private int stateGroupId;
|
||||
|
|
|
@ -36,7 +36,7 @@ public class GeyserMaterialInstance implements MaterialInstance {
|
|||
private final boolean faceDimming;
|
||||
private final boolean ambientOcclusion;
|
||||
|
||||
GeyserMaterialInstance(MaterialInstanceBuilder builder) {
|
||||
GeyserMaterialInstance(Builder builder) {
|
||||
this.texture = builder.texture;
|
||||
this.renderMethod = builder.renderMethod;
|
||||
this.faceDimming = builder.faceDimming;
|
||||
|
@ -63,7 +63,7 @@ public class GeyserMaterialInstance implements MaterialInstance {
|
|||
return ambientOcclusion;
|
||||
}
|
||||
|
||||
public static class MaterialInstanceBuilder implements Builder {
|
||||
public static class Builder implements MaterialInstance.Builder {
|
||||
private String texture;
|
||||
private String renderMethod;
|
||||
private boolean faceDimming;
|
||||
|
|
|
@ -37,7 +37,7 @@ import java.util.List;
|
|||
public class GeyserNonVanillaCustomBlockData extends GeyserCustomBlockData implements NonVanillaCustomBlockData {
|
||||
private final String namespace;
|
||||
|
||||
GeyserNonVanillaCustomBlockData(NonVanillaCustomBlockDataBuilder builder) {
|
||||
GeyserNonVanillaCustomBlockData(Builder builder) {
|
||||
super(builder);
|
||||
|
||||
this.namespace = builder.namespace;
|
||||
|
@ -56,58 +56,58 @@ public class GeyserNonVanillaCustomBlockData extends GeyserCustomBlockData imple
|
|||
return this.namespace;
|
||||
}
|
||||
|
||||
public static class NonVanillaCustomBlockDataBuilder extends CustomBlockDataBuilder implements NonVanillaCustomBlockData.Builder {
|
||||
public static class Builder extends GeyserCustomBlockData.Builder implements NonVanillaCustomBlockData.Builder {
|
||||
private String namespace;
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder namespace(@NonNull String namespace) {
|
||||
public Builder namespace(@NonNull String namespace) {
|
||||
this.namespace = namespace;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder name(@NonNull String name) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.name(name);
|
||||
public Builder name(@NonNull String name) {
|
||||
return (Builder) super.name(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder includedInCreativeInventory(boolean includedInCreativeInventory) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.includedInCreativeInventory(includedInCreativeInventory);
|
||||
public Builder includedInCreativeInventory(boolean includedInCreativeInventory) {
|
||||
return (Builder) super.includedInCreativeInventory(includedInCreativeInventory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder creativeCategory(@Nullable CreativeCategory creativeCategories) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.creativeCategory(creativeCategories);
|
||||
public Builder creativeCategory(@Nullable CreativeCategory creativeCategories) {
|
||||
return (Builder) super.creativeCategory(creativeCategories);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder creativeGroup(@Nullable String creativeGroup) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.creativeGroup(creativeGroup);
|
||||
public Builder creativeGroup(@Nullable String creativeGroup) {
|
||||
return (Builder) super.creativeGroup(creativeGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder components(@NonNull CustomBlockComponents components) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.components(components);
|
||||
public Builder components(@NonNull CustomBlockComponents components) {
|
||||
return (Builder) super.components(components);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder booleanProperty(@NonNull String propertyName) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.booleanProperty(propertyName);
|
||||
public Builder booleanProperty(@NonNull String propertyName) {
|
||||
return (Builder) super.booleanProperty(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder intProperty(@NonNull String propertyName, List<Integer> values) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.intProperty(propertyName, values);
|
||||
public Builder intProperty(@NonNull String propertyName, List<Integer> values) {
|
||||
return (Builder) super.intProperty(propertyName, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder stringProperty(@NonNull String propertyName, List<String> values) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.stringProperty(propertyName, values);
|
||||
public Builder stringProperty(@NonNull String propertyName, List<String> values) {
|
||||
return (Builder) super.stringProperty(propertyName, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder permutations(@NonNull List<CustomBlockPermutation> permutations) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.permutations(permutations);
|
||||
public Builder permutations(@NonNull List<CustomBlockPermutation> permutations) {
|
||||
return (Builder) super.permutations(permutations);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v649.Bedrock_v649;
|
||||
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
|
@ -46,7 +47,7 @@ public final class GameProtocol {
|
|||
* Default Bedrock codec that should act as a fallback. Should represent the latest available
|
||||
* release of the game that Geyser supports.
|
||||
*/
|
||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v630.CODEC;
|
||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v649.CODEC;
|
||||
|
||||
/**
|
||||
* A list of all supported Bedrock versions that can join Geyser
|
||||
|
@ -63,9 +64,12 @@ public final class GameProtocol {
|
|||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v622.CODEC.toBuilder()
|
||||
.minecraftVersion("1.20.40/1.20.41")
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v630.CODEC.toBuilder()
|
||||
.minecraftVersion("1.20.50/1.20.51")
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
|
||||
.minecraftVersion("1.20.60/1.20.61")
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,6 +92,10 @@ public final class GameProtocol {
|
|||
return session.getUpstream().getProtocolVersion() < Bedrock_v630.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
public static boolean is1_20_60orHigher(int protocolVersion) {
|
||||
return protocolVersion >= Bedrock_v649.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link PacketCodec} for Minecraft: Java Edition.
|
||||
*
|
||||
|
|
|
@ -100,6 +100,16 @@ public class LoggingPacketHandler implements BedrockPacketHandler {
|
|||
return defaultHandler(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketSignal handle(CameraPresetsPacket packet) {
|
||||
return defaultHandler(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketSignal handle(CameraInstructionPacket packet) {
|
||||
return defaultHandler(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketSignal handle(CommandBlockUpdatePacket packet) {
|
||||
return defaultHandler(packet);
|
||||
|
|
|
@ -33,8 +33,25 @@ import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
|
|||
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ResourcePackType;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.*;
|
||||
import org.cloudburstmc.protocol.bedrock.netty.codec.compression.CompressionStrategy;
|
||||
import org.cloudburstmc.protocol.bedrock.netty.codec.compression.SimpleCompressionStrategy;
|
||||
import org.cloudburstmc.protocol.bedrock.netty.codec.compression.ZlibCompression;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LoginPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ModalFormResponsePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.NetworkSettingsPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.RequestNetworkSettingsPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackChunkDataPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackChunkRequestPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackClientResponsePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackDataInfoPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ResourcePackStackPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ResourcePacksInfoPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetTitlePacket;
|
||||
import org.cloudburstmc.protocol.common.PacketSignal;
|
||||
import org.cloudburstmc.protocol.common.util.Zlib;
|
||||
import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
|
@ -57,18 +74,29 @@ import org.geysermc.geyser.util.VersionCheckUtils;
|
|||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.*;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
private boolean networkSettingsRequested = false;
|
||||
private final Deque<String> packsToSent = new ArrayDeque<>();
|
||||
private final Set<UUID> brokenResourcePacks = new HashSet<>();
|
||||
private final CompressionStrategy compressionStrategy;
|
||||
|
||||
private SessionLoadResourcePacksEventImpl resourcePackLoadEvent;
|
||||
|
||||
public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) {
|
||||
super(geyser, session);
|
||||
|
||||
ZlibCompression compression = new ZlibCompression(Zlib.RAW);
|
||||
compression.setLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
|
||||
this.compressionStrategy = new SimpleCompressionStrategy(compression);
|
||||
}
|
||||
|
||||
private PacketSignal translateAndDefault(BedrockPacket packet) {
|
||||
|
@ -136,16 +164,15 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
responsePacket.setCompressionAlgorithm(algorithm);
|
||||
responsePacket.setCompressionThreshold(512);
|
||||
session.sendUpstreamPacketImmediately(responsePacket);
|
||||
session.getUpstream().getSession().getPeer().setCompression(compressionStrategy);
|
||||
|
||||
session.getUpstream().getSession().setCompression(algorithm);
|
||||
session.getUpstream().getSession().setCompressionLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
|
||||
networkSettingsRequested = true;
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketSignal handle(LoginPacket loginPacket) {
|
||||
if (geyser.isShuttingDown()) {
|
||||
if (geyser.isShuttingDown() || geyser.isReloading()) {
|
||||
// Don't allow new players in if we're no longer operating
|
||||
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.message"));
|
||||
return PacketSignal.HANDLED;
|
||||
|
@ -331,6 +358,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]);
|
||||
PackCodec codec = pack.codec();
|
||||
ResourcePackManifest.Header header = pack.manifest().header();
|
||||
|
||||
data.setPackId(header.uuid());
|
||||
int chunkCount = (int) Math.ceil(codec.size() / (double) GeyserResourcePack.CHUNK_SIZE);
|
||||
data.setChunkCount(chunkCount);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -94,13 +94,16 @@ public final class GeyserServer {
|
|||
|
||||
private final GeyserImpl geyser;
|
||||
private EventLoopGroup group;
|
||||
// Split childGroup may improve IO
|
||||
private EventLoopGroup childGroup;
|
||||
private final ServerBootstrap bootstrap;
|
||||
private EventLoopGroup playerGroup;
|
||||
|
||||
@Getter
|
||||
private final ExpiringMap<InetSocketAddress, InetSocketAddress> proxiedAddresses;
|
||||
private final int listenCount;
|
||||
|
||||
private ChannelFuture bootstrapFuture;
|
||||
private ChannelFuture[] bootstrapFutures;
|
||||
|
||||
/**
|
||||
* The port to broadcast in the pong. This can be different from the port the server is bound to, e.g. due to port forwarding.
|
||||
|
@ -109,9 +112,14 @@ public final class GeyserServer {
|
|||
|
||||
public GeyserServer(GeyserImpl geyser, int threadCount) {
|
||||
this.geyser = geyser;
|
||||
this.group = TRANSPORT.eventLoopGroupFactory().apply(threadCount);
|
||||
this.listenCount = Bootstraps.isReusePortAvailable() ? Integer.getInteger("Geyser.ListenCount", 2) : 1;
|
||||
GeyserImpl.getInstance().getLogger().debug("Listen thread count: " + listenCount);
|
||||
this.group = TRANSPORT.eventLoopGroupFactory().apply(listenCount);
|
||||
this.childGroup = TRANSPORT.eventLoopGroupFactory().apply(threadCount);
|
||||
|
||||
this.bootstrap = this.createBootstrap(this.group);
|
||||
this.bootstrap = this.createBootstrap();
|
||||
// setup SO_REUSEPORT if exists
|
||||
Bootstraps.setupBootstrap(this.bootstrap);
|
||||
|
||||
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||
this.proxiedAddresses = ExpiringMap.builder()
|
||||
|
@ -130,46 +138,51 @@ public final class GeyserServer {
|
|||
}
|
||||
|
||||
public CompletableFuture<Void> bind(InetSocketAddress address) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
this.bootstrapFuture = this.bootstrap.bind(address).addListener(bindResult -> {
|
||||
if (bindResult.cause() != null) {
|
||||
future.completeExceptionally(bindResult.cause());
|
||||
return;
|
||||
}
|
||||
future.complete(null);
|
||||
});
|
||||
bootstrapFutures = new ChannelFuture[listenCount];
|
||||
for (int i = 0; i < listenCount; i++) {
|
||||
ChannelFuture future = bootstrap.bind(address);
|
||||
addHandlers(future);
|
||||
bootstrapFutures[i] = future;
|
||||
}
|
||||
|
||||
Channel channel = this.bootstrapFuture.channel();
|
||||
return Bootstraps.allOf(bootstrapFutures);
|
||||
}
|
||||
|
||||
private void addHandlers(ChannelFuture future) {
|
||||
Channel channel = future.channel();
|
||||
// Add our ping handler
|
||||
channel.pipeline()
|
||||
.addFirst(RakConnectionRequestHandler.NAME, new RakConnectionRequestHandler(this))
|
||||
.addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this));
|
||||
|
||||
// Add proxy handler
|
||||
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||
channel.pipeline().addFirst("proxy-protocol-decoder", new ProxyServerHandler());
|
||||
}
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
try {
|
||||
Future<?> future1 = this.group.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
Future<?> futureChildGroup = this.childGroup.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
this.childGroup = null;
|
||||
Future<?> futureGroup = this.group.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
this.group = null;
|
||||
Future<?> future2 = this.playerGroup.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
Future<?> futurePlayerGroup = this.playerGroup.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
this.playerGroup = null;
|
||||
future1.sync();
|
||||
future2.sync();
|
||||
|
||||
futureChildGroup.sync();
|
||||
futureGroup.sync();
|
||||
futurePlayerGroup.sync();
|
||||
|
||||
SkinProvider.shutdown();
|
||||
} catch (InterruptedException e) {
|
||||
GeyserImpl.getInstance().getLogger().severe("Exception in shutdown process", e);
|
||||
}
|
||||
this.bootstrapFuture.channel().closeFuture().syncUninterruptibly();
|
||||
for (ChannelFuture f : bootstrapFutures) {
|
||||
f.channel().closeFuture().syncUninterruptibly();
|
||||
}
|
||||
}
|
||||
|
||||
private ServerBootstrap createBootstrap(EventLoopGroup group) {
|
||||
private ServerBootstrap createBootstrap() {
|
||||
if (this.geyser.getConfig().isDebugMode()) {
|
||||
this.geyser.getLogger().debug("EventLoop type: " + TRANSPORT.datagramChannel());
|
||||
if (TRANSPORT.datagramChannel() == NioDatagramChannel.class) {
|
||||
|
@ -188,7 +201,7 @@ public final class GeyserServer {
|
|||
this.geyser.getLogger().debug("Setting MTU to " + this.geyser.getConfig().getMtu());
|
||||
return new ServerBootstrap()
|
||||
.channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannel()))
|
||||
.group(group)
|
||||
.group(group, childGroup)
|
||||
.option(RakChannelOption.RAK_HANDLE_PING, true)
|
||||
.option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu())
|
||||
.childHandler(serverInitializer);
|
||||
|
@ -224,7 +237,7 @@ public final class GeyserServer {
|
|||
return true;
|
||||
}
|
||||
|
||||
public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
|
||||
public BedrockPong onQuery(Channel channel, InetSocketAddress inetSocketAddress) {
|
||||
if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) {
|
||||
String ip;
|
||||
if (geyser.getConfig().isLogPlayerIpAddresses()) {
|
||||
|
@ -257,7 +270,7 @@ public final class GeyserServer {
|
|||
.version(GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()) // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
|
||||
.ipv4Port(this.broadcastPort)
|
||||
.ipv6Port(this.broadcastPort)
|
||||
.serverId(bootstrapFuture.channel().config().getOption(RakChannelOption.RAK_GUID));
|
||||
.serverId(channel.config().getOption(RakChannelOption.RAK_GUID));
|
||||
|
||||
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
|
||||
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
|
||||
|
|
|
@ -45,7 +45,7 @@ public class RakPingHandler extends SimpleChannelInboundHandler<RakPing> {
|
|||
protected void channelRead0(ChannelHandlerContext ctx, RakPing msg) {
|
||||
long guid = ctx.channel().config().getOption(RakChannelOption.RAK_GUID);
|
||||
|
||||
RakPong pong = msg.reply(guid, this.server.onQuery(msg.getSender()).toByteBuf());
|
||||
RakPong pong = msg.reply(guid, this.server.onQuery(ctx.channel(), msg.getSender()).toByteBuf());
|
||||
ctx.writeAndFlush(pong);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
package org.geysermc.geyser.registry.loader;
|
||||
|
||||
import org.geysermc.geyser.api.bedrock.camera.CameraFade;
|
||||
import org.geysermc.geyser.api.bedrock.camera.CameraPosition;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.NonVanillaCustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
||||
|
@ -39,6 +41,8 @@ import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
|||
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.api.pack.UrlPackCodec;
|
||||
import org.geysermc.geyser.impl.camera.GeyserCameraFade;
|
||||
import org.geysermc.geyser.impl.camera.GeyserCameraPosition;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.event.GeyserEventRegistrar;
|
||||
import org.geysermc.geyser.item.GeyserCustomItemData;
|
||||
|
@ -67,21 +71,25 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
|
|||
// misc
|
||||
providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0]));
|
||||
|
||||
providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.CustomBlockComponentsBuilder());
|
||||
providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.CustomBlockDataBuilder());
|
||||
providers.put(JavaBlockState.Builder.class, args -> new GeyserJavaBlockState.JavaBlockStateBuilder());
|
||||
providers.put(NonVanillaCustomBlockData.Builder.class, args -> new GeyserNonVanillaCustomBlockData.NonVanillaCustomBlockDataBuilder());
|
||||
providers.put(MaterialInstance.Builder.class, args -> new GeyserMaterialInstance.MaterialInstanceBuilder());
|
||||
providers.put(GeometryComponent.Builder.class, args -> new GeyserGeometryComponent.GeometryComponentBuilder());
|
||||
providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.Builder());
|
||||
providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.Builder());
|
||||
providers.put(JavaBlockState.Builder.class, args -> new GeyserJavaBlockState.Builder());
|
||||
providers.put(NonVanillaCustomBlockData.Builder.class, args -> new GeyserNonVanillaCustomBlockData.Builder());
|
||||
providers.put(MaterialInstance.Builder.class, args -> new GeyserMaterialInstance.Builder());
|
||||
providers.put(GeometryComponent.Builder.class, args -> new GeyserGeometryComponent.Builder());
|
||||
|
||||
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
|
||||
providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0]));
|
||||
providers.put(UrlPackCodec.class, args -> new GeyserUrlPackCodec((String) args[0], (String) args[1]));
|
||||
|
||||
// items
|
||||
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder());
|
||||
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder());
|
||||
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder());
|
||||
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.Builder());
|
||||
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.Builder());
|
||||
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.Builder());
|
||||
|
||||
// cameras
|
||||
providers.put(CameraFade.Builder.class, args -> new GeyserCameraFade.Builder());
|
||||
providers.put(CameraPosition.Builder.class, args -> new GeyserCameraPosition.Builder());
|
||||
|
||||
return providers;
|
||||
}
|
||||
|
|
|
@ -35,17 +35,22 @@ import org.geysermc.geyser.GeyserImpl;
|
|||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.component.*;
|
||||
import org.geysermc.geyser.api.block.custom.component.BoxComponent;
|
||||
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
||||
import org.geysermc.geyser.api.block.custom.component.GeometryComponent;
|
||||
import org.geysermc.geyser.api.block.custom.component.MaterialInstance;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.BlockFilterType;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.Face;
|
||||
import org.geysermc.geyser.api.block.custom.component.TransformationComponent;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.api.util.CreativeCategory;
|
||||
import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents.CustomBlockComponentsBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockData.CustomBlockDataBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockData;
|
||||
import org.geysermc.geyser.level.block.GeyserGeometryComponent;
|
||||
import org.geysermc.geyser.level.block.GeyserMaterialInstance;
|
||||
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.mappings.util.CustomBlockComponentsMapping;
|
||||
|
@ -57,7 +62,14 @@ import org.geysermc.geyser.util.BlockUtils;
|
|||
import org.geysermc.geyser.util.MathUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
@ -248,7 +260,7 @@ public class MappingsReader_v1 extends MappingsReader {
|
|||
boolean onlyOverrideStates = node.has("only_override_states") && node.get("only_override_states").asBoolean();
|
||||
|
||||
// Create the data for the overall block
|
||||
CustomBlockData.Builder customBlockDataBuilder = new CustomBlockDataBuilder()
|
||||
CustomBlockData.Builder customBlockDataBuilder = new GeyserCustomBlockData.Builder()
|
||||
.name(name)
|
||||
.includedInCreativeInventory(includedInCreativeInventory)
|
||||
.creativeCategory(creativeCategory)
|
||||
|
@ -360,7 +372,7 @@ public class MappingsReader_v1 extends MappingsReader {
|
|||
int id = BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(stateKey, -1);
|
||||
BoxComponent boxComponent = createBoxComponent(id);
|
||||
BoxComponent extendedBoxComponent = createExtendedBoxComponent(id);
|
||||
CustomBlockComponents.Builder builder = new CustomBlockComponentsBuilder()
|
||||
CustomBlockComponents.Builder builder = new GeyserCustomBlockComponents.Builder()
|
||||
.collisionBox(boxComponent)
|
||||
.selectionBox(boxComponent);
|
||||
|
||||
|
@ -392,12 +404,12 @@ public class MappingsReader_v1 extends MappingsReader {
|
|||
|
||||
if (node.has("geometry")) {
|
||||
if (node.get("geometry").isTextual()) {
|
||||
builder.geometry(new GeometryComponentBuilder()
|
||||
builder.geometry(new GeyserGeometryComponent.Builder()
|
||||
.identifier(node.get("geometry").asText())
|
||||
.build());
|
||||
} else {
|
||||
JsonNode geometry = node.get("geometry");
|
||||
GeometryComponentBuilder geometryBuilder = new GeometryComponentBuilder();
|
||||
GeometryComponent.Builder geometryBuilder = new GeyserGeometryComponent.Builder();
|
||||
if (geometry.has("identifier")) {
|
||||
geometryBuilder.identifier(geometry.get("identifier").asText());
|
||||
}
|
||||
|
@ -654,7 +666,7 @@ public class MappingsReader_v1 extends MappingsReader {
|
|||
ambientOcclusion = node.get("ambient_occlusion").asBoolean();
|
||||
}
|
||||
|
||||
return new MaterialInstanceBuilder()
|
||||
return new GeyserMaterialInstance.Builder()
|
||||
.texture(texture)
|
||||
.renderMethod(renderMethod)
|
||||
.faceDimming(faceDimming)
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
|
|||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v649.Bedrock_v649;
|
||||
import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
|
@ -118,6 +119,8 @@ public final class BlockRegistryPopulator {
|
|||
var blockMappers = ImmutableMap.<ObjectIntPair<String>, Remapper>builder()
|
||||
.put(ObjectIntPair.of("1_20_40", Bedrock_v622.CODEC.getProtocolVersion()), Conversion630_622::remapBlock)
|
||||
.put(ObjectIntPair.of("1_20_50", Bedrock_v630.CODEC.getProtocolVersion()), tag -> tag)
|
||||
// Only changes in 1.20.60 are hard_stained_glass (an EDU only block)
|
||||
.put(ObjectIntPair.of("1_20_60", Bedrock_v649.CODEC.getProtocolVersion()), tag -> tag)
|
||||
.build();
|
||||
|
||||
// We can keep this strong as nothing should be garbage collected
|
||||
|
@ -139,6 +142,7 @@ public final class BlockRegistryPopulator {
|
|||
builder.remove("version"); // Remove all nbt tags which are not needed for differentiating states
|
||||
builder.remove("name_hash"); // Quick workaround - was added in 1.19.20
|
||||
builder.remove("network_id"); // Added in 1.19.80 - ????
|
||||
builder.remove("block_id"); // Added in 1.20.60 //TODO verify this can be just removed
|
||||
//noinspection UnstableApiUsage
|
||||
builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
|
||||
vanillaBlockStates.set(i, builder.build());
|
||||
|
@ -154,6 +158,7 @@ public final class BlockRegistryPopulator {
|
|||
List<CustomBlockState> customExtBlockStates = new ArrayList<>();
|
||||
int[] remappedVanillaIds = new int[0];
|
||||
if (BlockRegistries.CUSTOM_BLOCKS.get().length != 0) {
|
||||
CustomBlockRegistryPopulator.BLOCK_ID.set(CustomBlockRegistryPopulator.START_OFFSET);
|
||||
for (CustomBlockData customBlock : BlockRegistries.CUSTOM_BLOCKS.get()) {
|
||||
customBlockProperties.add(CustomBlockRegistryPopulator.generateBlockPropertyData(customBlock, protocolVersion));
|
||||
CustomBlockRegistryPopulator.generateCustomBlockStates(customBlock, customBlockStates, customExtBlockStates);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -124,6 +124,7 @@ public class CreativeItemRegistryPopulator {
|
|||
builder.remove("name_hash");
|
||||
builder.remove("network_id");
|
||||
builder.remove("version");
|
||||
builder.remove("block_id");
|
||||
|
||||
blockDefinition = blockMappings.getDefinition(builder.build());
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -23,17 +23,19 @@ import org.geysermc.geyser.api.block.custom.property.CustomBlockProperty;
|
|||
import org.geysermc.geyser.api.block.custom.property.PropertyType;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomBlocksEvent;
|
||||
import org.geysermc.geyser.api.util.CreativeCategory;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockData;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockState;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents.CustomBlockComponentsBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockData.CustomBlockDataBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserGeometryComponent;
|
||||
import org.geysermc.geyser.level.block.GeyserMaterialInstance;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.mappings.MappingsConfigReader;
|
||||
import org.geysermc.geyser.registry.type.CustomSkull;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -41,6 +43,13 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
public class CustomBlockRegistryPopulator {
|
||||
|
||||
// Since 1.20.60, custom blocks need a block_id in their nbt tag
|
||||
public static AtomicInteger BLOCK_ID = new AtomicInteger();
|
||||
|
||||
// Custom block id's start at 10000, and count up
|
||||
public static final int START_OFFSET = 10000;
|
||||
|
||||
/**
|
||||
* The stage of population
|
||||
*/
|
||||
|
@ -89,7 +98,7 @@ public class CustomBlockRegistryPopulator {
|
|||
GeyserImpl.getInstance().getEventBus().fire(new GeyserDefineCustomBlocksEvent() {
|
||||
@Override
|
||||
public void register(@NonNull CustomBlockData customBlockData) {
|
||||
if (customBlockData.name().length() == 0) {
|
||||
if (customBlockData.name().isEmpty()) {
|
||||
throw new IllegalArgumentException("Custom block name must have at least 1 character.");
|
||||
}
|
||||
if (!CUSTOM_BLOCK_NAMES.add(customBlockData.name())) {
|
||||
|
@ -179,17 +188,17 @@ public class CustomBlockRegistryPopulator {
|
|||
});
|
||||
|
||||
BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.set(blockStateOverrides);
|
||||
if (blockStateOverrides.size() != 0) {
|
||||
if (!blockStateOverrides.isEmpty()) {
|
||||
GeyserImpl.getInstance().getLogger().info("Registered " + blockStateOverrides.size() + " custom block overrides.");
|
||||
}
|
||||
|
||||
BlockRegistries.CUSTOM_BLOCK_ITEM_OVERRIDES.set(CUSTOM_BLOCK_ITEM_OVERRIDES);
|
||||
if (CUSTOM_BLOCK_ITEM_OVERRIDES.size() != 0) {
|
||||
if (!CUSTOM_BLOCK_ITEM_OVERRIDES.isEmpty()) {
|
||||
GeyserImpl.getInstance().getLogger().info("Registered " + CUSTOM_BLOCK_ITEM_OVERRIDES.size() + " custom block item overrides.");
|
||||
}
|
||||
|
||||
BlockRegistries.EXTENDED_COLLISION_BOXES.set(extendedCollisionBoxes);
|
||||
if (extendedCollisionBoxes.size() != 0) {
|
||||
if (!extendedCollisionBoxes.isEmpty()) {
|
||||
GeyserImpl.getInstance().getLogger().info("Registered " + extendedCollisionBoxes.size() + " custom block extended collision boxes.");
|
||||
}
|
||||
}
|
||||
|
@ -199,7 +208,7 @@ public class CustomBlockRegistryPopulator {
|
|||
*/
|
||||
private static void populateNonVanilla() {
|
||||
BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.set(NON_VANILLA_BLOCK_STATE_OVERRIDES);
|
||||
if (NON_VANILLA_BLOCK_STATE_OVERRIDES.size() != 0) {
|
||||
if (!NON_VANILLA_BLOCK_STATE_OVERRIDES.isEmpty()) {
|
||||
GeyserImpl.getInstance().getLogger().info("Registered " + NON_VANILLA_BLOCK_STATE_OVERRIDES.size() + " non-vanilla block overrides.");
|
||||
}
|
||||
}
|
||||
|
@ -209,7 +218,7 @@ public class CustomBlockRegistryPopulator {
|
|||
*/
|
||||
private static void registration() {
|
||||
BlockRegistries.CUSTOM_BLOCKS.set(CUSTOM_BLOCKS.toArray(new CustomBlockData[0]));
|
||||
if (CUSTOM_BLOCKS.size() != 0) {
|
||||
if (!CUSTOM_BLOCKS.isEmpty()) {
|
||||
GeyserImpl.getInstance().getLogger().info("Registered " + CUSTOM_BLOCKS.size() + " custom blocks.");
|
||||
}
|
||||
}
|
||||
|
@ -279,7 +288,7 @@ public class CustomBlockRegistryPopulator {
|
|||
|
||||
CreativeCategory creativeCategory = customBlock.creativeCategory() != null ? customBlock.creativeCategory() : CreativeCategory.NONE;
|
||||
String creativeGroup = customBlock.creativeGroup() != null ? customBlock.creativeGroup() : "";
|
||||
NbtMap propertyTag = NbtMap.builder()
|
||||
NbtMapBuilder propertyTag = NbtMap.builder()
|
||||
.putCompound("components", CustomBlockRegistryPopulator.convertComponents(customBlock.components(), protocolVersion))
|
||||
// this is required or the client will crash
|
||||
// in the future, this can be used to replace items in the creative inventory
|
||||
|
@ -292,9 +301,14 @@ public class CustomBlockRegistryPopulator {
|
|||
// meaning of this version is unknown, but it's required for tags to work and should probably be checked periodically
|
||||
.putInt("molangVersion", 1)
|
||||
.putList("permutations", NbtType.COMPOUND, permutations)
|
||||
.putList("properties", NbtType.COMPOUND, properties)
|
||||
.build();
|
||||
return new BlockPropertyData(customBlock.identifier(), propertyTag);
|
||||
.putList("properties", NbtType.COMPOUND, properties);
|
||||
|
||||
if (GameProtocol.is1_20_60orHigher(protocolVersion)) {
|
||||
propertyTag.putCompound("vanilla_block_data", NbtMap.builder()
|
||||
.putInt("block_id", BLOCK_ID.getAndIncrement())
|
||||
.build());
|
||||
}
|
||||
return new BlockPropertyData(customBlock.identifier(), propertyTag.build());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -479,20 +493,20 @@ public class CustomBlockRegistryPopulator {
|
|||
}
|
||||
|
||||
private static CustomBlockData createExtendedCollisionBlock(BoxComponent boxComponent, int extendedCollisionBlock) {
|
||||
return new CustomBlockDataBuilder()
|
||||
return new GeyserCustomBlockData.Builder()
|
||||
.name("extended_collision_" + extendedCollisionBlock)
|
||||
.components(
|
||||
new CustomBlockComponentsBuilder()
|
||||
new GeyserCustomBlockComponents.Builder()
|
||||
.collisionBox(boxComponent)
|
||||
.selectionBox(BoxComponent.emptyBox())
|
||||
.materialInstance("*", new MaterialInstanceBuilder()
|
||||
.materialInstance("*", new GeyserMaterialInstance.Builder()
|
||||
.texture("glass")
|
||||
.renderMethod("alpha_test")
|
||||
.faceDimming(false)
|
||||
.ambientOcclusion(false)
|
||||
.build())
|
||||
.lightDampening(0)
|
||||
.geometry(new GeometryComponentBuilder()
|
||||
.geometry(new GeyserGeometryComponent.Builder()
|
||||
.identifier("geometry.invisible")
|
||||
.build())
|
||||
.build())
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.geysermc.geyser.item.GeyserCustomMappingData;
|
|||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.components.WearableSlot;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.registry.mappings.MappingsConfigReader;
|
||||
import org.geysermc.geyser.registry.type.GeyserMappingItem;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
|
@ -97,10 +98,10 @@ public class CustomItemRegistryPopulator {
|
|||
}
|
||||
}
|
||||
|
||||
public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, CustomItemData customItemData, int bedrockId) {
|
||||
public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, CustomItemData customItemData, int bedrockId, int protocolVersion) {
|
||||
ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true);
|
||||
|
||||
NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, mapping, customItemName, bedrockId);
|
||||
NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, mapping, customItemName, bedrockId, protocolVersion);
|
||||
ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build());
|
||||
|
||||
return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId);
|
||||
|
@ -124,7 +125,7 @@ public class CustomItemRegistryPopulator {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId) {
|
||||
public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) {
|
||||
String customIdentifier = customItemData.identifier();
|
||||
|
||||
Set<String> repairMaterials = customItemData.repairMaterials();
|
||||
|
@ -152,14 +153,14 @@ public class CustomItemRegistryPopulator {
|
|||
.build();
|
||||
|
||||
NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId,
|
||||
customItemData.creativeCategory(), customItemData.creativeGroup(), customItemData.isHat(), customItemData.displayHandheld());
|
||||
customItemData.creativeCategory(), customItemData.creativeGroup(), customItemData.isHat(), customItemData.displayHandheld(), protocolVersion);
|
||||
ComponentItemData componentItemData = new ComponentItemData(customIdentifier, builder.build());
|
||||
|
||||
return new NonVanillaItemRegistration(componentItemData, item, customItemMapping);
|
||||
}
|
||||
|
||||
private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, Item javaItem, GeyserMappingItem mapping,
|
||||
String customItemName, int customItemId) {
|
||||
String customItemName, int customItemId, int protocolVersion) {
|
||||
NbtMapBuilder builder = NbtMap.builder();
|
||||
builder.putString("name", customItemName)
|
||||
.putInt("id", customItemId);
|
||||
|
@ -167,7 +168,7 @@ public class CustomItemRegistryPopulator {
|
|||
NbtMapBuilder itemProperties = NbtMap.builder();
|
||||
NbtMapBuilder componentBuilder = NbtMap.builder();
|
||||
|
||||
setupBasicItemInfo(javaItem.maxDamage(), javaItem.maxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder);
|
||||
setupBasicItemInfo(javaItem.maxDamage(), javaItem.maxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder, protocolVersion);
|
||||
|
||||
boolean canDestroyInCreative = true;
|
||||
if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
|
||||
|
@ -176,7 +177,7 @@ public class CustomItemRegistryPopulator {
|
|||
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
|
||||
|
||||
if (mapping.getArmorType() != null) {
|
||||
computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), componentBuilder);
|
||||
computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), itemProperties, componentBuilder);
|
||||
}
|
||||
|
||||
if (mapping.getFirstBlockRuntimeId() != null) {
|
||||
|
@ -193,7 +194,7 @@ public class CustomItemRegistryPopulator {
|
|||
|
||||
switch (mapping.getBedrockIdentifier()) {
|
||||
case "minecraft:fire_charge", "minecraft:flint_and_steel" -> computeBlockItemProperties("minecraft:fire", componentBuilder);
|
||||
case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder);
|
||||
case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder, mapping.getBedrockIdentifier(), protocolVersion);
|
||||
case "minecraft:honey_bottle", "minecraft:milk_bucket", "minecraft:potion" -> computeConsumableProperties(itemProperties, componentBuilder, 2, true);
|
||||
case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" ->
|
||||
computeThrowableProperties(componentBuilder);
|
||||
|
@ -210,7 +211,7 @@ public class CustomItemRegistryPopulator {
|
|||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName,
|
||||
int customItemId, OptionalInt creativeCategory,
|
||||
String creativeGroup, boolean isHat, boolean displayHandheld) {
|
||||
String creativeGroup, boolean isHat, boolean displayHandheld, int protocolVersion) {
|
||||
NbtMapBuilder builder = NbtMap.builder();
|
||||
builder.putString("name", customItemName)
|
||||
.putInt("id", customItemId);
|
||||
|
@ -218,17 +219,17 @@ public class CustomItemRegistryPopulator {
|
|||
NbtMapBuilder itemProperties = NbtMap.builder();
|
||||
NbtMapBuilder componentBuilder = NbtMap.builder();
|
||||
|
||||
setupBasicItemInfo(customItemData.maxDamage(), customItemData.stackSize(), displayHandheld, customItemData, itemProperties, componentBuilder);
|
||||
setupBasicItemInfo(customItemData.maxDamage(), customItemData.stackSize(), displayHandheld, customItemData, itemProperties, componentBuilder, protocolVersion);
|
||||
|
||||
boolean canDestroyInCreative = true;
|
||||
if (customItemData.toolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
|
||||
canDestroyInCreative = computeToolProperties(customItemData.toolType(), itemProperties, componentBuilder);
|
||||
canDestroyInCreative = computeToolProperties(Objects.requireNonNull(customItemData.toolType()), itemProperties, componentBuilder);
|
||||
}
|
||||
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
|
||||
|
||||
String armorType = customItemData.armorType();
|
||||
if (armorType != null) {
|
||||
computeArmorProperties(armorType, customItemData.protectionValue(), componentBuilder);
|
||||
computeArmorProperties(armorType, customItemData.protectionValue(), itemProperties, componentBuilder);
|
||||
}
|
||||
|
||||
if (customItemData.isEdible()) {
|
||||
|
@ -236,7 +237,11 @@ public class CustomItemRegistryPopulator {
|
|||
}
|
||||
|
||||
if (customItemData.isChargeable()) {
|
||||
computeChargeableProperties(itemProperties, componentBuilder);
|
||||
String tooltype = customItemData.toolType();
|
||||
if (tooltype == null) {
|
||||
throw new IllegalArgumentException("tool type must be set if the custom item is chargeable!");
|
||||
}
|
||||
computeChargeableProperties(itemProperties, componentBuilder, "minecraft:" + tooltype, protocolVersion);
|
||||
}
|
||||
|
||||
computeRenderOffsets(isHat, customItemData, componentBuilder);
|
||||
|
@ -258,10 +263,21 @@ public class CustomItemRegistryPopulator {
|
|||
return builder;
|
||||
}
|
||||
|
||||
private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean displayHandheld, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
||||
itemProperties.putCompound("minecraft:icon", NbtMap.builder()
|
||||
.putString("texture", customItemData.icon())
|
||||
.build());
|
||||
private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean displayHandheld, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int protocolVersion) {
|
||||
NbtMap iconMap;
|
||||
if (GameProtocol.is1_20_60orHigher(protocolVersion)) {
|
||||
iconMap = NbtMap.builder()
|
||||
.putCompound("textures", NbtMap.builder()
|
||||
.putString("default", customItemData.icon())
|
||||
.build())
|
||||
.build();
|
||||
} else {
|
||||
iconMap = NbtMap.builder()
|
||||
.putString("texture", customItemData.icon())
|
||||
.build();
|
||||
}
|
||||
itemProperties.putCompound("minecraft:icon", iconMap);
|
||||
|
||||
componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", customItemData.displayName()).build());
|
||||
|
||||
// Add a Geyser tag to the item, allowing Molang queries
|
||||
|
@ -336,16 +352,19 @@ public class CustomItemRegistryPopulator {
|
|||
if (toolType.equals("sword")) {
|
||||
miningSpeed = 1.5f;
|
||||
canDestroyInCreative = false;
|
||||
componentBuilder.putCompound("minecraft:weapon", NbtMap.EMPTY);
|
||||
}
|
||||
|
||||
itemProperties.putBoolean("hand_equipped", true);
|
||||
itemProperties.putFloat("mining_speed", miningSpeed);
|
||||
|
||||
// This allows custom tools - shears, swords, shovels, axes etc to be enchanted or combined in the anvil
|
||||
itemProperties.putInt("enchantable_value", 1);
|
||||
itemProperties.putString("enchantable_slot", toolType);
|
||||
|
||||
return canDestroyInCreative;
|
||||
}
|
||||
|
||||
private static void computeArmorProperties(String armorType, int protectionValue, NbtMapBuilder componentBuilder) {
|
||||
private static void computeArmorProperties(String armorType, int protectionValue, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
||||
switch (armorType) {
|
||||
case "boots" -> {
|
||||
componentBuilder.putString("minecraft:render_offsets", "boots");
|
||||
|
@ -379,13 +398,73 @@ public class CustomItemRegistryPopulator {
|
|||
componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build());
|
||||
}
|
||||
|
||||
private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
||||
private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, String mapping, int protocolVersion) {
|
||||
// setting high use_duration prevents the consume animation from playing
|
||||
itemProperties.putInt("use_duration", Integer.MAX_VALUE);
|
||||
// display item as tool (mainly for crossbow and bow)
|
||||
itemProperties.putBoolean("hand_equipped", true);
|
||||
// ensure client moves at slow speed while charging (note: this was calculated by hand as the movement modifer value does not seem to scale linearly)
|
||||
componentBuilder.putCompound("minecraft:chargeable", NbtMap.builder().putFloat("movement_modifier", 0.35F).build());
|
||||
// Make bows, tridents, and crossbows enchantable
|
||||
itemProperties.putInt("enchantable_value", 1);
|
||||
|
||||
if (GameProtocol.is1_20_60orHigher(protocolVersion)) {
|
||||
componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder()
|
||||
.putFloat("use_duration", 100F)
|
||||
.putFloat("movement_modifier", 0.35F)
|
||||
.build());
|
||||
|
||||
switch (mapping) {
|
||||
case "minecraft:bow" -> {
|
||||
itemProperties.putString("enchantable_slot", "bow");
|
||||
itemProperties.putInt("frame_count", 3);
|
||||
|
||||
componentBuilder.putCompound("minecraft:shooter", NbtMap.builder()
|
||||
.putList("ammunition", NbtType.COMPOUND, List.of(
|
||||
NbtMap.builder()
|
||||
.putCompound("item", NbtMap.builder()
|
||||
.putString("name", "minecraft:arrow")
|
||||
.build())
|
||||
.putBoolean("use_offhand", true)
|
||||
.putBoolean("search_inventory", true)
|
||||
.build()
|
||||
))
|
||||
.putFloat("max_draw_duration", 0f)
|
||||
.putBoolean("charge_on_draw", true)
|
||||
.putBoolean("scale_power_by_draw_duration", true)
|
||||
.build());
|
||||
componentBuilder.putInt("minecraft:use_duration", 999);
|
||||
}
|
||||
case "minecraft:trident" -> {
|
||||
itemProperties.putString("enchantable_slot", "trident");
|
||||
componentBuilder.putInt("minecraft:use_duration", 999);
|
||||
}
|
||||
case "minecraft:crossbow" -> {
|
||||
itemProperties.putString("enchantable_slot", "crossbow");
|
||||
itemProperties.putInt("frame_count", 10);
|
||||
|
||||
componentBuilder.putCompound("minecraft:shooter", NbtMap.builder()
|
||||
.putList("ammunition", NbtType.COMPOUND, List.of(
|
||||
NbtMap.builder()
|
||||
.putCompound("item", NbtMap.builder()
|
||||
.putString("name", "minecraft:arrow")
|
||||
.build())
|
||||
.putBoolean("use_offhand", true)
|
||||
.putBoolean("search_inventory", true)
|
||||
.build()
|
||||
))
|
||||
.putFloat("max_draw_duration", 1f)
|
||||
.putBoolean("charge_on_draw", true)
|
||||
.putBoolean("scale_power_by_draw_duration", true)
|
||||
.build());
|
||||
componentBuilder.putInt("minecraft:use_duration", 999);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ensure client moves at slow speed while charging (note: this was calculated by hand as the movement modifer value does not seem to scale linearly)
|
||||
componentBuilder.putCompound("minecraft:chargeable", NbtMap.builder().putFloat("movement_modifier", 0.35F).build());
|
||||
|
||||
// keep item enchantable; also works on 1.20.50
|
||||
itemProperties.putString("enchantable_slot", mapping.replace("minecraft:", ""));
|
||||
}
|
||||
}
|
||||
|
||||
private static void computeConsumableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int useAnimation, boolean canAlwaysEat) {
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
|
|||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v649.Bedrock_v649;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
|
@ -59,6 +60,7 @@ import org.geysermc.geyser.inventory.item.StoredItemMappings;
|
|||
import org.geysermc.geyser.item.GeyserCustomMappingData;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.*;
|
||||
|
@ -89,6 +91,7 @@ public class ItemRegistryPopulator {
|
|||
List<PaletteVersion> paletteVersions = new ArrayList<>(3);
|
||||
paletteVersions.add(new PaletteVersion("1_20_40", Bedrock_v622.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion630_622::remapItem));
|
||||
paletteVersions.add(new PaletteVersion("1_20_50", Bedrock_v630.CODEC.getProtocolVersion()));
|
||||
paletteVersions.add(new PaletteVersion("1_20_60", Bedrock_v649.CODEC.getProtocolVersion(), Collections.emptyMap(), Conversion630_649::remapItem));
|
||||
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
|
||||
|
@ -272,7 +275,7 @@ public class ItemRegistryPopulator {
|
|||
|
||||
if (firstPass) {
|
||||
firstPass = false;
|
||||
if (states.size() == 0) {
|
||||
if (states.isEmpty()) {
|
||||
// No need to iterate and find all block states - this is the one, as there can't be any others
|
||||
bedrockBlock = bedrockBlockRuntimeId;
|
||||
break;
|
||||
|
@ -288,7 +291,7 @@ public class ItemRegistryPopulator {
|
|||
requiredBlockStatesBuilder.remove(nbtEntry.getKey());
|
||||
}
|
||||
}
|
||||
if (requiredBlockStatesBuilder.size() == 0) {
|
||||
if (requiredBlockStatesBuilder.isEmpty()) {
|
||||
// There are no required block states
|
||||
// E.G. there was only a direction property that is no longer in play
|
||||
// (States that are important include color for glass)
|
||||
|
@ -420,7 +423,7 @@ public class ItemRegistryPopulator {
|
|||
}
|
||||
|
||||
GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem(
|
||||
customItemName, javaItem, mappingItem, customItem, customProtocolId
|
||||
customItemName, javaItem, mappingItem, customItem, customProtocolId, palette.protocolVersion
|
||||
);
|
||||
// ComponentItemData - used to register some custom properties
|
||||
componentItemData.add(customMapping.componentItemData());
|
||||
|
@ -495,7 +498,7 @@ public class ItemRegistryPopulator {
|
|||
.count(1)
|
||||
.build());
|
||||
|
||||
registerFurnaceMinecart(nextFreeBedrockId++, componentItemData);
|
||||
registerFurnaceMinecart(nextFreeBedrockId++, componentItemData, palette.protocolVersion);
|
||||
|
||||
// Register any completely custom items given to us
|
||||
IntSet registeredJavaIds = new IntOpenHashSet(); // Used to check for duplicate item java ids
|
||||
|
@ -508,7 +511,7 @@ public class ItemRegistryPopulator {
|
|||
}
|
||||
|
||||
int customItemId = nextFreeBedrockId++;
|
||||
NonVanillaItemRegistration registration = CustomItemRegistryPopulator.registerCustomItem(customItem, customItemId);
|
||||
NonVanillaItemRegistration registration = CustomItemRegistryPopulator.registerCustomItem(customItem, customItemId, palette.protocolVersion);
|
||||
|
||||
componentItemData.add(registration.componentItemData());
|
||||
ItemMapping mapping = registration.mapping();
|
||||
|
@ -585,7 +588,7 @@ public class ItemRegistryPopulator {
|
|||
}
|
||||
}
|
||||
|
||||
private static void registerFurnaceMinecart(int nextFreeBedrockId, List<ComponentItemData> componentItemData) {
|
||||
private static void registerFurnaceMinecart(int nextFreeBedrockId, List<ComponentItemData> componentItemData, int protocolVersion) {
|
||||
NbtMapBuilder builder = NbtMap.builder();
|
||||
builder.putString("name", "geysermc:furnace_minecart")
|
||||
.putInt("id", nextFreeBedrockId);
|
||||
|
@ -594,11 +597,20 @@ public class ItemRegistryPopulator {
|
|||
|
||||
NbtMapBuilder componentBuilder = NbtMap.builder();
|
||||
// Conveniently, as of 1.16.200, the furnace minecart has a texture AND translation string already.
|
||||
itemProperties.putCompound("minecraft:icon", NbtMap.builder()
|
||||
.putString("texture", "minecart_furnace")
|
||||
.putString("frame", "0.000000")
|
||||
.putInt("frame_version", 1)
|
||||
.putString("legacy_id", "").build());
|
||||
// Not so conveniently, the way to set an icon changed in 1.20.60
|
||||
NbtMap iconMap;
|
||||
if (GameProtocol.is1_20_60orHigher(protocolVersion)) {
|
||||
iconMap = NbtMap.builder()
|
||||
.putCompound("textures", NbtMap.builder()
|
||||
.putString("default", "minecart_furnace")
|
||||
.build())
|
||||
.build();
|
||||
} else {
|
||||
iconMap = NbtMap.builder()
|
||||
.putString("texture", "minecart_furnace")
|
||||
.build();
|
||||
}
|
||||
itemProperties.putCompound("minecraft:icon", iconMap);
|
||||
componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", "item.minecartFurnace.name").build());
|
||||
|
||||
// Indicate that the arm animation should play on rails
|
||||
|
|
|
@ -34,8 +34,8 @@ import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
|||
import org.geysermc.geyser.api.block.custom.component.TransformationComponent;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockData;
|
||||
import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserGeometryComponent;
|
||||
import org.geysermc.geyser.level.block.GeyserMaterialInstance;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -65,9 +65,9 @@ public class CustomSkull {
|
|||
public CustomSkull(String skinHash) {
|
||||
this.skinHash = skinHash;
|
||||
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.Builder()
|
||||
.destructibleByMining(1.5f)
|
||||
.materialInstance("*", new MaterialInstanceBuilder()
|
||||
.materialInstance("*", new GeyserMaterialInstance.Builder()
|
||||
.texture("geyser." + skinHash + "_player_skin")
|
||||
.renderMethod("alpha_test")
|
||||
.faceDimming(true)
|
||||
|
@ -82,7 +82,7 @@ public class CustomSkull {
|
|||
addFloorPermutations(permutations);
|
||||
addWallPermutations(permutations);
|
||||
|
||||
customBlockData = new GeyserCustomBlockData.CustomBlockDataBuilder()
|
||||
customBlockData = new GeyserCustomBlockData.Builder()
|
||||
.name("player_skull_" + skinHash)
|
||||
.components(components)
|
||||
.intProperty(BITS_A_PROPERTY, IntStream.rangeClosed(0, 6).boxed().toList()) // This gives us exactly 21 block states
|
||||
|
@ -114,8 +114,8 @@ public class CustomSkull {
|
|||
}
|
||||
|
||||
private void addDefaultPermutation(List<CustomBlockPermutation> permutations) {
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||
.geometry(new GeometryComponentBuilder()
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.Builder()
|
||||
.geometry(new GeyserGeometryComponent.Builder()
|
||||
.identifier("geometry.geyser.player_skull_hand")
|
||||
.build())
|
||||
.transformation(new TransformationComponent(0, 180, 0))
|
||||
|
@ -131,10 +131,10 @@ public class CustomSkull {
|
|||
for (int quadrant = 0; quadrant < 4; quadrant++) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int floorRotation = 4 * quadrant + i;
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.Builder()
|
||||
.selectionBox(FLOOR_BOX)
|
||||
.collisionBox(FLOOR_BOX)
|
||||
.geometry(new GeometryComponentBuilder()
|
||||
.geometry(new GeyserGeometryComponent.Builder()
|
||||
.identifier("geometry.geyser.player_skull_floor_" + quadrantNames[i])
|
||||
.build())
|
||||
.transformation(new TransformationComponent(0, ROTATIONS[quadrant], 0))
|
||||
|
@ -150,10 +150,10 @@ public class CustomSkull {
|
|||
|
||||
private void addWallPermutations(List<CustomBlockPermutation> permutations) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.Builder()
|
||||
.selectionBox(WALL_BOX)
|
||||
.collisionBox(WALL_BOX)
|
||||
.geometry(new GeometryComponentBuilder()
|
||||
.geometry(new GeyserGeometryComponent.Builder()
|
||||
.identifier("geometry.geyser.player_skull_wall")
|
||||
.build())
|
||||
.transformation(new TransformationComponent(0, ROTATIONS[i], 0))
|
||||
|
|
|
@ -62,7 +62,7 @@ public final class ScoreboardUpdater extends Thread {
|
|||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!geyser.isShuttingDown()) {
|
||||
while (!geyser.isShuttingDown() && !geyser.isReloading()) {
|
||||
try {
|
||||
long timeTillAction = getTimeTillNextAction();
|
||||
if (timeTillAction > 0) {
|
||||
|
|
|
@ -101,8 +101,10 @@ import org.geysermc.floodgate.crypto.FloodgateCipher;
|
|||
import org.geysermc.floodgate.util.BedrockData;
|
||||
import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.bedrock.camera.CameraData;
|
||||
import org.geysermc.geyser.api.bedrock.camera.CameraShake;
|
||||
import org.geysermc.geyser.api.connection.GeyserConnection;
|
||||
import org.geysermc.geyser.api.entity.EntityData;
|
||||
import org.geysermc.geyser.api.entity.type.GeyserEntity;
|
||||
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
|
||||
import org.geysermc.geyser.api.event.bedrock.SessionDisconnectEvent;
|
||||
|
@ -110,10 +112,13 @@ import org.geysermc.geyser.api.event.bedrock.SessionLoginEvent;
|
|||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.network.RemoteServer;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.impl.camera.CameraDefinitions;
|
||||
import org.geysermc.geyser.impl.camera.GeyserCameraData;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.GeyserEntityData;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||
|
@ -151,7 +156,16 @@ import java.net.ConnectException;
|
|||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
@ -550,11 +564,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
@Setter
|
||||
private boolean waitingForStatistics = false;
|
||||
|
||||
/**
|
||||
* All fog effects that are currently applied to the client.
|
||||
*/
|
||||
private final Set<String> appliedFog = new HashSet<>();
|
||||
|
||||
private final Set<UUID> emotes;
|
||||
|
||||
/**
|
||||
|
@ -586,6 +595,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
*/
|
||||
private final Queue<Long> keepAliveCache = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final GeyserCameraData cameraData;
|
||||
|
||||
private final GeyserEntityData entityData;
|
||||
|
||||
private MinecraftProtocol protocol;
|
||||
|
||||
public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop) {
|
||||
|
@ -607,6 +620,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
this.skullCache = new SkullCache(this);
|
||||
this.tagCache = new TagCache();
|
||||
this.worldCache = new WorldCache(this);
|
||||
this.cameraData = new GeyserCameraData(this);
|
||||
this.entityData = new GeyserEntityData(this);
|
||||
|
||||
this.worldBorder = new WorldBorder(this);
|
||||
|
||||
|
@ -667,6 +682,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
entityPacket.setIdentifiers(Registries.BEDROCK_ENTITY_IDENTIFIERS.get());
|
||||
upstream.sendPacket(entityPacket);
|
||||
|
||||
CameraPresetsPacket cameraPresetsPacket = new CameraPresetsPacket();
|
||||
cameraPresetsPacket.getPresets().addAll(CameraDefinitions.CAMERA_PRESETS);
|
||||
upstream.sendPacket(cameraPresetsPacket);
|
||||
|
||||
CreativeContentPacket creativePacket = new CreativeContentPacket();
|
||||
creativePacket.setContents(this.itemMappings.getCreativeItems());
|
||||
upstream.sendPacket(creativePacket);
|
||||
|
@ -935,7 +954,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
).toString());
|
||||
} catch (Exception e) {
|
||||
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
|
||||
disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode()));
|
||||
disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.floodgate.encrypt_fail", getClientData().getLanguageCode()));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1182,12 +1201,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
// Set the mood
|
||||
if (shouldShowFog && !isInWorldBorderWarningArea) {
|
||||
isInWorldBorderWarningArea = true;
|
||||
sendFog("minecraft:fog_crimson_forest");
|
||||
camera().sendFog("minecraft:fog_crimson_forest");
|
||||
}
|
||||
}
|
||||
if (!shouldShowFog && isInWorldBorderWarningArea) {
|
||||
// Clear fog as we are outside the world border now
|
||||
removeFog("minecraft:fog_crimson_forest");
|
||||
camera().removeFog("minecraft:fog_crimson_forest");
|
||||
isInWorldBorderWarningArea = false;
|
||||
}
|
||||
|
||||
|
@ -1480,6 +1499,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
private void startGame() {
|
||||
this.upstream.getCodecHelper().setItemDefinitions(this.itemMappings);
|
||||
this.upstream.getCodecHelper().setBlockDefinitions((DefinitionRegistry) this.blockMappings); //FIXME
|
||||
this.upstream.getCodecHelper().setCameraPresetDefinitions(CameraDefinitions.CAMERA_DEFINITIONS);
|
||||
|
||||
StartGamePacket startGamePacket = new StartGamePacket();
|
||||
startGamePacket.setUniqueEntityId(playerEntity.getGeyserId());
|
||||
|
@ -1980,72 +2000,66 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
|
||||
@Override
|
||||
public @NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId) {
|
||||
CompletableFuture<GeyserEntity> future = new CompletableFuture<>();
|
||||
ensureInEventLoop(() -> future.complete(this.entityCache.getEntityByJavaId(javaId)));
|
||||
return future;
|
||||
return entities().entityByJavaId(javaId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId) {
|
||||
Entity entity = (Entity) emoter;
|
||||
if (entity.getSession() != this) {
|
||||
throw new IllegalStateException("Given entity must be from this session!");
|
||||
entities().showEmote(emoter, emoteId);
|
||||
}
|
||||
|
||||
public void lockInputs(boolean camera, boolean movement) {
|
||||
UpdateClientInputLocksPacket packet = new UpdateClientInputLocksPacket();
|
||||
final int cameraOffset = 1 << 1;
|
||||
final int movementOffset = 1 << 2;
|
||||
|
||||
int result = 0;
|
||||
if (camera) {
|
||||
result |= cameraOffset;
|
||||
}
|
||||
if (movement) {
|
||||
result |= movementOffset;
|
||||
}
|
||||
|
||||
EmotePacket packet = new EmotePacket();
|
||||
packet.setRuntimeEntityId(entity.getGeyserId());
|
||||
packet.setXuid("");
|
||||
packet.setPlatformId(""); // BDS sends empty
|
||||
packet.setEmoteId(emoteId);
|
||||
packet.setLockComponentData(result);
|
||||
packet.setServerPosition(this.playerEntity.getPosition());
|
||||
|
||||
sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull CameraData camera() {
|
||||
return this.cameraData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull EntityData entities() {
|
||||
return this.entityData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shakeCamera(float intensity, float duration, @NonNull CameraShake type) {
|
||||
CameraShakePacket packet = new CameraShakePacket();
|
||||
packet.setIntensity(intensity);
|
||||
packet.setDuration(duration);
|
||||
packet.setShakeType(type == CameraShake.POSITIONAL ? CameraShakeType.POSITIONAL : CameraShakeType.ROTATIONAL);
|
||||
packet.setShakeAction(CameraShakeAction.ADD);
|
||||
sendUpstreamPacket(packet);
|
||||
this.cameraData.shakeCamera(intensity, duration, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopCameraShake() {
|
||||
CameraShakePacket packet = new CameraShakePacket();
|
||||
// CameraShakeAction.STOP removes all types regardless of the given type, but regardless it can't be null
|
||||
packet.setShakeType(CameraShakeType.POSITIONAL);
|
||||
packet.setShakeAction(CameraShakeAction.STOP);
|
||||
sendUpstreamPacket(packet);
|
||||
this.cameraData.stopCameraShake();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendFog(String... fogNameSpaces) {
|
||||
Collections.addAll(this.appliedFog, fogNameSpaces);
|
||||
|
||||
PlayerFogPacket packet = new PlayerFogPacket();
|
||||
packet.getFogStack().addAll(this.appliedFog);
|
||||
sendUpstreamPacket(packet);
|
||||
this.cameraData.sendFog(fogNameSpaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFog(String... fogNameSpaces) {
|
||||
if (fogNameSpaces.length == 0) {
|
||||
this.appliedFog.clear();
|
||||
} else {
|
||||
for (String id : fogNameSpaces) {
|
||||
this.appliedFog.remove(id);
|
||||
}
|
||||
}
|
||||
PlayerFogPacket packet = new PlayerFogPacket();
|
||||
packet.getFogStack().addAll(this.appliedFog);
|
||||
sendUpstreamPacket(packet);
|
||||
this.cameraData.removeFog(fogNameSpaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Set<String> fogEffects() {
|
||||
// Use a copy so that sendFog/removeFog can be called while iterating the returned set (avoid CME)
|
||||
return Set.copyOf(this.appliedFog);
|
||||
return this.cameraData.fogEffects();
|
||||
}
|
||||
|
||||
public void addCommandEnum(String name, String enums) {
|
||||
|
|
|
@ -139,7 +139,7 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
|||
DimensionUtils.switchDimension(session, newDimension);
|
||||
} else if (DimensionUtils.isCustomBedrockNetherId() && newDimension.equalsIgnoreCase(DimensionUtils.NETHER)) {
|
||||
// If the player is spawning into the "fake" nether, send them some fog
|
||||
session.sendFog(DimensionUtils.BEDROCK_FOG_HELL);
|
||||
session.camera().sendFog(DimensionUtils.BEDROCK_FOG_HELL);
|
||||
}
|
||||
|
||||
ChunkUtils.loadDimension(session);
|
||||
|
|
|
@ -72,6 +72,7 @@ import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
|||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.util.BlockEntityUtils;
|
||||
import org.geysermc.geyser.util.ChunkUtils;
|
||||
import org.geysermc.geyser.util.DimensionUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.BitSet;
|
||||
|
@ -522,6 +523,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||
levelChunkPacket.setChunkX(packet.getX());
|
||||
levelChunkPacket.setChunkZ(packet.getZ());
|
||||
levelChunkPacket.setData(Unpooled.wrappedBuffer(payload));
|
||||
levelChunkPacket.setDimension(DimensionUtils.javaToBedrock(session.getChunkCache().getBedrockDimension()));
|
||||
session.sendUpstreamPacket(levelChunkPacket);
|
||||
|
||||
if (!lecterns.isEmpty()) {
|
||||
|
|
|
@ -244,6 +244,7 @@ public class ChunkUtils {
|
|||
byteBuf.readBytes(payload);
|
||||
|
||||
LevelChunkPacket data = new LevelChunkPacket();
|
||||
data.setDimension(DimensionUtils.javaToBedrock(session.getChunkCache().getBedrockDimension()));
|
||||
data.setChunkX(chunkX);
|
||||
data.setChunkZ(chunkZ);
|
||||
data.setSubChunksLength(0);
|
||||
|
|
|
@ -143,9 +143,9 @@ public class DimensionUtils {
|
|||
// thinks they are in the end dimension.
|
||||
if (isCustomBedrockNetherId()) {
|
||||
if (NETHER.equals(javaDimension)) {
|
||||
session.sendFog(BEDROCK_FOG_HELL);
|
||||
session.camera().sendFog(BEDROCK_FOG_HELL);
|
||||
} else if (NETHER.equals(previousDimension)) {
|
||||
session.removeFog(BEDROCK_FOG_HELL);
|
||||
session.camera().removeFog(BEDROCK_FOG_HELL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,6 +266,7 @@ public final class EntityUtils {
|
|||
* Convert Java GameMode to Bedrock GameType
|
||||
* Needed to account for ordinal differences (spectator is 3 in Java, 6 in Bedrock)
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // Must use survival_viewer due to limitations on Bedrock's spectator gamemode
|
||||
public static GameType toBedrockGamemode(GameMode gamemode) {
|
||||
return switch (gamemode) {
|
||||
case CREATIVE -> GameType.CREATIVE;
|
||||
|
|
BIN
core/src/main/resources/bedrock/block_palette.1_20_60.nbt
Normal file
BIN
core/src/main/resources/bedrock/block_palette.1_20_60.nbt
Normal file
Binary file not shown.
5787
core/src/main/resources/bedrock/creative_items.1_20_60.json
Normal file
5787
core/src/main/resources/bedrock/creative_items.1_20_60.json
Normal file
File diff suppressed because it is too large
Load diff
5998
core/src/main/resources/bedrock/runtime_item_states.1_20_60.json
Normal file
5998
core/src/main/resources/bedrock/runtime_item_states.1_20_60.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -7,5 +7,5 @@ org.gradle.vfs.watch=false
|
|||
|
||||
group=org.geysermc
|
||||
id=geyser
|
||||
version=2.2.1-SNAPSHOT
|
||||
version=2.2.2-SNAPSHOT
|
||||
description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers.
|
|
@ -9,10 +9,10 @@ netty = "4.1.103.Final"
|
|||
guava = "29.0-jre"
|
||||
gson = "2.3.1" # Provided by Spigot 1.8.8
|
||||
websocket = "1.5.1"
|
||||
protocol = "3.0.0.Beta1-20231206.150507-114"
|
||||
protocol-connection = "3.0.0.Beta1-20231206.150507-113"
|
||||
protocol = "3.0.0.Beta1-20240204.134050-120"
|
||||
protocol-connection = "3.0.0.Beta1-20240204.134050-119"
|
||||
raknet = "1.0.0.CR1-20231206.145325-12"
|
||||
blockstateupdater="1.20.50-20231106.161340-1"
|
||||
blockstateupdater="1.20.60-20240129.140535-1"
|
||||
mcauthlib = "d9d773e"
|
||||
mcprotocollib = "1.20.4-2-20240116.220521-7"
|
||||
adventure = "4.14.0"
|
||||
|
@ -98,6 +98,8 @@ protocol-common = { group = "org.cloudburstmc.protocol", name = "common", versio
|
|||
protocol-codec = { group = "org.cloudburstmc.protocol", name = "bedrock-codec", version.ref = "protocol" }
|
||||
protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-connection", version.ref = "protocol-connection" }
|
||||
|
||||
math = { group = "org.cloudburstmc.math", name = "immutable", version = "2.0" }
|
||||
|
||||
blockstateupdater = { group = "org.cloudburstmc", name = "block-state-updater", version.ref = "blockstateupdater"}
|
||||
|
||||
[bundles]
|
||||
|
|
Loading…
Reference in a new issue