Feature: Camera/Input locking API (#4332)

Adds API methods to control player cameras - including fancy transitions,  color fades, or simple input locks.
This commit is contained in:
chris 2024-01-31 11:21:06 +01:00 committed by GitHub
parent 07150db592
commit f555dc0a92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 1523 additions and 149 deletions

View file

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

View file

@ -39,12 +39,20 @@ import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.*;
import org.cloudburstmc.protocol.bedrock.data.Ability;
import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.data.GameType;
import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
import org.cloudburstmc.protocol.bedrock.packet.*;
import org.cloudburstmc.protocol.bedrock.packet.AddPlayerPacket;
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.Entity;
@ -433,4 +441,9 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
public UUID getTabListUuid() {
return getUuid();
}
@Override
public Vector3f position() {
return this.position.clone();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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;
@ -38,6 +40,8 @@ import org.geysermc.geyser.api.item.custom.CustomItemData;
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.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;
@ -65,20 +69,24 @@ 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]));
// items
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder());
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder());
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder());
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.Builder());
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.Builder());
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.Builder());
// cameras
providers.put(CameraFade.Builder.class, args -> new GeyserCameraFade.Builder());
providers.put(CameraPosition.Builder.class, args -> new GeyserCameraPosition.Builder());
return providers;
}

View file

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

View file

@ -23,11 +23,11 @@ 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.registry.BlockRegistries;
import org.geysermc.geyser.registry.mappings.MappingsConfigReader;
import org.geysermc.geyser.registry.type.CustomSkull;
@ -479,20 +479,20 @@ public class CustomBlockRegistryPopulator {
}
private static CustomBlockData createExtendedCollisionBlock(BoxComponent boxComponent, int extendedCollisionBlock) {
return new CustomBlockDataBuilder()
return new GeyserCustomBlockData.Builder()
.name("extended_collision_" + extendedCollisionBlock)
.components(
new CustomBlockComponentsBuilder()
new GeyserCustomBlockComponents.Builder()
.collisionBox(boxComponent)
.selectionBox(BoxComponent.emptyBox())
.materialInstance("*", new MaterialInstanceBuilder()
.materialInstance("*", new GeyserMaterialInstance.Builder()
.texture("glass")
.renderMethod("alpha_test")
.faceDimming(false)
.ambientOcclusion(false)
.build())
.lightDampening(0)
.geometry(new GeometryComponentBuilder()
.geometry(new GeyserGeometryComponent.Builder()
.identifier("geometry.invisible")
.build())
.build())

View file

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

View file

@ -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);
@ -1182,12 +1201,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
// Set the mood
if (shouldShowFog && !isInWorldBorderWarningArea) {
isInWorldBorderWarningArea = true;
sendFog("minecraft:fog_crimson_forest");
camera().sendFog("minecraft:fog_crimson_forest");
}
}
if (!shouldShowFog && isInWorldBorderWarningArea) {
// Clear fog as we are outside the world border now
removeFog("minecraft:fog_crimson_forest");
camera().removeFog("minecraft:fog_crimson_forest");
isInWorldBorderWarningArea = false;
}
@ -1480,6 +1499,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private void startGame() {
this.upstream.getCodecHelper().setItemDefinitions(this.itemMappings);
this.upstream.getCodecHelper().setBlockDefinitions((DefinitionRegistry) this.blockMappings); //FIXME
this.upstream.getCodecHelper().setCameraPresetDefinitions(CameraDefinitions.CAMERA_DEFINITIONS);
StartGamePacket startGamePacket = new StartGamePacket();
startGamePacket.setUniqueEntityId(playerEntity.getGeyserId());
@ -1980,72 +2000,66 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Override
public @NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId) {
CompletableFuture<GeyserEntity> future = new CompletableFuture<>();
ensureInEventLoop(() -> future.complete(this.entityCache.getEntityByJavaId(javaId)));
return future;
return entities().entityByJavaId(javaId);
}
@Override
public void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId) {
Entity entity = (Entity) emoter;
if (entity.getSession() != this) {
throw new IllegalStateException("Given entity must be from this session!");
entities().showEmote(emoter, emoteId);
}
public void lockInputs(boolean camera, boolean movement) {
UpdateClientInputLocksPacket packet = new UpdateClientInputLocksPacket();
final int cameraOffset = 1 << 1;
final int movementOffset = 1 << 2;
int result = 0;
if (camera) {
result |= cameraOffset;
}
if (movement) {
result |= movementOffset;
}
EmotePacket packet = new EmotePacket();
packet.setRuntimeEntityId(entity.getGeyserId());
packet.setXuid("");
packet.setPlatformId(""); // BDS sends empty
packet.setEmoteId(emoteId);
packet.setLockComponentData(result);
packet.setServerPosition(this.playerEntity.getPosition());
sendUpstreamPacket(packet);
}
@Override
public @NonNull CameraData camera() {
return this.cameraData;
}
@Override
public @NonNull EntityData entities() {
return this.entityData;
}
@Override
public void shakeCamera(float intensity, float duration, @NonNull CameraShake type) {
CameraShakePacket packet = new CameraShakePacket();
packet.setIntensity(intensity);
packet.setDuration(duration);
packet.setShakeType(type == CameraShake.POSITIONAL ? CameraShakeType.POSITIONAL : CameraShakeType.ROTATIONAL);
packet.setShakeAction(CameraShakeAction.ADD);
sendUpstreamPacket(packet);
this.cameraData.shakeCamera(intensity, duration, type);
}
@Override
public void stopCameraShake() {
CameraShakePacket packet = new CameraShakePacket();
// CameraShakeAction.STOP removes all types regardless of the given type, but regardless it can't be null
packet.setShakeType(CameraShakeType.POSITIONAL);
packet.setShakeAction(CameraShakeAction.STOP);
sendUpstreamPacket(packet);
this.cameraData.stopCameraShake();
}
@Override
public void sendFog(String... fogNameSpaces) {
Collections.addAll(this.appliedFog, fogNameSpaces);
PlayerFogPacket packet = new PlayerFogPacket();
packet.getFogStack().addAll(this.appliedFog);
sendUpstreamPacket(packet);
this.cameraData.sendFog(fogNameSpaces);
}
@Override
public void removeFog(String... fogNameSpaces) {
if (fogNameSpaces.length == 0) {
this.appliedFog.clear();
} else {
for (String id : fogNameSpaces) {
this.appliedFog.remove(id);
}
}
PlayerFogPacket packet = new PlayerFogPacket();
packet.getFogStack().addAll(this.appliedFog);
sendUpstreamPacket(packet);
this.cameraData.removeFog(fogNameSpaces);
}
@Override
public @NonNull Set<String> fogEffects() {
// Use a copy so that sendFog/removeFog can be called while iterating the returned set (avoid CME)
return Set.copyOf(this.appliedFog);
return this.cameraData.fogEffects();
}
public void addCommandEnum(String name, String enums) {

View file

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

View file

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