From 1192f2a53a20354e7d2292a1c710d6bfe779b600 Mon Sep 17 00:00:00 2001 From: T00thpick1 Date: Tue, 23 Jul 2013 21:30:38 -0500 Subject: [PATCH] Add API to control scaled health. Adds BUKKIT-4590 This commit implements the ability to set the scale of hearts that the client renders. When the Packet44UpdateAttributes packet is sent, the max health attribute is replaced with a scaled version, to preserve the scaled health illusion clientside. In order to accurately display the scaled health for players, a true health is stored within CraftPlayer, and the datawatcher now stores the scaled health. The getHealth() method for players still returns their true health. Changed setHealth() within EntityLiving to appropriately handle health for instances of EntityPlayer. Inlined a call to setHealth(getMaxHealth()) within the EntityLiving constructor to work around CraftEntity instantiation. Additionally fixes the health values sent when eating food within FoodMetaData and ItemFood, which previously sent the unscaled health; this commit alters them to send the properly scaled health. Additionally fixes BUKKIT-4535, BUKKIT-4536, and BUKKIT-4127 --- .../net/minecraft/server/EntityLiving.java | 24 ++++++- .../net/minecraft/server/EntityPlayer.java | 10 ++- .../minecraft/server/EntityTrackerEntry.java | 10 +++ .../net/minecraft/server/FoodMetaData.java | 2 +- .../java/net/minecraft/server/ItemFood.java | 2 +- .../java/net/minecraft/server/PlayerList.java | 2 +- .../craftbukkit/entity/CraftPlayer.java | 67 +++++++++++++++++-- 7 files changed, 106 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java index 45b1410e89..c7012c514c 100644 --- a/src/main/java/net/minecraft/server/EntityLiving.java +++ b/src/main/java/net/minecraft/server/EntityLiving.java @@ -82,7 +82,8 @@ public abstract class EntityLiving extends Entity { public EntityLiving(World world) { super(world); this.ay(); - this.setHealth(this.getMaxHealth()); + // CraftBukkit - setHealth(getMaxHealth()) -> current - inlined to skip the instanceof check for EntityPlayers + this.datawatcher.watch(6, (float) this.getAttributeInstance(GenericAttributes.a).getValue()); this.m = true; this.aM = (float) (Math.random() + 1.0D) * 0.01F; this.setPosition(this.locX, this.locY, this.locZ); @@ -583,10 +584,31 @@ public abstract class EntityLiving extends Entity { } public final float getHealth() { + // CraftBukkit start - Scaled Health + if (this instanceof EntityPlayer) { + return (float) ((EntityPlayer) this).getBukkitEntity().getHealth(); + } + // CraftBukkit end return this.datawatcher.getFloat(6); } public void setHealth(float f) { + // CraftBukkit start - Scaled Health + if (this instanceof EntityPlayer) { + org.bukkit.craftbukkit.entity.CraftPlayer player = ((EntityPlayer) this).getBukkitEntity(); + // Squeeze + if (f < 0.0F) { + player.setRealHealth(0.0D); + } else if (f > player.getMaxHealth()) { + player.setRealHealth(player.getMaxHealth()); + } else { + player.setRealHealth(f); + } + + this.datawatcher.watch(6, Float.valueOf(player.getScaledHealth())); + return; + } + // CraftBukkit end this.datawatcher.watch(6, Float.valueOf(MathHelper.a(f, 0.0F, this.getMaxHealth()))); } diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java index 955b75c3f1..58e52179bd 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -51,6 +51,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public int newLevel = 0; public int newTotalExp = 0; public boolean keepLevel = false; + public double maxHealthCache; // CraftBukkit end public EntityPlayer(MinecraftServer minecraftserver, World world, String s, PlayerInteractManager playerinteractmanager) { @@ -84,6 +85,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { this.displayName = this.name; this.listName = this.name; // this.canPickUpLoot = true; TODO + this.maxHealthCache = this.getMaxHealth(); // CraftBukkit end } @@ -234,7 +236,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { if (this.getHealth() != this.bP || this.bQ != this.foodData.a() || this.foodData.e() == 0.0F != this.bR) { // CraftBukkit - Optionally scale health - this.playerConnection.sendPacket(new Packet8UpdateHealth(getBukkitEntity().getScaledHealth(), this.foodData.a(), this.foodData.e())); + this.playerConnection.sendPacket(new Packet8UpdateHealth(this.getBukkitEntity().getScaledHealth(), this.foodData.a(), this.foodData.e())); this.bP = this.getHealth(); this.bQ = this.foodData.a(); this.bR = this.foodData.e() == 0.0F; @@ -246,6 +248,12 @@ public class EntityPlayer extends EntityHuman implements ICrafting { this.world.getServer().getScoreboardManager().updateAllScoresForList(IScoreboardCriteria.f, this.getLocalizedName(), com.google.common.collect.ImmutableList.of(this)); } + // CraftBukkit start - Force max health updates + if (this.maxHealthCache != this.getMaxHealth()) { + this.getBukkitEntity().updateScaledHealth(); + } + // CraftBukkit end + if (this.expTotal != this.lastSentExp) { this.lastSentExp = this.expTotal; this.playerConnection.sendPacket(new Packet43SetExperience(this.exp, this.expTotal, this.expLevel)); diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java index 673c2042a5..e05296c731 100644 --- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java +++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java @@ -250,6 +250,11 @@ public class EntityTrackerEntry { Set set = attributemapserver.b(); if (!set.isEmpty()) { + // CraftBukkit start - Send scaled max health + if (this.tracker instanceof EntityPlayer) { + ((EntityPlayer) this.tracker).getBukkitEntity().injectScaledMaxHealth(set, false); + } + // CraftBukkit end this.broadcastIncludingSelf(new Packet44UpdateAttributes(this.tracker.id, set)); } @@ -321,6 +326,11 @@ public class EntityTrackerEntry { AttributeMapServer attributemapserver = (AttributeMapServer) ((EntityLiving) this.tracker).aW(); Collection collection = attributemapserver.c(); + // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health + if (this.tracker.id == entityplayer.id) { + ((EntityPlayer) this.tracker).getBukkitEntity().injectScaledMaxHealth(collection, false); + } + // CraftBukkit end if (!collection.isEmpty()) { entityplayer.playerConnection.sendPacket(new Packet44UpdateAttributes(this.tracker.id, collection)); } diff --git a/src/main/java/net/minecraft/server/FoodMetaData.java b/src/main/java/net/minecraft/server/FoodMetaData.java index 6e5bf831e9..37349f5cd4 100644 --- a/src/main/java/net/minecraft/server/FoodMetaData.java +++ b/src/main/java/net/minecraft/server/FoodMetaData.java @@ -39,7 +39,7 @@ public class FoodMetaData { this.foodLevel = event.getFoodLevel(); } - ((EntityPlayer) entityhuman).playerConnection.sendPacket(new Packet8UpdateHealth(entityhuman.getHealth(), this.foodLevel, this.saturationLevel)); + ((EntityPlayer) entityhuman).playerConnection.sendPacket(new Packet8UpdateHealth(((EntityPlayer) entityhuman).getBukkitEntity().getScaledHealth(), this.foodLevel, this.saturationLevel)); // CraftBukkit end } } diff --git a/src/main/java/net/minecraft/server/ItemFood.java b/src/main/java/net/minecraft/server/ItemFood.java index c0274094d2..78bbb59b58 100644 --- a/src/main/java/net/minecraft/server/ItemFood.java +++ b/src/main/java/net/minecraft/server/ItemFood.java @@ -36,7 +36,7 @@ public class ItemFood extends Item { entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, this.getSaturationModifier()); } - ((EntityPlayer) entityhuman).playerConnection.sendPacket(new Packet8UpdateHealth(entityhuman.getHealth(), entityhuman.getFoodData().foodLevel, entityhuman.getFoodData().saturationLevel)); + ((EntityPlayer) entityhuman).playerConnection.sendPacket(new Packet8UpdateHealth(((EntityPlayer) entityhuman).getBukkitEntity().getScaledHealth(), entityhuman.getFoodData().foodLevel, entityhuman.getFoodData().saturationLevel)); // CraftBukkit end world.makeSound(entityhuman, "random.burp", 0.5F, world.random.nextFloat() * 0.1F + 0.9F); diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java index f24ba954a2..0f17c90cce 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -981,7 +981,7 @@ public abstract class PlayerList { public void updateClient(EntityPlayer entityplayer) { entityplayer.updateInventory(entityplayer.defaultContainer); - entityplayer.triggerHealthUpdate(); + entityplayer.getBukkitEntity().updateScaledHealth(); // CraftBukkit - Update scaled health on respawn and worldchange entityplayer.playerConnection.sendPacket(new Packet16BlockItemSwitch(entityplayer.inventory.itemInHandIndex)); } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 977adff6e1..fd7385663a 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -7,6 +7,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -61,7 +62,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player { private final Set channels = new HashSet(); private final Map hiddenPlayers = new MapMaker().softValues().makeMap(); private int hash = 0; - private boolean scaledHealth; + private double health = 20; + private boolean scaledHealth = false; + private double healthScale = 20; public CraftPlayer(CraftServer server, EntityPlayer entity) { super(server, entity); @@ -973,6 +976,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void setMaxHealth(double amount) { super.setMaxHealth(amount); + this.health = Math.min(this.health, health); getHandle().triggerHealthUpdate(); } @@ -999,15 +1003,66 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.server.getScoreboardManager().setPlayerBoard(this, scoreboard); } + public void setHealthScale(double value) { + Validate.isTrue((float) value > 0F, "Must be greater than 0"); + healthScale = value; + scaledHealth = true; + updateScaledHealth(); + } + + public double getHealthScale() { + return healthScale; + } + + public void setHealthScaled(boolean scale) { + if (scaledHealth != (scaledHealth = scale)) { + updateScaledHealth(); + } + } + + public boolean isHealthScaled() { + return scaledHealth; + } + public float getScaledHealth() { - return (float) (this.scaledHealth ? getHealth() / getMaxHealth() * 20.0D : getHealth()); + return (float) (isHealthScaled() ? getHealth() * getHealthScale() / getMaxHealth() : getHealth()); } - public void setScaleHealth(boolean scale) { - this.scaledHealth = scale; + @Override + public double getHealth() { + return health; } - public boolean isScaledHealth() { - return this.scaledHealth; + public void setRealHealth(double health) { + this.health = health; + } + + public void updateScaledHealth() { + AttributeMapServer attributemapserver = (AttributeMapServer) getHandle().aW(); + Set set = attributemapserver.b(); + + injectScaledMaxHealth(set, true); + + getHandle().getDataWatcher().watch(6, (float) getScaledHealth()); + getHandle().playerConnection.sendPacket(new Packet8UpdateHealth(getScaledHealth(), getHandle().getFoodData().a(), getHandle().getFoodData().e())); + getHandle().playerConnection.sendPacket(new Packet44UpdateAttributes(getHandle().id, set)); + + set.clear(); + getHandle().maxHealthCache = getMaxHealth(); + } + + public void injectScaledMaxHealth(Collection collection, boolean force) { + if (!scaledHealth && !force) { + return; + } + for (Object genericInstance : collection) { + IAttribute attribute = ((AttributeInstance) genericInstance).a(); + if (attribute.a().equals("generic.maxHealth")) { + collection.remove(genericInstance); + break; + } + continue; + } + collection.add(new AttributeModifiable(getHandle().aW(), (new AttributeRanged("generic.maxHealth", scaledHealth ? healthScale : getMaxHealth(), 0.0D, Float.MAX_VALUE)).a("Max Health").a(true))); } }