Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix health scaling again #3778

Merged
merged 1 commit into from
Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,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,82 +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();

this.impl$cachedMaxHealthAttribute = new AttributeInstance(Attributes.MAX_HEALTH, i -> {});
this.impl$cachedMaxHealthAttribute.setBaseValue(this.impl$healthScale);

if (!modifiers.isEmpty()) {
modifiers.forEach(attribute::addTransientModifier);
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;
}

}