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:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user