Add new chest type

This commit is contained in:
Sollace 2024-04-18 21:09:15 +01:00
parent 701c08a1bd
commit f68be46b0c
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
16 changed files with 671 additions and 34 deletions

File diff suppressed because one or more lines are too long

53
assets/models/mimic.java Normal file
View file

@ -0,0 +1,53 @@
// Made with Blockbench 4.9.4
// Exported for Minecraft version 1.17+ for Yarn
// Paste this class into your mod and generate all required imports
public class mimic extends EntityModel<Entity> {
private final ModelPart lid;
private final ModelPart tongue_r1;
private final ModelPart upper_teeth;
private final ModelPart cube_r1;
private final ModelPart lower_teeth;
private final ModelPart cube_r2;
public mimic(ModelPart root) {
this.lid = root.getChild("lid");
this.lower_teeth = root.getChild("lower_teeth");
}
public static TexturedModelData getTexturedModelData() {
ModelData modelData = new ModelData();
ModelPartData modelPartData = modelData.getRoot();
ModelPartData lid = modelPartData.addChild("lid", ModelPartBuilder.create(), ModelTransform.of(0.0F, 17.0F, -7.0F, -0.829F, 0.0F, -3.1416F));
ModelPartData tongue_r1 = lid.addChild("tongue_r1", ModelPartBuilder.create().uv(11, 34).cuboid(-3.0F, -10.0F, 1.0F, 6.0F, 1.0F, 8.0F, new Dilation(0.0F)), ModelTransform.of(0.0F, 7.0F, 7.0F, 0.5236F, 0.0F, 0.0F));
ModelPartData upper_teeth = lid.addChild("upper_teeth", ModelPartBuilder.create().uv(0, 0).cuboid(-1.0F, -8.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-4.0F, -8.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(2.0F, -8.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)), ModelTransform.pivot(0.0F, 7.0F, 7.0F));
ModelPartData cube_r1 = upper_teeth.addChild("cube_r1", ModelPartBuilder.create().uv(0, 0).cuboid(-6.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-9.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-12.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-6.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-9.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-12.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)), ModelTransform.of(0.0F, -7.0F, -7.0F, 0.0F, 1.5708F, 0.0F));
ModelPartData lower_teeth = modelPartData.addChild("lower_teeth", ModelPartBuilder.create().uv(0, 0).cuboid(-1.0F, -1.0F, 12.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-4.0F, -1.0F, 12.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(2.0F, -1.0F, 12.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)), ModelTransform.pivot(0.0F, 16.0F, -7.0F));
ModelPartData cube_r2 = lower_teeth.addChild("cube_r2", ModelPartBuilder.create().uv(0, 0).cuboid(-6.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-9.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-12.0F, -1.0F, -6.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-6.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-9.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-12.0F, -1.0F, 5.0F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)), ModelTransform.of(0.0F, 0.0F, 0.0F, 0.0F, 1.5708F, 0.0F));
return TexturedModelData.of(modelData, 64, 64);
}
@Override
public void setAngles(Entity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
}
@Override
public void render(MatrixStack matrices, VertexConsumer vertexConsumer, int light, int overlay, float red, float green, float blue, float alpha) {
lid.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
lower_teeth.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
}
}

View file

@ -112,6 +112,7 @@ public interface URenderers {
EntityRendererRegistry.register(UEntities.TENTACLE, TentacleEntityRenderer::new);
EntityRendererRegistry.register(UEntities.IGNOMINIOUS_BULB, IgnominiousBulbEntityRenderer::new);
EntityRendererRegistry.register(UEntities.SPECTER, EmptyEntityRenderer::new);
EntityRendererRegistry.register(UEntities.MIMIC, MimicEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.FANCY_BED, CloudBedBlockEntityRenderer::new);

View file

@ -7,7 +7,8 @@ import com.minelittlepony.unicopia.compat.pehkui.PehkUtil;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.behaviour.Disguise;
import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance;
import com.minelittlepony.unicopia.entity.behaviour.FallingBlockBehaviour;
import com.minelittlepony.unicopia.mixin.MixinBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient;
@ -86,7 +87,7 @@ class EntityDisguiseRenderer {
if (blockEntity != null) {
BlockEntityRenderer<BlockEntity> r = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(blockEntity);
if (r != null) {
((FallingBlockBehaviour.Positioned)blockEntity).setPos(e.getBlockPos());
((MixinBlockEntity)blockEntity).setPos(e.getBlockPos());
blockEntity.setWorld(e.getWorld());
matrices.push();

View file

@ -59,7 +59,7 @@ public class CloudChestBlockEntityRenderer extends ChestBlockEntityRenderer<Ches
matrices.pop();
}
private DoubleBlockProperties.PropertySource<? extends ChestBlockEntity> getProperties(BlockState state, ChestBlockEntity entity) {
public static DoubleBlockProperties.PropertySource<? extends ChestBlockEntity> getProperties(BlockState state, ChestBlockEntity entity) {
return entity.getWorld() != null
? ((AbstractChestBlock<?>)state.getBlock()).getBlockEntitySource(state, entity.getWorld(), entity.getPos(), true)
: DoubleBlockProperties.PropertyRetriever::getFallback;

View file

@ -9,10 +9,8 @@ import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.EntityRenderer;
import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis;
public class CrystalShardsEntityRenderer extends EntityRenderer<CrystalShardsEntity> {
@ -30,6 +28,8 @@ public class CrystalShardsEntityRenderer extends EntityRenderer<CrystalShardsEnt
@Override
public void render(CrystalShardsEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) {
vertices = FloatingArtefactEntityRenderer.getDestructionOverlayProvider(matrices, vertices, 4, FloatingArtefactEntityRenderer.getDestructionStage(entity));
matrices.push();
matrices.multiply(entity.getAttachmentFace().getRotationQuaternion());
matrices.scale(-1, -1, 1);
@ -39,11 +39,6 @@ public class CrystalShardsEntityRenderer extends EntityRenderer<CrystalShardsEnt
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
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);
matrices.pop();
super.render(entity, yaw, tickDelta, matrices, vertices, light);

View file

@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.client.render.entity;
import com.minelittlepony.unicopia.client.render.RenderLayers;
import com.minelittlepony.unicopia.entity.mob.FloatingArtefactEntity;
import com.minelittlepony.unicopia.entity.mob.StationaryObjectEntity;
import com.minelittlepony.unicopia.item.UItems;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.OverlayTexture;
@ -15,6 +16,7 @@ import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.PlayerScreenHandler;
import net.minecraft.util.Identifier;
@ -52,9 +54,7 @@ public class FloatingArtefactEntityRenderer extends EntityRenderer<FloatingArtef
matrices.translate(0, verticalOffset + variance * modelScaleY, 0);
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(entity.getRotation(timeDelta)));
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, 4F, getDestructionStage(entity)), lightUv, OverlayTexture.DEFAULT_UV, model);
matrices.pop();
@ -66,16 +66,24 @@ public class FloatingArtefactEntityRenderer extends EntityRenderer<FloatingArtef
return PlayerScreenHandler.BLOCK_ATLAS_TEXTURE;
}
static VertexConsumerProvider getDestructionOverlayProvider(MatrixStack matrices, VertexConsumerProvider vertices, int stage) {
static int getDestructionStage(StationaryObjectEntity entity) {
return (int)(MathHelper.clamp(1F - (entity.getHealth() / entity.getMaxHealth()), 0F, 1F) * (ModelLoader.field_32983 - 1F));
}
static int getDestructionStage(LivingEntity entity) {
return (int)(MathHelper.clamp(1F - (entity.getHealth() / entity.getMaxHealth()), 0F, 1F) * (ModelLoader.field_32983 - 1F));
}
static VertexConsumerProvider getDestructionOverlayProvider(MatrixStack matrices, VertexConsumerProvider vertices, float scale, int stage) {
if (stage <= 0) {
return vertices;
}
final MatrixStack.Entry entry = matrices.peek();
final OverlayVertexConsumer destructionOverlay = new OverlayVertexConsumer(
MinecraftClient.getInstance().getBufferBuilders().getEffectVertexConsumers().getBuffer(RenderLayers.getCrumbling(stage)),
MinecraftClient.getInstance().getBufferBuilders().getEffectVertexConsumers().getBuffer(RenderLayers.getCrumbling(MathHelper.clamp(stage, 0, ModelLoader.field_32983 - 1))),
entry.getPositionMatrix(),
entry.getNormalMatrix(),
4F
scale
);
return layer -> layer.hasCrumbling() ? VertexConsumers.union(destructionOverlay, vertices.getBuffer(layer)) : vertices.getBuffer(layer);
}

View file

@ -0,0 +1,143 @@
package com.minelittlepony.unicopia.client.render.entity;
import com.minelittlepony.unicopia.entity.mob.MimicEntity;
import net.minecraft.block.ChestBlock;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.ChestBlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.model.Dilation;
import net.minecraft.client.model.ModelData;
import net.minecraft.client.model.ModelPart;
import net.minecraft.client.model.ModelPartBuilder;
import net.minecraft.client.model.ModelPartData;
import net.minecraft.client.model.ModelTransform;
import net.minecraft.client.model.TexturedModelData;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.*;
import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis;
public class MimicEntityRenderer extends MobEntityRenderer<MimicEntity, MimicEntityRenderer.MimicModel> {
private static final Identifier TEXTURE = new Identifier("textures/entity/chest/normal.png");
public MimicEntityRenderer(EntityRendererFactory.Context context) {
super(context, new MimicModel(MimicModel.getTexturedModelData().createModel()), 0);
addFeature(new ChestFeature(this));
}
@Override
public void render(MimicEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) {
super.render(entity, yaw, tickDelta, matrices,
FloatingArtefactEntityRenderer.getDestructionOverlayProvider(
matrices,
vertices,
1,
FloatingArtefactEntityRenderer.getDestructionStage(entity)
), light);
}
@Override
public Identifier getTexture(MimicEntity entity) {
return TEXTURE;
}
@Override
protected float getLyingAngle(MimicEntity entity) {
return 0;
}
static class ChestFeature extends FeatureRenderer<MimicEntity, MimicModel> {
public ChestFeature(FeatureRendererContext<MimicEntity, MimicModel> context) {
super(context);
}
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, MimicEntity entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) {
BlockEntity tileData = entity.getChestData();
if (tileData != null) {
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(180));
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-entity.getPitch(tickDelta)));
matrices.push();
matrices.translate(-0.5, -1.5, -0.5);
MinecraftClient.getInstance().getBlockEntityRenderDispatcher().render(tileData, tickDelta, matrices, vertexConsumers);
matrices.pop();
}
}
}
static class MimicModel extends EntityModel<MimicEntity> {
private ModelPart part;
private ModelPart lid;
public MimicModel(ModelPart part) {
this.part = part;
this.lid = part.getChild("lid");
}
public static TexturedModelData getTexturedModelData() {
ModelData data = new ModelData();
ModelPartData root = data.getRoot();
ModelPartData lid = root.addChild("lid", ModelPartBuilder.create(), ModelTransform.of(0, 17, -7, -0.829F, 0, -3.1416F));
lid.addChild("tongue", ModelPartBuilder.create()
.uv(11, 34).cuboid(-3, -11, 1, 6, 1, 8, Dilation.NONE), ModelTransform.of(0, 6, 9, 0.8F, 0, 0));
lid.addChild("upper_teeth", ModelPartBuilder.create()
.uv(0, 0).cuboid(-1, -8, 5, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-4, -8, 5, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(2, -8, 5, 2, 4, 1, Dilation.NONE), ModelTransform.pivot(0, 6, 9))
.addChild("cube_r1", ModelPartBuilder.create()
.uv(0, 0).cuboid(-6, -1, -6, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-9, -1, -6, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-12, -1, -6, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-6, -1, 5, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-9, -1, 5, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-12, -1, 5, 2, 4, 1, Dilation.NONE), ModelTransform.of(0, -7, -7, 0, 1.5708F, 0));
root.addChild("lower_teeth", ModelPartBuilder.create()
.uv(0, 0).cuboid(-1, -1, 12, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-4, -1, 12, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(2, -1, 12, 2, 4, 1, Dilation.NONE), ModelTransform.pivot(0, 13, -7))
.addChild("cube_r2", ModelPartBuilder.create()
.uv(0, 0).cuboid(-6, -1, -6, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-9, -1, -6, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-12, -1, -6, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-6, -1, 5, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-9, -1, 5, 2, 4, 1, Dilation.NONE)
.uv(0, 0).cuboid(-12, -1, 5, 2, 4, 1, Dilation.NONE), ModelTransform.of(0, 0, 0, 0, 1.5708F, 0));
return TexturedModelData.of(data, 64, 64);
}
@Override
public void animateModel(MimicEntity entity, float limbAngle, float limbDistance, float tickDelta) {
var data = getTexturedModelData();
part = data.createModel();
lid = part.getChild("lid");
ChestBlockEntity tileData = entity.getChestData();
if (tileData != null) {
var properties = CloudChestBlockEntityRenderer.getProperties(tileData.getCachedState(), tileData);
float progress = 1 - (float)Math.pow(1 - properties.apply(ChestBlock.getAnimationProgressRetriever(tileData)).get(tickDelta), 3);
lid.pitch = -(progress * 1.5707964f);
} else {
lid.pitch = 0;
}
part.yaw = MathHelper.RADIANS_PER_DEGREE * 180;
part.pitch = -entity.getPitch(tickDelta) * MathHelper.RADIANS_PER_DEGREE;
}
@Override
public void setAngles(MimicEntity entity, float limbAngle, float limbDistance, float animationProgress, float headYaw, float headPitch) {
}
@Override
public void render(MatrixStack matrices, VertexConsumer vertices, int light, int overlay, float red, float green, float blue, float alpha) {
if (lid.pitch != 0) {
part.render(matrices, vertices, light, overlay);
}
}
}
}

View file

@ -6,6 +6,7 @@ import java.util.Optional;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.mixin.MixinBlockEntity;
import com.minelittlepony.unicopia.mixin.MixinFallingBlock;
import com.minelittlepony.unicopia.mixin.MixinFallingBlockEntity;
import com.minelittlepony.unicopia.util.Tickable;
@ -25,7 +26,6 @@ import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.state.property.Properties;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
@ -118,13 +118,9 @@ public class FallingBlockBehaviour extends EntityBehaviour<FallingBlockEntity> {
}
be.setWorld(entity.getWorld());
((Positioned)be).setPos(entity.getBlockPos());
((MixinBlockEntity)be).setPos(entity.getBlockPos());
ceb.tick();
be.setWorld(null);
}
}
public interface Positioned {
void setPos(BlockPos pos);
}
}

View file

@ -0,0 +1,361 @@
package com.minelittlepony.unicopia.entity.mob;
import java.util.HashSet;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.mixin.MixinBlockEntity;
import com.minelittlepony.unicopia.util.InventoryUtil;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.ChestBlock;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.ChestBlockEntity;
import net.minecraft.block.enums.ChestType;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.goal.MeleeAttackGoal;
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.entity.mob.PathAwareEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.loot.LootTables;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtOps;
import net.minecraft.screen.GenericContainerScreenHandler;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.ItemScatterer;
import net.minecraft.util.TypedActionResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
public class MimicEntity extends PathAwareEntity {
static final byte OPEN_MOUTH = (byte)200;
static final byte CLOSE_MOUTH = (byte)201;
static final TrackedData<NbtCompound> CHEST_DATA = DataTracker.registerData(MimicEntity.class, TrackedDataHandlerRegistry.NBT_COMPOUND);
static final TrackedData<Boolean> MOUTH_OPEN = DataTracker.registerData(MimicEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
@Nullable
private ChestBlockEntity chestData;
private int openTicks;
private final Set<PlayerEntity> observingPlayers = new HashSet<>();
@SuppressWarnings("deprecation")
public static TypedActionResult<MimicEntity> spawnFromChest(World world, BlockPos pos, PlayerEntity player) {
if (world.getBlockState(pos).isOf(Blocks.CHEST) && world.getBlockEntity(pos) instanceof ChestBlockEntity be) {
int difficulty = world.getDifficulty().ordinal() - 1;
if (difficulty < 0) {
return TypedActionResult.pass(null);
}
float spawnChance = (difficulty / 3F) * 0.25F;
float roll = world.random.nextFloat();
System.out.println("Roll mimic: " + roll + " < " + spawnChance);
if (roll >= spawnChance) {
return TypedActionResult.pass(null);
}
BlockState state = be.getCachedState();
if (state.getOrEmpty(ChestBlock.CHEST_TYPE).orElse(ChestType.SINGLE) != ChestType.SINGLE) {
return TypedActionResult.fail(null);
}
world.removeBlockEntity(pos);
world.setBlockState(pos, Blocks.AIR.getDefaultState());
MimicEntity mimic = UEntities.MIMIC.create(world);
Direction facing = state.getOrEmpty(ChestBlock.FACING).orElse(null);
float yaw = facing.asRotation();
be.setCachedState(be.getCachedState().getBlock().getDefaultState());
mimic.updatePositionAndAngles(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, yaw, 0);
mimic.setHeadYaw(yaw);
mimic.setBodyYaw(yaw);
mimic.setYaw(yaw);
mimic.setChest(be);
world.spawnEntity(mimic);
return TypedActionResult.success(mimic);
}
return TypedActionResult.fail(null);
}
MimicEntity(EntityType<? extends MimicEntity> type, World world) {
super(type, world);
ignoreCameraFrustum = true;
}
@Override
protected void initDataTracker() {
super.initDataTracker();
dataTracker.startTracking(CHEST_DATA, new NbtCompound());
dataTracker.startTracking(MOUTH_OPEN, false);
}
@Override
public boolean isCollidable() {
return isAlive();
}
@Nullable
@Override
public ItemStack getPickBlockStack() {
Item item = chestData.getCachedState().getBlock().asItem();
return item == Items.AIR ? null : item.getDefaultStack();
}
@Override
protected void initGoals() {
goalSelector.add(2, new AttackGoal(this, 1.0, false));
}
public void setChest(ChestBlock chest) {
if (chest.createBlockEntity(getBlockPos(), chest.getDefaultState()) instanceof ChestBlockEntity be) {
setChest(be);
be.setLootTable(LootTables.ABANDONED_MINESHAFT_CHEST, getWorld().getRandom().nextLong());
if (!getWorld().isClient) {
dataTracker.set(CHEST_DATA, writeChestData(chestData));
}
}
}
public void setChest(ChestBlockEntity chestData) {
this.chestData = chestData;
chestData.setWorld(getWorld());
if (!getWorld().isClient) {
dataTracker.set(CHEST_DATA, writeChestData(chestData));
}
}
@Nullable
public ChestBlockEntity getChestData() {
return chestData;
}
public boolean isMouthOpen() {
return dataTracker.get(MOUTH_OPEN);
}
public void setMouthOpen(boolean mouthOpen) {
if (mouthOpen == isMouthOpen()) {
return;
}
playSound(mouthOpen ? SoundEvents.BLOCK_CHEST_OPEN : SoundEvents.BLOCK_CHEST_CLOSE, 0.5F, 1F);
dataTracker.set(MOUTH_OPEN, mouthOpen);
if (chestData != null) {
chestData.onSyncedBlockEvent(1, mouthOpen ? 1 : 0);
}
}
public void playChompAnimation() {
openTicks = 5;
setMouthOpen(true);
}
@Override
public void tick() {
super.tick();
if (!getWorld().isClient) {
if (age < 12 || (getTarget() == null && this.lastAttackedTicks < age - 30)) {
BlockPos pos = getBlockPos();
setPosition(
pos.getX() + 0.5,
getY(),
pos.getZ() + 0.5
);
setBodyYaw(MathHelper.floor(getBodyYaw() / 90) * 90);
setYaw(MathHelper.floor(getYaw() / 90) * 90);
setHeadYaw(MathHelper.floor(getHeadYaw() / 90) * 90);
}
}
if (chestData == null) {
setChest((ChestBlock)Blocks.CHEST);
}
if (getWorld().isClient) {
((MixinBlockEntity)chestData).setPos(getBlockPos());
ChestBlockEntity.clientTick(getWorld(), getBlockPos(), chestData.getCachedState(), chestData);
}
if (openTicks > 0 && --openTicks <= 0) {
setMouthOpen(false);
}
if (getTarget() != null) {
if (openTicks <= 0 && age % 20 == 0) {
playChompAnimation();
}
}
}
@Override
protected ActionResult interactMob(PlayerEntity player, Hand hand) {
if (getTarget() == null && chestData != null) {
player.openHandledScreen(new NamedScreenHandlerFactory() {
@Override
public Text getDisplayName() {
return chestData.getDisplayName();
}
@Override
public ScreenHandler createMenu(int syncId, PlayerInventory inv, PlayerEntity player) {
return createScreenHandler(syncId, inv, player);
}
});
return ActionResult.SUCCESS;
}
return ActionResult.PASS;
}
public ScreenHandler createScreenHandler(int syncId, PlayerInventory inv, PlayerEntity player) {
chestData.checkLootInteraction(player);
setChest(chestData);
var inventory = InventoryUtil.copyInto(chestData, new SimpleInventory(chestData.size()) {
@Override
public void onOpen(PlayerEntity player) {
observingPlayers.add(player);
setMouthOpen(true);
}
@Override
public void onClose(PlayerEntity player) {
observingPlayers.remove(player);
setMouthOpen(!observingPlayers.isEmpty());
}
});
inventory.addListener(sender -> {
if (InventoryUtil.contentEquals(inventory, chestData)) {
return;
}
observingPlayers.clear();
playChompAnimation();
setTarget(player);
if (player instanceof ServerPlayerEntity spe) {
spe.closeHandledScreen();
}
});
return GenericContainerScreenHandler.createGeneric9x3(syncId, inv, inventory);
}
@Override
public void setAttacking(boolean attacking) {
super.setAttacking(attacking);
if (attacking) {
playChompAnimation();
}
}
@Override
protected void playHurtSound(DamageSource source) {
playChompAnimation();
if (source.getAttacker() instanceof LivingEntity l) {
setTarget(l);
}
}
@Override
public void onTrackedDataSet(TrackedData<?> data) {
super.onTrackedDataSet(data);
if (CHEST_DATA.equals(data) && getWorld().isClient) {
setChest(readChestData(dataTracker.get(CHEST_DATA)));
} else if (MOUTH_OPEN.equals(data)) {
if (chestData != null) {
chestData.onSyncedBlockEvent(1, isMouthOpen() ? 1 : 0);
}
}
}
@Override
public void readCustomDataFromNbt(NbtCompound nbt) {
super.readCustomDataFromNbt(nbt);
if (nbt.contains("chest", NbtElement.COMPOUND_TYPE)) {
chestData = readChestData(nbt.getCompound("chest"));
} else {
chestData = null;
}
}
@Nullable
private ChestBlockEntity readChestData(NbtCompound nbt) {
BlockState state = BlockState.CODEC.decode(NbtOps.INSTANCE, nbt.getCompound("state")).result().get().getFirst();
if (BlockEntity.createFromNbt(getBlockPos(), state, nbt.getCompound("data")) instanceof ChestBlockEntity data) {
data.setWorld(getWorld());
return data;
}
return null;
}
@Override
public void writeCustomDataToNbt(NbtCompound nbt) {
super.writeCustomDataToNbt(nbt);
if (chestData != null) {
nbt.put("chest", writeChestData(chestData));
}
}
@Nullable
private NbtCompound writeChestData(ChestBlockEntity chestData) {
NbtCompound chest = new NbtCompound();
chest.put("data", chestData.createNbtWithId());
chest.put("state", BlockState.CODEC.encode(chestData.getCachedState(), NbtOps.INSTANCE, new NbtCompound()).result().get());
return chest;
}
@Override
protected void dropLoot(DamageSource damageSource, boolean causedByPlayer) {
if (chestData != null) {
ItemScatterer.spawn(getWorld(), this, chestData);
ItemScatterer.spawn(getWorld(), getX(), getY(), getZ(), chestData.getCachedState().getBlock().asItem().getDefaultStack());
}
}
public class AttackGoal extends MeleeAttackGoal {
private int ticks;
public AttackGoal(PathAwareEntity mob, double speed, boolean pauseWhenMobIdle) {
super(mob, speed, pauseWhenMobIdle);
}
@Override
public void start() {
super.start();
this.ticks = 0;
}
@Override
public void stop() {
super.stop();
setAttacking(false);
}
@Override
public void tick() {
super.tick();
++ticks;
if (ticks >= 5 && getCooldown() < getMaxCooldown() / 2) {
setAttacking(true);
} else {
setAttacking(false);
}
}
}
}

View file

@ -88,6 +88,10 @@ public interface UEntities {
.fireImmune()
.spawnableFarFromPlayer()
.dimensions(EntityDimensions.fixed(1, 2)));
EntityType<MimicEntity> MIMIC = register("mimic", FabricEntityTypeBuilder.create(SpawnGroup.MONSTER, MimicEntity::new)
.fireImmune()
//.disableSummon()
.dimensions(EntityDimensions.changing(0.875F, 0.875F)));
static <T extends Entity> EntityType<T> register(String name, FabricEntityTypeBuilder<T> builder) {
EntityType<T> type = builder.build();
@ -104,6 +108,7 @@ public interface UEntities {
FabricDefaultAttributeRegistry.register(LOOT_BUG, LootBugEntity.createSilverfishAttributes());
FabricDefaultAttributeRegistry.register(IGNOMINIOUS_BULB, IgnominiousBulbEntity.createMobAttributes());
FabricDefaultAttributeRegistry.register(SPECTER, SpecterEntity.createAttributes());
FabricDefaultAttributeRegistry.register(MIMIC, MimicEntity.createMobAttributes());
if (!Unicopia.getConfig().disableButterflySpawning.get()) {
final Predicate<BiomeSelectionContext> butterflySpawnable = BiomeSelectors.foundInOverworld()

View file

@ -1,23 +1,15 @@
package com.minelittlepony.unicopia.mixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import com.minelittlepony.unicopia.entity.behaviour.FallingBlockBehaviour;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.math.BlockPos;
@Mixin(BlockEntity.class)
abstract class MixinBlockEntity implements FallingBlockBehaviour.Positioned {
@Shadow
public interface MixinBlockEntity {
@Mutable
private @Final BlockPos pos;
@Override
public void setPos(BlockPos pos) {
this.pos = pos;
}
@Accessor("pos")
void setPos(BlockPos pos);
}

View file

@ -0,0 +1,55 @@
package com.minelittlepony.unicopia.mixin;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.At.Shift;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.minelittlepony.unicopia.entity.mob.MimicEntity;
import net.minecraft.block.entity.LockableContainerBlockEntity;
import net.minecraft.block.entity.LootableContainerBlockEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.util.ActionResult;
@Mixin(LootableContainerBlockEntity.class)
abstract class MixinLootableContainerBlockEntity extends LockableContainerBlockEntity {
private boolean generateMimic;
MixinLootableContainerBlockEntity() { super(null, null, null); }
@Inject(
method = "checkLootInteraction",
at = @At(
value = "INVOKE",
target = "net/minecraft/loot/LootTable.supplyInventory(Lnet/minecraft/inventory/Inventory;Lnet/minecraft/loot/context/LootContextParameterSet;J)V",
shift = Shift.AFTER
))
private void onCheckLootInteraction(@Nullable PlayerEntity player, CallbackInfo info) {
if (player != null) {
generateMimic = true;
}
}
@Inject(
method = "createMenu",
at = @At(
value = "INVOKE",
target = "net/minecraft/block/entity/LootableContainerBlockEntity.checkLootInteraction(Lnet/minecraft/entity/player/PlayerEntity;)V",
shift = Shift.AFTER
), cancellable = true)
private void onCreateMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player, CallbackInfoReturnable<ScreenHandler> info) {
if (generateMimic) {
generateMimic = false;
var mimic = MimicEntity.spawnFromChest(player.getWorld(), getPos(), player);
if (mimic.getResult() == ActionResult.SUCCESS) {
info.setReturnValue(mimic.getValue().createScreenHandler(syncId, playerInventory, player));
}
}
}
}

View file

@ -1,5 +1,6 @@
package com.minelittlepony.unicopia.util;
import java.util.function.Function;
import java.util.stream.Stream;
import net.minecraft.inventory.Inventory;
@ -22,4 +23,27 @@ public interface InventoryUtil {
}
return -1;
}
static boolean contentEquals(Inventory a, Inventory b) {
if (a.size() != b.size()) {
return false;
}
for (int i = 0; i < a.size(); i++) {
if (!ItemStack.areEqual(a.getStack(i), b.getStack(i))) {
return false;
}
}
return true;
}
static <I extends Inventory> I copy(Inventory from, Function<Integer, I> factory) {
return copyInto(from, factory.apply(from.size()));
}
static <I extends Inventory> I copyInto(Inventory from, I into) {
for (int i = 0; i < from.size(); i++) {
into.setStack(i, from.getStack(i).copy());
}
return into;
}
}

View file

@ -378,6 +378,7 @@
"entity.unicopia.butterfly": "Butterfly",
"entity.unicopia.twittermite": "Twittermite",
"entity.unicopia.specter": "Specter",
"entity.unicopia.mimic": "Mimic",
"entity.unicopia.cast_spell": "Cast Spell",
"entity.unicopia.cast_spell.by": "a spell cast by %s",
"entity.unicopia.spellbook": "Spellbook",

View file

@ -30,6 +30,7 @@
"MixinItemEntity",
"MixinItemStack",
"MixinLivingEntity",
"MixinLootableContainerBlockEntity",
"MixinMilkBucketItem",
"MixinMobEntity",
"MixinPersistentProjectileEntity",