Fixed crystal heart health not syncing to the client and implement health/destruction and animations for the crystal shards

This commit is contained in:
Sollace 2023-09-02 20:54:15 +01:00
parent e923882ebf
commit 543adefd6d
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
6 changed files with 164 additions and 62 deletions

View file

@ -1,5 +1,7 @@
package com.minelittlepony.unicopia.client.render.entity; package com.minelittlepony.unicopia.client.render.entity;
import java.util.List;
import com.minelittlepony.unicopia.entity.mob.CrystalShardsEntity; import com.minelittlepony.unicopia.entity.mob.CrystalShardsEntity;
import net.minecraft.client.model.Dilation; import net.minecraft.client.model.Dilation;
@ -12,12 +14,15 @@ import net.minecraft.client.model.TexturedModelData;
import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.entity.model.EntityModel; import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.math.MathHelper;
public class CrystalShardsEntityModel extends EntityModel<CrystalShardsEntity> { public class CrystalShardsEntityModel extends EntityModel<CrystalShardsEntity> {
private final ModelPart part; private final ModelPart part;
private final List<ModelPart> crystals;
public CrystalShardsEntityModel(ModelPart root) { public CrystalShardsEntityModel(ModelPart root) {
this.part = root; this.part = root;
this.crystals = List.of(part.getChild("west"), part.getChild("north"), part.getChild("south"), part.getChild("east"), part.getChild("primary"));
} }
public static TexturedModelData getTexturedModelData() { public static TexturedModelData getTexturedModelData() {
@ -33,6 +38,22 @@ public class CrystalShardsEntityModel extends EntityModel<CrystalShardsEntity> {
@Override @Override
public void setAngles(CrystalShardsEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) { public void setAngles(CrystalShardsEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
float offset = 0;
float amplitude = 0.02F;
for (ModelPart part : crystals) {
part.resetTransform();
if (entity.isShaking()) {
float animationTime = (entity.age + ++offset) * 122F;
float sin = MathHelper.sin(animationTime) * amplitude;
part.pitch += sin;
part.yaw += MathHelper.cos(animationTime) * amplitude;
part.roll += -sin;
}
}
} }
@Override @Override

View file

@ -9,8 +9,10 @@ import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.EntityRenderer; import net.minecraft.client.render.entity.EntityRenderer;
import net.minecraft.client.render.entity.EntityRendererFactory; import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.RotationAxis;
public class CrystalShardsEntityRenderer extends EntityRenderer<CrystalShardsEntity> { public class CrystalShardsEntityRenderer extends EntityRenderer<CrystalShardsEntity> {
@ -37,6 +39,11 @@ public class CrystalShardsEntityRenderer extends EntityRenderer<CrystalShardsEnt
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw)); matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
model.setAngles(entity, 0, 0, 0, 0, 0); model.setAngles(entity, 0, 0, 0, 0, 0);
int destructionStage = (int)(MathHelper.clamp(1F - (entity.getHealth() / entity.getMaxHealth()), 0F, 1F) * (ModelLoader.field_32983 - 1F));
vertices = FloatingArtefactEntityRenderer.getDestructionOverlayProvider(matrices, vertices, destructionStage);
model.render(matrices, vertices.getBuffer(model.getLayer(getTexture(entity))), light, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); model.render(matrices, vertices.getBuffer(model.getLayer(getTexture(entity))), light, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1);
matrices.pop(); matrices.pop();
super.render(entity, yaw, tickDelta, matrices, vertices, light); super.render(entity, yaw, tickDelta, matrices, vertices, light);

View file

@ -52,7 +52,7 @@ public class FloatingArtefactEntityRenderer extends EntityRenderer<FloatingArtef
matrices.translate(0, verticalOffset + variance * modelScaleY, 0); matrices.translate(0, verticalOffset + variance * modelScaleY, 0);
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(entity.getRotation(timeDelta))); matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(entity.getRotation(timeDelta)));
int destructionStage = (int)(MathHelper.clamp(1 - (entity.getHealth() / entity.getMaxHealth()), 0, 1) * (ModelLoader.field_32983 - 1)); int destructionStage = (int)(MathHelper.clamp(1F - (entity.getHealth() / entity.getMaxHealth()), 0F, 1F) * (ModelLoader.field_32983 - 1F));
itemRenderer.renderItem(stack, ModelTransformationMode.GROUND, false, matrices, getDestructionOverlayProvider(matrices, vertices, destructionStage), lightUv, OverlayTexture.DEFAULT_UV, model); itemRenderer.renderItem(stack, ModelTransformationMode.GROUND, false, matrices, getDestructionOverlayProvider(matrices, vertices, destructionStage), lightUv, OverlayTexture.DEFAULT_UV, model);

View file

@ -6,13 +6,10 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.entity.MagicImmune;
import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.UItems;
import net.minecraft.block.SideShapeType; import net.minecraft.block.SideShapeType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.data.DataTracker; import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.data.TrackedData; import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.entity.data.TrackedDataHandlerRegistry;
@ -27,16 +24,16 @@ import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World; import net.minecraft.world.World;
public class CrystalShardsEntity extends Entity implements MagicImmune { public class CrystalShardsEntity extends StationaryObjectEntity {
static final byte SHAKE = 1; static final byte SHAKE = 1;
static final int FULL_GROWTH_AGE = 25; static final int FULL_GROWTH_AGE = 25;
private static final Set<Direction> ALL_DIRECTIONS = Set.of(Direction.values()); private static final Set<Direction> ALL_DIRECTIONS = Set.of(Direction.values());
private static final TrackedData<Direction> ATTACHMENT_FACE = DataTracker.registerData(SombraEntity.class, TrackedDataHandlerRegistry.FACING); private static final TrackedData<Direction> ATTACHMENT_FACE = DataTracker.registerData(CrystalShardsEntity.class, TrackedDataHandlerRegistry.FACING);
private static final TrackedData<Integer> GROWTH = DataTracker.registerData(SombraEntity.class, TrackedDataHandlerRegistry.INTEGER); private static final TrackedData<Integer> GROWTH = DataTracker.registerData(CrystalShardsEntity.class, TrackedDataHandlerRegistry.INTEGER);
private static final TrackedData<Boolean> DECAYING = DataTracker.registerData(SombraEntity.class, TrackedDataHandlerRegistry.BOOLEAN); private static final TrackedData<Boolean> DECAYING = DataTracker.registerData(CrystalShardsEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
private static final TrackedData<Boolean> CORRUPT = DataTracker.registerData(SombraEntity.class, TrackedDataHandlerRegistry.BOOLEAN); private static final TrackedData<Boolean> CORRUPT = DataTracker.registerData(CrystalShardsEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
public static boolean infestBlock(ServerWorld world, BlockPos pos) { public static boolean infestBlock(ServerWorld world, BlockPos pos) {
if (world.isAir(pos) || !world.getFluidState(pos).isOf(Fluids.EMPTY)) { if (world.isAir(pos) || !world.getFluidState(pos).isOf(Fluids.EMPTY)) {
@ -89,12 +86,18 @@ public class CrystalShardsEntity extends Entity implements MagicImmune {
@Override @Override
protected void initDataTracker() { protected void initDataTracker() {
super.initDataTracker();
dataTracker.startTracking(ATTACHMENT_FACE, Direction.UP); dataTracker.startTracking(ATTACHMENT_FACE, Direction.UP);
dataTracker.startTracking(GROWTH, 0); dataTracker.startTracking(GROWTH, 0);
dataTracker.startTracking(DECAYING, false); dataTracker.startTracking(DECAYING, false);
dataTracker.startTracking(CORRUPT, false); dataTracker.startTracking(CORRUPT, false);
} }
@Override
public float getMaxHealth() {
return 15F;
}
public float getGrowth(float tickDelta) { public float getGrowth(float tickDelta) {
int age = getGrowth(); int age = getGrowth();
float lerped = MathHelper.clamp(MathHelper.lerp(tickDelta, prevAge, age), 0, FULL_GROWTH_AGE) / (float)FULL_GROWTH_AGE; float lerped = MathHelper.clamp(MathHelper.lerp(tickDelta, prevAge, age), 0, FULL_GROWTH_AGE) / (float)FULL_GROWTH_AGE;
@ -157,19 +160,22 @@ public class CrystalShardsEntity extends Entity implements MagicImmune {
super.tick(); super.tick();
if (getGrowth() < FULL_GROWTH_AGE) { if (ticksShaking > 0 || getGrowth() < FULL_GROWTH_AGE) {
playSound(USounds.Vanilla.BLOCK_AMETHYST_BLOCK_CHIME, 1, 1); if (age % random.nextBetween(2, 5) == 0) {
playSound(USounds.Vanilla.BLOCK_AMETHYST_BLOCK_HIT, 1,
1 - MathHelper.clamp(getGrowth(1), 0, 1) * 0.5F);
}
} }
if (isInvalid(getWorld(), getBlockPos(), getAttachmentFace())) { if (isDead() || isInvalid(getWorld(), getBlockPos(), getAttachmentFace())) {
kill(); kill();
} }
} }
@Override @Override
public boolean damage(DamageSource source, float amount) { protected void onHurt() {
getWorld().sendEntityStatus(this, SHAKE); getWorld().sendEntityStatus(this, SHAKE);
return super.damage(source, amount); ticksShaking = 10;
} }
@Override @Override
@ -185,7 +191,7 @@ public class CrystalShardsEntity extends Entity implements MagicImmune {
public void handleStatus(byte status) { public void handleStatus(byte status) {
switch (status) { switch (status) {
case SHAKE: case SHAKE:
ticksShaking = 30; ticksShaking = 10;
break; break;
default: default:
super.handleStatus(status); super.handleStatus(status);
@ -194,7 +200,8 @@ public class CrystalShardsEntity extends Entity implements MagicImmune {
@Override @Override
public void writeCustomDataToNbt(NbtCompound nbt) { public void writeCustomDataToNbt(NbtCompound nbt) {
nbt.putFloat("yaw", this.getYaw()); super.writeCustomDataToNbt(nbt);
nbt.putFloat("yaw", getYaw());
nbt.putInt("growth", getGrowth()); nbt.putInt("growth", getGrowth());
nbt.putString("face", getAttachmentFace().getName()); nbt.putString("face", getAttachmentFace().getName());
nbt.putBoolean("decaying", isDecaying()); nbt.putBoolean("decaying", isDecaying());
@ -203,6 +210,7 @@ public class CrystalShardsEntity extends Entity implements MagicImmune {
@Override @Override
public void readCustomDataFromNbt(NbtCompound nbt) { public void readCustomDataFromNbt(NbtCompound nbt) {
super.readCustomDataFromNbt(nbt);
setYaw(nbt.getFloat("yaw")); setYaw(nbt.getFloat("yaw"));
setGrowth(nbt.getInt("growth")); setGrowth(nbt.getInt("growth"));
setAttachmentFace(Direction.byName(nbt.getString("face"))); setAttachmentFace(Direction.byName(nbt.getString("face")));

View file

@ -3,12 +3,9 @@ package com.minelittlepony.unicopia.entity.mob;
import java.util.Optional; import java.util.Optional;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.entity.MagicImmune;
import com.minelittlepony.unicopia.entity.damage.UDamageSources;
import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.server.world.Altar; import com.minelittlepony.unicopia.server.world.Altar;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.data.DataTracker; import net.minecraft.entity.data.DataTracker;
@ -21,7 +18,7 @@ import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
public class FloatingArtefactEntity extends Entity implements UDamageSources, MagicImmune { public class FloatingArtefactEntity extends StationaryObjectEntity {
private static final TrackedData<ItemStack> ITEM = DataTracker.registerData(FloatingArtefactEntity.class, TrackedDataHandlerRegistry.ITEM_STACK); private static final TrackedData<ItemStack> ITEM = DataTracker.registerData(FloatingArtefactEntity.class, TrackedDataHandlerRegistry.ITEM_STACK);
private static final TrackedData<Byte> STATE = DataTracker.registerData(FloatingArtefactEntity.class, TrackedDataHandlerRegistry.BYTE); private static final TrackedData<Byte> STATE = DataTracker.registerData(FloatingArtefactEntity.class, TrackedDataHandlerRegistry.BYTE);
private static final TrackedData<Float> TARGET_ROTATION_SPEED = DataTracker.registerData(FloatingArtefactEntity.class, TrackedDataHandlerRegistry.FLOAT); private static final TrackedData<Float> TARGET_ROTATION_SPEED = DataTracker.registerData(FloatingArtefactEntity.class, TrackedDataHandlerRegistry.FLOAT);
@ -40,7 +37,6 @@ public class FloatingArtefactEntity extends Entity implements UDamageSources, Ma
private int boostDuration; private int boostDuration;
private float health;
private int ticksUntilRegen; private int ticksUntilRegen;
public final float positionSeed; public final float positionSeed;
@ -50,11 +46,11 @@ public class FloatingArtefactEntity extends Entity implements UDamageSources, Ma
super(entityType, world); super(entityType, world);
positionSeed = (float)(Math.random() * Math.PI * 2); positionSeed = (float)(Math.random() * Math.PI * 2);
health = getMaxHealth();
} }
@Override @Override
protected void initDataTracker() { protected void initDataTracker() {
super.initDataTracker();
dataTracker.startTracking(ITEM, ItemStack.EMPTY); dataTracker.startTracking(ITEM, ItemStack.EMPTY);
dataTracker.startTracking(STATE, (byte)0); dataTracker.startTracking(STATE, (byte)0);
dataTracker.startTracking(TARGET_ROTATION_SPEED, 1F); dataTracker.startTracking(TARGET_ROTATION_SPEED, 1F);
@ -93,16 +89,9 @@ public class FloatingArtefactEntity extends Entity implements UDamageSources, Ma
return MathHelper.lerp(tickDelta, prevRotationSpeed, rotationSpeed); return MathHelper.lerp(tickDelta, prevRotationSpeed, rotationSpeed);
} }
public int getMaxHealth() { @Override
return 20; public float getMaxHealth() {
} return 20F;
public void setHealth(float health) {
this.health = MathHelper.clamp(health, 0, getMaxHealth());
}
public float getHealth() {
return health;
} }
@Override @Override
@ -163,16 +152,17 @@ public class FloatingArtefactEntity extends Entity implements UDamageSources, Ma
@Override @Override
protected void readCustomDataFromNbt(NbtCompound compound) { protected void readCustomDataFromNbt(NbtCompound compound) {
super.readCustomDataFromNbt(compound);
setStack(ItemStack.fromNbt(compound.getCompound("Item"))); setStack(ItemStack.fromNbt(compound.getCompound("Item")));
setState(State.valueOf(compound.getInt("State"))); setState(State.valueOf(compound.getInt("State")));
setRotationSpeed(compound.getFloat("spin"), compound.getInt("spinDuration")); setRotationSpeed(compound.getFloat("spin"), compound.getInt("spinDuration"));
setHealth(compound.getFloat("health"));
ticksUntilRegen = compound.getInt("regen"); ticksUntilRegen = compound.getInt("regen");
altar = Altar.SERIALIZER.readOptional("altar", compound); altar = Altar.SERIALIZER.readOptional("altar", compound);
} }
@Override @Override
protected void writeCustomDataToNbt(NbtCompound compound) { protected void writeCustomDataToNbt(NbtCompound compound) {
super.writeCustomDataToNbt(compound);
ItemStack stack = getStack(); ItemStack stack = getStack();
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
compound.put("Item", stack.writeNbt(new NbtCompound())); compound.put("Item", stack.writeNbt(new NbtCompound()));
@ -180,46 +170,46 @@ public class FloatingArtefactEntity extends Entity implements UDamageSources, Ma
compound.putInt("State", getState().ordinal()); compound.putInt("State", getState().ordinal());
compound.putFloat("spin", getRotationSpeed()); compound.putFloat("spin", getRotationSpeed());
compound.putInt("spinDuration", boostDuration); compound.putInt("spinDuration", boostDuration);
compound.putFloat("health", getHealth());
compound.putInt("regen", ticksUntilRegen); compound.putInt("regen", ticksUntilRegen);
Altar.SERIALIZER.writeOptional("altar", compound, altar); Altar.SERIALIZER.writeOptional("altar", compound, altar);
} }
@Override @Override
public boolean damage(DamageSource damageSource, float amount) { public boolean damage(DamageSource source, float damage) {
if (getWorld().isClient || isInvulnerable()) { if (getWorld().isClient || isInvulnerable()) {
return false; return false;
} }
if (isInvulnerableTo(damageSource) || !getStack().getItem().damage(damageSource)) { if (isInvulnerableTo(source) || !getStack().getItem().damage(source)) {
return false; return false;
} }
if (damageSource.isSourceCreativePlayer()) { ticksUntilRegen = REGEN_PAUSE_TICKS;
health = 0;
} else { if (source.isSourceCreativePlayer()) {
health -= amount; damage = getHealth();
ticksUntilRegen = REGEN_PAUSE_TICKS;
} }
if (health <= 0) { return super.damage(source, damage);
remove(RemovalReason.KILLED); }
ItemStack stack = getStack(); @Override
protected void onKilled(DamageSource source) {
ItemStack stack = getStack();
if (altar.isEmpty()) { if (altar.isEmpty()) {
if (!(stack.getItem() instanceof Artifact) || ((Artifact)stack.getItem()).onArtifactDestroyed(this) != ActionResult.SUCCESS) { if (!(stack.getItem() instanceof Artifact) || ((Artifact)stack.getItem()).onArtifactDestroyed(this) != ActionResult.SUCCESS) {
if (!damageSource.isSourceCreativePlayer()) { if (!source.isSourceCreativePlayer()) {
dropStack(stack); dropStack(stack);
}
} }
} }
} else {
playSound(USounds.ITEM_ICARUS_WINGS_WARN, 1, 1);
} }
}
return false; @Override
protected void onHurt() {
playSound(USounds.ITEM_ICARUS_WINGS_WARN, 1, 1);
} }
@Override @Override
@ -228,16 +218,6 @@ public class FloatingArtefactEntity extends Entity implements UDamageSources, Ma
altar.ifPresent(altar -> altar.tearDown(this, getWorld())); altar.ifPresent(altar -> altar.tearDown(this, getWorld()));
} }
@Override
public boolean canHit() {
return true;
}
@Override
public World asWorld() {
return getWorld();
}
public enum State { public enum State {
INITIALISING, INITIALISING,
RUNNING, RUNNING,

View file

@ -0,0 +1,86 @@
package com.minelittlepony.unicopia.entity.mob;
import com.minelittlepony.unicopia.entity.MagicImmune;
import com.minelittlepony.unicopia.entity.damage.UDamageSources;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
public abstract class StationaryObjectEntity extends Entity implements UDamageSources, MagicImmune {
private static final TrackedData<Float> HEALTH = DataTracker.registerData(StationaryObjectEntity.class, TrackedDataHandlerRegistry.FLOAT);
public StationaryObjectEntity(EntityType<?> entityType, World world) {
super(entityType, world);
}
@Override
protected void initDataTracker() {
dataTracker.startTracking(HEALTH, getMaxHealth());
}
public abstract float getMaxHealth();
public final float setHealth(float health) {
health = MathHelper.clamp(health, 0, getMaxHealth());
dataTracker.set(HEALTH, health);
return health;
}
public final float getHealth() {
return dataTracker.get(HEALTH);
}
public final boolean isDead() {
return isRemoved() || getHealth() <= 0;
}
@Override
public boolean damage(DamageSource source, float damage) {
if (!isDead()) {
if (setHealth(getHealth() - damage) <= 0) {
kill();
onKilled(source);
} else {
onHurt();
}
}
return false;
}
protected void onKilled(DamageSource source) {
}
protected void onHurt() {
}
@Override
protected void readCustomDataFromNbt(NbtCompound compound) {
if (compound.contains("health", NbtElement.FLOAT_TYPE)) {
setHealth(compound.getFloat("health"));
}
}
@Override
protected void writeCustomDataToNbt(NbtCompound compound) {
compound.putFloat("health", getHealth());
}
@Override
public final boolean canHit() {
return true;
}
@Override
public final World asWorld() {
return getWorld();
}
}