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 65ebe98f2..30d61ad9b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -26,19 +26,25 @@ package org.geysermc.connector.entity; import com.flowpowered.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.data.Attribute; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPropertiesPacket; import com.nukkitx.protocol.bedrock.data.EntityData; import com.nukkitx.protocol.bedrock.data.EntityDataDictionary; import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import lombok.Getter; import lombok.Setter; import org.geysermc.connector.console.GeyserLogger; +import org.geysermc.connector.entity.attribute.Attribute; +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.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -65,7 +71,7 @@ public class Entity { protected boolean valid; protected Set passengers = new HashSet(); - protected Map attributes = new HashMap(); + protected Map attributes = new HashMap(); public Entity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { this.entityId = entityId; @@ -143,4 +149,33 @@ public class Entity { dictionary.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); return dictionary; } + + public void updateBedrockAttributes(GeyserSession session) { + 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.getUpstream().sendPacket(updateAttributesPacket); + } + + // To be used at a later date + public void updateJavaAttributes(GeyserSession session) { + List attributes = new ArrayList<>(); + for (Map.Entry entry : this.attributes.entrySet()) { + if (!entry.getValue().getType().isBedrockAttribute()) + continue; + + attributes.add(AttributeUtils.getJavaAttribute(entry.getValue())); + } + + ServerEntityPropertiesPacket entityPropertiesPacket = new ServerEntityPropertiesPacket((int) entityId, attributes); + session.getDownstream().getSession().send(entityPropertiesPacket); + } } 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 new file mode 100644 index 000000000..ef3f67deb --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 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; + +@Getter +@AllArgsConstructor +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/AttributeType.java new file mode 100644 index 000000000..ba99e047a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019 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; + +@Getter +@AllArgsConstructor +public enum AttributeType { + + // Universal Attributes + FOLLOW_RANGE("generic.followRange", "minecraft:follow_range", 0f, 2048f, 32f), + KNOCKBACK_RESISTANCE("generic.knockbackResistance", "minecraft:knockback_resistance", 0f, 1f, 0f), + MOVEMENT_SPEED("generic.movementSpeed", "minecraft:movement", 0f, 1024f, 0.699999988079071f), + FLYING_SPEED("generic.flyingSpeed", "minecraft:movement", 0.0f, 1024.0f, 0.4000000059604645f), + ATTACK_DAMAGE("generic.attackDamage", "minecraft:attack_damage", 0f, 2048f, 1f), + + // Java Attributes + ARMOR("generic.armor", null, 0f, 30f, 0f), + ARMOR_TOUGHNESS("generic.armorToughness", null, 0F, 20f, 0f), + ATTACK_KNOCKBACK("generic.attackKnockback", null, 1.5f, Float.MAX_VALUE, 0f), + ATTACK_SPEED("generic.attackSpeed", null, 0f, 1024f, 4f), + LUCK("generic.luck", null, -1024f, 1024f, 0f), + MAX_HEALTH("generic.maxHealth", null, 0f, 1024f, 20f), + + // Bedrock Attributes + ABSORPTION(null, "minecraft:absorption", 0f, Float.MAX_VALUE, 0f), + EXHAUSTION(null, "minecraft:player.exhaustion", 0f, 5f, 0f), + EXPERIENCE(null, "minecraft:player.experience", 0f, 1f, 0f), + EXPERIENCE_LEVEL(null, "minecraft:player.level", 0f, 24791.00f, 0f), + HEALTH(null, "minecraft:health", 0f, 1024f, 20f), + HUNGER(null, "minecraft:player.hunger", 0f, 20f, 20f), + SATURATION(null, "minecraft:player.saturation", 0f, 20f, 20f); + + private String javaIdentifier; + private String bedrockIdentifier; + + private float minimum; + private float maximum; + private float defaultValue; + + public Attribute 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; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java b/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java index 0edcd59d4..b5aebc4d2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java @@ -38,7 +38,9 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntit import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityRotationPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityTeleportPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityVelocityPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerHealthPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerSetExperiencePacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnExpOrbPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnGlobalEntityPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnMobPacket; @@ -80,7 +82,9 @@ import org.geysermc.connector.network.translators.java.entity.JavaEntityProperti import org.geysermc.connector.network.translators.java.entity.JavaEntityRotationTranslator; import org.geysermc.connector.network.translators.java.entity.JavaEntityTeleportTranslator; import org.geysermc.connector.network.translators.java.entity.JavaEntityVelocityTranslator; +import org.geysermc.connector.network.translators.java.entity.player.JavaPlayerHealthTranslator; import org.geysermc.connector.network.translators.java.entity.player.JavaPlayerPositionRotationTranslator; +import org.geysermc.connector.network.translators.java.entity.player.JavaPlayerSetExperienceTranslator; import org.geysermc.connector.network.translators.java.entity.spawn.JavaSpawnExpOrbTranslator; import org.geysermc.connector.network.translators.java.entity.spawn.JavaSpawnGlobalEntityTranslator; import org.geysermc.connector.network.translators.java.entity.spawn.JavaSpawnMobTranslator; @@ -150,6 +154,8 @@ public class TranslatorsInit { Registry.registerJava(ServerSpawnPlayerPacket.class, new JavaSpawnPlayerTranslator()); Registry.registerJava(ServerPlayerPositionRotationPacket.class, new JavaPlayerPositionRotationTranslator()); + Registry.registerJava(ServerPlayerSetExperiencePacket.class, new JavaPlayerSetExperienceTranslator()); + Registry.registerJava(ServerPlayerHealthPacket.class, new JavaPlayerHealthTranslator()); Registry.registerJava(ServerNotifyClientPacket.class, new JavaNotifyClientTranslator()); Registry.registerJava(ServerEntityDestroyPacket.class, new JavaEntityDestroyTranslator()); 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 068ff2794..181b55ca2 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,8 +25,10 @@ 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.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -41,5 +43,33 @@ public class JavaEntityPropertiesTranslator extends PacketTranslator { + + @Override + public void translate(ServerPlayerHealthPacket packet, GeyserSession session) { + Entity entity = session.getPlayerEntity(); + if (entity == null) + return; + + SetHealthPacket setHealthPacket = new SetHealthPacket(); + setHealthPacket.setHealth((int) Math.ceil(packet.getHealth())); + session.getUpstream().sendPacket(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.getAttributes().put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(packet.getHealth(), maxHealth)); + entity.getAttributes().put(AttributeType.HUNGER, AttributeType.HUNGER.getAttribute(packet.getFood())); + entity.getAttributes().put(AttributeType.SATURATION, AttributeType.SATURATION.getAttribute(packet.getSaturation())); + entity.updateBedrockAttributes(session); + + if ((int) Math.ceil(packet.getHealth()) <= 0) { + session.getUpstream().sendPacket(new RespawnPacket()); + } + } +} 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 new file mode 100644 index 000000000..c478ae724 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerSetExperienceTranslator.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 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.network.translators.java.entity.player; + +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerSetExperiencePacket; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; + +public class JavaPlayerSetExperienceTranslator extends PacketTranslator { + + @Override + public void translate(ServerPlayerSetExperiencePacket packet, GeyserSession session) { + Entity entity = session.getPlayerEntity(); + if (entity == null) + return; + + entity.getAttributes().put(AttributeType.EXPERIENCE, AttributeType.EXPERIENCE.getAttribute(packet.getSlot())); + entity.getAttributes().put(AttributeType.EXPERIENCE_LEVEL, AttributeType.EXPERIENCE_LEVEL.getAttribute(packet.getLevel())); + entity.updateBedrockAttributes(session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/AttributeUtils.java b/connector/src/main/java/org/geysermc/connector/utils/AttributeUtils.java new file mode 100644 index 000000000..df58e2b18 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/AttributeUtils.java @@ -0,0 +1,45 @@ +package org.geysermc.connector.utils; + +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 com.nukkitx.protocol.bedrock.data.Attribute getBedrockAttribute(Attribute attribute) { + AttributeType type = attribute.getType(); + if (!type.isBedrockAttribute()) + return null; + + return new com.nukkitx.protocol.bedrock.data.Attribute(type.getBedrockIdentifier(), attribute.getMinimum(), attribute.getMaximum(), attribute.getValue(), attribute.getDefaultValue()); + } +}