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