diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 9e5b62ca2..ad43f2354 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -29,29 +29,23 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; -import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import lombok.Getter; import lombok.Setter; import net.kyori.adventure.text.Component; -import org.geysermc.connector.entity.attribute.Attribute; -import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.living.ArmorStandEntity; import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.chat.MessageTranslator; -import org.geysermc.connector.utils.AttributeUtils; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; @Getter @Setter @@ -77,7 +71,6 @@ public class Entity { protected boolean valid; protected LongOpenHashSet passengers = new LongOpenHashSet(); - protected Map attributes = new HashMap<>(); protected EntityDataMap metadata = new EntityDataMap(); public Entity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { @@ -116,6 +109,7 @@ public class Entity { addEntityPacket.setRotation(getBedrockRotation()); addEntityPacket.setEntityType(entityType.getType()); addEntityPacket.getMetadata().putAll(metadata); + addAdditionalSpawnData(addEntityPacket); valid = true; session.sendUpstreamPacket(addEntityPacket); @@ -123,6 +117,13 @@ public class Entity { session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); } + /** + * To be overridden in other entity classes, if additional things need to be done to the spawn entity packet. + */ + public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) { + + } + /** * Despawns the entity * @@ -231,23 +232,6 @@ public class Entity { updatePositionAndRotation(session, 0, 0, 0, yaw, pitch, isOnGround); } - public void updateBedrockAttributes(GeyserSession session) { - if (!valid) return; - - List attributes = new ArrayList<>(); - for (Map.Entry entry : this.attributes.entrySet()) { - if (!entry.getValue().getType().isBedrockAttribute()) - continue; - - attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); - } - - UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); - updateAttributesPacket.setRuntimeEntityId(geyserId); - updateAttributesPacket.setAttributes(attributes); - session.sendUpstreamPacket(updateAttributesPacket); - } - /** * Applies the Java metadata to the local Bedrock metadata copy * @param entityMetadata the Java entity metadata diff --git a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java index 4ffb626a8..70b1f0104 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -25,6 +25,7 @@ package org.geysermc.connector.entity; +import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; @@ -38,9 +39,10 @@ import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket; import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; -import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.attribute.GeyserAttributeType; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; @@ -48,8 +50,8 @@ import org.geysermc.connector.utils.AttributeUtils; import org.geysermc.connector.utils.ChunkUtils; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Map; @Getter @Setter @@ -62,6 +64,11 @@ public class LivingEntity extends Entity { protected ItemData hand = ItemData.AIR; protected ItemData offHand = ItemData.AIR; + @Getter(value = AccessLevel.NONE) + protected float health = 1f; // The default value in Java Edition before any entity metadata is sent + @Getter(value = AccessLevel.NONE) + protected float maxHealth = 20f; // The value Java Edition defaults to if no attribute is given + /** * A convenience variable for if the entity has reached the maximum frozen ticks and should be shaking */ @@ -69,6 +76,9 @@ public class LivingEntity extends Entity { public LivingEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + + // Matches Bedrock behavior; is always set to this + metadata.put(EntityData.HEALTH, 1); } @Override @@ -88,7 +98,13 @@ public class LivingEntity extends Entity { metadata.getFlags().setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04); break; case 9: - metadata.put(EntityData.HEALTH, entityMetadata.getValue()); + this.health = (float) entityMetadata.getValue(); + + AttributeData healthData = createHealthAttribute(); + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(geyserId); + attributesPacket.setAttributes(Collections.singletonList(healthData)); + session.sendUpstreamPacket(attributesPacket); break; case 10: metadata.put(EntityData.EFFECT_COLOR, entityMetadata.getValue()); @@ -139,12 +155,12 @@ public class LivingEntity extends Entity { metadata.getFlags().setFlag(EntityFlag.SHAKING, isShaking(session)); } - public void updateAllEquipment(GeyserSession session) { - if (!valid) return; - - updateArmor(session); - updateMainHand(session); - updateOffHand(session); + /** + * @return a Bedrock health attribute constructed from the data sent from the server + */ + protected AttributeData createHealthAttribute() { + // Default health needs to be specified as the max health in order for maximum hearts to show correctly on mounted entities + return new AttributeData(GeyserAttributeType.HEALTH.getBedrockIdentifier(), 0f, this.maxHealth, this.health, this.maxHealth); } public void updateArmor(GeyserSession session) { @@ -198,37 +214,67 @@ public class LivingEntity extends Entity { session.sendUpstreamPacket(offHandPacket); } - @Override - public void updateBedrockAttributes(GeyserSession session) { + /** + * Attributes are properties of an entity that are generally more runtime-based instead of permanent properties. + * Movement speed, current attack damage with a weapon, current knockback resistance. + * + * @param attributes the Java list of attributes sent from the server + */ + public void updateBedrockAttributes(GeyserSession session, List attributes) { if (!valid) return; - float maxHealth = this.attributes.containsKey(AttributeType.MAX_HEALTH) ? this.attributes.get(AttributeType.MAX_HEALTH).getValue() : getDefaultMaxHealth(); + List newAttributes = new ArrayList<>(); - List attributes = new ArrayList<>(); - for (Map.Entry entry : this.attributes.entrySet()) { - if (!entry.getValue().getType().isBedrockAttribute()) - continue; - if (entry.getValue().getType() == AttributeType.HEALTH) { - // Add health attribute to properly show hearts when mounting - // TODO: Not a perfect system, since it led to respawn bugs - attributes.add(new AttributeData("minecraft:health", 0.0f, maxHealth, metadata.getFloat(EntityData.HEALTH, 20f), maxHealth)); - continue; - } + for (Attribute attribute : attributes) { + // Convert the attribute to a Bedrock version, if relevant + updateAttribute(attribute, newAttributes); + } - attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); + if (newAttributes.isEmpty()) { + // If there are Java-only attributes or only attributes that are not translated by us + return; } UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); updateAttributesPacket.setRuntimeEntityId(geyserId); - updateAttributesPacket.setAttributes(attributes); + updateAttributesPacket.setAttributes(newAttributes); session.sendUpstreamPacket(updateAttributesPacket); } /** - * Used for the health visual when mounting an entity. - * @return the default maximum health for the entity. + * Takes the Java attribute and adds it to newAttributes as a Bedrock-formatted attribute */ - protected float getDefaultMaxHealth() { - return 20f; + protected void updateAttribute(Attribute javaAttribute, List newAttributes) { + switch (javaAttribute.getType()) { + case GENERIC_MAX_HEALTH: + this.maxHealth = (float) AttributeUtils.calculateValue(javaAttribute); + newAttributes.add(createHealthAttribute()); + break; + case GENERIC_ATTACK_DAMAGE: + newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE)); + break; + case GENERIC_FLYING_SPEED: + newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FLYING_SPEED)); + break; + case GENERIC_MOVEMENT_SPEED: + newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.MOVEMENT_SPEED)); + break; + case GENERIC_FOLLOW_RANGE: + newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE)); + break; + case GENERIC_KNOCKBACK_RESISTANCE: + newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE)); + break; + case HORSE_JUMP_STRENGTH: + newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH)); + break; + } + } + + /** + * Calculates the complete attribute value to send to Bedrock. Will be overriden if attributes need to be cached. + */ + protected AttributeData calculateAttribute(Attribute javaAttribute, GeyserAttributeType type) { + return type.getAttribute((float) AttributeUtils.calculateValue(javaAttribute)); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java b/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java deleted file mode 100644 index 9cb803898..000000000 --- a/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2019-2021 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.connector.entity.attribute; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -@Getter -@Setter -@AllArgsConstructor -@ToString -public class Attribute { - - private AttributeType type; - private float minimum; - private float maximum; - private float value; - private float defaultValue; -} diff --git a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java b/connector/src/main/java/org/geysermc/connector/entity/attribute/GeyserAttributeType.java similarity index 84% rename from connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java rename to connector/src/main/java/org/geysermc/connector/entity/attribute/GeyserAttributeType.java index 6877bb7c6..54719d89e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/attribute/GeyserAttributeType.java @@ -25,12 +25,13 @@ package org.geysermc.connector.entity.attribute; +import com.nukkitx.protocol.bedrock.data.AttributeData; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor -public enum AttributeType { +public enum GeyserAttributeType { // Universal Attributes FOLLOW_RANGE("minecraft:generic.follow_range", "minecraft:follow_range", 0f, 2048f, 32f), @@ -39,13 +40,13 @@ public enum AttributeType { FLYING_SPEED("minecraft:generic.flying_speed", "minecraft:movement", 0.0f, 1024.0f, 0.4000000059604645f), ATTACK_DAMAGE("minecraft:generic.attack_damage", "minecraft:attack_damage", 0f, 2048f, 1f), HORSE_JUMP_STRENGTH("minecraft:horse.jump_strength", "minecraft:horse.jump_strength", 0.0f, 2.0f, 0.7f), + LUCK("minecraft:generic.luck", "minecraft:luck", -1024f, 1024f, 0f), // Java Attributes ARMOR("minecraft:generic.armor", null, 0f, 30f, 0f), ARMOR_TOUGHNESS("minecraft:generic.armor_toughness", null, 0F, 20f, 0f), ATTACK_KNOCKBACK("minecraft:generic.attack_knockback", null, 1.5f, Float.MAX_VALUE, 0f), ATTACK_SPEED("minecraft:generic.attack_speed", null, 0f, 1024f, 4f), - LUCK("minecraft:generic.luck", null, -1024f, 1024f, 0f), MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f), // Bedrock Attributes @@ -64,19 +65,15 @@ public enum AttributeType { private final float maximum; private final float defaultValue; - public Attribute getAttribute(float value) { + public AttributeData getAttribute(float value) { return getAttribute(value, maximum); } - public Attribute getAttribute(float value, float maximum) { - return new Attribute(this, minimum, maximum, value, defaultValue); - } - - public boolean isJavaAttribute() { - return javaIdentifier != null; - } - - public boolean isBedrockAttribute() { - return bedrockIdentifier != null; + public AttributeData getAttribute(float value, float maximum) { + if (bedrockIdentifier == null) { + return null; + } + // Minimum, maximum, and default values are hardcoded on Java Edition, whereas controlled by the server on Bedrock + return new AttributeData(bedrockIdentifier, minimum, maximum, value, defaultValue); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java index f47ff2c7f..a7b7a55c6 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java @@ -25,20 +25,10 @@ package org.geysermc.connector.entity.living; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; -import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.AttributeUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; public class IronGolemEntity extends GolemEntity { @@ -49,32 +39,4 @@ public class IronGolemEntity extends GolemEntity { // Required, or else the overlay is black metadata.put(EntityData.COLOR_2, (byte) 0); } - - @Override - public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 9) { - // Required so the resource pack sees the entity health - attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(metadata.getFloat(EntityData.HEALTH), 100f)); - updateBedrockAttributes(session); - } - } - - @Override - public void updateBedrockAttributes(GeyserSession session) { - if (!valid) return; - - List attributes = new ArrayList<>(); - for (Map.Entry entry : this.attributes.entrySet()) { - if (!entry.getValue().getType().isBedrockAttribute()) - continue; - - attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); - } - - UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); - updateAttributesPacket.setRuntimeEntityId(geyserId); - updateAttributesPacket.setAttributes(attributes); - session.sendUpstreamPacket(updateAttributesPacket); - } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java index 27d736b33..bc07472ea 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java @@ -50,9 +50,4 @@ public class PigEntity extends AnimalEntity { public boolean canEat(GeyserSession session, String javaIdentifierStripped, ItemEntry itemEntry) { return javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot"); } - - @Override - protected float getDefaultMaxHealth() { - return 10f; - } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java index 5f4082e50..6364fe7aa 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java @@ -33,7 +33,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; -import org.geysermc.connector.entity.attribute.AttributeType; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.connector.entity.attribute.GeyserAttributeType; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -55,15 +56,24 @@ public class AbstractHorseEntity extends AnimalEntity { // Specifies the size of the entity's inventory. Required to place slots in the entity. metadata.put(EntityData.CONTAINER_BASE_SIZE, 2); - // Add dummy health attribute since LivingEntity updates the attribute for us - attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(20, 20)); - // Add horse jump strength attribute to allow donkeys and mules to jump - attributes.put(AttributeType.HORSE_JUMP_STRENGTH, AttributeType.HORSE_JUMP_STRENGTH.getAttribute(0.5f, 2)); + } + + @Override + public void spawnEntity(GeyserSession session) { + super.spawnEntity(session); + + // Add horse jump strength attribute to allow donkeys and mules to jump, if they don't send the attribute themselves. + // Confirmed broken without this code by making a new donkey in vanilla 1.17.1 + // The spawn packet does have an attributes section, but adding the jump strength property there causes the + // donkey to jump very high. + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(geyserId); + attributesPacket.getAttributes().add(GeyserAttributeType.HORSE_JUMP_STRENGTH.getAttribute(0.5f, 2)); + session.sendUpstreamPacket(attributesPacket); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x02) == 0x02); @@ -105,11 +115,6 @@ public class AbstractHorseEntity extends AnimalEntity { metadata.getFlags().setFlag(EntityFlag.WASD_CONTROLLED, true); super.updateBedrockMetadata(entityMetadata, session); - - if (entityMetadata.getId() == 9) { - // Update the health attribute - updateBedrockAttributes(session); - } } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java index 74bd5b7ce..942d006cf 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java @@ -35,16 +35,12 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.*; import lombok.Data; import org.geysermc.connector.entity.Tickable; -import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.attribute.GeyserAttributeType; import org.geysermc.connector.entity.living.InsentientEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.AttributeUtils; import org.geysermc.connector.utils.DimensionUtils; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicLong; @@ -100,44 +96,21 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable { super.updateBedrockMetadata(entityMetadata, session); - if (entityMetadata.getId() == 9) { // Health - // Update the health attribute, so that the death animation gets played - // Round health up, so that Bedrock doesn't consider the dragon to be dead when health is between 0 and 1 - float health = (float) Math.ceil(metadata.getFloat(EntityData.HEALTH)); - if (phase == 9 && health <= 0) { // Dying phase + if (entityMetadata.getId() == 9) { + if (phase == 9 && this.health <= 0) { // Dying phase EntityEventPacket entityEventPacket = new EntityEventPacket(); entityEventPacket.setType(EntityEventType.ENDER_DRAGON_DEATH); entityEventPacket.setRuntimeEntityId(geyserId); entityEventPacket.setData(0); session.sendUpstreamPacket(entityEventPacket); } - attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(health, 200)); - updateBedrockAttributes(session); } } - /** - * Send an updated list of attributes to the Bedrock client. - * This is overwritten to allow the health attribute to differ from - * the health specified in the metadata. - * - * @param session GeyserSession - */ @Override - public void updateBedrockAttributes(GeyserSession session) { - if (!valid) return; - - List attributes = new ArrayList<>(); - for (Map.Entry entry : this.attributes.entrySet()) { - if (!entry.getValue().getType().isBedrockAttribute()) - continue; - attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); - } - - UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); - updateAttributesPacket.setRuntimeEntityId(geyserId); - updateAttributesPacket.setAttributes(attributes); - session.sendUpstreamPacket(updateAttributesPacket); + protected AttributeData createHealthAttribute() { + // Round health up, so that Bedrock doesn't consider the dragon to be dead when health is between 0 and 1 + return GeyserAttributeType.HEALTH.getAttribute((float) Math.ceil(this.health), this.maxHealth); } @Override @@ -168,6 +141,13 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable { } } + @Override + public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) { + // Bedrock is EXTREMELY sensitive to the Ender Dragon's health - if it is dead once, it is dead for the rest of its life + // Ensure that the first spawn packet sent has health data so this cannot happen until it actually should + addEntityPacket.getAttributes().add(createHealthAttribute()); + } + @Override public boolean despawnEntity(GeyserSession session) { for (EnderDragonPartEntity part : allParts) { @@ -318,7 +298,7 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable { } private boolean isAlive() { - return metadata.getFloat(EntityData.HEALTH) > 0; + return health > 0; } private boolean isHovering() { diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java index 4f3cc5748..7b182da38 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java @@ -46,16 +46,14 @@ import lombok.Setter; import net.kyori.adventure.text.Component; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.LivingEntity; -import org.geysermc.connector.entity.attribute.Attribute; -import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.scoreboard.Team; -import org.geysermc.connector.utils.AttributeUtils; import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.connector.scoreboard.Team; -import java.util.*; +import java.util.Collections; +import java.util.UUID; import java.util.concurrent.TimeUnit; @Getter @Setter @@ -112,9 +110,6 @@ public class PlayerEntity extends LivingEntity { valid = true; session.sendUpstreamPacket(addPlayerPacket); - - updateAllEquipment(session); - updateBedrockAttributes(session); } public void sendPlayer(GeyserSession session) { @@ -350,22 +345,4 @@ public class PlayerEntity extends LivingEntity { metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); } - - @Override - public void updateBedrockAttributes(GeyserSession session) { // TODO: Don't use duplicated code - if (!valid) return; - - List attributes = new ArrayList<>(); - for (Map.Entry entry : this.attributes.entrySet()) { - if (!entry.getValue().getType().isBedrockAttribute()) - continue; - - attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); - } - - UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); - updateAttributesPacket.setRuntimeEntityId(geyserId); - updateAttributesPacket.setAttributes(attributes); - session.sendUpstreamPacket(updateAttributesPacket); - } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/SessionPlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/SessionPlayerEntity.java index 611d0d1e4..65f2baa66 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/SessionPlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/SessionPlayerEntity.java @@ -26,18 +26,34 @@ package org.geysermc.connector.entity.player; import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute; +import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Getter; +import org.geysermc.connector.entity.attribute.GeyserAttributeType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.AttributeUtils; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.UUID; /** * The entity class specifically for a {@link GeyserSession}'s player. */ public class SessionPlayerEntity extends PlayerEntity { + /** + * Used to fix some inconsistencies, especially in respawning. + */ + @Getter + protected final Map attributes = new Object2ObjectOpenHashMap<>(); /** * Whether to check for updated speed after all entity metadata has been processed */ @@ -83,14 +99,63 @@ public class SessionPlayerEntity extends PlayerEntity { } } + public float getMaxHealth() { + return maxHealth; + } + + @Override + public void setHealth(float health) { + super.setHealth(health); + } + + @Override + public AttributeData createHealthAttribute() { + // Max health must be divisible by two in bedrock + if ((maxHealth % 2) == 1) { + maxHealth += 1; + } + return super.createHealthAttribute(); + } + @Override public void updateBedrockMetadata(GeyserSession session) { super.updateBedrockMetadata(session); if (refreshSpeed) { - if (session.adjustSpeed()) { - updateBedrockAttributes(session); + AttributeData speedAttribute = session.adjustSpeed(); + if (speedAttribute != null) { + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(geyserId); + attributesPacket.setAttributes(Collections.singletonList(speedAttribute)); + session.sendUpstreamPacket(attributesPacket); } refreshSpeed = false; } } + + @Override + protected void updateAttribute(Attribute javaAttribute, List newAttributes) { + if (javaAttribute.getType() == AttributeType.GENERIC_ATTACK_SPEED) { + session.setAttackSpeed(AttributeUtils.calculateValue(javaAttribute)); + } else { + super.updateAttribute(javaAttribute, newAttributes); + } + } + + @Override + protected AttributeData calculateAttribute(Attribute javaAttribute, GeyserAttributeType type) { + AttributeData attributeData = super.calculateAttribute(javaAttribute, type); + + if (javaAttribute.getType() == AttributeType.GENERIC_MOVEMENT_SPEED) { + session.setOriginalSpeedAttribute(attributeData.getValue()); + AttributeData speedAttribute = session.adjustSpeed(); + if (speedAttribute != null) { + // Overwrite the attribute with our own + this.attributes.put(type, speedAttribute); + return speedAttribute; + } + } + + this.attributes.put(type, attributeData); + return attributeData; + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/SkullPlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/SkullPlayerEntity.java index 333726cab..60b7c70e3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/SkullPlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/SkullPlayerEntity.java @@ -87,9 +87,6 @@ public class SkullPlayerEntity extends PlayerEntity { valid = true; session.sendUpstreamPacket(addPlayerPacket); - - updateAllEquipment(session); - updateBedrockAttributes(session); } public void despawnEntity(GeyserSession session, Vector3i position) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 73dad7220..8c5455d1e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -78,8 +78,7 @@ import org.geysermc.connector.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.Tickable; -import org.geysermc.connector.entity.attribute.Attribute; -import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.attribute.GeyserAttributeType; import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.inventory.Inventory; @@ -880,9 +879,13 @@ public class GeyserSession implements CommandSender { this.sneaking = sneaking; // Update pose and bounding box on our end - if (!sneaking && adjustSpeed()) { + AttributeData speedAttribute; + if (!sneaking && (speedAttribute = adjustSpeed()) != null) { // Update attributes since we're still "sneaking" under a 1.5-block-tall area - playerEntity.updateBedrockAttributes(this); + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(playerEntity.getGeyserId()); + attributesPacket.setAttributes(Collections.singletonList(speedAttribute)); + sendUpstreamPacket(attributesPacket); // the server *should* update our pose once it has returned to normal } else { if (!flying) { @@ -928,23 +931,25 @@ public class GeyserSession implements CommandSender { /** * Adjusts speed if the player is crawling. * - * @return true if attributes should be updated. + * @return not null if attributes should be updated. */ - public boolean adjustSpeed() { - Attribute currentPlayerSpeed = playerEntity.getAttributes().get(AttributeType.MOVEMENT_SPEED); + public AttributeData adjustSpeed() { + AttributeData currentPlayerSpeed = playerEntity.getAttributes().get(GeyserAttributeType.MOVEMENT_SPEED); if (currentPlayerSpeed != null) { if ((pose.equals(Pose.SNEAKING) && !sneaking && collisionManager.isUnderSlab()) || (!swimmingInWater && playerEntity.getMetadata().getFlags().getFlag(EntityFlag.SWIMMING) && !collisionManager.isPlayerInWater())) { // Either of those conditions means that Bedrock goes zoom when they shouldn't be - currentPlayerSpeed.setValue(originalSpeedAttribute / 3.32f); - return true; + AttributeData speedAttribute = GeyserAttributeType.MOVEMENT_SPEED.getAttribute(originalSpeedAttribute / 3.32f); + playerEntity.getAttributes().put(GeyserAttributeType.MOVEMENT_SPEED, speedAttribute); + return speedAttribute; } else if (originalSpeedAttribute != currentPlayerSpeed.getValue()) { // Speed has reset to normal - currentPlayerSpeed.setValue(originalSpeedAttribute); - return true; + AttributeData speedAttribute = GeyserAttributeType.MOVEMENT_SPEED.getAttribute(originalSpeedAttribute); + playerEntity.getAttributes().put(GeyserAttributeType.MOVEMENT_SPEED, speedAttribute); + return speedAttribute; } } - return false; + return null; } /** @@ -977,8 +982,8 @@ public class GeyserSession implements CommandSender { return false; } - @Override - public String getLocale() { + @Override + public String getLocale() { return clientData.getLanguageCode(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java index 208e7c75d..cd9265c0e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -26,14 +26,13 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientKeepAlivePacket; +import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; -import org.geysermc.connector.entity.attribute.Attribute; -import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.attribute.GeyserAttributeType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.AttributeUtils; import org.geysermc.floodgate.util.DeviceOs; import java.util.Collections; @@ -70,11 +69,11 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator { @Override public void translate(PlayerActionPacket packet, GeyserSession session) { - Entity entity = session.getPlayerEntity(); + SessionPlayerEntity entity = session.getPlayerEntity(); // Send book update before any player action if (packet.getAction() != PlayerActionType.RESPAWN) { @@ -73,7 +73,10 @@ public class BedrockActionTranslator extends PacketTranslator(entity.getAttributes().values())); + session.sendUpstreamPacket(attributesPacket); break; case START_SWIMMING: ClientPlayerStatePacket startSwimPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SPRINTING); @@ -235,7 +238,12 @@ public class BedrockActionTranslator extends PacketTranslator(entity.getAttributes().values())); + session.sendUpstreamPacket(attributesPacket); + session.getEntityCache().updateBossBars(); break; case JUMP: diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java index 42f572e14..9a8de0ed2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java @@ -30,8 +30,8 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.attribute.GeyserAttributeType; +import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -44,11 +44,10 @@ public class JavaRespawnTranslator extends PacketTranslator @Override public void translate(ServerRespawnPacket packet, GeyserSession session) { - Entity entity = session.getPlayerEntity(); + SessionPlayerEntity entity = session.getPlayerEntity(); - float maxHealth = entity.getAttributes().containsKey(AttributeType.MAX_HEALTH) ? entity.getAttributes().get(AttributeType.MAX_HEALTH).getValue() : 20f; - // Max health must be divisible by two in bedrock - entity.getAttributes().put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(maxHealth, (maxHealth % 2 == 1 ? maxHealth + 1 : maxHealth))); + entity.setHealth(entity.getMaxHealth()); + entity.getAttributes().put(GeyserAttributeType.HEALTH, entity.createHealthAttribute()); session.addInventoryTask(() -> { session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java index 7c4a95cbb..642ba3bb8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java @@ -25,67 +25,26 @@ package org.geysermc.connector.network.translators.java.entity; -import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPropertiesPacket; import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.LivingEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.AttributeUtils; @Translator(packet = ServerEntityPropertiesPacket.class) public class JavaEntityPropertiesTranslator extends PacketTranslator { @Override public void translate(ServerEntityPropertiesPacket packet, GeyserSession session) { - boolean isSessionPlayer = false; - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + Entity entity; if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); - isSessionPlayer = true; + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); } - if (entity == null) return; + if (!(entity instanceof LivingEntity)) return; - for (Attribute attribute : packet.getAttributes()) { - switch (attribute.getType()) { - case GENERIC_MAX_HEALTH: - entity.getAttributes().put(AttributeType.MAX_HEALTH, AttributeType.MAX_HEALTH.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - case GENERIC_ATTACK_DAMAGE: - entity.getAttributes().put(AttributeType.ATTACK_DAMAGE, AttributeType.ATTACK_DAMAGE.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - case GENERIC_ATTACK_SPEED: - if (isSessionPlayer) { - // Get attack speed value for use in sending the faux cooldown - double attackSpeed = AttributeUtils.calculateValue(attribute); - session.setAttackSpeed(attackSpeed); - } - break; - case GENERIC_FLYING_SPEED: - entity.getAttributes().put(AttributeType.FLYING_SPEED, AttributeType.FLYING_SPEED.getAttribute((float) AttributeUtils.calculateValue(attribute))); - entity.getAttributes().put(AttributeType.MOVEMENT_SPEED, AttributeType.MOVEMENT_SPEED.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - case GENERIC_MOVEMENT_SPEED: - float value = (float) AttributeUtils.calculateValue(attribute); - entity.getAttributes().put(AttributeType.MOVEMENT_SPEED, AttributeType.MOVEMENT_SPEED.getAttribute(value)); - if (isSessionPlayer) { - session.setOriginalSpeedAttribute(value); - session.adjustSpeed(); - } - break; - case GENERIC_FOLLOW_RANGE: - entity.getAttributes().put(AttributeType.FOLLOW_RANGE, AttributeType.FOLLOW_RANGE.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - case GENERIC_KNOCKBACK_RESISTANCE: - entity.getAttributes().put(AttributeType.KNOCKBACK_RESISTANCE, AttributeType.KNOCKBACK_RESISTANCE.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - case HORSE_JUMP_STRENGTH: - entity.getAttributes().put(AttributeType.HORSE_JUMP_STRENGTH, AttributeType.HORSE_JUMP_STRENGTH.getAttribute((float) AttributeUtils.calculateValue(attribute))); - break; - } - } - - entity.updateBedrockAttributes(session); + ((LivingEntity) entity).updateBedrockAttributes(session, packet.getAttributes()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerHealthTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerHealthTranslator.java index c003e7ad0..05bdfd95b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerHealthTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerHealthTranslator.java @@ -25,38 +25,48 @@ package org.geysermc.connector.network.translators.java.entity.player; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.attribute.AttributeType; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerHealthPacket; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.packet.SetHealthPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.connector.entity.attribute.GeyserAttributeType; +import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerHealthPacket; -import com.nukkitx.protocol.bedrock.packet.SetHealthPacket; +import java.util.List; @Translator(packet = ServerPlayerHealthPacket.class) public class JavaPlayerHealthTranslator extends PacketTranslator { @Override public void translate(ServerPlayerHealthPacket packet, GeyserSession session) { - Entity entity = session.getPlayerEntity(); - if (entity == null) - return; + SessionPlayerEntity entity = session.getPlayerEntity(); int health = (int) Math.ceil(packet.getHealth()); SetHealthPacket setHealthPacket = new SetHealthPacket(); setHealthPacket.setHealth(health); session.sendUpstreamPacket(setHealthPacket); - float maxHealth = entity.getAttributes().containsKey(AttributeType.MAX_HEALTH) ? entity.getAttributes().get(AttributeType.MAX_HEALTH).getValue() : 20f; - // Max health must be divisible by two in bedrock - if ((maxHealth % 2) == 1) { - maxHealth += 1; - } + entity.setHealth(packet.getHealth()); - entity.getAttributes().put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(health, maxHealth)); - entity.getAttributes().put(AttributeType.HUNGER, AttributeType.HUNGER.getAttribute(packet.getFood())); - entity.getAttributes().put(AttributeType.SATURATION, AttributeType.SATURATION.getAttribute(packet.getSaturation())); - entity.updateBedrockAttributes(session); + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + List attributes = attributesPacket.getAttributes(); + + AttributeData healthAttribute = entity.createHealthAttribute(); + entity.getAttributes().put(GeyserAttributeType.HEALTH, healthAttribute); + attributes.add(healthAttribute); + + AttributeData hungerAttribute = GeyserAttributeType.HUNGER.getAttribute(packet.getFood()); + entity.getAttributes().put(GeyserAttributeType.HUNGER, hungerAttribute); + attributes.add(hungerAttribute); + + AttributeData saturationAttribute = GeyserAttributeType.SATURATION.getAttribute(packet.getSaturation()); + entity.getAttributes().put(GeyserAttributeType.SATURATION, saturationAttribute); + attributes.add(saturationAttribute); + + attributesPacket.setRuntimeEntityId(entity.getGeyserId()); + session.sendUpstreamPacket(attributesPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerSetExperienceTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerSetExperienceTranslator.java index 4ec808e8e..52cd0b587 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerSetExperienceTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerSetExperienceTranslator.java @@ -25,25 +25,32 @@ package org.geysermc.connector.network.translators.java.entity.player; -import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.attribute.AttributeType; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerSetExperiencePacket; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.connector.entity.attribute.GeyserAttributeType; +import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerSetExperiencePacket; +import java.util.Arrays; @Translator(packet = ServerPlayerSetExperiencePacket.class) public class JavaPlayerSetExperienceTranslator extends PacketTranslator { @Override public void translate(ServerPlayerSetExperiencePacket packet, GeyserSession session) { - Entity entity = session.getPlayerEntity(); - if (entity == null) - return; + SessionPlayerEntity entity = session.getPlayerEntity(); - entity.getAttributes().put(AttributeType.EXPERIENCE, AttributeType.EXPERIENCE.getAttribute(packet.getExperience())); - entity.getAttributes().put(AttributeType.EXPERIENCE_LEVEL, AttributeType.EXPERIENCE_LEVEL.getAttribute(packet.getLevel())); - entity.updateBedrockAttributes(session); + AttributeData experience = GeyserAttributeType.EXPERIENCE.getAttribute(packet.getExperience()); + entity.getAttributes().put(GeyserAttributeType.EXPERIENCE, experience); + AttributeData experienceLevel = GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(packet.getLevel()); + entity.getAttributes().put(GeyserAttributeType.EXPERIENCE_LEVEL, experienceLevel); + + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); + attributesPacket.setAttributes(Arrays.asList(experience, experienceLevel)); + session.sendUpstreamPacket(attributesPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java index 97d69a632..4cb967234 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java @@ -60,9 +60,7 @@ public class JavaSpawnPlayerTranslator extends PacketTranslator