diff --git a/HOW_TO_PLAY.md b/HOW_TO_PLAY.md
index c0bb6f37..a29546d9 100644
--- a/HOW_TO_PLAY.md
+++ b/HOW_TO_PLAY.md
@@ -3,7 +3,7 @@
[![ru](https://img.shields.io/badge/lang-ru-d52b1e.svg)](README_RU.md)
[![cn](https://img.shields.io/badge/lang-cn-de2910.svg)](README_CN.md)
-When starting a new world you're given the choice of which tribe to join. One of Unicorn, Pegasus, Earth, Bat, or Changeling.
+When starting a new world you're given the choice of which tribe to join. You can choose from _Unicorn_, _Pegasus_, _Earth Pony_, _Batpony_, or _Changeling_.
Depending on which race you pick, you're given different abilities, displayed on your HUD in a series of circular elements.
To activate an ability, simply press and hold the key corresponding to that ability. Some take longer than others, and certain abilities
@@ -17,7 +17,7 @@ also respond to quick, short single-taps, or double-taps.
For unicorns, casting spells is done through gems, which you can obtain whilst mining. You first need to craft a spellbook using a gem, and then
in the spellbook you can discover the magical traits of different items and recipes to combine them to create different spells, as well
- as modify existing one.
+ as modify existing ones.
Once you have a gem with a spell you want to use, you can equip it to your main-hand or off-hand slot by right-clicking with a gem in
one of either hand, then to activate it you us your primary ability. You can also cast spells directly from a gem by using the ability
@@ -34,7 +34,7 @@ also respond to quick, short single-taps, or double-taps.
### Earth Ponies
- Kicking & Stomping
- Earth ponies kick good.
+ *Earth ponies kick good*.
If Mine Little Pony is installed, and you have the appearance of a pony, kicking will target the block behind you, so twirl that rump!
Kicking blocks will incrementally mine them, and kicking trees will shake items loose from their branches. Kicking a tree _too much_ might destroy it,
@@ -47,7 +47,7 @@ also respond to quick, short single-taps, or double-taps.
- Bracing
- Earth ponies can brace themselves by sneaking! It, uh, makes you harder to push! yeah!
+ Earth ponies can brace themselves by sneaking! It, uh, makes you harder to push! Yeah!
### Pegasi / Bat Ponies
@@ -99,7 +99,7 @@ also respond to quick, short single-taps, or double-taps.
- Hanging of Ceilings
- Ever just want to hang out? Well bat ponies can, _literally_! All the cool kids are doing.
+ Ever just want to hang out? Well bat ponies can, _literally_! All the cool kids are doing it!
- Mangoes
@@ -121,6 +121,12 @@ also respond to quick, short single-taps, or double-taps.
Changelings can turn into damn near anything, even other players! And blocks! And hostile mobs!
Careful about turning into skeletons, though, because they hate the sun even more than bat ponies.
+
+ - Crawling
+
+ Changelings can crawl on Ceilings, which is very useful for lining up your shot, when attempting to shoot somepony in a cave,
+ while disguised as a stone block. Or is that just me? Anyways, they can only crawl along flat surfaces, meaning that if you go off the edge of a block,
+ you will fall off.
## Special Items, Plants, Tools
@@ -135,7 +141,7 @@ Fighting too close together with them may cause you some knockback.
Zap Apple Trees occur naturally in the world and are the only way to obtain zap apples in survival. They can appear in one of several different states:
-Hybernating, Flowering, Fruiting, or Withering
+*Hibernating, Flowering, Fruiting, or Withering*
They cycle through these states throughout the lunar cycle, so if you find one, and it's not in the state you want, wait around another few days and it
will eventually bear fruit, but don't try to harvest the apples before they're ripe, because they will zap you!
@@ -144,4 +150,6 @@ If you're able to obtain the wood and leaves, it also makes the perfect deterant
### Muffins
-They're bouncy and delicious, and pigs absolutely love them!
\ No newline at end of file
+They're bouncy and delicious, and pigs absolutely love them!
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 05a9d06f..e54b1da3 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
Bringing the magic of friendship to Minecraft!
-What started as a humble utility to make playing as a unicorn a little more emersive has grown into a full-blown pony
+What started as a humble utility to make playing as a unicorn a little more immersive has grown into a full-blown pony
conversion experience that brings new magic, mechanics and experience to the world of Minecraft to make it truly feel like you've
entered the world of Equestria!
@@ -24,13 +24,13 @@ Unicorns, Pegasi, Earth Ponies, and even Changelings get their own special abili
- *Play as a unicorn* and learn to use magic! Craft your first spellbook and experiment, finding the different spells you can
make and what they do, or simply delve into the lore to learn more about the past of this mysterious world!
- Besides casting spells, such as a shield to protect themselves, of fire a bolt of magic to incinerate your foes,
+ Besides casting spells, such as a shield to protect themselves, or a bolt of magic to incinerate your foes,
Unicorns can also teleport to get around obstacles or simply reach those hard to reach places.
- - *Play as a pegasus* and dominate the skies! Besides the ability to fly, pegasi can also perform rainbooms, control the weather by shoving them into jars,
+ - *Play as a pegasus* and dominate the skies! Besides the ability to fly, pegasi can also perform sonic rainbooms, control the weather by shoving them into jars,
and have a greater reach distance and speed than other races.
- - *Play as a humble background pony*! Earth Ponies are tougher and heavier than the other races. They also have the nify ability to
+ - *Play as a humble background pony*! Earth Ponies are tougher and heavier than the other races. They also have the nifty ability to
kick trees to get food and hasten the growth of crops. You'll never go hungry if you're an earth pony.
Feeling like going over to the dark side?
@@ -58,7 +58,7 @@ Unicorns, Pegasi, Earth Ponies, and even Changelings get their own special abili
- Airflow is simulated (badly)
Pegasi, beware about flying during storms! It can get dangerous out there!
- If you're playing as a flying species, or just like having nice things, try building a weather vein.
+ If you're playing as a flying species, or just like having nice things, try building a weather vane.
It shows the actual, totally real and not simulated badly, wind direction of your minecraft world. Just beware
that the direction and strength is situational (and bad), and will be different depending where you are and
how high up you are.
diff --git a/assets/accretion_disk.svg b/assets/accretion_disk.svg
new file mode 100644
index 00000000..ee302045
--- /dev/null
+++ b/assets/accretion_disk.svg
@@ -0,0 +1,80 @@
+
+
+
+
diff --git a/assets/models/bulb_angry.png b/assets/models/bulb_angry.png
new file mode 100644
index 00000000..5f5cc603
Binary files /dev/null and b/assets/models/bulb_angry.png differ
diff --git a/assets/models/bulb_idle.png b/assets/models/bulb_idle.png
new file mode 100644
index 00000000..7fe663ff
Binary files /dev/null and b/assets/models/bulb_idle.png differ
diff --git a/assets/models/cloud_bed.java b/assets/models/cloud_bed.java
deleted file mode 100644
index 88008846..00000000
--- a/assets/models/cloud_bed.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Made with Blockbench 4.8.3
-// Exported for Minecraft version 1.17 or later with Mojang mappings
-// Paste this class into your mod and generate all required imports
-
-
-public class cloud_bed extends EntityModel {
- // This layer location should be baked with EntityRendererProvider.Context in the entity renderer and passed into this model's constructor
- public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation("modid", "cloud_bed"), "main");
- private final ModelPart head;
- private final ModelPart foot;
-
- public cloud_bed(ModelPart root) {
- this.head = root.getChild("head");
- this.foot = root.getChild("foot");
- }
-
- public static LayerDefinition createBodyLayer() {
- MeshDefinition meshdefinition = new MeshDefinition();
- PartDefinition partdefinition = meshdefinition.getRoot();
-
- PartDefinition head = partdefinition.addOrReplaceChild("head", CubeListBuilder.create(), PartPose.offset(0.0F, 24.0F, 0.0F));
-
- PartDefinition main_r1 = head.addOrReplaceChild("main_r1", CubeListBuilder.create().texOffs(0, 0).addBox(-8.0F, -21.0F, -9.0F, 16.0F, 13.0F, 6.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 0.0F, -16.0F, -1.5708F, 0.0F, 0.0F));
-
- PartDefinition head_board = head.addOrReplaceChild("head_board", CubeListBuilder.create().texOffs(52, 24).addBox(7.0F, -13.0F, -3.0F, 2.0F, 13.0F, 3.0F, new CubeDeformation(0.0F))
- .texOffs(0, 43).addBox(-6.0F, -15.0F, -2.0F, 13.0F, 15.0F, 3.0F, new CubeDeformation(0.0F))
- .texOffs(52, 24).addBox(-8.0F, -13.0F, -3.0F, 2.0F, 13.0F, 3.0F, new CubeDeformation(0.0F)), PartPose.offset(-0.5F, 0.0F, 7.0F));
-
- PartDefinition foot = partdefinition.addOrReplaceChild("foot", CubeListBuilder.create(), PartPose.offset(0.0F, 24.0F, 0.0F));
-
- PartDefinition main_r2 = foot.addOrReplaceChild("main_r2", CubeListBuilder.create().texOffs(0, 22).addBox(-8.0F, -8.0F, -9.0F, 16.0F, 15.0F, 6.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 0.0F, 0.0F, -1.5708F, 0.0F, 0.0F));
-
- PartDefinition head_board2 = foot.addOrReplaceChild("head_board2", CubeListBuilder.create().texOffs(52, 40).addBox(6.0F, -13.0F, -2.0F, 3.0F, 10.0F, 3.0F, new CubeDeformation(0.0F))
- .texOffs(0, 43).addBox(-6.0F, -15.0F, -3.0F, 13.0F, 11.0F, 3.0F, new CubeDeformation(0.0F))
- .texOffs(52, 40).addBox(-8.0F, -13.0F, -2.0F, 3.0F, 10.0F, 3.0F, new CubeDeformation(0.0F)), PartPose.offset(-0.5F, 3.0F, -7.0F));
-
- return LayerDefinition.create(meshdefinition, 64, 64);
- }
-
- @Override
- public void setupAnim(T entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
-
- }
-
- @Override
- public void renderToBuffer(PoseStack poseStack, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {
- head.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
- foot.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
- }
-}
\ No newline at end of file
diff --git a/assets/models/cloud_chest.java b/assets/models/cloud_chest.java
deleted file mode 100644
index 87e684a2..00000000
--- a/assets/models/cloud_chest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Made with Blockbench 4.8.3
-// Exported for Minecraft version 1.17+ for Yarn
-// Paste this class into your mod and generate all required imports
-public class cloud_chest extends EntityModel {
- private final ModelPart lid;
- private final ModelPart lock_r1;
- private final ModelPart bb_main;
- public cloud_chest(ModelPart root) {
- this.lid = root.getChild("lid");
- this.bb_main = root.getChild("bb_main");
- }
- public static TexturedModelData getTexturedModelData() {
- ModelData modelData = new ModelData();
- ModelPartData modelPartData = modelData.getRoot();
- ModelPartData lid = modelPartData.addChild("lid", ModelPartBuilder.create().uv(0, 0).cuboid(-1.0F, -2.0F, 13.8F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
- .uv(0, 0).cuboid(-1.0F, -1.0F, 14.0F, 2.0F, 2.0F, 1.0F, new Dilation(0.0F))
- .uv(0, 0).cuboid(-7.0F, -5.0F, 0.0F, 14.0F, 5.0F, 14.0F, new Dilation(0.3F)), ModelTransform.of(0.0F, 16.0F, -7.0F, 1.0908F, 0.0F, 0.0F));
-
- ModelPartData lock_r1 = lid.addChild("lock_r1", ModelPartBuilder.create().uv(0, 0).cuboid(-2.0F, -4.0F, -0.5F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)), ModelTransform.of(-2.0F, 1.0F, 14.3F, 0.0F, 0.0F, 1.5708F));
-
- ModelPartData bb_main = modelPartData.addChild("bb_main", ModelPartBuilder.create().uv(0, 19).cuboid(-7.0F, -10.0F, -7.0F, 14.0F, 10.0F, 14.0F, new Dilation(0.0F)), ModelTransform.pivot(0.0F, 24.0F, 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);
- bb_main.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
- }
-}
\ No newline at end of file
diff --git a/assets/models/hay_bale.bbmodel b/assets/models/hay_bale.bbmodel
new file mode 100644
index 00000000..7328ccde
--- /dev/null
+++ b/assets/models/hay_bale.bbmodel
@@ -0,0 +1 @@
+{"meta":{"format_version":"4.5","model_format":"java_block","box_uv":false},"name":"hay_bale_bse","parent":"","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{},"resolution":{"width":16,"height":16},"elements":[{"name":"bottom_south_east","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[8,0,8],"to":[16,8,16],"autouv":0,"color":2,"origin":[0,0,0],"faces":{"north":{"uv":[0,8,8,16],"texture":0},"east":{"uv":[0,8,8,16],"texture":1},"south":{"uv":[8,8,16,16],"texture":1},"west":{"uv":[8,8,16,16],"texture":0},"up":{"uv":[8,8,16,16],"texture":0},"down":{"uv":[8,0,16,8],"texture":0}},"type":"cube","uuid":"523f4164-daa5-4b79-eec7-013710b7ae88"},{"name":"bottom_north_west","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[0,0,0],"to":[8,8,8],"autouv":0,"color":2,"origin":[0,0,0],"faces":{"north":{"uv":[8,8,16,16],"texture":1},"east":{"uv":[8,8,16,16],"texture":0},"south":{"uv":[0,8,8,16],"texture":0},"west":{"uv":[0,8,8,16],"texture":1},"up":{"uv":[0,0,8,8],"texture":0},"down":{"uv":[0,8,8,16],"texture":0}},"type":"cube","uuid":"1fd288fe-a293-1ca2-a5b7-15d602eb44f6"},{"name":"top_south_east","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[8,8,8],"to":[16,16,16],"autouv":0,"color":2,"origin":[0,0,0],"faces":{"north":{"uv":[0,0,8,8],"texture":0},"east":{"uv":[0,0,8,8],"texture":1},"south":{"uv":[8,0,16,8],"texture":1},"west":{"uv":[8,0,16,8],"texture":0},"up":{"uv":[8,8,16,16],"texture":0},"down":{"uv":[8,0,16,8],"texture":0}},"type":"cube","uuid":"4f48967d-c722-5593-6f77-079ee47d08af"},{"name":"top_north_west","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[0,8,0],"to":[8,16,8],"autouv":0,"color":2,"origin":[0,0,0],"faces":{"north":{"uv":[8,0,16,8],"texture":1},"east":{"uv":[8,0,16,8],"texture":0},"south":{"uv":[0,0,8,8],"texture":0},"west":{"uv":[0,0,8,8],"texture":1},"up":{"uv":[0,0,8,8],"texture":0},"down":{"uv":[0,8,8,16],"texture":0}},"type":"cube","uuid":"c4356ab3-a7a5-43e5-489a-9ebb52492675"},{"name":"bottom_south_west","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[0,0,8],"to":[8,8,16],"autouv":0,"color":2,"origin":[0,0,0],"faces":{"north":{"uv":[8,8,16,16],"texture":0},"east":{"uv":[0,8,8,16],"texture":0},"south":{"uv":[0,8,8,16],"texture":1},"west":{"uv":[8,8,16,16],"texture":1},"up":{"uv":[0,8,8,16],"texture":0},"down":{"uv":[0,0,8,8],"texture":0}},"type":"cube","uuid":"d07d02d7-5ab9-0026-b3ed-029701536cf6"},{"name":"bottom_north_east","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[8,0,0],"to":[16,8,8],"autouv":0,"color":2,"origin":[0,0,0],"faces":{"north":{"uv":[0,8,8,16],"texture":1},"east":{"uv":[8,8,16,16],"texture":1},"south":{"uv":[8,8,16,16],"texture":0},"west":{"uv":[0,8,8,16],"texture":0},"up":{"uv":[8,0,16,8],"texture":0},"down":{"uv":[8,8,16,16],"texture":0}},"type":"cube","uuid":"d10fb8d4-fe44-ca10-77f0-63b32855f5d6"},{"name":"top_south_west","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[0,8,8],"to":[8,16,16],"autouv":0,"color":2,"origin":[0,0,0],"faces":{"north":{"uv":[8,0,16,8],"texture":0},"east":{"uv":[0,0,8,8],"texture":0},"south":{"uv":[0,0,8,8],"texture":1},"west":{"uv":[8,0,16,8],"texture":1},"up":{"uv":[0,8,8,16],"texture":0},"down":{"uv":[0,0,8,8],"texture":0}},"type":"cube","uuid":"b9c9d695-4003-c9e6-efbb-9a75c658b47e"},{"name":"top_north_east","box_uv":false,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[8,8,0],"to":[16,16,8],"autouv":0,"color":2,"origin":[0,0,0],"faces":{"north":{"uv":[0,0,8,8],"texture":1},"east":{"uv":[8,0,16,8],"texture":1},"south":{"uv":[8,0,16,8],"texture":0},"west":{"uv":[0,0,8,8],"texture":0},"up":{"uv":[8,0,16,8],"texture":0},"down":{"uv":[8,8,16,16],"texture":0}},"type":"cube","uuid":"20a14bb3-edf7-1ae1-132e-ef3d56d84f67"}],"outliner":["523f4164-daa5-4b79-eec7-013710b7ae88","4f48967d-c722-5593-6f77-079ee47d08af","d10fb8d4-fe44-ca10-77f0-63b32855f5d6","20a14bb3-edf7-1ae1-132e-ef3d56d84f67","d07d02d7-5ab9-0026-b3ed-029701536cf6","b9c9d695-4003-c9e6-efbb-9a75c658b47e","1fd288fe-a293-1ca2-a5b7-15d602eb44f6","c4356ab3-a7a5-43e5-489a-9ebb52492675"],"textures":[{"path":"/home/sollace/Desktop/hay_block_top.png","name":"hay_block_top.png","folder":"blocks","namespace":"minecraft","id":"top","particle":false,"render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"mode":"bitmap","saved":true,"uuid":"f73f23e2-d591-dd05-db6f-60869e1592c7","relative_path":"../../../../../../../Desktop/hay_block_top.png","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAVVJREFUOE9tkyFOBEEQRWvckEAyuBWYFSRgCQgk3IgDcAKC4wo4rgBIxBIsJIg1iHVsAgnjhryevE6xTJvd6ar69ev/6ub+thte3yJmXR+c1bqNn++I+V5f/nuMm8Mvd83VRTccHUd8rvrYnbWRwUhafrQVjAJBDw/Gmubuuh3sMt+PWL5HASKY79df4x2xl+eRIeCFgXRBJXh+1sfTog2+ZSQTm/C9tR3R3Fx2AzPzwZkqMtkRpM84VQNpcUkiRTCRetbi9KSPh8dRmz8aMF+3858+oBQJZpPCABFVX/s2rWJuNNHKPFLRAHTVpxgWdFPA3FEm5FUbcwIBmHDcDxhk+opITmGgaHqrrapO1zyC+1H3gE66QDDPTFLeVGjneBFRilNWyirvCXmAolHdRH3O6wttHw06ObuuVQ1y0ua2EXM/cMojSMNzZi5fWX7KWdwppxjjF8NCOiclFu9xAAAAAElFTkSuQmCC"},{"path":"/home/sollace/Desktop/hay_block_side.png","name":"hay_block_side.png","folder":"blocks","namespace":"minecraft","id":"side","particle":true,"render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"mode":"bitmap","saved":true,"uuid":"1cf36f16-9f26-3125-d49c-45217088cbc7","relative_path":"../../../../../../../Desktop/hay_block_side.png","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAV1JREFUOE+VU7tOQkEUPGtiuAokeCExNIpBK2x9tITK7/EHqCmJv2DlNxhblZqORK2MiVxJBLmEhDVzktkclopt9p7X7OzcWde7bfiL6wMZDsfSalVl8Pwj7U5FXl4zjZn//pxKrV5aixunRXF33SNvGzFwdZmuARymBfnKFgpIIBx0crwrDgz2yytBE05AgYyQe/tYCup/vzs6gBg7l7u/Ofc+8ZJNckkrSSgg5ppNF1IsFTbq6NcrkCJPxAmgbPOMyYDMggaxSBTz6XGilGNAArmH/pnH3QEAVa36EPN9NFNtbN2KugGAAQvEwRiIfQqAASa23QMAqJNyDGivEDNUH8TOs+rjV0JALDoVPqHort9uehTpAZc7GedzqSZ7An8wtj34pm+CE5GE2+yiA7Fjxc5EXgFoUTTR1tYw9AEPsdYOTuQAH419E3xs1qnoh3aBgR20otKRVn2+Vuz/aIRXgq2GkfcAAAAASUVORK5CYII="}]}
\ No newline at end of file
diff --git a/assets/models/hay_bale.json b/assets/models/hay_bale.json
new file mode 100644
index 00000000..a2ce1538
--- /dev/null
+++ b/assets/models/hay_bale.json
@@ -0,0 +1,113 @@
+{
+ "textures": {
+ "top": "blocks/hay_block_top",
+ "particle": "blocks/hay_block_side",
+ "side": "blocks/hay_block_side"
+ },
+ "elements": [
+ {
+ "name": "bottom_south_east",
+ "from": [8, 0, 8],
+ "to": [16, 8, 16],
+ "faces": {
+ "north": {"uv": [0, 8, 8, 16], "texture": "#top"},
+ "east": {"uv": [0, 8, 8, 16], "texture": "#side"},
+ "south": {"uv": [8, 8, 16, 16], "texture": "#side"},
+ "west": {"uv": [8, 8, 16, 16], "texture": "#top"},
+ "up": {"uv": [8, 8, 16, 16], "texture": "#top"},
+ "down": {"uv": [8, 0, 16, 8], "texture": "#top"}
+ }
+ },
+ {
+ "name": "top_south_east",
+ "from": [8, 8, 8],
+ "to": [16, 16, 16],
+ "faces": {
+ "north": {"uv": [0, 0, 8, 8], "texture": "#top"},
+ "east": {"uv": [0, 0, 8, 8], "texture": "#side"},
+ "south": {"uv": [8, 0, 16, 8], "texture": "#side"},
+ "west": {"uv": [8, 0, 16, 8], "texture": "#top"},
+ "up": {"uv": [8, 8, 16, 16], "texture": "#top"},
+ "down": {"uv": [8, 0, 16, 8], "texture": "#top"}
+ }
+ },
+ {
+ "name": "bottom_north_east",
+ "from": [8, 0, 0],
+ "to": [16, 8, 8],
+ "faces": {
+ "north": {"uv": [0, 8, 8, 16], "texture": "#side"},
+ "east": {"uv": [8, 8, 16, 16], "texture": "#side"},
+ "south": {"uv": [8, 8, 16, 16], "texture": "#top"},
+ "west": {"uv": [0, 8, 8, 16], "texture": "#top"},
+ "up": {"uv": [8, 0, 16, 8], "texture": "#top"},
+ "down": {"uv": [8, 8, 16, 16], "texture": "#top"}
+ }
+ },
+ {
+ "name": "top_north_east",
+ "from": [8, 8, 0],
+ "to": [16, 16, 8],
+ "faces": {
+ "north": {"uv": [0, 0, 8, 8], "texture": "#side"},
+ "east": {"uv": [8, 0, 16, 8], "texture": "#side"},
+ "south": {"uv": [8, 0, 16, 8], "texture": "#top"},
+ "west": {"uv": [0, 0, 8, 8], "texture": "#top"},
+ "up": {"uv": [8, 0, 16, 8], "texture": "#top"},
+ "down": {"uv": [8, 8, 16, 16], "texture": "#top"}
+ }
+ },
+ {
+ "name": "bottom_south_west",
+ "from": [0, 0, 8],
+ "to": [8, 8, 16],
+ "faces": {
+ "north": {"uv": [8, 8, 16, 16], "texture": "#top"},
+ "east": {"uv": [0, 8, 8, 16], "texture": "#top"},
+ "south": {"uv": [0, 8, 8, 16], "texture": "#side"},
+ "west": {"uv": [8, 8, 16, 16], "texture": "#side"},
+ "up": {"uv": [0, 8, 8, 16], "texture": "#top"},
+ "down": {"uv": [0, 0, 8, 8], "texture": "#top"}
+ }
+ },
+ {
+ "name": "top_south_west",
+ "from": [0, 8, 8],
+ "to": [8, 16, 16],
+ "faces": {
+ "north": {"uv": [8, 0, 16, 8], "texture": "#top"},
+ "east": {"uv": [0, 0, 8, 8], "texture": "#top"},
+ "south": {"uv": [0, 0, 8, 8], "texture": "#side"},
+ "west": {"uv": [8, 0, 16, 8], "texture": "#side"},
+ "up": {"uv": [0, 8, 8, 16], "texture": "#top"},
+ "down": {"uv": [0, 0, 8, 8], "texture": "#top"}
+ }
+ },
+ {
+ "name": "bottom_north_west",
+ "from": [0, 0, 0],
+ "to": [8, 8, 8],
+ "faces": {
+ "north": {"uv": [8, 8, 16, 16], "texture": "#side"},
+ "east": {"uv": [8, 8, 16, 16], "texture": "#top"},
+ "south": {"uv": [0, 8, 8, 16], "texture": "#top"},
+ "west": {"uv": [0, 8, 8, 16], "texture": "#side"},
+ "up": {"uv": [0, 0, 8, 8], "texture": "#top"},
+ "down": {"uv": [0, 8, 8, 16], "texture": "#top"}
+ }
+ },
+ {
+ "name": "top_north_west",
+ "from": [0, 8, 0],
+ "to": [8, 16, 8],
+ "faces": {
+ "north": {"uv": [8, 0, 16, 8], "texture": "#side"},
+ "east": {"uv": [8, 0, 16, 8], "texture": "#top"},
+ "south": {"uv": [0, 0, 8, 8], "texture": "#top"},
+ "west": {"uv": [0, 0, 8, 8], "texture": "#side"},
+ "up": {"uv": [0, 0, 8, 8], "texture": "#top"},
+ "down": {"uv": [0, 8, 8, 16], "texture": "#top"}
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/assets/models/ignimious_bulb.bbmodel b/assets/models/ignimious_bulb.bbmodel
new file mode 100644
index 00000000..2c3b6593
--- /dev/null
+++ b/assets/models/ignimious_bulb.bbmodel
@@ -0,0 +1 @@
+{"meta":{"format_version":"4.5","model_format":"modded_entity","box_uv":true},"name":"ignimious_bulb","model_identifier":"","modded_entity_version":"Fabric 1.17+","modded_entity_flip_y":true,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{},"resolution":{"width":256,"height":256},"elements":[{"name":"jaw","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-23.956753782540364,-5.009498774448022,-24.13052619222006],"to":[24.043246217459636,17.990501225551977,23.86947380777994],"autouv":0,"color":8,"origin":[0.04324621745963608,-5.009498774448022,-0.13052619222006],"faces":{"north":{"uv":[48,48,96,71],"texture":0},"east":{"uv":[0,48,48,71],"texture":0},"south":{"uv":[144,48,192,71],"texture":0},"west":{"uv":[96,48,144,71],"texture":0},"up":{"uv":[96,48,48,0],"texture":0},"down":{"uv":[144,0,96,48],"texture":0}},"type":"cube","uuid":"800563f2-e637-d42d-6c38-616b6b34c9ca"},{"name":"head","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-22.956753782540364,10.990501225551977,-22.13052619222006],"to":[23.043246217459636,33.99050122555197,23.86947380777994],"autouv":0,"color":8,"rotation":[7.5,0,0],"origin":[0.04324621745963608,17.990501225551977,23.86947380777994],"uv_offset":[0,71],"faces":{"north":{"uv":[46,117,92,140],"texture":0},"east":{"uv":[0,117,46,140],"texture":0},"south":{"uv":[138,117,184,140],"texture":0},"west":{"uv":[92,117,138,140],"texture":0},"up":{"uv":[92,117,46,71],"texture":0},"down":{"uv":[138,71,92,117],"texture":0}},"type":"cube","uuid":"e6d05459-6b50-12b5-a46a-eb72b97f7c09"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-19,-9,-61],"to":[13,-9,-29],"autouv":0,"color":9,"rotation":[27.320650966819105,-50.33976442181192,-6.793186828559589],"origin":[0,-3,-1],"uv_offset":[112,0],"faces":{"north":{"uv":[144,32,176,32],"texture":0},"east":{"uv":[112,32,144,32],"texture":0},"south":{"uv":[208,32,240,32],"texture":0},"west":{"uv":[176,32,208,32],"texture":0},"up":{"uv":[176,32,144,0],"texture":0},"down":{"uv":[208,0,176,32],"texture":0}},"type":"cube","uuid":"42f404db-2eb0-2efd-bb09-8810376fcfbc"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-16,-3,-60],"to":[16,-3,-28],"autouv":0,"color":9,"rotation":[16.681567243909033,33.56061965535128,3.4685333161320835],"origin":[0,-3,-1],"uv_offset":[112,0],"faces":{"north":{"uv":[144,32,176,32],"texture":0},"east":{"uv":[112,32,144,32],"texture":0},"south":{"uv":[208,32,240,32],"texture":0},"west":{"uv":[176,32,208,32],"texture":0},"up":{"uv":[176,32,144,0],"texture":0},"down":{"uv":[208,0,176,32],"texture":0}},"type":"cube","uuid":"4c00dc9a-fef0-30b4-0f06-8bee9fe9b898"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-16,-6,-56],"to":[16,-6,-24],"autouv":0,"color":9,"rotation":[-162.2403464416216,58.02175378934316,-176.80418113816847],"origin":[0,-3,-1],"uv_offset":[112,0],"faces":{"north":{"uv":[144,32,176,32],"texture":0},"east":{"uv":[112,32,144,32],"texture":0},"south":{"uv":[208,32,240,32],"texture":0},"west":{"uv":[176,32,208,32],"texture":0},"up":{"uv":[176,32,144,0],"texture":0},"down":{"uv":[208,0,176,32],"texture":0}},"type":"cube","uuid":"5db8a4ea-e690-f657-b64a-323ec0c3a305"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-24,-6,-62],"to":[8,-6,-30],"autouv":0,"color":9,"rotation":[-169.75684992659043,-35.13964450125722,-170.6241287065347],"origin":[0,-3,-1],"uv_offset":[112,0],"faces":{"north":{"uv":[144,32,176,32],"texture":0},"east":{"uv":[112,32,144,32],"texture":0},"south":{"uv":[208,32,240,32],"texture":0},"west":{"uv":[176,32,208,32],"texture":0},"up":{"uv":[176,32,144,0],"texture":0},"down":{"uv":[208,0,176,32],"texture":0}},"type":"cube","uuid":"b2bbe86a-3379-0246-9181-127aed715432"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-16,-13,-58],"to":[16,-13,-26],"autouv":0,"color":9,"rotation":[144.36680378552091,-83.99459398899594,-115.75078983303227],"origin":[0,-3,-1],"uv_offset":[112,0],"faces":{"north":{"uv":[144,32,176,32],"texture":0},"east":{"uv":[112,32,144,32],"texture":0},"south":{"uv":[208,32,240,32],"texture":0},"west":{"uv":[176,32,208,32],"texture":0},"up":{"uv":[176,32,144,0],"texture":0},"down":{"uv":[208,0,176,32],"texture":0}},"type":"cube","uuid":"c63affed-9e2a-84af-5766-631a13679757"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-16,-9,-59],"to":[16,-9,-27],"autouv":0,"color":9,"rotation":[28.459049469615778,-5.7440354970636935,0.03610759351412133],"origin":[0,-3,-1],"uv_offset":[112,0],"faces":{"north":{"uv":[144,32,176,32],"texture":0},"east":{"uv":[112,32,144,32],"texture":0},"south":{"uv":[208,32,240,32],"texture":0},"west":{"uv":[176,32,208,32],"texture":0},"up":{"uv":[176,32,144,0],"texture":0},"down":{"uv":[208,0,176,32],"texture":0}},"type":"cube","uuid":"89bc35ae-6e61-dfa8-745f-0e317bb63260"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-12,-4,-52],"to":[20,-4,-20],"autouv":0,"color":9,"rotation":[40.560964930961916,83.77970397136126,27.74070945144022],"origin":[0,-3,-1],"uv_offset":[112,0],"faces":{"north":{"uv":[144,32,176,32],"texture":0},"east":{"uv":[112,32,144,32],"texture":0},"south":{"uv":[208,32,240,32],"texture":0},"west":{"uv":[176,32,208,32],"texture":0},"up":{"uv":[176,32,144,0],"texture":0},"down":{"uv":[208,0,176,32],"texture":0}},"type":"cube","uuid":"0a5c37c6-ad13-697a-58cd-934841edb237"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-18,-15,-61],"to":[14,-15,-29],"autouv":0,"color":9,"rotation":[-149.33066739822777,4.299459188610997,-178.742402181358],"origin":[0,-3,-1],"uv_offset":[112,0],"faces":{"north":{"uv":[144,32,176,32],"texture":0},"east":{"uv":[112,32,144,32],"texture":0},"south":{"uv":[208,32,240,32],"texture":0},"west":{"uv":[176,32,208,32],"texture":0},"up":{"uv":[176,32,144,0],"texture":0},"down":{"uv":[208,0,176,32],"texture":0}},"type":"cube","uuid":"d8b4bbe0-9707-0652-35e6-e28a54179fa3"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-34,-10,-84],"to":[30,-10,-20],"autouv":0,"color":9,"rotation":[111.7261647239295,-85.5550612830785,-96.58324240444844],"origin":[0,-3,-1],"uv_offset":[-64,140],"faces":{"north":{"uv":[0,204,64,204],"texture":0},"east":{"uv":[-64,204,0,204],"texture":0},"south":{"uv":[128,204,192,204],"texture":0},"west":{"uv":[64,204,128,204],"texture":0},"up":{"uv":[64,204,0,140],"texture":0},"down":{"uv":[128,140,64,204],"texture":0}},"type":"cube","uuid":"4e7c3e97-535b-9261-83a4-80fb76cc13d4"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-34,-10,-84],"to":[30,-10,-20],"autouv":0,"color":9,"rotation":[62.10613287479732,85.7200584727182,46.23842221938251],"origin":[0,-3,-1],"uv_offset":[-64,140],"faces":{"north":{"uv":[0,204,64,204],"texture":0},"east":{"uv":[-64,204,0,204],"texture":0},"south":{"uv":[128,204,192,204],"texture":0},"west":{"uv":[64,204,128,204],"texture":0},"up":{"uv":[64,204,0,140],"texture":0},"down":{"uv":[128,140,64,204],"texture":0}},"type":"cube","uuid":"2184d35d-e78b-c493-b1c4-a817cb45fa8b"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-34,-10,-84],"to":[30,-10,-20],"autouv":0,"color":9,"rotation":[-164.484342402648,7.951483564968085,-175.61972440808503],"origin":[0,-3,-1],"uv_offset":[-64,140],"faces":{"north":{"uv":[0,204,64,204],"texture":0},"east":{"uv":[-64,204,0,204],"texture":0},"south":{"uv":[128,204,192,204],"texture":0},"west":{"uv":[64,204,128,204],"texture":0},"up":{"uv":[64,204,0,140],"texture":0},"down":{"uv":[128,140,64,204],"texture":0}},"type":"cube","uuid":"24212edf-8ab0-e1e5-b67e-dd27ca7fee4f"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-34,-6,-84],"to":[30,-6,-20],"autouv":0,"color":9,"rotation":[15.186954129789529,2.0049709481247944,-1.9011648291348386],"origin":[0,-3,-1],"uv_offset":[-64,140],"faces":{"north":{"uv":[0,204,64,204],"texture":0},"east":{"uv":[-64,204,0,204],"texture":0},"south":{"uv":[128,204,192,204],"texture":0},"west":{"uv":[64,204,128,204],"texture":0},"up":{"uv":[64,204,0,140],"texture":0},"down":{"uv":[128,140,64,204],"texture":0}},"type":"cube","uuid":"dfd15693-f566-9bc7-b953-d303dec3ba18"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-17.373117897112767,65.61517788582364,17.389978221486494],"to":[14.626882102887233,65.61517788582364,49.389978221486494],"autouv":0,"color":9,"rotation":[113.4590494696158,-5.744035497063647,0.03610759351412453],"origin":[-1.3731178971127687,38.61517788582365,12.389978221486501],"uv_offset":[112,0],"faces":{"north":{"uv":[144,32,176,32],"texture":0},"east":{"uv":[112,32,144,32],"texture":0},"south":{"uv":[208,32,240,32],"texture":0},"west":{"uv":[176,32,208,32],"texture":0},"up":{"uv":[176,32,144,0],"texture":0},"down":{"uv":[208,0,176,32],"texture":0}},"type":"cube","uuid":"e020ca0b-24eb-a3cc-43ed-e87b0013e04d"}],"outliner":[{"name":"head","origin":[0,17,24],"rotation":[7.492942810808386,-0.32621370972236063,2.478638942680466],"color":0,"uuid":"e54419b9-7a64-8b11-6ad9-cea74cb14f41","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["e6d05459-6b50-12b5-a46a-eb72b97f7c09","800563f2-e637-d42d-6c38-616b6b34c9ca","e020ca0b-24eb-a3cc-43ed-e87b0013e04d"]},{"name":"leaves","origin":[0,-3,-1],"color":0,"uuid":"7cf17cab-353d-fddc-814d-3b708cb33637","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["4c00dc9a-fef0-30b4-0f06-8bee9fe9b898","42f404db-2eb0-2efd-bb09-8810376fcfbc","b2bbe86a-3379-0246-9181-127aed715432","c63affed-9e2a-84af-5766-631a13679757","4e7c3e97-535b-9261-83a4-80fb76cc13d4","dfd15693-f566-9bc7-b953-d303dec3ba18","2184d35d-e78b-c493-b1c4-a817cb45fa8b","24212edf-8ab0-e1e5-b67e-dd27ca7fee4f","89bc35ae-6e61-dfa8-745f-0e317bb63260","0a5c37c6-ad13-697a-58cd-934841edb237","d8b4bbe0-9707-0652-35e6-e28a54179fa3","5db8a4ea-e690-f657-b64a-323ec0c3a305"]}],"textures":[{"path":"/home/sollace/Documents/GitRepos/minecraft_mods/Unicopia/assets/models/bulb.png","name":"bulb.png","folder":"block","namespace":"","id":"0","particle":false,"render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"mode":"bitmap","saved":true,"uuid":"742fdfc6-ad6a-1d7e-e84d-c8133537cff8","relative_path":"../bulb.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/slime_pustule_cap.json b/assets/models/slime_pustule_cap.json
deleted file mode 100644
index f337aa4f..00000000
--- a/assets/models/slime_pustule_cap.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "parent": "minecraft:block/cube_all",
- "textures": {
- "all": "unicopia:block/slime_pustule",
- "particle": "unicopia:block/slime_pustule"
- },
- "elements": [
- {
- "from": [7, 14.9, 7],
- "to": [12, 16, 12],
- "faces": {
- "north": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
- "east": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
- "south": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
- "west": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
- "up": {"uv": [0, 0, 6, 6], "texture": "#all"},
- "down": {"uv": [0, 0, 6, 6], "texture": "#all"}
- }
- },
- {
- "from": [3, 15, 4],
- "to": [9, 16, 10],
- "faces": {
- "north": {"uv": [0, 0, 6, 1], "texture": "#all"},
- "east": {"uv": [0, 0, 6, 1], "texture": "#all"},
- "south": {"uv": [0, 0, 6, 1], "texture": "#all"},
- "west": {"uv": [0, 0, 6, 1], "texture": "#all"},
- "up": {"uv": [0, 0, 6, 6], "texture": "#all"},
- "down": {"uv": [0, 0, 6, 6], "texture": "#all"}
- }
- },
- {
- "name": "rope",
- "from": [7, 10, 7],
- "to": [9, 15, 9],
- "faces": {
- "north": {"uv": [1, 7, 5, 13], "texture": "#all"},
- "east": {"uv": [1, 7, 5, 13], "texture": "#all"},
- "south": {"uv": [1, 7, 5, 13], "texture": "#all"},
- "west": {"uv": [1, 7, 5, 13], "texture": "#all"},
- "up": {"uv": [2, 2, 4, 4], "texture": "#all"},
- "down": {"uv": [2, 2, 4, 4], "texture": "#all"}
- }
- }
- ]
-}
\ No newline at end of file
diff --git a/assets/models/slime_pustule_pod.json b/assets/models/slime_pustule_pod.json
deleted file mode 100644
index 7c15dd7b..00000000
--- a/assets/models/slime_pustule_pod.json
+++ /dev/null
@@ -1,137 +0,0 @@
-{
- "parent": "minecraft:block/cube_all",
- "textures": {
- "all": "unicopia:block/slime_pustule",
- "particle": "unicopia:block/slime_pustule"
- },
- "elements": [
- {
- "name": "rope",
- "from": [7.5, 0, 7.5],
- "to": [8.5, 16, 8.5],
- "faces": {
- "north": {"uv": [12, 0, 13, 16], "texture": "#all"},
- "east": {"uv": [13, 0, 14, 16], "texture": "#all"},
- "south": {"uv": [15, 0, 16, 16], "texture": "#all"},
- "west": {"uv": [14, 0, 15, 16], "texture": "#all"},
- "up": {"uv": [13, 0, 14, 1], "texture": "#all"},
- "down": {"uv": [12, 0, 13, 1], "texture": "#all"}
- }
- },
- {
- "from": [7, 14.9, 7],
- "to": [12, 16, 12],
- "faces": {
- "north": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
- "east": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
- "south": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
- "west": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
- "up": {"uv": [0, 0, 6, 6], "texture": "#all"},
- "down": {"uv": [0, 0, 6, 6], "texture": "#all"}
- }
- },
- {
- "from": [3, 15, 4],
- "to": [9, 16, 10],
- "faces": {
- "north": {"uv": [0, 0, 6, 1], "texture": "#all"},
- "east": {"uv": [0, 0, 6, 1], "texture": "#all"},
- "south": {"uv": [0, 0, 6, 1], "texture": "#all"},
- "west": {"uv": [0, 0, 6, 1], "texture": "#all"},
- "up": {"uv": [0, 0, 6, 6], "texture": "#all"},
- "down": {"uv": [0, 0, 6, 6], "texture": "#all"}
- }
- },
- {
- "name": "rope",
- "from": [7, 10, 7],
- "to": [9, 15, 9],
- "faces": {
- "north": {"uv": [1, 7, 5, 13], "texture": "#all"},
- "east": {"uv": [1, 7, 5, 13], "texture": "#all"},
- "south": {"uv": [1, 7, 5, 13], "texture": "#all"},
- "west": {"uv": [1, 7, 5, 13], "texture": "#all"},
- "up": {"uv": [2, 2, 4, 4], "texture": "#all"},
- "down": {"uv": [2, 2, 4, 4], "texture": "#all"}
- }
- },
- {
- "name": "rope",
- "from": [7, 15, 7],
- "to": [9, 20, 9],
- "faces": {
- "north": {"uv": [2, 7, 4, 13], "texture": "#all"},
- "east": {"uv": [2, 7, 4, 13], "texture": "#all"},
- "south": {"uv": [2, 7, 4, 13], "texture": "#all"},
- "west": {"uv": [2, 7, 4, 13], "texture": "#all"},
- "up": {"uv": [2, 2, 4, 4], "texture": "#all"},
- "down": {"uv": [2, 2, 4, 4], "texture": "#all"}
- }
- },
- {
- "name": "drop",
- "from": [5, 10, 5],
- "to": [11, 13, 11],
- "faces": {
- "north": {"uv": [0, 6, 6, 9], "texture": "#all"},
- "east": {"uv": [0, 6, 6, 9], "texture": "#all"},
- "south": {"uv": [0, 6, 6, 9], "texture": "#all"},
- "west": {"uv": [0, 6, 6, 9], "texture": "#all"},
- "up": {"uv": [0, 0, 6, 6], "texture": "#all"},
- "down": {"uv": [0, 0, 6, 6], "texture": "#all"}
- }
- },
- {
- "name": "drop",
- "from": [6, 13, 6],
- "to": [10, 15, 10],
- "faces": {
- "north": {"uv": [0, 6, 6, 8], "texture": "#all"},
- "east": {"uv": [0, 6, 6, 8], "texture": "#all"},
- "south": {"uv": [0, 6, 6, 8], "texture": "#all"},
- "west": {"uv": [0, 6, 6, 8], "texture": "#all"},
- "up": {"uv": [0, 0, 6, 6], "texture": "#all"},
- "down": {"uv": [0, 0, 6, 6], "texture": "#all"}
- }
- },
- {
- "name": "drop",
- "from": [5, 0, 5],
- "to": [11, 1, 11],
- "faces": {
- "north": {"uv": [0, 13, 6, 14], "texture": "#all"},
- "east": {"uv": [0, 13, 6, 14], "texture": "#all"},
- "south": {"uv": [0, 13, 6, 14], "texture": "#all"},
- "west": {"uv": [0, 13, 6, 14], "texture": "#all"},
- "up": {"uv": [0, 0, 6, 6], "texture": "#all"},
- "down": {"uv": [0, 0, 6, 6], "texture": "#all"}
- }
- },
- {
- "name": "drop",
- "from": [4, 1, 4],
- "to": [12, 10, 12],
- "faces": {
- "north": {"uv": [0, 6, 6, 14], "texture": "#all"},
- "east": {"uv": [0, 6, 6, 14], "texture": "#all"},
- "south": {"uv": [0, 6, 6, 14], "texture": "#all"},
- "west": {"uv": [0, 6, 6, 14], "texture": "#all"},
- "up": {"uv": [0, 0, 6, 6], "texture": "#all"},
- "down": {"uv": [0, 0, 6, 6], "texture": "#all"}
- }
- },
- {
- "name": "drop",
- "from": [5, 2, 5],
- "to": [11, 9, 11],
- "faces": {
- "north": {"uv": [7, 7, 11, 13], "texture": "#all"},
- "east": {"uv": [7, 7, 11, 13], "texture": "#all"},
- "south": {"uv": [7, 7, 11, 13], "texture": "#all"},
- "west": {"uv": [7, 7, 11, 13], "texture": "#all"},
- "up": {"uv": [7, 1, 11, 5], "texture": "#all"},
- "down": {"uv": [7, 1, 11, 5], "texture": "#all"}
- }
- }
- ]
-}
\ No newline at end of file
diff --git a/assets/models/slime_pustule_rope.json b/assets/models/slime_pustule_rope.json
deleted file mode 100644
index aab23dfc..00000000
--- a/assets/models/slime_pustule_rope.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "parent": "minecraft:block/cube_all",
- "textures": {
- "all": "unicopia:block/slime_pustule",
- "particle": "unicopia:block/slime_pustule"
- },
- "elements": [
- {
- "name": "rope",
- "from": [7.5, 0, 7.5],
- "to": [8.5, 16, 8.5],
- "faces": {
- "north": {"uv": [12, 0, 13, 16], "texture": "#all"},
- "east": {"uv": [13, 0, 14, 16], "texture": "#all"},
- "south": {"uv": [15, 0, 16, 16], "texture": "#all"},
- "west": {"uv": [14, 0, 15, 16], "texture": "#all"},
- "up": {"uv": [13, 0, 14, 1], "texture": "#all"},
- "down": {"uv": [12, 0, 13, 1], "texture": "#all"}
- }
- }
- ]
-}
\ No newline at end of file
diff --git a/assets/models/tentacle.bbmodel b/assets/models/tentacle.bbmodel
new file mode 100644
index 00000000..0ccec62c
--- /dev/null
+++ b/assets/models/tentacle.bbmodel
@@ -0,0 +1 @@
+{"meta":{"format_version":"4.5","model_format":"modded_entity","box_uv":true},"name":"tentacle","model_identifier":"","modded_entity_version":"Fabric 1.17+","modded_entity_flip_y":true,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{},"resolution":{"width":128,"height":128},"elements":[{"name":"a","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-7,-6,-7],"to":[7,10,7],"autouv":0,"color":7,"origin":[0,-6,0],"faces":{"north":{"uv":[14,14,28,30],"texture":0},"east":{"uv":[0,14,14,30],"texture":0},"south":{"uv":[42,14,56,30],"texture":0},"west":{"uv":[28,14,42,30],"texture":0},"up":{"uv":[28,14,14,0],"texture":0},"down":{"uv":[42,0,28,14],"texture":0}},"type":"cube","uuid":"43deca74-ef0e-9496-f060-a569a5f66a3c"},{"name":"b","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-6,8,-6],"to":[6,27,6],"autouv":0,"color":7,"origin":[0,0,0],"uv_offset":[0,30],"faces":{"north":{"uv":[12,42,24,61],"texture":0},"east":{"uv":[0,42,12,61],"texture":0},"south":{"uv":[36,42,48,61],"texture":0},"west":{"uv":[24,42,36,61],"texture":0},"up":{"uv":[24,42,12,30],"texture":0},"down":{"uv":[36,30,24,42],"texture":0}},"type":"cube","uuid":"2f7bd122-e685-5397-518a-d566774b5f7f"},{"name":"c","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-5,25,-5],"to":[5,48,5],"autouv":0,"color":7,"origin":[0,26,0],"uv_offset":[48,20],"faces":{"north":{"uv":[58,30,68,53],"texture":0},"east":{"uv":[48,30,58,53],"texture":0},"south":{"uv":[78,30,88,53],"texture":0},"west":{"uv":[68,30,78,53],"texture":0},"up":{"uv":[68,30,58,20],"texture":0},"down":{"uv":[78,20,68,30],"texture":0}},"type":"cube","uuid":"a32e4c53-99d7-93d5-23f7-ae163af4dae5"},{"name":"d","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-4,45,-4],"to":[4,66,4],"autouv":0,"color":7,"origin":[0,26,0],"uv_offset":[40,53],"faces":{"north":{"uv":[48,61,56,82],"texture":0},"east":{"uv":[40,61,48,82],"texture":0},"south":{"uv":[64,61,72,82],"texture":0},"west":{"uv":[56,61,64,82],"texture":0},"up":{"uv":[56,61,48,53],"texture":0},"down":{"uv":[64,53,56,61],"texture":0}},"type":"cube","uuid":"a4c5b57f-c555-154e-e673-7e114f6e5a21"},{"name":"e","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-3,64,-3],"to":[3,86,3],"autouv":0,"color":7,"origin":[-1.1793215979571126e-16,26,-4.382008627821946e-33],"uv_offset":[0,61],"faces":{"north":{"uv":[6,67,12,89],"texture":0},"east":{"uv":[0,67,6,89],"texture":0},"south":{"uv":[18,67,24,89],"texture":0},"west":{"uv":[12,67,18,89],"texture":0},"up":{"uv":[12,67,6,61],"texture":0},"down":{"uv":[18,61,12,67],"texture":0}},"type":"cube","uuid":"c8af392a-28ea-8a9f-cc0f-e8f19558f993"},{"name":"f","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-3,85,-3],"to":[3,100,3],"autouv":0,"color":7,"origin":[-2.620714662126916e-16,47,-9.737796950715435e-33],"uv_offset":[72,53],"faces":{"north":{"uv":[78,59,84,74],"texture":0},"east":{"uv":[72,59,78,74],"texture":0},"south":{"uv":[90,59,96,74],"texture":0},"west":{"uv":[84,59,90,74],"texture":0},"up":{"uv":[84,59,78,53],"texture":0},"down":{"uv":[90,53,84,59],"texture":0}},"type":"cube","uuid":"1d4836bc-6b01-9b8b-2958-1280f80f91b7"},{"name":"g","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-2.5999999999999996,99,-2.5999999999999996],"to":[2.5999999999999996,114,2.599999999999998],"autouv":0,"color":7,"origin":[-3.6034826604245074e-16,0,1],"uv_offset":[56,0],"faces":{"north":{"uv":[61.199999999999996,5.1999999999999975,66.39999999999999,20.199999999999996],"texture":0},"east":{"uv":[56,5.1999999999999975,61.199999999999996,20.199999999999996],"texture":0},"south":{"uv":[71.6,5.1999999999999975,76.8,20.199999999999996],"texture":0},"west":{"uv":[66.39999999999999,5.1999999999999975,71.6,20.199999999999996],"texture":0},"up":{"uv":[66.39999999999999,5.1999999999999975,61.199999999999996,0],"texture":0},"down":{"uv":[71.6,0,66.39999999999999,5.1999999999999975],"texture":0}},"type":"cube","uuid":"4001f6b1-caf3-d76f-2d5b-8636c85cd0fb"},{"name":"h","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-2.0999999999999996,113,-2.0999999999999996],"to":[2.0999999999999996,128,2.099999999999998],"autouv":0,"color":7,"origin":[-4.455214925615753e-16,14,1],"uv_offset":[24,61],"faces":{"north":{"uv":[28.199999999999996,65.2,32.4,80.19999999999999],"texture":0},"east":{"uv":[24,65.2,28.199999999999996,80.19999999999999],"texture":0},"south":{"uv":[36.599999999999994,65.2,40.8,80.19999999999999],"texture":0},"west":{"uv":[32.4,65.2,36.599999999999994,80.19999999999999],"texture":0},"up":{"uv":[32.4,65.2,28.199999999999996,61],"texture":0},"down":{"uv":[36.599999999999994,61,32.4,65.2],"texture":0}},"type":"cube","uuid":"4ba83545-3fcf-726d-033c-a1ac411a4ae3"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[3,-0.6703481918674496,-7.772537561589672],"to":[17,-0.6703481918674496,6.227462438410338],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[3,-0.6703481918674496,-7.772537561589672],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"43754e70-3fcd-b60a-62ee-01eadc22607a"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[3,-0.6703481918674496,-7.772537561589672],"to":[17,-0.6703481918674496,6.227462438410338],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[3,-0.6703481918674496,-7.772537561589672],"uv_offset":[86,0],"faces":{"north":{"uv":[100,14.000000000000002,114,14.000000000000002],"texture":0},"east":{"uv":[86,14.000000000000002,100,14.000000000000002],"texture":0},"south":{"uv":[128,14.000000000000002,142,14.000000000000002],"texture":0},"west":{"uv":[114,14.000000000000002,128,14.000000000000002],"texture":0},"up":{"uv":[114,14.000000000000002,100,0],"texture":0},"down":{"uv":[128,0,114,14.000000000000002],"texture":0}},"type":"cube","uuid":"e5054bff-a980-a8dc-0221-58208085978f"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[5,-0.6703481918674511,4.227462438410328],"to":[19,-0.6703481918674511,18.22746243841034],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[5,-0.6703481918674511,4.227462438410328],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"13ebb478-c830-3a6e-858d-2269a5ea0642"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[5,-0.6703481918674511,4.227462438410328],"to":[19,-0.6703481918674511,18.22746243841034],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[5,-0.6703481918674511,4.227462438410328],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"2a87966b-98f6-fe32-0698-06d07dd0caa9"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-6,-0.6703481918674511,4.227462438410328],"to":[8,-0.6703481918674511,18.22746243841034],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[-6,-0.6703481918674511,4.227462438410328],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"b2e2dbad-e1e3-ecb6-41d7-21cb1908deb8"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-6,-0.6703481918674511,4.227462438410328],"to":[8,-0.6703481918674511,18.22746243841034],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[-6,-0.6703481918674511,4.227462438410328],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"f93aa31f-c197-3e66-58ad-a1c638ecce97"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-6,-0.6703481918674499,-6.772537561589672],"to":[8,-0.6703481918674499,7.227462438410338],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[-6,-0.6703481918674499,-6.772537561589672],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"c1ca47a6-f9ba-555a-cc32-58d55b8809b9"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-6,-0.6703481918674499,-6.772537561589672],"to":[8,-0.6703481918674499,7.227462438410338],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[-6,-0.6703481918674499,-6.772537561589672],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"b8b98b2d-632b-3acd-29a8-55efa4850547"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[3,-0.6703481918674496,-7.772537561589672],"to":[17,-0.6703481918674496,6.227462438410338],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[3,-0.6703481918674496,-7.772537561589672],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"e3aaee1e-958c-370c-a10d-94e142df629c"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[3,-0.6703481918674496,-7.772537561589672],"to":[17,-0.6703481918674496,6.227462438410338],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[3,-0.6703481918674496,-7.772537561589672],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"6bf59ab2-03d6-2995-17bc-76e27e52bf6c"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[5,-0.6703481918674511,4.227462438410328],"to":[19,-0.6703481918674511,18.22746243841034],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[5,-0.6703481918674511,4.227462438410328],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"666a373c-9dd2-0cfc-c1b7-13a5fe7fe392"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[5,-0.6703481918674511,4.227462438410328],"to":[19,-0.6703481918674511,18.22746243841034],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[5,-0.6703481918674511,4.227462438410328],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"ebaca892-208f-1ec8-a7b0-4b8bf692d869"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-6,-0.6703481918674511,4.227462438410328],"to":[8,-0.6703481918674511,18.22746243841034],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[-6,-0.6703481918674511,4.227462438410328],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"02bb3148-7ad4-1fbe-f233-fcd02c1f00f4"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-6,-0.6703481918674511,4.227462438410328],"to":[8,-0.6703481918674511,18.22746243841034],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[-6,-0.6703481918674511,4.227462438410328],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"ea8790f9-39c1-9607-7faf-4054f0782315"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-6,-0.6703481918674499,-6.772537561589672],"to":[8,-0.6703481918674499,7.227462438410338],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[-6,-0.6703481918674499,-6.772537561589672],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"42f35de8-e9da-314a-ea8b-4ec3162ccc7b"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-6,-0.6703481918674499,-6.772537561589672],"to":[8,-0.6703481918674499,7.227462438410338],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[-6,-0.6703481918674499,-6.772537561589672],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"2a148807-d884-e05b-680e-36ba4ce450e5"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-4,40.62422566484149,-3.3435187525409757],"to":[10,40.62422566484149,10.656481247459041],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[-4,40.62422566484149,-3.3435187525409757],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"e0119d00-cad6-3842-bbe5-6885628ff35e"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-4,40.62422566484149,-3.3435187525409757],"to":[10,40.62422566484149,10.656481247459041],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[-4,40.62422566484149,-3.3435187525409757],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.00000000000001,114.00000000000001,14.00000000000001],"texture":0},"east":{"uv":[86,14.00000000000001,100.00000000000001,14.00000000000001],"texture":0},"south":{"uv":[128.00000000000003,14.00000000000001,142.00000000000003,14.00000000000001],"texture":0},"west":{"uv":[114.00000000000001,14.00000000000001,128.00000000000003,14.00000000000001],"texture":0},"up":{"uv":[114.00000000000001,14.00000000000001,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.00000000000001],"texture":0}},"type":"cube","uuid":"abe6d75d-fccf-7b97-e8ca-156f68f30b76"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,60.13549813476108,3.0055163398616545],"to":[16,60.13549813476108,17.005516339861675],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[2,60.13549813476108,3.0055163398616545],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.000000000000018,114.00000000000001,14.000000000000018],"texture":0},"east":{"uv":[86,14.000000000000018,100.00000000000001,14.000000000000018],"texture":0},"south":{"uv":[128.00000000000003,14.000000000000018,142.00000000000003,14.000000000000018],"texture":0},"west":{"uv":[114.00000000000001,14.000000000000018,128.00000000000003,14.000000000000018],"texture":0},"up":{"uv":[114.00000000000001,14.000000000000018,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.000000000000018],"texture":0}},"type":"cube","uuid":"bf2e26ba-937a-73c8-3ab2-9240c2690b0b"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2,60.13549813476108,3.0055163398616545],"to":[16,60.13549813476108,17.005516339861675],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[2,60.13549813476108,3.0055163398616545],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000001,14.000000000000018,114.00000000000001,14.000000000000018],"texture":0},"east":{"uv":[86,14.000000000000018,100.00000000000001,14.000000000000018],"texture":0},"south":{"uv":[128.00000000000003,14.000000000000018,142.00000000000003,14.000000000000018],"texture":0},"west":{"uv":[114.00000000000001,14.000000000000018,128.00000000000003,14.000000000000018],"texture":0},"up":{"uv":[114.00000000000001,14.000000000000018,100.00000000000001,0],"texture":0},"down":{"uv":[128,0,114.00000000000001,14.000000000000018],"texture":0}},"type":"cube","uuid":"61246d8c-3a21-f2e2-2b5e-656b618b9315"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2.0000000000000004,89.02514437942168,0.47799980017960014],"to":[16,89.02514437942168,14.47799980017963],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[2.0000000000000004,89.02514437942168,0.47799980017960014],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000003,14.000000000000021,114.00000000000003,14.000000000000021],"texture":0},"east":{"uv":[86,14.000000000000021,100.00000000000003,14.000000000000021],"texture":0},"south":{"uv":[128.00000000000006,14.000000000000021,142.00000000000006,14.000000000000021],"texture":0},"west":{"uv":[114.00000000000003,14.000000000000021,128.00000000000006,14.000000000000021],"texture":0},"up":{"uv":[114.00000000000003,14.000000000000021,100.00000000000003,0],"texture":0},"down":{"uv":[128.00000000000003,0,114.00000000000003,14.000000000000021],"texture":0}},"type":"cube","uuid":"ff5dbdd3-d6eb-27ba-e1ab-c1a0f550fd3e"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[2.0000000000000004,89.02514437942168,0.47799980017960014],"to":[16,89.02514437942168,14.47799980017963],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[2.0000000000000004,89.02514437942168,0.47799980017960014],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000003,14.000000000000021,114.00000000000003,14.000000000000021],"texture":0},"east":{"uv":[86,14.000000000000021,100.00000000000003,14.000000000000021],"texture":0},"south":{"uv":[128.00000000000006,14.000000000000021,142.00000000000006,14.000000000000021],"texture":0},"west":{"uv":[114.00000000000003,14.000000000000021,128.00000000000006,14.000000000000021],"texture":0},"up":{"uv":[114.00000000000003,14.000000000000021,100.00000000000003,0],"texture":0},"down":{"uv":[128.00000000000003,0,114.00000000000003,14.000000000000021],"texture":0}},"type":"cube","uuid":"8e0f248f-1228-4a38-2a74-cb0d23718fdc"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-22.881490097225765,145.88837332325411,6.8760974475566785],"to":[-8.881490097225756,145.88837332325411,20.876097447556763],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[-36.04320341964982,110.62188448501286,-2.507082580585397],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000009,14.000000000000085,114.0000000000001,14.000000000000085],"texture":0},"east":{"uv":[86,14.000000000000085,100.00000000000009,14.000000000000085],"texture":0},"south":{"uv":[128.00000000000017,14.000000000000085,142.0000000000002,14.000000000000085],"texture":0},"west":{"uv":[114.0000000000001,14.000000000000085,128.00000000000017,14.000000000000085],"texture":0},"up":{"uv":[114.0000000000001,14.000000000000085,100.00000000000009,0],"texture":0},"down":{"uv":[128.0000000000001,0,114.0000000000001,14.000000000000085],"texture":0}},"type":"cube","uuid":"817666d5-286f-6161-8446-3970704a3265"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[0.16641666179338577,107.95005796968688,-16.171809311462464],"to":[14.166416661793397,107.95005796968688,-2.17180931146247],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[-36.04320341964982,110.62188448501286,-2.507082580585397],"uv_offset":[86,0],"faces":{"north":{"uv":[100,13.999999999999993,114,13.999999999999993],"texture":0},"east":{"uv":[86,13.999999999999993,100,13.999999999999993],"texture":0},"south":{"uv":[128,13.999999999999993,142,13.999999999999993],"texture":0},"west":{"uv":[114,13.999999999999993,128,13.999999999999993],"texture":0},"up":{"uv":[114,13.999999999999993,100,0],"texture":0},"down":{"uv":[128,0,114,13.999999999999993],"texture":0}},"type":"cube","uuid":"5f3e16dd-eed6-733e-e1db-f1934b558e88"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-22.881490097225765,145.88837332325411,6.8760974475566785],"to":[-8.881490097225756,145.88837332325411,20.876097447556763],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[-36.04320341964982,110.62188448501286,-2.507082580585397],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000009,14.000000000000085,114.00000000000009,14.000000000000085],"texture":0},"east":{"uv":[86,14.000000000000085,100.00000000000009,14.000000000000085],"texture":0},"south":{"uv":[128.00000000000017,14.000000000000085,142.00000000000017,14.000000000000085],"texture":0},"west":{"uv":[114.00000000000009,14.000000000000085,128.00000000000017,14.000000000000085],"texture":0},"up":{"uv":[114.00000000000009,14.000000000000085,100.00000000000009,0],"texture":0},"down":{"uv":[128.0000000000001,0,114.00000000000009,14.000000000000085],"texture":0}},"type":"cube","uuid":"573cf719-c66a-d73e-148d-d14e9617d0ac"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[0.16641666179338577,107.95005796968688,-16.171809311462464],"to":[14.166416661793397,107.95005796968688,-2.17180931146247],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[-36.04320341964982,110.62188448501286,-2.507082580585397],"uv_offset":[86,0],"faces":{"north":{"uv":[100,13.999999999999995,114,13.999999999999995],"texture":0},"east":{"uv":[86,13.999999999999995,100,13.999999999999995],"texture":0},"south":{"uv":[128,13.999999999999995,142,13.999999999999995],"texture":0},"west":{"uv":[114,13.999999999999995,128,13.999999999999995],"texture":0},"up":{"uv":[114,13.999999999999995,100,0],"texture":0},"down":{"uv":[128,0,114,13.999999999999995],"texture":0}},"type":"cube","uuid":"e4e3e2ae-0684-ee78-4ec9-33f0f9461b19"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[-22.881490097225765,145.88837332325411,6.8760974475566785],"to":[-8.881490097225756,145.88837332325411,20.876097447556763],"autouv":0,"color":0,"rotation":[-180,-45.000000000000156,90],"origin":[-36.04320341964982,110.62188448501286,-2.507082580585397],"uv_offset":[86,0],"faces":{"north":{"uv":[100.00000000000009,14.000000000000085,114.00000000000009,14.000000000000085],"texture":0},"east":{"uv":[86,14.000000000000085,100.00000000000009,14.000000000000085],"texture":0},"south":{"uv":[128.00000000000017,14.000000000000085,142.00000000000017,14.000000000000085],"texture":0},"west":{"uv":[114.00000000000009,14.000000000000085,128.00000000000017,14.000000000000085],"texture":0},"up":{"uv":[114.00000000000009,14.000000000000085,100.00000000000009,0],"texture":0},"down":{"uv":[128.0000000000001,0,114.00000000000009,14.000000000000085],"texture":0}},"type":"cube","uuid":"c59ea2f3-dbd7-5b92-4640-4acc2121a8b2"},{"name":"cube","box_uv":true,"rescale":false,"locked":false,"render_order":"default","allow_mirror_modeling":true,"from":[0.16641666179338577,107.95005796968688,-16.171809311462464],"to":[14.166416661793397,107.95005796968688,-2.17180931146247],"autouv":0,"color":0,"rotation":[-89.99999999999994,-6.3611093629270335e-15,45],"origin":[-36.04320341964982,110.62188448501286,-2.507082580585397],"uv_offset":[86,0],"faces":{"north":{"uv":[100,13.999999999999995,114,13.999999999999995],"texture":0},"east":{"uv":[86,13.999999999999995,100,13.999999999999995],"texture":0},"south":{"uv":[128,13.999999999999995,142,13.999999999999995],"texture":0},"west":{"uv":[114,13.999999999999995,128,13.999999999999995],"texture":0},"up":{"uv":[114,13.999999999999995,100,0],"texture":0},"down":{"uv":[128,0,114,13.999999999999995],"texture":0}},"type":"cube","uuid":"3bb469e2-e5a5-7cfc-3e9c-6fd43b8a3ca0"}],"outliner":[{"name":"bone_a","origin":[0,0,0],"rotation":[-7.951386703658792e-15,0,0],"color":0,"uuid":"836e9354-0309-70b5-cb60-e7f38118e743","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["43deca74-ef0e-9496-f060-a569a5f66a3c",{"name":"flower_4","origin":[-6,-0.6703481918674499,-6.772537561589672],"rotation":[-91.26598229300457,52.08446097361874,-64.05377573888674],"color":0,"uuid":"2ea8b2cf-a2c0-c454-2fb5-d9bc522a921c","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["c1ca47a6-f9ba-555a-cc32-58d55b8809b9","b8b98b2d-632b-3acd-29a8-55efa4850547",{"name":"flower_8","origin":[-6,-0.6703481918674499,-6.772537561589672],"rotation":[-167.64364206893785,46.98788282368353,-125.90165504433261],"color":0,"uuid":"02ef3343-dfcc-9af4-29a2-6dc427c70898","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["42f35de8-e9da-314a-ea8b-4ec3162ccc7b","2a148807-d884-e05b-680e-36ba4ce450e5"]}]},{"name":"flower_3","origin":[-6,-0.6703481918674511,4.227462438410328],"rotation":[137.96042965468234,-13.430580338215425,-145.38236406964168],"color":0,"uuid":"2fc2f20b-05ce-0410-dd40-8d55ec1d814a","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["b2e2dbad-e1e3-ecb6-41d7-21cb1908deb8","f93aa31f-c197-3e66-58ad-a1c638ecce97",{"name":"flower_7","origin":[-6,-0.6703481918674511,4.227462438410328],"rotation":[137.96042965468234,-13.430580338215425,-145.38236406964168],"color":0,"uuid":"3c08633a-6d30-3951-f49a-b815826de0cd","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["02bb3148-7ad4-1fbe-f233-fcd02c1f00f4","ea8790f9-39c1-9607-7faf-4054f0782315"]}]},{"name":"flower_2","origin":[5,-0.6703481918674511,4.227462438410328],"rotation":[72.75299322235041,-55.44873947197246,-103.02095072899253],"color":0,"uuid":"8cc35e91-dbf2-1e20-efc6-d0c6dc9e1e32","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["13ebb478-c830-3a6e-858d-2269a5ea0642","2a87966b-98f6-fe32-0698-06d07dd0caa9",{"name":"flower_6","origin":[5,-0.6703481918674511,4.227462438410328],"rotation":[72.75299322235041,-55.44873947197246,-103.02095072899253],"color":0,"uuid":"93297f86-e3ca-4d71-a6c8-298ff77bbb1a","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["666a373c-9dd2-0cfc-c1b7-13a5fe7fe392","ebaca892-208f-1ec8-a7b0-4b8bf692d869"]}]},{"name":"flower_1","origin":[3,-0.6703481918674496,-7.772537561589672],"rotation":[-34.96649542120608,3.067430398401471,-33.59739024796235],"color":0,"uuid":"59363886-4b5a-2a3c-75cd-8d6276de69b3","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["e5054bff-a980-a8dc-0221-58208085978f","43754e70-3fcd-b60a-62ee-01eadc22607a",{"name":"flower_5","origin":[3,-0.6703481918674496,-7.772537561589672],"rotation":[-34.96649542120608,3.067430398401471,-33.59739024796235],"color":0,"uuid":"0265d420-aeaa-b538-1252-89df12b0f9b6","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["e3aaee1e-958c-370c-a10d-94e142df629c","6bf59ab2-03d6-2995-17bc-76e27e52bf6c"]}]},{"name":"bone_b","origin":[0,9,0],"rotation":[15,0,0],"color":0,"uuid":"aa6d7f64-89b9-a900-a3dc-3a9fcdc22618","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["2f7bd122-e685-5397-518a-d566774b5f7f",{"name":"bone_c","origin":[0,25,0],"rotation":[-6.599650964036798e-14,0,0],"color":0,"uuid":"7b4502a2-fe6a-53ad-2f9f-391f31027828","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":[{"name":"flower_9","origin":[-4,40.62422566484149,-3.3435187525409757],"rotation":[-167.64364206893785,46.987882823683556,-140.9016550443326],"color":0,"uuid":"5ba196d8-95ae-bace-28cb-48bdb2d6cbac","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["e0119d00-cad6-3842-bbe5-6885628ff35e","abe6d75d-fccf-7b97-e8ca-156f68f30b76"]},"a32e4c53-99d7-93d5-23f7-ae163af4dae5",{"name":"bone_d","origin":[0,43,0],"rotation":[10.000000000000005,2.1289409148363706e-15,3.753897236198147e-16],"color":0,"uuid":"286e227f-8a7a-cfd3-27a0-86aed0c10bb7","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":[{"name":"flower_10","origin":[2,60.13549813476108,3.0055163398616545],"rotation":[163.90070263038493,34.045350837165316,140.2721846400254],"color":0,"uuid":"1ed2c36a-c359-91ea-40ba-572a6c8757f7","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["bf2e26ba-937a-73c8-3ab2-9240c2690b0b","61246d8c-3a21-f2e2-2b5e-656b618b9315"]},"a4c5b57f-c555-154e-e673-7e114f6e5a21",{"name":"bone_e","origin":[0,61,0],"rotation":[-10,2.1289409148363706e-15,3.753897236198147e-16],"color":0,"uuid":"960ce6d7-f146-7814-e0c9-b11b5b43956a","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["c8af392a-28ea-8a9f-cc0f-e8f19558f993",{"name":"bone_f","origin":[0,83,0],"rotation":[-10,2.1289409148363706e-15,3.753897236198147e-16],"color":0,"uuid":"3f876c03-f9b2-1dac-08bc-990abce00d76","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["1d4836bc-6b01-9b8b-2958-1280f80f91b7",{"name":"flower_11","origin":[2.0000000000000004,89.02514437942168,0.47799980017960014],"rotation":[-158.0615625451014,-19.935030254861065,145.712572187446],"color":0,"uuid":"42c04c52-8414-cc27-f1a2-bce1e6fdc48c","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["ff5dbdd3-d6eb-27ba-e1ab-c1a0f550fd3e","8e0f248f-1228-4a38-2a74-cb0d23718fdc"]},{"name":"bone_g","origin":[0,98,0],"rotation":[-17.5,2.1289409148363706e-15,3.753897236198147e-16],"color":0,"uuid":"87fdafb8-862b-0fc4-f194-483e4c6ab1d2","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["4001f6b1-caf3-d76f-2d5b-8636c85cd0fb",{"name":"bone_h","origin":[0,111,0],"rotation":[-15,2.1289409148363706e-15,3.753897236198147e-16],"color":0,"uuid":"bd8fb7b0-ef45-7564-a12a-05cb47421493","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["4ba83545-3fcf-726d-033c-a1ac411a4ae3",{"name":"flower_12","origin":[-4.852359674770397e-16,126.1462282992986,0.6365198729059891],"rotation":[-158.06156254510137,-19.93503025486101,170.7125721874457],"color":0,"uuid":"faa91113-8626-24ec-baa2-9003ea1e0eda","export":true,"mirror_uv":false,"isOpen":true,"locked":false,"visibility":true,"autouv":0,"children":["817666d5-286f-6161-8446-3970704a3265","5f3e16dd-eed6-733e-e1db-f1934b558e88",{"name":"flower_13","origin":[-4.852359674770397e-16,126.1462282992986,0.6365198729059891],"rotation":[-158.06156254510134,-19.935030254861015,148.21257218744574],"color":0,"uuid":"749d951a-ecbc-4197-9c32-365bbb0a98a9","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["573cf719-c66a-d73e-148d-d14e9617d0ac","e4e3e2ae-0684-ee78-4ec9-33f0f9461b19"]},{"name":"flower_14","origin":[-4.852359674770397e-16,126.1462282992986,0.6365198729059891],"rotation":[111.22147558087971,59.83922506916349,99.72701164210366],"color":0,"uuid":"3c36c30a-bdb5-bede-7d1c-4073204f716c","export":true,"mirror_uv":false,"isOpen":false,"locked":false,"visibility":true,"autouv":0,"children":["c59ea2f3-dbd7-5b92-4640-4acc2121a8b2","3bb469e2-e5a5-7cfc-3e9c-6fd43b8a3ca0"]}]}]}]}]}]}]}]}]}]}],"textures":[{"path":"/home/sollace/Documents/GitRepos/minecraft_mods/Unicopia/assets/models/tentacle.png","name":"tentacle.png","folder":"block","namespace":"","id":"0","particle":false,"render_mode":"default","render_sides":"auto","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"mode":"bitmap","saved":true,"uuid":"59729803-364e-77c5-ab9a-5c088a316d59","relative_path":"../tentacle.png","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAIABJREFUeF7kvXnwbdlV3/fZw5nufH/jm6d+PUlqiRYSEhJCIBEMhoRRljCSMJRi4qqEVAY7KVeqUnY5cdnlsp14Aoc4tkmcxImBKhEghsgChBBCoqWWep7e/H7j/d3xjHtI7dNS/iAI9/u9fnQD3fXqveq+9/z2O2edvdda32EJjvnPIw9+xKciRZmGxEsirVlVBSKRYByN8PScoiPhhjE0WqGkYuyg8p4ZnrFpiGONUBpvSvoqZdYYjBDUtkTKGCNg5Q3Pvvi/imMu9f/3tfc+/CPekWJtQyIVtav5zWf/2at2/Vdrnb/3Ot/6nb/i/80v/Tuv6jqPfbF3XvpzXoqGnodKZRSuIRUC6wy7eE64mtxrBt0ON21O3wgKlfGAr1jYmFjUbRBUQtKLFYfLBaeiHsqL9qEvpaVuKkSScYqIjz/z08de6++9kR9+43/smyTioDbMfMkJPeYXvvjXX7Xrv9oB8F//tyv/V/5yV3zn933B4x2/9PNvfdXWeuwLvfXSh/047TBb7LCerKHCm608B75hTSXEHhoPsTNciSN0s+CEzzjylkJIzkhPIwULH5G6kr5PMMKhdczKOxpnyW1DamuE0Hzmyqu4AzzyMb/uI1ZNTZJ12EHw2S/898e+F6/2A//q9d7zbT/rw59/41e/X4Qg+OQn/w1Z5wTToyf5zK999FVZ77Ev8i0P/lm/XkQ8oysuqIiVcxw4Qd8r8gik8HSMxYXtXcWUtmZTxkyrGd1kjK+nHEUxA6HBOoxzbCrFgfdoLAdW8BYleck7VHXEb974hWOv9fc+oG975Mf9iWjEtcU+y3RI7Go+88Q/eNWufzcB8dW3PVzjqwEQ/qyjPqaZs3Xim9nf/U1+/Ve+91VZ77Ev8p77Pub7OqE0Cw5jxcBJSlPj6zkuzpBygBcFjfcoJ4mwTJKITuVZjyRTU6GjFFmU+LSLbkompuRkOmBJg6xLnEoZt/kC/PYLP3Pstf7eB/LGR37Mf1O0xrVqRaMiNDW//PhPvmrXv5sACN8NQRB+D9v++//0J3xdT9t8aDp5nMsP/RiTwy+xd/uTPPGF/+au13zsC3z7pY/4BYLalfRkQpMKuk5hnWViBVbAuvKsbEFHdliaitVoTDqZMZaClVRoV+NFgvAh8XNIKTmv++x7QW6n9IxmIg0GePyF4x0B73r0r/mT1nFfskFlC0zk+YK1fNvoBLVfcVhWTNMBlwTcmE+p5Ih3Riv+5dSx3Uk4UcOeP+CESFipMXlxyL964r889n17pcHx1SD47O88S1ketF8r89s0zYoLlz7A00/8Pb782F+563Uc+wLfcPEDvoiGXLQVCw8TDbNyyXayRkd6NmTC/mqHWimipM9CCjZdw5XakkqNwhJr0D6iEpChqbxBVA2FEJyIU3Kt6NU1T5sFL730fx5rre+//Bf9eidjFXfpWEvkKw50h4ek43ODHqfzgreqlFJ0eF73mK0OyKyjVBFja5hLwV6eczpNOZCOwjk+87n/9FhreSUPP2z73d45inyHbv88RwePkXXPUCyv88yTP8mZC99Nkm5RrG68tgHwngd+3K/sHKmiNuGTjaCXCCZC0bUgopirVc1F5alDmehrHIKjpmKjN6CuVyyaFTLewJolViSsScGObzgZKkkZI4jYr1eMpOIzz//zY930733LX/ejeI2d1QucdwlPixyifltd9OucNwjFQZbwkhjzNqe44XP6XvCFrMvloqTycEOCrxZ8XXaK7a7mr/7qh461llcSAF89+7POSbxzdPvnOJp8uX3gzz31j9uE+ML9H6IuJ3zLt3w/f//vvOWu1nLsL7/90kd9V8YkqqFqCvZVxkhIOkQcmZw0GWGqCSpSLKygWcvQ+yUbicY0OUJ0OfBTRiKhZy0i1OM+lIoRfSFwQrbXWUlLVAm+fO1/O9Zav/3Rv+ovVDkHDIj1nEHoS3jNyMFuKokrC2rMEEtNzFRqYl+ymYwYC3iss865yYvMK8uLieGtWvMzn/0vjrWWVxoA4XPf+N5/7lUoi1XGeO1NVNURs+mTPP34fwc4fujDP8k/+LuP3vU6jn2BN1z6qD8lLDtAx1tkrOk7ydy2ST2x9jhASU1ZLhgyxMSWkao5NJrGeRKhSLVm1tSYZsko6nFDGs6QsDsQ9I4a+tJzkPb5whP/6Fhr/aGH/6I/Eg605mZHcraAuol4MO5zs75Nnm6RFUeIrMPb0XxSeqxL2NQF41pztn+exXKXz7slKu2ys9jjC0/+jWOt5U4CIHz2697+N3x4MaJoyHD8Buaz51A6/UoQwOTgt+56Hce+wJsuftArFbMF7EQZSz/hZJMhtUALT+VSGmokKSeqI/ZCnS8bFr5m7FOcrsn0qN1iD23NiNAXkJw2BUciQoXOIQojPHuN5dmXjlcFvPXSf+DHccrUTBmuXaBcNjwcDVlGDa7aI/YpxpR4qTigz7puGCd9nrIWvdzhqHORh8wuX4rgvlKxL2J+68m7T75eaTC84z3/xNuQvNZL3vu+70FKxT/76Y+2X+8Sc/3g1479DMM1jv3l9z7w5/1Os+SCTkk8PF9O6UeaRPWwIiJ2FcuqoZNo5rYh1hGiyamFZi1eY88ecSpJeMEITuBInUXLLp4VU+PoyYi4CduyJI4Fv/jcPz3WWr/jDX/J41bE9DFpj4Oq5E3dDW6phmh5wBVX84h37HvPJZWwUx9QxCMQnsKBlEPGdkkpFVWTs/SC33n+7x1rLa/0of9+n/vx/+jTPrSuf/ofvrf92Vtr3+BdpIlczO39Tx57Pcf+4jsvf8QPjcfqLpmr8HHKuMp5AkPV9vW7lKJi00uOooRCx5w0guebQ+6Ptuk0c/aEhE6fQV0xsys2yZiGv1S1xEnFyudkeo1JnfPlq//Lsdb67Q//hNeyQ+o0t80+B2S8b3CWfV1QL+Ys4oyBKah9zLh3Ejl5guejIWtuThJ3kdF2mx98vt7hslMMheeffvlvHWstdxMAv993t9a+0Vs8ke6xs/erx1rTsb4UFvPw2e/3MurRVxlxfpvbusOYiEYpRiFxEwlNvWKox9SR5WZTkqkUSUkhUjp1zliWPO00J4m5pRT3W89ECtZsyUpLirpgKDvsqIQnXjgeFvCOCz/q97XkzQieFpquM8RCtGVo3yXsJoo31Tnz5CSFm9HYOVaOKM2KzXgdqxre0j/B40e3CUnZDZPz2FN/+9j37dUOgrMb7/bIjOvHDYC3nv+wHwYkTzj6yZBFPqEnIlIcS2Vo0ByVCy6pHrejDmdcwZPGMXChwaO4FQvOmZRuMmTfTIgtxNTU6Zi6OiJ3A4xrsGnGw16wqG7j03Vs0+CEZyNk+9biZEzPVOxLhZCSxEXEuqZ2HuMFXZWybpbsErFn5yTZOr6pCJnGhojYdQ6PwZuKsY7ZVRqc461RypelZC3usleXnBZDfBLh89vsfwVz2JYdusk6B/6A0gsGVlMqzyVnOVAZQnfJlzPwdXuUfeLZf/i6CYC7DSjx9ff9sF+FG6ljpNPISFL6mhWOM05RuoZhMuTIRjhxgKoEQsm2TJrUczoqwWMx3lC6kLY5FjridLXEi4yVqBmLiEMcXS85oOZUuoGzK6blgoFK2a0NOo0ZIUhkzG1jqKKIbjWlo7os6ymT9S73rcLVFY0vEUmPajHlIO4walZkOsFrQdZ4Fq6kaRryOON0Z8TJOOWLfsVD1ZDrsqabnGFj9QQ3ZYQWESdlj+uiC2YfAjbhCsZS47BEqsO+kpzBMAl5ju7wc0+8ftrGdx0Ajzz457xbLVkTXXTiMBYyq1iJAqM12nnmUlJ5ydiVaDmgG0UoP+NZqdgqa3rZOstyypqOuFkuiOIxF52njDVX6iXbURfV1Fzpp1yoYbcp6DmI6jnPSc83RGOeowEv2bCWbpyCcHREzPM2R9iSXCV0kay85W1Rj6PGctMcoVVCiqCyFUfA/ULxkm0wvuIhkVJnawgt6Edj5sKTC4GWArfYbY+XKRUmXaMWMcPqkNiGxrNHyQwbWY6sZqAHpPUuh1rRb0p+6ZnXP3fglQaGeP+5H/R5u+EXoDuIokDGESiP9xatu8xNxXqU4r3gilzxptKRi5RF0kFVU9J0i10zpbYRF5UiN2FHSZhZQ4RiSk0/1cSNI60cMlbclqGELLgYbfDlch8lPcKHTqIkkT1Scnas5yIWheZpu+IUiq7QyCbndtIhLmd0lGZJwiae6xTETUWjBcZr1gIi2T9H5QybpmSZ9XhADtm3OS9WB6Sy17agdboRDi2my0M6kcQkET0DRkVMXMPX+5ShEjzuKhArPvml4+Ujr/Sh/GF+Tpy/+EN+y1ikBCFiiuEJLlUrtHNcNSUIQ8epFkiJVY+VqDhBj6tmzlanS2E9ERLvRFu6vSRLzjrNxJU8FJKsEDiu5EZquORjbjUVmdCo0C7GtFjAJOlwsp4Tmr9XQtfQzunpiG2VcMNWLGOwNmGjKRFC0ZeOeW2QfomWfQbCcOQcC2dwtgal6AtHLBNM9xwzaiInORlQSNknq4841DBuBLum5uFsEy9g0swY+IhtXfGiEtwqHac8LZg1jhJqu+Kar/ntp/+HPz45wIWHPuj7tWe9XjLJNnmn7PIlV6K8JTUltdQkLkerjKXQeNtwYAzr8RodX5CLmBNSMrWWXtJB2prnTYmLkrbNO65WPC9yjBrybiX5rLW8EcEtpTHYFkZe2jnGScZRRBW4ASIjqo/opiMOqxlTJRnVJUZKOkJjvGNgcxYy4cAU9KSjthIrHIU39EVE5Rssmq8fnGfWSTgs5wjbcHnrIWaLCTKfYaIMWRRU45MM/JJ5PmGiIjpSo8OvuuSWzBjWh2xvvIGr85sUjeSZ518f3IFXY6cQD174fn+fGnE9YPF2SdU7x6rJsb5CNAVRyMLTATkNW7XlViS47ELWPqJKJJ2i4ooM/b6Xz/B+2MKVYF56XE8il1PQXaLOkNwI+lQ0TUVucmYjxSgfou2S8z7hRXGI9DFn4h5fLOZ0pWPNSxpfoEXKrhaMXcSOaejiMNLijWUooesT9qWlqnO6Ohw8Dm09l+ItDqKG3ME8HbAwK0Z5Trc3xIglg3SNMm9Y1wk3Fkd8/vrxMIdX42G8FtcQ73v4J/yoaWikY0bGI511buc3eKHJ2cz67Ncv3+DMK2axRTBi3S8pvaRjGuYC+s6jfU0jBC+4GivHnAmplGzoRpJJccD53kVuFXOuLm7S7/WIbIUKfQORkJZ7qGyjrf+/ZBwbUjIRjnXXcFp2uBKaS80K52oeSgbMnGVhNTJN2S8X9MwKEcdkwiPrwB/wHFiPlYLre59ot+v3nP8zvheldIVk4gwzt6KjO2wkGbt1+H4H0Wg+9cxP/bHZ3l9JQIm3XfpRHwsDvksnUZwRA64V1zFRjzpfcjqFQ9/F+gVbMuN5n5C4Iyam5j4idtOIzDlKEkblgqnuQxayfc8Tod0rI+bpFg9LeK46ZKRTXA3O7DMXQ1Kfo2UWkn6muqFrPKWWJJgWQs5V6MqVWFew9DGRzSnI2oCNQ4IoQsXicM6BNS00XeBIkIxwfPLWL74cAJc+7ANaeSWQUG3JSdFl4g3Wrzgdr3FkGhph+cQLx4OdX8nNfj1+Rtx/5gd9X7YAPjE9MEt27ILYWdaA26kmq00LPGg6NAlQL1hTKYcuJ1EZhzZnI5C+Ah2choGSTMWAws9BWnq6TyEi1mlYNQ1SJcimYhIJlBWcTlJy65nZhkh2OKdibtumzbi7rsshK9bjLlVdEYs+B2JKx/bYpmBXp0izoFYxG1GXo7Jo4dLdPrxpseRf3/jlNgDue/ij/mQF2CVFshZCCFVaFsqT6AG23qUUEY9f+d//ZO0AD21+m1exxHjZ1tSzOiRHmjUjyF2NxjMgMCJgFnVbzn5sVtQyED8166GANAohHCvgtNLMXYEUIfsPJVnM0Kc0tmQqFSNRtlh/D8UeEmEsvXhIY5bUYRtwgoWr6eoBqlkRyaALsDiRMFIaZw3CFRx5R6IU0HBoY7ZV3NLIOjqhcQIRWsnGcXXvZbTsHff/uHeuYIOUQzunMDFv7g15opwgTMVSZ2xHKZ967o9Phv9KdhxxYuPd/lTUYe4VW0oxb3I6WFaB70cgc6zotHx/SxOP6bjwf8L2KvFI9l1DozK2pePQllgbtd/3KhR5rqWFp/EIJQzTpmGkgjBEY2TKfV7znDJ0qqptzqysbzuRh8pzxqUsQm+iylmJwB4GG85wraiLRasfmIuGTiClxAlVvkApRY1gr1pwKhu3ieaVg8+0AfDGix/xa1qz6RxTW7Lwip5O2mQxt449o+hry2NXjgc7v5Kb/Xr8jLiw9a0+tQKdpZiywEqD8qotqcKO6V3A72FfOkYhEw+9duVBhBawQ5mXiR9CifYBb+gQOKLNC2ZCkukOZ+N1rtV7YMOOotFKsC5SboeOYiNYk44jUbdEDClgYXI6CpRriCwUrkYJQV+P26C6LpboRtANFHIBTdRh2SyJXM1pkZBLw8Rbat3lYPc32gD49osf8aEj6Bq44las6w6ZUIgk5aAoEVHGmz38zHPHI568Hh/uK1mTuH/rW3yFYVOPsS68hUs6qiY3FqFiimbJmoqYmoZlnLZ7Q1yETt8QoRTOeYbSUwJR4PBbwVIYulIzsTVxlNJzPfainCEhJZBo71iZBp9tMhSKmT2g4xPi9qSRLJHo0HXzCueLlj7WUCL1COVqRq5gQUg8HSMf41XCvFmQiFDueUQ4lqxhrAY8O/nNNgC+9+IP+4kKeIdtA1wrz8IW1ELijGCUaCJh+b+fPR7s/Epu9uvxM2L79Pv8Vl1RhvarSjGupLI5yhmqcPDbnE6UUYUOXOjSITiQpuXLhbyh9pYMTz+0ZP3L4NHIeQ6xpDpj7hwXZEZJzQSJxXHWWuooBFPoyyctmPOUgC0ZsSNWbPqEQCGV4SwHJi7njAyZf4LyFYlzrETYeUSrQWx3gVA9+KZt3wYwp+sckYh55vCzbQB84wMf84flin63R5GvuOAbnlERa95xOR5wUxiumZorL/zPf7KSwHdvfau/IuBR1eeaXRJA1TrQtE3NIhAlXd0Gwkkb0bQMlOb/216TcMO9YdNHREpSBzJnkHSFcz88uEgx8Am9wPRxBTNXk4bk0SsaYbCyjwoQq7MkCEodo6TE1hX7GDICbu/Iw84iNSd1EI9WdHCsvIdAG7O+JZQ6W7wsTJGesAc0SDpK8dL+ywFw8eEP+AtmHR+qHAHduuZU3GOmNLkp6QrP1Fu++NK/+JMVAHe6Lb3n7A/4wPU3TrT8NMeSIzdkHMibouCkE9TecKQ1G87wO1d+/p7e0O+7/KP+uaZuuYgBXYidYxn6CtLxVt/wqUaxRsFQJGgVUfiaRZQQV0dc0AMqWzORCdJaNqOUpVkivGQVx205WXiBCuIX3SOvJgxcRq4tU6U5XzccKE3hbKiTWIsHzGyN84YtMhb1kk/feH0fKXf8cN5+6t/zmYrYU45NEdEYx+nwplUlKu60cHIRauuA0SvNZ67/3B3/jDsJyvdc/hG/Ug0nG0XRdv8DtiBb8cl1VXG/7LGsCvAlfZ2xtA0zW7YQtgaG3nHgayZes0HZtoz7ukta5RyKnH60wUQ3nCMjtznzqEsU4GztuZYviYwg7YzRSpGtblGrAZEQ5MqwExuuPPV/3NO//53cq9/vs3e8uPPnv9efDX1zLRhVgi0pmLsK7eFIeKK6II+7XNIZ+82Cz1+9tzvAN539Ad+PUrIg3aqmvBDFbLqAH2jiAGoFMmqghHctG5MgROnT9SYUmBQipgl9RavoSYEXMUt8i0G84Ka8myFXbdjZPD1jkDJl0y3Y0YGUothwklVAJkVCZnIilbHuK1wcsIiUXVvx2Iuv77LyjgPgwsUP+DPhYVvJaWlb5G6lIhZmQeICETyQyFKclC2i+IVrP3vHP+NOovrbL/2In7dcw4qbddOikaeCHiEok2zdIntxaBUb2m16oSUXQp1glhzIPt22F2CJjKMSJZ1kxE4+xXrLI3Gfg7bhbNgQlheLBRvpSbSbtcSSYWhgNUty76lCeusl+82U+/onmZVzTuk+H3/+9d1YuuOH89b7f8yHZsrSr1iFB13P2RB9vCzZISKrD9mIA+nCcSQ1V67d29bqN1/6oJ+WK85119lpcrrRiFl+k74aEumEwybnolShouQp69kUjrLOcUqA6rW9jjpLGM8NQlV0opjSmJYZ1rEGrQPlzDDHc8vmxL0TDOsVRfi6E2hb4KI+V2rDA4llvw6t5dDo8tRW8PhLr++q4o4D4C0XPuC71Yyj0JqNtzBmyn4nRliBt4ZEdkgC0hZk3SLmqSv3Nqt+24UP+cSbVtgx83AyYBQ2x7c+A6IVhHSSbbTQuGKPKOox0CnP1wt6SrYNoZA75HlOqhw29DasaHGB0yFhJCSDglx7ijpvK5eujlnzectP2GygijSToH4mYs05bqXwCKEENvzaH7cj4NEzH/AKg6RhITWpq5AyIVb9VjgxwHBNpiSxYL1xfOrqv7zjILuTI+Dt53/QdwPW7w15nNIXCmfqtlcQ2tFT6zjl4LZQrEnZ6g2q2tNNGqTzrYvJJNDRgmBFKOqmptIRWbGiiuMWwwj0tsIY+hpqr/EyIg3q5XLR9iq2siGH1YKlhJ7UDIzlKLTGHVy7djxV853cg7v57B0/nK87/wHvpSS2Qbc3pq6P8CIoZpK2U9gLaGBABeMOfd/wKy/d2yz43Rd+wKtoTNcUzMySqY44URfsJ32mZsZAdEhkygzJ5cAFjDYZuZxNpRkLzX7ALcK/TjFWkue8o7Rl20TqB98C71hq2HKKoqlaNrR2OXPTUGjFUbOkr2MiF0piw+1EcbIUbUBuSM2v3+Md8G4efvjuHQfAo+c+4NvGrMjQkaE0tqV+SZ1xUBwQ67ito42IMb7mS/d4B/jON/1XfmqnZE0Qb/ZRdcM1adkgoSpzxlmXVbOiE61hZUFaS17QDZlLOWVnzOM1pF9gq2Be5drSVUcRJxBMVkeorM9B7bCBo9Ad4PIViRAsGs+adtTGtrjFXCi2kjG73rJer/BJr7We+fhTf/gysjsJijsOgO8+92F/21V0lW+JGeeV51lvKcs5XZ1RSd9y+32zYk3EfOL6vd0C3/7Of+Tvt0smy9228thSCS86zzkZUShHY1ecbCwzlfKcr8jKnDdkF/m8OSRVmne5qpWz3ex3OT2FDS25LmpW9RyJY10IlgGUkgnCOnygsnW36a+OKGLNKWe4oToEoewJ3WVlc46s52TQNGYJP/eFv3bH9/hOHuDdfvaOF/eeC3/WH9mSZezYCMyeACTFGYeVQmpL1NKxFF3tyV3K01fvLcPmkYf/E9+xMzpec8vWXFaKZ+Ka/tJAgLKjlInrY0aONy9TnpSet6VnmdgJHWYclJ5FUP5kFRtLyzCWpDblGjUPx5vcaCuehjMJzKsSbWpWyZih97yQ32QQ94mcCERkui5wEQLVLedWveC+6AT/6oW/f8f3+G4f6p18/44X9+7T3+OFj3BZB10vscG8yDfIQBMfnKfxNTeXh5wREN6hx258/I5/xp38Bc5f/qDvFUVLVAmdvUpGrVGFD7W/tywtL9PBA+nJlsQuJk96DM2KGzr4GcIJ55jYhjIbI42h8J6eV5xLYmZWcNUrOqJiOxszq1eBs0JOzlCOmZcHdLWmbysO4qzlNOh8j/Wox0wMeeza61tEIr755Hf7aYBJ9ZCOq1jzK27SsAyU7iCskJKXRMPQSBbOMdQxtXd0rWVPQWoUpbKcDF4/ruawBYQ8mzpqZePhvFy1II1lqWQLFw9kUPgEFlGNNSU91Sd2DUJq8pYFXHMUNPtpxmbTtHBvIJHviog3+YgnhGHdOHSc0HeKqS8owlmchVLPQBIRm5qJhW6sODA1vbAOnzDwK/K4x+nAHCqDBHyBCyziUPs7y6C3RVkvaFzEMu0yriaEgrIQHSJ/1FYOJlQdWrJO0EEqbtZTzqgBQ9UhpyJXiptVzoO9+/l/nvu79/QFuJOX5fdtBQe27K1mRZKN6diC27XhXBpuiED6gMSF80+0rdWFaEjqpi2Z8izjRFVzKGnNHXpK82TU8GiuuWorEi3YizR9Y6iNoRP1aMyq1dpFoTHjPHVgf7iidRSbVHV7lCxdTU92godowHjxkW7JH0Y1bW+h8LrtwwfUMg9MdKXb83fXFwx94CppBrak8Ckrv+RMy2CImMQJZ8Pv1YxUSSadLbbnOyzTDGcCd+BlKnlI6g6JuRhrbsd9zlvPjWrW6hWm2iFNwWbUQ1LTDYRX51AqwlhLJWNGgYq2XKDTTkuH++KLr2+auXj49Hd5F4+4oDzTekEZCBNOMZcdTmvFrllw2mfsSoMmJ9Kj9jNFoI55yyxWbBWmfbu7IqEM2bAW3KoLslgybCKcatgLjB5j2RMRm0FoKSO2gtiknmOV4JTzXA0qYGGphCAJJNPw7tQVEkmSKaZ18CAqECpI2QQd55kQUSpBYgKHoWI97bY8hRuqYrgMgVJS6g4JDWs+yMcSLilDIbqtWeWBjqiqmkQ6ZOA7ioALaGobzC2G1H5J3QTX06ZtAJXetjyGpLH4SLHnYNPVFFpwPgBHPqZyq3aHlJ2Uz75wb5Pgu94B3nXue7wWHaweMLW7lMspvaC8daHZA+NoxCr46TUNZwKwonpcDZi8DrWzoBMcwERD5S0D7dmvHf3gHBoaL960yqIg3AxASzCHS3SKslMWway5WRLHfVold9jqA4kkiFCF5Jqz3B/FPG4tY6Xo2JI9a+g3rmUAn4wibvqKOO0yKCsOQxcyCscT7Mmcs3VI3oIr2aqtThqg1/ILDEomFIG5VE0ZZUP67oipiElcCL6USDgWaLqij9Q5VRU0gQ7vLOu6x0EUGCg1a9GQVX3UlsSNrXgoXW93kRerOb20zw6SW/cYC7nhwGnYAAAgAElEQVTrAPi2sz/gl1HW+vFNwrbrPWtKtcnUbRN0dh2GgV4R5GAKnveB+RtTBSFICBKv2NeOjtUYZ9nQmkkjyXXOhlV0dMx+2bCdxUTUHNi4lYSNbc3Vr5yjfbJW9DEN3sE4HkWw45YINWpJoS5J2W1WbChPZYIjRrdFHk8iGAQ9IZ5NVzBvDammDESXPNDUnCCQVlxgF7UKxrL1GwhuJv1wbFTz1oh66hpc8BEsF7i4g7eB82iIWr7AskUPpcw4aArO6C673qEDAV5n1IGKJgMnIIBDXWIfjrcgtO2yKeBXrt9bMOyuA+DR09/jw1EcDBSOioImgp7QLOqSStJKr6XuIH3N0hh05KldylrwALYFlZLM6orTOgbnmfkiSD4ZyYh9U1NrSbfxXIo8t12Mkw37zrGeDIjLFQd6yCPGcIscZSoGnSEvFCsSacl0r+1V6fAQfdVS0SOX4EQBqs+omFGmI5Yh46+XtAIXNW55jIHMmmKY65hOVVBKSS2jlup2Knn5GDtqFhQhh5AxZ3zC3C9obE3lBWejmDzkK7Vh4as2iZyGoHOG/aCX1NANHca6Ike2pJGh7BKE1bMyZ3uwTd0U/Nb1/+v1nQS+7dR3ehuttZSuqgkwqGkFmD4aMKhX3AiO3cHGVXiW1rEeD5g6QxSwf1u1bh4hoauC+6e1LQlUBS5hOM+d4SXjuC/W7ATuXxQ8+ip6dcUl2eOxpuS92ZCrTUPhS+Ig/MgyjkL+IHvtQ9tUXWR5xK5YMdIDbnlLz9cYHXNYO1IVpGmw8EukVWy0FQPcDnpCG1JJSeUsb4oTrlNx0DT00lOMXM1OMyWwDq2QnBJDKrff1vOTyrCVdhBqyOFql804QMKaMrSGjSVNOozav3ePQ7FsK47AmYyTpPU4DghkAJziqMdj1+8tH+Kud4BvPf89Ple9Vub1WHmboco4srCpB+R+SuwdkYyo4xGLxVV0PKBL3foBBgrVypRMQ9eLqE3g5qLHtst5zlRsaUUc95jk+5xON6jKOSrpcruesR2vU9RT4mxM7CQ2+POQcBgSOfeyNOxWb52HasdLbt4mZ3m1Yhj1WtOKSoZ8wuJ9ynW/4GRTszIeFffbslA1Yev2xCpm5gyNkIx8OOPjlqreU6pVJRd1027dS9VHuzmqabAyYk0YSjXCNQetKCUXtDmMDMYVDrqdDNMoIlcileOKU/RUhq9zDjLD2ULjOj0++8K9BcPuOgDeev77fBR1qZykNlNOhdanhSbpM6lWnPCWk9GY20EZ5Bctd052ethixXnVYSnDid5ww0SMdMR9JOyYA3KS1gSydoZesk5ji9b69DYV94XcwSlEd4CxfTZFya7NWdUrVBwhvOXAONZlQqYj9lczRpFr1xgOmMDn35YFu1VCLx1w1MxJXcMMwwOqx46rWhXwSgdSqmp3AR2yd2cQ6RjhPYNqSdUaSMqWftqie/lRy0YOO15jLHG2Tc837FQTuipm7jVdKVtT66AmWsvWmdZHhPQ1WOUsZJeRnXMzFgwqQZr2+MLrPQAePfdn/FZvi6vlinVTUEjJNh22O2s8n0/o+BqyhGdDCWZaw9X2Ld1Og2/QjLjxDJRiVnmkDgKRgKJZjgKz2Clq6akixbyqeEgGhL5i4jSlq4i8YKtzgut+xVowikRxVEzJugOqouJifxsCpiD7XPVTkrLmUGtEeLtFQqHCQxVs+JhZecRIK2aqx1xZUjdHGtceUZWpW+Vw5SOmwrDRGbNazlsenw60994Z9s2ctSpnV8dkQSurJCfj0+z5I/q25hYV91vFiz4cgwmV86SqS2VWxMoiTckw3WYWvBGCr0IckxjJ5268zpPA+09+l7/U2WIeRJWy5EqgVOlBy4AJVrDLetkaJ543tgVNOpRYYxjFmmXQCfiaSAtWIuF0+TJpMrYLVi6lkpb1OG0Trm5wG4l7mGLeqnGE8eRBoilyXBBnmhld60kH68xXJTJyvEUOeFIZhFEM7ZLrIuJhmbzsHIbnhllwLh1wUO8zjkfslMFudsSRWzEwDQTpoClbO5vQ0cwCdSvqMiUjq/YxyuGtZF11mAmCgV2oR+ArTa91vd4+/CebKeO4yzAYXYmIZVBDxQkngotYkMOHxpi3jNNea+K8dB5nA+G05ne+Ik+/2636Xn1ffP2F7/IrOvimJBVBNx+8sRIeCBTp1R57KmkduU6EZFDSEh2HSjOzQcdpKK1nS+rWISRWDWVTEarhUTJuefZxE9i1njjKWFVzVDBoCjc4GEeK4BWkWwr3ej5rHUZ7cZfbzRzvDNt6m9v1hDcn61w3E1QwraLPzDVclp6bSjCMNlgF1p5U9FyODOe+1bwkHWdFym+8dDyH0X/bDf+uBz/mJ6ahT0NPp8yFJS8dn/4jpi0U7z/3vf6ZWDMqSg7qmrMKChnzgJRcdaF3v8KpAYTuma3IbUVGqOVpzRYWSpKGLd9W9IkZh1o/qH9V1pZXBPjUGzJXtt2/8BY1zgUn1vbXQvdbB/F6NW2FJWshcOoZHVcTBdJla/USaFoVUWskJZkjGKqIUOHLeL3tB4ylYhIUx82qlbldkxVrxvNb9wiN/OZLP+pzDdtGQxSzVy+JYsWnn/knr+uy7/cGtnj01Hf4eXqO9fIaTklE49AiY6Eq9q1p+/xbusMN69ggUK+q1itozVek8Sa+mrEUDY1p2FIRwT5gbvJ2uNEkIGsy52RZtEIKpVL2qkOUHHHCBQpVQifpUjRlqysshW31/5WfE1tPrNfoCMVeMJGwJVWc0m0icuVY15ItEYPYYK4D9FuzDDu+Shi29vOSwjp+9x4FwLvP//t+5ffZEhlVmIkkA0AV84tX/qc/WgHwwMnv9KNoC2sOyJTkevD99ZrEKxa+YC3w/YJ7ZzBPrvK2m6d8KAsjpJF4X+NVxNK/jLi5FimU7bCokdMcyJzYOmZSkCRr1PVhmHwWGsFY7xGqj7GLVnMYmk3bep2DMG9ABM+KjJWPSJRk4E3bju7HaStdS4OjeLlga3yafUN7jByt5uisR+ngzS3vX/KLL94bOPYbLn04dB7oNoLNNHgYhDyg5ndexdlG/7Zj6NX4/+JNp/+0JxkhAmM2kBhFxXnZJSHhSCzBBpu0IV7WVPUCLT1H1nFOpxyJDoVZthZzJ4RgHrJtMq52FeeaMA8m4VpzwDAkcTpDfKUaCJ097w2V0JxVXZ70MwKxMzSQMtFlalekokHGY1AJHbPEmYYqSL7C7JxQWnYCkCSIkwFnbcNCp+TCcq6quRbIqqFd72p+99q94STed/ZD/p3xmM8p2obYfVIRCsJfP6ar+avxMI9zDfHw9vu97I6oA9vFle2kz4nRrbAyDG48qTWHIm47f94u8QGiU8ESVtOP++z5gqaaEcseWgiOsHS8o1JJCwYF5/C5d2wEFo2I2jExLsC5dY2II1KxxszsIKym9jm9dAxVcAvxnNRd9qNu6wk8kxXrRjINa4zWQiZBGkrWZIOiKbiKYyMojKMh86ZsVSBBOPrrV+7NDvCO8x/yjU64X/fZrUuEdtxygmde+B//aB0Bp7e/w59MOohQpoUGioITesg00KBNa7GIjjdZNTNWrqAh4mSsmdcNpR7SiyzlYpeF1sSkRHFMp1lxFMqkaMCqnLKqLQ93Nsl8w9V60nbaQoW2Cvi5Spnnh/jIt6PmYhGHmoSpWRJu8FrQ+oXdwU3Z7Ur06mUvoIWoWXcKdEoWZRw2HpEq7itDN9JggudxXfPZK3eHx7/njf+ZP2kcXqfc8MExzTERHU75AmzoDTacVTGfX85IsoyRFJxpFL/maja0QuvASkqQbtEioUE/sRFoa8IykJZPPP7aOo+Lrzv17/oqEqwZwzSc4cFnT/Va0UPtKpyE9bRHUSypzJIWOg7GjlGXJNpsGTDz8rBN3lJpiKRrJWKBU5ClY+b1ipkPWXvCGHgiNH1saMzEVFKS9yI6q4rSNq1LZ5R0WFEzNo4daxh2Bu2DDDrD0ANYyYQkGrZehFVo9AR7WR1cQRUbKHaEbX0O3yx7eGH4hRfvTp37Tec+5sXgBE05YRgFh7QCFa0TzIumVaDGw4vNlAeiMdZLnh6v2FqOoS6JAkykeuRNzhs6AybFgpup5oEy5wZxW07//Jf/5mu6Y3zNH/6DD37MF9WSq2mfvF5yWXVxvuRXn787qdP3f8Pf8pfTAfPlkrnM+Bef+wt/4A34vgd/wq+aKUpE3DIlHd1FRAlZPaFUKbeD8ZNpiFRwBxV0dMbCwRktmRb7GN+l0p5BktIUJYmOWVDSlV2GvuKqFcS+IZOeoe5zVJfMVZCQ9dg3S84la63K6IZKeIuU3DKz1mM4GEvkfskquJIGM+w65D+ehRB044ij5QqTjHmbgifqOegh2k25LIcsYtir89YA+1Nf+sOZP/S18oOvefPfeN8P+y0R5umETuCQebVoxRJP3OWZ+o63/U1/XzxuhzXcrPf4hd/9z//AAHjb+Q/6M6rLLRHMKfucjTyTYEoUPIF9QyCo2kDuFF3mbkVTNmRZv51LHGzn0jDDWNL6AF4SKVdl1eYrGy7ntvcErtDFyFNVhn1RtTSvoBoO+MI3hfrebXAoAwvItNYzUw/nwxzCwFOwsq18lnLFxfgcR5Qt8LRXwkY6pCcX3F4Zvj49wQ05R9ma2wF5DHSzSHK5Uvzsl+5++udxkr+vfudr3vz3X/oRX6vQ4avJZHACCe7flheu3N0O8MF3/G1/ni1+u9PwoPX841//sT8wAP7UpY/6PCSTAYRpArZQEeseRwFcGqREHcHm7RyvOhyF9pT3bImIA2HYtoLnO57zed0KOCdNmBmo27kFc2PbxpXyZcsiCtOvwxBZIxSndMasOGxbyVk8oIwy3GrB9UQycopcR5yrCxIRSCYNvTCESo14Qr7EfctQCr88Kq/TX2NVT8nUEOOC7a5llSY4H+x2FSUNP/fka5wDfK3oefvFj/i6TWLCcGdJLxgpmIonjjm966s/5y+886d8YB8ZbcmF5qd++z/8AwPgXQ9+1BsThn8YKl9wzg2oI8HtQB61DS7KWE/HzPIjFhj6UrZTy6xMW6uZHRNYSsF9KuJsKrlpgpIhgDeWTaHaFu4kCDmU4IZM2LCeroIXg2WtKSD4CaR9epEhNh1WKqOu99kwjicouCBSbPcUZTlhZEuuNNCXq9ZkYvPEg8z2r6JkxeMuCEtWXNS6dTUN43YnNHzyqdfWmvZr3vz33v+jPnDtA8dt4jxjq8i85V/f4RHwfY/8Jd/zAWihNZHqZjFJXXHgDUlwEg20qzB0Qmm+HMe8pZFMm3krzCxDOVrfRMQn2DCH7LgwbyjMKFi0I2aSKEwiq9mVlhPRkJl1DExgBDcMnaPpjZlURWs2WdmGcZS1JlWzYsUjSdIih1MShsWEUhSodK0dV3/bN+31rlRH3Nc9j1IdzsuaG+kpzlvHS/UuaTVBxh0WNkL0zjJwDaeaOZ+pp6yQPDgc0HFdbh5dJbhYHTaLllPYCRyJkAe4JdIZPv3ca9s6/poB8O7LH/EqitixjmEgZQZf3VLwqSt3Bq78qYf+sg/Tw/P0iLHutnDpGdVnL5Rps12yUNM3BdvJgNNO8qsYelgyEXEjAERml83uKeZ2jm8KEt1tgalQseTBxkXHXPa+ZSxd854TWcqghqvCMA6mkY4WCh7YYGytWQs0L7NgIoMaOCHO+nypzjkVqPE6Zlov2Y0Uj8ou+/UCn24xE4bUp5zpbFKGnChk+IkjKmtiQTtcam+1x7M4LgaqmhJsq4wdH2hwgeWYcFrUrVDkXb0xv7TcYUzVlre/9tTfeW2rgIcu/IhvwjlZ65Z3p4gJZ67AMiQOroCgRCt37tdVO34lmD9sGsG+Fq2DZ5rEiLJuadk66rJXzpglMQ87KDbewHpRtSBN0hScXF/naSIuFg3baZfni702w48dHIhgAzdrcYURfXZ9SVTfwsUbDMOAqmLSDpPoqGGr6wu9/8DyvS8a45uap8hbW/m1uuAwDTMHgoNYzbj2zJKchY3b+cTBtzi4mwfz6fNhhmBTttTugINW1YKFTDkZYORYc0DCO0XMQeh0Bpp4M2WaRpwswnCKwFO0dOItjpppi0GkgSpelsTpAB3aqFVF3NEUJljq14yTTa6LfmusnaoVH3/6NT4Cvvnyh33eGISQrGVrjKqcF4XhjIPHvQmvDxcrOIj7nKhXlAEBtHUrg0qamm/UnhdszJFdsa17rQFkEyZvhZwhUmzFfZJkTBrGxCbrTPPnyfQWkypYuwYXrvDGFryUah4OgxkVbOkeN5qaIhi+5bcZhOMj2ULWB+zXMzZlB5kkrXm0l5qj2DOqlkyiPlvBtCkeUlZHLQsoqI2QKakoEbJPXs7ZVIIXAiDRsoUD4hk4DRGBIVg6z0bgOASPIRbcCsdPE5RSlnq8jasXvLHMueUFSay43njWO13SUGK2QyuCF7IgivtQF8SyCtgle2aBF4LNZLPlDM59zkO+y8dfeo0D4H2XPuR71nMUh7cisIESPlcvyZTB25hRKimrirEQ3NIpkdV0ZIMKBFFpuBocgcLsAKG4qjwPuoSJCMSKcFYf0F9/hJVIeXMG1/ZuMQ6NnYDh17eQyYBeFVhZji8GM0ghqJ1ojSZtGrNTC3ann6eTbAZbaVwzRQYun1SsFwWj4Ql2qwbrwigZx7YOKt0B14WFEDhR0lLGL7pgZFW23bxN5dkPwyxadnDKkY9QEoryACNgKTpsiBV1a5bt2OquY5qarcBJDPayvmxHyi/KgjJMT7MO21nDLPfoRWHXWrWSOqIhvWZB6eEWKcqtGIaKavMSZV6DmTCKMj7+zGuLHop33P9jXgXiZyiNnCeTBqkkO+1snxgrJevG4mUYKxV694J9Ct4oB9zEkbmX3fkDhnCtWDBMUqr2v0PhLecHD/NgOuTZfIbwKRcTwxedYzvw/AZj9ouKiIprftViCcGc53pwJY27vKWu+fz0CZbYdvLHLQoSVzEwsK7idlcIlLV9cmwo68K4WZlwtbvOw/NrPO2C0LNhW2TccsGqVlIGnmEkscWMZZS+bHUnqlb8kcq4VTsdiRjhKoYimGANOJShtFSciEYsm1k71Tx0AJfEdPSopYudCdKwYF5rJbuiZNDdYr2aUNs2B2TNJnxZw0ZvvRWTrhY38DLh08++ti5i4psu/7CPdUIepm4Eu7RY03hP2QQ8JfTVPad02lqwb/iGuYc1IXlWq3bs+wnreA5PHOxWdMxF1W1ds4omOId41tfezKEPN1PT85LChV6+46Uy5qw95Lo5oBePmDdhPFvNoauRKiVTjudXBReaa+zboNRNebLKGTvBuoZDmXB/vMETbkFdzdoEsyV/Jkk72q0JQk+f86yPOBHmCDnBgVgxcz2S4DFMw00Ma8nGy1yC0F8wpq143hWP23XcDLzFzjbbXvKirXgwmD+GppBxTOsdmgBFdC8zLHdQPjSdRDuLeD1eJ5eiVQwvTHA5LRlG3VaX5IPSOEm4XhySNZrPXr+3Hkr/tiaR+JY3/HlflDMI7J0wOiYOg54MMo0RxZw87eDrJanqMRCOLwYyhg3K4WCkZFrELVQLQZTZcwJvSl4Ulq4XBIHNeHCeojGcK5YMsy7PNo73nXwzv7y4QhRkY8U1LkWneX62j+vHdFTGotzlIWN5Me4jyl0yrVkay6wu+H/bO5MfO64qjP9qulX1hu7XY3qy284MOIIFggghIVYsmIQiJCQiFgi2/GfskBBsYAUoYkESHEiMHSfu6b3u1/3mGm9ddG5YIjlSlKJkat1tneNzvq5bde75vq9yS9YtrSzH7XZZI+JBMbPsHDnTBwyIi6G9vZRH9qDKGFqdv4pLMaHwheur6LoKL8uoul36umRUFvIcIdYlVXdAlkzsQssL8TapSNDkEwb9AztbmBRjOwEcVpp+vIswxaaLK6Y92FkWHHS3KZwes3zIXO5WHGWZTmt+jz1fVmwyHpa5/Ur5y2ecqzytwU/7uXPv7k/NYdihKzdeJsetQHnwWB6pjmtv3s6cjEQYMPL2jLh2yBeDPB/EXcsj1XPLp5ejQ2gZwtOTr4hACJ7959jXogiu2PJzFkrxhfiIh9cnaLWw9m9iHztxNK9EL/MuN7y8GlI5PZ5QcTb5O4frz1sRSOEgiu7DQJYwfTgyiisRqyoz2wRXZ/S6GzxajLklVjKyN4DP1BXNYzG4EB9D2UuGWOZ+pUaHHWtHMxTFU5Pbc1mOiYkprYlFv/sC607GSI4d1cPkc0bVgttRzFVestzc53g6JclSa08XE7LRW+NR6HE8veGimFmG02U35ktLobt1ORWFUqHSu5p3Hn6+MnpPBcC3X/6l6Ysrp9inZh9R+F2KcIPj1djeusn0b6oTtlWAzkUi1uN5P6cMIubZin23b2nR75YLay4lQx/ZFch1YrV5D7wDLkTJM/2QKNi2LmRp75jJ7BGjEF4vYk691DKRjtU6/XTBI1nrWnzMfSGcLD7A666zWF0SVh6nMTyXhmShx1KtsY3mxSzhLSPu4xFl2OF8NeFAdhJKkL0U2UswSrEqNKsyZ80N7V2BvAsI1eyqXGCMsJ9KdoIQTCwfP5xkE94+bTa162kNftrPne9/8VdG+x10GLCaToiVjEM1Y1d0c312O7eZzO9zkxtrIjEIupy7JUfC/K0Cy/6RNe2x61iBpqC/T7QYMVUZ8zxgyyjuBj7vicS8jnncSbin7vB+suSwTK3viDwUJ6rPjtonXZ7xtnPOnqNQecl0eUKpeoxXl3iOT98X4+kLujrgJOiyG8FqPrIz/HVZU/M71kBaRr3CQii8wP61F/Em5FOmIgUrSyyVx0Do3qWDZ1aW2iU2NG6ZEUYb3PY9PkqvuX/2+//poOZpDfysP3e+9dIvzPNBz3LzL7MRuyq0388y/586hlBtkyfXdqAiZ+WFgTuWBxBxni6sX/AiHLDpVYySkQWMDGwO5IXRk+3gLaIi5dR1ec13+JcXsxfcYlROUGJWNbtgPRrwkIK70S1O0wXf9Av+mie4nuFk8j67QQdyWRDpEbiaJ0WO5+bs0WdeiTRtRk8UA1y5u+gzLeb2xS73DKqQ/UbhA4j7UcjAyKi3pG9fTHto1zAVDgGZNZLOtLZ/BItsReJonvzHcuazFrqp/975xos/M+vRHvNCLkcWnGdz0s6A7nyIinYoTMVZOeEuHuMiQUdys5Vbr8DEyS1VLFRbVHnCXRXw0OmRmIVd+hSuYGo26IQ9bpVL5ss5406f2A3YENEGrXngwJfDPiflhBe82zypLjjLU74B/M7T7MxP7a6/2MhsqgEfmZydNOFEzuv+Jru64kk6YyF8fn/ByhlY/6KVLqwVnegWigexaPmJKZZJx6QqIiq0dSinEDcEkcjxSUxi3ceFw3CVTy1h9vHoz8/6E+BNo9SWXbZ8Yhw28oklQMZCk1by5SzymFdcmpDIGi5W1q5tSYEcHUbm6XJXYPl6EUNX4RRTxh6syQqVs8nM5By4fS7LEWF4yB1T8rd0jgkqe926nXqs4oDjrVd5MHqHdVc4+wsyrZjM32NLFLuNeBVF3LgxXj5mbgzr4Qa3XIdFsWQpIk++YjfY5VKPrKRbKt6DuSb3KqLec3jZDCdd4oVr5FVK4HW57UXcT4f2ZXZcJWxWkd1CFr0CMZg8vXrGAfDVl39uBqxz4s3sN+pGkcnD0FrBuqrHHQeGec6HRqxlc+sH6Lsy1JEiVXSikLTqoPMxxdoBu9qhLER4wWfkpPg6IvEiullCFIdkWqzoHe51Qp6YCK0r7oosjE5YhYfE5YL3i0/kZYwJGM4fkukpPTfCcYSACrNkwlCUPTs7FOUKv0wsBdwPelYGQhRJUtEENCUzTzEwhoHftX5Dsik8EltZB3qe4lT5RIuUI5OxEssavcSL1oizxLqlPLr807P9BPjOC2+aoRKJE+HIl4yM3FWX3GSayIlFdx2nmCAS8ULxkivdDRVS5FNyawC5pBftcZCXnCnf6gucFTdWZ0f4An064PeJhFVUzLnXP7KM4rQoiVWfD7TGVIX1KnoxPGSUntvNowUex7IVPHmbuewnihOH49JVEY/TG3q6ZM3tsQpcitWIjtfhuhfiZSG3ixtrVxMWMFAiHp0TuOJ8nltfg0LuBTREvuJCK14115zYAVePMooR14tJMbfGeQ/O/vBsA+D14x+Znc4xw3zEZaG5E3bINOwZkWzpsHIT6wKyXWjGUZdYPvPyiV23EvUOkV0vna5dBhVVDVkevXAqPKXsDd9bn1El6+vHb5hMnEtVxKkDh0bh6gmXprC8RBnr7HjVJ/qApiL3O8xlrl9UrEUbXInEix+SOSG7VcnCLSxjSBxONwJl5eVH6ZLc5OzLSjw+gXhluRo3S7g//OMzDoA7PzbznSM2plOryCVunysxkfUq9r0B0yjndHZDblxCFw7DvtXJz5IxC7NgRIfdaGD/omQ6OJGFS1EMFx0dx+f+418/0wVs6tv9p83LOTp+wxx4oWXYFKsxY+XTEZq2XOgQYVwRkJxzXV1bPZwddYyoBF9nQwK/Y89e15cV8QGxk5Kml5ZbH8vc33ifu2PIp/2Ptr/33yvgfO3we6aMBqQiByVLDcKPD4Svv6Bwt9gKHN5PznCDLkdZhollKWLMVTbHD4TrN2Wv/zwHrsuwnLF0VjhFSV/EE9KCt86bLZDw/w4M597RD829eI9/lDeWnLFTiKnCinkwwD/Y5+AyI9YrTvMxWm4F6bDML61EihuKXk/KnvHpD57jPE04L2fWbDovxUXc460nv2mPgAajzHnpzk/MmieyC3BelWxUMgnzKAqPKz9m38nsVXFaiP6vh1HbzNIR54ksZzjQWedOoVHbd6nmV8ycBJ1qXOWxzFP+2XCZtAb3ppbUnNcOf2B2hfqFeP6krEroeh3rnnHiKpRZsWPZMh2SqqIbhFTpDX/t88kAAAGSSURBVB+zYsNdszt5r3R6PC7XcJ05pb4iK8VDK0a5/uduG1dLlZ7hIM5Xbn3XeFVAL960dKvHbkhXF2xGA/zS4WE545iSkSfEzzXicsmFTuyL4oZe2EFLbnrsb+7TX11zKTQonVBEm4yzGx6c/rY9AhoMoLY5DW5OHam1AKijyg2O0QKgwc2pI7UWAHVUucExWgA0uDl1pNYCoI4qNzhGC4AGN6eO1FoA1FHlBsdoAdDg5tSRWguAOqrc4BgtABrcnDpSawFQR5UbHKMFQIObU0dqLQDqqHKDY7QAaHBz6kitBUAdVW5wjBYADW5OHam1AKijyg2O0QKgwc2pI7UWAHVUucExWgA0uDl1pNYCoI4qNzhGC4AGN6eO1FoA1FHlBsdoAdDg5tSRWguAOqrc4BgtABrcnDpSawFQR5UbHKMFQIObU0dqLQDqqHKDY7QAaHBz6kitBUAdVW5wjBYADW5OHam1AKijyg2O0QKgwc2pI7UWAHVUucExWgA0uDl1pNYCoI4qNzhGC4AGN6eO1P4N4fWgcG6aV1kAAAAASUVORK5CYII="}],"fabricOptions":{"header":"package com.example.mod;","entity":"Entity","render":"","members":""}}
\ No newline at end of file
diff --git a/assets/models/tentacle.png b/assets/models/tentacle.png
new file mode 100644
index 00000000..1026c1c0
Binary files /dev/null and b/assets/models/tentacle.png differ
diff --git a/assets/spectral_clock_0.xcf b/assets/spectral_clock_0.xcf
new file mode 100644
index 00000000..82bac275
Binary files /dev/null and b/assets/spectral_clock_0.xcf differ
diff --git a/src/main/java/com/minelittlepony/unicopia/Config.java b/src/main/java/com/minelittlepony/unicopia/Config.java
index c0f86374..3a128a0e 100644
--- a/src/main/java/com/minelittlepony/unicopia/Config.java
+++ b/src/main/java/com/minelittlepony/unicopia/Config.java
@@ -45,6 +45,16 @@ public class Config extends com.minelittlepony.common.util.settings.Config {
.addComment("Removes butterflies from spawning in your world")
.addComment("Turn this ON if you have another mod that adds butterflies.");
+ public final Setting simplifiedPortals = value("compatibility", "simplifiedPortals", false)
+ .addComment("Disables dynamic portal rendering");
+
+ public final Setting fancyPortalRefreshRate = value("client", "fancyPortalRefreshRate", -1L)
+ .addComment("Sets the refresh rate of portals when using fancy portal rendering")
+ .addComment("Set to -1 (default) for unlimited");
+
+ public final Setting maxPortalRecursion = value("client", "maxPortalRecursion", 2)
+ .addComment("Sets the maximum depth to reach when rendering portals through portals");
+
public Config() {
super(new HeirarchicalJsonConfigAdapter(new GsonBuilder()
.registerTypeAdapter(Race.class, RegistryTypeAdapter.of(Race.REGISTRY))
diff --git a/src/main/java/com/minelittlepony/unicopia/Debug.java b/src/main/java/com/minelittlepony/unicopia/Debug.java
index 2e5a9982..8cc8847e 100644
--- a/src/main/java/com/minelittlepony/unicopia/Debug.java
+++ b/src/main/java/com/minelittlepony/unicopia/Debug.java
@@ -1,33 +1,35 @@
package com.minelittlepony.unicopia;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import com.google.common.collect.Sets;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.entity.mob.AirBalloonEntity;
import com.minelittlepony.unicopia.entity.mob.UEntities;
import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.registry.Registries;
+import net.minecraft.registry.tag.TagKey;
+import net.minecraft.util.Identifier;
import net.minecraft.world.World;
+import net.minecraft.world.dimension.DimensionTypes;
public interface Debug {
boolean SPELLBOOK_CHAPTERS = Boolean.getBoolean("unicopia.debug.spellbookChapters");
boolean CHECK_GAME_VALUES = Boolean.getBoolean("unicopia.debug.checkGameValues");
+ boolean CHECK_TRAIT_COVERAGE = Boolean.getBoolean("unicopia.debug.checkTraitCoverage");
- boolean[] TESTS_COMPLETE = {false};
+ AtomicReference LAST_TESTED_WORLD = new AtomicReference<>(null);
static void runTests(World world) {
- if (!CHECK_GAME_VALUES || TESTS_COMPLETE[0]) {
+ if (!CHECK_GAME_VALUES || !world.getDimensionKey().getValue().equals(DimensionTypes.OVERWORLD_ID) || (LAST_TESTED_WORLD.getAndSet(world) == world)) {
return;
}
- TESTS_COMPLETE[0] = true;
- try {
- Registries.ITEM.getEntrySet().forEach(entry -> {
- if (SpellTraits.of(entry.getValue()).isEmpty()) {
- // Unicopia.LOGGER.warn("No traits registered for item {}", entry.getKey());
- }
- });
- } catch (Throwable t) {
- throw new IllegalStateException("Tests failed", t);
+ if (CHECK_TRAIT_COVERAGE) {
+ testTraitCoverage();
}
try {
@@ -40,4 +42,30 @@ public interface Debug {
throw new IllegalStateException("Tests failed", t);
}
}
+
+ private static void testTraitCoverage() {
+ Registries.ITEM.getEntrySet().stream().collect(Collectors.toMap(
+ entry -> entry.getKey().getValue().getNamespace(),
+ Set::of,
+ Sets::union
+ )).forEach((namespace, entries) -> {
+ @SuppressWarnings("deprecation")
+ var unregistered = entries.stream()
+ .filter(entry -> !entry.getValue().getRegistryEntry().isIn(UTags.HAS_NO_TRAITS) && SpellTraits.of(entry.getValue()).isEmpty())
+ .map(entry -> {
+ String id = entry.getKey().getValue().toString();
+
+ return id + "(" + Registries.ITEM.streamTags()
+ .filter(entry.getValue().getRegistryEntry()::isIn)
+ .map(TagKey::id)
+ .map(Identifier::toString)
+ .collect(Collectors.joining(", ")) + ")";
+ })
+ .toList();
+
+ if (!unregistered.isEmpty()) {
+ Unicopia.LOGGER.warn("No traits registered for {} items in namepsace {} {}", unregistered.size(), namespace, String.join(",\r\n", unregistered));
+ }
+ });
+ }
}
diff --git a/src/main/java/com/minelittlepony/unicopia/EntityConvertable.java b/src/main/java/com/minelittlepony/unicopia/EntityConvertable.java
index d21ac152..6a0ffce7 100644
--- a/src/main/java/com/minelittlepony/unicopia/EntityConvertable.java
+++ b/src/main/java/com/minelittlepony/unicopia/EntityConvertable.java
@@ -25,7 +25,7 @@ public interface EntityConvertable extends WorldConvertable {
* Gets the center position where this caster is located.
*/
default Vec3d getOriginVector() {
- return asEntity().getPos();
+ return asEntity().getPos().add(0, asEntity().getHeight() * 0.5F, 0);
}
@Override
diff --git a/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java b/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java
index be559b38..9f055212 100644
--- a/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java
+++ b/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java
@@ -33,7 +33,7 @@ public interface EquinePredicates {
Predicate IS_CASTER = e -> !e.isRemoved() && (e instanceof Caster || IS_PLAYER.test(e));
Predicate IS_PLACED_SPELL = e -> e instanceof Caster && !e.isRemoved();
- Predicate IS_MAGIC_IMMUNE = e -> (e instanceof MagicImmune || !(e instanceof LivingEntity)) && !(e instanceof ItemEntity);
+ Predicate IS_MAGIC_IMMUNE = e -> (e instanceof MagicImmune || !(e instanceof LivingEntity)) && !(e instanceof ItemEntity) && !(e instanceof ExperienceOrbEntity);
Predicate EXCEPT_MAGIC_IMMUNE = IS_MAGIC_IMMUNE.negate();
Predicate VALID_LIVING_AND_NOT_MAGIC_IMMUNE = EntityPredicates.VALID_LIVING_ENTITY.and(EXCEPT_MAGIC_IMMUNE);
diff --git a/src/main/java/com/minelittlepony/unicopia/InteractionManager.java b/src/main/java/com/minelittlepony/unicopia/InteractionManager.java
index 0e507331..7c60d0d5 100644
--- a/src/main/java/com/minelittlepony/unicopia/InteractionManager.java
+++ b/src/main/java/com/minelittlepony/unicopia/InteractionManager.java
@@ -1,22 +1,19 @@
package com.minelittlepony.unicopia;
import java.util.Map;
-import java.util.Optional;
+import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import com.minelittlepony.unicopia.ability.magic.CasterView;
import com.minelittlepony.unicopia.entity.player.dummy.DummyPlayerEntity;
-import com.minelittlepony.unicopia.server.world.Ether;
+import com.minelittlepony.unicopia.particle.ParticleSpawner;
import com.mojang.authlib.GameProfile;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
-import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
-import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class InteractionManager {
@@ -37,11 +34,8 @@ public class InteractionManager {
return INSTANCE;
}
- public Optional getCasterView(BlockView view) {
- if (view instanceof ServerWorld world) {
- return Optional.of(Ether.get(world));
- }
- return Optional.empty();
+ public ParticleSpawner createBoundParticle(UUID id) {
+ return ParticleSpawner.EMPTY;
}
public Map readChapters(PacketByteBuf buf) {
@@ -92,4 +86,8 @@ public class InteractionManager {
public PlayerEntity createPlayer(World world, GameProfile profile) {
return new DummyPlayerEntity(world, profile);
}
+
+ public void sendPlayerLookAngles(PlayerEntity player) {
+
+ }
}
diff --git a/src/main/java/com/minelittlepony/unicopia/Owned.java b/src/main/java/com/minelittlepony/unicopia/Owned.java
index 37642a1d..337a59ed 100644
--- a/src/main/java/com/minelittlepony/unicopia/Owned.java
+++ b/src/main/java/com/minelittlepony/unicopia/Owned.java
@@ -5,6 +5,8 @@ import java.util.UUID;
import org.jetbrains.annotations.Nullable;
+import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
+
import net.minecraft.entity.Entity;
/**
@@ -27,14 +29,23 @@ public interface Owned {
* Since {@link Owned#getMaster()} will only return if the owner is loaded, use this to perform checks
* in the owner's absence.
*/
- default Optional getMasterId() {
- return Optional.of(getMaster()).map(Entity::getUuid);
+ Optional getMasterId();
+
+ default boolean isOwnerOrFriend(Entity target) {
+ return FriendshipBraceletItem.isComrade(this, target) || isOwnerOrVehicle(target);
+ }
+
+ default boolean isOwnerOrVehicle(@Nullable Entity target) {
+ if (isOwnedBy(target)) {
+ return true;
+ }
+
+ Entity owner = getMaster();
+ return target != null && owner != null && owner.isConnectedThroughVehicle(target);
}
default boolean isOwnedBy(@Nullable Object owner) {
- return owner instanceof Entity e
- && getMasterId().isPresent()
- && e.getUuid().equals(getMasterId().get());
+ return owner instanceof Entity e && e.getUuid().equals(getMasterId().orElse(null));
}
default boolean hasCommonOwner(Owned> sibling) {
diff --git a/src/main/java/com/minelittlepony/unicopia/USounds.java b/src/main/java/com/minelittlepony/unicopia/USounds.java
index 23980e16..3f53eca3 100644
--- a/src/main/java/com/minelittlepony/unicopia/USounds.java
+++ b/src/main/java/com/minelittlepony/unicopia/USounds.java
@@ -62,11 +62,18 @@ public interface USounds {
SoundEvent ENTITY_SOMBRA_AMBIENT = register("entity.sombra.ambient");
SoundEvent ENTITY_SOMBRA_LAUGH = register("entity.sombra.laugh");
SoundEvent ENTITY_SOMBRA_SNICKER = register("entity.sombra.snicker");
- SoundEvent ENTITY_SOMBRA_SCARY = USounds.Vanilla.ENTITY_GHAST_AMBIENT;
+ SoundEvent ENTITY_SOMBRA_SCARY = ENTITY_GHAST_AMBIENT;
SoundEvent ENTITY_CRYSTAL_SHARDS_AMBIENT = BLOCK_AMETHYST_BLOCK_HIT;
SoundEvent ENTITY_CRYSTAL_SHARDS_JOSTLE = BLOCK_AMETHYST_BLOCK_BREAK;
+ SoundEvent ENTITY_IGNIMEOUS_BULB_HURT = ENTITY_WARDEN_HURT;
+ SoundEvent ENTITY_IGNIMEOUS_BULB_DEATH = ENTITY_WARDEN_DEATH;
+
+ SoundEvent ENTITY_TENTACLE_ROAR = ENTITY_RAVAGER_ROAR;
+ SoundEvent ENTITY_TENTACLE_AMBIENT = BLOCK_CONDUIT_AMBIENT_SHORT;
+ SoundEvent ENTITY_TENTACLE_DIG = ENTITY_WARDEN_DIG;
+
SoundEvent ITEM_AMULET_CHARGING = register("item.amulet.charging");
SoundEvent ITEM_AMULET_RECHARGE = register("item.amulet.recharge");
@@ -90,6 +97,8 @@ public interface USounds {
SoundEvent ITEM_STAFF_STRIKE = ENTITY_PLAYER_ATTACK_CRIT;
SoundEvent ITEM_MAGIC_STAFF_CHARGE = ENTITY_GUARDIAN_ATTACK;
+ SoundEvent ITEM_CURING_JOKE_CURE = BLOCK_AMETHYST_BLOCK_BREAK;
+
SoundEvent ITEM_ROCK_LAND = BLOCK_STONE_HIT;
RegistryEntry.Reference ITEM_MUFFIN_BOUNCE = BLOCK_NOTE_BLOCK_BANJO;
diff --git a/src/main/java/com/minelittlepony/unicopia/UTags.java b/src/main/java/com/minelittlepony/unicopia/UTags.java
index 91b4fcae..08da479c 100644
--- a/src/main/java/com/minelittlepony/unicopia/UTags.java
+++ b/src/main/java/com/minelittlepony/unicopia/UTags.java
@@ -23,6 +23,7 @@ public interface UTags {
TagKey- SHADES = item("shades");
TagKey
- CHANGELING_EDIBLE = item("food_types/changeling_edible");
TagKey
- SPOOKED_MOB_DROPS = item("spooked_mob_drops");
+ TagKey
- HAS_NO_TRAITS = item("has_no_traits");
TagKey
- IS_DELIVERED_AGGRESSIVELY = item("is_delivered_aggressively");
TagKey
- FLOATS_ON_CLOUDS = item("floats_on_clouds");
TagKey
- COOLS_OFF_KIRINS = item("cools_off_kirins");
@@ -42,6 +43,8 @@ public interface UTags {
TagKey CRYSTAL_HEART_BASE = block("crystal_heart_base");
TagKey CRYSTAL_HEART_ORNAMENT = block("crystal_heart_ornament");
+ TagKey UNAFFECTED_BY_GROW_ABILITY = block("unaffected_by_grow_ability");
+ TagKey KICKS_UP_DUST = block("kicks_up_dust");
TagKey POLEARM_MINEABLE = block("mineable/polearm");
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java
index d9a977b4..8a291d0a 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java
@@ -209,6 +209,7 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable {
warmup = 0;
if (data.isPresent()) {
+ InteractionManager.instance().sendPlayerLookAngles(player.asEntity());
Channel.CLIENT_PLAYER_ABILITY.sendToServer(new MsgPlayerAbility<>(ability, data, ActivationType.NONE));
} else {
player.asEntity().playSound(USounds.GUI_ABILITY_FAIL, 1, 1);
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java
index 4c841b1a..64fabfa4 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java
@@ -76,9 +76,9 @@ public class ChangeFormAbility implements Ability {
targets.forEach(target -> {
Race supressed = target.getSuppressedRace();
if (target == player || supressed.isUnset() == isTransforming) {
- Race actualRace = target.getSpecies();
+ Race actualRace = isTransforming ? target.getSpecies() : Race.UNSET;
target.setSpecies(supressed.or(player.getCompositeRace().potential()));
- target.setSuppressedRace(isTransforming ? actualRace : Race.UNSET);
+ target.setSuppressedRace(actualRace);
}
});
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyGrowAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyGrowAbility.java
index ee82de7d..ec910617 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyGrowAbility.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyGrowAbility.java
@@ -1,22 +1,37 @@
package com.minelittlepony.unicopia.ability;
import java.util.Optional;
-
+import java.util.function.DoubleSupplier;
+import java.util.function.Supplier;
import com.minelittlepony.unicopia.Race;
+import com.minelittlepony.unicopia.USounds;
+import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.entity.player.Pony;
+import com.minelittlepony.unicopia.item.TransformCropsRecipe;
+import com.minelittlepony.unicopia.item.URecipes;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
+import com.minelittlepony.unicopia.particle.ParticleUtils;
+import com.minelittlepony.unicopia.server.world.BlockDestructionManager;
import com.minelittlepony.unicopia.util.TraceHelper;
+import com.minelittlepony.unicopia.util.VecHelper;
+
+import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
+import net.minecraft.block.CarrotsBlock;
+import net.minecraft.block.FarmlandBlock;
import net.minecraft.item.BoneMealItem;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
+import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
+import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
+import net.minecraft.world.WorldEvents;
/**
* Earth Pony ability to grow crops
@@ -57,10 +72,14 @@ public class EarthPonyGrowAbility implements Ability {
public boolean apply(Pony player, Pos data) {
int count = 0;
- for (BlockPos pos : BlockPos.iterate(
- data.pos().add(-2, -2, -2),
- data.pos().add( 2, 2, 2))) {
- count += applySingle(player.asWorld(), player.asWorld().getBlockState(pos), pos);
+ if (!applyDirectly(player, data.pos())) {
+ for (BlockPos pos : BlockPos.iterate(
+ data.pos().add(-2, -2, -2),
+ data.pos().add( 2, 2, 2))) {
+ count += applySingle(player, player.asWorld(), player.asWorld().getBlockState(pos), pos);
+ }
+ } else {
+ count = 1;
}
if (count > 0) {
@@ -69,7 +88,7 @@ public class EarthPonyGrowAbility implements Ability {
return true;
}
- protected int applySingle(World w, BlockState state, BlockPos pos) {
+ protected int applySingle(Pony player, World w, BlockState state, BlockPos pos) {
ItemStack stack = new ItemStack(Items.BONE_MEAL);
@@ -77,12 +96,33 @@ public class EarthPonyGrowAbility implements Ability {
return growable.grow(w, state, pos) ? 1 : 0;
}
+ if (state.isOf(Blocks.CARROTS)) {
+ if (state.get(CarrotsBlock.AGE) == CarrotsBlock.MAX_AGE) {
+ boolean transform = w.random.nextInt(3) == 0;
+ spawnConversionParticles(w, pos, transform);
+ if (transform) {
+ w.setBlockState(pos, UBlocks.GOLD_ROOT.getDefaultState().with(CarrotsBlock.AGE, CarrotsBlock.MAX_AGE));
+ }
+
+ return 5;
+ }
+ }
+
+ if (w.getBlockState(pos).isIn(UTags.UNAFFECTED_BY_GROW_ABILITY)) {
+ return 0;
+ }
+
if (BoneMealItem.useOnFertilizable(stack, w, pos)) {
if (w.random.nextInt(350) == 0) {
if (w.getBlockState(pos.down()).isOf(Blocks.FARMLAND)) {
- w.setBlockState(pos.down(), Blocks.DIRT.getDefaultState());
+ FarmlandBlock.setToDirt(null, state, w, pos.down());
}
w.setBlockState(pos, UBlocks.PLUNDER_VINE_BUD.getDefaultState());
+ } else if (w.random.nextInt(5000) == 0) {
+ if (w.getBlockState(pos.down()).isOf(Blocks.FARMLAND)) {
+ FarmlandBlock.setToDirt(null, state, w, pos.down());
+ }
+ UBlocks.CURING_JOKE.grow(w, state, pos);
}
return 1;
}
@@ -94,6 +134,56 @@ public class EarthPonyGrowAbility implements Ability {
return 0;
}
+ private boolean applyDirectly(Pony player, BlockPos pos) {
+ return player.asWorld().getRecipeManager()
+ .getAllMatches(URecipes.GROWING, new TransformCropsRecipe.PlacementArea(player, pos), player.asWorld())
+ .stream()
+ .map(recipe -> recipe.value().checkPattern(player.asWorld(), pos))
+ .filter(result -> result.matchedLocations().size() + 1 >= TransformCropsRecipe.MINIMUM_INPUT)
+ .filter(result -> {
+ boolean transform = result.shoudTransform(player.asWorld().random);
+
+ player.playSound(USounds.ENTITY_CRYSTAL_SHARDS_AMBIENT, 1);
+
+ result.matchedLocations().forEach(cell -> {
+ spawnConversionParticles(player.asWorld(), cell.up(), false);
+ BlockDestructionManager manager = BlockDestructionManager.of(player.asWorld());
+ if (transform) {
+ if (manager.damageBlock(cell, 8) >= BlockDestructionManager.MAX_DAMAGE || player.asWorld().random.nextInt(20) == 0) {
+ player.asWorld().setBlockState(cell, Blocks.DIRT.getDefaultState());
+ player.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, cell, Block.getRawIdFromState(player.asWorld().getBlockState(cell)));
+ }
+ } else {
+ if (manager.damageBlock(cell, 4) >= BlockDestructionManager.MAX_DAMAGE || player.asWorld().random.nextInt(20) == 0) {
+ player.asWorld().setBlockState(cell, Blocks.DIRT.getDefaultState());
+ player.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, cell, Block.getRawIdFromState(player.asWorld().getBlockState(cell)));
+ }
+ }
+ });
+
+ spawnConversionParticles(player.asWorld(), pos, transform);
+ if (transform) {
+ player.asWorld().setBlockState(pos, result.recipe().getResult(player.asWorld(), pos));
+ }
+
+ return true;
+ })
+ .findFirst()
+ .isPresent();
+ }
+
+ private static void spawnConversionParticles(World w, BlockPos pos, boolean success) {
+ DoubleSupplier vecComponentFactory = () -> w.random.nextTriangular(0, 0.5);
+ Supplier posSupplier = () -> pos.toCenterPos().add(VecHelper.supply(vecComponentFactory));
+
+ for (int i = 0; i < 25; i++) {
+ ParticleUtils.spawnParticle(w, new MagicParticleEffect(0xFFFF00), posSupplier.get(), Vec3d.ZERO);
+ if (success) {
+ ParticleUtils.spawnParticle(w, ParticleTypes.CLOUD, posSupplier.get(), Vec3d.ZERO);
+ }
+ }
+ }
+
@Override
public void warmUp(Pony player, AbilitySlot slot) {
player.getMagicalReserves().getExertion().addPercent(30);
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java
index 1a5c72ef..e81f5a0d 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java
@@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.AwaitTickQueue;
import com.minelittlepony.unicopia.Race;
+import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.Living;
@@ -17,6 +18,7 @@ import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.server.world.BlockDestructionManager;
import com.minelittlepony.unicopia.util.PosHelper;
+import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
@@ -26,6 +28,7 @@ import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
+import net.minecraft.particle.BlockStateParticleEffect;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
@@ -33,6 +36,7 @@ import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper;
+import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.WorldEvents;
@@ -156,7 +160,10 @@ public class EarthPonyStompAbility implements Ability {
spawnEffectAround(player, center, radius, rad);
ParticleUtils.spawnParticle(player.getWorld(), UParticles.GROUND_POUND, player.getX(), player.getY() - 1, player.getZ(), 0, 0, 0);
- ParticleUtils.spawnParticle(player.getWorld(), UParticles.SHOCKWAVE, player.getX(), player.getY() - 1, player.getZ(), 0, 0, 0);
+ BlockState steppingState = player.getSteppingBlockState();
+ if (steppingState.isIn(UTags.KICKS_UP_DUST)) {
+ ParticleUtils.spawnParticle(player.getWorld(), new BlockStateParticleEffect(UParticles.DUST_CLOUD, steppingState), player.getBlockPos().down().toCenterPos(), Vec3d.ZERO);
+ }
iplayer.subtractEnergyCost(rad);
iplayer.asEntity().addExhaustion(3);
@@ -218,6 +225,12 @@ public class EarthPonyStompAbility implements Ability {
} else {
w.syncWorldEvent(WorldEvents.BLOCK_BROKEN, pos, Block.getRawIdFromState(state));
}
+
+ if (state.isIn(UTags.KICKS_UP_DUST)) {
+ if (w.random.nextInt(4) == 0 && w.isAir(pos.up()) && w.getFluidState(pos.up()).isEmpty()) {
+ ParticleUtils.spawnParticle(w, new BlockStateParticleEffect(UParticles.DUST_CLOUD, state), pos.up().toCenterPos(), VecHelper.supply(() -> w.random.nextTriangular(0, 0.1F)));
+ }
+ }
}
@Override
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/PegasusRainboomAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/PegasusRainboomAbility.java
index 087e1cb0..bbe7256e 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/PegasusRainboomAbility.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/PegasusRainboomAbility.java
@@ -10,10 +10,6 @@ import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
-import com.minelittlepony.unicopia.particle.OrientedBillboardParticleEffect;
-import com.minelittlepony.unicopia.particle.UParticles;
-
-import net.minecraft.util.math.Vec3d;
/**
* Pegasus ability to perform rainbooms
@@ -72,7 +68,6 @@ public class PegasusRainboomAbility implements Ability {
}
if (player.consumeSuperMove()) {
- player.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, player.getPhysics().getMotionAngle()), player.getOriginVector(), Vec3d.ZERO);
SpellType.RAINBOOM.withTraits().apply(player, CastingMethod.INNATE);
}
return true;
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java
index 99dda711..d965f9da 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java
@@ -7,6 +7,7 @@ import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.ability.magic.Caster;
+import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.player.Pony;
@@ -92,7 +93,11 @@ public class UnicornDispellAbility implements Ability {
public boolean apply(Pony player, Pos data) {
player.setAnimation(Animation.WOLOLO, Animation.Recipient.ANYONE);
Caster.stream(VecHelper.findInRange(player.asEntity(), player.asWorld(), data.vec(), 3, EquinePredicates.IS_PLACED_SPELL).stream()).forEach(target -> {
- target.getSpellSlot().clear();
+ target.getSpellSlot().forEach(spell -> {
+ spell.setDead();
+ spell.tickDying(target);
+ return Operation.ofBoolean(!spell.isDead());
+ }, true);
});
return true;
}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/Affine.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/Affine.java
index 007da627..7cdfdaef 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/Affine.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/Affine.java
@@ -19,4 +19,8 @@ public interface Affine {
default boolean isFriendlyTogether(Affine other) {
return getAffinity() != Affinity.BAD && other.getAffinity() != Affinity.BAD;
}
+
+ default boolean applyInversion(Affine other, boolean friendly) {
+ return isEnemy(other) ? !friendly : friendly;
+ }
}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java
index 98264836..150b1004 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java
@@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.*;
import com.minelittlepony.unicopia.entity.damage.UDamageSources;
import com.minelittlepony.unicopia.particle.ParticleSource;
+import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.server.world.ModificationType;
import com.minelittlepony.unicopia.util.SoundEmitter;
import com.minelittlepony.unicopia.util.VecHelper;
@@ -99,11 +100,7 @@ public interface Caster extends
}
default boolean canCastAt(Vec3d pos) {
- return findAllSpellsInRange(500, SpellType.ARCANE_PROTECTION::isOn).noneMatch(caster -> caster
- .getSpellSlot().get(SpellType.ARCANE_PROTECTION, false)
- .filter(spell -> spell.blocksMagicFor(caster, this, pos))
- .isPresent()
- );
+ return !Ether.get(asWorld()).anyMatch(SpellType.ARCANE_PROTECTION, (spell, caster) -> spell.blocksMagicFor(caster, this, pos));
}
static Stream> stream(Stream entities) {
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/CasterView.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/CasterView.java
deleted file mode 100644
index 572f4542..00000000
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/CasterView.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.minelittlepony.unicopia.ability.magic;
-
-import java.util.Map;
-import java.util.function.Predicate;
-import java.util.stream.Stream;
-
-import org.jetbrains.annotations.Nullable;
-
-import com.minelittlepony.unicopia.EquinePredicates;
-import com.minelittlepony.unicopia.ability.magic.spell.Spell;
-import com.minelittlepony.unicopia.util.VecHelper;
-
-import net.minecraft.entity.Entity;
-import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.math.Vec3d;
-import net.minecraft.world.*;
-
-public interface CasterView {
- EntityView getWorld();
-
- default
Stream, S>> findAllSpellsInRange(BlockPos pos, double radius, SpellPredicate type) {
- return findAllCastersInRange(pos, radius).flatMap(caster -> {
- return caster.getSpellSlot().stream(type, false).map(spell -> {
- return Map.entry(caster, spell);
- });
- });
- }
-
- default Stream> findAllCastersInRange(BlockPos pos, double radius) {
- return findAllCastersInRange(pos, radius, null);
- }
-
- default Stream> findAllCastersInRange(BlockPos pos, double radius, @Nullable Predicate test) {
- return Caster.stream(findAllEntitiesInRange(pos, radius, test == null ? EquinePredicates.IS_CASTER : EquinePredicates.IS_CASTER.and(test)));
- }
-
- default Stream findAllEntitiesInRange(BlockPos pos, double radius, @Nullable Predicate test) {
- return VecHelper.findInRange(null, getWorld(), Vec3d.ofCenter(pos), radius, test).stream();
- }
-
- default Stream findAllEntitiesInRange(BlockPos pos, double radius) {
- return findAllEntitiesInRange(pos, radius, null);
- }
-}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java
index bbe1ad70..e703911d 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java
@@ -39,6 +39,13 @@ public interface SpellContainer {
*/
void put(@Nullable Spell effect);
+ /**
+ * Cleanly removes a spell from this spell container.
+ *
+ * @param spellid ID of the spell to remove.
+ */
+ void remove(UUID spellid);
+
/**
* Removes all active effects that match or contain a matching effect.
*
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java
index f6219d62..68a006ed 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java
@@ -69,11 +69,20 @@ public abstract class AbstractDelegatingSpell implements Spell,
getDelegates().forEach(Spell::setDead);
}
+ @Override
+ public void tickDying(Caster> caster) {
+ }
+
@Override
public boolean isDead() {
return getDelegates().isEmpty() || getDelegates().stream().allMatch(Spell::isDead);
}
+ @Override
+ public boolean isDying() {
+ return false;
+ }
+
@Override
public boolean isDirty() {
return dirty || getDelegates().stream().anyMatch(Spell::isDirty);
@@ -110,7 +119,13 @@ public abstract class AbstractDelegatingSpell implements Spell,
@Override
public boolean tick(Caster> source, Situation situation) {
- return execute(getDelegates().stream(), a -> a.tick(source, situation));
+ return execute(getDelegates().stream(), a -> {
+ if (a.isDying()) {
+ a.tickDying(source);
+ return !a.isDead();
+ }
+ return a.tick(source, situation);
+ });
}
@Override
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java
index 7154056a..ed03506d 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java
@@ -13,10 +13,6 @@ import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgCasterLookRequest;
-import com.minelittlepony.unicopia.particle.OrientedBillboardParticleEffect;
-import com.minelittlepony.unicopia.particle.ParticleHandle;
-import com.minelittlepony.unicopia.particle.UParticles;
-import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable;
@@ -24,6 +20,7 @@ import net.minecraft.nbt.*;
import net.minecraft.registry.*;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
+import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
@@ -41,9 +38,10 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
private RegistryKey dimension;
/**
- * The visual effect
+ * ID of the placed counterpart of this spell.
*/
- private final ParticleHandle particlEffect = new ParticleHandle();
+ @Nullable
+ private UUID placedSpellId;
/**
* The cast spell entity
@@ -58,6 +56,13 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
public float pitch;
public float yaw;
+ private int prevAge;
+ private int age;
+
+ private boolean dead;
+ private int prevDeathTicks;
+ private int deathTicks;
+
private Optional position = Optional.empty();
public PlaceableSpell(CustomisedSpellType> type) {
@@ -69,23 +74,42 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
return this;
}
+ public float getAge(float tickDelta) {
+ return MathHelper.lerp(tickDelta, prevAge, age);
+ }
+
+ public float getScale(float tickDelta) {
+ float add = MathHelper.clamp(getAge(tickDelta) / 25F, 0, 1);
+ float subtract = dead ? 1 - (MathHelper.lerp(tickDelta, prevDeathTicks, deathTicks) / 20F) : 0;
+ return MathHelper.clamp(add - subtract, 0, 1);
+ }
+
+ @Override
+ public boolean isDying() {
+ return dead && deathTicks > 0;
+ }
+
+ @Override
+ public void setDead() {
+ super.setDead();
+ dead = true;
+ deathTicks = 20;
+ }
+
+ @Override
+ public boolean isDead() {
+ return dead && deathTicks <= 0;
+ }
+
@Override
public Collection getDelegates() {
return List.of(spell);
}
- @Override
- public void setDead() {
- super.setDead();
- particlEffect.destroy();
- }
-
@Override
public boolean tick(Caster> source, Situation situation) {
-
if (situation == Situation.BODY) {
if (!source.isClient()) {
-
if (dimension == null) {
dimension = source.asWorld().getRegistryKey();
if (source instanceof Pony) {
@@ -105,29 +129,31 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
if (situation == Situation.GROUND_ENTITY) {
if (!source.isClient()) {
- Ether ether = Ether.get(source.asWorld());
- if (ether.getEntry(getType(), source).isEmpty()) {
+ if (Ether.get(source.asWorld()).get(this, source) == null) {
setDead();
return false;
}
}
- if (spell instanceof PlacementDelegate delegate) {
- delegate.updatePlacement(source, this);
+ prevAge = age;
+ if (age < 25) {
+ age++;
}
- getParticleEffectAttachment(source).ifPresent(p -> {
- p.setAttribute(Attachment.ATTR_COLOR, spell.getType().getColor());
- });
-
return super.tick(source, Situation.GROUND);
}
return !isDead();
}
+ @Override
+ public void tickDying(Caster> caster) {
+ prevDeathTicks = deathTicks;
+ deathTicks--;
+ }
+
private void checkDetachment(Caster> source, EntityValues> target) {
- if (getWorld(source).map(Ether::get).flatMap(ether -> ether.getEntry(getType(), target.uuid())).isEmpty()) {
+ if (getWorld(source).map(Ether::get).map(ether -> ether.get(getType(), target, placedSpellId)).isEmpty()) {
setDead();
}
}
@@ -143,7 +169,8 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
entity.getSpellSlot().put(copy);
entity.setCaster(source);
entity.getWorld().spawnEntity(entity);
- Ether.get(entity.getWorld()).put(getType(), entity);
+ placedSpellId = copy.getUuid();
+ Ether.get(entity.getWorld()).getOrCreate(copy, entity);
castEntity.set(entity);
setDirty();
@@ -174,8 +201,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
if (!source.isClient()) {
castEntity.getTarget().ifPresent(target -> {
getWorld(source).map(Ether::get)
- .flatMap(ether -> ether.getEntry(getType(), target.uuid()))
- .ifPresent(Ether.Entry::markDead);
+ .ifPresent(ether -> ether.remove(getType(), target.uuid()));
});
castEntity.set(null);
getSpellEntity(source).ifPresent(e -> {
@@ -183,7 +209,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
});
if (source.asEntity() instanceof CastSpellEntity spellcast) {
- Ether.get(source.asWorld()).remove(getType(), source);
+ Ether.get(source.asWorld()).remove(this, source);
}
}
super.onDestroyed(source);
@@ -197,12 +223,6 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
return castEntity.getTarget().map(EntityValues::pos);
}
- public Optional getParticleEffectAttachment(Caster> source) {
- return particlEffect.update(getUuid(), source, spawner -> {
- spawner.addParticle(new OrientedBillboardParticleEffect(UParticles.MAGIC_RUNES, pitch + 90, yaw), Vec3d.ZERO, Vec3d.ZERO);
- });
- }
-
protected Optional getWorld(Caster> source) {
return Optional.ofNullable(dimension)
.map(dim -> source.asWorld().getServer().getWorld(dim));
@@ -211,11 +231,17 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
+ compound.putBoolean("dead", dead);
+ compound.putInt("deathTicks", deathTicks);
+ compound.putInt("age", age);
compound.putFloat("pitch", pitch);
compound.putFloat("yaw", yaw);
position.ifPresent(pos -> {
compound.put("position", NbtSerialisable.writeVector(pos));
});
+ if (placedSpellId != null) {
+ compound.putUuid("placedSpellId", placedSpellId);
+ }
if (dimension != null) {
compound.putString("dimension", dimension.getValue().toString());
}
@@ -226,9 +252,13 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
+ dead = compound.getBoolean("dead");
+ deathTicks = compound.getInt("deathTicks");
+ age = compound.getInt("age");
pitch = compound.getFloat("pitch");
yaw = compound.getFloat("yaw");
position = compound.contains("position") ? Optional.of(NbtSerialisable.readVector(compound.getList("position", NbtElement.FLOAT_TYPE))) : Optional.empty();
+ placedSpellId = compound.containsUuid("placedSpellId") ? compound.getUuid("placedSpellId") : null;
if (compound.contains("dimension", NbtElement.STRING_TYPE)) {
Identifier id = Identifier.tryParse(compound.getString("dimension"));
if (id != null) {
@@ -257,9 +287,6 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
}
public interface PlacementDelegate {
-
void onPlaced(Caster> source, PlaceableSpell parent, CastSpellEntity entity);
-
- void updatePlacement(Caster> source, PlaceableSpell parent);
}
}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java
index 4bf0d726..6c6d1099 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java
@@ -1,12 +1,16 @@
package com.minelittlepony.unicopia.ability.magic.spell;
+import org.jetbrains.annotations.Nullable;
+
+import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.*;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.player.Pony;
-import com.minelittlepony.unicopia.particle.ParticleHandle;
-import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
+import com.minelittlepony.unicopia.particle.OrientedBillboardParticleEffect;
+import com.minelittlepony.unicopia.particle.ParticleSpawner;
+import com.minelittlepony.unicopia.particle.TargetBoundParticleEffect;
import com.minelittlepony.unicopia.server.world.ModificationType;
import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.util.shape.Shape;
@@ -26,7 +30,8 @@ public class RainboomAbilitySpell extends AbstractSpell {
private static final int RADIUS = 5;
private static final Shape EFFECT_RANGE = new Sphere(false, RADIUS);
- private final ParticleHandle particlEffect = new ParticleHandle();
+ @Nullable
+ private ParticleSpawner boundParticle;
private int age;
@@ -35,11 +40,6 @@ public class RainboomAbilitySpell extends AbstractSpell {
setHidden(true);
}
- @Override
- protected void onDestroyed(Caster> source) {
- particlEffect.destroy();
- }
-
@Override
public boolean tick(Caster> source, Situation situation) {
@@ -47,14 +47,15 @@ public class RainboomAbilitySpell extends AbstractSpell {
return false;
}
- particlEffect.update(getUuid(), source, spawner -> {
- spawner.addParticle(UParticles.RAINBOOM_TRAIL, source.getOriginVector(), Vec3d.ZERO);
- }).ifPresent(attachment -> {
- attachment.setAttribute(Attachment.ATTR_BOUND, 1);
- });
-
if (source.isClient()) {
- // source.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, source.getPhysics().getMotionAngle()), source.getOriginVector(), Vec3d.ZERO);
+ if (boundParticle == null) {
+ boundParticle = InteractionManager.INSTANCE.createBoundParticle(getUuid());
+ }
+ boundParticle.addParticle(new TargetBoundParticleEffect(UParticles.RAINBOOM_TRAIL, source.asEntity()), source.getOriginVector(), Vec3d.ZERO);
+
+ if (age == 0) {
+ source.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, source.getPhysics().getMotionAngle()), source.getOriginVector(), Vec3d.ZERO);
+ }
}
source.findAllEntitiesInRange(RADIUS).forEach(e -> {
@@ -92,5 +93,6 @@ public class RainboomAbilitySpell extends AbstractSpell {
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
age = compound.getInt("age");
+ boundParticle = null;
}
}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java
index d6bb1ccf..55a5f3bf 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java
@@ -61,6 +61,8 @@ public interface Spell extends NbtSerialisable, Affine {
*/
boolean isDead();
+ boolean isDying();
+
/**
* Returns true if this effect has changes that need to be sent to the client.
*/
@@ -68,6 +70,7 @@ public interface Spell extends NbtSerialisable, Affine {
/**
* Applies this spell to the supplied caster.
+ * @param caster The caster to apply the spell to
*/
default boolean apply(Caster> caster) {
caster.getSpellSlot().put(this);
@@ -76,7 +79,7 @@ public interface Spell extends NbtSerialisable, Affine {
/**
* Gets the default form of this spell used to apply to a caster.
- * @param caster
+ * @param caster The caster currently fueling this spell
*/
default Spell prepareForCast(Caster> caster, CastingMethod method) {
return this;
@@ -89,6 +92,12 @@ public interface Spell extends NbtSerialisable, Affine {
*/
boolean tick(Caster> caster, Situation situation);
+ /**
+ * Called on spells that are actively dying to update any post-death animations before removal.
+ * @param caster The caster currently fueling this spell
+ */
+ void tickDying(Caster> caster);
+
/**
* Marks this effect as dirty.
*/
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java
index 9e19b8ae..d81e4f39 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java
@@ -29,7 +29,7 @@ public interface TimedSpell extends Spell {
}
public float getPercentTimeRemaining(float tickDelta) {
- return MathHelper.lerp(tickDelta, prevDuration, duration) / maxDuration;
+ return MathHelper.lerp(tickDelta, prevDuration, duration) / (float)maxDuration;
}
public int getTicksRemaining() {
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/AltarRecipeMatch.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/AltarRecipeMatch.java
new file mode 100644
index 00000000..820f7667
--- /dev/null
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/crafting/AltarRecipeMatch.java
@@ -0,0 +1,42 @@
+package com.minelittlepony.unicopia.ability.magic.spell.crafting;
+
+import java.util.List;
+
+import org.jetbrains.annotations.Nullable;
+
+import com.minelittlepony.unicopia.item.UItems;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.ItemEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+
+public record AltarRecipeMatch(
+ ItemEntity target,
+ List ingredients,
+ ItemStack result
+ ) {
+
+ @Nullable
+ public static AltarRecipeMatch of(List inputs) {
+ ItemEntity clock = inputs.stream().filter(item -> item.getStack().isOf(Items.CLOCK)).findFirst().orElse(null);
+
+ if (clock != null) {
+ return new AltarRecipeMatch(clock, List.of(), UItems.SPECTRAL_CLOCK.getDefaultStack());
+ }
+
+ return null;
+ }
+
+ public boolean isRemoved() {
+ return target.isRemoved() || ingredients.stream().anyMatch(ItemEntity::isRemoved);
+ }
+
+ public void craft() {
+ ItemStack clockStack = result.copyWithCount(target.getStack().getCount());
+ clockStack.setNbt(target.getStack().getNbt());
+ target.setStack(clockStack);
+ target.setInvulnerable(true);
+ ingredients.forEach(Entity::discard);
+ }
+}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java
index a8c1b706..8b64d9c3 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java
@@ -12,6 +12,7 @@ import net.minecraft.nbt.NbtCompound;
public abstract class AbstractSpell implements Spell {
private boolean dead;
+ private boolean dying;
private boolean dirty;
private boolean hidden;
private boolean destroyed;
@@ -45,7 +46,7 @@ public abstract class AbstractSpell implements Spell {
@Override
public final void setDead() {
- dead = true;
+ dying = true;
setDirty();
}
@@ -54,6 +55,11 @@ public abstract class AbstractSpell implements Spell {
return dead;
}
+ @Override
+ public final boolean isDying() {
+ return dying;
+ }
+
@Override
public final boolean isDirty() {
return dirty;
@@ -82,6 +88,11 @@ public abstract class AbstractSpell implements Spell {
protected void onDestroyed(Caster> caster) {
}
+ @Override
+ public void tickDying(Caster> caster) {
+ dead = true;
+ }
+
@Override
public final void destroy(Caster> caster) {
if (destroyed) {
@@ -94,6 +105,7 @@ public abstract class AbstractSpell implements Spell {
@Override
public void toNBT(NbtCompound compound) {
+ compound.putBoolean("dying", dying);
compound.putBoolean("dead", dead);
compound.putBoolean("hidden", hidden);
compound.putUuid("uuid", uuid);
@@ -106,6 +118,7 @@ public abstract class AbstractSpell implements Spell {
if (compound.contains("uuid")) {
uuid = compound.getUuid("uuid");
}
+ dying = compound.getBoolean("dying");
dead = compound.getBoolean("dead");
hidden = compound.getBoolean("hidden");
if (compound.contains("traits")) {
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java
index 75615785..920c5bd5 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java
@@ -9,6 +9,7 @@ import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
+import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity;
@@ -42,6 +43,8 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO);
}
});
+ } else {
+ Ether.get(source.asWorld()).getOrCreate(this, source);
}
source.findAllSpellsInRange(radius, e -> isValidTarget(source, e)).filter(caster -> !caster.hasCommonOwner(source)).forEach(caster -> {
@@ -51,6 +54,11 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
return !isDead();
}
+ @Override
+ protected void onDestroyed(Caster> caster) {
+ Ether.get(caster.asWorld()).remove(this, caster);
+ }
+
/**
* Calculates the maximum radius of the shield. aka The area of effect.
*/
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java
index 669411a1..9af5cddb 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java
@@ -7,7 +7,6 @@ import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.particle.FollowingParticleEffect;
-import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
@@ -16,6 +15,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.nbt.NbtCompound;
+import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
@@ -44,9 +44,10 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
if (timer.getTicksRemaining() <= 0) {
return false;
}
+
+ setDirty();
}
- setDirty();
target.getOrEmpty(caster.asWorld())
.filter(entity -> entity.distanceTo(caster.asEntity()) > getDrawDropOffRange(caster))
.ifPresent(entity -> {
@@ -59,12 +60,13 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
@Override
public void generateParticles(Caster> source) {
- double range = getDrawDropOffRange(source) + 10;
+ double range = getDrawDropOffRange(source);
+ Vec3d origin = getOrigin(source);
- source.spawnParticles(getOrigin(source), new Sphere(false, range), 7, p -> {
+ source.spawnParticles(origin, new Sphere(false, range), 7, p -> {
source.addParticle(
- new FollowingParticleEffect(UParticles.HEALTH_DRAIN, source.asEntity(), 0.4F)
- .withChild(new MagicParticleEffect(getType().getColor())),
+ new FollowingParticleEffect(UParticles.HEALTH_DRAIN, origin, 0.4F)
+ .withChild(ParticleTypes.AMBIENT_ENTITY_EFFECT),
p,
Vec3d.ZERO
);
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java
index c2b837b0..63afe303 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java
@@ -11,12 +11,10 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.*;
import com.minelittlepony.unicopia.entity.mob.UEntityAttributes;
import com.minelittlepony.unicopia.entity.player.Pony;
-import com.minelittlepony.unicopia.particle.ParticleHandle;
-import com.minelittlepony.unicopia.particle.SphereParticleEffect;
import com.minelittlepony.unicopia.particle.UParticles;
-import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
+import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
@@ -48,11 +46,11 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
.with(Trait.POWER, 1)
.build();
- protected final ParticleHandle particlEffect = new ParticleHandle();
-
private final Timer timer;
private int struggles;
+
+ private float prevRadius;
private float radius;
protected BubbleSpell(CustomisedSpellType> type) {
@@ -66,6 +64,10 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
return timer;
}
+ public float getRadius(float tickDelta) {
+ return MathHelper.lerp(tickDelta, prevRadius, radius);
+ }
+
@Override
public boolean apply(Caster> source) {
@@ -95,14 +97,19 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
public boolean tick(Caster> source, Situation situation) {
if (situation == Situation.PROJECTILE) {
-
- source.spawnParticles(ParticleTypes.BUBBLE, 2);
+ source.spawnParticles(UParticles.BUBBLE, 2);
return true;
}
timer.tick();
- if (timer.getTicksRemaining() <= 0) {
+ boolean done = timer.getTicksRemaining() <= 0;
+
+ source.spawnParticles(source.getOriginVector().add(0, 1, 0), new Sphere(true, radius * (done ? 0.25F : 0.5F)), done ? 13 : 1, pos -> {
+ source.addParticle(done ? ParticleTypes.BUBBLE_POP : UParticles.BUBBLE, pos, Vec3d.ZERO);
+ });
+
+ if (done) {
return false;
}
@@ -116,7 +123,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
source.asEntity().fallDistance = 0;
- Vec3d origin = source.getOriginVector();
+ prevRadius = radius;
if (source instanceof Pony pony && pony.sneakingChanged() && pony.asEntity().isSneaking()) {
setDirty();
@@ -128,18 +135,11 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
}
}
- particlEffect.update(getUuid(), source, spawner -> {
- spawner.addParticle(new SphereParticleEffect(UParticles.SPHERE, 0xFFFFFF, 0.3F, 0, new Vec3d(0, radius / 2F, 0)), origin, Vec3d.ZERO);
- }).ifPresent(p -> {
- p.setAttribute(Attachment.ATTR_RADIUS, radius);
- });
-
return !isDead();
}
@Override
protected void onDestroyed(Caster> source) {
- particlEffect.destroy();
if (source.asEntity() instanceof LivingEntity l) {
MODIFIERS.forEach((attribute, modifier) -> {
if (l.getAttributes().hasAttribute(attribute)) {
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java
index bda10118..02eb96d3 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java
@@ -10,11 +10,11 @@ import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
+import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
-import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
+import com.minelittlepony.unicopia.particle.FollowingParticleEffect;
import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils;
-import com.minelittlepony.unicopia.particle.SphereParticleEffect;
import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
@@ -37,10 +37,6 @@ import net.minecraft.world.World.ExplosionSourceType;
/**
* More powerful version of the vortex spell which creates a black hole.
- *
- * TODO: Possible uses
- * - Garbage bin
- * - Link with a teleportation spell to create a wormhole
*/
public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelegate.BlockHitListener {
public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder()
@@ -52,7 +48,6 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
private static final Vec3d SPHERE_OFFSET = new Vec3d(0, 2, 0);
- private int age = 0;
private float accumulatedMass = 0;
protected DarkVortexSpell(CustomisedSpellType> type) {
@@ -84,17 +79,10 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
return true;
}
- age++;
- setDirty();
-
- if (age % 20 == 0) {
+ if (source.asEntity().age % 20 == 0) {
source.asWorld().playSound(null, source.getOrigin(), USounds.AMBIENT_DARK_VORTEX_ADDITIONS, SoundCategory.AMBIENT, 1, 1);
}
- if (!source.subtractEnergyCost(-accumulatedMass)) {
- setDead();
- }
-
if (!source.isClient() && source.asWorld().random.nextInt(300) == 0) {
ParticleUtils.spawnParticle(source.asWorld(), LightningBoltParticleEffect.DEFAULT, getOrigin(source), Vec3d.ZERO);
}
@@ -102,6 +90,13 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
return super.tick(source, situation);
}
+ @Override
+ protected void consumeManage(Caster> source, long costMultiplier, float knowledge) {
+ if (!source.subtractEnergyCost(-accumulatedMass)) {
+ setDead();
+ }
+ }
+
@Override
public boolean isFriendlyTogether(Affine other) {
return accumulatedMass < 4;
@@ -116,21 +111,20 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
public void generateParticles(Caster> source) {
super.generateParticles(source);
- float radius = (float)getEventHorizonRadius();
-
- particlEffect.update(getUuid(), source, spawner -> {
- spawner.addParticle(new SphereParticleEffect(UParticles.SPHERE, 0x000000, 0.99F, radius, SPHERE_OFFSET), source.getOriginVector(), Vec3d.ZERO);
- }).ifPresent(p -> {
- p.setAttribute(Attachment.ATTR_RADIUS, radius);
- p.setAttribute(Attachment.ATTR_OPACITY, 2F);
- });
- particlEffect.update(getUuid(), "_ring", source, spawner -> {
- spawner.addParticle(new SphereParticleEffect(UParticles.DISK, 0xAAAAAA, 0.4F, radius + 1, SPHERE_OFFSET), getOrigin(source), Vec3d.ZERO);
- }).ifPresent(p -> {
- p.setAttribute(Attachment.ATTR_RADIUS, radius * 0F);
- });
-
- source.spawnParticles(ParticleTypes.SMOKE, 3);
+ if (getEventHorizonRadius() > 0.3) {
+ double range = getDrawDropOffRange(source);
+ Vec3d origin = getOrigin(source);
+ source.spawnParticles(origin, new Sphere(false, range), 1, p -> {
+ if (!source.asWorld().isAir(BlockPos.ofFloored(p))) {
+ source.addParticle(
+ new FollowingParticleEffect(UParticles.HEALTH_DRAIN, origin, 0.4F)
+ .withChild(ParticleTypes.CAMPFIRE_SIGNAL_SMOKE),
+ p,
+ Vec3d.ZERO
+ );
+ }
+ });
+ }
}
@Override
@@ -162,7 +156,6 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
applyRadialEffect(source, e, e.getPos().distanceTo(origin), radius);
});
}
- setDirty();
});
}
}
@@ -180,8 +173,8 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
// 2. max force (at dist 0) is taken from accumulated mass
// 3. force reaches 0 at distance of drawDropOffRange
- private double getEventHorizonRadius() {
- return Math.sqrt(Math.max(0.001, getMass() - 12));
+ public double getEventHorizonRadius() {
+ return Math.sqrt(Math.max(0.001, getMass() / 3F));
}
private double getAttractiveForce(Caster> source, Entity target) {
@@ -189,8 +182,7 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
}
private double getMass() {
- float pulse = (float)Math.sin(age * 8) / 1F;
- return 10 + Math.min(15, Math.min(0.5F + pulse, (float)Math.exp(age) / 8F - 90) + accumulatedMass / 10F) + pulse;
+ return Math.min(15, 0.1F + accumulatedMass / 10F);
}
@Override
@@ -202,6 +194,11 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
if (distance <= getEventHorizonRadius() + 0.5) {
target.setVelocity(target.getVelocity().multiply(distance / (2 * radius)));
+ if (distance < 1) {
+ target.setVelocity(target.getVelocity().multiply(distance));
+
+ }
+ Living.updateVelocity(target);
@Nullable
Entity master = source.getMaster();
@@ -221,13 +218,19 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
double massOfTarget = AttractionUtils.getMass(target);
- accumulatedMass += massOfTarget;
- setDirty();
+ if (!source.isClient() && massOfTarget != 0) {
+ accumulatedMass += massOfTarget;
+ setDirty();
+ }
+
target.damage(source.damageOf(UDamageTypes.GAVITY_WELL_RECOIL, source), Integer.MAX_VALUE);
if (!(target instanceof PlayerEntity)) {
target.discard();
source.asWorld().playSound(null, source.getOrigin(), USounds.ENCHANTMENT_CONSUMPTION_CONSUME, SoundCategory.AMBIENT, 2, 0.02F);
}
+ if (target.isAlive()) {
+ target.damage(source.asEntity().getDamageSources().outOfWorld(), Integer.MAX_VALUE);
+ }
source.subtractEnergyCost(-massOfTarget * 10);
source.asWorld().playSound(null, source.getOrigin(), USounds.AMBIENT_DARK_VORTEX_MOOD, SoundCategory.AMBIENT, 2, 0.02F);
@@ -243,14 +246,12 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
- compound.putInt("age", age);
compound.putFloat("accumulatedMass", accumulatedMass);
}
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
- age = compound.getInt("age");
accumulatedMass = compound.getFloat("accumulatedMass");
}
}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java
index 4806d10d..9affae3e 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java
@@ -12,7 +12,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.util.math.Vec3d;
/**
- * An area-effect spell that disperses illussions.
+ * An area-effect spell that disperses illusions.
*/
public class DisperseIllusionSpell extends AbstractAreaEffectSpell {
protected DisperseIllusionSpell(CustomisedSpellType> type) {
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java
index 14d3abf8..a58b77de 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java
@@ -6,8 +6,6 @@ import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
-import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
-import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
@@ -16,7 +14,7 @@ import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.math.Vec3d;
-public class DisplacementSpell extends AbstractSpell implements HomingSpell, PlaceableSpell.PlacementDelegate, ProjectileDelegate.EntityHitListener {
+public class DisplacementSpell extends AbstractSpell implements HomingSpell, ProjectileDelegate.EntityHitListener {
private final EntityReference target = new EntityReference<>();
@@ -67,19 +65,6 @@ public class DisplacementSpell extends AbstractSpell implements HomingSpell, Pla
originator.subtractEnergyCost(destinationPos.distanceTo(sourcePos) / 20F);
}
- @Override
- public void onPlaced(Caster> source, PlaceableSpell parent, CastSpellEntity entity) {
-
- }
-
- @Override
- public void updatePlacement(Caster> caster, PlaceableSpell parent) {
- parent.getParticleEffectAttachment(caster).ifPresent(attachment -> {
- float r = 3 - (1 - ((ticks + 10) / 20F)) * 3;
- attachment.setAttribute(Attachment.ATTR_RADIUS, r);
- });
- }
-
private void teleport(Caster> source, Entity entity, Vec3d pos, Vec3d vel) {
entity.teleport(pos.x, pos.y, pos.z);
entity.setVelocity(vel);
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java
index c2324a5f..a76ef43b 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java
@@ -48,7 +48,7 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
if (caster instanceof MagicProjectileEntity && getTraits().get(Trait.FOCUS) >= 50) {
caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49,
- EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.notOwnerOrFriend(this, caster))
+ EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster))
).findFirst().ifPresent(target -> {
((MagicProjectileEntity)caster).setHomingTarget(target);
});
@@ -60,7 +60,7 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
if (getTraits().get(Trait.FOCUS) >= 50 && target.getOrEmpty(caster.asWorld()).isEmpty()) {
target.set(caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49,
- EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.notOwnerOrFriend(this, caster))
+ EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster))
).findFirst().orElse(null));
}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java
index 5ff51120..d88d9dd0 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java
@@ -5,14 +5,16 @@ import java.util.Set;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster;
-import com.minelittlepony.unicopia.ability.magic.CasterView;
+import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
+import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
+import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.util.shape.*;
@@ -21,8 +23,10 @@ import net.minecraft.fluid.*;
import net.minecraft.nbt.*;
import net.minecraft.state.property.Properties;
import net.minecraft.registry.tag.TagKey;
+import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class HydrophobicSpell extends AbstractSpell {
@@ -41,16 +45,15 @@ public class HydrophobicSpell extends AbstractSpell {
}
@Override
- public boolean apply(Caster> source) {
- if (getTraits().get(Trait.GENEROSITY) > 0) {
- return toPlaceable().apply(source);
+ public Spell prepareForCast(Caster> caster, CastingMethod method) {
+ if ((method == CastingMethod.DIRECT || method == CastingMethod.STAFF) && getTraits().get(Trait.GENEROSITY) > 0) {
+ return toPlaceable();
}
- return super.apply(source);
+ return this;
}
@Override
public boolean tick(Caster> source, Situation situation) {
-
if (!source.isClient()) {
World world = source.asWorld();
@@ -86,7 +89,11 @@ public class HydrophobicSpell extends AbstractSpell {
setDead();
}
- source.spawnParticles(new Sphere(true, getRange(source)), 10, pos -> {
+ double range = getRange(source);
+ var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
+ entry.radius = (float)range;
+
+ source.spawnParticles(new Sphere(true, range), 10, pos -> {
BlockPos bp = BlockPos.ofFloored(pos);
if (source.asWorld().getFluidState(bp.up()).isIn(affectedFluid)) {
source.addParticle(UParticles.RAIN_DROPS, pos, Vec3d.ZERO);
@@ -107,6 +114,7 @@ public class HydrophobicSpell extends AbstractSpell {
@Override
protected void onDestroyed(Caster> caster) {
+ Ether.get(caster.asWorld()).remove(this, caster);
storedFluidPositions.removeIf(entry -> {
entry.restore(caster.asWorld());
return true;
@@ -162,13 +170,20 @@ public class HydrophobicSpell extends AbstractSpell {
}
}
- public boolean blocksFlow(Caster> caster, BlockPos pos, FluidState fluid) {
- return fluid.isIn(affectedFluid) && pos.isWithinDistance(caster.getOrigin(), getRange(caster) + 1);
+ public boolean blocksFlow(Ether.Entry> entry, Vec3d center, BlockPos pos, FluidState fluid) {
+ return fluid.isIn(affectedFluid) && pos.isWithinDistance(center, (double)entry.radius + 1);
}
- public static boolean blocksFluidFlow(CasterView world, BlockPos pos, FluidState state) {
- return world.findAllSpellsInRange(pos, 500, SpellType.HYDROPHOBIC).anyMatch(pair -> {
- return pair.getValue().blocksFlow(pair.getKey(), pos, state);
- });
+ public static boolean blocksFluidFlow(BlockView world, BlockPos pos, FluidState state) {
+ if (world instanceof ServerWorld sw) {
+ return Ether.get(sw).anyMatch(SpellType.HYDROPHOBIC, entry -> {
+ var spell = entry.getSpell();
+ var target = entry.entity.getTarget().orElse(null);
+ return spell != null && target != null && spell.blocksFlow(entry, target.pos(), pos, state);
+ });
+ }
+
+ return false;
+
}
}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java
index 680a0ff1..82a66ad9 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java
@@ -1,6 +1,9 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.Optional;
+import java.util.UUID;
+
+import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia;
@@ -9,9 +12,9 @@ import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.EntityReference;
+import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
import com.minelittlepony.unicopia.particle.*;
-import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.shape.*;
@@ -20,8 +23,10 @@ import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.nbt.NbtCompound;
-import net.minecraft.particle.ParticleEffect;
+import net.minecraft.network.packet.s2c.play.PositionFlag;
import net.minecraft.particle.ParticleTypes;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.WorldEvents;
@@ -34,12 +39,14 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
.build();
private static final Shape PARTICLE_AREA = new Sphere(true, 2, 1, 1, 0);
+ @Nullable
+ private UUID targetPortalId;
+ private float targetPortalPitch;
+ private float targetPortalYaw;
private final EntityReference teleportationTarget = new EntityReference<>();
private boolean publishedPosition;
- private final ParticleHandle particleEffect = new ParticleHandle();
-
private float pitch;
private float yaw;
@@ -49,6 +56,39 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
super(type);
}
+ public boolean isLinked() {
+ return teleportationTarget.getTarget().isPresent();
+ }
+
+ public Optional> getTarget() {
+ return teleportationTarget.getTarget();
+ }
+
+ public float getPitch() {
+ return pitch;
+ }
+
+ public float getYaw() {
+ return yaw;
+ }
+
+ public float getTargetPitch() {
+ return targetPortalPitch;
+ }
+
+ public float getTargetYaw() {
+ return targetPortalYaw;
+ }
+
+ public float getYawDifference() {
+ return MathHelper.wrapDegrees(180 + targetPortalYaw - yaw);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Optional> getTarget(Caster> source) {
+ return getTarget().map(target -> Ether.get(source.asWorld()).get((SpellType)getType(), target, targetPortalId));
+ }
+
@Override
public boolean apply(Caster> caster) {
setOrientation(caster.asEntity().getPitch(), caster.asEntity().getYaw());
@@ -60,29 +100,12 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
if (situation == Situation.GROUND) {
if (source.isClient()) {
- Vec3d origin = source.getOriginVector();
-
- ParticleEffect effect = teleportationTarget.getTarget()
- .map(target -> {
- getType();
- return (ParticleEffect)new FollowingParticleEffect(UParticles.HEALTH_DRAIN, target.pos(), 0.2F).withChild(ParticleTypes.ELECTRIC_SPARK);
- })
- .orElse(ParticleTypes.ELECTRIC_SPARK);
-
- source.spawnParticles(origin, particleArea, 5, pos -> {
- source.addParticle(effect, pos, Vec3d.ZERO);
- });
-
- teleportationTarget.getTarget().ifPresentOrElse(target -> {
- particleEffect.update(getUuid(), source, spawner -> {
- spawner.addParticle(new SphereParticleEffect(UParticles.DISK, getType().getColor(), 0.8F, 1.8F, new Vec3d(-pitch + 90, -yaw, 0)), source.getOriginVector(), Vec3d.ZERO);
- });
- }, () -> {
- particleEffect.destroy();
+ source.spawnParticles(particleArea, 5, pos -> {
+ source.addParticle(ParticleTypes.ELECTRIC_SPARK, pos, Vec3d.ZERO);
});
} else {
- teleportationTarget.getTarget().ifPresent(target -> {
- if (Ether.get(source.asWorld()).getEntry(getType(), target.uuid()).isEmpty()) {
+ getTarget().ifPresent(target -> {
+ if (Ether.get(source.asWorld()).get(getType(), target, targetPortalId) == null) {
Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + target.uuid());
teleportationTarget.set(null);
setDirty();
@@ -96,37 +119,56 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
);
}
- if (!publishedPosition) {
- publishedPosition = true;
- Ether.Entry entry = Ether.get(source.asWorld()).put(getType(), source);
- entry.pitch = pitch;
- entry.yaw = yaw;
- }
+ var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
+ entry.pitch = pitch;
+ entry.yaw = yaw;
+ Ether.get(source.asWorld()).markDirty();
}
return !isDead();
}
- private void tickWithTargetLink(Caster> source, Ether.Entry destination) {
+ private void tickWithTargetLink(Caster> source, Ether.Entry> destination) {
+
+ if (!MathHelper.approximatelyEquals(targetPortalPitch, destination.pitch)) {
+ targetPortalPitch = destination.pitch;
+ setDirty();
+ }
+ if (!MathHelper.approximatelyEquals(targetPortalYaw, destination.yaw)) {
+ targetPortalYaw = destination.yaw;
+ setDirty();
+ }
destination.entity.getTarget().ifPresent(target -> {
source.findAllEntitiesInRange(1).forEach(entity -> {
- if (!entity.hasPortalCooldown() && entity.timeUntilRegen <= 0) {
+ if (!entity.hasPortalCooldown()) {
+
+ float approachYaw = Math.abs(MathHelper.wrapDegrees(entity.getYaw() - this.yaw));
+ if (approachYaw > 80) {
+ return;
+ }
+
Vec3d offset = entity.getPos().subtract(source.getOriginVector());
- float yawDifference = pitch < 15 ? (180 - yaw + destination.yaw) : 0;
- Vec3d dest = target.pos().add(offset.rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE)).add(0, 0.05, 0);
+ float yawDifference = pitch < 15 ? getYawDifference() : 0;
+ Vec3d dest = target.pos().add(offset.rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE)).add(0, 0.1, 0);
+
+ if (entity.getWorld().isTopSolid(BlockPos.ofFloored(dest).up(), entity)) {
+ dest = dest.add(0, 1, 0);
+ }
entity.resetPortalCooldown();
- entity.timeUntilRegen = 100;
- entity.setYaw(entity.getYaw() + yawDifference);
+ float yaw = MathHelper.wrapDegrees(entity.getYaw() + yawDifference);
+
entity.setVelocity(entity.getVelocity().rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE));
entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1);
- entity.teleport(dest.x, dest.y, dest.z);
+ entity.teleport((ServerWorld)entity.getWorld(), dest.x, dest.y, dest.z, PositionFlag.VALUES, yaw, entity.getPitch());
entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1);
setDirty();
+ Living.updateVelocity(entity);
+
if (!source.subtractEnergyCost(Math.sqrt(entity.getPos().subtract(dest).length()))) {
setDead();
}
@@ -142,20 +184,15 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
return;
}
- Ether ether = Ether.get(source.asWorld());
- ether.getEntries(getType())
- .stream()
- .filter(entry -> entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.isSet())
- .findAny()
- .ifPresent(entry -> {
+ Ether.get(source.asWorld()).anyMatch(getType(), entry -> {
+ if (entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.isSet()) {
entry.setTaken(true);
teleportationTarget.copyFrom(entry.entity);
+ targetPortalId = entry.getSpellId();
setDirty();
- });
- }
-
- private Optional getTarget(Caster> source) {
- return teleportationTarget.getTarget().flatMap(target -> Ether.get(source.asWorld()).getEntry(getType(), target.uuid()));
+ }
+ return false;
+ });
}
@Override
@@ -174,41 +211,40 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
LivingEntity caster = source.getMaster();
Vec3d targetPos = caster.getRotationVector().multiply(3).add(caster.getEyePos());
parent.setOrientation(pitch, yaw);
- entity.setPos(targetPos.x, caster.getY() + 1.5, targetPos.z);
- }
-
- @Override
- public void updatePlacement(Caster> source, PlaceableSpell parent) {
- parent.getParticleEffectAttachment(source).ifPresent(attachment -> {
- attachment.setAttribute(Attachment.ATTR_RADIUS, 2);
- attachment.setAttribute(Attachment.ATTR_OPACITY, 0.92F);
- });
+ entity.setPos(targetPos.x, Math.abs(pitch) > 15 ? targetPos.y : caster.getPos().y, targetPos.z);
}
@Override
protected void onDestroyed(Caster> caster) {
- particleEffect.destroy();
Ether ether = Ether.get(caster.asWorld());
- ether.remove(getType(), caster.asEntity().getUuid());
+ ether.remove(getType(), caster);
getTarget(caster).ifPresent(e -> e.setTaken(false));
}
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
+ if (targetPortalId != null) {
+ compound.putUuid("targetPortalId", targetPortalId);
+ }
compound.putBoolean("publishedPosition", publishedPosition);
compound.put("teleportationTarget", teleportationTarget.toNBT());
compound.putFloat("pitch", pitch);
compound.putFloat("yaw", yaw);
+ compound.putFloat("targetPortalPitch", targetPortalPitch);
+ compound.putFloat("targetPortalYaw", targetPortalYaw);
}
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
+ targetPortalId = compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : null;
publishedPosition = compound.getBoolean("publishedPosition");
teleportationTarget.fromNBT(compound.getCompound("teleportationTarget"));
pitch = compound.getFloat("pitch");
yaw = compound.getFloat("yaw");
+ targetPortalPitch = compound.getFloat("targetPortalPitch");
+ targetPortalYaw = compound.getFloat("targetPortalYaw");
particleArea = PARTICLE_AREA.rotate(
pitch * MathHelper.RADIANS_PER_DEGREE,
(180 - yaw) * MathHelper.RADIANS_PER_DEGREE
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java
index 61cc6e79..04831ef6 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java
@@ -1,5 +1,7 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect;
+import java.util.Optional;
+
import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia;
@@ -10,11 +12,9 @@ import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.player.Pony;
+import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
-import com.minelittlepony.unicopia.particle.ParticleHandle;
-import com.minelittlepony.unicopia.particle.SphereParticleEffect;
-import com.minelittlepony.unicopia.particle.UParticles;
-import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
+import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.projectile.ProjectileUtil;
import com.minelittlepony.unicopia.util.shape.Sphere;
@@ -30,6 +30,7 @@ import net.minecraft.entity.passive.PassiveEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.vehicle.AbstractMinecartEntity;
import net.minecraft.entity.vehicle.BoatEntity;
+import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
public class ShieldSpell extends AbstractSpell {
@@ -40,9 +41,16 @@ public class ShieldSpell extends AbstractSpell {
.with(Trait.AIR, 9)
.build();
- protected final ParticleHandle particlEffect = new ParticleHandle();
+ private final TargetSelecter targetSelecter = new TargetSelecter(this).setFilter(this::isValidTarget);
- private final TargetSelecter targetSelecter = new TargetSelecter(this);
+ private float prevRadius;
+ private float radius;
+
+ private float rangeMultiplier;
+ private float targetRangeMultiplier;
+
+ private int prevTicksDying;
+ private int ticksDying;
protected ShieldSpell(CustomisedSpellType> type) {
super(type);
@@ -53,33 +61,27 @@ public class ShieldSpell extends AbstractSpell {
return method == CastingMethod.STAFF || getTraits().get(Trait.GENEROSITY) > 0 ? toPlaceable() : this;
}
- @Override
- protected void onDestroyed(Caster> caster) {
- particlEffect.destroy();
- }
-
@Override
public Affinity getAffinity() {
return getTraits().get(Trait.DARKNESS) > 0 ? Affinity.BAD : Affinity.GOOD;
}
protected void generateParticles(Caster> source) {
- float radius = (float)getDrawDropOffRange(source);
Vec3d origin = getOrigin(source);
source.spawnParticles(origin, new Sphere(true, radius), (int)(radius * 6), pos -> {
source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO);
- });
- particlEffect.update(getUuid(), source, spawner -> {
- spawner.addParticle(new SphereParticleEffect(UParticles.SPHERE, getType().getColor(), 0.3F, radius), origin, Vec3d.ZERO);
- }).ifPresent(p -> {
- p.setAttribute(Attachment.ATTR_RADIUS, radius);
+ if (source.asWorld().random.nextInt(10) == 0 && source.asWorld().random.nextFloat() < source.getCorruption().getScaled(1)) {
+ ParticleUtils.spawnParticle(source.asWorld(), new LightningBoltParticleEffect(true, 3, 2, 0.1F, Optional.empty()), pos, Vec3d.ZERO);
+ }
});
}
@Override
public boolean tick(Caster> source, Situation situation) {
+ prevRadius = radius;
+ radius = (float)getDrawDropOffRange(source);
if (source.isClient()) {
generateParticles(source);
@@ -97,27 +99,53 @@ public class ShieldSpell extends AbstractSpell {
long costMultiplier = applyEntities(source);
if (costMultiplier > 0) {
- double cost = 2 - source.getLevel().getScaled(2);
-
- cost *= costMultiplier / ((1 + source.getLevel().get()) * 3F);
- cost /= knowledge;
- cost += getDrawDropOffRange(source) / 10F;
-
- if (!source.subtractEnergyCost(cost)) {
- setDead();
- }
+ consumeManage(source, costMultiplier, knowledge);
}
return !isDead();
}
+ @Override
+ public void tickDying(Caster> caster) {
+ prevTicksDying = ticksDying;
+ if (ticksDying++ > 25) {
+ super.tickDying(caster);
+ }
+ }
+
+ protected void consumeManage(Caster> source, long costMultiplier, float knowledge) {
+ double cost = 2 - source.getLevel().getScaled(2);
+
+ cost *= costMultiplier / ((1 + source.getLevel().get()) * 3F);
+ cost /= knowledge;
+ cost += radius / 10F;
+
+ if (!source.subtractEnergyCost(cost)) {
+ setDead();
+ }
+ }
+
+ public float getRadius(float tickDelta) {
+ float base = MathHelper.lerp(tickDelta, prevRadius, radius);
+ float scale = MathHelper.clamp(MathHelper.lerp(tickDelta, prevTicksDying, ticksDying), 0, 1);
+ return base * scale;
+ }
+
/**
* Calculates the maximum radius of the shield. aka The area of effect.
*/
public double getDrawDropOffRange(Caster> source) {
- float multiplier = source instanceof Pony pony && pony.asEntity().isSneaking() ? 1 : 2;
+ targetRangeMultiplier = source instanceof Pony pony && pony.asEntity().isSneaking() ? 1 : 2;
+ if (rangeMultiplier < targetRangeMultiplier - 0.1F) {
+ rangeMultiplier += 0.1F;
+ } else if (rangeMultiplier > targetRangeMultiplier + 0.1) {
+ rangeMultiplier -= 0.1F;
+ } else {
+ rangeMultiplier = targetRangeMultiplier;
+ }
+
float min = (source instanceof Pony ? 4 : 6) + getTraits().get(Trait.POWER);
- double range = (min + (source.getLevel().getScaled(source instanceof Pony ? 4 : 40) * (source instanceof Pony ? 2 : 10))) / multiplier;
+ double range = (min + (source.getLevel().getScaled(source instanceof Pony ? 4 : 40) * (source instanceof Pony ? 2 : 10))) / rangeMultiplier;
return range;
}
@@ -146,11 +174,8 @@ public class ShieldSpell extends AbstractSpell {
}
protected long applyEntities(Caster> source) {
- double radius = getDrawDropOffRange(source);
-
Vec3d origin = getOrigin(source);
-
- targetSelecter.getEntities(source, radius, this::isValidTarget).forEach(i -> {
+ targetSelecter.getEntities(source, radius).forEach(i -> {
try {
applyRadialEffect(source, i, i.getPos().distanceTo(origin), radius);
} catch (Throwable e) {
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java
index b2482157..87119eab 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java
@@ -10,28 +10,32 @@ import java.util.stream.Stream;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.Caster;
-import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
-import com.minelittlepony.unicopia.entity.player.Pony;
-import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
import net.minecraft.entity.Entity;
+import net.minecraft.predicate.entity.EntityPredicates;
public class TargetSelecter {
-
private final Map targets = new TreeMap<>();
private final Spell spell;
+ private BiPredicate, Entity> filter = (a, b) -> true;
+
public TargetSelecter(Spell spell) {
this.spell = spell;
}
- public Stream getEntities(Caster> source, double radius, BiPredicate, Entity> filter) {
+ public TargetSelecter setFilter(BiPredicate, Entity> filter) {
+ this.filter = filter;
+ return this;
+ }
+
+ public Stream getEntities(Caster> source, double radius) {
targets.values().removeIf(Target::tick);
return source.findAllEntitiesInRange(radius)
- .filter(entity -> entity.isAlive() && !entity.isRemoved() && notOwnerOrFriend(spell, source, entity) && !SpellPredicate.IS_SHIELD_LIKE.isOn(entity))
+ .filter(EntityPredicates.VALID_ENTITY)
.filter(EquinePredicates.EXCEPT_MAGIC_IMMUNE)
- .filter(e -> filter.test(source, e))
+ .filter(entity -> entity != source.asEntity() && validTarget(spell, source, entity) && filter.test(source, entity))
.map(i -> {
targets.computeIfAbsent(i.getUuid(), Target::new);
return i;
@@ -42,30 +46,19 @@ public class TargetSelecter {
return targets.values().stream().filter(Target::canHurt).count();
}
- public static Predicate notOwnerOrFriend(Affine affine, Caster> source) {
- return target -> notOwnerOrFriend(affine, source, target);
+ public static Predicate validTarget(Affine affine, Caster> source) {
+ return target -> validTarget(affine, source, target);
}
- public static Predicate isOwnerOrFriend(Affine affine, Caster> source) {
- return target -> isOwnerOrFriend(affine, source, target);
- }
-
- public static boolean notOwnerOrFriend(Affine affine, Caster> source, Entity target) {
+ public static boolean validTarget(Affine affine, Caster> source, Entity target) {
return !isOwnerOrFriend(affine, source, target);
}
- public static boolean isOwnerOrFriend(Affine affine, Caster> source, Entity target) {
- Entity owner = source.getMaster();
-
- if (affine.isEnemy(source)) {
- return FriendshipBraceletItem.isComrade(source, target);
- }
-
- return FriendshipBraceletItem.isComrade(source, target)
- || (owner != null && (Pony.equal(target, owner) || owner.isConnectedThroughVehicle(target)));
+ public static boolean isOwnerOrFriend(Affine affine, Caster> source, Entity target) {
+ return affine.applyInversion(source, source.isOwnerOrFriend(target));
}
- static final class Target {
+ private static final class Target {
private int cooldown = 20;
Target(UUID id) { }
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/ItemWithTraits.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/ItemWithTraits.java
new file mode 100644
index 00000000..5b6cd107
--- /dev/null
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/ItemWithTraits.java
@@ -0,0 +1,5 @@
+package com.minelittlepony.unicopia.ability.magic.spell.trait;
+
+public interface ItemWithTraits {
+ SpellTraits getDefaultTraits();
+}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java
index 58c24bc4..17cd9568 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java
@@ -29,6 +29,7 @@ import net.fabricmc.api.Environment;
import net.minecraft.block.Block;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.Item;
+import net.minecraft.item.SpawnEggItem;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
@@ -41,6 +42,7 @@ import net.minecraft.registry.Registries;
public final class SpellTraits implements Iterable> {
public static final SpellTraits EMPTY = new SpellTraits(Map.of());
+ private static final SpellTraits SPAWN_EGG_TRAITS = new SpellTraits(Map.of(Trait.LIFE, 20F));
private static Map REGISTRY = new HashMap<>();
static final Map> ITEMS = new HashMap<>();
@@ -220,6 +222,12 @@ public final class SpellTraits implements Iterable> {
}
public static SpellTraits of(Item item) {
+ if (item instanceof ItemWithTraits i) {
+ return i.getDefaultTraits();
+ }
+ if (item instanceof SpawnEggItem) {
+ return SPAWN_EGG_TRAITS;
+ }
return REGISTRY.getOrDefault(Registries.ITEM.getId(item), EMPTY);
}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/BaseZapAppleLeavesBlock.java b/src/main/java/com/minelittlepony/unicopia/block/BaseZapAppleLeavesBlock.java
index 84cfd2b8..d8ef1910 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/BaseZapAppleLeavesBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/BaseZapAppleLeavesBlock.java
@@ -14,7 +14,7 @@ import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.*;
-public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock {
+public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock, ZapStagedBlock {
public static final MapCodec CODEC = LeavesBlock.createCodec(BaseZapAppleLeavesBlock::new);
public static Settings settings() {
@@ -39,61 +39,29 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
}
@Override
- public boolean hasRandomTicks(BlockState state) {
- return !state.get(PERSISTENT);
- }
-
- @Override
- public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
- super.randomTick(state, world, pos, random);
- tryAdvanceStage(state, world, pos, random);
- }
-
- @Override
- public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
- if (state.get(PERSISTENT)) {
- return state;
+ public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
+ if (state.get(PERSISTENT) || oldState.isOf(state.getBlock())) {
+ return;
}
-
- if (world instanceof ServerWorld sw) {
- ZapAppleStageStore store = ZapAppleStageStore.get(sw);
- ZapAppleStageStore.Stage currentStage = store.getStage();
- if (currentStage == ZapAppleStageStore.Stage.HIBERNATING) {
- return currentStage.getNewState(state);
- }
- }
-
- return state;
+ updateStage(state, world, pos);
}
@Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.scheduledTick(state, world, pos, random);
- tryAdvanceStage(state, world, pos, random);
- if (!state.get(PERSISTENT)) {
- world.scheduleBlockTick(pos, this, 1);
- }
- }
-
- private void tryAdvanceStage(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (state.get(PERSISTENT)) {
return;
}
-
- ZapAppleStageStore store = ZapAppleStageStore.get(world);
- ZapAppleStageStore.Stage newStage = store.getStage();
- if (!world.isDay() && getStage(state).mustChangeIntoInstantly(newStage)) {
- world.setBlockState(pos, newStage.getNewState(state));
- onStageChanged(store, newStage, world, state, pos, random);
- }
+ tryAdvanceStage(state, world, pos, random);
}
- protected ZapAppleStageStore.Stage getStage(BlockState state) {
+ @Override
+ public ZapAppleStageStore.Stage getStage(BlockState state) {
return ZapAppleStageStore.Stage.FLOWERING;
}
@Override
- protected boolean shouldDecay(BlockState state) {
+ protected final boolean shouldDecay(BlockState state) {
return false;
}
@@ -124,40 +92,10 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
@Override
public int getTint(BlockState state, @Nullable BlockRenderView world, @Nullable BlockPos pos, int foliageColor) {
-
if (pos == null) {
return 0x4C7EFA;
}
return TintedBlock.blend(TintedBlock.rotate(foliageColor, 2), 0x0000FF, 0.3F);
}
-
- static void onStageChanged(ZapAppleStageStore store, ZapAppleStageStore.Stage stage, ServerWorld world, BlockState state, BlockPos pos, Random random) {
- boolean mustFruit = Random.create(state.getRenderingSeed(pos)).nextInt(5) < 2;
- BlockState below = world.getBlockState(pos.down());
-
- if (world.isAir(pos.down())) {
- if (stage == ZapAppleStageStore.Stage.FRUITING && mustFruit) {
- world.setBlockState(pos.down(), UBlocks.ZAP_BULB.getDefaultState(), Block.NOTIFY_ALL);
- store.triggerLightningStrike(pos);
- }
- }
-
- if (stage != ZapAppleStageStore.Stage.HIBERNATING && world.getRandom().nextInt(10) == 0) {
- store.triggerLightningStrike(pos);
- }
-
- if (stage == ZapAppleStageStore.Stage.RIPE) {
- if (below.isOf(UBlocks.ZAP_BULB)) {
- world.setBlockState(pos.down(), UBlocks.ZAP_APPLE.getDefaultState(), Block.NOTIFY_ALL);
- store.playMoonEffect(pos);
- }
- }
-
- if (mustFruit && stage == ZapAppleStageStore.Stage.HIBERNATING) {
- if (below.isOf(UBlocks.ZAP_APPLE) || below.isOf(UBlocks.ZAP_BULB)) {
- world.setBlockState(pos.down(), Blocks.AIR.getDefaultState());
- }
- }
- }
}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/CuringJokeBlock.java b/src/main/java/com/minelittlepony/unicopia/block/CuringJokeBlock.java
new file mode 100644
index 00000000..d5d0dced
--- /dev/null
+++ b/src/main/java/com/minelittlepony/unicopia/block/CuringJokeBlock.java
@@ -0,0 +1,66 @@
+package com.minelittlepony.unicopia.block;
+
+import java.util.List;
+
+import com.minelittlepony.unicopia.ability.EarthPonyGrowAbility.Growable;
+import com.minelittlepony.unicopia.entity.mob.IgnominiousBulbEntity;
+import com.minelittlepony.unicopia.particle.MagicParticleEffect;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import net.minecraft.block.BlockState;
+import net.minecraft.block.FlowerBlock;
+import net.minecraft.block.SuspiciousStewIngredient;
+import net.minecraft.client.util.ParticleUtil;
+import net.minecraft.entity.Dismounting;
+import net.minecraft.entity.effect.StatusEffect;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.random.Random;
+import net.minecraft.world.World;
+
+public class CuringJokeBlock extends FlowerBlock implements Growable {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ STEW_EFFECT_CODEC.forGetter(FlowerBlock::getStewEffects),
+ FlowerBlock.createSettingsCodec()
+ ).apply(instance, CuringJokeBlock::new));
+
+ public CuringJokeBlock(StatusEffect suspiciousStewEffect, int effectDuration, Settings settings) {
+ super(suspiciousStewEffect, effectDuration, settings);
+ }
+
+ protected CuringJokeBlock(List effects, Settings settings) {
+ super(effects, settings);
+ }
+
+ @Override
+ public MapCodec extends FlowerBlock> getCodec() {
+ return CODEC;
+ }
+
+ @Override
+ public void randomDisplayTick(BlockState state, World world, BlockPos pos, Random random) {
+ for (int i = 0; i < 3; i++) {
+ ParticleUtil.spawnParticle(world, pos, random, new MagicParticleEffect(0x3388EE));
+ }
+ }
+
+ @Override
+ public boolean grow(World world, BlockState state, BlockPos pos) {
+ var otherFlowers = BlockPos.streamOutwards(pos, 16, 16, 16)
+ .filter(p -> world.getBlockState(p).isOf(this))
+ .map(BlockPos::toImmutable)
+ .toList();
+
+ IgnominiousBulbEntity bulb = new IgnominiousBulbEntity(world);
+ bulb.setBaby(true);
+ bulb.updatePositionAndAngles(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0);
+
+ if (Dismounting.canPlaceEntityAt(world, bulb, bulb.getBoundingBox())) {
+ otherFlowers.forEach(p -> world.breakBlock(p, false));
+ world.spawnEntity(bulb);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/EdibleBlock.java b/src/main/java/com/minelittlepony/unicopia/block/EdibleBlock.java
new file mode 100644
index 00000000..b450c828
--- /dev/null
+++ b/src/main/java/com/minelittlepony/unicopia/block/EdibleBlock.java
@@ -0,0 +1,229 @@
+package com.minelittlepony.unicopia.block;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+import org.jetbrains.annotations.Nullable;
+
+import com.minelittlepony.unicopia.USounds;
+import com.minelittlepony.unicopia.block.state.StateUtil;
+import com.minelittlepony.unicopia.entity.player.Pony;
+
+import net.fabricmc.fabric.api.event.player.UseBlockCallback;
+import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.Blocks;
+import net.minecraft.block.HayBlock;
+import net.minecraft.block.ShapeContext;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.registry.Registries;
+import net.minecraft.registry.tag.ItemTags;
+import net.minecraft.sound.SoundCategory;
+import net.minecraft.state.StateManager;
+import net.minecraft.state.property.BooleanProperty;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.Hand;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.Util;
+import net.minecraft.util.hit.BlockHitResult;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.util.shape.VoxelShape;
+import net.minecraft.util.shape.VoxelShapes;
+import net.minecraft.world.BlockView;
+import net.minecraft.world.World;
+
+public class EdibleBlock extends HayBlock {
+ private static final List REGISTRY = new ArrayList<>();
+
+ static final BooleanProperty TOP_NORTH_EAST = BooleanProperty.of("top_north_east");
+ static final BooleanProperty TOP_NORTH_WEST = BooleanProperty.of("top_north_west");
+ static final BooleanProperty TOP_SOUTH_EAST = BooleanProperty.of("top_south_east");
+ static final BooleanProperty TOP_SOUTH_WEST = BooleanProperty.of("top_south_west");
+
+ static final BooleanProperty BOTTOM_NORTH_EAST = BooleanProperty.of("bottom_north_east");
+ static final BooleanProperty BOTTOM_NORTH_WEST = BooleanProperty.of("bottom_north_west");
+ static final BooleanProperty BOTTOM_SOUTH_EAST = BooleanProperty.of("bottom_south_east");
+ static final BooleanProperty BOTTOM_SOUTH_WEST = BooleanProperty.of("bottom_south_west");
+
+ // [up/down][north/south][west/east]
+ private static final BooleanProperty[] SEGMENTS = {
+ BOTTOM_NORTH_WEST,
+ BOTTOM_NORTH_EAST,
+ BOTTOM_SOUTH_WEST,
+ BOTTOM_SOUTH_EAST,
+ TOP_NORTH_WEST,
+ TOP_NORTH_EAST,
+ TOP_SOUTH_WEST,
+ TOP_SOUTH_EAST
+ };
+ private static final VoxelShape[] SHAPES = {
+ Block.createCuboidShape(0, 0, 0, 8, 8, 8),
+ Block.createCuboidShape(8, 0, 0, 16, 8, 8),
+ Block.createCuboidShape(0, 0, 8, 8, 8, 16),
+ Block.createCuboidShape(8, 0, 8, 16, 8, 16),
+ Block.createCuboidShape(0, 8, 0, 8, 16, 8),
+ Block.createCuboidShape(8, 8, 0, 16, 16, 8),
+ Block.createCuboidShape(0, 8, 8, 8, 16, 16),
+ Block.createCuboidShape(8, 8, 8, 16, 16, 16)
+ };
+ private static final Function SHAPE_CACHE = Util.memoize(state -> {
+ @Nullable
+ VoxelShape shape = null;
+ for (int i = 0; i < SEGMENTS.length; i++) {
+ if (state.get(SEGMENTS[i])) {
+ shape = shape == null ? SHAPES[i] : VoxelShapes.union(shape, SHAPES[i]);
+ }
+ }
+ return shape == null ? VoxelShapes.fullCube() : shape.simplify();
+ });
+
+ static void bootstrap() {
+ UseBlockCallback.EVENT.register((PlayerEntity player, World world, Hand hand, BlockHitResult hitResult) -> {
+ if (!Pony.of(player).getSpecies().isEquine()
+ || (player.shouldCancelInteraction() && (!player.getMainHandStack().isEmpty() || !player.getOffHandStack().isEmpty()))) {
+ return ActionResult.PASS;
+ }
+
+ BlockPos pos = hitResult.getBlockPos();
+ BlockState state = world.getBlockState(pos);
+
+ for (EdibleBlock edibleBlock : REGISTRY) {
+ Block match = edibleBlock.getBaseBlock();
+ if (match != Blocks.AIR && state.isOf(match)) {
+ ActionResult result = StateUtil.copyState(state, edibleBlock.getDefaultState()).onUse(world, player, hand, hitResult);
+
+ if (result.isAccepted()) {
+ return result;
+ }
+ }
+ }
+
+ return ActionResult.PASS;
+ });
+ }
+
+ private final Identifier baseBlock;
+ private final Identifier material;
+
+ public EdibleBlock(Identifier baseBlock, Identifier material, boolean register) {
+ super(Settings.copy(Blocks.HAY_BLOCK));
+ for (BooleanProperty segment : SEGMENTS) {
+ setDefaultState(getDefaultState().with(segment, true));
+ }
+ this.baseBlock = baseBlock;
+ this.material = material;
+ if (register) {
+ REGISTRY.add(this);
+ FlammableBlockRegistry.getDefaultInstance().add(this, 60, 20);
+ }
+ }
+
+ public Block getBaseBlock() {
+ return Registries.BLOCK.get(baseBlock);
+ }
+
+ @Override
+ public String getTranslationKey() {
+ return getBaseBlock().getTranslationKey();
+ }
+
+ @Override
+ protected void appendProperties(StateManager.Builder builder) {
+ super.appendProperties(builder);
+ builder.add(SEGMENTS);
+ }
+
+ @Override
+ public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
+ return SHAPE_CACHE.apply(state);
+ }
+
+ @Override
+ @Deprecated
+ public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
+ if (player.isSpectator()) {
+ return ActionResult.FAIL;
+ }
+
+ ItemStack stack = player.getStackInHand(hand);
+
+ if (!stack.isEmpty() && stack.isOf(Registries.ITEM.get(material))) {
+ BooleanProperty segment = getHitCorner(hit, 1);
+
+ if (!state.get(segment)) {
+ if (!player.isCreative()) {
+ stack.decrement(1);
+ }
+ if (!world.isClient) {
+ state = state.with(segment, true);
+ if (SHAPE_CACHE.apply(state) == VoxelShapes.fullCube()) {
+ state = StateUtil.copyState(state, getBaseBlock().getDefaultState());
+ }
+ world.setBlockState(pos, state);
+ }
+ world.playSound(player, pos, getSoundGroup(state).getPlaceSound(), SoundCategory.BLOCKS);
+
+ return ActionResult.SUCCESS;
+ }
+
+ return ActionResult.FAIL;
+ }
+
+ BooleanProperty corner = getHitCorner(hit, -1);
+
+ if (!state.get(corner)) {
+ return ActionResult.PASS;
+ }
+
+ boolean usingHoe = stack.isIn(ItemTags.HOES);
+
+ if (!usingHoe) {
+ if (!(player.isCreative() || player.getHungerManager().isNotFull()) || !player.isSneaking()) {
+ return ActionResult.FAIL;
+ }
+ }
+
+ if (!world.isClient) {
+ state = state.with(corner, false);
+ if (SHAPE_CACHE.apply(state) == VoxelShapes.fullCube()) {
+ world.removeBlock(pos, false);
+ } else {
+ world.setBlockState(pos, state);
+ }
+ }
+
+ if (usingHoe) {
+ stack.damage(1, player, p -> p.sendToolBreakStatus(hand));
+ dropStack(world, pos, Registries.ITEM.get(material).getDefaultStack());
+ player.playSound(USounds.Vanilla.ITEM_HOE_TILL, 1, 1);
+ } else {
+ player.playSound(USounds.Vanilla.ENTITY_GENERIC_EAT, 1, 1);
+ if (world.random.nextInt(10) == 0) {
+ player.playSound(USounds.Vanilla.ENTITY_PLAYER_BURP, 1, player.getSoundPitch());
+ }
+ player.getHungerManager().add(4, 2.3F);
+ }
+ return ActionResult.SUCCESS;
+ }
+
+ static BooleanProperty getHitCorner(BlockHitResult hit, int direction) {
+ Vec3d pos = hit.getPos().add(Vec3d.of(hit.getSide().getVector()).multiply(direction * 0.001F));
+
+ BlockPos bPos = hit.getBlockPos();
+
+ return SEGMENTS[
+ (4 * getIndex(pos.y, bPos.getY()))
+ + (2 * getIndex(pos.z, bPos.getZ()))
+ + (getIndex(pos.x, bPos.getX()))
+ ];
+ }
+
+ static int getIndex(double axisHit, int tile) {
+ axisHit -= tile;
+ return Math.abs(axisHit) > 0.5 ? 1 : 0;
+ }
+}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/EnchantedFruitBlock.java b/src/main/java/com/minelittlepony/unicopia/block/EnchantedFruitBlock.java
new file mode 100644
index 00000000..15c2f6ad
--- /dev/null
+++ b/src/main/java/com/minelittlepony/unicopia/block/EnchantedFruitBlock.java
@@ -0,0 +1,31 @@
+package com.minelittlepony.unicopia.block;
+
+import com.mojang.serialization.MapCodec;
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.state.StateManager;
+import net.minecraft.state.property.BooleanProperty;
+import net.minecraft.util.math.Direction;
+import net.minecraft.util.shape.VoxelShape;
+
+public class EnchantedFruitBlock extends FruitBlock {
+ static final BooleanProperty ENCHANTED = BooleanProperty.of("enchanted");
+
+ private static final MapCodec CODEC = createCodec(EnchantedFruitBlock::new);
+
+ public EnchantedFruitBlock(Direction attachmentFace, Block stem, VoxelShape shape, boolean flammable, Settings settings) {
+ super(attachmentFace, stem, shape, flammable, settings);
+ setDefaultState(getDefaultState().with(ENCHANTED, false));
+ }
+
+ @Override
+ public MapCodec extends FruitBlock> getCodec() {
+ return CODEC;
+ }
+
+ @Override
+ protected void appendProperties(StateManager.Builder builder) {
+ super.appendProperties(builder);
+ builder.add(ENCHANTED);
+ }
+}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/FancyBedBlock.java b/src/main/java/com/minelittlepony/unicopia/block/FancyBedBlock.java
index 802394b6..7b74d5ec 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/FancyBedBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/FancyBedBlock.java
@@ -52,7 +52,7 @@ public class FancyBedBlock extends BedBlock {
))
);
- private final String base;
+ protected final String base;
public FancyBedBlock(String base, Settings settings) {
super(DyeColor.WHITE, settings);
diff --git a/src/main/java/com/minelittlepony/unicopia/block/FruitBearingBlock.java b/src/main/java/com/minelittlepony/unicopia/block/FruitBearingBlock.java
index 0e49ae15..a1279a39 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/FruitBearingBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/FruitBearingBlock.java
@@ -42,10 +42,10 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
public static final List REGISTRY = new ArrayList<>();
- private final Supplier fruit;
- private final Supplier rottenFruitSupplier;
+ protected final Supplier fruit;
+ protected final Supplier rottenFruitSupplier;
- private final int overlay;
+ protected final int overlay;
public FruitBearingBlock(int overlay, Supplier fruit, Supplier rottenFruitSupplier, Settings settings) {
super(settings
@@ -78,6 +78,14 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
return true;
}
+ protected boolean shouldAdvance(Random random) {
+ return true;
+ }
+
+ protected BlockState getPlacedFruitState(Random random) {
+ return fruit.get().getDefaultState();
+ }
+
@Override
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.randomTick(state, world, pos, random);
@@ -86,10 +94,14 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
return;
}
- if (world.isDay()) {
+ if (world.getBaseLightLevel(pos, 0) > 8) {
BlockSoundGroup group = getSoundGroup(state);
int steps = FertilizableUtil.getGrowthSteps(world, pos, state, random);
while (steps-- > 0) {
+ if (!shouldAdvance(random)) {
+ continue;
+ }
+
if (state.get(STAGE) == Stage.FRUITING) {
state = state.cycle(AGE);
if (state.get(AGE) > 20) {
@@ -105,7 +117,7 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
if (stage == Stage.FRUITING && isPositionValidForFruit(state, pos)) {
if (world.isAir(fruitPosition)) {
- world.setBlockState(fruitPosition, fruit.get().getDefaultState(), Block.NOTIFY_ALL);
+ world.setBlockState(fruitPosition, getPlacedFruitState(random), Block.NOTIFY_ALL);
}
}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/FruitBlock.java b/src/main/java/com/minelittlepony/unicopia/block/FruitBlock.java
index 2458d5bb..9aed5d1b 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/FruitBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/FruitBlock.java
@@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.block;
import java.util.List;
import com.minelittlepony.unicopia.ability.EarthPonyKickAbility.Buckable;
+import com.mojang.datafixers.util.Function5;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
@@ -23,20 +24,24 @@ public class FruitBlock extends Block implements Buckable {
public static final int DEFAULT_FRUIT_SIZE = 5;
public static final double DEFAULT_STEM_OFFSET = 2.6F;
public static final VoxelShape DEFAULT_SHAPE = createFruitShape(DEFAULT_STEM_OFFSET, DEFAULT_FRUIT_SIZE);
- public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
- Direction.CODEC.fieldOf("attachment_face").forGetter(b -> b.attachmentFace),
- Registries.BLOCK.getCodec().fieldOf("stem").forGetter(b -> b.stem),
- RecordCodecBuilder.create(i -> i.group(
- Codec.DOUBLE.fieldOf("stem_offset").forGetter(b -> (double)0),
- Codec.DOUBLE.fieldOf("fruit_offset").forGetter(b -> (double)0)
- ).apply(i, FruitBlock::createFruitShape)).fieldOf("shape").forGetter(b -> b.shape),
- Codec.BOOL.fieldOf("flammable").forGetter(b -> false),
- BedBlock.createSettingsCodec()
- ).apply(instance, FruitBlock::new));
+ private static final MapCodec CODEC = createCodec(FruitBlock::new);
- private final Direction attachmentFace;
- private final Block stem;
- private final VoxelShape shape;
+ protected final Direction attachmentFace;
+ protected final Block stem;
+ protected final VoxelShape shape;
+
+ public static MapCodec createCodec(Function5 constructor) {
+ return RecordCodecBuilder.mapCodec(instance -> instance.group(
+ Direction.CODEC.fieldOf("attachment_face").forGetter(b -> b.attachmentFace),
+ Registries.BLOCK.getCodec().fieldOf("stem").forGetter(b -> b.stem),
+ RecordCodecBuilder.create(i -> i.group(
+ Codec.DOUBLE.fieldOf("stem_offset").forGetter(b -> (double)0),
+ Codec.DOUBLE.fieldOf("fruit_offset").forGetter(b -> (double)0)
+ ).apply(i, FruitBlock::createFruitShape)).fieldOf("shape").forGetter(b -> b.shape),
+ Codec.BOOL.fieldOf("flammable").forGetter(b -> false),
+ BedBlock.createSettingsCodec()
+ ).apply(instance, constructor));
+ }
public static VoxelShape createFruitShape(double stemOffset, double fruitSize) {
final double min = (16 - fruitSize) * 0.5;
diff --git a/src/main/java/com/minelittlepony/unicopia/block/GoldenOakLeavesBlock.java b/src/main/java/com/minelittlepony/unicopia/block/GoldenOakLeavesBlock.java
new file mode 100644
index 00000000..01f30449
--- /dev/null
+++ b/src/main/java/com/minelittlepony/unicopia/block/GoldenOakLeavesBlock.java
@@ -0,0 +1,43 @@
+package com.minelittlepony.unicopia.block;
+
+import java.util.function.Supplier;
+
+import com.minelittlepony.unicopia.util.CodecUtils;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import net.minecraft.block.BedBlock;
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.item.ItemStack;
+import net.minecraft.registry.Registries;
+import net.minecraft.util.math.random.Random;
+
+public class GoldenOakLeavesBlock extends FruitBearingBlock {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ Codec.INT.fieldOf("overlay").forGetter(b -> b.overlay),
+ CodecUtils.supplierOf(Registries.BLOCK.getCodec()).fieldOf("fruit").forGetter(b -> b.fruit),
+ CodecUtils.supplierOf(ItemStack.CODEC).fieldOf("rotten_fruit").forGetter(b -> b.rottenFruitSupplier),
+ BedBlock.createSettingsCodec()
+ ).apply(instance, GoldenOakLeavesBlock::new));
+
+ public GoldenOakLeavesBlock(int overlay, Supplier fruit, Supplier rottenFruitSupplier, Settings settings) {
+ super(overlay, fruit, rottenFruitSupplier, settings);
+ }
+
+ @Override
+ public MapCodec extends GoldenOakLeavesBlock> getCodec() {
+ return CODEC;
+ }
+
+ @Override
+ protected boolean shouldAdvance(Random random) {
+ return random.nextInt(1000) == 0;
+ }
+
+ @Override
+ protected BlockState getPlacedFruitState(Random random) {
+ return super.getPlacedFruitState(random).with(EnchantedFruitBlock.ENCHANTED, random.nextInt(1000) == 0);
+ }
+}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java b/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java
index 1fa1ec1a..5f0d7ab2 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java
@@ -21,6 +21,7 @@ import com.minelittlepony.unicopia.block.cloud.SoggyCloudBlock;
import com.minelittlepony.unicopia.block.cloud.SoggyCloudSlabBlock;
import com.minelittlepony.unicopia.block.cloud.SoggyCloudStairsBlock;
import com.minelittlepony.unicopia.block.cloud.UnstableCloudBlock;
+import com.minelittlepony.unicopia.entity.effect.UEffects;
import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.item.cloud.CloudBlockItem;
import com.minelittlepony.unicopia.item.group.ItemGroupRegistry;
@@ -28,6 +29,7 @@ import com.minelittlepony.unicopia.server.world.UTreeGen;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
import net.fabricmc.fabric.api.registry.StrippableBlockRegistry;
+import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.block.*;
import net.minecraft.block.AbstractBlock.Settings;
import net.minecraft.block.entity.BlockEntityType;
@@ -136,6 +138,15 @@ public interface UBlocks {
() -> UItems.APPLE_PIE_HOOF,
Settings.create().solid().mapColor(MapColor.ORANGE).strength(0.5F).sounds(BlockSoundGroup.WOOL).pistonBehavior(PistonBehavior.DESTROY)
));
+ Block GOLDEN_OAK_LEAVES = register("golden_oak_leaves", new GoldenOakLeavesBlock(
+ MapColor.GOLD.color,
+ () -> UBlocks.GOLDEN_APPLE,
+ () -> Items.GOLDEN_APPLE.getDefaultStack(),
+ FabricBlockSettings.copy(Blocks.OAK_LEAVES)
+ ), ItemGroups.NATURAL);
+ Block GOLDEN_APPLE = register("golden_apple", new EnchantedFruitBlock(Direction.DOWN, GOLDEN_OAK_LEAVES, FruitBlock.DEFAULT_SHAPE, false, Settings.create().mapColor(MapColor.GOLD)));
+ Block GOLDEN_OAK_SPROUT = register("golden_oak_sprout", new SproutBlock(0xE5FFCC88, () -> UItems.GOLDEN_OAK_SEEDS, () -> UTreeGen.GOLDEN_APPLE_TREE.sapling().map(Block::getDefaultState).get(), SproutBlock.settings()));
+ Block GOLDEN_OAK_LOG = register("golden_oak_log", BlockConstructionUtils.createLogBlock(MapColor.OFF_WHITE, MapColor.GOLD), ItemGroups.BUILDING_BLOCKS);
SegmentedCropBlock OATS = register("oats", SegmentedCropBlock.create(11, 5, () -> UItems.OAT_SEEDS, null, null, AbstractBlock.Settings.copy(Blocks.WHEAT)));
SegmentedCropBlock OATS_STEM = register("oats_stem", OATS.createNext(5));
@@ -143,6 +154,13 @@ public interface UBlocks {
Block PLUNDER_VINE = register("plunder_vine", new ThornBlock(() -> UBlocks.PLUNDER_VINE_BUD, Settings.create().mapColor(MapColor.DARK_CRIMSON).hardness(1).ticksRandomly().sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.DESTROY)));
Block PLUNDER_VINE_BUD = register("plunder_vine_bud", new ThornBudBlock(PLUNDER_VINE.getDefaultState(), Settings.create().mapColor(MapColor.DARK_CRIMSON).hardness(1).nonOpaque().ticksRandomly().sounds(BlockSoundGroup.GRASS).pistonBehavior(PistonBehavior.DESTROY)));
+ CuringJokeBlock CURING_JOKE = register("curing_joke", new CuringJokeBlock(UEffects.BUTTER_FINGERS, 7, AbstractBlock.Settings.create().mapColor(MapColor.PALE_PURPLE).noCollision().breakInstantly().sounds(BlockSoundGroup.GRASS).offset(AbstractBlock.OffsetType.XZ).pistonBehavior(PistonBehavior.DESTROY)));
+ Block GOLD_ROOT = register("gold_root", new CarrotsBlock(AbstractBlock.Settings.create().mapColor(MapColor.GOLD).noCollision().ticksRandomly().breakInstantly().sounds(BlockSoundGroup.CROP).pistonBehavior(PistonBehavior.DESTROY)) {
+ @Override
+ protected ItemConvertible getSeedsItem() {
+ return Items.GOLDEN_CARROT;
+ }
+ });
Block CHITIN = register("chitin", new SnowyBlock(Settings.create().mapColor(MapColor.PALE_PURPLE).hardness(5).requiresTool().ticksRandomly().sounds(BlockSoundGroup.CORAL)), ItemGroups.NATURAL);
Block SURFACE_CHITIN = register("surface_chitin", new GrowableBlock(() -> CHITIN, Settings.copy(CHITIN)), ItemGroups.NATURAL);
@@ -156,49 +174,42 @@ public interface UBlocks {
Block SLIME_PUSTULE = register("slime_pustule", new SlimePustuleBlock(Settings.copy(Blocks.SLIME_BLOCK)), ItemGroups.NATURAL);
Block SHAPING_BENCH = register("shaping_bench", new ShapingBenchBlock(Settings.create().mapColor(MapColor.OFF_WHITE).hardness(0.3F).resistance(0).sounds(BlockSoundGroup.WOOL)), ItemGroups.FUNCTIONAL);
- Block CLOUD = register("cloud", new NaturalCloudBlock(Settings.create().mapColor(MapColor.OFF_WHITE).hardness(0.3F).resistance(0).sounds(BlockSoundGroup.WOOL), true,
+ Block CLOUD = register("cloud", new NaturalCloudBlock(true,
() -> UBlocks.SOGGY_CLOUD,
- () -> UBlocks.COMPACTED_CLOUD), ItemGroups.NATURAL);
+ () -> UBlocks.COMPACTED_CLOUD,
+ Settings.create().mapColor(MapColor.OFF_WHITE).hardness(0.3F).resistance(0).sounds(BlockSoundGroup.WOOL)), ItemGroups.NATURAL);
Block COMPACTED_CLOUD = register("compacted_cloud", new CompactedCloudBlock(CLOUD.getDefaultState()));
- Block CLOUD_SLAB = register("cloud_slab", new CloudSlabBlock(Settings.copy(CLOUD), true, () -> UBlocks.SOGGY_CLOUD_SLAB), ItemGroups.NATURAL);
- PoreousCloudStairsBlock CLOUD_STAIRS = register("cloud_stairs", new PoreousCloudStairsBlock(CLOUD.getDefaultState(), Settings.copy(CLOUD), () -> UBlocks.SOGGY_CLOUD_STAIRS), ItemGroups.NATURAL);
+ Block CLOUD_SLAB = register("cloud_slab", new CloudSlabBlock(true, () -> UBlocks.SOGGY_CLOUD_SLAB, Settings.copy(CLOUD)), ItemGroups.NATURAL);
+ PoreousCloudStairsBlock CLOUD_STAIRS = register("cloud_stairs", new PoreousCloudStairsBlock(CLOUD.getDefaultState(), () -> UBlocks.SOGGY_CLOUD_STAIRS, Settings.copy(CLOUD)), ItemGroups.NATURAL);
- Block CLOUD_PLANKS = register("cloud_planks", new NaturalCloudBlock(Settings.copy(CLOUD).hardness(0.4F).requiresTool().solid(), false,
- null,
- () -> UBlocks.COMPACTED_CLOUD_PLANKS), ItemGroups.BUILDING_BLOCKS);
+ Block CLOUD_PLANKS = register("cloud_planks", new NaturalCloudBlock(false, null, () -> UBlocks.COMPACTED_CLOUD_PLANKS, Settings.copy(CLOUD).hardness(0.4F).requiresTool().solid()), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_CLOUD_PLANKS = register("compacted_cloud_planks", new CompactedCloudBlock(CLOUD_PLANKS.getDefaultState()));
- Block CLOUD_PLANK_SLAB = register("cloud_plank_slab", new CloudSlabBlock(Settings.copy(CLOUD_PLANKS), false, null), ItemGroups.BUILDING_BLOCKS);
+ Block CLOUD_PLANK_SLAB = register("cloud_plank_slab", new CloudSlabBlock(false, null, Settings.copy(CLOUD_PLANKS)), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_PLANK_STAIRS = register("cloud_plank_stairs", new CloudStairsBlock(CLOUD_PLANKS.getDefaultState(), Settings.copy(CLOUD_PLANKS)), ItemGroups.BUILDING_BLOCKS);
- Block CLOUD_BRICKS = register("cloud_bricks", new NaturalCloudBlock(Settings.copy(CLOUD).hardness(0.6F).requiresTool().solid(), false,
- null,
- () -> UBlocks.COMPACTED_CLOUD_BRICKS), ItemGroups.BUILDING_BLOCKS);
+ Block CLOUD_BRICKS = register("cloud_bricks", new NaturalCloudBlock(false, null, () -> UBlocks.COMPACTED_CLOUD_BRICKS, Settings.copy(CLOUD).hardness(0.6F).requiresTool().solid()), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_CLOUD_BRICKS = register("compacted_cloud_bricks", new CompactedCloudBlock(CLOUD_BRICKS.getDefaultState()));
- Block CLOUD_BRICK_SLAB = register("cloud_brick_slab", new CloudSlabBlock(Settings.copy(CLOUD_BRICKS), false, null), ItemGroups.BUILDING_BLOCKS);
+ Block CLOUD_BRICK_SLAB = register("cloud_brick_slab", new CloudSlabBlock(false, null, Settings.copy(CLOUD_BRICKS)), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_BRICK_STAIRS = register("cloud_brick_stairs", new CloudStairsBlock(CLOUD_BRICKS.getDefaultState(), Settings.copy(CLOUD_PLANKS)), ItemGroups.BUILDING_BLOCKS);
- Block ETCHED_CLOUD = register("etched_cloud", new NaturalCloudBlock(Settings.copy(CLOUD_BRICKS), false,
- null,
- () -> UBlocks.COMPACTED_CLOUD_BRICKS), ItemGroups.BUILDING_BLOCKS);
+ Block ETCHED_CLOUD = register("etched_cloud", new NaturalCloudBlock(false, null, () -> UBlocks.COMPACTED_CLOUD_BRICKS, Settings.copy(CLOUD_BRICKS)), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_ETCHED_CLOUD = register("compacted_etched_cloud", new CompactedCloudBlock(ETCHED_CLOUD.getDefaultState()));
- Block ETCHED_CLOUD_SLAB = register("etched_cloud_slab", new CloudSlabBlock(Settings.copy(ETCHED_CLOUD), false, null), ItemGroups.BUILDING_BLOCKS);
+ Block ETCHED_CLOUD_SLAB = register("etched_cloud_slab", new CloudSlabBlock(false, null, Settings.copy(ETCHED_CLOUD)), ItemGroups.BUILDING_BLOCKS);
Block ETCHED_CLOUD_STAIRS = register("etched_cloud_stairs", new CloudStairsBlock(ETCHED_CLOUD.getDefaultState(), Settings.copy(CLOUD_PLANKS)), ItemGroups.BUILDING_BLOCKS);
- SoggyCloudBlock SOGGY_CLOUD = register("soggy_cloud", new SoggyCloudBlock(Settings.copy(CLOUD).hardness(0.7F), () -> UBlocks.CLOUD));
- SoggyCloudSlabBlock SOGGY_CLOUD_SLAB = register("soggy_cloud_slab", new SoggyCloudSlabBlock(Settings.copy(SOGGY_CLOUD), () -> UBlocks.CLOUD_SLAB));
- SoggyCloudStairsBlock SOGGY_CLOUD_STAIRS = register("soggy_cloud_stairs", new SoggyCloudStairsBlock(SOGGY_CLOUD.getDefaultState(), Settings.copy(CLOUD), () -> UBlocks.CLOUD_STAIRS));
+ SoggyCloudBlock SOGGY_CLOUD = register("soggy_cloud", new SoggyCloudBlock(() -> UBlocks.CLOUD, Settings.copy(CLOUD).hardness(0.7F)));
+ SoggyCloudSlabBlock SOGGY_CLOUD_SLAB = register("soggy_cloud_slab", new SoggyCloudSlabBlock(() -> UBlocks.CLOUD_SLAB, Settings.copy(SOGGY_CLOUD)));
+ SoggyCloudStairsBlock SOGGY_CLOUD_STAIRS = register("soggy_cloud_stairs", new SoggyCloudStairsBlock(SOGGY_CLOUD.getDefaultState(), () -> UBlocks.CLOUD_STAIRS, Settings.copy(CLOUD)));
- Block DENSE_CLOUD = register("dense_cloud", new NaturalCloudBlock(Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL).solid(), false,
- null,
- () -> UBlocks.COMPACTED_DENSE_CLOUD), ItemGroups.BUILDING_BLOCKS);
+ Block DENSE_CLOUD = register("dense_cloud", new NaturalCloudBlock(false, null, () -> UBlocks.COMPACTED_DENSE_CLOUD, Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL).solid()), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_DENSE_CLOUD = register("compacted_dense_cloud", new CompactedCloudBlock(DENSE_CLOUD.getDefaultState()));
- Block DENSE_CLOUD_SLAB = register("dense_cloud_slab", new CloudSlabBlock(Settings.copy(DENSE_CLOUD), false, null), ItemGroups.BUILDING_BLOCKS);
+ Block DENSE_CLOUD_SLAB = register("dense_cloud_slab", new CloudSlabBlock(false, null, Settings.copy(DENSE_CLOUD)), ItemGroups.BUILDING_BLOCKS);
Block DENSE_CLOUD_STAIRS = register("dense_cloud_stairs", new CloudStairsBlock(DENSE_CLOUD.getDefaultState(), Settings.copy(DENSE_CLOUD)), ItemGroups.BUILDING_BLOCKS);
- Block CARVED_CLOUD = register("carved_cloud", new OrientedCloudBlock(Settings.copy(CLOUD).hardness(0.4F).requiresTool().solid(), false), ItemGroups.BUILDING_BLOCKS);
+ Block CARVED_CLOUD = register("carved_cloud", new OrientedCloudBlock(false, Settings.copy(CLOUD).hardness(0.4F).requiresTool().solid()), ItemGroups.BUILDING_BLOCKS);
Block UNSTABLE_CLOUD = register("unstable_cloud", new UnstableCloudBlock(Settings.copy(CLOUD)), ItemGroups.NATURAL);
Block CLOUD_PILLAR = register("cloud_pillar", new CloudPillarBlock(Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL).solid()), ItemGroups.NATURAL);
- Block CLOUD_CHEST = register("cloud_chest", new CloudChestBlock(Settings.copy(DENSE_CLOUD).instrument(Instrument.BASS).strength(2.5f), DENSE_CLOUD.getDefaultState()), ItemGroups.FUNCTIONAL);
+ Block CLOUD_CHEST = register("cloud_chest", new CloudChestBlock(DENSE_CLOUD.getDefaultState(), Settings.copy(DENSE_CLOUD).instrument(Instrument.BASS).strength(2.5f)), ItemGroups.FUNCTIONAL);
Block CLOTH_BED = register("cloth_bed", new FancyBedBlock("cloth", Settings.copy(Blocks.WHITE_BED).sounds(BlockSoundGroup.WOOD)));
Block CLOUD_BED = register("cloud_bed", new CloudBedBlock("cloud", CLOUD.getDefaultState(), Settings.copy(Blocks.WHITE_BED).sounds(BlockSoundGroup.WOOL)));
@@ -209,7 +220,9 @@ public interface UBlocks {
Block STABLE_DOOR = register("stable_door", new StableDoorBlock(BlockSetType.OAK, Settings.copy(Blocks.OAK_DOOR)), ItemGroups.FUNCTIONAL);
Block DARK_OAK_DOOR = register("dark_oak_stable_door", new StableDoorBlock(BlockSetType.OAK, Settings.copy(Blocks.OAK_DOOR)), ItemGroups.FUNCTIONAL);
Block CRYSTAL_DOOR = register("crystal_door", new CrystalDoorBlock(UWoodTypes.CRYSTAL, Settings.copy(Blocks.IRON_DOOR)), ItemGroups.FUNCTIONAL);
- Block CLOUD_DOOR = register("cloud_door", new CloudDoorBlock(Settings.copy(CLOUD), CLOUD.getDefaultState(), UWoodTypes.CLOUD), ItemGroups.FUNCTIONAL);
+ Block CLOUD_DOOR = register("cloud_door", new CloudDoorBlock(CLOUD.getDefaultState(), UWoodTypes.CLOUD, Settings.copy(CLOUD)), ItemGroups.FUNCTIONAL);
+
+ EdibleBlock HAY_BLOCK = register("hay_block", new EdibleBlock(new Identifier("hay_block"), new Identifier("wheat"), true));
private static T register(String name, T item) {
return register(Unicopia.id(name), item);
@@ -234,10 +247,15 @@ public interface UBlocks {
if (block instanceof CloudLike || block instanceof SlimePustuleBlock || block instanceof PileBlock) {
SEMI_TRANSPARENT_BLOCKS.add(block);
}
+
return Registry.register(Registries.BLOCK, id, block);
}
static void bootstrap() {
+ if (FabricLoader.getInstance().isModLoaded("farmersdelight")) {
+ register("rice_block", new EdibleBlock(new Identifier("farmersdelight", "rice_bale"), new Identifier("farmersdelight", "rice_panicle"), true));
+ register("straw_block", new EdibleBlock(new Identifier("farmersdelight", "straw_bale"), new Identifier("farmersdelight", "straw"), true));
+ }
BlockEntityTypeSupportHelper.of(BlockEntityType.SIGN).addSupportedBlocks(PALM_SIGN, PALM_WALL_SIGN);
BlockEntityTypeSupportHelper.of(BlockEntityType.HANGING_SIGN).addSupportedBlocks(PALM_HANGING_SIGN, PALM_WALL_HANGING_SIGN);
@@ -245,21 +263,24 @@ public interface UBlocks {
StrippableBlockRegistry.register(PALM_LOG, STRIPPED_PALM_LOG);
StrippableBlockRegistry.register(ZAP_WOOD, STRIPPED_ZAP_WOOD);
StrippableBlockRegistry.register(PALM_WOOD, STRIPPED_PALM_WOOD);
- Collections.addAll(TRANSLUCENT_BLOCKS, WEATHER_VANE, CHITIN_SPIKES, PLUNDER_VINE, PLUNDER_VINE_BUD, CLAM_SHELL, SCALLOP_SHELL, TURRET_SHELL);
+ Collections.addAll(TRANSLUCENT_BLOCKS, WEATHER_VANE, CHITIN_SPIKES, PLUNDER_VINE, PLUNDER_VINE_BUD, CLAM_SHELL, SCALLOP_SHELL, TURRET_SHELL, CURING_JOKE);
TintedBlock.REGISTRY.add(PALM_LEAVES);
FlammableBlockRegistry.getDefaultInstance().add(GREEN_APPLE_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(SWEET_APPLE_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(SOUR_APPLE_LEAVES, 30, 60);
+ FlammableBlockRegistry.getDefaultInstance().add(GOLDEN_OAK_LEAVES, 60, 120);
FlammableBlockRegistry.getDefaultInstance().add(MANGO_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(PALM_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(PALM_LOG, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(PALM_WOOD, 5, 5);
+ FlammableBlockRegistry.getDefaultInstance().add(GOLDEN_OAK_LOG, 15, 15);
FlammableBlockRegistry.getDefaultInstance().add(STRIPPED_PALM_LOG, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(STRIPPED_PALM_WOOD, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(PALM_PLANKS, 5, 20);
FlammableBlockRegistry.getDefaultInstance().add(BANANAS, 5, 20);
UBlockEntities.bootstrap();
+ EdibleBlock.bootstrap();
}
}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/UWoodTypes.java b/src/main/java/com/minelittlepony/unicopia/block/UWoodTypes.java
index 0a44586e..5f76ed97 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/UWoodTypes.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/UWoodTypes.java
@@ -15,6 +15,7 @@ import net.minecraft.util.Identifier;
public interface UWoodTypes {
WoodType PALM = register("palm");
+ WoodType GOLDEN_OAK = register("golden_oak");
RegistryKey PALM_BOAT_TYPE = TerraformBoatTypeRegistry.createKey(Unicopia.id("palm"));
BlockSetType CLOUD = new BlockSetTypeBuilder()
diff --git a/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesBlock.java b/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesBlock.java
index 0a0baea8..ffdbe38a 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesBlock.java
@@ -5,11 +5,8 @@ import com.mojang.serialization.MapCodec;
import net.minecraft.block.*;
import net.minecraft.item.ItemPlacementContext;
-import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.*;
-import net.minecraft.util.math.BlockPos;
-import net.minecraft.world.World;
public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock {
public static final MapCodec CODEC = createCodec(ZapAppleLeavesBlock::new);
@@ -25,31 +22,13 @@ public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock {
return CODEC;
}
- @Override
- public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
- if (state.get(PERSISTENT)
- || oldState.isOf(state.getBlock())
- || oldState.isOf(UBlocks.ZAP_LEAVES)
- || oldState.isOf(UBlocks.FLOWERING_ZAP_LEAVES)
- || oldState.isOf(UBlocks.ZAP_LEAVES_PLACEHOLDER)
- || !(world instanceof ServerWorld sw)) {
- return;
- }
-
- ZapAppleStageStore store = ZapAppleStageStore.get(sw);
- ZapAppleStageStore.Stage currentStage = store.getStage();
- if (currentStage != getStage(state)) {
- world.setBlockState(pos, currentStage.getNewState(state));
- }
- }
-
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(STAGE, ZapAppleStageStore.Stage.GREENING);
}
@Override
- protected ZapAppleStageStore.Stage getStage(BlockState state) {
+ public ZapAppleStageStore.Stage getStage(BlockState state) {
return state.get(STAGE);
}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesPlaceholderBlock.java b/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesPlaceholderBlock.java
index 7d30ce32..b2434136 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesPlaceholderBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesPlaceholderBlock.java
@@ -1,15 +1,16 @@
package com.minelittlepony.unicopia.block;
import com.minelittlepony.unicopia.server.world.ZapAppleStageStore;
+import com.minelittlepony.unicopia.server.world.ZapAppleStageStore.Stage;
import com.mojang.serialization.MapCodec;
import net.minecraft.block.*;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random;
-import net.minecraft.world.WorldAccess;
+import net.minecraft.world.World;
-public class ZapAppleLeavesPlaceholderBlock extends AirBlock {
+public class ZapAppleLeavesPlaceholderBlock extends AirBlock implements ZapStagedBlock {
public static final MapCodec CODEC = createCodec(ZapAppleLeavesPlaceholderBlock::new);
ZapAppleLeavesPlaceholderBlock(Settings settings) {
@@ -23,37 +24,19 @@ public class ZapAppleLeavesPlaceholderBlock extends AirBlock {
}
@Override
- public boolean hasRandomTicks(BlockState state) {
- return true;
+ public Stage getStage(BlockState state) {
+ return ZapAppleStageStore.Stage.HIBERNATING;
}
@Override
- public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
-
- if (world instanceof ServerWorld sw) {
- ZapAppleStageStore store = ZapAppleStageStore.get(sw);
- ZapAppleStageStore.Stage currentStage = store.getStage();
- if (currentStage != ZapAppleStageStore.Stage.HIBERNATING) {
- return currentStage.getNewState(state);
- }
- }
-
- return state;
+ public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
+ updateStage(state, world, pos);
}
@Deprecated
@Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.scheduledTick(state, world, pos, random);
-
- ZapAppleStageStore store = ZapAppleStageStore.get(world);
- ZapAppleStageStore.Stage newStage = store.getStage();
- if (!world.isDay() && ZapAppleStageStore.Stage.HIBERNATING.mustChangeIntoInstantly(newStage)) {
- state = newStage.getNewState(state);
- world.setBlockState(pos, state);
- BaseZapAppleLeavesBlock.onStageChanged(store, newStage, world, state, pos, random);
- }
-
- world.scheduleBlockTick(pos, state.getBlock(), 1);
+ tryAdvanceStage(state, world, pos, random);
}
}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/ZapStagedBlock.java b/src/main/java/com/minelittlepony/unicopia/block/ZapStagedBlock.java
new file mode 100644
index 00000000..5e3f41d4
--- /dev/null
+++ b/src/main/java/com/minelittlepony/unicopia/block/ZapStagedBlock.java
@@ -0,0 +1,87 @@
+package com.minelittlepony.unicopia.block;
+
+import com.minelittlepony.unicopia.server.world.ZapAppleStageStore;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.Blocks;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.random.Random;
+import net.minecraft.world.World;
+
+public interface ZapStagedBlock {
+ ZapAppleStageStore.Stage getStage(BlockState state);
+
+ default void updateStage(BlockState state, World world, BlockPos pos) {
+ if (!(world instanceof ServerWorld sw)) {
+ return;
+ }
+ ZapAppleStageStore.Stage currentStage = ZapAppleStageStore.get(sw).getStage();
+ if (currentStage != getStage(state)) {
+ state = getState(currentStage);
+ world.setBlockState(pos, state);
+ }
+ world.scheduleBlockTick(pos, state.getBlock(), 1);
+ }
+
+ default void tryAdvanceStage(BlockState state, ServerWorld world, BlockPos pos, Random random) {
+ ZapAppleStageStore store = ZapAppleStageStore.get(world);
+ ZapAppleStageStore.Stage currentStage = store.getStage();
+ if (!world.isDay() && currentStage != getStage(state)) {
+ int transitionRate = getTransitionRate(currentStage);
+ if (transitionRate == 0 || random.nextInt(transitionRate) == 0) {
+ state = getState(currentStage);
+ world.setBlockState(pos, state);
+ onStageChanged(store, currentStage, world, state, pos, random);
+ }
+ }
+ world.scheduleBlockTick(pos, state.getBlock(), 1);
+ }
+
+ default int getTransitionRate(ZapAppleStageStore.Stage stage) {
+ if (stage == ZapAppleStageStore.Stage.HIBERNATING || stage == ZapAppleStageStore.Stage.GREENING) {
+ return 10;
+ }
+ return 2500;
+ }
+
+ default BlockState getState(ZapAppleStageStore.Stage stage) {
+ if (stage == ZapAppleStageStore.Stage.HIBERNATING) {
+ return UBlocks.ZAP_LEAVES_PLACEHOLDER.getDefaultState();
+ }
+ if (stage == ZapAppleStageStore.Stage.FLOWERING) {
+ return UBlocks.FLOWERING_ZAP_LEAVES.getDefaultState();
+ }
+ return UBlocks.ZAP_LEAVES.getDefaultState().with(ZapAppleLeavesBlock.STAGE, stage);
+ }
+
+ private static void onStageChanged(ZapAppleStageStore store, ZapAppleStageStore.Stage stage, ServerWorld world, BlockState state, BlockPos pos, Random random) {
+ boolean mustFruit = Random.create(state.getRenderingSeed(pos)).nextInt(5) < 2;
+ BlockState below = world.getBlockState(pos.down());
+
+ if (world.isAir(pos.down())) {
+ if (stage == ZapAppleStageStore.Stage.FRUITING && mustFruit) {
+ world.setBlockState(pos.down(), UBlocks.ZAP_BULB.getDefaultState(), Block.NOTIFY_ALL);
+ store.triggerLightningStrike(pos);
+ }
+ }
+
+ if (stage != ZapAppleStageStore.Stage.HIBERNATING && world.getRandom().nextInt(10) == 0) {
+ store.triggerLightningStrike(pos);
+ }
+
+ if (stage == ZapAppleStageStore.Stage.RIPE) {
+ if (below.isOf(UBlocks.ZAP_BULB)) {
+ world.setBlockState(pos.down(), UBlocks.ZAP_APPLE.getDefaultState(), Block.NOTIFY_ALL);
+ store.playMoonEffect(pos);
+ }
+ }
+
+ if (mustFruit && stage == ZapAppleStageStore.Stage.HIBERNATING) {
+ if (below.isOf(UBlocks.ZAP_APPLE) || below.isOf(UBlocks.ZAP_BULB)) {
+ world.setBlockState(pos.down(), Blocks.AIR.getDefaultState());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBedBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBedBlock.java
index 6ecc1497..78f79309 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBedBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBedBlock.java
@@ -4,6 +4,11 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.block.FancyBedBlock;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import net.minecraft.block.BedBlock;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.entity.Entity;
@@ -20,6 +25,12 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class CloudBedBlock extends FancyBedBlock implements CloudLike {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ Codec.STRING.fieldOf("base").forGetter(b -> b.base),
+ BlockState.CODEC.fieldOf("base_state").forGetter(b -> b.baseState),
+ BedBlock.createSettingsCodec()
+ ).apply(instance, CloudBedBlock::new));
+
private final BlockState baseState;
private final CloudBlock baseBlock;
@@ -29,6 +40,12 @@ public class CloudBedBlock extends FancyBedBlock implements CloudLike {
this.baseBlock = (CloudBlock)baseState.getBlock();
}
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public MapCodec getCodec() {
+ return (MapCodec)CODEC;
+ }
+
@Override
public final VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
if (!baseBlock.canInteract(baseState, world, pos, EquineContext.of(context))) {
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBlock.java
index 490d7e4d..5453677d 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBlock.java
@@ -4,7 +4,11 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.entity.player.Pony;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import net.minecraft.block.BedBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
@@ -27,13 +31,23 @@ import net.minecraft.world.LightType;
import net.minecraft.world.World;
public class CloudBlock extends Block implements CloudLike {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ Codec.BOOL.fieldOf("meltable").forGetter(b -> b.meltable),
+ BedBlock.createSettingsCodec()
+ ).apply(instance, CloudBlock::new));
+
protected final boolean meltable;
- public CloudBlock(Settings settings, boolean meltable) {
+ public CloudBlock(boolean meltable, Settings settings) {
super((meltable ? settings.ticksRandomly() : settings).nonOpaque());
this.meltable = meltable;
}
+ @Override
+ public MapCodec extends CloudBlock> getCodec() {
+ return CODEC;
+ }
+
@Override
public void onEntityLand(BlockView world, Entity entity) {
boolean bounce = Math.abs(entity.getVelocity().y) > 0.3;
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudChestBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudChestBlock.java
index 86aa498d..6fa559e3 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudChestBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudChestBlock.java
@@ -6,10 +6,14 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.block.UBlockEntities;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
import net.minecraft.block.BlockState;
import net.minecraft.block.ChestBlock;
import net.minecraft.block.DoubleBlockProperties;
import net.minecraft.block.ShapeContext;
+import net.minecraft.block.StairsBlock;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.block.entity.ChestBlockEntity;
@@ -33,6 +37,10 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class CloudChestBlock extends ChestBlock implements CloudLike {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ BlockState.CODEC.fieldOf("base_state").forGetter(block -> block.baseState),
+ StairsBlock.createSettingsCodec()
+ ).apply(instance, CloudChestBlock::new));
private final BlockState baseState;
private final CloudBlock baseBlock;
@@ -76,12 +84,17 @@ public class CloudChestBlock extends ChestBlock implements CloudLike {
}
};
- public CloudChestBlock(Settings settings, BlockState baseState) {
+ public CloudChestBlock(BlockState baseState, Settings settings) {
super(settings, () -> UBlockEntities.CLOUD_CHEST);
this.baseState = baseState;
this.baseBlock = (CloudBlock)baseState.getBlock();
}
+ @Override
+ public MapCodec extends ChestBlock> getCodec() {
+ return CODEC;
+ }
+
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new TileData(pos, state);
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudDoorBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudDoorBlock.java
index dce7d864..b1749d42 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudDoorBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudDoorBlock.java
@@ -4,6 +4,8 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.Race;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.block.BlockSetType;
import net.minecraft.block.BlockState;
@@ -22,15 +24,24 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class CloudDoorBlock extends DoorBlock implements CloudLike {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ BlockState.CODEC.fieldOf("base_state").forGetter(b -> b.baseState),
+ BlockSetType.CODEC.fieldOf("block_set_type").forGetter(DoorBlock::getBlockSetType),
+ DoorBlock.createSettingsCodec()
+ ).apply(instance, CloudDoorBlock::new));
private final BlockState baseState;
private final CloudBlock baseBlock;
- public CloudDoorBlock(Settings settings, BlockState baseState, BlockSetType blockSet) {
+ public CloudDoorBlock(BlockState baseState, BlockSetType blockSet, Settings settings) {
super(blockSet, settings);
this.baseState = baseState;
this.baseBlock = (CloudBlock)baseState.getBlock();
}
+ @Override
+ public MapCodec extends CloudDoorBlock> getCodec() {
+ return CODEC;
+ }
@Override
public final VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudPillarBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudPillarBlock.java
index 5655b652..722b4d60 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudPillarBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudPillarBlock.java
@@ -5,6 +5,7 @@ import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
+import com.mojang.serialization.MapCodec;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
@@ -20,6 +21,7 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.WorldAccess;
public class CloudPillarBlock extends CloudBlock {
+ private static final MapCodec CODEC = Block.createCodec(CloudPillarBlock::new);
private static final BooleanProperty NORTH = BooleanProperty.of("north");
private static final BooleanProperty SOUTH = BooleanProperty.of("south");
private static final Map DIRECTION_PROPERTIES = Map.of(
@@ -41,10 +43,15 @@ public class CloudPillarBlock extends CloudBlock {
// [1,0] [1,1]
public CloudPillarBlock(Settings settings) {
- super(settings, false);
+ super(false, settings);
setDefaultState(getDefaultState().with(NORTH, true).with(SOUTH, true));
}
+ @Override
+ public MapCodec getCodec() {
+ return CODEC;
+ }
+
@Override
protected void appendProperties(StateManager.Builder builder) {
builder.add(NORTH, SOUTH);
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudSlabBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudSlabBlock.java
index e1204745..c445809b 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudSlabBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudSlabBlock.java
@@ -5,7 +5,12 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
+import com.minelittlepony.unicopia.util.CodecUtils;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import net.minecraft.block.BedBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
@@ -24,14 +29,24 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.WorldAccess;
public class CloudSlabBlock extends WaterloggableCloudBlock {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ Codec.BOOL.fieldOf("meltable").forGetter(b -> b.meltable),
+ CodecUtils.supplierOf(Soakable.CODEC).optionalFieldOf("soggy_block", null).forGetter(b -> b.soggyBlock),
+ BedBlock.createSettingsCodec()
+ ).apply(instance, WaterloggableCloudBlock::new));
private static final VoxelShape BOTTOM_SHAPE = Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, 8.0, 16.0);
private static final VoxelShape TOP_SHAPE = Block.createCuboidShape(0.0, 8.0, 0.0, 16.0, 16.0, 16.0);
- public CloudSlabBlock(Settings settings, boolean meltable, @Nullable Supplier soggyBlock) {
- super(settings, meltable, soggyBlock);
+ public CloudSlabBlock(boolean meltable, @Nullable Supplier soggyBlock, Settings settings) {
+ super(meltable, soggyBlock, settings);
setDefaultState(getDefaultState().with(SlabBlock.TYPE, SlabType.BOTTOM));
}
+ @Override
+ public MapCodec extends WaterloggableCloudBlock> getCodec() {
+ return CODEC;
+ }
+
@Override
public boolean hasSidedTransparency(BlockState state) {
return state.get(SlabBlock.TYPE) != SlabType.DOUBLE;
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudStairsBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudStairsBlock.java
index bafd9e21..1c127f92 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudStairsBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudStairsBlock.java
@@ -3,6 +3,9 @@ package com.minelittlepony.unicopia.block.cloud;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.StairsBlock;
@@ -17,6 +20,10 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class CloudStairsBlock extends StairsBlock implements CloudLike {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ BlockState.CODEC.fieldOf("base_state").forGetter(block -> block.baseBlockState),
+ StairsBlock.createSettingsCodec()
+ ).apply(instance, CloudStairsBlock::new));
private final CloudBlock baseBlock;
@@ -25,6 +32,11 @@ public class CloudStairsBlock extends StairsBlock implements CloudLike {
this.baseBlock = (CloudBlock)baseState.getBlock();
}
+ @Override
+ public MapCodec extends StairsBlock> getCodec() {
+ return CODEC;
+ }
+
@Override
public void onEntityLand(BlockView world, Entity entity) {
baseBlock.onEntityLand(world, entity);
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/CompactedCloudBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/CompactedCloudBlock.java
index 50d27fe1..86456378 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/CompactedCloudBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/CompactedCloudBlock.java
@@ -5,6 +5,8 @@ import java.util.Map;
import java.util.function.Function;
import com.minelittlepony.unicopia.EquineContext;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
@@ -32,6 +34,9 @@ import net.minecraft.world.World;
import net.minecraft.world.WorldView;
public class CompactedCloudBlock extends CloudBlock {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ BlockState.CODEC.fieldOf("base_state").forGetter(block -> block.baseState)
+ ).apply(instance, CompactedCloudBlock::new));
static final Map FACING_PROPERTIES = ConnectingBlock.FACING_PROPERTIES;
static final Collection PROPERTIES = FACING_PROPERTIES.values();
@@ -49,13 +54,18 @@ public class CompactedCloudBlock extends CloudBlock {
private final BlockState baseState;
public CompactedCloudBlock(BlockState baseState) {
- super(Settings.copy(baseState.getBlock()).dropsLike(baseState.getBlock()), true);
+ super(true, Settings.copy(baseState.getBlock()).dropsLike(baseState.getBlock()));
this.baseState = baseState;
PROPERTIES.forEach(property -> {
setDefaultState(getDefaultState().with(property, true));
});
}
+ @Override
+ public MapCodec getCodec() {
+ return CODEC;
+ }
+
@Override
public ItemStack getPickStack(WorldView world, BlockPos pos, BlockState state) {
return baseState.getBlock().getPickStack(world, pos, baseState);
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/NaturalCloudBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/NaturalCloudBlock.java
index 2d7325dc..de0ec27e 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/NaturalCloudBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/NaturalCloudBlock.java
@@ -4,10 +4,17 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
+import com.minelittlepony.unicopia.util.CodecUtils;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import net.minecraft.block.BedBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
+import net.minecraft.registry.Registries;
import net.minecraft.registry.tag.ItemTags;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
@@ -19,16 +26,28 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class NaturalCloudBlock extends PoreousCloudBlock {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ Codec.BOOL.fieldOf("meltable").forGetter(b -> b.meltable),
+ CodecUtils.supplierOf(Soakable.CODEC).optionalFieldOf("soggy_block", null).forGetter(b -> b.soggyBlock),
+ CodecUtils.supplierOf(Registries.BLOCK.getCodec()).fieldOf("compacted_block").forGetter(b -> b.compactedBlock),
+ BedBlock.createSettingsCodec()
+ ).apply(instance, NaturalCloudBlock::new));
private final Supplier compactedBlock;
- public NaturalCloudBlock(Settings settings, boolean meltable,
+ public NaturalCloudBlock(boolean meltable,
@Nullable Supplier soggyBlock,
- Supplier compactedBlock) {
- super(settings.nonOpaque(), meltable, soggyBlock);
+ Supplier compactedBlock,
+ Settings settings) {
+ super(meltable, soggyBlock, settings.nonOpaque());
this.compactedBlock = compactedBlock;
}
+ @Override
+ public MapCodec getCodec() {
+ return CODEC;
+ }
+
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ItemStack stack = player.getStackInHand(hand);
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/OrientedCloudBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/OrientedCloudBlock.java
index 2d3886db..52cd3d79 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/OrientedCloudBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/OrientedCloudBlock.java
@@ -1,7 +1,11 @@
package com.minelittlepony.unicopia.block.cloud;
import com.minelittlepony.unicopia.EquineContext;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import net.minecraft.block.BedBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemPlacementContext;
@@ -13,13 +17,22 @@ import net.minecraft.util.BlockRotation;
import net.minecraft.util.math.Direction;
public class OrientedCloudBlock extends CloudBlock {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ Codec.BOOL.fieldOf("meltable").forGetter(b -> b.meltable),
+ BedBlock.createSettingsCodec()
+ ).apply(instance, OrientedCloudBlock::new));
public static final DirectionProperty FACING = Properties.FACING;
- public OrientedCloudBlock(Settings settings, boolean meltable) {
- super(settings, meltable);
+ public OrientedCloudBlock(boolean meltable, Settings settings) {
+ super(meltable, settings);
this.setDefaultState(getDefaultState().with(FACING, Direction.UP));
}
+ @Override
+ public MapCodec extends CloudBlock> getCodec() {
+ return CODEC;
+ }
+
@Override
protected void appendProperties(StateManager.Builder builder) {
builder.add(FACING);
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/PoreousCloudBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/PoreousCloudBlock.java
index 5e17ca06..fdc1aaa6 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/PoreousCloudBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/PoreousCloudBlock.java
@@ -4,25 +4,43 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
+import com.minelittlepony.unicopia.block.state.StateUtil;
+import com.minelittlepony.unicopia.util.CodecUtils;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import net.minecraft.block.BedBlock;
import net.minecraft.block.BlockState;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random;
public class PoreousCloudBlock extends CloudBlock implements Soakable {
- @Nullable
- private final Supplier soggyBlock;
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ Codec.BOOL.fieldOf("meltable").forGetter(b -> b.meltable),
+ CodecUtils.supplierOf(Soakable.CODEC).optionalFieldOf("soggy_block", null).forGetter(b -> b.soggyBlock),
+ BedBlock.createSettingsCodec()
+ ).apply(instance, PoreousCloudBlock::new));
- public PoreousCloudBlock(Settings settings, boolean meltable, @Nullable Supplier soggyBlock) {
- super(settings.nonOpaque(), meltable);
+ @Nullable
+ protected final Supplier soggyBlock;
+
+ public PoreousCloudBlock(boolean meltable, @Nullable Supplier soggyBlock, Settings settings) {
+ super(meltable, settings.nonOpaque());
this.soggyBlock = soggyBlock;
}
+ @Override
+ public MapCodec extends PoreousCloudBlock> getCodec() {
+ return CODEC;
+ }
+
@Nullable
@Override
public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) {
- return Soakable.copyProperties(state, getDefaultState());
+ return StateUtil.copyState(state, getDefaultState());
}
return soggyBlock == null ? null : soggyBlock.get().getStateWithMoisture(state, moisture);
}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/PoreousCloudStairsBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/PoreousCloudStairsBlock.java
index 6733ee2d..1ff03471 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/PoreousCloudStairsBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/PoreousCloudStairsBlock.java
@@ -4,22 +4,38 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
+import com.minelittlepony.unicopia.block.state.StateUtil;
+import com.minelittlepony.unicopia.util.CodecUtils;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
import net.minecraft.block.BlockState;
+import net.minecraft.block.StairsBlock;
public class PoreousCloudStairsBlock extends CloudStairsBlock implements Soakable {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ BlockState.CODEC.fieldOf("base_state").forGetter(block -> block.baseBlockState),
+ CodecUtils.supplierOf(Soakable.CODEC).optionalFieldOf("soggy_block", null).forGetter(b -> b.soggyBlock),
+ StairsBlock.createSettingsCodec()
+ ).apply(instance, PoreousCloudStairsBlock::new));
protected final Supplier soggyBlock;
- public PoreousCloudStairsBlock(BlockState baseState, Settings settings, Supplier soggyBlock) {
+ public PoreousCloudStairsBlock(BlockState baseState, Supplier soggyBlock, Settings settings) {
super(baseState, settings);
this.soggyBlock = soggyBlock;
}
+ @Override
+ public MapCodec extends PoreousCloudStairsBlock> getCodec() {
+ return CODEC;
+ }
+
@Nullable
@Override
public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) {
- return Soakable.copyProperties(state, getDefaultState());
+ return StateUtil.copyState(state, getDefaultState());
}
return soggyBlock.get().getStateWithMoisture(state, moisture);
}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/ShapingBenchBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/ShapingBenchBlock.java
index 06c47ceb..4e3d7a68 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/ShapingBenchBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/ShapingBenchBlock.java
@@ -4,6 +4,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.container.ShapingBenchScreenHandler;
+import com.mojang.serialization.MapCodec;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
@@ -23,6 +24,7 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class ShapingBenchBlock extends CloudBlock {
+ private static final MapCodec CODEC = Block.createCodec(ShapingBenchBlock::new);
private static final VoxelShape SHAPE = VoxelShapes.union(
Block.createCuboidShape(0, 13, 0, 3, 18, 3),
Block.createCuboidShape(13, 13, 0, 16, 18, 3),
@@ -34,7 +36,12 @@ public class ShapingBenchBlock extends CloudBlock {
);
public ShapingBenchBlock(Settings settings) {
- super(settings, false);
+ super(false, settings);
+ }
+
+ @Override
+ public MapCodec getCodec() {
+ return CODEC;
}
@Override
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/Soakable.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/Soakable.java
index e800bbf8..866d5a29 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/Soakable.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/Soakable.java
@@ -1,21 +1,22 @@
package com.minelittlepony.unicopia.block.cloud;
import java.util.Arrays;
-
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds;
+import com.mojang.serialization.Codec;
+import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.particle.ParticleTypes;
+import net.minecraft.registry.Registries;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.state.property.IntProperty;
-import net.minecraft.state.property.Property;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Util;
@@ -27,6 +28,8 @@ import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
public interface Soakable {
+ Codec CODEC = Registries.BLOCK.getCodec().xmap(b -> (Soakable)b, s -> (Block)s);
+
IntProperty MOISTURE = IntProperty.of("moisture", 1, 7);
Direction[] DIRECTIONS = Arrays.stream(Direction.values()).filter(d -> d != Direction.UP).toArray(Direction[]::new);
@@ -105,15 +108,4 @@ public interface Soakable {
world.setBlockState(pos, soakable.getStateWithMoisture(state, newMoisture));
world.playSound(null, pos, SoundEvents.ENTITY_SALMON_FLOP, SoundCategory.BLOCKS, 1, (float)world.random.nextTriangular(0.5, 0.3F));
}
-
- @Nullable
- @SuppressWarnings({ "rawtypes", "unchecked" })
- static BlockState copyProperties(BlockState from, @Nullable BlockState to) {
- if (to != null) {
- for (Property property : from.getProperties()) {
- to = to.withIfExists(property, from.get(property));
- }
- }
- return to;
- }
}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudBlock.java
index 94b713ee..bb177f18 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudBlock.java
@@ -4,10 +4,17 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
+import com.minelittlepony.unicopia.block.state.StateUtil;
+import com.minelittlepony.unicopia.util.CodecUtils;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import net.minecraft.block.BedBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
+import net.minecraft.registry.Registries;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
@@ -19,15 +26,24 @@ import net.minecraft.world.World;
import net.minecraft.world.WorldView;
public class SoggyCloudBlock extends CloudBlock implements Soakable {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ CodecUtils.supplierOf(Registries.BLOCK.getCodec()).fieldOf("dry_block").forGetter(b -> b.dryBlock),
+ BedBlock.createSettingsCodec()
+ ).apply(instance, SoggyCloudBlock::new));
private final Supplier dryBlock;
- public SoggyCloudBlock(Settings settings, Supplier dryBlock) {
- super(settings.ticksRandomly(), false);
+ public SoggyCloudBlock(Supplier dryBlock, Settings settings) {
+ super(false, settings.ticksRandomly());
setDefaultState(getDefaultState().with(MOISTURE, 7));
this.dryBlock = dryBlock;
}
+ @Override
+ public MapCodec extends SoggyCloudBlock> getCodec() {
+ return CODEC;
+ }
+
@Override
protected void appendProperties(StateManager.Builder builder) {
super.appendProperties(builder);
@@ -43,9 +59,9 @@ public class SoggyCloudBlock extends CloudBlock implements Soakable {
@Override
public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) {
- return Soakable.copyProperties(state, dryBlock.get().getDefaultState());
+ return StateUtil.copyState(state, dryBlock.get().getDefaultState());
}
- return Soakable.copyProperties(state, getDefaultState()).with(MOISTURE, moisture);
+ return StateUtil.copyState(state, getDefaultState()).with(MOISTURE, moisture);
}
@Override
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudSlabBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudSlabBlock.java
index c858c82c..6867ce41 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudSlabBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudSlabBlock.java
@@ -4,10 +4,17 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
+import com.minelittlepony.unicopia.block.state.StateUtil;
+import com.minelittlepony.unicopia.util.CodecUtils;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import net.minecraft.block.BedBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
+import net.minecraft.registry.Registries;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
@@ -19,15 +26,24 @@ import net.minecraft.world.World;
import net.minecraft.world.WorldView;
public class SoggyCloudSlabBlock extends CloudSlabBlock {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ CodecUtils.supplierOf(Registries.BLOCK.getCodec()).fieldOf("dry_block").forGetter(b -> b.dryBlock),
+ BedBlock.createSettingsCodec()
+ ).apply(instance, SoggyCloudSlabBlock::new));
private final Supplier dryBlock;
- public SoggyCloudSlabBlock(Settings settings, Supplier dryBlock) {
- super(settings.ticksRandomly(), false, null);
+ public SoggyCloudSlabBlock(Supplier dryBlock, Settings settings) {
+ super(false, null, settings.ticksRandomly());
setDefaultState(getDefaultState().with(MOISTURE, 7));
this.dryBlock = dryBlock;
}
+ @Override
+ public MapCodec extends SoggyCloudSlabBlock> getCodec() {
+ return CODEC;
+ }
+
@Override
protected void appendProperties(StateManager.Builder builder) {
super.appendProperties(builder);
@@ -43,9 +59,9 @@ public class SoggyCloudSlabBlock extends CloudSlabBlock {
@Override
public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) {
- return Soakable.copyProperties(state, dryBlock.get().getDefaultState());
+ return StateUtil.copyState(state, dryBlock.get().getDefaultState());
}
- return Soakable.copyProperties(state, getDefaultState()).with(MOISTURE, moisture);
+ return StateUtil.copyState(state, getDefaultState()).with(MOISTURE, moisture);
}
@Override
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudStairsBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudStairsBlock.java
index 3a5b973a..fe3da5ea 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudStairsBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/SoggyCloudStairsBlock.java
@@ -4,23 +4,40 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
+import com.minelittlepony.unicopia.block.state.StateUtil;
+import com.minelittlepony.unicopia.util.CodecUtils;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
+import net.minecraft.block.StairsBlock;
import net.minecraft.item.ItemStack;
+import net.minecraft.registry.Registries;
import net.minecraft.state.StateManager;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.WorldView;
public class SoggyCloudStairsBlock extends CloudStairsBlock implements Soakable {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ BlockState.CODEC.fieldOf("base_state").forGetter(block -> block.baseBlockState),
+ CodecUtils.supplierOf(Registries.BLOCK.getCodec()).optionalFieldOf("soggy_block", null).forGetter(b -> b.dryBlock),
+ StairsBlock.createSettingsCodec()
+ ).apply(instance, SoggyCloudStairsBlock::new));
private final Supplier dryBlock;
- public SoggyCloudStairsBlock(BlockState baseState, Settings settings, Supplier dryBlock) {
+ public SoggyCloudStairsBlock(BlockState baseState, Supplier dryBlock, Settings settings) {
super(baseState, settings);
setDefaultState(getDefaultState().with(MOISTURE, 7));
this.dryBlock = dryBlock;
}
+ @Override
+ public MapCodec extends SoggyCloudStairsBlock> getCodec() {
+ return CODEC;
+ }
+
@Override
protected void appendProperties(StateManager.Builder builder) {
super.appendProperties(builder);
@@ -36,8 +53,8 @@ public class SoggyCloudStairsBlock extends CloudStairsBlock implements Soakable
@Override
public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) {
- return Soakable.copyProperties(state, dryBlock.get().getDefaultState());
+ return StateUtil.copyState(state, dryBlock.get().getDefaultState());
}
- return Soakable.copyProperties(state, getDefaultState()).with(MOISTURE, moisture);
+ return StateUtil.copyState(state, getDefaultState()).with(MOISTURE, moisture);
}
}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/UnstableCloudBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/UnstableCloudBlock.java
index 30bf49c4..7f552eba 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/UnstableCloudBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/UnstableCloudBlock.java
@@ -5,6 +5,7 @@ import java.util.Optional;
import com.minelittlepony.unicopia.entity.mob.StormCloudEntity;
import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils;
+import com.mojang.serialization.MapCodec;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
@@ -28,14 +29,20 @@ import net.minecraft.world.poi.PointOfInterestStorage;
import net.minecraft.world.poi.PointOfInterestTypes;
public class UnstableCloudBlock extends CloudBlock {
+ private static final MapCodec CODEC = Block.createCodec(UnstableCloudBlock::new);
private static final int MAX_CHARGE = 6;
private static final IntProperty CHARGE = IntProperty.of("charge", 0, MAX_CHARGE);
public UnstableCloudBlock(Settings settings) {
- super(settings, false);
+ super(false, settings);
setDefaultState(getDefaultState().with(CHARGE, 0));
}
+ @Override
+ public MapCodec getCodec() {
+ return CODEC;
+ }
+
@Override
protected void appendProperties(StateManager.Builder builder) {
builder.add(CHARGE);
diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/WaterloggableCloudBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/WaterloggableCloudBlock.java
index 38efed62..915ea406 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/cloud/WaterloggableCloudBlock.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/WaterloggableCloudBlock.java
@@ -5,7 +5,12 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
+import com.minelittlepony.unicopia.util.CodecUtils;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import net.minecraft.block.BedBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Waterloggable;
@@ -23,13 +28,24 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.WorldAccess;
public class WaterloggableCloudBlock extends PoreousCloudBlock implements Waterloggable {
+ private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
+ Codec.BOOL.fieldOf("meltable").forGetter(b -> b.meltable),
+ CodecUtils.supplierOf(Soakable.CODEC).optionalFieldOf("soggy_block", null).forGetter(b -> b.soggyBlock),
+ BedBlock.createSettingsCodec()
+ ).apply(instance, WaterloggableCloudBlock::new));
+
public static final BooleanProperty WATERLOGGED = Properties.WATERLOGGED;
- public WaterloggableCloudBlock(Settings settings, boolean meltable, @Nullable Supplier soggyBlock) {
- super(settings, meltable, soggyBlock);
+ public WaterloggableCloudBlock(boolean meltable, @Nullable Supplier soggyBlock, Settings settings) {
+ super(meltable, soggyBlock, settings);
setDefaultState(getDefaultState().with(WATERLOGGED, false));
}
+ @Override
+ public MapCodec extends WaterloggableCloudBlock> getCodec() {
+ return CODEC;
+ }
+
@Override
protected void appendProperties(StateManager.Builder builder) {
builder.add(WATERLOGGED);
diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/StateChange.java b/src/main/java/com/minelittlepony/unicopia/block/state/StateChange.java
index 72e7b9e4..972eef4f 100644
--- a/src/main/java/com/minelittlepony/unicopia/block/state/StateChange.java
+++ b/src/main/java/com/minelittlepony/unicopia/block/state/StateChange.java
@@ -11,7 +11,6 @@ import com.minelittlepony.unicopia.Unicopia;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
-import net.minecraft.state.property.Property;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.registry.Registries;
@@ -50,7 +49,7 @@ public abstract class StateChange {
return state;
}
return Registries.BLOCK.getOrEmpty(id).map(Block::getDefaultState)
- .map(newState -> merge(newState, state))
+ .map(newState -> StateUtil.copyState(state, newState))
.orElse(state);
}
};
@@ -101,17 +100,4 @@ public abstract class StateChange {
return serializer.apply(json);
}).orElseThrow(() -> new IllegalArgumentException("Invalid action " + action));
}
-
- private static BlockState merge(BlockState into, BlockState from) {
- for (var property : from.getProperties()) {
- if (into.contains(property)) {
- into = copy(into, from, property);
- }
- }
- return into;
- }
-
- private static > BlockState copy(BlockState to, BlockState from, Property property) {
- return to.with(property, from.get(property));
- }
}
diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/StateUtil.java b/src/main/java/com/minelittlepony/unicopia/block/state/StateUtil.java
new file mode 100644
index 00000000..42334368
--- /dev/null
+++ b/src/main/java/com/minelittlepony/unicopia/block/state/StateUtil.java
@@ -0,0 +1,19 @@
+package com.minelittlepony.unicopia.block.state;
+
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.block.BlockState;
+import net.minecraft.state.property.Property;
+
+public interface StateUtil {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ static BlockState copyState(BlockState from, @Nullable BlockState to) {
+ if (to == null) {
+ return to;
+ }
+ for (var property : from.getProperties()) {
+ to = to.withIfExists((Property)property, from.get(property));
+ }
+ return to;
+ }
+}
diff --git a/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java b/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java
index 0be0da61..837b65fd 100644
--- a/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java
+++ b/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java
@@ -2,7 +2,7 @@ package com.minelittlepony.unicopia.client;
import java.lang.ref.WeakReference;
import java.util.Map;
-import java.util.Optional;
+import java.util.UUID;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -13,19 +13,20 @@ import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.FlightType;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.USounds;
-import com.minelittlepony.unicopia.ability.magic.CasterView;
import com.minelittlepony.unicopia.client.gui.DismissSpellScreen;
import com.minelittlepony.unicopia.client.gui.spellbook.ClientChapters;
+import com.minelittlepony.unicopia.client.particle.ClientBoundParticleSpawner;
import com.minelittlepony.unicopia.client.sound.*;
import com.minelittlepony.unicopia.entity.player.PlayerPhysics;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.entity.player.dummy.DummyClientPlayerEntity;
-import com.minelittlepony.unicopia.server.world.Ether;
+import com.minelittlepony.unicopia.particle.ParticleSpawner;
import com.mojang.authlib.GameProfile;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.sound.AggressiveBeeSoundInstance;
import net.minecraft.client.sound.MovingMinecartSoundInstance;
import net.minecraft.client.sound.PassiveBeeSoundInstance;
@@ -37,32 +38,21 @@ import net.minecraft.entity.passive.BeeEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.vehicle.AbstractMinecartEntity;
import net.minecraft.network.PacketByteBuf;
-import net.minecraft.server.world.ServerWorld;
+import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.sound.SoundCategory;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.random.Random;
-import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class ClientInteractionManager extends InteractionManager {
private final MinecraftClient client = MinecraftClient.getInstance();
- private final Optional clientWorld = Optional.of(() -> MinecraftClient.getInstance().world);
-
private final Int2ObjectMap> playingSounds = new Int2ObjectOpenHashMap<>();
- @Override
- public Optional getCasterView(BlockView view) {
- if (view instanceof ServerWorld world) {
- return Optional.of(Ether.get(world));
- }
- return clientWorld;
- }
-
@Override
public Map readChapters(PacketByteBuf buffer) {
- return buffer.readMap(PacketByteBuf::readIdentifier, ClientChapters::loadChapter);
+ return buffer.readMap(PacketByteBuf::readIdentifier, ClientChapters::loadChapter);
}
@Override
@@ -159,4 +149,16 @@ public class ClientInteractionManager extends InteractionManager {
public int getViewMode() {
return client.options.getPerspective().ordinal();
}
+
+ @Override
+ public ParticleSpawner createBoundParticle(UUID id) {
+ return new ClientBoundParticleSpawner(id);
+ }
+
+ @Override
+ public void sendPlayerLookAngles(PlayerEntity player) {
+ if (player instanceof ClientPlayerEntity c) {
+ c.networkHandler.sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(player.getYaw(), player.getPitch(), player.isOnGround()));
+ }
+ }
}
diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java
index 5bf94a22..ab09e503 100644
--- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java
+++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java
@@ -10,6 +10,8 @@ import com.minelittlepony.unicopia.block.cloud.CloudChestBlock;
import com.minelittlepony.unicopia.client.particle.ChangelingMagicParticle;
import com.minelittlepony.unicopia.client.particle.CloudsEscapingParticle;
import com.minelittlepony.unicopia.client.particle.DiskParticle;
+import com.minelittlepony.unicopia.client.particle.DustCloudParticle;
+import com.minelittlepony.unicopia.client.particle.FloatingBubbleParticle;
import com.minelittlepony.unicopia.client.particle.GroundPoundParticle;
import com.minelittlepony.unicopia.client.particle.HealthDrainParticle;
import com.minelittlepony.unicopia.client.particle.LightningBoltParticle;
@@ -22,6 +24,7 @@ import com.minelittlepony.unicopia.client.particle.ShockwaveParticle;
import com.minelittlepony.unicopia.client.particle.SphereParticle;
import com.minelittlepony.unicopia.client.render.*;
import com.minelittlepony.unicopia.client.render.entity.*;
+import com.minelittlepony.unicopia.client.render.shader.UShaders;
import com.minelittlepony.unicopia.client.render.spell.SpellRendererFactory;
import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.item.ChameleonItem;
@@ -59,12 +62,14 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView;
+@SuppressWarnings("deprecation")
public interface URenderers {
BlockEntity CHEST_RENDER_ENTITY = new CloudChestBlock.TileData(BlockPos.ORIGIN, UBlocks.CLOUD_CHEST.getDefaultState());
static void bootstrap() {
ParticleFactoryRegistry.getInstance().register(UParticles.UNICORN_MAGIC, createFactory(MagicParticle::new));
ParticleFactoryRegistry.getInstance().register(UParticles.CHANGELING_MAGIC, createFactory(ChangelingMagicParticle::new));
+ ParticleFactoryRegistry.getInstance().register(UParticles.BUBBLE, createFactory(FloatingBubbleParticle::new));
ParticleFactoryRegistry.getInstance().register(UParticles.RAIN_DROPS, createFactory(RaindropsParticle::new));
ParticleFactoryRegistry.getInstance().register(UParticles.HEALTH_DRAIN, createFactory(HealthDrainParticle::create));
ParticleFactoryRegistry.getInstance().register(UParticles.RAINBOOM_RING, RainboomParticle::new);
@@ -76,6 +81,7 @@ public interface URenderers {
ParticleFactoryRegistry.getInstance().register(UParticles.GROUND_POUND, GroundPoundParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.CLOUDS_ESCAPING, CloudsEscapingParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.LIGHTNING_BOLT, LightningBoltParticle::new);
+ ParticleFactoryRegistry.getInstance().register(UParticles.DUST_CLOUD, DustCloudParticle::new);
AccessoryFeatureRenderer.register(
BraceletFeatureRenderer::new, AmuletFeatureRenderer::new, GlassesFeatureRenderer::new,
@@ -97,6 +103,8 @@ public interface URenderers {
EntityRendererRegistry.register(UEntities.AIR_BALLOON, AirBalloonEntityRenderer::new);
EntityRendererRegistry.register(UEntities.FRIENDLY_CREEPER, FriendlyCreeperEntityRenderer::new);
EntityRendererRegistry.register(UEntities.LOOT_BUG, LootBugEntityRenderer::new);
+ EntityRendererRegistry.register(UEntities.TENTACLE, TentacleEntityRenderer::new);
+ EntityRendererRegistry.register(UEntities.IGNOMINIOUS_BULB, IgnominiousBulbEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.FANCY_BED, CloudBedBlockEntityRenderer::new);
@@ -108,6 +116,7 @@ public interface URenderers {
PolearmRenderer.register(UItems.WOODEN_POLEARM, UItems.STONE_POLEARM, UItems.IRON_POLEARM, UItems.GOLDEN_POLEARM, UItems.DIAMOND_POLEARM, UItems.NETHERITE_POLEARM);
ModelPredicateProviderRegistry.register(UItems.GEMSTONE, new Identifier("affinity"), (stack, world, entity, seed) -> EnchantableItem.isEnchanted(stack) ? EnchantableItem.getSpellKey(stack).getAffinity().getAlignment() : 0);
ModelPredicateProviderRegistry.register(UItems.ROCK_CANDY, new Identifier("count"), (stack, world, entity, seed) -> stack.getCount() / (float)stack.getMaxCount());
+ ModelPredicateProviderRegistry.register(Unicopia.id("zap_cycle"), (stack, world, entity, seed) -> UnicopiaClient.getInstance().getZapStageDelta());
ColorProviderRegistry.BLOCK.register(URenderers::getTintedBlockColor, TintedBlock.REGISTRY.stream().toArray(Block[]::new));
ColorProviderRegistry.ITEM.register((stack, i) -> getTintedBlockColor(Block.getBlockFromItem(stack.getItem()).getDefaultState(), null, null, i), TintedBlock.REGISTRY.stream().map(Block::asItem).filter(i -> i != Items.AIR).toArray(Item[]::new));
@@ -123,6 +132,7 @@ public interface URenderers {
TerraformBoatClientHelper.registerModelLayers(Unicopia.id("palm"), false);
SpellRendererFactory.bootstrap();
+ UShaders.bootstrap();
}
private static void register(DynamicItemRenderer renderer, ItemConvertible...items) {
diff --git a/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java b/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java
index 32b01a8e..9ea6a700 100644
--- a/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java
+++ b/src/main/java/com/minelittlepony/unicopia/client/UnicopiaClient.java
@@ -22,6 +22,7 @@ import com.minelittlepony.unicopia.entity.player.PlayerCamera;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.handler.ClientNetworkHandlerImpl;
import com.minelittlepony.unicopia.server.world.WeatherConditions;
+import com.minelittlepony.unicopia.server.world.ZapAppleStageStore;
import com.minelittlepony.unicopia.util.Lerp;
import net.fabricmc.api.ClientModInitializer;
@@ -53,6 +54,9 @@ public class UnicopiaClient implements ClientModInitializer {
public final Lerp tangentalSkyAngle = new Lerp(0, true);
public final Lerp skyAngle = new Lerp(0, true);
+ private ZapAppleStageStore.Stage zapAppleStage = ZapAppleStageStore.Stage.HIBERNATING;
+ private long zapStageTime;
+
public static Optional getCamera() {
PlayerEntity player = MinecraftClient.getInstance().player;
@@ -84,6 +88,15 @@ public class UnicopiaClient implements ClientModInitializer {
instance = this;
}
+ public void setZapAppleStage(ZapAppleStageStore.Stage stage, long delta) {
+ zapAppleStage = stage;
+ zapStageTime = delta;
+ }
+
+ public float getZapStageDelta() {
+ return zapAppleStage.getCycleProgress(zapStageTime);
+ }
+
public float getSkyAngleDelta(float tickDelta) {
if (MinecraftClient.getInstance().world == null) {
return 0;
@@ -135,6 +148,8 @@ public class UnicopiaClient implements ClientModInitializer {
world.setRainGradient(gradient);
world.setThunderGradient(gradient);
}
+
+ zapStageTime++;
}
private Float getTargetRainGradient(ClientWorld world, BlockPos pos, float tickDelta) {
diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/MagicText.java b/src/main/java/com/minelittlepony/unicopia/client/gui/MagicText.java
new file mode 100644
index 00000000..4387217f
--- /dev/null
+++ b/src/main/java/com/minelittlepony/unicopia/client/gui/MagicText.java
@@ -0,0 +1,16 @@
+package com.minelittlepony.unicopia.client.gui;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.util.math.MathHelper;
+
+public interface MagicText {
+ static int getColor() {
+ MinecraftClient client = MinecraftClient.getInstance();
+ float ticks = client.player.age + client.getTickDelta();
+
+ float sin = (MathHelper.sin(ticks / 10F) + 1) * 155 * 0.25F;
+ float cos = (MathHelper.cos((ticks + 10) / 10F) + 1) * 155 * 0.25F;
+
+ return (int)(sin + cos);
+ }
+}
diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/ParagraphWrappingVisitor.java b/src/main/java/com/minelittlepony/unicopia/client/gui/ParagraphWrappingVisitor.java
index 0a947e9c..66f38d23 100644
--- a/src/main/java/com/minelittlepony/unicopia/client/gui/ParagraphWrappingVisitor.java
+++ b/src/main/java/com/minelittlepony/unicopia/client/gui/ParagraphWrappingVisitor.java
@@ -2,7 +2,6 @@ package com.minelittlepony.unicopia.client.gui;
import java.util.*;
import java.util.function.BiConsumer;
-
import it.unimi.dsi.fastutil.ints.Int2IntFunction;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextHandler;
@@ -31,12 +30,18 @@ public class ParagraphWrappingVisitor implements StyledVisitor