Attribute rewrite

This commit solves a couple issues:
- Entities that don't implement LivingEntity don't need to bother with attributes
- We don't need to cache all attributes for every entity
- Mounted entities' hearts were inconsistent

No entity as of this commit caches their attributes except for the player entity.
This commit is contained in:
Camotoy 2021-07-07 22:44:53 -04:00
parent ad9987517d
commit 32bbd8ae1c
No known key found for this signature in database
GPG Key ID: 7EEFB66FE798081F
20 changed files with 292 additions and 384 deletions

View File

@ -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<AttributeType, Attribute> 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<AttributeData> attributes = new ArrayList<>();
for (Map.Entry<AttributeType, Attribute> 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

View File

@ -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<Attribute> attributes) {
if (!valid) return;
float maxHealth = this.attributes.containsKey(AttributeType.MAX_HEALTH) ? this.attributes.get(AttributeType.MAX_HEALTH).getValue() : getDefaultMaxHealth();
List<AttributeData> newAttributes = new ArrayList<>();
List<AttributeData> attributes = new ArrayList<>();
for (Map.Entry<AttributeType, org.geysermc.connector.entity.attribute.Attribute> 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<AttributeData> 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));
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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<AttributeData> attributes = new ArrayList<>();
for (Map.Entry<AttributeType, org.geysermc.connector.entity.attribute.Attribute> 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);
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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<AttributeData> attributes = new ArrayList<>();
for (Map.Entry<AttributeType, org.geysermc.connector.entity.attribute.Attribute> 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() {

View File

@ -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<AttributeData> attributes = new ArrayList<>();
for (Map.Entry<AttributeType, Attribute> 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);
}
}

View File

@ -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<GeyserAttributeType, AttributeData> 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<AttributeData> 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;
}
}

View File

@ -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) {

View File

@ -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();
}

View File

@ -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<Netwo
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
Attribute attribute = session.getPlayerEntity().getAttributes().get(AttributeType.EXPERIENCE_LEVEL);
AttributeData attribute = session.getPlayerEntity().getAttributes().get(GeyserAttributeType.EXPERIENCE_LEVEL);
if (attribute != null) {
attributesPacket.setAttributes(Collections.singletonList(AttributeUtils.getBedrockAttribute(attribute)));
attributesPacket.setAttributes(Collections.singletonList(attribute));
} else {
attributesPacket.setAttributes(Collections.singletonList(AttributeUtils.getBedrockAttribute(AttributeType.EXPERIENCE_LEVEL.getAttribute(0))));
attributesPacket.setAttributes(Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0)));
}
session.getConnector().getGeneralThreadPool().schedule(

View File

@ -35,12 +35,10 @@ import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.PlayerActionType;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket;
import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket;
import com.nukkitx.protocol.bedrock.packet.*;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.ItemFrameEntity;
import org.geysermc.connector.entity.player.SessionPlayerEntity;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
@ -49,12 +47,14 @@ import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.BlockUtils;
import java.util.ArrayList;
@Translator(packet = PlayerActionPacket.class)
public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket> {
@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<PlayerActionPacket
eventPacket.setData(0);
session.sendUpstreamPacket(eventPacket);
// Resend attributes or else in rare cases the user can think they're not dead when they are, upon joining the server
entity.updateBedrockAttributes(session);
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(entity.getGeyserId());
attributesPacket.setAttributes(new ArrayList<>(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<PlayerActionPacket
PlayStatusPacket spawnPacket = new PlayStatusPacket();
spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
session.sendUpstreamPacket(spawnPacket);
entity.updateBedrockAttributes(session);
attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(entity.getGeyserId());
attributesPacket.setAttributes(new ArrayList<>(entity.getAttributes().values()));
session.sendUpstreamPacket(attributesPacket);
session.getEntityCache().updateBossBars();
break;
case JUMP:

View File

@ -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<ServerRespawnPacket>
@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);

View File

@ -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<ServerEntityPropertiesPacket> {
@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());
}
}

View File

@ -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<ServerPlayerHealthPacket> {
@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<AttributeData> 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);
}
}

View File

@ -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<ServerPlayerSetExperiencePacket> {
@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);
}
}

View File

@ -60,9 +60,7 @@ public class JavaSpawnPlayerTranslator extends PacketTranslator<ServerSpawnPlaye
}
session.getEntityCache().cacheEntity(entity);
if (session.getUpstream().isInitialized()) {
entity.sendPlayer(session);
SkinManager.requestAndHandleSkinAndCape(entity, session, null);
}
entity.sendPlayer(session);
SkinManager.requestAndHandleSkinAndCape(entity, session, null);
}
}

View File

@ -25,59 +25,18 @@
package org.geysermc.connector.utils;
import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute;
import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeModifier;
import com.github.steveice10.mc.protocol.data.game.entity.attribute.ModifierOperation;
import com.nukkitx.protocol.bedrock.data.AttributeData;
import org.geysermc.connector.entity.attribute.Attribute;
import org.geysermc.connector.entity.attribute.AttributeType;
public class AttributeUtils {
public static com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute getJavaAttribute(Attribute attribute) {
if (!attribute.getType().isJavaAttribute())
return null;
com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType type = null;
try {
type = com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType.valueOf("GENERIC_" + attribute.getType().name());
} catch (Exception ex) {
// Catch and loop since attributes can semi-overlap
for (AttributeType attributeType : AttributeType.values()) {
if (!attributeType.isJavaAttribute())
continue;
if (!attributeType.getJavaIdentifier().equals(attribute.getType().getJavaIdentifier()))
continue;
try {
type = com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType.valueOf("GENERIC_" + attributeType.name());
} catch (Exception e) {
continue;
}
}
}
if (type == null)
return null;
return new com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute(type, attribute.getValue());
}
public static AttributeData getBedrockAttribute(Attribute attribute) {
AttributeType type = attribute.getType();
if (!type.isBedrockAttribute())
return null;
return new AttributeData(type.getBedrockIdentifier(), attribute.getMinimum(), attribute.getMaximum(), attribute.getValue(), attribute.getDefaultValue());
}
/**
* Retrieve the base attribute value with all modifiers applied.
* https://minecraft.gamepedia.com/Attribute#Modifiers
* @param attribute The attribute to calculate the total value.
* @return The finished attribute with all modifiers applied.
*/
public static double calculateValue(com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute attribute) {
public static double calculateValue(Attribute attribute) {
double base = attribute.getValue();
for (AttributeModifier modifier : attribute.getModifiers()) {
if (modifier.getOperation() == ModifierOperation.ADD) {