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.VindicatorEntity;
|
||||
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.registry.Registries;
|
||||
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<BoatEntity> MANGROVE_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<MooshroomEntity> MOOSHROOM;
|
||||
public static final EntityDefinition<ChestedHorseEntity> MULE;
|
||||
@@ -662,10 +664,15 @@ public final class EntityDefinitions {
|
||||
.addTranslator(MetadataTypes.BYTE, AvatarEntity::setSkinVisibility)
|
||||
.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)
|
||||
.type(EntityType.PLAYER)
|
||||
.height(1.8f).width(0.6f)
|
||||
.offset(1.62f)
|
||||
.addTranslator(MetadataTypes.FLOAT, PlayerEntity::setAbsorptionHearts)
|
||||
.addTranslator(null) // Player score
|
||||
.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.level.block.Blocks;
|
||||
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.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.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.player.ResolvableProfile;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -88,7 +92,7 @@ public class AvatarEntity extends LivingEntity {
|
||||
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) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
this.username = username;
|
||||
@@ -223,6 +227,32 @@ public class AvatarEntity extends LivingEntity {
|
||||
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) {
|
||||
// OptionalPack usage for toggling skin bits
|
||||
// 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();
|
||||
|
||||
skullUUID = skull.getUuid();
|
||||
texturesProperty = skull.getTexturesProperty();
|
||||
|
||||
SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> {
|
||||
setSkin(skull.getTexturesProperty(), false, () -> session.scheduleInEventLoop(() -> {
|
||||
// Delay to minimize split-second "player" pop-in
|
||||
setFlag(EntityFlag.INVISIBLE, false);
|
||||
updateBedrockMetadata();
|
||||
}, 250, TimeUnit.MILLISECONDS)));
|
||||
}, 250, TimeUnit.MILLISECONDS));
|
||||
} else {
|
||||
// Just a rotation/position change
|
||||
setFlag(EntityFlag.INVISIBLE, false);
|
||||
|
||||
@@ -67,6 +67,8 @@ public class SkinManager {
|
||||
.expireAfterAccess(1, TimeUnit.HOURS)
|
||||
.build();
|
||||
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);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.cloudburstmc.protocol.bedrock.packet.PlayerSkinPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.skin.Skin;
|
||||
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.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
@@ -55,7 +56,7 @@ public class SkullSkinManager extends SkinManager {
|
||||
.build();
|
||||
}
|
||||
|
||||
public static void requestAndHandleSkin(SkullPlayerEntity entity, GeyserSession session,
|
||||
public static void requestAndHandleSkin(AvatarEntity entity, GeyserSession session,
|
||||
Consumer<Skin> skinConsumer) {
|
||||
BiConsumer<Skin, Throwable> applySkin = (skin, throwable) -> {
|
||||
try {
|
||||
@@ -77,11 +78,13 @@ public class SkullSkinManager extends SkinManager {
|
||||
|
||||
GameProfileData data = GameProfileData.from(entity);
|
||||
if (data == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Using fallback skin for skull at " + entity.getSkullPosition() +
|
||||
" with texture value: " + entity.getTexturesProperty() + " and UUID: " + entity.getSkullUUID());
|
||||
if (entity instanceof SkullPlayerEntity skullEntity) {
|
||||
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
|
||||
SkinData fallback = SkinProvider.determineFallbackSkinData(entity.getSkullUUID());
|
||||
SkinData fallback = SkinProvider.determineFallbackSkinData(skullEntity.getSkullUUID());
|
||||
applySkin.accept(fallback.skin(), null);
|
||||
}
|
||||
} else {
|
||||
SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true)
|
||||
.whenCompleteAsync(applySkin);
|
||||
|
||||
@@ -91,11 +91,10 @@ public class JavaPlayerInfoUpdateTranslator extends PacketTranslator<Clientbound
|
||||
session.getEntityCache().addPlayerEntity(playerEntity);
|
||||
}
|
||||
playerEntity.setUsername(name);
|
||||
playerEntity.setTexturesProperty(texturesProperty);
|
||||
|
||||
if (self) {
|
||||
SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape ->
|
||||
GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername()));
|
||||
playerEntity.setSkin(profile, true,
|
||||
() -> GeyserImpl.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user