mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-19 14:59:27 +00:00
Start working on mannequins
This commit is contained in:
@@ -148,6 +148,7 @@ import org.geysermc.geyser.entity.type.living.monster.raid.RaidParticipantEntity
|
||||
import org.geysermc.geyser.entity.type.living.monster.raid.RavagerEntity;
|
||||
import org.geysermc.geyser.entity.type.living.monster.raid.SpellcasterIllagerEntity;
|
||||
import org.geysermc.geyser.entity.type.living.monster.raid.VindicatorEntity;
|
||||
import org.geysermc.geyser.entity.type.player.AvatarEntity;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
@@ -653,12 +654,18 @@ public final class EntityDefinitions {
|
||||
.addTranslator(MetadataTypes.ROTATIONS, ArmorStandEntity::setLeftLegRotation)
|
||||
.addTranslator(MetadataTypes.ROTATIONS, ArmorStandEntity::setRightLegRotation)
|
||||
.build();
|
||||
PLAYER = EntityDefinition.<PlayerEntity>inherited(null, livingEntityBase)
|
||||
|
||||
EntityDefinition<AvatarEntity> avatarEntityBase = EntityDefinition.inherited(AvatarEntity::new, livingEntityBase)
|
||||
.height(1.8f).width(0.6f)
|
||||
.offset(1.62f)
|
||||
.addTranslator(null) // Player main hand
|
||||
.addTranslator(MetadataTypes.BYTE, AvatarEntity::setSkinVisibility)
|
||||
.build();
|
||||
|
||||
PLAYER = EntityDefinition.<PlayerEntity>inherited(null, avatarEntityBase)
|
||||
.type(EntityType.PLAYER)
|
||||
.height(1.8f).width(0.6f)
|
||||
.offset(1.62f)
|
||||
.addTranslator(null) // Player main hand
|
||||
.addTranslator(MetadataTypes.BYTE, PlayerEntity::setSkinVisibility)
|
||||
.addTranslator(MetadataTypes.FLOAT, PlayerEntity::setAbsorptionHearts)
|
||||
.addTranslator(null) // Player score
|
||||
.addTranslator(MetadataTypes.OPTIONAL_UNSIGNED_INT, PlayerEntity::setLeftParrot)
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.type.player;
|
||||
|
||||
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.Ability;
|
||||
import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
|
||||
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.packet.AddPlayerPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.ItemTranslator;
|
||||
import org.geysermc.geyser.util.ChunkUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AvatarEntity extends LivingEntity {
|
||||
public static final float SNEAKING_POSE_HEIGHT = 1.5f;
|
||||
protected static final List<AbilityLayer> BASE_ABILITY_LAYER;
|
||||
|
||||
protected String username;
|
||||
|
||||
/**
|
||||
* The textures property from the GameProfile.
|
||||
*/
|
||||
@Nullable
|
||||
protected String texturesProperty;
|
||||
|
||||
private String cachedScore = "";
|
||||
private boolean scoreVisible = true;
|
||||
|
||||
@Nullable
|
||||
private Vector3i bedPosition;
|
||||
|
||||
static {
|
||||
AbilityLayer abilityLayer = new AbilityLayer();
|
||||
abilityLayer.setLayerType(AbilityLayer.Type.BASE);
|
||||
Ability[] abilities = Ability.values();
|
||||
Collections.addAll(abilityLayer.getAbilitiesSet(), abilities); // Apparently all the abilities you're working with
|
||||
Collections.addAll(abilityLayer.getAbilityValues(), abilities); // Apparently all the abilities the player can work with
|
||||
BASE_ABILITY_LAYER = Collections.singletonList(abilityLayer);
|
||||
}
|
||||
|
||||
public AvatarEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<? extends AvatarEntity> definition,
|
||||
Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw, String username) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
this.username = username;
|
||||
this.nametag = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeMetadata() {
|
||||
super.initializeMetadata();
|
||||
// For the OptionalPack, set all bits as invisible by default as this matches Java Edition behavior
|
||||
dirtyMetadata.put(EntityDataTypes.MARK_VARIANT, 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnEntity() {
|
||||
AddPlayerPacket addPlayerPacket = new AddPlayerPacket();
|
||||
addPlayerPacket.setUuid(uuid);
|
||||
addPlayerPacket.setUsername(username);
|
||||
addPlayerPacket.setRuntimeEntityId(geyserId);
|
||||
addPlayerPacket.setUniqueEntityId(geyserId);
|
||||
addPlayerPacket.setPosition(position.sub(0, definition.offset(), 0));
|
||||
addPlayerPacket.setRotation(getBedrockRotation());
|
||||
addPlayerPacket.setMotion(motion);
|
||||
addPlayerPacket.setHand(ItemTranslator.translateToBedrock(session, getMainHandItem()));
|
||||
addPlayerPacket.getAdventureSettings().setCommandPermission(CommandPermission.ANY);
|
||||
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
|
||||
addPlayerPacket.setDeviceId("");
|
||||
addPlayerPacket.setPlatformChatId("");
|
||||
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);
|
||||
dirtyMetadata.apply(addPlayerPacket.getMetadata());
|
||||
|
||||
setFlagsDirty(false);
|
||||
|
||||
valid = true;
|
||||
session.sendUpstreamPacket(addPlayerPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
|
||||
setPosition(position);
|
||||
setYaw(yaw);
|
||||
setPitch(pitch);
|
||||
setHeadYaw(headYaw);
|
||||
|
||||
setOnGround(isOnGround);
|
||||
|
||||
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
|
||||
movePlayerPacket.setRuntimeEntityId(geyserId);
|
||||
movePlayerPacket.setPosition(this.position);
|
||||
movePlayerPacket.setRotation(getBedrockRotation());
|
||||
movePlayerPacket.setOnGround(isOnGround);
|
||||
movePlayerPacket.setMode(this instanceof SessionPlayerEntity || teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL);
|
||||
if (movePlayerPacket.getMode() == MovePlayerPacket.Mode.TELEPORT) {
|
||||
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(movePlayerPacket);
|
||||
|
||||
if (teleported && !(this instanceof SessionPlayerEntity)) {
|
||||
// As of 1.19.0, head yaw seems to be ignored during teleports, also don't do this for session player.
|
||||
updateHeadLookRotation(headYaw);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
|
||||
setYaw(yaw);
|
||||
setPitch(pitch);
|
||||
setHeadYaw(headYaw);
|
||||
this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ);
|
||||
|
||||
setOnGround(isOnGround);
|
||||
|
||||
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
|
||||
movePlayerPacket.setRuntimeEntityId(geyserId);
|
||||
movePlayerPacket.setPosition(position);
|
||||
movePlayerPacket.setRotation(getBedrockRotation());
|
||||
movePlayerPacket.setOnGround(isOnGround);
|
||||
movePlayerPacket.setMode(this instanceof SessionPlayerEntity ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL);
|
||||
// If the player is moved while sleeping, we have to adjust their y, so it appears
|
||||
// correctly on Bedrock. This fixes GSit's lay.
|
||||
if (getFlag(EntityFlag.SLEEPING)) {
|
||||
if (bedPosition != null && (bedPosition.getY() == 0 || bedPosition.distanceSquared(position.toInt()) > 4)) {
|
||||
// Force the player movement by using a teleport
|
||||
movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - definition.offset() + 0.2f, position.getZ()));
|
||||
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
|
||||
}
|
||||
}
|
||||
|
||||
if (movePlayerPacket.getMode() == MovePlayerPacket.Mode.TELEPORT) {
|
||||
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(movePlayerPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPosition(Vector3f position) {
|
||||
if (this.bedPosition != null) {
|
||||
// As of Bedrock 1.21.22 and Fabric 1.21.1
|
||||
// Messes with Bedrock if we send this to the client itself, though.
|
||||
super.setPosition(position.up(0.2f));
|
||||
} else {
|
||||
super.setPosition(position.add(0, definition.offset(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Vector3i setBedPosition(EntityMetadata<Optional<Vector3i>, ?> entityMetadata) {
|
||||
bedPosition = super.setBedPosition(entityMetadata);
|
||||
if (bedPosition != null) {
|
||||
// Required to sync position of entity to bed
|
||||
// Fixes https://github.com/GeyserMC/Geyser/issues/3595 on vanilla 1.19.3 servers - did not happen on Paper
|
||||
this.setPosition(bedPosition.toFloat());
|
||||
|
||||
// TODO evaluate if needed
|
||||
int bed = session.getGeyser().getWorldManager().getBlockAt(session, bedPosition);
|
||||
// Bed has to be updated, or else player is floating in the air
|
||||
ChunkUtils.updateBlock(session, bed, bedPosition);
|
||||
|
||||
// Indicate that the player should enter the sleep cycle
|
||||
// Has to be a byte or it does not work
|
||||
// (Bed position is what actually triggers sleep - "pose" is only optional)
|
||||
dirtyMetadata.put(EntityDataTypes.PLAYER_FLAGS, (byte) 2);
|
||||
} else {
|
||||
// Player is no longer sleeping
|
||||
dirtyMetadata.put(EntityDataTypes.PLAYER_FLAGS, (byte) 0);
|
||||
return null;
|
||||
}
|
||||
return bedPosition;
|
||||
}
|
||||
|
||||
public void setSkinVisibility(ByteEntityMetadata entityMetadata) {
|
||||
// OptionalPack usage for toggling skin bits
|
||||
// In Java Edition, a bit being set means that part should be enabled
|
||||
// However, to ensure that the pack still works on other servers, we invert the bit so all values by default
|
||||
// are true (0).
|
||||
dirtyMetadata.put(EntityDataTypes.MARK_VARIANT, ~entityMetadata.getPrimitiveValue() & 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayName(EntityMetadata<Optional<Component>, ?> entityMetadata) {
|
||||
// Doesn't do anything for players
|
||||
// TODO test mannequins
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayNameVisible(BooleanEntityMetadata entityMetadata) {
|
||||
// Doesn't do anything for players
|
||||
// TODO test mannequins
|
||||
}
|
||||
|
||||
public void setBelowNameText(String text) {
|
||||
if (text == null) {
|
||||
text = "";
|
||||
}
|
||||
|
||||
boolean changed = !Objects.equals(cachedScore, text);
|
||||
cachedScore = text;
|
||||
if (scoreVisible && changed) {
|
||||
dirtyMetadata.put(EntityDataTypes.SCORE, text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scoreVisibility(boolean show) {
|
||||
boolean visibilityChanged = scoreVisible != show;
|
||||
scoreVisible = show;
|
||||
if (!visibilityChanged) {
|
||||
return;
|
||||
}
|
||||
// if the player has no cachedScore, we never have to change the score.
|
||||
// hide = set to "" (does nothing), show = change from "" (does nothing)
|
||||
if (cachedScore.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
dirtyMetadata.put(EntityDataTypes.SCORE, show ? cachedScore : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPose(Pose pose) {
|
||||
super.setPose(pose);
|
||||
setFlag(EntityFlag.SWIMMING, false);
|
||||
setFlag(EntityFlag.CRAWLING, false);
|
||||
|
||||
if (pose == Pose.SWIMMING) {
|
||||
// This is just for, so we know if player is swimming or crawling.
|
||||
// TODO test, changed from position (field) to position() (method), which adds offset
|
||||
if (session.getGeyser().getWorldManager().blockAt(session, position.toInt()).is(Blocks.WATER)) {
|
||||
setFlag(EntityFlag.SWIMMING, true);
|
||||
} else {
|
||||
setFlag(EntityFlag.CRAWLING, true);
|
||||
// Look at https://github.com/GeyserMC/Geyser/issues/5316, we're fixing this by spoofing player pitch to 0.
|
||||
updateRotation(this.yaw, 0, this.onGround);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPitch(float pitch) {
|
||||
super.setPitch(getFlag(EntityFlag.CRAWLING) ? 0 : pitch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDimensionsFromPose(Pose pose) {
|
||||
float height;
|
||||
float width;
|
||||
switch (pose) {
|
||||
case SNEAKING -> {
|
||||
height = SNEAKING_POSE_HEIGHT;
|
||||
width = definition.width();
|
||||
}
|
||||
case FALL_FLYING, SPIN_ATTACK, SWIMMING -> {
|
||||
height = 0.6f;
|
||||
width = definition.width();
|
||||
}
|
||||
case DYING -> {
|
||||
height = 0.2f;
|
||||
width = 0.2f;
|
||||
}
|
||||
default -> {
|
||||
super.setDimensionsFromPose(pose);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setBoundingBoxWidth(width);
|
||||
setBoundingBoxHeight(height);
|
||||
}
|
||||
}
|
||||
@@ -27,73 +27,28 @@ package org.geysermc.geyser.entity.type.player;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
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.Ability;
|
||||
import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
|
||||
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.AddPlayerPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
|
||||
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.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.ItemTranslator;
|
||||
import org.geysermc.geyser.util.ChunkUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Getter @Setter
|
||||
public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
public static final float SNEAKING_POSE_HEIGHT = 1.5f;
|
||||
protected static final List<AbilityLayer> BASE_ABILITY_LAYER;
|
||||
|
||||
static {
|
||||
AbilityLayer abilityLayer = new AbilityLayer();
|
||||
abilityLayer.setLayerType(AbilityLayer.Type.BASE);
|
||||
Ability[] abilities = Ability.values();
|
||||
Collections.addAll(abilityLayer.getAbilitiesSet(), abilities); // Apparently all the abilities you're working with
|
||||
Collections.addAll(abilityLayer.getAbilityValues(), abilities); // Apparently all the abilities the player can work with
|
||||
BASE_ABILITY_LAYER = Collections.singletonList(abilityLayer);
|
||||
}
|
||||
|
||||
private String username;
|
||||
|
||||
private String cachedScore = "";
|
||||
private boolean scoreVisible = true;
|
||||
|
||||
/**
|
||||
* The textures property from the GameProfile.
|
||||
*/
|
||||
@Nullable
|
||||
private String texturesProperty;
|
||||
|
||||
@Nullable
|
||||
private Vector3i bedPosition;
|
||||
public class PlayerEntity extends AvatarEntity implements GeyserPlayerEntity {
|
||||
|
||||
/**
|
||||
* Saves the parrot currently on the player's left shoulder; otherwise null
|
||||
@@ -111,48 +66,17 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
|
||||
public PlayerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, Vector3f position,
|
||||
Vector3f motion, float yaw, float pitch, float headYaw, String username, @Nullable String texturesProperty) {
|
||||
super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw);
|
||||
|
||||
this.username = username;
|
||||
this.nametag = username;
|
||||
super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw, username);
|
||||
this.texturesProperty = texturesProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeMetadata() {
|
||||
super.initializeMetadata();
|
||||
// For the OptionalPack, set all bits as invisible by default as this matches Java Edition behavior
|
||||
dirtyMetadata.put(EntityDataTypes.MARK_VARIANT, 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnEntity() {
|
||||
AddPlayerPacket addPlayerPacket = new AddPlayerPacket();
|
||||
addPlayerPacket.setUuid(uuid);
|
||||
addPlayerPacket.setUsername(username);
|
||||
addPlayerPacket.setRuntimeEntityId(geyserId);
|
||||
addPlayerPacket.setUniqueEntityId(geyserId);
|
||||
addPlayerPacket.setPosition(position.sub(0, definition.offset(), 0));
|
||||
addPlayerPacket.setRotation(getBedrockRotation());
|
||||
addPlayerPacket.setMotion(motion);
|
||||
addPlayerPacket.setHand(ItemTranslator.translateToBedrock(session, getMainHandItem()));
|
||||
addPlayerPacket.getAdventureSettings().setCommandPermission(CommandPermission.ANY);
|
||||
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
|
||||
addPlayerPacket.setDeviceId("");
|
||||
addPlayerPacket.setPlatformChatId("");
|
||||
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);
|
||||
|
||||
valid = true;
|
||||
session.sendUpstreamPacket(addPlayerPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -186,30 +110,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
|
||||
@Override
|
||||
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
|
||||
setPosition(position);
|
||||
setYaw(yaw);
|
||||
setPitch(pitch);
|
||||
setHeadYaw(headYaw);
|
||||
|
||||
setOnGround(isOnGround);
|
||||
|
||||
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
|
||||
movePlayerPacket.setRuntimeEntityId(geyserId);
|
||||
movePlayerPacket.setPosition(this.position);
|
||||
movePlayerPacket.setRotation(getBedrockRotation());
|
||||
movePlayerPacket.setOnGround(isOnGround);
|
||||
movePlayerPacket.setMode(this instanceof SessionPlayerEntity || teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL);
|
||||
if (movePlayerPacket.getMode() == MovePlayerPacket.Mode.TELEPORT) {
|
||||
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(movePlayerPacket);
|
||||
|
||||
if (teleported && !(this instanceof SessionPlayerEntity)) {
|
||||
// As of 1.19.0, head yaw seems to be ignored during teleports, also don't do this for session player.
|
||||
updateHeadLookRotation(headYaw);
|
||||
}
|
||||
|
||||
super.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported);
|
||||
if (leftParrot != null) {
|
||||
leftParrot.moveAbsolute(position, yaw, pitch, headYaw, true, teleported);
|
||||
}
|
||||
@@ -220,34 +121,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
|
||||
@Override
|
||||
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
|
||||
setYaw(yaw);
|
||||
setPitch(pitch);
|
||||
setHeadYaw(headYaw);
|
||||
this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ);
|
||||
|
||||
setOnGround(isOnGround);
|
||||
|
||||
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
|
||||
movePlayerPacket.setRuntimeEntityId(geyserId);
|
||||
movePlayerPacket.setPosition(position);
|
||||
movePlayerPacket.setRotation(getBedrockRotation());
|
||||
movePlayerPacket.setOnGround(isOnGround);
|
||||
movePlayerPacket.setMode(this instanceof SessionPlayerEntity ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL);
|
||||
// If the player is moved while sleeping, we have to adjust their y, so it appears
|
||||
// correctly on Bedrock. This fixes GSit's lay.
|
||||
if (getFlag(EntityFlag.SLEEPING)) {
|
||||
if (bedPosition != null && (bedPosition.getY() == 0 || bedPosition.distanceSquared(position.toInt()) > 4)) {
|
||||
// Force the player movement by using a teleport
|
||||
movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - definition.offset() + 0.2f, position.getZ()));
|
||||
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
|
||||
}
|
||||
}
|
||||
|
||||
if (movePlayerPacket.getMode() == MovePlayerPacket.Mode.TELEPORT) {
|
||||
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(movePlayerPacket);
|
||||
super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround);
|
||||
if (leftParrot != null) {
|
||||
leftParrot.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, true);
|
||||
}
|
||||
@@ -256,42 +130,6 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPosition(Vector3f position) {
|
||||
if (this.bedPosition != null) {
|
||||
// As of Bedrock 1.21.22 and Fabric 1.21.1
|
||||
// Messes with Bedrock if we send this to the client itself, though.
|
||||
super.setPosition(position.up(0.2f));
|
||||
} else {
|
||||
super.setPosition(position.add(0, definition.offset(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Vector3i setBedPosition(EntityMetadata<Optional<Vector3i>, ?> entityMetadata) {
|
||||
bedPosition = super.setBedPosition(entityMetadata);
|
||||
if (bedPosition != null) {
|
||||
// Required to sync position of entity to bed
|
||||
// Fixes https://github.com/GeyserMC/Geyser/issues/3595 on vanilla 1.19.3 servers - did not happen on Paper
|
||||
this.setPosition(bedPosition.toFloat());
|
||||
|
||||
// TODO evaluate if needed
|
||||
int bed = session.getGeyser().getWorldManager().getBlockAt(session, bedPosition);
|
||||
// Bed has to be updated, or else player is floating in the air
|
||||
ChunkUtils.updateBlock(session, bed, bedPosition);
|
||||
|
||||
// Indicate that the player should enter the sleep cycle
|
||||
// Has to be a byte or it does not work
|
||||
// (Bed position is what actually triggers sleep - "pose" is only optional)
|
||||
dirtyMetadata.put(EntityDataTypes.PLAYER_FLAGS, (byte) 2);
|
||||
} else {
|
||||
// Player is no longer sleeping
|
||||
dirtyMetadata.put(EntityDataTypes.PLAYER_FLAGS, (byte) 0);
|
||||
return null;
|
||||
}
|
||||
return bedPosition;
|
||||
}
|
||||
|
||||
public void setAbsorptionHearts(FloatEntityMetadata entityMetadata) {
|
||||
// Extra hearts - is not metadata but an attribute on Bedrock
|
||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||
@@ -302,14 +140,6 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
session.sendUpstreamPacket(attributesPacket);
|
||||
}
|
||||
|
||||
public void setSkinVisibility(ByteEntityMetadata entityMetadata) {
|
||||
// OptionalPack usage for toggling skin bits
|
||||
// In Java Edition, a bit being set means that part should be enabled
|
||||
// However, to ensure that the pack still works on other servers, we invert the bit so all values by default
|
||||
// are true (0).
|
||||
dirtyMetadata.put(EntityDataTypes.MARK_VARIANT, ~entityMetadata.getPrimitiveValue() & 0xff);
|
||||
}
|
||||
|
||||
public void setLeftParrot(EntityMetadata<OptionalInt, ?> entityMetadata) {
|
||||
setParrot(entityMetadata.getValue(), true);
|
||||
}
|
||||
@@ -322,7 +152,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
* Sets the parrot occupying the shoulder. Bedrock Edition requires a full entity whereas Java Edition just
|
||||
* spawns it from the NBT data provided
|
||||
*/
|
||||
protected void setParrot(OptionalInt variant, boolean isLeft) { // TODO test this as of 1.21.9
|
||||
protected void setParrot(OptionalInt variant, boolean isLeft) {
|
||||
if (variant.isPresent()) {
|
||||
if ((isLeft && leftParrot != null) || (!isLeft && rightParrot != null)) {
|
||||
// No need to update a parrot's data when it already exists
|
||||
@@ -362,21 +192,12 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayName(EntityMetadata<Optional<Component>, ?> entityMetadata) {
|
||||
// Doesn't do anything for players
|
||||
}
|
||||
|
||||
@Override
|
||||
public String teamIdentifier() {
|
||||
return username;
|
||||
}
|
||||
|
||||
// TODO test mannequins
|
||||
@Override
|
||||
protected void setNametag(@Nullable String nametag, boolean fromDisplayName) {
|
||||
// when fromDisplayName, LivingEntity will call scoreboard code. After that
|
||||
@@ -388,87 +209,6 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
super.setNametag(nametag, fromDisplayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayNameVisible(BooleanEntityMetadata entityMetadata) {
|
||||
// Doesn't do anything for players
|
||||
}
|
||||
|
||||
public void setBelowNameText(String text) {
|
||||
if (text == null) {
|
||||
text = "";
|
||||
}
|
||||
|
||||
boolean changed = !Objects.equals(cachedScore, text);
|
||||
cachedScore = text;
|
||||
if (isScoreVisible() && changed) {
|
||||
dirtyMetadata.put(EntityDataTypes.SCORE, text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scoreVisibility(boolean show) {
|
||||
boolean visibilityChanged = scoreVisible != show;
|
||||
scoreVisible = show;
|
||||
if (!visibilityChanged) {
|
||||
return;
|
||||
}
|
||||
// if the player has no cachedScore, we never have to change the score.
|
||||
// hide = set to "" (does nothing), show = change from "" (does nothing)
|
||||
if (cachedScore.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
dirtyMetadata.put(EntityDataTypes.SCORE, show ? cachedScore : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPose(Pose pose) {
|
||||
super.setPose(pose);
|
||||
setFlag(EntityFlag.SWIMMING, false);
|
||||
setFlag(EntityFlag.CRAWLING, false);
|
||||
|
||||
if (pose == Pose.SWIMMING) {
|
||||
// This is just for, so we know if player is swimming or crawling.
|
||||
if (session.getGeyser().getWorldManager().blockAt(session, this.position().toInt()).is(Blocks.WATER)) {
|
||||
setFlag(EntityFlag.SWIMMING, true);
|
||||
} else {
|
||||
setFlag(EntityFlag.CRAWLING, true);
|
||||
// Look at https://github.com/GeyserMC/Geyser/issues/5316, we're fixing this by spoofing player pitch to 0.
|
||||
updateRotation(this.yaw, 0, this.onGround);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPitch(float pitch) {
|
||||
super.setPitch(getFlag(EntityFlag.CRAWLING) ? 0 : pitch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDimensionsFromPose(Pose pose) {
|
||||
float height;
|
||||
float width;
|
||||
switch (pose) {
|
||||
case SNEAKING -> {
|
||||
height = SNEAKING_POSE_HEIGHT;
|
||||
width = definition.width();
|
||||
}
|
||||
case FALL_FLYING, SPIN_ATTACK, SWIMMING -> {
|
||||
height = 0.6f;
|
||||
width = definition.width();
|
||||
}
|
||||
case DYING -> {
|
||||
height = 0.2f;
|
||||
width = 0.2f;
|
||||
}
|
||||
default -> {
|
||||
super.setDimensionsFromPose(pose);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setBoundingBoxWidth(width);
|
||||
setBoundingBoxHeight(height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the UUID that should be used when dealing with Bedrock's tab list.
|
||||
*/
|
||||
|
||||
@@ -34,6 +34,7 @@ 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.packet.AddPlayerPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.level.block.property.Properties;
|
||||
import org.geysermc.geyser.level.block.type.BlockState;
|
||||
import org.geysermc.geyser.level.block.type.WallSkullBlock;
|
||||
@@ -51,7 +52,7 @@ import java.util.concurrent.TimeUnit;
|
||||
* A wrapper to handle skulls more effectively - skulls have to be treated as entities since there are no
|
||||
* custom player skulls in Bedrock.
|
||||
*/
|
||||
public class SkullPlayerEntity extends PlayerEntity {
|
||||
public class SkullPlayerEntity extends AvatarEntity {
|
||||
|
||||
@Getter
|
||||
private UUID skullUUID;
|
||||
@@ -60,7 +61,7 @@ public class SkullPlayerEntity extends PlayerEntity {
|
||||
private Vector3i skullPosition;
|
||||
|
||||
public SkullPlayerEntity(GeyserSession session, long geyserId) {
|
||||
super(session, 0, geyserId, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "", null);
|
||||
super(session, 0, geyserId, UUID.randomUUID(), EntityDefinitions.PLAYER, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,45 +75,16 @@ public class SkullPlayerEntity extends PlayerEntity {
|
||||
setFlag(EntityFlag.INVISIBLE, true); // Until the skin is loaded
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwritten so each entity doesn't check for a linked entity
|
||||
*/
|
||||
@Override
|
||||
public void spawnEntity() {
|
||||
AddPlayerPacket addPlayerPacket = new AddPlayerPacket();
|
||||
addPlayerPacket.setUuid(getUuid());
|
||||
addPlayerPacket.setUsername(getUsername());
|
||||
addPlayerPacket.setRuntimeEntityId(geyserId);
|
||||
addPlayerPacket.setUniqueEntityId(geyserId);
|
||||
addPlayerPacket.setPosition(position.sub(0, definition.offset(), 0));
|
||||
addPlayerPacket.setRotation(getBedrockRotation());
|
||||
addPlayerPacket.setMotion(motion);
|
||||
addPlayerPacket.setHand(ItemTranslator.translateToBedrock(session, getMainHandItem()));
|
||||
addPlayerPacket.getAdventureSettings().setCommandPermission(CommandPermission.ANY);
|
||||
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
|
||||
addPlayerPacket.setDeviceId("");
|
||||
addPlayerPacket.setPlatformChatId("");
|
||||
addPlayerPacket.setGameType(GameType.SURVIVAL);
|
||||
addPlayerPacket.setAbilityLayers(BASE_ABILITY_LAYER);
|
||||
addPlayerPacket.getMetadata().putFlags(flags);
|
||||
dirtyMetadata.apply(addPlayerPacket.getMetadata());
|
||||
|
||||
setFlagsDirty(false);
|
||||
|
||||
valid = true;
|
||||
session.sendUpstreamPacket(addPlayerPacket);
|
||||
}
|
||||
|
||||
public void updateSkull(SkullCache.Skull skull) {
|
||||
skullPosition = skull.getPosition();
|
||||
|
||||
if (!Objects.equals(skull.getTexturesProperty(), getTexturesProperty()) || !Objects.equals(skullUUID, skull.getUuid())) {
|
||||
if (!Objects.equals(skull.getTexturesProperty(), texturesProperty) || !Objects.equals(skullUUID, skull.getUuid())) {
|
||||
// Make skull invisible as we change skins
|
||||
setFlag(EntityFlag.INVISIBLE, true);
|
||||
updateBedrockMetadata();
|
||||
|
||||
skullUUID = skull.getUuid();
|
||||
setTexturesProperty(skull.getTexturesProperty());
|
||||
texturesProperty = skull.getTexturesProperty();
|
||||
|
||||
SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> {
|
||||
// Delay to minimize split-second "player" pop-in
|
||||
|
||||
Reference in New Issue
Block a user