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)
[![cn](https://img.shields.io/badge/lang-cn-de2910.svg)](README_CN.md)
When starting a new world you're given the choice of which tribe to join. One of Unicorn, Pegasus, Earth, Bat, or Changeling.
When starting a new world you're given the choice of which tribe to join. You can choose from _Unicorn_, _Pegasus_, _Earth Pony_, _Batpony_, or _Changeling_.
Depending on which race you pick, you're given different abilities, displayed on your HUD in a series of circular elements.
To activate an ability, simply press and hold the key corresponding to that ability. Some take longer than others, and certain abilities
@ -17,7 +17,7 @@ also respond to quick, short single-taps, or double-taps.
For unicorns, casting spells is done through gems, which you can obtain whilst mining. You first need to craft a spellbook using a gem, and then
in the spellbook you can discover the magical traits of different items and recipes to combine them to create different spells, as well
as modify existing one.
as modify existing ones.
Once you have a gem with a spell you want to use, you can equip it to your main-hand or off-hand slot by right-clicking with a gem in
one of either hand, then to activate it you us your primary ability. You can also cast spells directly from a gem by using the ability
@ -34,7 +34,7 @@ also respond to quick, short single-taps, or double-taps.
### Earth Ponies
- Kicking & Stomping
Earth ponies kick good.
*Earth ponies kick good*.
If Mine Little Pony is installed, and you have the appearance of a pony, kicking will target the block behind you, so twirl that rump!
Kicking blocks will incrementally mine them, and kicking trees will shake items loose from their branches. Kicking a tree _too much_ might destroy it,
@ -47,7 +47,7 @@ also respond to quick, short single-taps, or double-taps.
- Bracing
Earth ponies can brace themselves by sneaking! It, uh, makes you harder to push! yeah!
Earth ponies can brace themselves by sneaking! It, uh, makes you harder to push! Yeah!
### Pegasi / Bat Ponies
@ -99,7 +99,7 @@ also respond to quick, short single-taps, or double-taps.
- Hanging of Ceilings
Ever just want to hang out? Well bat ponies can, _literally_! All the cool kids are doing.
Ever just want to hang out? Well bat ponies can, _literally_! All the cool kids are doing it!
- Mangoes
@ -121,6 +121,12 @@ also respond to quick, short single-taps, or double-taps.
Changelings can turn into damn near anything, even other players! And blocks! And hostile mobs!
Careful about turning into skeletons, though, because they hate the sun even more than bat ponies.
- Crawling
Changelings can crawl on Ceilings, which is very useful for lining up your shot, when attempting to shoot somepony in a cave,
while disguised as a stone block. Or is that just me? Anyways, they can only crawl along flat surfaces, meaning that if you go off the edge of a block,
you will fall off.
## Special Items, Plants, Tools
@ -135,7 +141,7 @@ Fighting too close together with them may cause you some knockback.
Zap Apple Trees occur naturally in the world and are the only way to obtain zap apples in survival. They can appear in one of several different states:
Hybernating, Flowering, Fruiting, or Withering
*Hibernating, Flowering, Fruiting, or Withering*
They cycle through these states throughout the lunar cycle, so if you find one, and it's not in the state you want, wait around another few days and it
will eventually bear fruit, but don't try to harvest the apples before they're ripe, because they will zap you!
@ -144,4 +150,6 @@ If you're able to obtain the wood and leaves, it also makes the perfect deterant
### Muffins
They're bouncy and delicious, and pigs absolutely love them!
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!
What started as a humble utility to make playing as a unicorn a little more emersive has grown into a full-blown pony
What started as a humble utility to make playing as a unicorn a little more immersive has grown into a full-blown pony
conversion experience that brings new magic, mechanics and experience to the world of Minecraft to make it truly feel like you've
entered the world of Equestria!
@ -24,13 +24,13 @@ Unicorns, Pegasi, Earth Ponies, and even Changelings get their own special abili
- *Play as a unicorn* and learn to use magic! Craft your first spellbook and experiment, finding the different spells you can
make and what they do, or simply delve into the lore to learn more about the past of this mysterious world!
Besides casting spells, such as a shield to protect themselves, of fire a bolt of magic to incinerate your foes,
Besides casting spells, such as a shield to protect themselves, or a bolt of magic to incinerate your foes,
Unicorns can also teleport to get around obstacles or simply reach those hard to reach places.
- *Play as a pegasus* and dominate the skies! Besides the ability to fly, pegasi can also perform rainbooms, control the weather by shoving them into jars,
- *Play as a pegasus* and dominate the skies! Besides the ability to fly, pegasi can also perform sonic rainbooms, control the weather by shoving them into jars,
and have a greater reach distance and speed than other races.
- *Play as a humble background pony*! Earth Ponies are tougher and heavier than the other races. They also have the nify ability to
- *Play as a humble background pony*! Earth Ponies are tougher and heavier than the other races. They also have the nifty ability to
kick trees to get food and hasten the growth of crops. You'll never go hungry if you're an earth pony.
Feeling like going over to the dark side?
@ -58,7 +58,7 @@ Unicorns, Pegasi, Earth Ponies, and even Changelings get their own special abili
- Airflow is simulated (badly)
Pegasi, beware about flying during storms! It can get dangerous out there!
If you're playing as a flying species, or just like having nice things, try building a weather vein.
If you're playing as a flying species, or just like having nice things, try building a weather vane.
It shows the actual, totally real and not simulated badly, wind direction of your minecraft world. Just beware
that the direction and strength is situational (and bad), and will be different depending where you are and
how high up you are.

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("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() {
super(new HeirarchicalJsonConfigAdapter(new GsonBuilder()
.registerTypeAdapter(Race.class, RegistryTypeAdapter.of(Race.REGISTRY))

View file

@ -1,33 +1,35 @@
package com.minelittlepony.unicopia;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import com.google.common.collect.Sets;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.entity.mob.AirBalloonEntity;
import com.minelittlepony.unicopia.entity.mob.UEntities;
import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.registry.Registries;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.util.Identifier;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionTypes;
public interface Debug {
boolean SPELLBOOK_CHAPTERS = Boolean.getBoolean("unicopia.debug.spellbookChapters");
boolean CHECK_GAME_VALUES = Boolean.getBoolean("unicopia.debug.checkGameValues");
boolean CHECK_TRAIT_COVERAGE = Boolean.getBoolean("unicopia.debug.checkTraitCoverage");
boolean[] TESTS_COMPLETE = {false};
AtomicReference<World> LAST_TESTED_WORLD = new AtomicReference<>(null);
static void runTests(World world) {
if (!CHECK_GAME_VALUES || TESTS_COMPLETE[0]) {
if (!CHECK_GAME_VALUES || !world.getDimensionKey().getValue().equals(DimensionTypes.OVERWORLD_ID) || (LAST_TESTED_WORLD.getAndSet(world) == world)) {
return;
}
TESTS_COMPLETE[0] = true;
try {
Registries.ITEM.getEntrySet().forEach(entry -> {
if (SpellTraits.of(entry.getValue()).isEmpty()) {
// Unicopia.LOGGER.warn("No traits registered for item {}", entry.getKey());
}
});
} catch (Throwable t) {
throw new IllegalStateException("Tests failed", t);
if (CHECK_TRAIT_COVERAGE) {
testTraitCoverage();
}
try {
@ -40,4 +42,30 @@ public interface Debug {
throw new IllegalStateException("Tests failed", t);
}
}
private static void testTraitCoverage() {
Registries.ITEM.getEntrySet().stream().collect(Collectors.toMap(
entry -> entry.getKey().getValue().getNamespace(),
Set::of,
Sets::union
)).forEach((namespace, entries) -> {
@SuppressWarnings("deprecation")
var unregistered = entries.stream()
.filter(entry -> !entry.getValue().getRegistryEntry().isIn(UTags.HAS_NO_TRAITS) && SpellTraits.of(entry.getValue()).isEmpty())
.map(entry -> {
String id = entry.getKey().getValue().toString();
return id + "(" + Registries.ITEM.streamTags()
.filter(entry.getValue().getRegistryEntry()::isIn)
.map(TagKey::id)
.map(Identifier::toString)
.collect(Collectors.joining(", ")) + ")";
})
.toList();
if (!unregistered.isEmpty()) {
Unicopia.LOGGER.warn("No traits registered for {} items in namepsace {} {}", unregistered.size(), namespace, String.join(",\r\n", unregistered));
}
});
}
}

View file

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

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_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> VALID_LIVING_AND_NOT_MAGIC_IMMUNE = EntityPredicates.VALID_LIVING_ENTITY.and(EXCEPT_MAGIC_IMMUNE);

View file

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

View file

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

View file

@ -62,11 +62,18 @@ public interface USounds {
SoundEvent ENTITY_SOMBRA_AMBIENT = register("entity.sombra.ambient");
SoundEvent ENTITY_SOMBRA_LAUGH = register("entity.sombra.laugh");
SoundEvent ENTITY_SOMBRA_SNICKER = register("entity.sombra.snicker");
SoundEvent ENTITY_SOMBRA_SCARY = USounds.Vanilla.ENTITY_GHAST_AMBIENT;
SoundEvent ENTITY_SOMBRA_SCARY = ENTITY_GHAST_AMBIENT;
SoundEvent ENTITY_CRYSTAL_SHARDS_AMBIENT = BLOCK_AMETHYST_BLOCK_HIT;
SoundEvent ENTITY_CRYSTAL_SHARDS_JOSTLE = BLOCK_AMETHYST_BLOCK_BREAK;
SoundEvent ENTITY_IGNIMEOUS_BULB_HURT = ENTITY_WARDEN_HURT;
SoundEvent ENTITY_IGNIMEOUS_BULB_DEATH = ENTITY_WARDEN_DEATH;
SoundEvent ENTITY_TENTACLE_ROAR = ENTITY_RAVAGER_ROAR;
SoundEvent ENTITY_TENTACLE_AMBIENT = BLOCK_CONDUIT_AMBIENT_SHORT;
SoundEvent ENTITY_TENTACLE_DIG = ENTITY_WARDEN_DIG;
SoundEvent ITEM_AMULET_CHARGING = register("item.amulet.charging");
SoundEvent ITEM_AMULET_RECHARGE = register("item.amulet.recharge");
@ -90,6 +97,8 @@ public interface USounds {
SoundEvent ITEM_STAFF_STRIKE = ENTITY_PLAYER_ATTACK_CRIT;
SoundEvent ITEM_MAGIC_STAFF_CHARGE = ENTITY_GUARDIAN_ATTACK;
SoundEvent ITEM_CURING_JOKE_CURE = BLOCK_AMETHYST_BLOCK_BREAK;
SoundEvent ITEM_ROCK_LAND = BLOCK_STONE_HIT;
RegistryEntry.Reference<SoundEvent> ITEM_MUFFIN_BOUNCE = BLOCK_NOTE_BLOCK_BANJO;

View file

@ -23,6 +23,7 @@ public interface UTags {
TagKey<Item> SHADES = item("shades");
TagKey<Item> CHANGELING_EDIBLE = item("food_types/changeling_edible");
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> FLOATS_ON_CLOUDS = item("floats_on_clouds");
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_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");

View file

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

View file

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

View file

@ -1,22 +1,37 @@
package com.minelittlepony.unicopia.ability;
import java.util.Optional;
import java.util.function.DoubleSupplier;
import java.util.function.Supplier;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.TransformCropsRecipe;
import com.minelittlepony.unicopia.item.URecipes;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.server.world.BlockDestructionManager;
import com.minelittlepony.unicopia.util.TraceHelper;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.CarrotsBlock;
import net.minecraft.block.FarmlandBlock;
import net.minecraft.item.BoneMealItem;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.WorldEvents;
/**
* Earth Pony ability to grow crops
@ -57,10 +72,14 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
public boolean apply(Pony player, Pos data) {
int count = 0;
for (BlockPos pos : BlockPos.iterate(
data.pos().add(-2, -2, -2),
data.pos().add( 2, 2, 2))) {
count += applySingle(player.asWorld(), player.asWorld().getBlockState(pos), pos);
if (!applyDirectly(player, data.pos())) {
for (BlockPos pos : BlockPos.iterate(
data.pos().add(-2, -2, -2),
data.pos().add( 2, 2, 2))) {
count += applySingle(player, player.asWorld(), player.asWorld().getBlockState(pos), pos);
}
} else {
count = 1;
}
if (count > 0) {
@ -69,7 +88,7 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
return true;
}
protected int applySingle(World w, BlockState state, BlockPos pos) {
protected int applySingle(Pony player, World w, BlockState state, BlockPos pos) {
ItemStack stack = new ItemStack(Items.BONE_MEAL);
@ -77,12 +96,33 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
return growable.grow(w, state, pos) ? 1 : 0;
}
if (state.isOf(Blocks.CARROTS)) {
if (state.get(CarrotsBlock.AGE) == CarrotsBlock.MAX_AGE) {
boolean transform = w.random.nextInt(3) == 0;
spawnConversionParticles(w, pos, transform);
if (transform) {
w.setBlockState(pos, UBlocks.GOLD_ROOT.getDefaultState().with(CarrotsBlock.AGE, CarrotsBlock.MAX_AGE));
}
return 5;
}
}
if (w.getBlockState(pos).isIn(UTags.UNAFFECTED_BY_GROW_ABILITY)) {
return 0;
}
if (BoneMealItem.useOnFertilizable(stack, w, pos)) {
if (w.random.nextInt(350) == 0) {
if (w.getBlockState(pos.down()).isOf(Blocks.FARMLAND)) {
w.setBlockState(pos.down(), Blocks.DIRT.getDefaultState());
FarmlandBlock.setToDirt(null, state, w, pos.down());
}
w.setBlockState(pos, UBlocks.PLUNDER_VINE_BUD.getDefaultState());
} else if (w.random.nextInt(5000) == 0) {
if (w.getBlockState(pos.down()).isOf(Blocks.FARMLAND)) {
FarmlandBlock.setToDirt(null, state, w, pos.down());
}
UBlocks.CURING_JOKE.grow(w, state, pos);
}
return 1;
}
@ -94,6 +134,56 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
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
public void warmUp(Pony player, AbilitySlot slot) {
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.Race;
import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.Living;
@ -17,6 +18,7 @@ import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.server.world.BlockDestructionManager;
import com.minelittlepony.unicopia.util.PosHelper;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
@ -26,6 +28,7 @@ import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.particle.BlockStateParticleEffect;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
@ -33,6 +36,7 @@ import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.WorldEvents;
@ -156,7 +160,10 @@ public class EarthPonyStompAbility implements Ability<Hit> {
spawnEffectAround(player, center, radius, rad);
ParticleUtils.spawnParticle(player.getWorld(), UParticles.GROUND_POUND, player.getX(), player.getY() - 1, player.getZ(), 0, 0, 0);
ParticleUtils.spawnParticle(player.getWorld(), UParticles.SHOCKWAVE, player.getX(), player.getY() - 1, player.getZ(), 0, 0, 0);
BlockState steppingState = player.getSteppingBlockState();
if (steppingState.isIn(UTags.KICKS_UP_DUST)) {
ParticleUtils.spawnParticle(player.getWorld(), new BlockStateParticleEffect(UParticles.DUST_CLOUD, steppingState), player.getBlockPos().down().toCenterPos(), Vec3d.ZERO);
}
iplayer.subtractEnergyCost(rad);
iplayer.asEntity().addExhaustion(3);
@ -218,6 +225,12 @@ public class EarthPonyStompAbility implements Ability<Hit> {
} else {
w.syncWorldEvent(WorldEvents.BLOCK_BROKEN, pos, Block.getRawIdFromState(state));
}
if (state.isIn(UTags.KICKS_UP_DUST)) {
if (w.random.nextInt(4) == 0 && w.isAir(pos.up()) && w.getFluidState(pos.up()).isEmpty()) {
ParticleUtils.spawnParticle(w, new BlockStateParticleEffect(UParticles.DUST_CLOUD, state), pos.up().toCenterPos(), VecHelper.supply(() -> w.random.nextTriangular(0, 0.1F)));
}
}
}
@Override

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

View file

@ -7,6 +7,7 @@ import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.player.Pony;
@ -92,7 +93,11 @@ public class UnicornDispellAbility implements Ability<Pos> {
public boolean apply(Pony player, Pos data) {
player.setAnimation(Animation.WOLOLO, Animation.Recipient.ANYONE);
Caster.stream(VecHelper.findInRange(player.asEntity(), player.asWorld(), data.vec(), 3, EquinePredicates.IS_PLACED_SPELL).stream()).forEach(target -> {
target.getSpellSlot().clear();
target.getSpellSlot().forEach(spell -> {
spell.setDead();
spell.tickDying(target);
return Operation.ofBoolean(!spell.isDead());
}, true);
});
return true;
}

View file

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

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

View file

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

View file

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

View file

@ -1,12 +1,16 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.*;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.ParticleHandle;
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.particle.OrientedBillboardParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleSpawner;
import com.minelittlepony.unicopia.particle.TargetBoundParticleEffect;
import com.minelittlepony.unicopia.server.world.ModificationType;
import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.util.shape.Shape;
@ -26,7 +30,8 @@ public class RainboomAbilitySpell extends AbstractSpell {
private static final int RADIUS = 5;
private static final Shape EFFECT_RANGE = new Sphere(false, RADIUS);
private final ParticleHandle particlEffect = new ParticleHandle();
@Nullable
private ParticleSpawner boundParticle;
private int age;
@ -35,11 +40,6 @@ public class RainboomAbilitySpell extends AbstractSpell {
setHidden(true);
}
@Override
protected void onDestroyed(Caster<?> source) {
particlEffect.destroy();
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
@ -47,14 +47,15 @@ public class RainboomAbilitySpell extends AbstractSpell {
return false;
}
particlEffect.update(getUuid(), source, spawner -> {
spawner.addParticle(UParticles.RAINBOOM_TRAIL, source.getOriginVector(), Vec3d.ZERO);
}).ifPresent(attachment -> {
attachment.setAttribute(Attachment.ATTR_BOUND, 1);
});
if (source.isClient()) {
// source.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, source.getPhysics().getMotionAngle()), source.getOriginVector(), Vec3d.ZERO);
if (boundParticle == null) {
boundParticle = InteractionManager.INSTANCE.createBoundParticle(getUuid());
}
boundParticle.addParticle(new TargetBoundParticleEffect(UParticles.RAINBOOM_TRAIL, source.asEntity()), source.getOriginVector(), Vec3d.ZERO);
if (age == 0) {
source.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, source.getPhysics().getMotionAngle()), source.getOriginVector(), Vec3d.ZERO);
}
}
source.findAllEntitiesInRange(RADIUS).forEach(e -> {
@ -92,5 +93,6 @@ public class RainboomAbilitySpell extends AbstractSpell {
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
age = compound.getInt("age");
boundParticle = null;
}
}

View file

@ -61,6 +61,8 @@ public interface Spell extends NbtSerialisable, Affine {
*/
boolean isDead();
boolean isDying();
/**
* Returns true if this effect has changes that need to be sent to the client.
*/
@ -68,6 +70,7 @@ public interface Spell extends NbtSerialisable, Affine {
/**
* Applies this spell to the supplied caster.
* @param caster The caster to apply the spell to
*/
default boolean apply(Caster<?> caster) {
caster.getSpellSlot().put(this);
@ -76,7 +79,7 @@ public interface Spell extends NbtSerialisable, Affine {
/**
* Gets the default form of this spell used to apply to a caster.
* @param caster
* @param caster The caster currently fueling this spell
*/
default Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return this;
@ -89,6 +92,12 @@ public interface Spell extends NbtSerialisable, Affine {
*/
boolean tick(Caster<?> caster, Situation situation);
/**
* Called on spells that are actively dying to update any post-death animations before removal.
* @param caster The caster currently fueling this spell
*/
void tickDying(Caster<?> caster);
/**
* Marks this effect as dirty.
*/

View file

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

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

View file

@ -9,6 +9,7 @@ import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity;
@ -42,6 +43,8 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO);
}
});
} else {
Ether.get(source.asWorld()).getOrCreate(this, source);
}
source.findAllSpellsInRange(radius, e -> isValidTarget(source, e)).filter(caster -> !caster.hasCommonOwner(source)).forEach(caster -> {
@ -51,6 +54,11 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
return !isDead();
}
@Override
protected void onDestroyed(Caster<?> caster) {
Ether.get(caster.asWorld()).remove(this, caster);
}
/**
* Calculates the maximum radius of the shield. aka The area of effect.
*/

View file

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

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

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

View file

@ -12,7 +12,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.util.math.Vec3d;
/**
* An area-effect spell that disperses illussions.
* An area-effect spell that disperses illusions.
*/
public class DisperseIllusionSpell extends AbstractAreaEffectSpell {
protected DisperseIllusionSpell(CustomisedSpellType<?> type) {

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.entity.EntityReference;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
@ -16,7 +14,7 @@ import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.math.Vec3d;
public class DisplacementSpell extends AbstractSpell implements HomingSpell, PlaceableSpell.PlacementDelegate, ProjectileDelegate.EntityHitListener {
public class DisplacementSpell extends AbstractSpell implements HomingSpell, ProjectileDelegate.EntityHitListener {
private final EntityReference<Entity> target = new EntityReference<>();
@ -67,19 +65,6 @@ public class DisplacementSpell extends AbstractSpell implements HomingSpell, Pla
originator.subtractEnergyCost(destinationPos.distanceTo(sourcePos) / 20F);
}
@Override
public void onPlaced(Caster<?> source, PlaceableSpell parent, CastSpellEntity entity) {
}
@Override
public void updatePlacement(Caster<?> caster, PlaceableSpell parent) {
parent.getParticleEffectAttachment(caster).ifPresent(attachment -> {
float r = 3 - (1 - ((ticks + 10) / 20F)) * 3;
attachment.setAttribute(Attachment.ATTR_RADIUS, r);
});
}
private void teleport(Caster<?> source, Entity entity, Vec3d pos, Vec3d vel) {
entity.teleport(pos.x, pos.y, pos.z);
entity.setVelocity(vel);

View file

@ -48,7 +48,7 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
if (caster instanceof MagicProjectileEntity && getTraits().get(Trait.FOCUS) >= 50) {
caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49,
EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.notOwnerOrFriend(this, caster))
EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster))
).findFirst().ifPresent(target -> {
((MagicProjectileEntity)caster).setHomingTarget(target);
});
@ -60,7 +60,7 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
if (getTraits().get(Trait.FOCUS) >= 50 && target.getOrEmpty(caster.asWorld()).isEmpty()) {
target.set(caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49,
EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.notOwnerOrFriend(this, caster))
EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster))
).findFirst().orElse(null));
}

View file

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

View file

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

View file

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

View file

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

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.inventory.Inventory;
import net.minecraft.item.Item;
import net.minecraft.item.SpawnEggItem;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
@ -41,6 +42,7 @@ import net.minecraft.registry.Registries;
public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
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<>();
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) {
if (item instanceof ItemWithTraits i) {
return i.getDefaultTraits();
}
if (item instanceof SpawnEggItem) {
return SPAWN_EGG_TRAITS;
}
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.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 Settings settings() {
@ -39,61 +39,29 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
}
@Override
public boolean hasRandomTicks(BlockState state) {
return !state.get(PERSISTENT);
}
@Override
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.randomTick(state, world, pos, random);
tryAdvanceStage(state, world, pos, random);
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
if (state.get(PERSISTENT)) {
return state;
public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
if (state.get(PERSISTENT) || oldState.isOf(state.getBlock())) {
return;
}
if (world instanceof ServerWorld sw) {
ZapAppleStageStore store = ZapAppleStageStore.get(sw);
ZapAppleStageStore.Stage currentStage = store.getStage();
if (currentStage == ZapAppleStageStore.Stage.HIBERNATING) {
return currentStage.getNewState(state);
}
}
return state;
updateStage(state, world, pos);
}
@Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.scheduledTick(state, world, pos, random);
tryAdvanceStage(state, world, pos, random);
if (!state.get(PERSISTENT)) {
world.scheduleBlockTick(pos, this, 1);
}
}
private void tryAdvanceStage(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (state.get(PERSISTENT)) {
return;
}
ZapAppleStageStore store = ZapAppleStageStore.get(world);
ZapAppleStageStore.Stage newStage = store.getStage();
if (!world.isDay() && getStage(state).mustChangeIntoInstantly(newStage)) {
world.setBlockState(pos, newStage.getNewState(state));
onStageChanged(store, newStage, world, state, pos, random);
}
tryAdvanceStage(state, world, pos, random);
}
protected ZapAppleStageStore.Stage getStage(BlockState state) {
@Override
public ZapAppleStageStore.Stage getStage(BlockState state) {
return ZapAppleStageStore.Stage.FLOWERING;
}
@Override
protected boolean shouldDecay(BlockState state) {
protected final boolean shouldDecay(BlockState state) {
return false;
}
@ -124,40 +92,10 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
@Override
public int getTint(BlockState state, @Nullable BlockRenderView world, @Nullable BlockPos pos, int foliageColor) {
if (pos == null) {
return 0x4C7EFA;
}
return TintedBlock.blend(TintedBlock.rotate(foliageColor, 2), 0x0000FF, 0.3F);
}
static void onStageChanged(ZapAppleStageStore store, ZapAppleStageStore.Stage stage, ServerWorld world, BlockState state, BlockPos pos, Random random) {
boolean mustFruit = Random.create(state.getRenderingSeed(pos)).nextInt(5) < 2;
BlockState below = world.getBlockState(pos.down());
if (world.isAir(pos.down())) {
if (stage == ZapAppleStageStore.Stage.FRUITING && mustFruit) {
world.setBlockState(pos.down(), UBlocks.ZAP_BULB.getDefaultState(), Block.NOTIFY_ALL);
store.triggerLightningStrike(pos);
}
}
if (stage != ZapAppleStageStore.Stage.HIBERNATING && world.getRandom().nextInt(10) == 0) {
store.triggerLightningStrike(pos);
}
if (stage == ZapAppleStageStore.Stage.RIPE) {
if (below.isOf(UBlocks.ZAP_BULB)) {
world.setBlockState(pos.down(), UBlocks.ZAP_APPLE.getDefaultState(), Block.NOTIFY_ALL);
store.playMoonEffect(pos);
}
}
if (mustFruit && stage == ZapAppleStageStore.Stage.HIBERNATING) {
if (below.isOf(UBlocks.ZAP_APPLE) || below.isOf(UBlocks.ZAP_BULB)) {
world.setBlockState(pos.down(), Blocks.AIR.getDefaultState());
}
}
}
}

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

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.block;
import java.util.List;
import com.minelittlepony.unicopia.ability.EarthPonyKickAbility.Buckable;
import com.mojang.datafixers.util.Function5;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
@ -23,20 +24,24 @@ public class FruitBlock extends Block implements Buckable {
public static final int DEFAULT_FRUIT_SIZE = 5;
public static final double DEFAULT_STEM_OFFSET = 2.6F;
public static final VoxelShape DEFAULT_SHAPE = createFruitShape(DEFAULT_STEM_OFFSET, DEFAULT_FRUIT_SIZE);
public static final MapCodec<FruitBlock> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
Direction.CODEC.fieldOf("attachment_face").forGetter(b -> b.attachmentFace),
Registries.BLOCK.getCodec().fieldOf("stem").forGetter(b -> b.stem),
RecordCodecBuilder.<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 static final MapCodec<FruitBlock> CODEC = createCodec(FruitBlock::new);
private final Direction attachmentFace;
private final Block stem;
private final VoxelShape shape;
protected final Direction attachmentFace;
protected final Block stem;
protected final VoxelShape shape;
public static <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) {
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.SoggyCloudStairsBlock;
import com.minelittlepony.unicopia.block.cloud.UnstableCloudBlock;
import com.minelittlepony.unicopia.entity.effect.UEffects;
import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.item.cloud.CloudBlockItem;
import com.minelittlepony.unicopia.item.group.ItemGroupRegistry;
@ -28,6 +29,7 @@ import com.minelittlepony.unicopia.server.world.UTreeGen;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
import net.fabricmc.fabric.api.registry.StrippableBlockRegistry;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.block.*;
import net.minecraft.block.AbstractBlock.Settings;
import net.minecraft.block.entity.BlockEntityType;
@ -136,6 +138,15 @@ public interface UBlocks {
() -> UItems.APPLE_PIE_HOOF,
Settings.create().solid().mapColor(MapColor.ORANGE).strength(0.5F).sounds(BlockSoundGroup.WOOL).pistonBehavior(PistonBehavior.DESTROY)
));
Block GOLDEN_OAK_LEAVES = register("golden_oak_leaves", new GoldenOakLeavesBlock(
MapColor.GOLD.color,
() -> UBlocks.GOLDEN_APPLE,
() -> Items.GOLDEN_APPLE.getDefaultStack(),
FabricBlockSettings.copy(Blocks.OAK_LEAVES)
), ItemGroups.NATURAL);
Block GOLDEN_APPLE = register("golden_apple", new EnchantedFruitBlock(Direction.DOWN, GOLDEN_OAK_LEAVES, FruitBlock.DEFAULT_SHAPE, false, Settings.create().mapColor(MapColor.GOLD)));
Block GOLDEN_OAK_SPROUT = register("golden_oak_sprout", new SproutBlock(0xE5FFCC88, () -> UItems.GOLDEN_OAK_SEEDS, () -> UTreeGen.GOLDEN_APPLE_TREE.sapling().map(Block::getDefaultState).get(), SproutBlock.settings()));
Block GOLDEN_OAK_LOG = register("golden_oak_log", BlockConstructionUtils.createLogBlock(MapColor.OFF_WHITE, MapColor.GOLD), ItemGroups.BUILDING_BLOCKS);
SegmentedCropBlock OATS = register("oats", SegmentedCropBlock.create(11, 5, () -> UItems.OAT_SEEDS, null, null, AbstractBlock.Settings.copy(Blocks.WHEAT)));
SegmentedCropBlock OATS_STEM = register("oats_stem", OATS.createNext(5));
@ -143,6 +154,13 @@ public interface UBlocks {
Block PLUNDER_VINE = register("plunder_vine", new ThornBlock(() -> UBlocks.PLUNDER_VINE_BUD, Settings.create().mapColor(MapColor.DARK_CRIMSON).hardness(1).ticksRandomly().sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.DESTROY)));
Block PLUNDER_VINE_BUD = register("plunder_vine_bud", new ThornBudBlock(PLUNDER_VINE.getDefaultState(), Settings.create().mapColor(MapColor.DARK_CRIMSON).hardness(1).nonOpaque().ticksRandomly().sounds(BlockSoundGroup.GRASS).pistonBehavior(PistonBehavior.DESTROY)));
CuringJokeBlock CURING_JOKE = register("curing_joke", new CuringJokeBlock(UEffects.BUTTER_FINGERS, 7, AbstractBlock.Settings.create().mapColor(MapColor.PALE_PURPLE).noCollision().breakInstantly().sounds(BlockSoundGroup.GRASS).offset(AbstractBlock.OffsetType.XZ).pistonBehavior(PistonBehavior.DESTROY)));
Block GOLD_ROOT = register("gold_root", new CarrotsBlock(AbstractBlock.Settings.create().mapColor(MapColor.GOLD).noCollision().ticksRandomly().breakInstantly().sounds(BlockSoundGroup.CROP).pistonBehavior(PistonBehavior.DESTROY)) {
@Override
protected ItemConvertible getSeedsItem() {
return Items.GOLDEN_CARROT;
}
});
Block CHITIN = register("chitin", new SnowyBlock(Settings.create().mapColor(MapColor.PALE_PURPLE).hardness(5).requiresTool().ticksRandomly().sounds(BlockSoundGroup.CORAL)), ItemGroups.NATURAL);
Block SURFACE_CHITIN = register("surface_chitin", new GrowableBlock(() -> CHITIN, Settings.copy(CHITIN)), ItemGroups.NATURAL);
@ -156,49 +174,42 @@ public interface UBlocks {
Block SLIME_PUSTULE = register("slime_pustule", new SlimePustuleBlock(Settings.copy(Blocks.SLIME_BLOCK)), ItemGroups.NATURAL);
Block SHAPING_BENCH = register("shaping_bench", new ShapingBenchBlock(Settings.create().mapColor(MapColor.OFF_WHITE).hardness(0.3F).resistance(0).sounds(BlockSoundGroup.WOOL)), ItemGroups.FUNCTIONAL);
Block CLOUD = register("cloud", new NaturalCloudBlock(Settings.create().mapColor(MapColor.OFF_WHITE).hardness(0.3F).resistance(0).sounds(BlockSoundGroup.WOOL), true,
Block CLOUD = register("cloud", new NaturalCloudBlock(true,
() -> UBlocks.SOGGY_CLOUD,
() -> UBlocks.COMPACTED_CLOUD), ItemGroups.NATURAL);
() -> UBlocks.COMPACTED_CLOUD,
Settings.create().mapColor(MapColor.OFF_WHITE).hardness(0.3F).resistance(0).sounds(BlockSoundGroup.WOOL)), ItemGroups.NATURAL);
Block COMPACTED_CLOUD = register("compacted_cloud", new CompactedCloudBlock(CLOUD.getDefaultState()));
Block CLOUD_SLAB = register("cloud_slab", new CloudSlabBlock(Settings.copy(CLOUD), true, () -> UBlocks.SOGGY_CLOUD_SLAB), ItemGroups.NATURAL);
PoreousCloudStairsBlock CLOUD_STAIRS = register("cloud_stairs", new PoreousCloudStairsBlock(CLOUD.getDefaultState(), Settings.copy(CLOUD), () -> UBlocks.SOGGY_CLOUD_STAIRS), ItemGroups.NATURAL);
Block CLOUD_SLAB = register("cloud_slab", new CloudSlabBlock(true, () -> UBlocks.SOGGY_CLOUD_SLAB, Settings.copy(CLOUD)), ItemGroups.NATURAL);
PoreousCloudStairsBlock CLOUD_STAIRS = register("cloud_stairs", new PoreousCloudStairsBlock(CLOUD.getDefaultState(), () -> UBlocks.SOGGY_CLOUD_STAIRS, Settings.copy(CLOUD)), ItemGroups.NATURAL);
Block CLOUD_PLANKS = register("cloud_planks", new NaturalCloudBlock(Settings.copy(CLOUD).hardness(0.4F).requiresTool().solid(), false,
null,
() -> UBlocks.COMPACTED_CLOUD_PLANKS), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_PLANKS = register("cloud_planks", new NaturalCloudBlock(false, null, () -> UBlocks.COMPACTED_CLOUD_PLANKS, Settings.copy(CLOUD).hardness(0.4F).requiresTool().solid()), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_CLOUD_PLANKS = register("compacted_cloud_planks", new CompactedCloudBlock(CLOUD_PLANKS.getDefaultState()));
Block CLOUD_PLANK_SLAB = register("cloud_plank_slab", new CloudSlabBlock(Settings.copy(CLOUD_PLANKS), false, null), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_PLANK_SLAB = register("cloud_plank_slab", new CloudSlabBlock(false, null, Settings.copy(CLOUD_PLANKS)), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_PLANK_STAIRS = register("cloud_plank_stairs", new CloudStairsBlock(CLOUD_PLANKS.getDefaultState(), Settings.copy(CLOUD_PLANKS)), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_BRICKS = register("cloud_bricks", new NaturalCloudBlock(Settings.copy(CLOUD).hardness(0.6F).requiresTool().solid(), false,
null,
() -> UBlocks.COMPACTED_CLOUD_BRICKS), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_BRICKS = register("cloud_bricks", new NaturalCloudBlock(false, null, () -> UBlocks.COMPACTED_CLOUD_BRICKS, Settings.copy(CLOUD).hardness(0.6F).requiresTool().solid()), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_CLOUD_BRICKS = register("compacted_cloud_bricks", new CompactedCloudBlock(CLOUD_BRICKS.getDefaultState()));
Block CLOUD_BRICK_SLAB = register("cloud_brick_slab", new CloudSlabBlock(Settings.copy(CLOUD_BRICKS), false, null), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_BRICK_SLAB = register("cloud_brick_slab", new CloudSlabBlock(false, null, Settings.copy(CLOUD_BRICKS)), ItemGroups.BUILDING_BLOCKS);
Block CLOUD_BRICK_STAIRS = register("cloud_brick_stairs", new CloudStairsBlock(CLOUD_BRICKS.getDefaultState(), Settings.copy(CLOUD_PLANKS)), ItemGroups.BUILDING_BLOCKS);
Block ETCHED_CLOUD = register("etched_cloud", new NaturalCloudBlock(Settings.copy(CLOUD_BRICKS), false,
null,
() -> UBlocks.COMPACTED_CLOUD_BRICKS), ItemGroups.BUILDING_BLOCKS);
Block ETCHED_CLOUD = register("etched_cloud", new NaturalCloudBlock(false, null, () -> UBlocks.COMPACTED_CLOUD_BRICKS, Settings.copy(CLOUD_BRICKS)), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_ETCHED_CLOUD = register("compacted_etched_cloud", new CompactedCloudBlock(ETCHED_CLOUD.getDefaultState()));
Block ETCHED_CLOUD_SLAB = register("etched_cloud_slab", new CloudSlabBlock(Settings.copy(ETCHED_CLOUD), false, null), ItemGroups.BUILDING_BLOCKS);
Block ETCHED_CLOUD_SLAB = register("etched_cloud_slab", new CloudSlabBlock(false, null, Settings.copy(ETCHED_CLOUD)), ItemGroups.BUILDING_BLOCKS);
Block ETCHED_CLOUD_STAIRS = register("etched_cloud_stairs", new CloudStairsBlock(ETCHED_CLOUD.getDefaultState(), Settings.copy(CLOUD_PLANKS)), ItemGroups.BUILDING_BLOCKS);
SoggyCloudBlock SOGGY_CLOUD = register("soggy_cloud", new SoggyCloudBlock(Settings.copy(CLOUD).hardness(0.7F), () -> UBlocks.CLOUD));
SoggyCloudSlabBlock SOGGY_CLOUD_SLAB = register("soggy_cloud_slab", new SoggyCloudSlabBlock(Settings.copy(SOGGY_CLOUD), () -> UBlocks.CLOUD_SLAB));
SoggyCloudStairsBlock SOGGY_CLOUD_STAIRS = register("soggy_cloud_stairs", new SoggyCloudStairsBlock(SOGGY_CLOUD.getDefaultState(), Settings.copy(CLOUD), () -> UBlocks.CLOUD_STAIRS));
SoggyCloudBlock SOGGY_CLOUD = register("soggy_cloud", new SoggyCloudBlock(() -> UBlocks.CLOUD, Settings.copy(CLOUD).hardness(0.7F)));
SoggyCloudSlabBlock SOGGY_CLOUD_SLAB = register("soggy_cloud_slab", new SoggyCloudSlabBlock(() -> UBlocks.CLOUD_SLAB, Settings.copy(SOGGY_CLOUD)));
SoggyCloudStairsBlock SOGGY_CLOUD_STAIRS = register("soggy_cloud_stairs", new SoggyCloudStairsBlock(SOGGY_CLOUD.getDefaultState(), () -> UBlocks.CLOUD_STAIRS, Settings.copy(CLOUD)));
Block DENSE_CLOUD = register("dense_cloud", new NaturalCloudBlock(Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL).solid(), false,
null,
() -> UBlocks.COMPACTED_DENSE_CLOUD), ItemGroups.BUILDING_BLOCKS);
Block DENSE_CLOUD = register("dense_cloud", new NaturalCloudBlock(false, null, () -> UBlocks.COMPACTED_DENSE_CLOUD, Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL).solid()), ItemGroups.BUILDING_BLOCKS);
Block COMPACTED_DENSE_CLOUD = register("compacted_dense_cloud", new CompactedCloudBlock(DENSE_CLOUD.getDefaultState()));
Block DENSE_CLOUD_SLAB = register("dense_cloud_slab", new CloudSlabBlock(Settings.copy(DENSE_CLOUD), false, null), ItemGroups.BUILDING_BLOCKS);
Block DENSE_CLOUD_SLAB = register("dense_cloud_slab", new CloudSlabBlock(false, null, Settings.copy(DENSE_CLOUD)), ItemGroups.BUILDING_BLOCKS);
Block DENSE_CLOUD_STAIRS = register("dense_cloud_stairs", new CloudStairsBlock(DENSE_CLOUD.getDefaultState(), Settings.copy(DENSE_CLOUD)), ItemGroups.BUILDING_BLOCKS);
Block CARVED_CLOUD = register("carved_cloud", new OrientedCloudBlock(Settings.copy(CLOUD).hardness(0.4F).requiresTool().solid(), false), ItemGroups.BUILDING_BLOCKS);
Block CARVED_CLOUD = register("carved_cloud", new OrientedCloudBlock(false, Settings.copy(CLOUD).hardness(0.4F).requiresTool().solid()), ItemGroups.BUILDING_BLOCKS);
Block UNSTABLE_CLOUD = register("unstable_cloud", new UnstableCloudBlock(Settings.copy(CLOUD)), ItemGroups.NATURAL);
Block CLOUD_PILLAR = register("cloud_pillar", new CloudPillarBlock(Settings.create().mapColor(MapColor.GRAY).hardness(0.5F).resistance(0).sounds(BlockSoundGroup.WOOL).solid()), ItemGroups.NATURAL);
Block CLOUD_CHEST = register("cloud_chest", new CloudChestBlock(Settings.copy(DENSE_CLOUD).instrument(Instrument.BASS).strength(2.5f), DENSE_CLOUD.getDefaultState()), ItemGroups.FUNCTIONAL);
Block CLOUD_CHEST = register("cloud_chest", new CloudChestBlock(DENSE_CLOUD.getDefaultState(), Settings.copy(DENSE_CLOUD).instrument(Instrument.BASS).strength(2.5f)), ItemGroups.FUNCTIONAL);
Block CLOTH_BED = register("cloth_bed", new FancyBedBlock("cloth", Settings.copy(Blocks.WHITE_BED).sounds(BlockSoundGroup.WOOD)));
Block CLOUD_BED = register("cloud_bed", new CloudBedBlock("cloud", CLOUD.getDefaultState(), Settings.copy(Blocks.WHITE_BED).sounds(BlockSoundGroup.WOOL)));
@ -209,7 +220,9 @@ public interface UBlocks {
Block STABLE_DOOR = register("stable_door", new StableDoorBlock(BlockSetType.OAK, Settings.copy(Blocks.OAK_DOOR)), ItemGroups.FUNCTIONAL);
Block DARK_OAK_DOOR = register("dark_oak_stable_door", new StableDoorBlock(BlockSetType.OAK, Settings.copy(Blocks.OAK_DOOR)), ItemGroups.FUNCTIONAL);
Block CRYSTAL_DOOR = register("crystal_door", new CrystalDoorBlock(UWoodTypes.CRYSTAL, Settings.copy(Blocks.IRON_DOOR)), ItemGroups.FUNCTIONAL);
Block CLOUD_DOOR = register("cloud_door", new CloudDoorBlock(Settings.copy(CLOUD), CLOUD.getDefaultState(), UWoodTypes.CLOUD), ItemGroups.FUNCTIONAL);
Block CLOUD_DOOR = register("cloud_door", new CloudDoorBlock(CLOUD.getDefaultState(), UWoodTypes.CLOUD, Settings.copy(CLOUD)), ItemGroups.FUNCTIONAL);
EdibleBlock HAY_BLOCK = register("hay_block", new EdibleBlock(new Identifier("hay_block"), new Identifier("wheat"), true));
private static <T extends Block> T register(String name, T item) {
return register(Unicopia.id(name), item);
@ -234,10 +247,15 @@ public interface UBlocks {
if (block instanceof CloudLike || block instanceof SlimePustuleBlock || block instanceof PileBlock) {
SEMI_TRANSPARENT_BLOCKS.add(block);
}
return Registry.register(Registries.BLOCK, id, block);
}
static void bootstrap() {
if (FabricLoader.getInstance().isModLoaded("farmersdelight")) {
register("rice_block", new EdibleBlock(new Identifier("farmersdelight", "rice_bale"), new Identifier("farmersdelight", "rice_panicle"), true));
register("straw_block", new EdibleBlock(new Identifier("farmersdelight", "straw_bale"), new Identifier("farmersdelight", "straw"), true));
}
BlockEntityTypeSupportHelper.of(BlockEntityType.SIGN).addSupportedBlocks(PALM_SIGN, PALM_WALL_SIGN);
BlockEntityTypeSupportHelper.of(BlockEntityType.HANGING_SIGN).addSupportedBlocks(PALM_HANGING_SIGN, PALM_WALL_HANGING_SIGN);
@ -245,21 +263,24 @@ public interface UBlocks {
StrippableBlockRegistry.register(PALM_LOG, STRIPPED_PALM_LOG);
StrippableBlockRegistry.register(ZAP_WOOD, STRIPPED_ZAP_WOOD);
StrippableBlockRegistry.register(PALM_WOOD, STRIPPED_PALM_WOOD);
Collections.addAll(TRANSLUCENT_BLOCKS, WEATHER_VANE, CHITIN_SPIKES, PLUNDER_VINE, PLUNDER_VINE_BUD, CLAM_SHELL, SCALLOP_SHELL, TURRET_SHELL);
Collections.addAll(TRANSLUCENT_BLOCKS, WEATHER_VANE, CHITIN_SPIKES, PLUNDER_VINE, PLUNDER_VINE_BUD, CLAM_SHELL, SCALLOP_SHELL, TURRET_SHELL, CURING_JOKE);
TintedBlock.REGISTRY.add(PALM_LEAVES);
FlammableBlockRegistry.getDefaultInstance().add(GREEN_APPLE_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(SWEET_APPLE_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(SOUR_APPLE_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(GOLDEN_OAK_LEAVES, 60, 120);
FlammableBlockRegistry.getDefaultInstance().add(MANGO_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(PALM_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(PALM_LOG, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(PALM_WOOD, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(GOLDEN_OAK_LOG, 15, 15);
FlammableBlockRegistry.getDefaultInstance().add(STRIPPED_PALM_LOG, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(STRIPPED_PALM_WOOD, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(PALM_PLANKS, 5, 20);
FlammableBlockRegistry.getDefaultInstance().add(BANANAS, 5, 20);
UBlockEntities.bootstrap();
EdibleBlock.bootstrap();
}
}

View file

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

View file

@ -5,11 +5,8 @@ import com.mojang.serialization.MapCodec;
import net.minecraft.block.*;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock {
public static final MapCodec<ZapAppleLeavesBlock> CODEC = createCodec(ZapAppleLeavesBlock::new);
@ -25,31 +22,13 @@ public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock {
return CODEC;
}
@Override
public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
if (state.get(PERSISTENT)
|| oldState.isOf(state.getBlock())
|| oldState.isOf(UBlocks.ZAP_LEAVES)
|| oldState.isOf(UBlocks.FLOWERING_ZAP_LEAVES)
|| oldState.isOf(UBlocks.ZAP_LEAVES_PLACEHOLDER)
|| !(world instanceof ServerWorld sw)) {
return;
}
ZapAppleStageStore store = ZapAppleStageStore.get(sw);
ZapAppleStageStore.Stage currentStage = store.getStage();
if (currentStage != getStage(state)) {
world.setBlockState(pos, currentStage.getNewState(state));
}
}
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(STAGE, ZapAppleStageStore.Stage.GREENING);
}
@Override
protected ZapAppleStageStore.Stage getStage(BlockState state) {
public ZapAppleStageStore.Stage getStage(BlockState state) {
return state.get(STAGE);
}

View file

@ -1,15 +1,16 @@
package com.minelittlepony.unicopia.block;
import com.minelittlepony.unicopia.server.world.ZapAppleStageStore;
import com.minelittlepony.unicopia.server.world.ZapAppleStageStore.Stage;
import com.mojang.serialization.MapCodec;
import net.minecraft.block.*;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.WorldAccess;
import net.minecraft.world.World;
public class ZapAppleLeavesPlaceholderBlock extends AirBlock {
public class ZapAppleLeavesPlaceholderBlock extends AirBlock implements ZapStagedBlock {
public static final MapCodec<ZapAppleLeavesPlaceholderBlock> CODEC = createCodec(ZapAppleLeavesPlaceholderBlock::new);
ZapAppleLeavesPlaceholderBlock(Settings settings) {
@ -23,37 +24,19 @@ public class ZapAppleLeavesPlaceholderBlock extends AirBlock {
}
@Override
public boolean hasRandomTicks(BlockState state) {
return true;
public Stage getStage(BlockState state) {
return ZapAppleStageStore.Stage.HIBERNATING;
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
if (world instanceof ServerWorld sw) {
ZapAppleStageStore store = ZapAppleStageStore.get(sw);
ZapAppleStageStore.Stage currentStage = store.getStage();
if (currentStage != ZapAppleStageStore.Stage.HIBERNATING) {
return currentStage.getNewState(state);
}
}
return state;
public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
updateStage(state, world, pos);
}
@Deprecated
@Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.scheduledTick(state, world, pos, random);
ZapAppleStageStore store = ZapAppleStageStore.get(world);
ZapAppleStageStore.Stage newStage = store.getStage();
if (!world.isDay() && ZapAppleStageStore.Stage.HIBERNATING.mustChangeIntoInstantly(newStage)) {
state = newStage.getNewState(state);
world.setBlockState(pos, state);
BaseZapAppleLeavesBlock.onStageChanged(store, newStage, world, state, pos, random);
}
world.scheduleBlockTick(pos, state.getBlock(), 1);
tryAdvanceStage(state, world, pos, random);
}
}

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,25 +4,43 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.block.state.StateUtil;
import com.minelittlepony.unicopia.util.CodecUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.block.BedBlock;
import net.minecraft.block.BlockState;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random;
public class PoreousCloudBlock extends CloudBlock implements Soakable {
@Nullable
private final Supplier<Soakable> soggyBlock;
private static final MapCodec<PoreousCloudBlock> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
Codec.BOOL.fieldOf("meltable").forGetter(b -> b.meltable),
CodecUtils.supplierOf(Soakable.CODEC).optionalFieldOf("soggy_block", null).forGetter(b -> b.soggyBlock),
BedBlock.createSettingsCodec()
).apply(instance, PoreousCloudBlock::new));
public PoreousCloudBlock(Settings settings, boolean meltable, @Nullable Supplier<Soakable> soggyBlock) {
super(settings.nonOpaque(), meltable);
@Nullable
protected final Supplier<Soakable> soggyBlock;
public PoreousCloudBlock(boolean meltable, @Nullable Supplier<Soakable> soggyBlock, Settings settings) {
super(meltable, settings.nonOpaque());
this.soggyBlock = soggyBlock;
}
@Override
public MapCodec<? extends PoreousCloudBlock> getCodec() {
return CODEC;
}
@Nullable
@Override
public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) {
return Soakable.copyProperties(state, getDefaultState());
return StateUtil.copyState(state, getDefaultState());
}
return soggyBlock == null ? null : soggyBlock.get().getStateWithMoisture(state, moisture);
}

View file

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

View file

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

View file

@ -1,21 +1,22 @@
package com.minelittlepony.unicopia.block.cloud;
import java.util.Arrays;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds;
import com.mojang.serialization.Codec;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.registry.Registries;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.state.property.IntProperty;
import net.minecraft.state.property.Property;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Util;
@ -27,6 +28,8 @@ import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
public interface Soakable {
Codec<Soakable> CODEC = Registries.BLOCK.getCodec().xmap(b -> (Soakable)b, s -> (Block)s);
IntProperty MOISTURE = IntProperty.of("moisture", 1, 7);
Direction[] DIRECTIONS = Arrays.stream(Direction.values()).filter(d -> d != Direction.UP).toArray(Direction[]::new);
@ -105,15 +108,4 @@ public interface Soakable {
world.setBlockState(pos, soakable.getStateWithMoisture(state, newMoisture));
world.playSound(null, pos, SoundEvents.ENTITY_SALMON_FLOP, SoundCategory.BLOCKS, 1, (float)world.random.nextTriangular(0.5, 0.3F));
}
@Nullable
@SuppressWarnings({ "rawtypes", "unchecked" })
static BlockState copyProperties(BlockState from, @Nullable BlockState to) {
if (to != null) {
for (Property property : from.getProperties()) {
to = to.withIfExists(property, from.get(property));
}
}
return to;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,7 +11,6 @@ import com.minelittlepony.unicopia.Unicopia;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.state.property.Property;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.registry.Registries;
@ -50,7 +49,7 @@ public abstract class StateChange {
return state;
}
return Registries.BLOCK.getOrEmpty(id).map(Block::getDefaultState)
.map(newState -> merge(newState, state))
.map(newState -> StateUtil.copyState(state, newState))
.orElse(state);
}
};
@ -101,17 +100,4 @@ public abstract class StateChange {
return serializer.apply(json);
}).orElseThrow(() -> new IllegalArgumentException("Invalid action " + action));
}
private static BlockState merge(BlockState into, BlockState from) {
for (var property : from.getProperties()) {
if (into.contains(property)) {
into = copy(into, from, property);
}
}
return into;
}
private static <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.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.function.Supplier;
@ -13,19 +13,20 @@ import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.FlightType;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.CasterView;
import com.minelittlepony.unicopia.client.gui.DismissSpellScreen;
import com.minelittlepony.unicopia.client.gui.spellbook.ClientChapters;
import com.minelittlepony.unicopia.client.particle.ClientBoundParticleSpawner;
import com.minelittlepony.unicopia.client.sound.*;
import com.minelittlepony.unicopia.entity.player.PlayerPhysics;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.entity.player.dummy.DummyClientPlayerEntity;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.particle.ParticleSpawner;
import com.mojang.authlib.GameProfile;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.sound.AggressiveBeeSoundInstance;
import net.minecraft.client.sound.MovingMinecartSoundInstance;
import net.minecraft.client.sound.PassiveBeeSoundInstance;
@ -37,32 +38,21 @@ import net.minecraft.entity.passive.BeeEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.vehicle.AbstractMinecartEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.sound.SoundCategory;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class ClientInteractionManager extends InteractionManager {
private final MinecraftClient client = MinecraftClient.getInstance();
private final Optional<CasterView> clientWorld = Optional.of(() -> MinecraftClient.getInstance().world);
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
public Map<Identifier, ?> readChapters(PacketByteBuf buffer) {
return buffer.readMap(PacketByteBuf::readIdentifier, ClientChapters::loadChapter);
return buffer.readMap(PacketByteBuf::readIdentifier, ClientChapters::loadChapter);
}
@Override
@ -159,4 +149,16 @@ public class ClientInteractionManager extends InteractionManager {
public int getViewMode() {
return client.options.getPerspective().ordinal();
}
@Override
public ParticleSpawner createBoundParticle(UUID id) {
return new ClientBoundParticleSpawner(id);
}
@Override
public void sendPlayerLookAngles(PlayerEntity player) {
if (player instanceof ClientPlayerEntity c) {
c.networkHandler.sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(player.getYaw(), player.getPitch(), player.isOnGround()));
}
}
}

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.CloudsEscapingParticle;
import com.minelittlepony.unicopia.client.particle.DiskParticle;
import com.minelittlepony.unicopia.client.particle.DustCloudParticle;
import com.minelittlepony.unicopia.client.particle.FloatingBubbleParticle;
import com.minelittlepony.unicopia.client.particle.GroundPoundParticle;
import com.minelittlepony.unicopia.client.particle.HealthDrainParticle;
import com.minelittlepony.unicopia.client.particle.LightningBoltParticle;
@ -22,6 +24,7 @@ import com.minelittlepony.unicopia.client.particle.ShockwaveParticle;
import com.minelittlepony.unicopia.client.particle.SphereParticle;
import com.minelittlepony.unicopia.client.render.*;
import com.minelittlepony.unicopia.client.render.entity.*;
import com.minelittlepony.unicopia.client.render.shader.UShaders;
import com.minelittlepony.unicopia.client.render.spell.SpellRendererFactory;
import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.item.ChameleonItem;
@ -59,12 +62,14 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView;
@SuppressWarnings("deprecation")
public interface URenderers {
BlockEntity CHEST_RENDER_ENTITY = new CloudChestBlock.TileData(BlockPos.ORIGIN, UBlocks.CLOUD_CHEST.getDefaultState());
static void bootstrap() {
ParticleFactoryRegistry.getInstance().register(UParticles.UNICORN_MAGIC, createFactory(MagicParticle::new));
ParticleFactoryRegistry.getInstance().register(UParticles.CHANGELING_MAGIC, createFactory(ChangelingMagicParticle::new));
ParticleFactoryRegistry.getInstance().register(UParticles.BUBBLE, createFactory(FloatingBubbleParticle::new));
ParticleFactoryRegistry.getInstance().register(UParticles.RAIN_DROPS, createFactory(RaindropsParticle::new));
ParticleFactoryRegistry.getInstance().register(UParticles.HEALTH_DRAIN, createFactory(HealthDrainParticle::create));
ParticleFactoryRegistry.getInstance().register(UParticles.RAINBOOM_RING, RainboomParticle::new);
@ -76,6 +81,7 @@ public interface URenderers {
ParticleFactoryRegistry.getInstance().register(UParticles.GROUND_POUND, GroundPoundParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.CLOUDS_ESCAPING, CloudsEscapingParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.LIGHTNING_BOLT, LightningBoltParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.DUST_CLOUD, DustCloudParticle::new);
AccessoryFeatureRenderer.register(
BraceletFeatureRenderer::new, AmuletFeatureRenderer::new, GlassesFeatureRenderer::new,
@ -97,6 +103,8 @@ public interface URenderers {
EntityRendererRegistry.register(UEntities.AIR_BALLOON, AirBalloonEntityRenderer::new);
EntityRendererRegistry.register(UEntities.FRIENDLY_CREEPER, FriendlyCreeperEntityRenderer::new);
EntityRendererRegistry.register(UEntities.LOOT_BUG, LootBugEntityRenderer::new);
EntityRendererRegistry.register(UEntities.TENTACLE, TentacleEntityRenderer::new);
EntityRendererRegistry.register(UEntities.IGNOMINIOUS_BULB, IgnominiousBulbEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.FANCY_BED, CloudBedBlockEntityRenderer::new);
@ -108,6 +116,7 @@ public interface URenderers {
PolearmRenderer.register(UItems.WOODEN_POLEARM, UItems.STONE_POLEARM, UItems.IRON_POLEARM, UItems.GOLDEN_POLEARM, UItems.DIAMOND_POLEARM, UItems.NETHERITE_POLEARM);
ModelPredicateProviderRegistry.register(UItems.GEMSTONE, new Identifier("affinity"), (stack, world, entity, seed) -> EnchantableItem.isEnchanted(stack) ? EnchantableItem.getSpellKey(stack).getAffinity().getAlignment() : 0);
ModelPredicateProviderRegistry.register(UItems.ROCK_CANDY, new Identifier("count"), (stack, world, entity, seed) -> stack.getCount() / (float)stack.getMaxCount());
ModelPredicateProviderRegistry.register(Unicopia.id("zap_cycle"), (stack, world, entity, seed) -> UnicopiaClient.getInstance().getZapStageDelta());
ColorProviderRegistry.BLOCK.register(URenderers::getTintedBlockColor, TintedBlock.REGISTRY.stream().toArray(Block[]::new));
ColorProviderRegistry.ITEM.register((stack, i) -> getTintedBlockColor(Block.getBlockFromItem(stack.getItem()).getDefaultState(), null, null, i), TintedBlock.REGISTRY.stream().map(Block::asItem).filter(i -> i != Items.AIR).toArray(Item[]::new));
@ -123,6 +132,7 @@ public interface URenderers {
TerraformBoatClientHelper.registerModelLayers(Unicopia.id("palm"), false);
SpellRendererFactory.bootstrap();
UShaders.bootstrap();
}
private static void register(DynamicItemRenderer renderer, ItemConvertible...items) {

View file

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

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.function.BiConsumer;
import it.unimi.dsi.fastutil.ints.Int2IntFunction;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextHandler;
@ -31,12 +30,18 @@ public class ParagraphWrappingVisitor implements StyledVisitor<Object> {
}
@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);
while (!s.isEmpty()) {
int trimmedLength = handler.getTrimmedLength(s, remainingLength, style);
int trimmedLength = handler.getTrimmedLength(s, remainingLength, inlineStyle);
int newline = s.indexOf('\n');
if (newline >= 0 && newline < trimmedLength) {
@ -57,7 +62,7 @@ public class ParagraphWrappingVisitor implements StyledVisitor<Object> {
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);
// advance if appending the next segment would cause an overflow
@ -101,14 +106,42 @@ public class ParagraphWrappingVisitor implements StyledVisitor<Object> {
}
public void advance() {
line++;
if (progressedNonEmpty || currentLineCollectedLength > 0) {
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();
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.magic.spell.effect.CustomisedSpellType;
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.entity.ItemTracker;
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) {
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);
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.unicopia.ability.magic.spell.crafting.SpellbookRecipe;
import com.minelittlepony.unicopia.client.gui.DrawableUtil;
import com.minelittlepony.unicopia.client.gui.MagicText;
import com.minelittlepony.unicopia.container.SpellbookState;
import com.minelittlepony.unicopia.item.URecipes;
import com.mojang.blaze3d.systems.RenderSystem;
@ -43,13 +44,10 @@ public class SpellbookCraftingPageContent extends ScrollContainer implements Spe
@Override
public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) {
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);
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());
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

View file

@ -72,7 +72,7 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content
float currentScaledLevel = pony.getLevel().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(
currentScaledLevel,
currentCorruption
@ -89,7 +89,7 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content
matrices.push();
matrices.translate(screen.getBackgroundWidth() / 2 + SpellbookScreen.TITLE_X - 10, y, 0);
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();
Bounds bounds = screen.getFrameBounds();

View file

@ -159,7 +159,6 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
tabs.getAllTabs().forEach(tab -> {
Bounds bounds = tab.bounds();
chapters.getCurrentChapter();
boolean hover = bounds.contains(mouseX, mouseY);
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.dimension.Bounds;
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.SpellbookChapterList.Content;
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.entity.player.Pony;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.network.PacketByteBuf;
@ -21,13 +21,15 @@ import net.minecraft.util.*;
public class DynamicContent implements Content {
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 final List<Page> pages;
private Bounds bounds = Bounds.empty();
private final Panel leftPanel = new Panel(this);
private final Panel rightPanel = new Panel(this);
public DynamicContent(PacketByteBuf buffer) {
pages = buffer.readList(Page::new);
}
@ -35,32 +37,25 @@ public class DynamicContent implements Content {
@Override
public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) {
int pageIndex = state.getOffset() * 2;
MinecraftClient client = MinecraftClient.getInstance();
getPage(pageIndex).ifPresent(page -> page.draw(context, mouseX, mouseY, container));
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);
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);
}
@Override
public void copyStateFrom(Content old) {
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;
setBounds(o.bounds);
}
}
private Optional<Page> getPage(int index) {
Optional<Page> getPage(int index) {
if (index < 0 || index >= pages.size()) {
return Optional.empty();
}
@ -71,9 +66,15 @@ public class DynamicContent implements Content {
this.bounds = bounds;
pages.forEach(page -> {
page.reset();
int oldHeight = page.bounds.height;
page.bounds.copy(bounds);
page.bounds.left = 0;
page.bounds.top = 0;
page.bounds.width /= 2;
page.bounds.height = oldHeight;
});
leftPanel.setBounds(bounds);
}
@Override
@ -83,6 +84,10 @@ public class DynamicContent implements Content {
screen.addPageButtons(187, 30, 350, incr -> {
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 {
@ -131,6 +136,19 @@ public class DynamicContent implements Content {
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
public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) {
@ -141,26 +159,24 @@ public class DynamicContent implements Content {
if (!compiled) {
compiled = true;
int relativeY = 0;
int textHeight = 0;
for (PageElement element : elements.stream().filter(PageElement::isInline).toList()) {
element.compile(relativeY, container);
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();
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.translate(bounds.left, bounds.top + 16, 0);
matrices.translate(0, -8, 0);
elements.stream().filter(PageElement::isFloating).forEach(element -> {
Bounds bounds = element.bounds();
matrices.push();
bounds.translate(matrices);
element.bounds().translate(matrices);
element.draw(context, mouseX, mouseY, container);
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.unicopia.ability.magic.spell.crafting.SpellbookRecipe;
import com.minelittlepony.unicopia.client.gui.spellbook.IngredientTree;
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookScreen;
import com.minelittlepony.unicopia.entity.player.Pony;
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 {
@Override
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 -> {
if (recipe instanceof SpellbookRecipe spellRecipe) {
boolean needsMoreXp = page.getLevel() < 0 || Pony.of(MinecraftClient.getInstance().player).getLevel().get() < page.getLevel();
IngredientTree tree = new IngredientTree(
bounds().left + page().getBounds().left,
bounds().top + page().getBounds().top + y + 10,
bounds().left,
bounds().top + y - 10,
page().getBounds().width - 20
).obfuscateResult(needsMoreXp);
spellRecipe.buildCraftingTree(tree);

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