1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-19 14:59:27 +00:00

Basic mannequin implementation

This commit is contained in:
Eclipse
2025-09-29 19:33:07 +00:00
parent 5e69361d69
commit 870b199f35
7 changed files with 106 additions and 16 deletions

View File

@@ -149,6 +149,7 @@ 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.SpellcasterIllagerEntity;
import org.geysermc.geyser.entity.type.living.monster.raid.VindicatorEntity; 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.AvatarEntity;
import org.geysermc.geyser.entity.type.player.MannequinEntity;
import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.translator.text.MessageTranslator;
@@ -239,6 +240,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<MagmaCubeEntity> MAGMA_CUBE; public static final EntityDefinition<MagmaCubeEntity> MAGMA_CUBE;
public static final EntityDefinition<BoatEntity> MANGROVE_BOAT; public static final EntityDefinition<BoatEntity> MANGROVE_BOAT;
public static final EntityDefinition<ChestBoatEntity> MANGROVE_CHEST_BOAT; public static final EntityDefinition<ChestBoatEntity> MANGROVE_CHEST_BOAT;
public static final EntityDefinition<MannequinEntity> MANNEQUIN;
public static final EntityDefinition<MinecartEntity> MINECART; public static final EntityDefinition<MinecartEntity> MINECART;
public static final EntityDefinition<MooshroomEntity> MOOSHROOM; public static final EntityDefinition<MooshroomEntity> MOOSHROOM;
public static final EntityDefinition<ChestedHorseEntity> MULE; public static final EntityDefinition<ChestedHorseEntity> MULE;
@@ -662,10 +664,15 @@ public final class EntityDefinitions {
.addTranslator(MetadataTypes.BYTE, AvatarEntity::setSkinVisibility) .addTranslator(MetadataTypes.BYTE, AvatarEntity::setSkinVisibility)
.build(); .build();
MANNEQUIN = EntityDefinition.inherited(MannequinEntity::new, avatarEntityBase)
.type(EntityType.MANNEQUIN)
.addTranslator(MetadataTypes.RESOLVABLE_PROFILE, MannequinEntity::setProfile)
.addTranslator(null) // Immovable
.addTranslator(MetadataTypes.OPTIONAL_COMPONENT, MannequinEntity::setDescription)
.build();
PLAYER = EntityDefinition.<PlayerEntity>inherited(null, avatarEntityBase) PLAYER = EntityDefinition.<PlayerEntity>inherited(null, avatarEntityBase)
.type(EntityType.PLAYER) .type(EntityType.PLAYER)
.height(1.8f).width(0.6f)
.offset(1.62f)
.addTranslator(MetadataTypes.FLOAT, PlayerEntity::setAbsorptionHearts) .addTranslator(MetadataTypes.FLOAT, PlayerEntity::setAbsorptionHearts)
.addTranslator(null) // Player score .addTranslator(null) // Player score
.addTranslator(MetadataTypes.OPTIONAL_UNSIGNED_INT, PlayerEntity::setLeftParrot) .addTranslator(MetadataTypes.OPTIONAL_UNSIGNED_INT, PlayerEntity::setLeftParrot)

View File

@@ -44,12 +44,16 @@ import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.SkinManager;
import org.geysermc.geyser.skin.SkullSkinManager;
import org.geysermc.geyser.translator.item.ItemTranslator; import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.geyser.util.ChunkUtils; import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.mcprotocollib.auth.GameProfile;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata; 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.Pose;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; 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.ByteEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.ResolvableProfile;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -88,7 +92,7 @@ public class AvatarEntity extends LivingEntity {
BASE_ABILITY_LAYER = Collections.singletonList(abilityLayer); BASE_ABILITY_LAYER = Collections.singletonList(abilityLayer);
} }
public AvatarEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<? extends AvatarEntity> definition, public AvatarEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition,
Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw, String username) { Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw, String username) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
this.username = username; this.username = username;
@@ -223,6 +227,32 @@ public class AvatarEntity extends LivingEntity {
return bedPosition; return bedPosition;
} }
public void setSkin(ResolvableProfile profile, boolean cape, Runnable after) {
SkinManager.resolveProfile(profile).thenAccept(resolved -> setSkin(resolved, cape, after));
}
public void setSkin(GameProfile profile, boolean cape, Runnable after) {
GameProfile.Property textures = profile.getProperty("textures");
if (textures != null) {
setSkin(textures.getValue(), cape, after);
} else {
setSkin((String) null, cape, after);
}
}
public void setSkin(String texturesProperty, boolean cape, Runnable after) {
if (Objects.equals(texturesProperty, this.texturesProperty)) {
return;
}
this.texturesProperty = texturesProperty;
if (cape) {
SkinManager.requestAndHandleSkinAndCape(this, session, skin -> after.run());
} else {
SkullSkinManager.requestAndHandleSkin(this, session, skin -> after.run());
}
}
public void setSkinVisibility(ByteEntityMetadata entityMetadata) { public void setSkinVisibility(ByteEntityMetadata entityMetadata) {
// OptionalPack usage for toggling skin bits // OptionalPack usage for toggling skin bits
// In Java Edition, a bit being set means that part should be enabled // In Java Edition, a bit being set means that part should be enabled

View File

@@ -0,0 +1,51 @@
/*
* 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.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.ResolvableProfile;
import java.util.Optional;
import java.util.UUID;
public class MannequinEntity extends AvatarEntity {
public MannequinEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw, "Mannequin"); // TODO from translation
}
public void setProfile(EntityMetadata<ResolvableProfile, ?> entityMetadata) {
setSkin(entityMetadata.getValue(), true, () -> {});
}
public void setDescription(EntityMetadata<Optional<Component>, ?> entityMetadata) {
}
}

View File

@@ -84,13 +84,11 @@ public class SkullPlayerEntity extends AvatarEntity {
updateBedrockMetadata(); updateBedrockMetadata();
skullUUID = skull.getUuid(); skullUUID = skull.getUuid();
texturesProperty = skull.getTexturesProperty(); setSkin(skull.getTexturesProperty(), false, () -> session.scheduleInEventLoop(() -> {
SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> {
// Delay to minimize split-second "player" pop-in // Delay to minimize split-second "player" pop-in
setFlag(EntityFlag.INVISIBLE, false); setFlag(EntityFlag.INVISIBLE, false);
updateBedrockMetadata(); updateBedrockMetadata();
}, 250, TimeUnit.MILLISECONDS))); }, 250, TimeUnit.MILLISECONDS));
} else { } else {
// Just a rotation/position change // Just a rotation/position change
setFlag(EntityFlag.INVISIBLE, false); setFlag(EntityFlag.INVISIBLE, false);

View File

@@ -67,6 +67,8 @@ public class SkinManager {
.expireAfterAccess(1, TimeUnit.HOURS) .expireAfterAccess(1, TimeUnit.HOURS)
.build(); .build();
private static final UUID EMPTY_UUID = new UUID(0L, 0L); private static final UUID EMPTY_UUID = new UUID(0L, 0L);
public static final GameProfile EMPTY_PROFILE = new GameProfile((UUID) null, null);
public static final ResolvableProfile EMPTY_RESOLVABLE_PROFILE = new ResolvableProfile(EMPTY_PROFILE, null, null, null, null, false);
static final String GEOMETRY = new String(FileUtils.readAllBytes("bedrock/geometries/geo.json"), StandardCharsets.UTF_8); static final String GEOMETRY = new String(FileUtils.readAllBytes("bedrock/geometries/geo.json"), StandardCharsets.UTF_8);

View File

@@ -31,6 +31,7 @@ import org.cloudburstmc.protocol.bedrock.packet.PlayerSkinPacket;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.skin.Skin; import org.geysermc.geyser.api.skin.Skin;
import org.geysermc.geyser.api.skin.SkinData; import org.geysermc.geyser.api.skin.SkinData;
import org.geysermc.geyser.entity.type.player.AvatarEntity;
import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
@@ -55,7 +56,7 @@ public class SkullSkinManager extends SkinManager {
.build(); .build();
} }
public static void requestAndHandleSkin(SkullPlayerEntity entity, GeyserSession session, public static void requestAndHandleSkin(AvatarEntity entity, GeyserSession session,
Consumer<Skin> skinConsumer) { Consumer<Skin> skinConsumer) {
BiConsumer<Skin, Throwable> applySkin = (skin, throwable) -> { BiConsumer<Skin, Throwable> applySkin = (skin, throwable) -> {
try { try {
@@ -77,11 +78,13 @@ public class SkullSkinManager extends SkinManager {
GameProfileData data = GameProfileData.from(entity); GameProfileData data = GameProfileData.from(entity);
if (data == null) { if (data == null) {
GeyserImpl.getInstance().getLogger().debug("Using fallback skin for skull at " + entity.getSkullPosition() + if (entity instanceof SkullPlayerEntity skullEntity) {
" with texture value: " + entity.getTexturesProperty() + " and UUID: " + entity.getSkullUUID()); GeyserImpl.getInstance().getLogger().debug("Using fallback skin for skull at " + skullEntity.getSkullPosition() +
" with texture value: " + entity.getTexturesProperty() + " and UUID: " + skullEntity.getSkullUUID());
// No texture available, fallback using the UUID // No texture available, fallback using the UUID
SkinData fallback = SkinProvider.determineFallbackSkinData(entity.getSkullUUID()); SkinData fallback = SkinProvider.determineFallbackSkinData(skullEntity.getSkullUUID());
applySkin.accept(fallback.skin(), null); applySkin.accept(fallback.skin(), null);
}
} else { } else {
SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true) SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true)
.whenCompleteAsync(applySkin); .whenCompleteAsync(applySkin);

View File

@@ -91,11 +91,10 @@ public class JavaPlayerInfoUpdateTranslator extends PacketTranslator<Clientbound
session.getEntityCache().addPlayerEntity(playerEntity); session.getEntityCache().addPlayerEntity(playerEntity);
} }
playerEntity.setUsername(name); playerEntity.setUsername(name);
playerEntity.setTexturesProperty(texturesProperty);
if (self) { if (self) {
SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> playerEntity.setSkin(profile, true,
GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername())); () -> GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername()));
} }
} }
} }