From f68be46b0cac145b1e85d4cae48f4d740b23df74 Mon Sep 17 00:00:00 2001 From: Sollace Date: Thu, 18 Apr 2024 21:09:15 +0100 Subject: [PATCH] Add new chest type --- assets/models/mimic.bbmodel | 1 + assets/models/mimic.java | 53 +++ .../unicopia/client/URenderers.java | 1 + .../client/render/EntityDisguiseRenderer.java | 5 +- .../entity/CloudChestBlockEntityRenderer.java | 2 +- .../entity/CrystalShardsEntityRenderer.java | 9 +- .../FloatingArtefactEntityRenderer.java | 20 +- .../render/entity/MimicEntityRenderer.java | 143 +++++++ .../behaviour/FallingBlockBehaviour.java | 8 +- .../unicopia/entity/mob/MimicEntity.java | 361 ++++++++++++++++++ .../unicopia/entity/mob/UEntities.java | 5 + .../unicopia/mixin/MixinBlockEntity.java | 16 +- .../MixinLootableContainerBlockEntity.java | 55 +++ .../unicopia/util/InventoryUtil.java | 24 ++ .../resources/assets/unicopia/lang/en_us.json | 1 + src/main/resources/unicopia.mixin.json | 1 + 16 files changed, 671 insertions(+), 34 deletions(-) create mode 100644 assets/models/mimic.bbmodel create mode 100644 assets/models/mimic.java create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/entity/MimicEntityRenderer.java create mode 100644 src/main/java/com/minelittlepony/unicopia/entity/mob/MimicEntity.java create mode 100644 src/main/java/com/minelittlepony/unicopia/mixin/MixinLootableContainerBlockEntity.java diff --git a/assets/models/mimic.bbmodel b/assets/models/mimic.bbmodel new file mode 100644 index 00000000..f4a6339d --- /dev/null +++ b/assets/models/mimic.bbmodel @@ -0,0 +1 @@ +{"meta":{"format_version":"4.9","model_format":"modded_entity","box_uv":true},"name":"mimic","model_identifier":"","modded_entity_version":"Fabric 1.17+","modded_entity_flip_y":true,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"timeline_setups":[],"unhandled_root_fields":{},"resolution":{"width":64,"height":64},"elements":[{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-1,5,5],"to":[1,9,6],"autouv":0,"color":0,"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"9d61ad16-6c82-9a20-5701-12ba5093bf3c"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,5,5],"to":[4,9,6],"autouv":0,"color":0,"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"d25b1a82-7e3e-c44f-623d-6ea50d66066f"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-4,5,5],"to":[-2,9,6],"autouv":0,"color":0,"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"afb53261-4022-e68b-7664-d6a854d3239f"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[10,5,-2],"to":[12,9,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"0ffff8f3-55f3-ba96-fd1e-f1364423e084"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[7,5,-2],"to":[9,9,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"491606c6-699f-841f-a8ab-e8cdec547c68"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[4,5,-2],"to":[6,9,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"b4218883-bb0c-c89b-1cc8-a056d52c536e"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[4,5,-13],"to":[6,9,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"85b2bed3-39f9-1e1c-ecd2-d688e58e2901"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[7,5,-13],"to":[9,9,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"025ec1fd-f7bc-2d27-0393-50f206244268"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[10,5,-13],"to":[12,9,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,8,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"62541bca-746b-c4c1-b4fa-f80ae51de5a7"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-1,4,5],"to":[1,8,6],"autouv":0,"color":0,"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"8c299ddd-aee9-17a1-3a67-2b45fec41dbd"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,4,5],"to":[4,8,6],"autouv":0,"color":0,"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"c3e8c8d9-92d0-b027-aaff-323d36237f18"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-4,4,5],"to":[-2,8,6],"autouv":0,"color":0,"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"74c2d8e5-4f69-f77d-814a-b71479a7d8a3"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[10,4,-2],"to":[12,8,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"bea44a0b-36e2-2669-cd89-28e0afd4c448"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[7,4,-2],"to":[9,8,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"949c17f4-860c-7c24-189a-fa0e74f3666e"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[4,4,-2],"to":[6,8,-1],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"06536cdb-7e75-261b-c8d5-a32b7a5a0e76"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[10,4,-13],"to":[12,8,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"0e52be90-4685-d6f1-6b91-c3eb5f8593ca"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[7,4,-13],"to":[9,8,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"c343e272-4edc-25ec-e874-f562d4f6f0ec"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[4,4,-13],"to":[6,8,-12],"autouv":0,"color":0,"rotation":[0,-90,0],"origin":[0,7,-7],"faces":{"north":{"uv":[1,1,3,5],"texture":0},"east":{"uv":[0,1,1,5],"texture":0},"south":{"uv":[4,1,6,5],"texture":0},"west":{"uv":[3,1,4,5],"texture":0},"up":{"uv":[3,1,1,0],"texture":0},"down":{"uv":[5,0,3,1],"texture":0}},"type":"cube","uuid":"1ee91a5f-63e6-af9d-582c-21b579f93773"},{"name":"tongue","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-3,9,1],"to":[3,10,9],"autouv":0,"color":0,"rotation":[-30.000000000000068,0,0],"origin":[0,0,0],"uv_offset":[11,34],"faces":{"north":{"uv":[19,42,25,43],"texture":0},"east":{"uv":[11,42,19,43],"texture":0},"south":{"uv":[33,42,39,43],"texture":0},"west":{"uv":[25,42,33,43],"texture":0},"up":{"uv":[25,42,19,34],"texture":0},"down":{"uv":[31,34,25,42],"texture":0}},"type":"cube","uuid":"5370d06e-ecbb-fb06-26bb-8225234d4589"}],"outliner":[{"name":"lid","origin":[0,7,-7],"rotation":[47.5,0,-180],"color":0,"uuid":"704f8283-e7f5-5ff9-eb85-66ed5a7e1199","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["5370d06e-ecbb-fb06-26bb-8225234d4589",{"name":"upper_teeth","origin":[0,0,0],"color":0,"uuid":"6ece85ba-c460-6c7c-1bbd-2f4390cf4c44","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["8c299ddd-aee9-17a1-3a67-2b45fec41dbd","c3e8c8d9-92d0-b027-aaff-323d36237f18","74c2d8e5-4f69-f77d-814a-b71479a7d8a3","bea44a0b-36e2-2669-cd89-28e0afd4c448","949c17f4-860c-7c24-189a-fa0e74f3666e","06536cdb-7e75-261b-c8d5-a32b7a5a0e76","0e52be90-4685-d6f1-6b91-c3eb5f8593ca","c343e272-4edc-25ec-e874-f562d4f6f0ec","1ee91a5f-63e6-af9d-582c-21b579f93773"]}]},{"name":"lower_teeth","origin":[0,8,-7],"color":0,"uuid":"95035b43-0bfe-81c7-51ac-bb824489552b","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["9d61ad16-6c82-9a20-5701-12ba5093bf3c","d25b1a82-7e3e-c44f-623d-6ea50d66066f","afb53261-4022-e68b-7664-d6a854d3239f","0ffff8f3-55f3-ba96-fd1e-f1364423e084","491606c6-699f-841f-a8ab-e8cdec547c68","b4218883-bb0c-c89b-1cc8-a056d52c536e","62541bca-746b-c4c1-b4fa-f80ae51de5a7","025ec1fd-f7bc-2d27-0393-50f206244268","85b2bed3-39f9-1e1c-ecd2-d688e58e2901"]}],"textures":[{"path":"/home/sollace/Documents/GitRepos/minecraft_mods/normal.png","name":"normal.png","folder":"","namespace":"","id":"0","width":64,"height":64,"uv_width":64,"uv_height":64,"particle":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":true,"uuid":"b2322db6-1c29-09ad-3b26-7e50165557cd","relative_path":"../../../../normal.png","source":"data:image/png;base64,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"}],"fabricOptions":{"header":"package com.example.mod;","entity":"Entity","render":"","members":""}} \ No newline at end of file diff --git a/assets/models/mimic.java b/assets/models/mimic.java new file mode 100644 index 00000000..fa5f7932 --- /dev/null +++ b/assets/models/mimic.java @@ -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 { + 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); + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index 704f90cb..b4f09f99 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -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); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java index 4d8ecef3..40a83c9d 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java @@ -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 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(); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CloudChestBlockEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CloudChestBlockEntityRenderer.java index 20662111..d1dfa12a 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CloudChestBlockEntityRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CloudChestBlockEntityRenderer.java @@ -59,7 +59,7 @@ public class CloudChestBlockEntityRenderer extends ChestBlockEntityRenderer getProperties(BlockState state, ChestBlockEntity entity) { + public static DoubleBlockProperties.PropertySource getProperties(BlockState state, ChestBlockEntity entity) { return entity.getWorld() != null ? ((AbstractChestBlock)state.getBlock()).getBlockEntitySource(state, entity.getWorld(), entity.getPos(), true) : DoubleBlockProperties.PropertyRetriever::getFallback; diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CrystalShardsEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CrystalShardsEntityRenderer.java index 5404bb6e..94bae2a7 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CrystalShardsEntityRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CrystalShardsEntityRenderer.java @@ -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 { @@ -30,6 +28,8 @@ public class CrystalShardsEntityRenderer extends EntityRenderer layer.hasCrumbling() ? VertexConsumers.union(destructionOverlay, vertices.getBuffer(layer)) : vertices.getBuffer(layer); } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/MimicEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/MimicEntityRenderer.java new file mode 100644 index 00000000..e2c91bc3 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/MimicEntityRenderer.java @@ -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 { + 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 { + public ChestFeature(FeatureRendererContext 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 { + 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); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java index b9d440a4..d932e0cc 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java @@ -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 { } 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); - } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/MimicEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/MimicEntity.java new file mode 100644 index 00000000..1fe4eb1a --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/MimicEntity.java @@ -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 CHEST_DATA = DataTracker.registerData(MimicEntity.class, TrackedDataHandlerRegistry.NBT_COMPOUND); + static final TrackedData MOUTH_OPEN = DataTracker.registerData(MimicEntity.class, TrackedDataHandlerRegistry.BOOLEAN); + + @Nullable + private ChestBlockEntity chestData; + + private int openTicks; + private final Set observingPlayers = new HashSet<>(); + + @SuppressWarnings("deprecation") + public static TypedActionResult 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 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); + } + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java index 76b0de8a..8f1237ae 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java @@ -88,6 +88,10 @@ public interface UEntities { .fireImmune() .spawnableFarFromPlayer() .dimensions(EntityDimensions.fixed(1, 2))); + EntityType MIMIC = register("mimic", FabricEntityTypeBuilder.create(SpawnGroup.MONSTER, MimicEntity::new) + .fireImmune() + //.disableSummon() + .dimensions(EntityDimensions.changing(0.875F, 0.875F))); static EntityType register(String name, FabricEntityTypeBuilder builder) { EntityType 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 butterflySpawnable = BiomeSelectors.foundInOverworld() diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinBlockEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinBlockEntity.java index 8d98283e..2a874a22 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinBlockEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinBlockEntity.java @@ -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); } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLootableContainerBlockEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLootableContainerBlockEntity.java new file mode 100644 index 00000000..4cb375ca --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLootableContainerBlockEntity.java @@ -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 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)); + } + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java b/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java index 2b19564e..035e1f0d 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java +++ b/src/main/java/com/minelittlepony/unicopia/util/InventoryUtil.java @@ -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 copy(Inventory from, Function factory) { + return copyInto(from, factory.apply(from.size())); + } + + static I copyInto(Inventory from, I into) { + for (int i = 0; i < from.size(); i++) { + into.setStack(i, from.getStack(i).copy()); + } + return into; + } } diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 4833e984..d9ccf5ac 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -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", diff --git a/src/main/resources/unicopia.mixin.json b/src/main/resources/unicopia.mixin.json index 855a75b7..46be0255 100644 --- a/src/main/resources/unicopia.mixin.json +++ b/src/main/resources/unicopia.mixin.json @@ -30,6 +30,7 @@ "MixinItemEntity", "MixinItemStack", "MixinLivingEntity", + "MixinLootableContainerBlockEntity", "MixinMilkBucketItem", "MixinMobEntity", "MixinPersistentProjectileEntity",