Merge branch '1.20.2' into 1.20.4

# Conflicts:
#	src/main/java/com/minelittlepony/unicopia/block/BaseZapAppleLeavesBlock.java
#	src/main/java/com/minelittlepony/unicopia/block/UBlocks.java
#	src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesBlock.java
#	src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesPlaceholderBlock.java
#	src/main/java/com/minelittlepony/unicopia/container/SpellbookChapterLoader.java
#	src/main/java/com/minelittlepony/unicopia/entity/mob/SpellbookEntity.java
#	src/main/java/com/minelittlepony/unicopia/projectile/PhysicsBodyProjectileEntity.java
This commit is contained in:
Sollace 2024-02-02 22:35:42 +00:00
commit 398ed0743d
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
487 changed files with 9510 additions and 2897 deletions

View file

@ -3,7 +3,7 @@
[![ru](https://img.shields.io/badge/lang-ru-d52b1e.svg)](README_RU.md) [![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) [![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. 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 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 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 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 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 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 ### Earth Ponies
- Kicking & Stomping - 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! 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, 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 - 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 ### Pegasi / Bat Ponies
@ -99,7 +99,7 @@ also respond to quick, short single-taps, or double-taps.
- Hanging of Ceilings - 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 - 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! 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. 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 ## 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: 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 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! 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 ### Muffins
They're bouncy and delicious, and pigs absolutely love them! They're bouncy and delicious, and pigs absolutely love them!
<!-- https://media.tenor.com/GTYRNMAuLIUAAAAC/muffin-button-muffins.gif -->

View file

@ -11,7 +11,7 @@
Bringing the magic of friendship to Minecraft! 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 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! 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 - *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! 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. 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. 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. 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? 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) - Airflow is simulated (badly)
Pegasi, beware about flying during storms! It can get dangerous out there! 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 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 that the direction and strength is situational (and bad), and will be different depending where you are and
how high up you are. how high up you are.

80
assets/accretion_disk.svg Normal file
View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="793.70081"
height="1122.5197"
viewBox="0 0 210 297"
version="1.1"
id="svg8"
sodipodi:docname="accretion_disk.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<defs
id="defs2">
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1006"
x="-0.047823932"
width="1.0956479"
y="-0.048177369"
height="1.0963547">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="11.80918"
id="feGaussianBlur1008" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="0.65297002"
inkscape:cx="638.40178"
inkscape:cy="466.56457"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
objecttolerance="1"
inkscape:window-width="2048"
inkscape:window-height="1076"
inkscape:window-x="-4"
inkscape:window-y="4"
inkscape:window-maximized="1"
inkscape:pagecheckerboard="true"
units="px" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#d98282;stroke:#000000;stroke-width:5.90000009;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1;filter:url(#filter1006);stroke-miterlimit:4;stroke-dasharray:none"
d="M 273.33203 61.957031 C 218.19856 132.07255 244.67649 272.3247 300.8125 327.65625 C 212.65683 342.56237 117.8187 290.41151 96.970703 214.49609 C 106.48046 303.18347 223.38426 385.06833 302.34375 385.77344 C 250.43451 458.78189 146.3945 488.61281 78.105469 449.44141 C 147.35021 505.66464 287.99747 481.38179 344.34961 426.17578 C 358.39121 514.5561 305.1554 608.79183 229.00586 628.76758 C 317.79645 620.27585 401.01904 504.31983 402.66211 425.35938 C 475.15092 478.02297 503.88024 582.37041 463.99023 650.24219 C 520.94121 581.59474 498.14594 440.69846 443.26758 384.42969 C 531.7397 373.62573 624.38067 429.5873 642.12695 506.28711 C 636.22847 417.28661 522.7479 330.71796 443.91992 326.89844 C 498.97294 256.29628 604.20699 230.99439 670.73828 273.08203 C 603.98948 213.9172 462.42095 232.0939 402.21289 284.1875 C 386.71456 195.15366 439.99868 100.94561 516.1582 81.007812 C 427.36338 89.45531 344.08309 205.36983 342.04102 283.99023 C 267.83445 232.87989 235.23336 130.85029 273.33203 61.957031 z M 371.38086 289.78711 A 64.321476 64.321476 0 0 1 435.70117 354.10742 A 64.321476 64.321476 0 0 1 371.38086 418.42969 A 64.321476 64.321476 0 0 1 307.05859 354.10742 A 64.321476 64.321476 0 0 1 371.38086 289.78711 z "
transform="scale(0.26458333)"
id="path828"
inkscape:export-xdpi="37.806454"
inkscape:export-ydpi="37.806454" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
assets/models/bulb_idle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -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<T extends Entity> extends EntityModel<T> {
// 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);
}
}

View file

@ -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<Entity> {
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);
}
}

File diff suppressed because one or more lines are too long

113
assets/models/hay_bale.json Normal file
View file

@ -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"}
}
}
]
}

File diff suppressed because one or more lines are too long

View file

@ -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"}
}
}
]
}

View file

@ -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"}
}
}
]
}

View file

@ -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"}
}
}
]
}

File diff suppressed because one or more lines are too long

BIN
assets/models/tentacle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
assets/spectral_clock_0.xcf Normal file

Binary file not shown.

View file

@ -45,6 +45,16 @@ public class Config extends com.minelittlepony.common.util.settings.Config {
.addComment("Removes butterflies from spawning in your world") .addComment("Removes butterflies from spawning in your world")
.addComment("Turn this ON if you have another mod that adds butterflies."); .addComment("Turn this ON if you have another mod that adds butterflies.");
public final Setting<Boolean> simplifiedPortals = value("compatibility", "simplifiedPortals", false)
.addComment("Disables dynamic portal rendering");
public final Setting<Long> 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<Integer> maxPortalRecursion = value("client", "maxPortalRecursion", 2)
.addComment("Sets the maximum depth to reach when rendering portals through portals");
public Config() { public Config() {
super(new HeirarchicalJsonConfigAdapter(new GsonBuilder() super(new HeirarchicalJsonConfigAdapter(new GsonBuilder()
.registerTypeAdapter(Race.class, RegistryTypeAdapter.of(Race.REGISTRY)) .registerTypeAdapter(Race.class, RegistryTypeAdapter.of(Race.REGISTRY))

View file

@ -1,33 +1,35 @@
package com.minelittlepony.unicopia; 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.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.entity.mob.AirBalloonEntity; import com.minelittlepony.unicopia.entity.mob.AirBalloonEntity;
import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.entity.mob.UEntities;
import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.registry.Registries; 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.World;
import net.minecraft.world.dimension.DimensionTypes;
public interface Debug { public interface Debug {
boolean SPELLBOOK_CHAPTERS = Boolean.getBoolean("unicopia.debug.spellbookChapters"); boolean SPELLBOOK_CHAPTERS = Boolean.getBoolean("unicopia.debug.spellbookChapters");
boolean CHECK_GAME_VALUES = Boolean.getBoolean("unicopia.debug.checkGameValues"); boolean CHECK_GAME_VALUES = Boolean.getBoolean("unicopia.debug.checkGameValues");
boolean CHECK_TRAIT_COVERAGE = Boolean.getBoolean("unicopia.debug.checkTraitCoverage");
boolean[] TESTS_COMPLETE = {false}; AtomicReference<World> LAST_TESTED_WORLD = new AtomicReference<>(null);
static void runTests(World world) { 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; return;
} }
TESTS_COMPLETE[0] = true;
try { if (CHECK_TRAIT_COVERAGE) {
Registries.ITEM.getEntrySet().forEach(entry -> { testTraitCoverage();
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);
} }
try { try {
@ -40,4 +42,30 @@ public interface Debug {
throw new IllegalStateException("Tests failed", t); 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));
}
});
}
} }

View file

@ -25,7 +25,7 @@ public interface EntityConvertable<E extends Entity> extends WorldConvertable {
* Gets the center position where this caster is located. * Gets the center position where this caster is located.
*/ */
default Vec3d getOriginVector() { default Vec3d getOriginVector() {
return asEntity().getPos(); return asEntity().getPos().add(0, asEntity().getHeight() * 0.5F, 0);
} }
@Override @Override

View file

@ -33,7 +33,7 @@ public interface EquinePredicates {
Predicate<Entity> IS_CASTER = e -> !e.isRemoved() && (e instanceof Caster || IS_PLAYER.test(e)); Predicate<Entity> IS_CASTER = e -> !e.isRemoved() && (e instanceof Caster || IS_PLAYER.test(e));
Predicate<Entity> IS_PLACED_SPELL = e -> e instanceof Caster && !e.isRemoved(); Predicate<Entity> IS_PLACED_SPELL = e -> e instanceof Caster && !e.isRemoved();
Predicate<Entity> IS_MAGIC_IMMUNE = e -> (e instanceof MagicImmune || !(e instanceof LivingEntity)) && !(e instanceof ItemEntity); Predicate<Entity> IS_MAGIC_IMMUNE = e -> (e instanceof MagicImmune || !(e instanceof LivingEntity)) && !(e instanceof ItemEntity) && !(e instanceof ExperienceOrbEntity);
Predicate<Entity> EXCEPT_MAGIC_IMMUNE = IS_MAGIC_IMMUNE.negate(); Predicate<Entity> EXCEPT_MAGIC_IMMUNE = IS_MAGIC_IMMUNE.negate();
Predicate<Entity> VALID_LIVING_AND_NOT_MAGIC_IMMUNE = EntityPredicates.VALID_LIVING_ENTITY.and(EXCEPT_MAGIC_IMMUNE); Predicate<Entity> VALID_LIVING_AND_NOT_MAGIC_IMMUNE = EntityPredicates.VALID_LIVING_ENTITY.and(EXCEPT_MAGIC_IMMUNE);

View file

@ -1,22 +1,19 @@
package com.minelittlepony.unicopia; package com.minelittlepony.unicopia;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.UUID;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.CasterView;
import com.minelittlepony.unicopia.entity.player.dummy.DummyPlayerEntity; 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 com.mojang.authlib.GameProfile;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class InteractionManager { public class InteractionManager {
@ -37,11 +34,8 @@ public class InteractionManager {
return INSTANCE; return INSTANCE;
} }
public Optional<CasterView> getCasterView(BlockView view) { public ParticleSpawner createBoundParticle(UUID id) {
if (view instanceof ServerWorld world) { return ParticleSpawner.EMPTY;
return Optional.of(Ether.get(world));
}
return Optional.empty();
} }
public Map<Identifier, ?> readChapters(PacketByteBuf buf) { public Map<Identifier, ?> readChapters(PacketByteBuf buf) {
@ -92,4 +86,8 @@ public class InteractionManager {
public PlayerEntity createPlayer(World world, GameProfile profile) { public PlayerEntity createPlayer(World world, GameProfile profile) {
return new DummyPlayerEntity(world, profile); return new DummyPlayerEntity(world, profile);
} }
public void sendPlayerLookAngles(PlayerEntity player) {
}
} }

View file

@ -5,6 +5,8 @@ import java.util.UUID;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
/** /**
@ -27,14 +29,23 @@ public interface Owned<E extends Entity> {
* Since {@link Owned#getMaster()} will only return if the owner is loaded, use this to perform checks * Since {@link Owned#getMaster()} will only return if the owner is loaded, use this to perform checks
* in the owner's absence. * in the owner's absence.
*/ */
default Optional<UUID> getMasterId() { Optional<UUID> getMasterId();
return Optional.of(getMaster()).map(Entity::getUuid);
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) { default boolean isOwnedBy(@Nullable Object owner) {
return owner instanceof Entity e return owner instanceof Entity e && e.getUuid().equals(getMasterId().orElse(null));
&& getMasterId().isPresent()
&& e.getUuid().equals(getMasterId().get());
} }
default boolean hasCommonOwner(Owned<?> sibling) { default boolean hasCommonOwner(Owned<?> sibling) {

View file

@ -62,11 +62,18 @@ public interface USounds {
SoundEvent ENTITY_SOMBRA_AMBIENT = register("entity.sombra.ambient"); SoundEvent ENTITY_SOMBRA_AMBIENT = register("entity.sombra.ambient");
SoundEvent ENTITY_SOMBRA_LAUGH = register("entity.sombra.laugh"); SoundEvent ENTITY_SOMBRA_LAUGH = register("entity.sombra.laugh");
SoundEvent ENTITY_SOMBRA_SNICKER = register("entity.sombra.snicker"); 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_AMBIENT = BLOCK_AMETHYST_BLOCK_HIT;
SoundEvent ENTITY_CRYSTAL_SHARDS_JOSTLE = BLOCK_AMETHYST_BLOCK_BREAK; 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_CHARGING = register("item.amulet.charging");
SoundEvent ITEM_AMULET_RECHARGE = register("item.amulet.recharge"); 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_STAFF_STRIKE = ENTITY_PLAYER_ATTACK_CRIT;
SoundEvent ITEM_MAGIC_STAFF_CHARGE = ENTITY_GUARDIAN_ATTACK; SoundEvent ITEM_MAGIC_STAFF_CHARGE = ENTITY_GUARDIAN_ATTACK;
SoundEvent ITEM_CURING_JOKE_CURE = BLOCK_AMETHYST_BLOCK_BREAK;
SoundEvent ITEM_ROCK_LAND = BLOCK_STONE_HIT; SoundEvent ITEM_ROCK_LAND = BLOCK_STONE_HIT;
RegistryEntry.Reference<SoundEvent> ITEM_MUFFIN_BOUNCE = BLOCK_NOTE_BLOCK_BANJO; RegistryEntry.Reference<SoundEvent> ITEM_MUFFIN_BOUNCE = BLOCK_NOTE_BLOCK_BANJO;

View file

@ -23,6 +23,7 @@ public interface UTags {
TagKey<Item> SHADES = item("shades"); TagKey<Item> SHADES = item("shades");
TagKey<Item> CHANGELING_EDIBLE = item("food_types/changeling_edible"); TagKey<Item> CHANGELING_EDIBLE = item("food_types/changeling_edible");
TagKey<Item> SPOOKED_MOB_DROPS = item("spooked_mob_drops"); TagKey<Item> SPOOKED_MOB_DROPS = item("spooked_mob_drops");
TagKey<Item> HAS_NO_TRAITS = item("has_no_traits");
TagKey<Item> IS_DELIVERED_AGGRESSIVELY = item("is_delivered_aggressively"); TagKey<Item> IS_DELIVERED_AGGRESSIVELY = item("is_delivered_aggressively");
TagKey<Item> FLOATS_ON_CLOUDS = item("floats_on_clouds"); TagKey<Item> FLOATS_ON_CLOUDS = item("floats_on_clouds");
TagKey<Item> COOLS_OFF_KIRINS = item("cools_off_kirins"); TagKey<Item> COOLS_OFF_KIRINS = item("cools_off_kirins");
@ -42,6 +43,8 @@ public interface UTags {
TagKey<Block> CRYSTAL_HEART_BASE = block("crystal_heart_base"); TagKey<Block> CRYSTAL_HEART_BASE = block("crystal_heart_base");
TagKey<Block> CRYSTAL_HEART_ORNAMENT = block("crystal_heart_ornament"); TagKey<Block> CRYSTAL_HEART_ORNAMENT = block("crystal_heart_ornament");
TagKey<Block> UNAFFECTED_BY_GROW_ABILITY = block("unaffected_by_grow_ability");
TagKey<Block> KICKS_UP_DUST = block("kicks_up_dust");
TagKey<Block> POLEARM_MINEABLE = block("mineable/polearm"); TagKey<Block> POLEARM_MINEABLE = block("mineable/polearm");

View file

@ -209,6 +209,7 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable {
warmup = 0; warmup = 0;
if (data.isPresent()) { if (data.isPresent()) {
InteractionManager.instance().sendPlayerLookAngles(player.asEntity());
Channel.CLIENT_PLAYER_ABILITY.sendToServer(new MsgPlayerAbility<>(ability, data, ActivationType.NONE)); Channel.CLIENT_PLAYER_ABILITY.sendToServer(new MsgPlayerAbility<>(ability, data, ActivationType.NONE));
} else { } else {
player.asEntity().playSound(USounds.GUI_ABILITY_FAIL, 1, 1); player.asEntity().playSound(USounds.GUI_ABILITY_FAIL, 1, 1);

View file

@ -76,9 +76,9 @@ public class ChangeFormAbility implements Ability<Hit> {
targets.forEach(target -> { targets.forEach(target -> {
Race supressed = target.getSuppressedRace(); Race supressed = target.getSuppressedRace();
if (target == player || supressed.isUnset() == isTransforming) { 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.setSpecies(supressed.or(player.getCompositeRace().potential()));
target.setSuppressedRace(isTransforming ? actualRace : Race.UNSET); target.setSuppressedRace(actualRace);
} }
}); });

View file

@ -1,22 +1,37 @@
package com.minelittlepony.unicopia.ability; package com.minelittlepony.unicopia.ability;
import java.util.Optional; import java.util.Optional;
import java.util.function.DoubleSupplier;
import java.util.function.Supplier;
import com.minelittlepony.unicopia.Race; 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.Hit;
import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.block.UBlocks; import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.entity.player.Pony; 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.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.TraceHelper;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.block.CarrotsBlock;
import net.minecraft.block.FarmlandBlock;
import net.minecraft.item.BoneMealItem; import net.minecraft.item.BoneMealItem;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.Items; import net.minecraft.item.Items;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.WorldEvents;
/** /**
* Earth Pony ability to grow crops * Earth Pony ability to grow crops
@ -57,10 +72,14 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
public boolean apply(Pony player, Pos data) { public boolean apply(Pony player, Pos data) {
int count = 0; int count = 0;
for (BlockPos pos : BlockPos.iterate( if (!applyDirectly(player, data.pos())) {
data.pos().add(-2, -2, -2), 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); data.pos().add( 2, 2, 2))) {
count += applySingle(player, player.asWorld(), player.asWorld().getBlockState(pos), pos);
}
} else {
count = 1;
} }
if (count > 0) { if (count > 0) {
@ -69,7 +88,7 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
return true; 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); ItemStack stack = new ItemStack(Items.BONE_MEAL);
@ -77,12 +96,33 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
return growable.grow(w, state, pos) ? 1 : 0; 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 (BoneMealItem.useOnFertilizable(stack, w, pos)) {
if (w.random.nextInt(350) == 0) { if (w.random.nextInt(350) == 0) {
if (w.getBlockState(pos.down()).isOf(Blocks.FARMLAND)) { 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()); 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; return 1;
} }
@ -94,6 +134,56 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
return 0; 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<Vec3d> 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 @Override
public void warmUp(Pony player, AbilitySlot slot) { public void warmUp(Pony player, AbilitySlot slot) {
player.getMagicalReserves().getExertion().addPercent(30); player.getMagicalReserves().getExertion().addPercent(30);

View file

@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.AwaitTickQueue; import com.minelittlepony.unicopia.AwaitTickQueue;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.Living; 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.particle.UParticles;
import com.minelittlepony.unicopia.server.world.BlockDestructionManager; import com.minelittlepony.unicopia.server.world.BlockDestructionManager;
import com.minelittlepony.unicopia.util.PosHelper; import com.minelittlepony.unicopia.util.PosHelper;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -26,6 +28,7 @@ import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.particle.BlockStateParticleEffect;
import net.minecraft.registry.tag.BlockTags; import net.minecraft.registry.tag.BlockTags;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier; 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.BlockPos;
import net.minecraft.util.math.Box; import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.WorldEvents; import net.minecraft.world.WorldEvents;
@ -156,7 +160,10 @@ public class EarthPonyStompAbility implements Ability<Hit> {
spawnEffectAround(player, center, radius, rad); 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.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.subtractEnergyCost(rad);
iplayer.asEntity().addExhaustion(3); iplayer.asEntity().addExhaustion(3);
@ -218,6 +225,12 @@ public class EarthPonyStompAbility implements Ability<Hit> {
} else { } else {
w.syncWorldEvent(WorldEvents.BLOCK_BROKEN, pos, Block.getRawIdFromState(state)); 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 @Override

View file

@ -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.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.MagicParticleEffect; 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 * Pegasus ability to perform rainbooms
@ -72,7 +68,6 @@ public class PegasusRainboomAbility implements Ability<Hit> {
} }
if (player.consumeSuperMove()) { if (player.consumeSuperMove()) {
player.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, player.getPhysics().getMotionAngle()), player.getOriginVector(), Vec3d.ZERO);
SpellType.RAINBOOM.withTraits().apply(player, CastingMethod.INNATE); SpellType.RAINBOOM.withTraits().apply(player, CastingMethod.INNATE);
} }
return true; return true;

View file

@ -7,6 +7,7 @@ import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.ability.magic.Caster; 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.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
@ -92,7 +93,11 @@ public class UnicornDispellAbility implements Ability<Pos> {
public boolean apply(Pony player, Pos data) { public boolean apply(Pony player, Pos data) {
player.setAnimation(Animation.WOLOLO, Animation.Recipient.ANYONE); 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 -> { 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; return true;
} }

View file

@ -19,4 +19,8 @@ public interface Affine {
default boolean isFriendlyTogether(Affine other) { default boolean isFriendlyTogether(Affine other) {
return getAffinity() != Affinity.BAD && other.getAffinity() != Affinity.BAD; return getAffinity() != Affinity.BAD && other.getAffinity() != Affinity.BAD;
} }
default boolean applyInversion(Affine other, boolean friendly) {
return isEnemy(other) ? !friendly : friendly;
}
} }

View file

@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.*;
import com.minelittlepony.unicopia.entity.damage.UDamageSources; import com.minelittlepony.unicopia.entity.damage.UDamageSources;
import com.minelittlepony.unicopia.particle.ParticleSource; import com.minelittlepony.unicopia.particle.ParticleSource;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.server.world.ModificationType; import com.minelittlepony.unicopia.server.world.ModificationType;
import com.minelittlepony.unicopia.util.SoundEmitter; import com.minelittlepony.unicopia.util.SoundEmitter;
import com.minelittlepony.unicopia.util.VecHelper; import com.minelittlepony.unicopia.util.VecHelper;
@ -99,11 +100,7 @@ public interface Caster<E extends Entity> extends
} }
default boolean canCastAt(Vec3d pos) { default boolean canCastAt(Vec3d pos) {
return findAllSpellsInRange(500, SpellType.ARCANE_PROTECTION::isOn).noneMatch(caster -> caster return !Ether.get(asWorld()).anyMatch(SpellType.ARCANE_PROTECTION, (spell, caster) -> spell.blocksMagicFor(caster, this, pos));
.getSpellSlot().get(SpellType.ARCANE_PROTECTION, false)
.filter(spell -> spell.blocksMagicFor(caster, this, pos))
.isPresent()
);
} }
static Stream<Caster<?>> stream(Stream<Entity> entities) { static Stream<Caster<?>> stream(Stream<Entity> entities) {

View file

@ -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 <S extends Spell> Stream<Map.Entry<Caster<?>, S>> findAllSpellsInRange(BlockPos pos, double radius, SpellPredicate<S> type) {
return findAllCastersInRange(pos, radius).flatMap(caster -> {
return caster.getSpellSlot().stream(type, false).map(spell -> {
return Map.entry(caster, spell);
});
});
}
default Stream<Caster<?>> findAllCastersInRange(BlockPos pos, double radius) {
return findAllCastersInRange(pos, radius, null);
}
default Stream<Caster<?>> findAllCastersInRange(BlockPos pos, double radius, @Nullable Predicate<Entity> test) {
return Caster.stream(findAllEntitiesInRange(pos, radius, test == null ? EquinePredicates.IS_CASTER : EquinePredicates.IS_CASTER.and(test)));
}
default Stream<Entity> findAllEntitiesInRange(BlockPos pos, double radius, @Nullable Predicate<Entity> test) {
return VecHelper.findInRange(null, getWorld(), Vec3d.ofCenter(pos), radius, test).stream();
}
default Stream<Entity> findAllEntitiesInRange(BlockPos pos, double radius) {
return findAllEntitiesInRange(pos, radius, null);
}
}

View file

@ -39,6 +39,13 @@ public interface SpellContainer {
*/ */
void put(@Nullable Spell effect); 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. * Removes all active effects that match or contain a matching effect.
* *

View file

@ -69,11 +69,20 @@ public abstract class AbstractDelegatingSpell implements Spell,
getDelegates().forEach(Spell::setDead); getDelegates().forEach(Spell::setDead);
} }
@Override
public void tickDying(Caster<?> caster) {
}
@Override @Override
public boolean isDead() { public boolean isDead() {
return getDelegates().isEmpty() || getDelegates().stream().allMatch(Spell::isDead); return getDelegates().isEmpty() || getDelegates().stream().allMatch(Spell::isDead);
} }
@Override
public boolean isDying() {
return false;
}
@Override @Override
public boolean isDirty() { public boolean isDirty() {
return dirty || getDelegates().stream().anyMatch(Spell::isDirty); return dirty || getDelegates().stream().anyMatch(Spell::isDirty);
@ -110,7 +119,13 @@ public abstract class AbstractDelegatingSpell implements Spell,
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { 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 @Override

View file

@ -13,10 +13,6 @@ import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgCasterLookRequest; 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.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.NbtSerialisable;
@ -24,6 +20,7 @@ import net.minecraft.nbt.*;
import net.minecraft.registry.*; import net.minecraft.registry.*;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
@ -41,9 +38,10 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
private RegistryKey<World> dimension; private RegistryKey<World> 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 * The cast spell entity
@ -58,6 +56,13 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
public float pitch; public float pitch;
public float yaw; public float yaw;
private int prevAge;
private int age;
private boolean dead;
private int prevDeathTicks;
private int deathTicks;
private Optional<Vec3d> position = Optional.empty(); private Optional<Vec3d> position = Optional.empty();
public PlaceableSpell(CustomisedSpellType<?> type) { public PlaceableSpell(CustomisedSpellType<?> type) {
@ -69,23 +74,42 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
return this; 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 @Override
public Collection<Spell> getDelegates() { public Collection<Spell> getDelegates() {
return List.of(spell); return List.of(spell);
} }
@Override
public void setDead() {
super.setDead();
particlEffect.destroy();
}
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
if (situation == Situation.BODY) { if (situation == Situation.BODY) {
if (!source.isClient()) { if (!source.isClient()) {
if (dimension == null) { if (dimension == null) {
dimension = source.asWorld().getRegistryKey(); dimension = source.asWorld().getRegistryKey();
if (source instanceof Pony) { if (source instanceof Pony) {
@ -105,29 +129,31 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
if (situation == Situation.GROUND_ENTITY) { if (situation == Situation.GROUND_ENTITY) {
if (!source.isClient()) { if (!source.isClient()) {
Ether ether = Ether.get(source.asWorld()); if (Ether.get(source.asWorld()).get(this, source) == null) {
if (ether.getEntry(getType(), source).isEmpty()) {
setDead(); setDead();
return false; return false;
} }
} }
if (spell instanceof PlacementDelegate delegate) { prevAge = age;
delegate.updatePlacement(source, this); if (age < 25) {
age++;
} }
getParticleEffectAttachment(source).ifPresent(p -> {
p.setAttribute(Attachment.ATTR_COLOR, spell.getType().getColor());
});
return super.tick(source, Situation.GROUND); return super.tick(source, Situation.GROUND);
} }
return !isDead(); return !isDead();
} }
@Override
public void tickDying(Caster<?> caster) {
prevDeathTicks = deathTicks;
deathTicks--;
}
private void checkDetachment(Caster<?> source, EntityValues<?> target) { 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(); setDead();
} }
} }
@ -143,7 +169,8 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
entity.getSpellSlot().put(copy); entity.getSpellSlot().put(copy);
entity.setCaster(source); entity.setCaster(source);
entity.getWorld().spawnEntity(entity); entity.getWorld().spawnEntity(entity);
Ether.get(entity.getWorld()).put(getType(), entity); placedSpellId = copy.getUuid();
Ether.get(entity.getWorld()).getOrCreate(copy, entity);
castEntity.set(entity); castEntity.set(entity);
setDirty(); setDirty();
@ -174,8 +201,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
if (!source.isClient()) { if (!source.isClient()) {
castEntity.getTarget().ifPresent(target -> { castEntity.getTarget().ifPresent(target -> {
getWorld(source).map(Ether::get) getWorld(source).map(Ether::get)
.flatMap(ether -> ether.getEntry(getType(), target.uuid())) .ifPresent(ether -> ether.remove(getType(), target.uuid()));
.ifPresent(Ether.Entry::markDead);
}); });
castEntity.set(null); castEntity.set(null);
getSpellEntity(source).ifPresent(e -> { getSpellEntity(source).ifPresent(e -> {
@ -183,7 +209,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
}); });
if (source.asEntity() instanceof CastSpellEntity spellcast) { if (source.asEntity() instanceof CastSpellEntity spellcast) {
Ether.get(source.asWorld()).remove(getType(), source); Ether.get(source.asWorld()).remove(this, source);
} }
} }
super.onDestroyed(source); super.onDestroyed(source);
@ -197,12 +223,6 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
return castEntity.getTarget().map(EntityValues::pos); return castEntity.getTarget().map(EntityValues::pos);
} }
public Optional<Attachment> 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<World> getWorld(Caster<?> source) { protected Optional<World> getWorld(Caster<?> source) {
return Optional.ofNullable(dimension) return Optional.ofNullable(dimension)
.map(dim -> source.asWorld().getServer().getWorld(dim)); .map(dim -> source.asWorld().getServer().getWorld(dim));
@ -211,11 +231,17 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
super.toNBT(compound); super.toNBT(compound);
compound.putBoolean("dead", dead);
compound.putInt("deathTicks", deathTicks);
compound.putInt("age", age);
compound.putFloat("pitch", pitch); compound.putFloat("pitch", pitch);
compound.putFloat("yaw", yaw); compound.putFloat("yaw", yaw);
position.ifPresent(pos -> { position.ifPresent(pos -> {
compound.put("position", NbtSerialisable.writeVector(pos)); compound.put("position", NbtSerialisable.writeVector(pos));
}); });
if (placedSpellId != null) {
compound.putUuid("placedSpellId", placedSpellId);
}
if (dimension != null) { if (dimension != null) {
compound.putString("dimension", dimension.getValue().toString()); compound.putString("dimension", dimension.getValue().toString());
} }
@ -226,9 +252,13 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
dead = compound.getBoolean("dead");
deathTicks = compound.getInt("deathTicks");
age = compound.getInt("age");
pitch = compound.getFloat("pitch"); pitch = compound.getFloat("pitch");
yaw = compound.getFloat("yaw"); yaw = compound.getFloat("yaw");
position = compound.contains("position") ? Optional.of(NbtSerialisable.readVector(compound.getList("position", NbtElement.FLOAT_TYPE))) : Optional.empty(); 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)) { if (compound.contains("dimension", NbtElement.STRING_TYPE)) {
Identifier id = Identifier.tryParse(compound.getString("dimension")); Identifier id = Identifier.tryParse(compound.getString("dimension"));
if (id != null) { if (id != null) {
@ -257,9 +287,6 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
} }
public interface PlacementDelegate { public interface PlacementDelegate {
void onPlaced(Caster<?> source, PlaceableSpell parent, CastSpellEntity entity); void onPlaced(Caster<?> source, PlaceableSpell parent, CastSpellEntity entity);
void updatePlacement(Caster<?> source, PlaceableSpell parent);
} }
} }

View file

@ -1,12 +1,16 @@
package com.minelittlepony.unicopia.ability.magic.spell; 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.UTags;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.*; import com.minelittlepony.unicopia.ability.magic.spell.effect.*;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.ParticleHandle; import com.minelittlepony.unicopia.particle.OrientedBillboardParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment; import com.minelittlepony.unicopia.particle.ParticleSpawner;
import com.minelittlepony.unicopia.particle.TargetBoundParticleEffect;
import com.minelittlepony.unicopia.server.world.ModificationType; import com.minelittlepony.unicopia.server.world.ModificationType;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.util.shape.Shape; 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 int RADIUS = 5;
private static final Shape EFFECT_RANGE = new Sphere(false, RADIUS); private static final Shape EFFECT_RANGE = new Sphere(false, RADIUS);
private final ParticleHandle particlEffect = new ParticleHandle(); @Nullable
private ParticleSpawner boundParticle;
private int age; private int age;
@ -35,11 +40,6 @@ public class RainboomAbilitySpell extends AbstractSpell {
setHidden(true); setHidden(true);
} }
@Override
protected void onDestroyed(Caster<?> source) {
particlEffect.destroy();
}
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
@ -47,14 +47,15 @@ public class RainboomAbilitySpell extends AbstractSpell {
return false; 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()) { 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 -> { source.findAllEntitiesInRange(RADIUS).forEach(e -> {
@ -92,5 +93,6 @@ public class RainboomAbilitySpell extends AbstractSpell {
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
age = compound.getInt("age"); age = compound.getInt("age");
boundParticle = null;
} }
} }

View file

@ -61,6 +61,8 @@ public interface Spell extends NbtSerialisable, Affine {
*/ */
boolean isDead(); boolean isDead();
boolean isDying();
/** /**
* Returns true if this effect has changes that need to be sent to the client. * 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. * Applies this spell to the supplied caster.
* @param caster The caster to apply the spell to
*/ */
default boolean apply(Caster<?> caster) { default boolean apply(Caster<?> caster) {
caster.getSpellSlot().put(this); 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. * 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) { default Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return this; return this;
@ -89,6 +92,12 @@ public interface Spell extends NbtSerialisable, Affine {
*/ */
boolean tick(Caster<?> caster, Situation situation); 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. * Marks this effect as dirty.
*/ */

View file

@ -29,7 +29,7 @@ public interface TimedSpell extends Spell {
} }
public float getPercentTimeRemaining(float tickDelta) { public float getPercentTimeRemaining(float tickDelta) {
return MathHelper.lerp(tickDelta, prevDuration, duration) / maxDuration; return MathHelper.lerp(tickDelta, prevDuration, duration) / (float)maxDuration;
} }
public int getTicksRemaining() { public int getTicksRemaining() {

View file

@ -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<ItemEntity> ingredients,
ItemStack result
) {
@Nullable
public static AltarRecipeMatch of(List<ItemEntity> 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);
}
}

View file

@ -12,6 +12,7 @@ import net.minecraft.nbt.NbtCompound;
public abstract class AbstractSpell implements Spell { public abstract class AbstractSpell implements Spell {
private boolean dead; private boolean dead;
private boolean dying;
private boolean dirty; private boolean dirty;
private boolean hidden; private boolean hidden;
private boolean destroyed; private boolean destroyed;
@ -45,7 +46,7 @@ public abstract class AbstractSpell implements Spell {
@Override @Override
public final void setDead() { public final void setDead() {
dead = true; dying = true;
setDirty(); setDirty();
} }
@ -54,6 +55,11 @@ public abstract class AbstractSpell implements Spell {
return dead; return dead;
} }
@Override
public final boolean isDying() {
return dying;
}
@Override @Override
public final boolean isDirty() { public final boolean isDirty() {
return dirty; return dirty;
@ -82,6 +88,11 @@ public abstract class AbstractSpell implements Spell {
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
} }
@Override
public void tickDying(Caster<?> caster) {
dead = true;
}
@Override @Override
public final void destroy(Caster<?> caster) { public final void destroy(Caster<?> caster) {
if (destroyed) { if (destroyed) {
@ -94,6 +105,7 @@ public abstract class AbstractSpell implements Spell {
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
compound.putBoolean("dying", dying);
compound.putBoolean("dead", dead); compound.putBoolean("dead", dead);
compound.putBoolean("hidden", hidden); compound.putBoolean("hidden", hidden);
compound.putUuid("uuid", uuid); compound.putUuid("uuid", uuid);
@ -106,6 +118,7 @@ public abstract class AbstractSpell implements Spell {
if (compound.contains("uuid")) { if (compound.contains("uuid")) {
uuid = compound.getUuid("uuid"); uuid = compound.getUuid("uuid");
} }
dying = compound.getBoolean("dying");
dead = compound.getBoolean("dead"); dead = compound.getBoolean("dead");
hidden = compound.getBoolean("hidden"); hidden = compound.getBoolean("hidden");
if (compound.contains("traits")) { if (compound.contains("traits")) {

View file

@ -9,6 +9,7 @@ import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.FriendshipBraceletItem; import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.shape.Sphere; import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@ -42,6 +43,8 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO); 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 -> { 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(); 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. * Calculates the maximum radius of the shield. aka The area of effect.
*/ */

View file

@ -7,7 +7,6 @@ import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.FollowingParticleEffect;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate; 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.Entity;
import net.minecraft.entity.ItemEntity; import net.minecraft.entity.ItemEntity;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
@ -44,9 +44,10 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
if (timer.getTicksRemaining() <= 0) { if (timer.getTicksRemaining() <= 0) {
return false; return false;
} }
setDirty();
} }
setDirty();
target.getOrEmpty(caster.asWorld()) target.getOrEmpty(caster.asWorld())
.filter(entity -> entity.distanceTo(caster.asEntity()) > getDrawDropOffRange(caster)) .filter(entity -> entity.distanceTo(caster.asEntity()) > getDrawDropOffRange(caster))
.ifPresent(entity -> { .ifPresent(entity -> {
@ -59,12 +60,13 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
@Override @Override
public void generateParticles(Caster<?> source) { 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( source.addParticle(
new FollowingParticleEffect(UParticles.HEALTH_DRAIN, source.asEntity(), 0.4F) new FollowingParticleEffect(UParticles.HEALTH_DRAIN, origin, 0.4F)
.withChild(new MagicParticleEffect(getType().getColor())), .withChild(ParticleTypes.AMBIENT_ENTITY_EFFECT),
p, p,
Vec3d.ZERO Vec3d.ZERO
); );

View file

@ -11,12 +11,10 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.*;
import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; import com.minelittlepony.unicopia.entity.mob.UEntityAttributes;
import com.minelittlepony.unicopia.entity.player.Pony; 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.UParticles;
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
@ -48,11 +46,11 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
.with(Trait.POWER, 1) .with(Trait.POWER, 1)
.build(); .build();
protected final ParticleHandle particlEffect = new ParticleHandle();
private final Timer timer; private final Timer timer;
private int struggles; private int struggles;
private float prevRadius;
private float radius; private float radius;
protected BubbleSpell(CustomisedSpellType<?> type) { protected BubbleSpell(CustomisedSpellType<?> type) {
@ -66,6 +64,10 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
return timer; return timer;
} }
public float getRadius(float tickDelta) {
return MathHelper.lerp(tickDelta, prevRadius, radius);
}
@Override @Override
public boolean apply(Caster<?> source) { public boolean apply(Caster<?> source) {
@ -95,14 +97,19 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
if (situation == Situation.PROJECTILE) { if (situation == Situation.PROJECTILE) {
source.spawnParticles(UParticles.BUBBLE, 2);
source.spawnParticles(ParticleTypes.BUBBLE, 2);
return true; return true;
} }
timer.tick(); 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; return false;
} }
@ -116,7 +123,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
source.asEntity().fallDistance = 0; source.asEntity().fallDistance = 0;
Vec3d origin = source.getOriginVector(); prevRadius = radius;
if (source instanceof Pony pony && pony.sneakingChanged() && pony.asEntity().isSneaking()) { if (source instanceof Pony pony && pony.sneakingChanged() && pony.asEntity().isSneaking()) {
setDirty(); 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(); return !isDead();
} }
@Override @Override
protected void onDestroyed(Caster<?> source) { protected void onDestroyed(Caster<?> source) {
particlEffect.destroy();
if (source.asEntity() instanceof LivingEntity l) { if (source.asEntity() instanceof LivingEntity l) {
MODIFIERS.forEach((attribute, modifier) -> { MODIFIERS.forEach((attribute, modifier) -> {
if (l.getAttributes().hasAttribute(attribute)) { if (l.getAttributes().hasAttribute(attribute)) {

View file

@ -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.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; 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.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.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.particle.SphereParticleEffect;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate; 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. * 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 class DarkVortexSpell extends AttractiveSpell implements ProjectileDelegate.BlockHitListener {
public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder() 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 static final Vec3d SPHERE_OFFSET = new Vec3d(0, 2, 0);
private int age = 0;
private float accumulatedMass = 0; private float accumulatedMass = 0;
protected DarkVortexSpell(CustomisedSpellType<?> type) { protected DarkVortexSpell(CustomisedSpellType<?> type) {
@ -84,17 +79,10 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
return true; return true;
} }
age++; if (source.asEntity().age % 20 == 0) {
setDirty();
if (age % 20 == 0) {
source.asWorld().playSound(null, source.getOrigin(), USounds.AMBIENT_DARK_VORTEX_ADDITIONS, SoundCategory.AMBIENT, 1, 1); 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) { if (!source.isClient() && source.asWorld().random.nextInt(300) == 0) {
ParticleUtils.spawnParticle(source.asWorld(), LightningBoltParticleEffect.DEFAULT, getOrigin(source), Vec3d.ZERO); 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); return super.tick(source, situation);
} }
@Override
protected void consumeManage(Caster<?> source, long costMultiplier, float knowledge) {
if (!source.subtractEnergyCost(-accumulatedMass)) {
setDead();
}
}
@Override @Override
public boolean isFriendlyTogether(Affine other) { public boolean isFriendlyTogether(Affine other) {
return accumulatedMass < 4; return accumulatedMass < 4;
@ -116,21 +111,20 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
public void generateParticles(Caster<?> source) { public void generateParticles(Caster<?> source) {
super.generateParticles(source); super.generateParticles(source);
float radius = (float)getEventHorizonRadius(); if (getEventHorizonRadius() > 0.3) {
double range = getDrawDropOffRange(source);
particlEffect.update(getUuid(), source, spawner -> { Vec3d origin = getOrigin(source);
spawner.addParticle(new SphereParticleEffect(UParticles.SPHERE, 0x000000, 0.99F, radius, SPHERE_OFFSET), source.getOriginVector(), Vec3d.ZERO); source.spawnParticles(origin, new Sphere(false, range), 1, p -> {
}).ifPresent(p -> { if (!source.asWorld().isAir(BlockPos.ofFloored(p))) {
p.setAttribute(Attachment.ATTR_RADIUS, radius); source.addParticle(
p.setAttribute(Attachment.ATTR_OPACITY, 2F); new FollowingParticleEffect(UParticles.HEALTH_DRAIN, origin, 0.4F)
}); .withChild(ParticleTypes.CAMPFIRE_SIGNAL_SMOKE),
particlEffect.update(getUuid(), "_ring", source, spawner -> { p,
spawner.addParticle(new SphereParticleEffect(UParticles.DISK, 0xAAAAAA, 0.4F, radius + 1, SPHERE_OFFSET), getOrigin(source), Vec3d.ZERO); Vec3d.ZERO
}).ifPresent(p -> { );
p.setAttribute(Attachment.ATTR_RADIUS, radius * 0F); }
}); });
}
source.spawnParticles(ParticleTypes.SMOKE, 3);
} }
@Override @Override
@ -162,7 +156,6 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
applyRadialEffect(source, e, e.getPos().distanceTo(origin), radius); 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 // 2. max force (at dist 0) is taken from accumulated mass
// 3. force reaches 0 at distance of drawDropOffRange // 3. force reaches 0 at distance of drawDropOffRange
private double getEventHorizonRadius() { public double getEventHorizonRadius() {
return Math.sqrt(Math.max(0.001, getMass() - 12)); return Math.sqrt(Math.max(0.001, getMass() / 3F));
} }
private double getAttractiveForce(Caster<?> source, Entity target) { private double getAttractiveForce(Caster<?> source, Entity target) {
@ -189,8 +182,7 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
} }
private double getMass() { private double getMass() {
float pulse = (float)Math.sin(age * 8) / 1F; return Math.min(15, 0.1F + accumulatedMass / 10F);
return 10 + Math.min(15, Math.min(0.5F + pulse, (float)Math.exp(age) / 8F - 90) + accumulatedMass / 10F) + pulse;
} }
@Override @Override
@ -202,6 +194,11 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
if (distance <= getEventHorizonRadius() + 0.5) { if (distance <= getEventHorizonRadius() + 0.5) {
target.setVelocity(target.getVelocity().multiply(distance / (2 * radius))); target.setVelocity(target.getVelocity().multiply(distance / (2 * radius)));
if (distance < 1) {
target.setVelocity(target.getVelocity().multiply(distance));
}
Living.updateVelocity(target);
@Nullable @Nullable
Entity master = source.getMaster(); Entity master = source.getMaster();
@ -221,13 +218,19 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
double massOfTarget = AttractionUtils.getMass(target); double massOfTarget = AttractionUtils.getMass(target);
accumulatedMass += massOfTarget; if (!source.isClient() && massOfTarget != 0) {
setDirty(); accumulatedMass += massOfTarget;
setDirty();
}
target.damage(source.damageOf(UDamageTypes.GAVITY_WELL_RECOIL, source), Integer.MAX_VALUE); target.damage(source.damageOf(UDamageTypes.GAVITY_WELL_RECOIL, source), Integer.MAX_VALUE);
if (!(target instanceof PlayerEntity)) { if (!(target instanceof PlayerEntity)) {
target.discard(); target.discard();
source.asWorld().playSound(null, source.getOrigin(), USounds.ENCHANTMENT_CONSUMPTION_CONSUME, SoundCategory.AMBIENT, 2, 0.02F); 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.subtractEnergyCost(-massOfTarget * 10);
source.asWorld().playSound(null, source.getOrigin(), USounds.AMBIENT_DARK_VORTEX_MOOD, SoundCategory.AMBIENT, 2, 0.02F); 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 @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
super.toNBT(compound); super.toNBT(compound);
compound.putInt("age", age);
compound.putFloat("accumulatedMass", accumulatedMass); compound.putFloat("accumulatedMass", accumulatedMass);
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
age = compound.getInt("age");
accumulatedMass = compound.getFloat("accumulatedMass"); accumulatedMass = compound.getFloat("accumulatedMass");
} }
} }

View file

@ -12,7 +12,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.util.math.Vec3d; 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 { public class DisperseIllusionSpell extends AbstractAreaEffectSpell {
protected DisperseIllusionSpell(CustomisedSpellType<?> type) { protected DisperseIllusionSpell(CustomisedSpellType<?> type) {

View file

@ -6,8 +6,6 @@ import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; 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.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate; 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.hit.EntityHitResult;
import net.minecraft.util.math.Vec3d; 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<Entity> target = new EntityReference<>(); private final EntityReference<Entity> target = new EntityReference<>();
@ -67,19 +65,6 @@ public class DisplacementSpell extends AbstractSpell implements HomingSpell, Pla
originator.subtractEnergyCost(destinationPos.distanceTo(sourcePos) / 20F); 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) { private void teleport(Caster<?> source, Entity entity, Vec3d pos, Vec3d vel) {
entity.teleport(pos.x, pos.y, pos.z); entity.teleport(pos.x, pos.y, pos.z);
entity.setVelocity(vel); entity.setVelocity(vel);

View file

@ -48,7 +48,7 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
if (caster instanceof MagicProjectileEntity && getTraits().get(Trait.FOCUS) >= 50) { if (caster instanceof MagicProjectileEntity && getTraits().get(Trait.FOCUS) >= 50) {
caster.findAllEntitiesInRange( caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49, 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 -> { ).findFirst().ifPresent(target -> {
((MagicProjectileEntity)caster).setHomingTarget(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()) { if (getTraits().get(Trait.FOCUS) >= 50 && target.getOrEmpty(caster.asWorld()).isEmpty()) {
target.set(caster.findAllEntitiesInRange( target.set(caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49, 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)); ).findFirst().orElse(null));
} }

View file

@ -5,14 +5,16 @@ import java.util.Set;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster; 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.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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.util.shape.*; import com.minelittlepony.unicopia.util.shape.*;
@ -21,8 +23,10 @@ import net.minecraft.fluid.*;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraft.state.property.Properties; import net.minecraft.state.property.Properties;
import net.minecraft.registry.tag.TagKey; import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class HydrophobicSpell extends AbstractSpell { public class HydrophobicSpell extends AbstractSpell {
@ -41,16 +45,15 @@ public class HydrophobicSpell extends AbstractSpell {
} }
@Override @Override
public boolean apply(Caster<?> source) { public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
if (getTraits().get(Trait.GENEROSITY) > 0) { if ((method == CastingMethod.DIRECT || method == CastingMethod.STAFF) && getTraits().get(Trait.GENEROSITY) > 0) {
return toPlaceable().apply(source); return toPlaceable();
} }
return super.apply(source); return this;
} }
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
if (!source.isClient()) { if (!source.isClient()) {
World world = source.asWorld(); World world = source.asWorld();
@ -86,7 +89,11 @@ public class HydrophobicSpell extends AbstractSpell {
setDead(); 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); BlockPos bp = BlockPos.ofFloored(pos);
if (source.asWorld().getFluidState(bp.up()).isIn(affectedFluid)) { if (source.asWorld().getFluidState(bp.up()).isIn(affectedFluid)) {
source.addParticle(UParticles.RAIN_DROPS, pos, Vec3d.ZERO); source.addParticle(UParticles.RAIN_DROPS, pos, Vec3d.ZERO);
@ -107,6 +114,7 @@ public class HydrophobicSpell extends AbstractSpell {
@Override @Override
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
Ether.get(caster.asWorld()).remove(this, caster);
storedFluidPositions.removeIf(entry -> { storedFluidPositions.removeIf(entry -> {
entry.restore(caster.asWorld()); entry.restore(caster.asWorld());
return true; return true;
@ -162,13 +170,20 @@ public class HydrophobicSpell extends AbstractSpell {
} }
} }
public boolean blocksFlow(Caster<?> caster, BlockPos pos, FluidState fluid) { public boolean blocksFlow(Ether.Entry<?> entry, Vec3d center, BlockPos pos, FluidState fluid) {
return fluid.isIn(affectedFluid) && pos.isWithinDistance(caster.getOrigin(), getRange(caster) + 1); return fluid.isIn(affectedFluid) && pos.isWithinDistance(center, (double)entry.radius + 1);
} }
public static boolean blocksFluidFlow(CasterView world, BlockPos pos, FluidState state) { public static boolean blocksFluidFlow(BlockView world, BlockPos pos, FluidState state) {
return world.findAllSpellsInRange(pos, 500, SpellType.HYDROPHOBIC).anyMatch(pair -> { if (world instanceof ServerWorld sw) {
return pair.getValue().blocksFlow(pair.getKey(), pos, state); 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;
} }
} }

View file

@ -1,6 +1,9 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect; package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia; 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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
import com.minelittlepony.unicopia.particle.*; import com.minelittlepony.unicopia.particle.*;
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.shape.*; import com.minelittlepony.unicopia.util.shape.*;
@ -20,8 +23,10 @@ import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.nbt.NbtCompound; 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.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.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.WorldEvents; import net.minecraft.world.WorldEvents;
@ -34,12 +39,14 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
.build(); .build();
private static final Shape PARTICLE_AREA = new Sphere(true, 2, 1, 1, 0); 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<Entity> teleportationTarget = new EntityReference<>(); private final EntityReference<Entity> teleportationTarget = new EntityReference<>();
private boolean publishedPosition; private boolean publishedPosition;
private final ParticleHandle particleEffect = new ParticleHandle();
private float pitch; private float pitch;
private float yaw; private float yaw;
@ -49,6 +56,39 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
super(type); super(type);
} }
public boolean isLinked() {
return teleportationTarget.getTarget().isPresent();
}
public Optional<EntityReference.EntityValues<Entity>> 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<Ether.Entry<PortalSpell>> getTarget(Caster<?> source) {
return getTarget().map(target -> Ether.get(source.asWorld()).get((SpellType<PortalSpell>)getType(), target, targetPortalId));
}
@Override @Override
public boolean apply(Caster<?> caster) { public boolean apply(Caster<?> caster) {
setOrientation(caster.asEntity().getPitch(), caster.asEntity().getYaw()); setOrientation(caster.asEntity().getPitch(), caster.asEntity().getYaw());
@ -60,29 +100,12 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
if (situation == Situation.GROUND) { if (situation == Situation.GROUND) {
if (source.isClient()) { if (source.isClient()) {
Vec3d origin = source.getOriginVector(); source.spawnParticles(particleArea, 5, pos -> {
source.addParticle(ParticleTypes.ELECTRIC_SPARK, pos, Vec3d.ZERO);
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();
}); });
} else { } else {
teleportationTarget.getTarget().ifPresent(target -> { getTarget().ifPresent(target -> {
if (Ether.get(source.asWorld()).getEntry(getType(), target.uuid()).isEmpty()) { if (Ether.get(source.asWorld()).get(getType(), target, targetPortalId) == null) {
Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + target.uuid()); Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + target.uuid());
teleportationTarget.set(null); teleportationTarget.set(null);
setDirty(); setDirty();
@ -96,37 +119,56 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
); );
} }
if (!publishedPosition) { var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
publishedPosition = true; entry.pitch = pitch;
Ether.Entry entry = Ether.get(source.asWorld()).put(getType(), source); entry.yaw = yaw;
entry.pitch = pitch; Ether.get(source.asWorld()).markDirty();
entry.yaw = yaw;
}
} }
return !isDead(); 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 -> { destination.entity.getTarget().ifPresent(target -> {
source.findAllEntitiesInRange(1).forEach(entity -> { 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()); Vec3d offset = entity.getPos().subtract(source.getOriginVector());
float yawDifference = pitch < 15 ? (180 - yaw + destination.yaw) : 0; float yawDifference = pitch < 15 ? getYawDifference() : 0;
Vec3d dest = target.pos().add(offset.rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE)).add(0, 0.05, 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.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.setVelocity(entity.getVelocity().rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE));
entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1); 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); entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1);
setDirty(); setDirty();
Living.updateVelocity(entity);
if (!source.subtractEnergyCost(Math.sqrt(entity.getPos().subtract(dest).length()))) { if (!source.subtractEnergyCost(Math.sqrt(entity.getPos().subtract(dest).length()))) {
setDead(); setDead();
} }
@ -142,20 +184,15 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
return; return;
} }
Ether ether = Ether.get(source.asWorld()); Ether.get(source.asWorld()).anyMatch(getType(), entry -> {
ether.getEntries(getType()) if (entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.isSet()) {
.stream()
.filter(entry -> entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.isSet())
.findAny()
.ifPresent(entry -> {
entry.setTaken(true); entry.setTaken(true);
teleportationTarget.copyFrom(entry.entity); teleportationTarget.copyFrom(entry.entity);
targetPortalId = entry.getSpellId();
setDirty(); setDirty();
}); }
} return false;
});
private Optional<Ether.Entry> getTarget(Caster<?> source) {
return teleportationTarget.getTarget().flatMap(target -> Ether.get(source.asWorld()).getEntry(getType(), target.uuid()));
} }
@Override @Override
@ -174,41 +211,40 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
LivingEntity caster = source.getMaster(); LivingEntity caster = source.getMaster();
Vec3d targetPos = caster.getRotationVector().multiply(3).add(caster.getEyePos()); Vec3d targetPos = caster.getRotationVector().multiply(3).add(caster.getEyePos());
parent.setOrientation(pitch, yaw); parent.setOrientation(pitch, yaw);
entity.setPos(targetPos.x, caster.getY() + 1.5, targetPos.z); entity.setPos(targetPos.x, Math.abs(pitch) > 15 ? targetPos.y : caster.getPos().y, 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);
});
} }
@Override @Override
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
particleEffect.destroy();
Ether ether = Ether.get(caster.asWorld()); Ether ether = Ether.get(caster.asWorld());
ether.remove(getType(), caster.asEntity().getUuid()); ether.remove(getType(), caster);
getTarget(caster).ifPresent(e -> e.setTaken(false)); getTarget(caster).ifPresent(e -> e.setTaken(false));
} }
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
super.toNBT(compound); super.toNBT(compound);
if (targetPortalId != null) {
compound.putUuid("targetPortalId", targetPortalId);
}
compound.putBoolean("publishedPosition", publishedPosition); compound.putBoolean("publishedPosition", publishedPosition);
compound.put("teleportationTarget", teleportationTarget.toNBT()); compound.put("teleportationTarget", teleportationTarget.toNBT());
compound.putFloat("pitch", pitch); compound.putFloat("pitch", pitch);
compound.putFloat("yaw", yaw); compound.putFloat("yaw", yaw);
compound.putFloat("targetPortalPitch", targetPortalPitch);
compound.putFloat("targetPortalYaw", targetPortalYaw);
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
targetPortalId = compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : null;
publishedPosition = compound.getBoolean("publishedPosition"); publishedPosition = compound.getBoolean("publishedPosition");
teleportationTarget.fromNBT(compound.getCompound("teleportationTarget")); teleportationTarget.fromNBT(compound.getCompound("teleportationTarget"));
pitch = compound.getFloat("pitch"); pitch = compound.getFloat("pitch");
yaw = compound.getFloat("yaw"); yaw = compound.getFloat("yaw");
targetPortalPitch = compound.getFloat("targetPortalPitch");
targetPortalYaw = compound.getFloat("targetPortalYaw");
particleArea = PARTICLE_AREA.rotate( particleArea = PARTICLE_AREA.rotate(
pitch * MathHelper.RADIANS_PER_DEGREE, pitch * MathHelper.RADIANS_PER_DEGREE,
(180 - yaw) * MathHelper.RADIANS_PER_DEGREE (180 - yaw) * MathHelper.RADIANS_PER_DEGREE

View file

@ -1,5 +1,7 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect; package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.Optional;
import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia; 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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleHandle; import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.particle.SphereParticleEffect;
import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.projectile.ProjectileUtil; import com.minelittlepony.unicopia.projectile.ProjectileUtil;
import com.minelittlepony.unicopia.util.shape.Sphere; 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.player.PlayerEntity;
import net.minecraft.entity.vehicle.AbstractMinecartEntity; import net.minecraft.entity.vehicle.AbstractMinecartEntity;
import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
public class ShieldSpell extends AbstractSpell { public class ShieldSpell extends AbstractSpell {
@ -40,9 +41,16 @@ public class ShieldSpell extends AbstractSpell {
.with(Trait.AIR, 9) .with(Trait.AIR, 9)
.build(); .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) { protected ShieldSpell(CustomisedSpellType<?> type) {
super(type); super(type);
@ -53,33 +61,27 @@ public class ShieldSpell extends AbstractSpell {
return method == CastingMethod.STAFF || getTraits().get(Trait.GENEROSITY) > 0 ? toPlaceable() : this; return method == CastingMethod.STAFF || getTraits().get(Trait.GENEROSITY) > 0 ? toPlaceable() : this;
} }
@Override
protected void onDestroyed(Caster<?> caster) {
particlEffect.destroy();
}
@Override @Override
public Affinity getAffinity() { public Affinity getAffinity() {
return getTraits().get(Trait.DARKNESS) > 0 ? Affinity.BAD : Affinity.GOOD; return getTraits().get(Trait.DARKNESS) > 0 ? Affinity.BAD : Affinity.GOOD;
} }
protected void generateParticles(Caster<?> source) { protected void generateParticles(Caster<?> source) {
float radius = (float)getDrawDropOffRange(source);
Vec3d origin = getOrigin(source); Vec3d origin = getOrigin(source);
source.spawnParticles(origin, new Sphere(true, radius), (int)(radius * 6), pos -> { source.spawnParticles(origin, new Sphere(true, radius), (int)(radius * 6), pos -> {
source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO); source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO);
});
particlEffect.update(getUuid(), source, spawner -> { if (source.asWorld().random.nextInt(10) == 0 && source.asWorld().random.nextFloat() < source.getCorruption().getScaled(1)) {
spawner.addParticle(new SphereParticleEffect(UParticles.SPHERE, getType().getColor(), 0.3F, radius), origin, Vec3d.ZERO); ParticleUtils.spawnParticle(source.asWorld(), new LightningBoltParticleEffect(true, 3, 2, 0.1F, Optional.empty()), pos, Vec3d.ZERO);
}).ifPresent(p -> { }
p.setAttribute(Attachment.ATTR_RADIUS, radius);
}); });
} }
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
prevRadius = radius;
radius = (float)getDrawDropOffRange(source);
if (source.isClient()) { if (source.isClient()) {
generateParticles(source); generateParticles(source);
@ -97,27 +99,53 @@ public class ShieldSpell extends AbstractSpell {
long costMultiplier = applyEntities(source); long costMultiplier = applyEntities(source);
if (costMultiplier > 0) { if (costMultiplier > 0) {
double cost = 2 - source.getLevel().getScaled(2); consumeManage(source, costMultiplier, knowledge);
cost *= costMultiplier / ((1 + source.getLevel().get()) * 3F);
cost /= knowledge;
cost += getDrawDropOffRange(source) / 10F;
if (!source.subtractEnergyCost(cost)) {
setDead();
}
} }
return !isDead(); 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. * Calculates the maximum radius of the shield. aka The area of effect.
*/ */
public double getDrawDropOffRange(Caster<?> source) { 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); 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; return range;
} }
@ -146,11 +174,8 @@ public class ShieldSpell extends AbstractSpell {
} }
protected long applyEntities(Caster<?> source) { protected long applyEntities(Caster<?> source) {
double radius = getDrawDropOffRange(source);
Vec3d origin = getOrigin(source); Vec3d origin = getOrigin(source);
targetSelecter.getEntities(source, radius).forEach(i -> {
targetSelecter.getEntities(source, radius, this::isValidTarget).forEach(i -> {
try { try {
applyRadialEffect(source, i, i.getPos().distanceTo(origin), radius); applyRadialEffect(source, i, i.getPos().distanceTo(origin), radius);
} catch (Throwable e) { } catch (Throwable e) {

View file

@ -10,28 +10,32 @@ import java.util.stream.Stream;
import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.Caster; 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.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.entity.Entity;
import net.minecraft.predicate.entity.EntityPredicates;
public class TargetSelecter { public class TargetSelecter {
private final Map<UUID, Target> targets = new TreeMap<>(); private final Map<UUID, Target> targets = new TreeMap<>();
private final Spell spell; private final Spell spell;
private BiPredicate<Caster<?>, Entity> filter = (a, b) -> true;
public TargetSelecter(Spell spell) { public TargetSelecter(Spell spell) {
this.spell = spell; this.spell = spell;
} }
public Stream<Entity> getEntities(Caster<?> source, double radius, BiPredicate<Caster<?>, Entity> filter) { public TargetSelecter setFilter(BiPredicate<Caster<?>, Entity> filter) {
this.filter = filter;
return this;
}
public Stream<Entity> getEntities(Caster<?> source, double radius) {
targets.values().removeIf(Target::tick); targets.values().removeIf(Target::tick);
return source.findAllEntitiesInRange(radius) 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(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 -> { .map(i -> {
targets.computeIfAbsent(i.getUuid(), Target::new); targets.computeIfAbsent(i.getUuid(), Target::new);
return i; return i;
@ -42,30 +46,19 @@ public class TargetSelecter {
return targets.values().stream().filter(Target::canHurt).count(); return targets.values().stream().filter(Target::canHurt).count();
} }
public static <T extends Entity> Predicate<T> notOwnerOrFriend(Affine affine, Caster<?> source) { public static <T extends Entity> Predicate<T> validTarget(Affine affine, Caster<?> source) {
return target -> notOwnerOrFriend(affine, source, target); return target -> validTarget(affine, source, target);
} }
public static <T extends Entity> Predicate<T> isOwnerOrFriend(Affine affine, Caster<?> source) { public static boolean validTarget(Affine affine, Caster<?> source, Entity target) {
return target -> isOwnerOrFriend(affine, source, target);
}
public static <T extends Entity> boolean notOwnerOrFriend(Affine affine, Caster<?> source, Entity target) {
return !isOwnerOrFriend(affine, source, target); return !isOwnerOrFriend(affine, source, target);
} }
public static <T extends Entity> boolean isOwnerOrFriend(Affine affine, Caster<?> source, Entity target) { public static boolean isOwnerOrFriend(Affine affine, Caster<?> source, Entity target) {
Entity owner = source.getMaster(); return affine.applyInversion(source, source.isOwnerOrFriend(target));
if (affine.isEnemy(source)) {
return FriendshipBraceletItem.isComrade(source, target);
}
return FriendshipBraceletItem.isComrade(source, target)
|| (owner != null && (Pony.equal(target, owner) || owner.isConnectedThroughVehicle(target)));
} }
static final class Target { private static final class Target {
private int cooldown = 20; private int cooldown = 20;
Target(UUID id) { } Target(UUID id) { }

View file

@ -0,0 +1,5 @@
package com.minelittlepony.unicopia.ability.magic.spell.trait;
public interface ItemWithTraits {
SpellTraits getDefaultTraits();
}

View file

@ -29,6 +29,7 @@ import net.fabricmc.api.Environment;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.inventory.Inventory; import net.minecraft.inventory.Inventory;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.SpawnEggItem;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtElement;
@ -41,6 +42,7 @@ import net.minecraft.registry.Registries;
public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> { public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
public static final SpellTraits EMPTY = new SpellTraits(Map.of()); 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<Identifier, SpellTraits> REGISTRY = new HashMap<>(); private static Map<Identifier, SpellTraits> REGISTRY = new HashMap<>();
static final Map<Trait, List<Item>> ITEMS = new HashMap<>(); static final Map<Trait, List<Item>> ITEMS = new HashMap<>();
@ -220,6 +222,12 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
} }
public static SpellTraits of(Item item) { 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); return REGISTRY.getOrDefault(Registries.ITEM.getId(item), EMPTY);
} }

View file

@ -14,7 +14,7 @@ import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.*; import net.minecraft.world.*;
public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock { public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock, ZapStagedBlock {
public static final MapCodec<BaseZapAppleLeavesBlock> CODEC = LeavesBlock.createCodec(BaseZapAppleLeavesBlock::new); public static final MapCodec<BaseZapAppleLeavesBlock> CODEC = LeavesBlock.createCodec(BaseZapAppleLeavesBlock::new);
public static Settings settings() { public static Settings settings() {
@ -39,61 +39,29 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
} }
@Override @Override
public boolean hasRandomTicks(BlockState state) { public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
return !state.get(PERSISTENT); if (state.get(PERSISTENT) || oldState.isOf(state.getBlock())) {
} return;
@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;
} }
updateStage(state, world, pos);
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;
} }
@Override @Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.scheduledTick(state, world, pos, 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)) { if (state.get(PERSISTENT)) {
return; return;
} }
tryAdvanceStage(state, world, pos, random);
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);
}
} }
protected ZapAppleStageStore.Stage getStage(BlockState state) { @Override
public ZapAppleStageStore.Stage getStage(BlockState state) {
return ZapAppleStageStore.Stage.FLOWERING; return ZapAppleStageStore.Stage.FLOWERING;
} }
@Override @Override
protected boolean shouldDecay(BlockState state) { protected final boolean shouldDecay(BlockState state) {
return false; return false;
} }
@ -124,40 +92,10 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
@Override @Override
public int getTint(BlockState state, @Nullable BlockRenderView world, @Nullable BlockPos pos, int foliageColor) { public int getTint(BlockState state, @Nullable BlockRenderView world, @Nullable BlockPos pos, int foliageColor) {
if (pos == null) { if (pos == null) {
return 0x4C7EFA; return 0x4C7EFA;
} }
return TintedBlock.blend(TintedBlock.rotate(foliageColor, 2), 0x0000FF, 0.3F); 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());
}
}
}
} }

View file

@ -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<CuringJokeBlock> 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<SuspiciousStewIngredient.StewEffect> 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;
}
}

View file

@ -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<EdibleBlock> 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<BlockState, VoxelShape> 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<Block, BlockState> 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;
}
}

View file

@ -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<EnchantedFruitBlock> 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<Block, BlockState> builder) {
super.appendProperties(builder);
builder.add(ENCHANTED);
}
}

View file

@ -52,7 +52,7 @@ public class FancyBedBlock extends BedBlock {
)) ))
); );
private final String base; protected final String base;
public FancyBedBlock(String base, Settings settings) { public FancyBedBlock(String base, Settings settings) {
super(DyeColor.WHITE, settings); super(DyeColor.WHITE, settings);

View file

@ -42,10 +42,10 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
public static final List<FruitBearingBlock> REGISTRY = new ArrayList<>(); public static final List<FruitBearingBlock> REGISTRY = new ArrayList<>();
private final Supplier<Block> fruit; protected final Supplier<Block> fruit;
private final Supplier<ItemStack> rottenFruitSupplier; protected final Supplier<ItemStack> rottenFruitSupplier;
private final int overlay; protected final int overlay;
public FruitBearingBlock(int overlay, Supplier<Block> fruit, Supplier<ItemStack> rottenFruitSupplier, Settings settings) { public FruitBearingBlock(int overlay, Supplier<Block> fruit, Supplier<ItemStack> rottenFruitSupplier, Settings settings) {
super(settings super(settings
@ -78,6 +78,14 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
return true; return true;
} }
protected boolean shouldAdvance(Random random) {
return true;
}
protected BlockState getPlacedFruitState(Random random) {
return fruit.get().getDefaultState();
}
@Override @Override
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.randomTick(state, world, pos, random); super.randomTick(state, world, pos, random);
@ -86,10 +94,14 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
return; return;
} }
if (world.isDay()) { if (world.getBaseLightLevel(pos, 0) > 8) {
BlockSoundGroup group = getSoundGroup(state); BlockSoundGroup group = getSoundGroup(state);
int steps = FertilizableUtil.getGrowthSteps(world, pos, state, random); int steps = FertilizableUtil.getGrowthSteps(world, pos, state, random);
while (steps-- > 0) { while (steps-- > 0) {
if (!shouldAdvance(random)) {
continue;
}
if (state.get(STAGE) == Stage.FRUITING) { if (state.get(STAGE) == Stage.FRUITING) {
state = state.cycle(AGE); state = state.cycle(AGE);
if (state.get(AGE) > 20) { 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 (stage == Stage.FRUITING && isPositionValidForFruit(state, pos)) {
if (world.isAir(fruitPosition)) { if (world.isAir(fruitPosition)) {
world.setBlockState(fruitPosition, fruit.get().getDefaultState(), Block.NOTIFY_ALL); world.setBlockState(fruitPosition, getPlacedFruitState(random), Block.NOTIFY_ALL);
} }
} }

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.block;
import java.util.List; import java.util.List;
import com.minelittlepony.unicopia.ability.EarthPonyKickAbility.Buckable; import com.minelittlepony.unicopia.ability.EarthPonyKickAbility.Buckable;
import com.mojang.datafixers.util.Function5;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec; import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder; 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 int DEFAULT_FRUIT_SIZE = 5;
public static final double DEFAULT_STEM_OFFSET = 2.6F; 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 VoxelShape DEFAULT_SHAPE = createFruitShape(DEFAULT_STEM_OFFSET, DEFAULT_FRUIT_SIZE);
public static final MapCodec<FruitBlock> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( private static final MapCodec<FruitBlock> CODEC = createCodec(FruitBlock::new);
Direction.CODEC.fieldOf("attachment_face").forGetter(b -> b.attachmentFace),
Registries.BLOCK.getCodec().fieldOf("stem").forGetter(b -> b.stem),
RecordCodecBuilder.<VoxelShape>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 final Direction attachmentFace; protected final Direction attachmentFace;
private final Block stem; protected final Block stem;
private final VoxelShape shape; protected final VoxelShape shape;
public static <T extends FruitBlock> MapCodec<T> createCodec(Function5<Direction, Block, VoxelShape, Boolean, Settings, T> 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.<VoxelShape>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) { public static VoxelShape createFruitShape(double stemOffset, double fruitSize) {
final double min = (16 - fruitSize) * 0.5; final double min = (16 - fruitSize) * 0.5;

View file

@ -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<GoldenOakLeavesBlock> CODEC = RecordCodecBuilder.<GoldenOakLeavesBlock>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<Block> fruit, Supplier<ItemStack> 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);
}
}

View file

@ -21,6 +21,7 @@ import com.minelittlepony.unicopia.block.cloud.SoggyCloudBlock;
import com.minelittlepony.unicopia.block.cloud.SoggyCloudSlabBlock; import com.minelittlepony.unicopia.block.cloud.SoggyCloudSlabBlock;
import com.minelittlepony.unicopia.block.cloud.SoggyCloudStairsBlock; import com.minelittlepony.unicopia.block.cloud.SoggyCloudStairsBlock;
import com.minelittlepony.unicopia.block.cloud.UnstableCloudBlock; 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.UItems;
import com.minelittlepony.unicopia.item.cloud.CloudBlockItem; import com.minelittlepony.unicopia.item.cloud.CloudBlockItem;
import com.minelittlepony.unicopia.item.group.ItemGroupRegistry; 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.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.registry.FlammableBlockRegistry; import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
import net.fabricmc.fabric.api.registry.StrippableBlockRegistry; import net.fabricmc.fabric.api.registry.StrippableBlockRegistry;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.block.*; import net.minecraft.block.*;
import net.minecraft.block.AbstractBlock.Settings; import net.minecraft.block.AbstractBlock.Settings;
import net.minecraft.block.entity.BlockEntityType; import net.minecraft.block.entity.BlockEntityType;
@ -136,6 +138,15 @@ public interface UBlocks {
() -> UItems.APPLE_PIE_HOOF, () -> UItems.APPLE_PIE_HOOF,
Settings.create().solid().mapColor(MapColor.ORANGE).strength(0.5F).sounds(BlockSoundGroup.WOOL).pistonBehavior(PistonBehavior.DESTROY) 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 = 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)); 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 = 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))); 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 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); 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 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 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.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 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); 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(), Settings.copy(CLOUD), () -> UBlocks.SOGGY_CLOUD_STAIRS), 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, 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);
null,
() -> UBlocks.COMPACTED_CLOUD_PLANKS), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_CLOUD_PLANKS = register("compacted_cloud_planks", new CompactedCloudBlock(CLOUD_PLANKS.getDefaultState())); 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_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, 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);
null,
() -> UBlocks.COMPACTED_CLOUD_BRICKS), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_CLOUD_BRICKS = register("compacted_cloud_bricks", new CompactedCloudBlock(CLOUD_BRICKS.getDefaultState())); 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 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, Block ETCHED_CLOUD = register("etched_cloud", new NaturalCloudBlock(false, null, () -> UBlocks.COMPACTED_CLOUD_BRICKS, Settings.copy(CLOUD_BRICKS)), ItemGroups.BUILDING_BLOCKS);
null,
() -> UBlocks.COMPACTED_CLOUD_BRICKS), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_ETCHED_CLOUD = register("compacted_etched_cloud", new CompactedCloudBlock(ETCHED_CLOUD.getDefaultState())); 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); 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)); 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(Settings.copy(SOGGY_CLOUD), () -> UBlocks.CLOUD_SLAB)); 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(), Settings.copy(CLOUD), () -> UBlocks.CLOUD_STAIRS)); 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, 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);
null,
() -> UBlocks.COMPACTED_DENSE_CLOUD), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_DENSE_CLOUD = register("compacted_dense_cloud", new CompactedCloudBlock(DENSE_CLOUD.getDefaultState())); 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 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 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_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 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))); 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 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 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 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 extends Block> T register(String name, T item) { private static <T extends Block> T register(String name, T item) {
return register(Unicopia.id(name), item); return register(Unicopia.id(name), item);
@ -234,10 +247,15 @@ public interface UBlocks {
if (block instanceof CloudLike || block instanceof SlimePustuleBlock || block instanceof PileBlock) { if (block instanceof CloudLike || block instanceof SlimePustuleBlock || block instanceof PileBlock) {
SEMI_TRANSPARENT_BLOCKS.add(block); SEMI_TRANSPARENT_BLOCKS.add(block);
} }
return Registry.register(Registries.BLOCK, id, block); return Registry.register(Registries.BLOCK, id, block);
} }
static void bootstrap() { 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.SIGN).addSupportedBlocks(PALM_SIGN, PALM_WALL_SIGN);
BlockEntityTypeSupportHelper.of(BlockEntityType.HANGING_SIGN).addSupportedBlocks(PALM_HANGING_SIGN, PALM_WALL_HANGING_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(PALM_LOG, STRIPPED_PALM_LOG);
StrippableBlockRegistry.register(ZAP_WOOD, STRIPPED_ZAP_WOOD); StrippableBlockRegistry.register(ZAP_WOOD, STRIPPED_ZAP_WOOD);
StrippableBlockRegistry.register(PALM_WOOD, STRIPPED_PALM_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); TintedBlock.REGISTRY.add(PALM_LEAVES);
FlammableBlockRegistry.getDefaultInstance().add(GREEN_APPLE_LEAVES, 30, 60); FlammableBlockRegistry.getDefaultInstance().add(GREEN_APPLE_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(SWEET_APPLE_LEAVES, 30, 60); FlammableBlockRegistry.getDefaultInstance().add(SWEET_APPLE_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(SOUR_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(MANGO_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(PALM_LEAVES, 30, 60); FlammableBlockRegistry.getDefaultInstance().add(PALM_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(PALM_LOG, 5, 5); FlammableBlockRegistry.getDefaultInstance().add(PALM_LOG, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(PALM_WOOD, 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_LOG, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(STRIPPED_PALM_WOOD, 5, 5); FlammableBlockRegistry.getDefaultInstance().add(STRIPPED_PALM_WOOD, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(PALM_PLANKS, 5, 20); FlammableBlockRegistry.getDefaultInstance().add(PALM_PLANKS, 5, 20);
FlammableBlockRegistry.getDefaultInstance().add(BANANAS, 5, 20); FlammableBlockRegistry.getDefaultInstance().add(BANANAS, 5, 20);
UBlockEntities.bootstrap(); UBlockEntities.bootstrap();
EdibleBlock.bootstrap();
} }
} }

View file

@ -15,6 +15,7 @@ import net.minecraft.util.Identifier;
public interface UWoodTypes { public interface UWoodTypes {
WoodType PALM = register("palm"); WoodType PALM = register("palm");
WoodType GOLDEN_OAK = register("golden_oak");
RegistryKey<TerraformBoatType> PALM_BOAT_TYPE = TerraformBoatTypeRegistry.createKey(Unicopia.id("palm")); RegistryKey<TerraformBoatType> PALM_BOAT_TYPE = TerraformBoatTypeRegistry.createKey(Unicopia.id("palm"));
BlockSetType CLOUD = new BlockSetTypeBuilder() BlockSetType CLOUD = new BlockSetTypeBuilder()

View file

@ -5,11 +5,8 @@ import com.mojang.serialization.MapCodec;
import net.minecraft.block.*; import net.minecraft.block.*;
import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemPlacementContext;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.state.property.*; import net.minecraft.state.property.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock { public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock {
public static final MapCodec<ZapAppleLeavesBlock> CODEC = createCodec(ZapAppleLeavesBlock::new); public static final MapCodec<ZapAppleLeavesBlock> CODEC = createCodec(ZapAppleLeavesBlock::new);
@ -25,31 +22,13 @@ public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock {
return CODEC; 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 @Override
public BlockState getPlacementState(ItemPlacementContext ctx) { public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(STAGE, ZapAppleStageStore.Stage.GREENING); return super.getPlacementState(ctx).with(STAGE, ZapAppleStageStore.Stage.GREENING);
} }
@Override @Override
protected ZapAppleStageStore.Stage getStage(BlockState state) { public ZapAppleStageStore.Stage getStage(BlockState state) {
return state.get(STAGE); return state.get(STAGE);
} }

View file

@ -1,15 +1,16 @@
package com.minelittlepony.unicopia.block; package com.minelittlepony.unicopia.block;
import com.minelittlepony.unicopia.server.world.ZapAppleStageStore; import com.minelittlepony.unicopia.server.world.ZapAppleStageStore;
import com.minelittlepony.unicopia.server.world.ZapAppleStageStore.Stage;
import com.mojang.serialization.MapCodec; import com.mojang.serialization.MapCodec;
import net.minecraft.block.*; import net.minecraft.block.*;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.*; import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random; 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<ZapAppleLeavesPlaceholderBlock> CODEC = createCodec(ZapAppleLeavesPlaceholderBlock::new); public static final MapCodec<ZapAppleLeavesPlaceholderBlock> CODEC = createCodec(ZapAppleLeavesPlaceholderBlock::new);
ZapAppleLeavesPlaceholderBlock(Settings settings) { ZapAppleLeavesPlaceholderBlock(Settings settings) {
@ -23,37 +24,19 @@ public class ZapAppleLeavesPlaceholderBlock extends AirBlock {
} }
@Override @Override
public boolean hasRandomTicks(BlockState state) { public Stage getStage(BlockState state) {
return true; return ZapAppleStageStore.Stage.HIBERNATING;
} }
@Override @Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) { public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
updateStage(state, world, pos);
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;
} }
@Deprecated @Deprecated
@Override @Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.scheduledTick(state, world, pos, random); super.scheduledTick(state, world, pos, random);
tryAdvanceStage(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);
} }
} }

View file

@ -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());
}
}
}
}

View file

@ -4,6 +4,11 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext; import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.block.FancyBedBlock; 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.BlockState;
import net.minecraft.block.ShapeContext; import net.minecraft.block.ShapeContext;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@ -20,6 +25,12 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class CloudBedBlock extends FancyBedBlock implements CloudLike { public class CloudBedBlock extends FancyBedBlock implements CloudLike {
private static final MapCodec<CloudBedBlock> 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 BlockState baseState;
private final CloudBlock baseBlock; private final CloudBlock baseBlock;
@ -29,6 +40,12 @@ public class CloudBedBlock extends FancyBedBlock implements CloudLike {
this.baseBlock = (CloudBlock)baseState.getBlock(); this.baseBlock = (CloudBlock)baseState.getBlock();
} }
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public MapCodec<BedBlock> getCodec() {
return (MapCodec)CODEC;
}
@Override @Override
public final VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { public final VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
if (!baseBlock.canInteract(baseState, world, pos, EquineContext.of(context))) { if (!baseBlock.canInteract(baseState, world, pos, EquineContext.of(context))) {

View file

@ -4,7 +4,11 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext; import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.entity.player.Pony; 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.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext; import net.minecraft.block.ShapeContext;
@ -27,13 +31,23 @@ import net.minecraft.world.LightType;
import net.minecraft.world.World; import net.minecraft.world.World;
public class CloudBlock extends Block implements CloudLike { public class CloudBlock extends Block implements CloudLike {
private static final MapCodec<CloudBlock> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
Codec.BOOL.fieldOf("meltable").forGetter(b -> b.meltable),
BedBlock.createSettingsCodec()
).apply(instance, CloudBlock::new));
protected final boolean meltable; protected final boolean meltable;
public CloudBlock(Settings settings, boolean meltable) { public CloudBlock(boolean meltable, Settings settings) {
super((meltable ? settings.ticksRandomly() : settings).nonOpaque()); super((meltable ? settings.ticksRandomly() : settings).nonOpaque());
this.meltable = meltable; this.meltable = meltable;
} }
@Override
public MapCodec<? extends CloudBlock> getCodec() {
return CODEC;
}
@Override @Override
public void onEntityLand(BlockView world, Entity entity) { public void onEntityLand(BlockView world, Entity entity) {
boolean bounce = Math.abs(entity.getVelocity().y) > 0.3; boolean bounce = Math.abs(entity.getVelocity().y) > 0.3;

View file

@ -6,10 +6,14 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext; import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.block.UBlockEntities; 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.BlockState;
import net.minecraft.block.ChestBlock; import net.minecraft.block.ChestBlock;
import net.minecraft.block.DoubleBlockProperties; import net.minecraft.block.DoubleBlockProperties;
import net.minecraft.block.ShapeContext; import net.minecraft.block.ShapeContext;
import net.minecraft.block.StairsBlock;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType; import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.block.entity.ChestBlockEntity; import net.minecraft.block.entity.ChestBlockEntity;
@ -33,6 +37,10 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class CloudChestBlock extends ChestBlock implements CloudLike { public class CloudChestBlock extends ChestBlock implements CloudLike {
private static final MapCodec<CloudChestBlock> 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 BlockState baseState;
private final CloudBlock baseBlock; 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); super(settings, () -> UBlockEntities.CLOUD_CHEST);
this.baseState = baseState; this.baseState = baseState;
this.baseBlock = (CloudBlock)baseState.getBlock(); this.baseBlock = (CloudBlock)baseState.getBlock();
} }
@Override
public MapCodec<? extends ChestBlock> getCodec() {
return CODEC;
}
@Override @Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new TileData(pos, state); return new TileData(pos, state);

View file

@ -4,6 +4,8 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext; import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.Race; 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.BlockSetType;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -22,15 +24,24 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class CloudDoorBlock extends DoorBlock implements CloudLike { public class CloudDoorBlock extends DoorBlock implements CloudLike {
private static final MapCodec<CloudDoorBlock> 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 BlockState baseState;
private final CloudBlock baseBlock; private final CloudBlock baseBlock;
public CloudDoorBlock(Settings settings, BlockState baseState, BlockSetType blockSet) { public CloudDoorBlock(BlockState baseState, BlockSetType blockSet, Settings settings) {
super(blockSet, settings); super(blockSet, settings);
this.baseState = baseState; this.baseState = baseState;
this.baseBlock = (CloudBlock)baseState.getBlock(); this.baseBlock = (CloudBlock)baseState.getBlock();
} }
@Override
public MapCodec<? extends CloudDoorBlock> getCodec() {
return CODEC;
}
@Override @Override
public final VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { public final VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {

View file

@ -5,6 +5,7 @@ import java.util.Map;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext; import com.minelittlepony.unicopia.EquineContext;
import com.mojang.serialization.MapCodec;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -20,6 +21,7 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.WorldAccess; import net.minecraft.world.WorldAccess;
public class CloudPillarBlock extends CloudBlock { public class CloudPillarBlock extends CloudBlock {
private static final MapCodec<CloudPillarBlock> CODEC = Block.createCodec(CloudPillarBlock::new);
private static final BooleanProperty NORTH = BooleanProperty.of("north"); private static final BooleanProperty NORTH = BooleanProperty.of("north");
private static final BooleanProperty SOUTH = BooleanProperty.of("south"); private static final BooleanProperty SOUTH = BooleanProperty.of("south");
private static final Map<Direction, BooleanProperty> DIRECTION_PROPERTIES = Map.of( private static final Map<Direction, BooleanProperty> DIRECTION_PROPERTIES = Map.of(
@ -41,10 +43,15 @@ public class CloudPillarBlock extends CloudBlock {
// [1,0] [1,1] // [1,0] [1,1]
public CloudPillarBlock(Settings settings) { public CloudPillarBlock(Settings settings) {
super(settings, false); super(false, settings);
setDefaultState(getDefaultState().with(NORTH, true).with(SOUTH, true)); setDefaultState(getDefaultState().with(NORTH, true).with(SOUTH, true));
} }
@Override
public MapCodec<CloudPillarBlock> getCodec() {
return CODEC;
}
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(NORTH, SOUTH); builder.add(NORTH, SOUTH);

View file

@ -5,7 +5,12 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext; 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.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext; import net.minecraft.block.ShapeContext;
@ -24,14 +29,24 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.WorldAccess; import net.minecraft.world.WorldAccess;
public class CloudSlabBlock extends WaterloggableCloudBlock { public class CloudSlabBlock extends WaterloggableCloudBlock {
private static final MapCodec<WaterloggableCloudBlock> 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 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); 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<Soakable> soggyBlock) { public CloudSlabBlock(boolean meltable, @Nullable Supplier<Soakable> soggyBlock, Settings settings) {
super(settings, meltable, soggyBlock); super(meltable, soggyBlock, settings);
setDefaultState(getDefaultState().with(SlabBlock.TYPE, SlabType.BOTTOM)); setDefaultState(getDefaultState().with(SlabBlock.TYPE, SlabType.BOTTOM));
} }
@Override
public MapCodec<? extends WaterloggableCloudBlock> getCodec() {
return CODEC;
}
@Override @Override
public boolean hasSidedTransparency(BlockState state) { public boolean hasSidedTransparency(BlockState state) {
return state.get(SlabBlock.TYPE) != SlabType.DOUBLE; return state.get(SlabBlock.TYPE) != SlabType.DOUBLE;

View file

@ -3,6 +3,9 @@ package com.minelittlepony.unicopia.block.cloud;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext; 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.BlockState;
import net.minecraft.block.ShapeContext; import net.minecraft.block.ShapeContext;
import net.minecraft.block.StairsBlock; import net.minecraft.block.StairsBlock;
@ -17,6 +20,10 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class CloudStairsBlock extends StairsBlock implements CloudLike { public class CloudStairsBlock extends StairsBlock implements CloudLike {
private static final MapCodec<CloudStairsBlock> 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; private final CloudBlock baseBlock;
@ -25,6 +32,11 @@ public class CloudStairsBlock extends StairsBlock implements CloudLike {
this.baseBlock = (CloudBlock)baseState.getBlock(); this.baseBlock = (CloudBlock)baseState.getBlock();
} }
@Override
public MapCodec<? extends StairsBlock> getCodec() {
return CODEC;
}
@Override @Override
public void onEntityLand(BlockView world, Entity entity) { public void onEntityLand(BlockView world, Entity entity) {
baseBlock.onEntityLand(world, entity); baseBlock.onEntityLand(world, entity);

View file

@ -5,6 +5,8 @@ import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import com.minelittlepony.unicopia.EquineContext; 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.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -32,6 +34,9 @@ import net.minecraft.world.World;
import net.minecraft.world.WorldView; import net.minecraft.world.WorldView;
public class CompactedCloudBlock extends CloudBlock { public class CompactedCloudBlock extends CloudBlock {
private static final MapCodec<CompactedCloudBlock> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
BlockState.CODEC.fieldOf("base_state").forGetter(block -> block.baseState)
).apply(instance, CompactedCloudBlock::new));
static final Map<Direction, BooleanProperty> FACING_PROPERTIES = ConnectingBlock.FACING_PROPERTIES; static final Map<Direction, BooleanProperty> FACING_PROPERTIES = ConnectingBlock.FACING_PROPERTIES;
static final Collection<BooleanProperty> PROPERTIES = FACING_PROPERTIES.values(); static final Collection<BooleanProperty> PROPERTIES = FACING_PROPERTIES.values();
@ -49,13 +54,18 @@ public class CompactedCloudBlock extends CloudBlock {
private final BlockState baseState; private final BlockState baseState;
public CompactedCloudBlock(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; this.baseState = baseState;
PROPERTIES.forEach(property -> { PROPERTIES.forEach(property -> {
setDefaultState(getDefaultState().with(property, true)); setDefaultState(getDefaultState().with(property, true));
}); });
} }
@Override
public MapCodec<CompactedCloudBlock> getCodec() {
return CODEC;
}
@Override @Override
public ItemStack getPickStack(WorldView world, BlockPos pos, BlockState state) { public ItemStack getPickStack(WorldView world, BlockPos pos, BlockState state) {
return baseState.getBlock().getPickStack(world, pos, baseState); return baseState.getBlock().getPickStack(world, pos, baseState);

View file

@ -4,10 +4,17 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; 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.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.registry.Registries;
import net.minecraft.registry.tag.ItemTags; import net.minecraft.registry.tag.ItemTags;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents; import net.minecraft.sound.SoundEvents;
@ -19,16 +26,28 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
public class NaturalCloudBlock extends PoreousCloudBlock { public class NaturalCloudBlock extends PoreousCloudBlock {
private static final MapCodec<NaturalCloudBlock> CODEC = RecordCodecBuilder.<NaturalCloudBlock>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<Block> compactedBlock; private final Supplier<Block> compactedBlock;
public NaturalCloudBlock(Settings settings, boolean meltable, public NaturalCloudBlock(boolean meltable,
@Nullable Supplier<Soakable> soggyBlock, @Nullable Supplier<Soakable> soggyBlock,
Supplier<Block> compactedBlock) { Supplier<Block> compactedBlock,
super(settings.nonOpaque(), meltable, soggyBlock); Settings settings) {
super(meltable, soggyBlock, settings.nonOpaque());
this.compactedBlock = compactedBlock; this.compactedBlock = compactedBlock;
} }
@Override
public MapCodec<NaturalCloudBlock> getCodec() {
return CODEC;
}
@Override @Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ItemStack stack = player.getStackInHand(hand); ItemStack stack = player.getStackInHand(hand);

View file

@ -1,7 +1,11 @@
package com.minelittlepony.unicopia.block.cloud; package com.minelittlepony.unicopia.block.cloud;
import com.minelittlepony.unicopia.EquineContext; 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.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemPlacementContext;
@ -13,13 +17,22 @@ import net.minecraft.util.BlockRotation;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
public class OrientedCloudBlock extends CloudBlock { public class OrientedCloudBlock extends CloudBlock {
private static final MapCodec<OrientedCloudBlock> 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 static final DirectionProperty FACING = Properties.FACING;
public OrientedCloudBlock(Settings settings, boolean meltable) { public OrientedCloudBlock(boolean meltable, Settings settings) {
super(settings, meltable); super(meltable, settings);
this.setDefaultState(getDefaultState().with(FACING, Direction.UP)); this.setDefaultState(getDefaultState().with(FACING, Direction.UP));
} }
@Override
public MapCodec<? extends CloudBlock> getCodec() {
return CODEC;
}
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(FACING); builder.add(FACING);

View file

@ -4,25 +4,43 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; 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.block.BlockState;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
public class PoreousCloudBlock extends CloudBlock implements Soakable { public class PoreousCloudBlock extends CloudBlock implements Soakable {
@Nullable private static final MapCodec<PoreousCloudBlock> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
private final Supplier<Soakable> soggyBlock; 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<Soakable> soggyBlock) { @Nullable
super(settings.nonOpaque(), meltable); protected final Supplier<Soakable> soggyBlock;
public PoreousCloudBlock(boolean meltable, @Nullable Supplier<Soakable> soggyBlock, Settings settings) {
super(meltable, settings.nonOpaque());
this.soggyBlock = soggyBlock; this.soggyBlock = soggyBlock;
} }
@Override
public MapCodec<? extends PoreousCloudBlock> getCodec() {
return CODEC;
}
@Nullable @Nullable
@Override @Override
public BlockState getStateWithMoisture(BlockState state, int moisture) { public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) { if (moisture <= 0) {
return Soakable.copyProperties(state, getDefaultState()); return StateUtil.copyState(state, getDefaultState());
} }
return soggyBlock == null ? null : soggyBlock.get().getStateWithMoisture(state, moisture); return soggyBlock == null ? null : soggyBlock.get().getStateWithMoisture(state, moisture);
} }

View file

@ -4,22 +4,38 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; 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.BlockState;
import net.minecraft.block.StairsBlock;
public class PoreousCloudStairsBlock extends CloudStairsBlock implements Soakable { public class PoreousCloudStairsBlock extends CloudStairsBlock implements Soakable {
private static final MapCodec<PoreousCloudStairsBlock> 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<Soakable> soggyBlock; protected final Supplier<Soakable> soggyBlock;
public PoreousCloudStairsBlock(BlockState baseState, Settings settings, Supplier<Soakable> soggyBlock) { public PoreousCloudStairsBlock(BlockState baseState, Supplier<Soakable> soggyBlock, Settings settings) {
super(baseState, settings); super(baseState, settings);
this.soggyBlock = soggyBlock; this.soggyBlock = soggyBlock;
} }
@Override
public MapCodec<? extends PoreousCloudStairsBlock> getCodec() {
return CODEC;
}
@Nullable @Nullable
@Override @Override
public BlockState getStateWithMoisture(BlockState state, int moisture) { public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) { if (moisture <= 0) {
return Soakable.copyProperties(state, getDefaultState()); return StateUtil.copyState(state, getDefaultState());
} }
return soggyBlock.get().getStateWithMoisture(state, moisture); return soggyBlock.get().getStateWithMoisture(state, moisture);
} }

View file

@ -4,6 +4,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext; import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.container.ShapingBenchScreenHandler; import com.minelittlepony.unicopia.container.ShapingBenchScreenHandler;
import com.mojang.serialization.MapCodec;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -23,6 +24,7 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class ShapingBenchBlock extends CloudBlock { public class ShapingBenchBlock extends CloudBlock {
private static final MapCodec<ShapingBenchBlock> CODEC = Block.createCodec(ShapingBenchBlock::new);
private static final VoxelShape SHAPE = VoxelShapes.union( private static final VoxelShape SHAPE = VoxelShapes.union(
Block.createCuboidShape(0, 13, 0, 3, 18, 3), Block.createCuboidShape(0, 13, 0, 3, 18, 3),
Block.createCuboidShape(13, 13, 0, 16, 18, 3), Block.createCuboidShape(13, 13, 0, 16, 18, 3),
@ -34,7 +36,12 @@ public class ShapingBenchBlock extends CloudBlock {
); );
public ShapingBenchBlock(Settings settings) { public ShapingBenchBlock(Settings settings) {
super(settings, false); super(false, settings);
}
@Override
public MapCodec<ShapingBenchBlock> getCodec() {
return CODEC;
} }
@Override @Override

View file

@ -1,21 +1,22 @@
package com.minelittlepony.unicopia.block.cloud; package com.minelittlepony.unicopia.block.cloud;
import java.util.Arrays; import java.util.Arrays;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.mojang.serialization.Codec;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.Items; import net.minecraft.item.Items;
import net.minecraft.particle.ParticleTypes; import net.minecraft.particle.ParticleTypes;
import net.minecraft.registry.Registries;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents; import net.minecraft.sound.SoundEvents;
import net.minecraft.state.property.IntProperty; import net.minecraft.state.property.IntProperty;
import net.minecraft.state.property.Property;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.minecraft.util.Util; import net.minecraft.util.Util;
@ -27,6 +28,8 @@ import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent; import net.minecraft.world.event.GameEvent;
public interface Soakable { public interface Soakable {
Codec<Soakable> CODEC = Registries.BLOCK.getCodec().xmap(b -> (Soakable)b, s -> (Block)s);
IntProperty MOISTURE = IntProperty.of("moisture", 1, 7); IntProperty MOISTURE = IntProperty.of("moisture", 1, 7);
Direction[] DIRECTIONS = Arrays.stream(Direction.values()).filter(d -> d != Direction.UP).toArray(Direction[]::new); 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.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)); 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;
}
} }

View file

@ -4,10 +4,17 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; 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.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.registry.Registries;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
@ -19,15 +26,24 @@ import net.minecraft.world.World;
import net.minecraft.world.WorldView; import net.minecraft.world.WorldView;
public class SoggyCloudBlock extends CloudBlock implements Soakable { public class SoggyCloudBlock extends CloudBlock implements Soakable {
private static final MapCodec<SoggyCloudBlock> 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<Block> dryBlock; private final Supplier<Block> dryBlock;
public SoggyCloudBlock(Settings settings, Supplier<Block> dryBlock) { public SoggyCloudBlock(Supplier<Block> dryBlock, Settings settings) {
super(settings.ticksRandomly(), false); super(false, settings.ticksRandomly());
setDefaultState(getDefaultState().with(MOISTURE, 7)); setDefaultState(getDefaultState().with(MOISTURE, 7));
this.dryBlock = dryBlock; this.dryBlock = dryBlock;
} }
@Override
public MapCodec<? extends SoggyCloudBlock> getCodec() {
return CODEC;
}
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder); super.appendProperties(builder);
@ -43,9 +59,9 @@ public class SoggyCloudBlock extends CloudBlock implements Soakable {
@Override @Override
public BlockState getStateWithMoisture(BlockState state, int moisture) { public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) { 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 @Override

View file

@ -4,10 +4,17 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; 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.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.registry.Registries;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
@ -19,15 +26,24 @@ import net.minecraft.world.World;
import net.minecraft.world.WorldView; import net.minecraft.world.WorldView;
public class SoggyCloudSlabBlock extends CloudSlabBlock { public class SoggyCloudSlabBlock extends CloudSlabBlock {
private static final MapCodec<SoggyCloudSlabBlock> 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<Block> dryBlock; private final Supplier<Block> dryBlock;
public SoggyCloudSlabBlock(Settings settings, Supplier<Block> dryBlock) { public SoggyCloudSlabBlock(Supplier<Block> dryBlock, Settings settings) {
super(settings.ticksRandomly(), false, null); super(false, null, settings.ticksRandomly());
setDefaultState(getDefaultState().with(MOISTURE, 7)); setDefaultState(getDefaultState().with(MOISTURE, 7));
this.dryBlock = dryBlock; this.dryBlock = dryBlock;
} }
@Override
public MapCodec<? extends SoggyCloudSlabBlock> getCodec() {
return CODEC;
}
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder); super.appendProperties(builder);
@ -43,9 +59,9 @@ public class SoggyCloudSlabBlock extends CloudSlabBlock {
@Override @Override
public BlockState getStateWithMoisture(BlockState state, int moisture) { public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) { 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 @Override

View file

@ -4,23 +4,40 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; 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.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.StairsBlock;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.registry.Registries;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.WorldView; import net.minecraft.world.WorldView;
public class SoggyCloudStairsBlock extends CloudStairsBlock implements Soakable { public class SoggyCloudStairsBlock extends CloudStairsBlock implements Soakable {
private static final MapCodec<SoggyCloudStairsBlock> 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<Block> dryBlock; private final Supplier<Block> dryBlock;
public SoggyCloudStairsBlock(BlockState baseState, Settings settings, Supplier<Block> dryBlock) { public SoggyCloudStairsBlock(BlockState baseState, Supplier<Block> dryBlock, Settings settings) {
super(baseState, settings); super(baseState, settings);
setDefaultState(getDefaultState().with(MOISTURE, 7)); setDefaultState(getDefaultState().with(MOISTURE, 7));
this.dryBlock = dryBlock; this.dryBlock = dryBlock;
} }
@Override
public MapCodec<? extends SoggyCloudStairsBlock> getCodec() {
return CODEC;
}
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder); super.appendProperties(builder);
@ -36,8 +53,8 @@ public class SoggyCloudStairsBlock extends CloudStairsBlock implements Soakable
@Override @Override
public BlockState getStateWithMoisture(BlockState state, int moisture) { public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) { 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);
} }
} }

View file

@ -5,6 +5,7 @@ import java.util.Optional;
import com.minelittlepony.unicopia.entity.mob.StormCloudEntity; import com.minelittlepony.unicopia.entity.mob.StormCloudEntity;
import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.mojang.serialization.MapCodec;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -28,14 +29,20 @@ import net.minecraft.world.poi.PointOfInterestStorage;
import net.minecraft.world.poi.PointOfInterestTypes; import net.minecraft.world.poi.PointOfInterestTypes;
public class UnstableCloudBlock extends CloudBlock { public class UnstableCloudBlock extends CloudBlock {
private static final MapCodec<UnstableCloudBlock> CODEC = Block.createCodec(UnstableCloudBlock::new);
private static final int MAX_CHARGE = 6; private static final int MAX_CHARGE = 6;
private static final IntProperty CHARGE = IntProperty.of("charge", 0, MAX_CHARGE); private static final IntProperty CHARGE = IntProperty.of("charge", 0, MAX_CHARGE);
public UnstableCloudBlock(Settings settings) { public UnstableCloudBlock(Settings settings) {
super(settings, false); super(false, settings);
setDefaultState(getDefaultState().with(CHARGE, 0)); setDefaultState(getDefaultState().with(CHARGE, 0));
} }
@Override
public MapCodec<UnstableCloudBlock> getCodec() {
return CODEC;
}
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(CHARGE); builder.add(CHARGE);

View file

@ -5,7 +5,12 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext; 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.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Waterloggable; import net.minecraft.block.Waterloggable;
@ -23,13 +28,24 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.WorldAccess; import net.minecraft.world.WorldAccess;
public class WaterloggableCloudBlock extends PoreousCloudBlock implements Waterloggable { public class WaterloggableCloudBlock extends PoreousCloudBlock implements Waterloggable {
private static final MapCodec<WaterloggableCloudBlock> 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 static final BooleanProperty WATERLOGGED = Properties.WATERLOGGED;
public WaterloggableCloudBlock(Settings settings, boolean meltable, @Nullable Supplier<Soakable> soggyBlock) { public WaterloggableCloudBlock(boolean meltable, @Nullable Supplier<Soakable> soggyBlock, Settings settings) {
super(settings, meltable, soggyBlock); super(meltable, soggyBlock, settings);
setDefaultState(getDefaultState().with(WATERLOGGED, false)); setDefaultState(getDefaultState().with(WATERLOGGED, false));
} }
@Override
public MapCodec<? extends WaterloggableCloudBlock> getCodec() {
return CODEC;
}
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(WATERLOGGED); builder.add(WATERLOGGED);

View file

@ -11,7 +11,6 @@ import com.minelittlepony.unicopia.Unicopia;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.state.property.Property;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper; import net.minecraft.util.JsonHelper;
import net.minecraft.registry.Registries; import net.minecraft.registry.Registries;
@ -50,7 +49,7 @@ public abstract class StateChange {
return state; return state;
} }
return Registries.BLOCK.getOrEmpty(id).map(Block::getDefaultState) return Registries.BLOCK.getOrEmpty(id).map(Block::getDefaultState)
.map(newState -> merge(newState, state)) .map(newState -> StateUtil.copyState(state, newState))
.orElse(state); .orElse(state);
} }
}; };
@ -101,17 +100,4 @@ public abstract class StateChange {
return serializer.apply(json); return serializer.apply(json);
}).orElseThrow(() -> new IllegalArgumentException("Invalid action " + action)); }).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 <T extends Comparable<T>> BlockState copy(BlockState to, BlockState from, Property<T> property) {
return to.with(property, from.get(property));
}
} }

View file

@ -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;
}
}

View file

@ -2,7 +2,7 @@ package com.minelittlepony.unicopia.client;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.UUID;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -13,19 +13,20 @@ import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.FlightType; import com.minelittlepony.unicopia.FlightType;
import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.USounds; 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.DismissSpellScreen;
import com.minelittlepony.unicopia.client.gui.spellbook.ClientChapters; 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.client.sound.*;
import com.minelittlepony.unicopia.entity.player.PlayerPhysics; import com.minelittlepony.unicopia.entity.player.PlayerPhysics;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.entity.player.dummy.DummyClientPlayerEntity; 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 com.mojang.authlib.GameProfile;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.sound.AggressiveBeeSoundInstance; import net.minecraft.client.sound.AggressiveBeeSoundInstance;
import net.minecraft.client.sound.MovingMinecartSoundInstance; import net.minecraft.client.sound.MovingMinecartSoundInstance;
import net.minecraft.client.sound.PassiveBeeSoundInstance; 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.player.PlayerEntity;
import net.minecraft.entity.vehicle.AbstractMinecartEntity; import net.minecraft.entity.vehicle.AbstractMinecartEntity;
import net.minecraft.network.PacketByteBuf; 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.sound.SoundCategory;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class ClientInteractionManager extends InteractionManager { public class ClientInteractionManager extends InteractionManager {
private final MinecraftClient client = MinecraftClient.getInstance(); private final MinecraftClient client = MinecraftClient.getInstance();
private final Optional<CasterView> clientWorld = Optional.of(() -> MinecraftClient.getInstance().world);
private final Int2ObjectMap<WeakReference<TickableSoundInstance>> playingSounds = new Int2ObjectOpenHashMap<>(); private final Int2ObjectMap<WeakReference<TickableSoundInstance>> playingSounds = new Int2ObjectOpenHashMap<>();
@Override
public Optional<CasterView> getCasterView(BlockView view) {
if (view instanceof ServerWorld world) {
return Optional.of(Ether.get(world));
}
return clientWorld;
}
@Override @Override
public Map<Identifier, ?> readChapters(PacketByteBuf buffer) { public Map<Identifier, ?> readChapters(PacketByteBuf buffer) {
return buffer.readMap(PacketByteBuf::readIdentifier, ClientChapters::loadChapter); return buffer.readMap(PacketByteBuf::readIdentifier, ClientChapters::loadChapter);
} }
@Override @Override
@ -159,4 +149,16 @@ public class ClientInteractionManager extends InteractionManager {
public int getViewMode() { public int getViewMode() {
return client.options.getPerspective().ordinal(); 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()));
}
}
} }

View file

@ -10,6 +10,8 @@ import com.minelittlepony.unicopia.block.cloud.CloudChestBlock;
import com.minelittlepony.unicopia.client.particle.ChangelingMagicParticle; import com.minelittlepony.unicopia.client.particle.ChangelingMagicParticle;
import com.minelittlepony.unicopia.client.particle.CloudsEscapingParticle; import com.minelittlepony.unicopia.client.particle.CloudsEscapingParticle;
import com.minelittlepony.unicopia.client.particle.DiskParticle; 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.GroundPoundParticle;
import com.minelittlepony.unicopia.client.particle.HealthDrainParticle; import com.minelittlepony.unicopia.client.particle.HealthDrainParticle;
import com.minelittlepony.unicopia.client.particle.LightningBoltParticle; 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.particle.SphereParticle;
import com.minelittlepony.unicopia.client.render.*; import com.minelittlepony.unicopia.client.render.*;
import com.minelittlepony.unicopia.client.render.entity.*; 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.client.render.spell.SpellRendererFactory;
import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.item.ChameleonItem; import com.minelittlepony.unicopia.item.ChameleonItem;
@ -59,12 +62,14 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView; import net.minecraft.world.BlockRenderView;
@SuppressWarnings("deprecation")
public interface URenderers { public interface URenderers {
BlockEntity CHEST_RENDER_ENTITY = new CloudChestBlock.TileData(BlockPos.ORIGIN, UBlocks.CLOUD_CHEST.getDefaultState()); BlockEntity CHEST_RENDER_ENTITY = new CloudChestBlock.TileData(BlockPos.ORIGIN, UBlocks.CLOUD_CHEST.getDefaultState());
static void bootstrap() { static void bootstrap() {
ParticleFactoryRegistry.getInstance().register(UParticles.UNICORN_MAGIC, createFactory(MagicParticle::new)); ParticleFactoryRegistry.getInstance().register(UParticles.UNICORN_MAGIC, createFactory(MagicParticle::new));
ParticleFactoryRegistry.getInstance().register(UParticles.CHANGELING_MAGIC, createFactory(ChangelingMagicParticle::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.RAIN_DROPS, createFactory(RaindropsParticle::new));
ParticleFactoryRegistry.getInstance().register(UParticles.HEALTH_DRAIN, createFactory(HealthDrainParticle::create)); ParticleFactoryRegistry.getInstance().register(UParticles.HEALTH_DRAIN, createFactory(HealthDrainParticle::create));
ParticleFactoryRegistry.getInstance().register(UParticles.RAINBOOM_RING, RainboomParticle::new); 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.GROUND_POUND, GroundPoundParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.CLOUDS_ESCAPING, CloudsEscapingParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.CLOUDS_ESCAPING, CloudsEscapingParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.LIGHTNING_BOLT, LightningBoltParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.LIGHTNING_BOLT, LightningBoltParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.DUST_CLOUD, DustCloudParticle::new);
AccessoryFeatureRenderer.register( AccessoryFeatureRenderer.register(
BraceletFeatureRenderer::new, AmuletFeatureRenderer::new, GlassesFeatureRenderer::new, BraceletFeatureRenderer::new, AmuletFeatureRenderer::new, GlassesFeatureRenderer::new,
@ -97,6 +103,8 @@ public interface URenderers {
EntityRendererRegistry.register(UEntities.AIR_BALLOON, AirBalloonEntityRenderer::new); EntityRendererRegistry.register(UEntities.AIR_BALLOON, AirBalloonEntityRenderer::new);
EntityRendererRegistry.register(UEntities.FRIENDLY_CREEPER, FriendlyCreeperEntityRenderer::new); EntityRendererRegistry.register(UEntities.FRIENDLY_CREEPER, FriendlyCreeperEntityRenderer::new);
EntityRendererRegistry.register(UEntities.LOOT_BUG, LootBugEntityRenderer::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.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.FANCY_BED, CloudBedBlockEntityRenderer::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); 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.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(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.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)); 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); TerraformBoatClientHelper.registerModelLayers(Unicopia.id("palm"), false);
SpellRendererFactory.bootstrap(); SpellRendererFactory.bootstrap();
UShaders.bootstrap();
} }
private static void register(DynamicItemRenderer renderer, ItemConvertible...items) { private static void register(DynamicItemRenderer renderer, ItemConvertible...items) {

View file

@ -22,6 +22,7 @@ import com.minelittlepony.unicopia.entity.player.PlayerCamera;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.handler.ClientNetworkHandlerImpl; import com.minelittlepony.unicopia.network.handler.ClientNetworkHandlerImpl;
import com.minelittlepony.unicopia.server.world.WeatherConditions; import com.minelittlepony.unicopia.server.world.WeatherConditions;
import com.minelittlepony.unicopia.server.world.ZapAppleStageStore;
import com.minelittlepony.unicopia.util.Lerp; import com.minelittlepony.unicopia.util.Lerp;
import net.fabricmc.api.ClientModInitializer; 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 tangentalSkyAngle = new Lerp(0, true);
public final Lerp skyAngle = 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<PlayerCamera> getCamera() { public static Optional<PlayerCamera> getCamera() {
PlayerEntity player = MinecraftClient.getInstance().player; PlayerEntity player = MinecraftClient.getInstance().player;
@ -84,6 +88,15 @@ public class UnicopiaClient implements ClientModInitializer {
instance = this; 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) { public float getSkyAngleDelta(float tickDelta) {
if (MinecraftClient.getInstance().world == null) { if (MinecraftClient.getInstance().world == null) {
return 0; return 0;
@ -135,6 +148,8 @@ public class UnicopiaClient implements ClientModInitializer {
world.setRainGradient(gradient); world.setRainGradient(gradient);
world.setThunderGradient(gradient); world.setThunderGradient(gradient);
} }
zapStageTime++;
} }
private Float getTargetRainGradient(ClientWorld world, BlockPos pos, float tickDelta) { private Float getTargetRainGradient(ClientWorld world, BlockPos pos, float tickDelta) {

View file

@ -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);
}
}

View file

@ -2,7 +2,6 @@ package com.minelittlepony.unicopia.client.gui;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import it.unimi.dsi.fastutil.ints.Int2IntFunction; import it.unimi.dsi.fastutil.ints.Int2IntFunction;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextHandler; import net.minecraft.client.font.TextHandler;
@ -31,12 +30,18 @@ public class ParagraphWrappingVisitor implements StyledVisitor<Object> {
} }
@Override @Override
public Optional<Object> accept(Style style, String s) { public Optional<Object> accept(Style initialStyle, String s) {
StyleBreakingVisitor visitor = new StyleBreakingVisitor(initialStyle, this::acceptFragment);
TextVisitFactory.visitFormatted(s, 0, initialStyle, initialStyle, visitor);
visitor.flush();
return Optional.empty();
}
private Optional<Object> acceptFragment(Style inlineStyle, String s) {
int remainingLength = (int)(pageWidth - currentLineCollectedLength); int remainingLength = (int)(pageWidth - currentLineCollectedLength);
while (!s.isEmpty()) { while (!s.isEmpty()) {
int trimmedLength = handler.getTrimmedLength(s, remainingLength, style); int trimmedLength = handler.getTrimmedLength(s, remainingLength, inlineStyle);
int newline = s.indexOf('\n'); int newline = s.indexOf('\n');
if (newline >= 0 && newline < trimmedLength) { if (newline >= 0 && newline < trimmedLength) {
@ -57,7 +62,7 @@ public class ParagraphWrappingVisitor implements StyledVisitor<Object> {
trimmedLength = lastSpace > 0 ? Math.min(lastSpace, trimmedLength) : trimmedLength; trimmedLength = lastSpace > 0 ? Math.min(lastSpace, trimmedLength) : trimmedLength;
} }
Text fragment = Text.literal(s.substring(0, trimmedLength).trim()).setStyle(style); Text fragment = Text.literal(s.substring(0, trimmedLength).trim()).setStyle(inlineStyle);
float grabbedWidth = handler.getWidth(fragment); float grabbedWidth = handler.getWidth(fragment);
// advance if appending the next segment would cause an overflow // advance if appending the next segment would cause an overflow
@ -101,14 +106,42 @@ public class ParagraphWrappingVisitor implements StyledVisitor<Object> {
} }
public void advance() { public void advance() {
line++;
if (progressedNonEmpty || currentLineCollectedLength > 0) { if (progressedNonEmpty || currentLineCollectedLength > 0) {
progressedNonEmpty = true; progressedNonEmpty = true;
lineConsumer.accept(currentLine, (++line) * font.fontHeight); lineConsumer.accept(currentLine, line * font.fontHeight);
} }
pageWidth = widthSupplier.applyAsInt((++line) * font.fontHeight); pageWidth = widthSupplier.applyAsInt(line * font.fontHeight);
currentLine = Text.empty(); currentLine = Text.empty();
currentLineCollectedLength = 0; currentLineCollectedLength = 0;
} }
record StyledString (String string, Style style) {} static final class StyleBreakingVisitor implements CharacterVisitor {
private final StyledVisitor<?> fragmentVisitor;
private final StringBuilder collectedText = new StringBuilder();
private Style currentStyle;
public StyleBreakingVisitor(Style initialStyle, StyledVisitor<?> fragmentVisitor) {
this.currentStyle = initialStyle;
this.fragmentVisitor = fragmentVisitor;
}
@Override
public boolean accept(int index, Style style, int codepoint) {
if (!style.equals(currentStyle)) {
flush();
}
currentStyle = style;
collectedText.append((char)codepoint);
return true;
}
public void flush() {
if (collectedText.length() > 0) {
fragmentVisitor.accept(currentStyle, collectedText.toString());
collectedText.setLength(0);
}
}
}
} }

View file

@ -7,6 +7,8 @@ import com.minelittlepony.unicopia.*;
import com.minelittlepony.unicopia.ability.*; import com.minelittlepony.unicopia.ability.*;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.client.render.RenderLayers;
import com.minelittlepony.unicopia.client.render.spell.DarkVortexSpellRenderer;
import com.minelittlepony.unicopia.client.sound.*; import com.minelittlepony.unicopia.client.sound.*;
import com.minelittlepony.unicopia.entity.ItemTracker; import com.minelittlepony.unicopia.entity.ItemTracker;
import com.minelittlepony.unicopia.entity.effect.EffectUtils; import com.minelittlepony.unicopia.entity.effect.EffectUtils;
@ -193,6 +195,19 @@ public class UHud {
protected void renderViewEffects(Pony pony, DrawContext context, int scaledWidth, int scaledHeight, float tickDelta) { protected void renderViewEffects(Pony pony, DrawContext context, int scaledWidth, int scaledHeight, float tickDelta) {
float vortexDistortion = DarkVortexSpellRenderer.getCameraDistortion();
if (vortexDistortion > 20) {
context.fill(RenderLayers.getEndPortal(), 0, 0, scaledWidth, scaledHeight, 0);
context.getMatrices().push();
context.getMatrices().translate(scaledWidth / 2, scaledHeight / 2, 0);
DrawableUtil.drawArc(context.getMatrices(), 0, 20, 0, MathHelper.TAU, 0x000000FF, false);
context.getMatrices().pop();
return;
} else if (vortexDistortion > 0) {
context.fill(0, 0, scaledWidth, scaledHeight, (int)((vortexDistortion / 20F) * 255) << 24);
}
boolean hasEffect = client.player.hasStatusEffect(UEffects.SUN_BLINDNESS); boolean hasEffect = client.player.hasStatusEffect(UEffects.SUN_BLINDNESS);
ItemStack glasses = GlassesItem.getForEntity(client.player); ItemStack glasses = GlassesItem.getForEntity(client.player);

View file

@ -5,6 +5,7 @@ import com.minelittlepony.common.client.gui.ScrollContainer;
import com.minelittlepony.common.client.gui.element.Label; import com.minelittlepony.common.client.gui.element.Label;
import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellbookRecipe; import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellbookRecipe;
import com.minelittlepony.unicopia.client.gui.DrawableUtil; import com.minelittlepony.unicopia.client.gui.DrawableUtil;
import com.minelittlepony.unicopia.client.gui.MagicText;
import com.minelittlepony.unicopia.container.SpellbookState; import com.minelittlepony.unicopia.container.SpellbookState;
import com.minelittlepony.unicopia.item.URecipes; import com.minelittlepony.unicopia.item.URecipes;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
@ -43,13 +44,10 @@ public class SpellbookCraftingPageContent extends ScrollContainer implements Spe
@Override @Override
public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) { public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) {
DrawableUtil.drawScaledText(context, state.getOffset() == 0 ? INVENTORY_TITLE : RECIPES_TITLE, screen.getFrameBounds().left + screen.getFrameBounds().width / 2 + 20, SpellbookScreen.TITLE_Y, 1.3F, MagicText.getColor());
int headerColor = mouseY % 255;
DrawableUtil.drawScaledText(context, state.getOffset() == 0 ? INVENTORY_TITLE : RECIPES_TITLE, screen.getFrameBounds().left + screen.getFrameBounds().width / 2 + 20, SpellbookScreen.TITLE_Y, 1.3F, headerColor);
Text pageText = Text.translatable("%s/%s", state.getOffset() + 1, TOTAL_PAGES); Text pageText = Text.translatable("%s/%s", state.getOffset() + 1, TOTAL_PAGES);
context.drawText(textRenderer, pageText, (int)(337 - textRenderer.getWidth(pageText) / 2F), 190, headerColor, false); context.drawText(textRenderer, pageText, (int)(337 - textRenderer.getWidth(pageText) / 2F), 190, MagicText.getColor(), false);
} }
@Override @Override

View file

@ -72,7 +72,7 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content
float currentScaledLevel = pony.getLevel().getScaled(1); float currentScaledLevel = pony.getLevel().getScaled(1);
float currentCorruption = pony.getCorruption().getScaled(1); float currentCorruption = pony.getCorruption().getScaled(1);
DrawableUtil.drawScaledText(context, pony.asEntity().getName(), SpellbookScreen.TITLE_X, y, 1.3F, SpellbookScreen.TITLE_COLOR); DrawableUtil.drawScaledText(context, pony.asEntity().getName(), SpellbookScreen.TITLE_X, y, 1.3F, MagicText.getColor());
DrawableUtil.drawScaledText(context, ExperienceGroup.forLevel( DrawableUtil.drawScaledText(context, ExperienceGroup.forLevel(
currentScaledLevel, currentScaledLevel,
currentCorruption currentCorruption
@ -89,7 +89,7 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content
matrices.push(); matrices.push();
matrices.translate(screen.getBackgroundWidth() / 2 + SpellbookScreen.TITLE_X - 10, y, 0); matrices.translate(screen.getBackgroundWidth() / 2 + SpellbookScreen.TITLE_X - 10, y, 0);
matrices.scale(1.3F, 1.3F, 1); matrices.scale(1.3F, 1.3F, 1);
context.drawText(font, SpellbookCraftingPageContent.INVENTORY_TITLE, 0, 0, SpellbookScreen.TITLE_COLOR, false); context.drawText(font, SpellbookCraftingPageContent.INVENTORY_TITLE, 0, 0, MagicText.getColor(), false);
matrices.pop(); matrices.pop();
Bounds bounds = screen.getFrameBounds(); Bounds bounds = screen.getFrameBounds();

View file

@ -159,7 +159,6 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
tabs.getAllTabs().forEach(tab -> { tabs.getAllTabs().forEach(tab -> {
Bounds bounds = tab.bounds(); Bounds bounds = tab.bounds();
chapters.getCurrentChapter();
boolean hover = bounds.contains(mouseX, mouseY); boolean hover = bounds.contains(mouseX, mouseY);
int color = tab.chapter().color() & 0xFFFFFF; int color = tab.chapter().color() & 0xFFFFFF;

View file

@ -5,6 +5,7 @@ import java.util.*;
import com.minelittlepony.common.client.gui.IViewRoot; import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.common.client.gui.dimension.Bounds; import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.unicopia.client.gui.DrawableUtil; import com.minelittlepony.unicopia.client.gui.DrawableUtil;
import com.minelittlepony.unicopia.client.gui.MagicText;
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookScreen; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookScreen;
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.Content; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.Content;
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.Drawable; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.Drawable;
@ -12,7 +13,6 @@ import com.minelittlepony.unicopia.container.SpellbookChapterLoader.Flow;
import com.minelittlepony.unicopia.container.SpellbookState; import com.minelittlepony.unicopia.container.SpellbookState;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
@ -21,13 +21,15 @@ import net.minecraft.util.*;
public class DynamicContent implements Content { public class DynamicContent implements Content {
private static final Text UNKNOWN = Text.of("???"); private static final Text UNKNOWN = Text.of("???");
private static final Text UNKNOWN_LEVEL = Text.literal("Level: ???").formatted(Formatting.DARK_GREEN);
private SpellbookState.PageState state = new SpellbookState.PageState(); private SpellbookState.PageState state = new SpellbookState.PageState();
private final List<Page> pages; private final List<Page> pages;
private Bounds bounds = Bounds.empty(); private Bounds bounds = Bounds.empty();
private final Panel leftPanel = new Panel(this);
private final Panel rightPanel = new Panel(this);
public DynamicContent(PacketByteBuf buffer) { public DynamicContent(PacketByteBuf buffer) {
pages = buffer.readList(Page::new); pages = buffer.readList(Page::new);
} }
@ -35,32 +37,25 @@ public class DynamicContent implements Content {
@Override @Override
public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) { public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) {
int pageIndex = state.getOffset() * 2; int pageIndex = state.getOffset() * 2;
MinecraftClient client = MinecraftClient.getInstance();
getPage(pageIndex).ifPresent(page -> page.draw(context, mouseX, mouseY, container)); Text pageText = Text.translatable("%s/%s", (pageIndex / 2) + 1, (int)Math.ceil(pages.size() / 2F));
context.drawText(client.textRenderer, pageText, (int)(337 - client.textRenderer.getWidth(pageText) / 2F), 190, MagicText.getColor(), false);
context.getMatrices().push();
getPage(pageIndex + 1).ifPresent(page -> {
page.bounds.left = bounds.left + bounds.width / 2 + 20;
page.draw(context, mouseX, mouseY, container);
});
context.getMatrices().pop();
TextRenderer font = MinecraftClient.getInstance().textRenderer;
int headerColor = mouseY % 255;
Text pageText = Text.translatable("%s/%s", (pageIndex / 2) + 1, pages.size() / 2);
context.drawText(font, pageText, (int)(337 - font.getWidth(pageText) / 2F), 190, headerColor, false);
} }
@Override @Override
public void copyStateFrom(Content old) { public void copyStateFrom(Content old) {
if (old instanceof DynamicContent o) { if (old instanceof DynamicContent o) {
if (state.getOffset() == o.state.getOffset()) {
leftPanel.verticalScrollbar.getScrubber().scrollTo(o.leftPanel.verticalScrollbar.getScrubber().getPosition(), false);
rightPanel.verticalScrollbar.getScrubber().scrollTo(o.rightPanel.verticalScrollbar.getScrubber().getPosition(), false);
}
state = o.state; state = o.state;
setBounds(o.bounds); setBounds(o.bounds);
} }
} }
private Optional<Page> getPage(int index) { Optional<Page> getPage(int index) {
if (index < 0 || index >= pages.size()) { if (index < 0 || index >= pages.size()) {
return Optional.empty(); return Optional.empty();
} }
@ -71,9 +66,15 @@ public class DynamicContent implements Content {
this.bounds = bounds; this.bounds = bounds;
pages.forEach(page -> { pages.forEach(page -> {
page.reset(); page.reset();
int oldHeight = page.bounds.height;
page.bounds.copy(bounds); page.bounds.copy(bounds);
page.bounds.left = 0;
page.bounds.top = 0;
page.bounds.width /= 2; page.bounds.width /= 2;
page.bounds.height = oldHeight;
}); });
leftPanel.setBounds(bounds);
} }
@Override @Override
@ -83,6 +84,10 @@ public class DynamicContent implements Content {
screen.addPageButtons(187, 30, 350, incr -> { screen.addPageButtons(187, 30, 350, incr -> {
state.swap(incr, (int)Math.ceil(pages.size() / 2F)); state.swap(incr, (int)Math.ceil(pages.size() / 2F));
}); });
int pageIndex = state.getOffset() * 2;
leftPanel.init(screen, pageIndex);
rightPanel.init(screen, pageIndex + 1);
} }
class Page implements Drawable { class Page implements Drawable {
@ -131,6 +136,19 @@ public class DynamicContent implements Content {
compiled = false; compiled = false;
} }
public void drawHeader(DrawContext context, int mouseX, int mouseY) {
if (elements.isEmpty()) {
return;
}
boolean needsMoreXp = level < 0 || Pony.of(MinecraftClient.getInstance().player).getLevel().get() < level;
int x = bounds.left;
int y = bounds.top - 16;
DrawableUtil.drawScaledText(context, needsMoreXp ? UNKNOWN : title, x, y, 1.3F, MagicText.getColor());
DrawableUtil.drawScaledText(context, Text.translatable("gui.unicopia.spellbook.page.level_requirement", level < 0 ? "???" : "" + (level + 1)).formatted(Formatting.DARK_GREEN), x, y + 12, 0.8F, MagicText.getColor());
}
@Override @Override
public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) { public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) {
@ -141,26 +159,24 @@ public class DynamicContent implements Content {
if (!compiled) { if (!compiled) {
compiled = true; compiled = true;
int relativeY = 0; int relativeY = 0;
int textHeight = 0;
for (PageElement element : elements.stream().filter(PageElement::isInline).toList()) { for (PageElement element : elements.stream().filter(PageElement::isInline).toList()) {
element.compile(relativeY, container); element.compile(relativeY, container);
relativeY += element.bounds().height; relativeY += element.bounds().height;
if (element instanceof TextBlock) {
textHeight += element.bounds().height;
}
} }
bounds.height = textHeight == 0 ? 0 : relativeY;
} }
boolean needsMoreXp = level < 0 || Pony.of(MinecraftClient.getInstance().player).getLevel().get() < level;
int headerColor = mouseY % 255;
MatrixStack matrices = context.getMatrices(); MatrixStack matrices = context.getMatrices();
DrawableUtil.drawScaledText(context, needsMoreXp ? UNKNOWN : title, bounds.left, bounds.top - 10, 1.3F, headerColor);
DrawableUtil.drawScaledText(context, level < 0 ? UNKNOWN_LEVEL : Text.literal("Level: " + (level + 1)).formatted(Formatting.DARK_GREEN), bounds.left, bounds.top - 10 + 12, 0.8F, headerColor);
matrices.push(); matrices.push();
matrices.translate(bounds.left, bounds.top + 16, 0); matrices.translate(0, -8, 0);
elements.stream().filter(PageElement::isFloating).forEach(element -> { elements.stream().filter(PageElement::isFloating).forEach(element -> {
Bounds bounds = element.bounds();
matrices.push(); matrices.push();
bounds.translate(matrices); element.bounds().translate(matrices);
element.draw(context, mouseX, mouseY, container); element.draw(context, mouseX, mouseY, container);
matrices.pop(); matrices.pop();
}); });

View file

@ -0,0 +1,78 @@
package com.minelittlepony.unicopia.client.gui.spellbook.element;
import java.util.Optional;
import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.common.client.gui.ScrollContainer;
import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookScreen;
import com.minelittlepony.unicopia.client.gui.spellbook.element.DynamicContent.Page;
import net.minecraft.client.gui.DrawContext;
class Panel extends ScrollContainer {
private final DynamicContent content;
Panel(DynamicContent content) {
this.content = content;
}
private Optional<Page> page = Optional.empty();
public void init(SpellbookScreen screen, int pageIndex) {
verticalScrollbar.layoutToEnd = true;
getContentPadding().top = 15;
page = content.getPage(pageIndex);
int width = screen.getBackgroundWidth() / 2;
margin.top = screen.getY() + 35;
margin.bottom = screen.height - screen.getBackgroundHeight() - screen.getY() + 40;
margin.left = screen.getX() + 30;
if (pageIndex % 2 == 1) {
margin.left += screen.getBackgroundWidth() / 2 - 20;
margin.right = screen.getX() + 20;
} else {
margin.right = screen.width - width - margin.left + 30;
}
init(() -> {});
screen.addDrawable(this);
((IViewRoot)screen).getChildElements().add(this);
}
@Override
protected void renderContents(DrawContext context, int mouseX, int mouseY, float partialTicks) {
page.ifPresent(p -> {
int oldHeight = p.getBounds().height;
p.draw(context, mouseX, mouseY, this);
if (p.getBounds().height != oldHeight) {
verticalScrollbar.reposition();
}
});
super.renderContents(context, mouseX, mouseY, partialTicks);
}
@Override
public Bounds getContentBounds() {
return page == null ? Bounds.empty() : page.map(page -> {
return new Bounds(0, 0, 1, page.getBounds().height);
}).orElse(Bounds.empty());
}
@Override
protected void drawBackground(DrawContext context, int mouseX, int mouseY, float partialTicks) { }
@Override
protected void drawDecorations(DrawContext context, int mouseX, int mouseY, float partialTicks) { }
@Override
protected void drawOverlays(DrawContext context, int mouseX, int mouseY, float partialTicks) {
context.getMatrices().push();
this.getBounds().translate(context.getMatrices());
page.ifPresent(p -> {
p.drawHeader(context, mouseX, mouseY);
});
context.getMatrices().pop();
super.drawOverlays(context, mouseX, mouseY, partialTicks);
}
}

View file

@ -4,7 +4,6 @@ import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.common.client.gui.dimension.Bounds; import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellbookRecipe; import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellbookRecipe;
import com.minelittlepony.unicopia.client.gui.spellbook.IngredientTree; import com.minelittlepony.unicopia.client.gui.spellbook.IngredientTree;
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookScreen;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
@ -14,18 +13,14 @@ import net.minecraft.util.Identifier;
record Recipe (DynamicContent.Page page, Identifier id, Bounds bounds) implements PageElement { record Recipe (DynamicContent.Page page, Identifier id, Bounds bounds) implements PageElement {
@Override @Override
public void compile(int y, IViewRoot container) { public void compile(int y, IViewRoot container) {
if (container instanceof SpellbookScreen book) {
bounds().left = book.getX();
bounds().top = book.getY();
}
MinecraftClient.getInstance().world.getRecipeManager().get(id).map(RecipeEntry::value).ifPresent(recipe -> { MinecraftClient.getInstance().world.getRecipeManager().get(id).map(RecipeEntry::value).ifPresent(recipe -> {
if (recipe instanceof SpellbookRecipe spellRecipe) { if (recipe instanceof SpellbookRecipe spellRecipe) {
boolean needsMoreXp = page.getLevel() < 0 || Pony.of(MinecraftClient.getInstance().player).getLevel().get() < page.getLevel(); boolean needsMoreXp = page.getLevel() < 0 || Pony.of(MinecraftClient.getInstance().player).getLevel().get() < page.getLevel();
IngredientTree tree = new IngredientTree( IngredientTree tree = new IngredientTree(
bounds().left + page().getBounds().left, bounds().left,
bounds().top + page().getBounds().top + y + 10, bounds().top + y - 10,
page().getBounds().width - 20 page().getBounds().width - 20
).obfuscateResult(needsMoreXp); ).obfuscateResult(needsMoreXp);
spellRecipe.buildCraftingTree(tree); spellRecipe.buildCraftingTree(tree);

Some files were not shown because too many files have changed in this diff Show more