Skip to content

Commit

Permalink
fix health scaling (#3778)
Browse files Browse the repository at this point in the history
  • Loading branch information
vectrixdevelops authored Jan 8, 2023
1 parent b4e5748 commit a060038
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public interface ServerPlayerEntityHealthScaleBridge {

void bridge$resetHealthScale();

double bridge$getHealthScale();
Double bridge$getHealthScale();

float bridge$getInternalScaledHealth();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import org.spongepowered.common.bridge.data.SpongeDataHolderBridge;
import org.spongepowered.common.bridge.server.level.ServerPlayerEntityHealthScaleBridge;
import org.spongepowered.common.mixin.core.world.entity.player.PlayerMixin;
import org.spongepowered.common.util.Constants;

import java.util.Collection;
import java.util.Iterator;
Expand All @@ -58,8 +57,8 @@ public abstract class ServerPlayerMixin_HealthScale extends PlayerMixin implemen
@Shadow public ServerGamePacketListenerImpl connection;
// @formatter:on

private double impl$healthScale = Constants.Entity.Player.DEFAULT_HEALTH_SCALE;
private float impl$cachedModifiedHealth = -1;
private AttributeInstance impl$cachedMaxHealthAttribute = null;
private Double impl$healthScale = null;

@Inject(method = "doTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;getArmorValue()I", ordinal = 1))
private void updateHealthPriorToArmor(final CallbackInfo ci) {
Expand All @@ -78,7 +77,6 @@ private void updateHealthPriorToArmor(final CallbackInfo ci) {
}

this.impl$healthScale = scale;
this.impl$cachedModifiedHealth = -1;
this.lastSentHealth = -1.0F;

((SpongeDataHolderBridge) this).bridge$offer(Keys.HEALTH_SCALE, scale);
Expand All @@ -89,8 +87,7 @@ private void updateHealthPriorToArmor(final CallbackInfo ci) {

@Override
public void bridge$resetHealthScale() {
this.impl$healthScale = Constants.Entity.Player.DEFAULT_HEALTH_SCALE;
this.impl$cachedModifiedHealth = -1;
this.impl$healthScale = null;
this.lastSentHealth = -1.0F;

((SpongeDataHolderBridge) this).bridge$remove(Keys.HEALTH_SCALE);
Expand All @@ -110,80 +107,89 @@ private void updateHealthPriorToArmor(final CallbackInfo ci) {
final FoodData foodData = this.shadow$getFoodData();
this.connection.send(new ClientboundSetHealthPacket(this.bridge$getInternalScaledHealth(), foodData.getFoodLevel(), foodData.getSaturationLevel()));
this.connection.send(new ClientboundUpdateAttributesPacket(this.shadow$getId(), dirtyInstances));

// Reset the dirty instances since they've now been manually updated on the client.
dirtyInstances.clear();

// Clear the cached value, so it doesn't carry over to future calls to getInternalScaledHealth.
this.impl$cachedMaxHealthAttribute = null;
}

@Override
public void bridge$injectScaledHealth(final Collection<AttributeInstance> set) {
// We need to remove the existing attribute instance for max health, since it's not always going to be the
// same as SharedMonsterAttributes.MAX_HEALTH
@Nullable Collection<AttributeModifier> modifiers = null;
@Nullable AttributeInstance attribute = null;
boolean foundMax = false; // Sometimes the max health isn't modified and no longer dirty
for (final Iterator<AttributeInstance> iter = set.iterator(); iter.hasNext(); ) {
final AttributeInstance dirtyInstance = iter.next();
if ("attribute.name.generic.maxHealth".equals(dirtyInstance.getAttribute().getDescriptionId())) {
foundMax = true;
modifiers = dirtyInstance.getModifiers();
attribute = dirtyInstance;
iter.remove();
break;
}
}

if (!foundMax) {
// Means we didn't find the max health attribute and need to fetch the modifiers from
// the cached map because it wasn't marked dirty for some reason
modifiers = this.shadow$getAttribute(Attributes.MAX_HEALTH).getModifiers();
attribute = this.shadow$getAttribute(Attributes.MAX_HEALTH);
}

final AttributeInstance attribute = new AttributeInstance(Attributes.MAX_HEALTH, i -> {});
if (this.bridge$isHealthScaled()) {
attribute.setBaseValue(this.impl$healthScale);
}
final Collection<AttributeModifier> modifiers = attribute.getModifiers();

if (!modifiers.isEmpty()) {
modifiers.forEach(attribute::addTransientModifier);
this.impl$cachedMaxHealthAttribute = new AttributeInstance(Attributes.MAX_HEALTH, i -> {});
this.impl$cachedMaxHealthAttribute.setBaseValue(this.impl$healthScale);

if (!modifiers.isEmpty()) {
modifiers.forEach(this.impl$cachedMaxHealthAttribute::addTransientModifier);
}
} else {
this.impl$cachedMaxHealthAttribute = attribute;
}
set.add(attribute);

set.add(this.impl$cachedMaxHealthAttribute);
}

@Override
public double bridge$getHealthScale() {
public Double bridge$getHealthScale() {
return this.impl$healthScale;
}

@Override
public float bridge$getInternalScaledHealth() {
if (!this.bridge$isHealthScaled()) {
return this.shadow$getHealth();
}
if (this.impl$cachedModifiedHealth == -1) {
// Because attribute modifiers from mods can add onto health and multiply health, we
// need to replicate what the mod may be trying to represent, regardless whether the health scale
// says to show only x hearts.
final AttributeInstance maxAttribute = this.shadow$getAttribute(Attributes.MAX_HEALTH);
double modifiedScale = this.impl$healthScale;
// Apply additive modifiers
for (final AttributeModifier modifier : maxAttribute.getModifiers(AttributeModifier.Operation.ADDITION)) {
modifiedScale += modifier.getAmount();
}

for (final AttributeModifier modifier : maxAttribute.getModifiers(AttributeModifier.Operation.MULTIPLY_BASE)) {
modifiedScale += modifiedScale * modifier.getAmount();
}
float maximumHealth = this.shadow$getMaxHealth();
float healthScale = maximumHealth;

for (final AttributeModifier modifier : maxAttribute.getModifiers(AttributeModifier.Operation.MULTIPLY_TOTAL)) {
modifiedScale *= 1.0D + modifier.getAmount();
// Attribute modifiers from mods can add onto health and multiply health, we need to replicate
// what the mod may be trying to represent, regardless whether the health scale says to show
// only x hearts.
if (this.bridge$isHealthScaled()) {
final AttributeInstance attribute;
if (this.impl$cachedMaxHealthAttribute == null) {
final Collection<AttributeModifier> modifiers = this.shadow$getAttribute(Attributes.MAX_HEALTH).getModifiers();
attribute = new AttributeInstance(Attributes.MAX_HEALTH, i -> {});

attribute.setBaseValue(this.impl$healthScale);

if (!modifiers.isEmpty()) {
modifiers.forEach(attribute::addTransientModifier);
}
} else {
attribute = this.impl$cachedMaxHealthAttribute;
}

this.impl$cachedModifiedHealth = (float) modifiedScale;
healthScale = (float) attribute.getValue();
}
return (this.shadow$getHealth() / this.shadow$getMaxHealth()) * this.impl$cachedModifiedHealth;

return (this.shadow$getHealth() / maximumHealth) * healthScale;
}

@Override
public boolean bridge$isHealthScaled() {
return this.impl$healthScale != Constants.Entity.Player.DEFAULT_HEALTH_SCALE;
return this.impl$healthScale != null;
}

}

0 comments on commit a060038

Please sign in to comment.