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
This commit is contained in:
T00thpick1 2013-07-23 21:30:38 -05:00 committed by Wesley Wolfe
parent 4ad3cdd4b5
commit 1192f2a53a
7 changed files with 106 additions and 11 deletions

View file

@ -82,7 +82,8 @@ public abstract class EntityLiving extends Entity {
public EntityLiving(World world) { public EntityLiving(World world) {
super(world); super(world);
this.ay(); 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.m = true;
this.aM = (float) (Math.random() + 1.0D) * 0.01F; this.aM = (float) (Math.random() + 1.0D) * 0.01F;
this.setPosition(this.locX, this.locY, this.locZ); this.setPosition(this.locX, this.locY, this.locZ);
@ -583,10 +584,31 @@ public abstract class EntityLiving extends Entity {
} }
public final float getHealth() { 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); return this.datawatcher.getFloat(6);
} }
public void setHealth(float f) { 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()))); this.datawatcher.watch(6, Float.valueOf(MathHelper.a(f, 0.0F, this.getMaxHealth())));
} }

View file

@ -51,6 +51,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
public int newLevel = 0; public int newLevel = 0;
public int newTotalExp = 0; public int newTotalExp = 0;
public boolean keepLevel = false; public boolean keepLevel = false;
public double maxHealthCache;
// CraftBukkit end // CraftBukkit end
public EntityPlayer(MinecraftServer minecraftserver, World world, String s, PlayerInteractManager playerinteractmanager) { 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.displayName = this.name;
this.listName = this.name; this.listName = this.name;
// this.canPickUpLoot = true; TODO // this.canPickUpLoot = true; TODO
this.maxHealthCache = this.getMaxHealth();
// CraftBukkit end // 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) { if (this.getHealth() != this.bP || this.bQ != this.foodData.a() || this.foodData.e() == 0.0F != this.bR) {
// CraftBukkit - Optionally scale health // 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.bP = this.getHealth();
this.bQ = this.foodData.a(); this.bQ = this.foodData.a();
this.bR = this.foodData.e() == 0.0F; 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)); 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) { if (this.expTotal != this.lastSentExp) {
this.lastSentExp = this.expTotal; this.lastSentExp = this.expTotal;
this.playerConnection.sendPacket(new Packet43SetExperience(this.exp, this.expTotal, this.expLevel)); this.playerConnection.sendPacket(new Packet43SetExperience(this.exp, this.expTotal, this.expLevel));

View file

@ -250,6 +250,11 @@ public class EntityTrackerEntry {
Set set = attributemapserver.b(); Set set = attributemapserver.b();
if (!set.isEmpty()) { 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)); this.broadcastIncludingSelf(new Packet44UpdateAttributes(this.tracker.id, set));
} }
@ -321,6 +326,11 @@ public class EntityTrackerEntry {
AttributeMapServer attributemapserver = (AttributeMapServer) ((EntityLiving) this.tracker).aW(); AttributeMapServer attributemapserver = (AttributeMapServer) ((EntityLiving) this.tracker).aW();
Collection collection = attributemapserver.c(); 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()) { if (!collection.isEmpty()) {
entityplayer.playerConnection.sendPacket(new Packet44UpdateAttributes(this.tracker.id, collection)); entityplayer.playerConnection.sendPacket(new Packet44UpdateAttributes(this.tracker.id, collection));
} }

View file

@ -39,7 +39,7 @@ public class FoodMetaData {
this.foodLevel = event.getFoodLevel(); 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 // CraftBukkit end
} }
} }

View file

@ -36,7 +36,7 @@ public class ItemFood extends Item {
entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, this.getSaturationModifier()); 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 // CraftBukkit end
world.makeSound(entityhuman, "random.burp", 0.5F, world.random.nextFloat() * 0.1F + 0.9F); world.makeSound(entityhuman, "random.burp", 0.5F, world.random.nextFloat() * 0.1F + 0.9F);

View file

@ -981,7 +981,7 @@ public abstract class PlayerList {
public void updateClient(EntityPlayer entityplayer) { public void updateClient(EntityPlayer entityplayer) {
entityplayer.updateInventory(entityplayer.defaultContainer); 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)); entityplayer.playerConnection.sendPacket(new Packet16BlockItemSwitch(entityplayer.inventory.itemInHandIndex));
} }

View file

@ -7,6 +7,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@ -61,7 +62,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
private final Set<String> channels = new HashSet<String>(); private final Set<String> channels = new HashSet<String>();
private final Map<String, Player> hiddenPlayers = new MapMaker().softValues().makeMap(); private final Map<String, Player> hiddenPlayers = new MapMaker().softValues().makeMap();
private int hash = 0; 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) { public CraftPlayer(CraftServer server, EntityPlayer entity) {
super(server, entity); super(server, entity);
@ -973,6 +976,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@Override @Override
public void setMaxHealth(double amount) { public void setMaxHealth(double amount) {
super.setMaxHealth(amount); super.setMaxHealth(amount);
this.health = Math.min(this.health, health);
getHandle().triggerHealthUpdate(); getHandle().triggerHealthUpdate();
} }
@ -999,15 +1003,66 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
this.server.getScoreboardManager().setPlayerBoard(this, scoreboard); 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() { public float getScaledHealth() {
return (float) (this.scaledHealth ? getHealth() / getMaxHealth() * 20.0D : getHealth()); return (float) (isHealthScaled() ? getHealth() * getHealthScale() / getMaxHealth() : getHealth());
} }
public void setScaleHealth(boolean scale) { @Override
this.scaledHealth = scale; public double getHealth() {
return health;
} }
public boolean isScaledHealth() { public void setRealHealth(double health) {
return this.scaledHealth; 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)));
} }
} }