Merge branch '1.20.1' into 1.20.2

# Conflicts:
#	src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/Recipe.java
#	src/main/resources/assets/unicopia/lang/zh_cn.json
This commit is contained in:
Sollace 2024-02-02 21:08:49 +00:00
commit ad1784b417
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
471 changed files with 9175 additions and 2817 deletions

80
assets/accretion_disk.svg Normal file
View file

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

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
assets/models/bulb_idle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -1,50 +0,0 @@
// Made with Blockbench 4.8.3
// Exported for Minecraft version 1.17 or later with Mojang mappings
// Paste this class into your mod and generate all required imports
public class cloud_bed<T extends Entity> extends EntityModel<T> {
// This layer location should be baked with EntityRendererProvider.Context in the entity renderer and passed into this model's constructor
public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation("modid", "cloud_bed"), "main");
private final ModelPart head;
private final ModelPart foot;
public cloud_bed(ModelPart root) {
this.head = root.getChild("head");
this.foot = root.getChild("foot");
}
public static LayerDefinition createBodyLayer() {
MeshDefinition meshdefinition = new MeshDefinition();
PartDefinition partdefinition = meshdefinition.getRoot();
PartDefinition head = partdefinition.addOrReplaceChild("head", CubeListBuilder.create(), PartPose.offset(0.0F, 24.0F, 0.0F));
PartDefinition main_r1 = head.addOrReplaceChild("main_r1", CubeListBuilder.create().texOffs(0, 0).addBox(-8.0F, -21.0F, -9.0F, 16.0F, 13.0F, 6.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 0.0F, -16.0F, -1.5708F, 0.0F, 0.0F));
PartDefinition head_board = head.addOrReplaceChild("head_board", CubeListBuilder.create().texOffs(52, 24).addBox(7.0F, -13.0F, -3.0F, 2.0F, 13.0F, 3.0F, new CubeDeformation(0.0F))
.texOffs(0, 43).addBox(-6.0F, -15.0F, -2.0F, 13.0F, 15.0F, 3.0F, new CubeDeformation(0.0F))
.texOffs(52, 24).addBox(-8.0F, -13.0F, -3.0F, 2.0F, 13.0F, 3.0F, new CubeDeformation(0.0F)), PartPose.offset(-0.5F, 0.0F, 7.0F));
PartDefinition foot = partdefinition.addOrReplaceChild("foot", CubeListBuilder.create(), PartPose.offset(0.0F, 24.0F, 0.0F));
PartDefinition main_r2 = foot.addOrReplaceChild("main_r2", CubeListBuilder.create().texOffs(0, 22).addBox(-8.0F, -8.0F, -9.0F, 16.0F, 15.0F, 6.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 0.0F, 0.0F, -1.5708F, 0.0F, 0.0F));
PartDefinition head_board2 = foot.addOrReplaceChild("head_board2", CubeListBuilder.create().texOffs(52, 40).addBox(6.0F, -13.0F, -2.0F, 3.0F, 10.0F, 3.0F, new CubeDeformation(0.0F))
.texOffs(0, 43).addBox(-6.0F, -15.0F, -3.0F, 13.0F, 11.0F, 3.0F, new CubeDeformation(0.0F))
.texOffs(52, 40).addBox(-8.0F, -13.0F, -2.0F, 3.0F, 10.0F, 3.0F, new CubeDeformation(0.0F)), PartPose.offset(-0.5F, 3.0F, -7.0F));
return LayerDefinition.create(meshdefinition, 64, 64);
}
@Override
public void setupAnim(T entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
}
@Override
public void renderToBuffer(PoseStack poseStack, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {
head.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
foot.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
}
}

View file

@ -1,32 +0,0 @@
// Made with Blockbench 4.8.3
// Exported for Minecraft version 1.17+ for Yarn
// Paste this class into your mod and generate all required imports
public class cloud_chest extends EntityModel<Entity> {
private final ModelPart lid;
private final ModelPart lock_r1;
private final ModelPart bb_main;
public cloud_chest(ModelPart root) {
this.lid = root.getChild("lid");
this.bb_main = root.getChild("bb_main");
}
public static TexturedModelData getTexturedModelData() {
ModelData modelData = new ModelData();
ModelPartData modelPartData = modelData.getRoot();
ModelPartData lid = modelPartData.addChild("lid", ModelPartBuilder.create().uv(0, 0).cuboid(-1.0F, -2.0F, 13.8F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-1.0F, -1.0F, 14.0F, 2.0F, 2.0F, 1.0F, new Dilation(0.0F))
.uv(0, 0).cuboid(-7.0F, -5.0F, 0.0F, 14.0F, 5.0F, 14.0F, new Dilation(0.3F)), ModelTransform.of(0.0F, 16.0F, -7.0F, 1.0908F, 0.0F, 0.0F));
ModelPartData lock_r1 = lid.addChild("lock_r1", ModelPartBuilder.create().uv(0, 0).cuboid(-2.0F, -4.0F, -0.5F, 2.0F, 4.0F, 1.0F, new Dilation(0.0F)), ModelTransform.of(-2.0F, 1.0F, 14.3F, 0.0F, 0.0F, 1.5708F));
ModelPartData bb_main = modelPartData.addChild("bb_main", ModelPartBuilder.create().uv(0, 19).cuboid(-7.0F, -10.0F, -7.0F, 14.0F, 10.0F, 14.0F, new Dilation(0.0F)), ModelTransform.pivot(0.0F, 24.0F, 0.0F));
return TexturedModelData.of(modelData, 64, 64);
}
@Override
public void setAngles(Entity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
}
@Override
public void render(MatrixStack matrices, VertexConsumer vertexConsumer, int light, int overlay, float red, float green, float blue, float alpha) {
lid.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
bb_main.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
}
}

File diff suppressed because one or more lines are too long

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

@ -0,0 +1,113 @@
{
"textures": {
"top": "blocks/hay_block_top",
"particle": "blocks/hay_block_side",
"side": "blocks/hay_block_side"
},
"elements": [
{
"name": "bottom_south_east",
"from": [8, 0, 8],
"to": [16, 8, 16],
"faces": {
"north": {"uv": [0, 8, 8, 16], "texture": "#top"},
"east": {"uv": [0, 8, 8, 16], "texture": "#side"},
"south": {"uv": [8, 8, 16, 16], "texture": "#side"},
"west": {"uv": [8, 8, 16, 16], "texture": "#top"},
"up": {"uv": [8, 8, 16, 16], "texture": "#top"},
"down": {"uv": [8, 0, 16, 8], "texture": "#top"}
}
},
{
"name": "top_south_east",
"from": [8, 8, 8],
"to": [16, 16, 16],
"faces": {
"north": {"uv": [0, 0, 8, 8], "texture": "#top"},
"east": {"uv": [0, 0, 8, 8], "texture": "#side"},
"south": {"uv": [8, 0, 16, 8], "texture": "#side"},
"west": {"uv": [8, 0, 16, 8], "texture": "#top"},
"up": {"uv": [8, 8, 16, 16], "texture": "#top"},
"down": {"uv": [8, 0, 16, 8], "texture": "#top"}
}
},
{
"name": "bottom_north_east",
"from": [8, 0, 0],
"to": [16, 8, 8],
"faces": {
"north": {"uv": [0, 8, 8, 16], "texture": "#side"},
"east": {"uv": [8, 8, 16, 16], "texture": "#side"},
"south": {"uv": [8, 8, 16, 16], "texture": "#top"},
"west": {"uv": [0, 8, 8, 16], "texture": "#top"},
"up": {"uv": [8, 0, 16, 8], "texture": "#top"},
"down": {"uv": [8, 8, 16, 16], "texture": "#top"}
}
},
{
"name": "top_north_east",
"from": [8, 8, 0],
"to": [16, 16, 8],
"faces": {
"north": {"uv": [0, 0, 8, 8], "texture": "#side"},
"east": {"uv": [8, 0, 16, 8], "texture": "#side"},
"south": {"uv": [8, 0, 16, 8], "texture": "#top"},
"west": {"uv": [0, 0, 8, 8], "texture": "#top"},
"up": {"uv": [8, 0, 16, 8], "texture": "#top"},
"down": {"uv": [8, 8, 16, 16], "texture": "#top"}
}
},
{
"name": "bottom_south_west",
"from": [0, 0, 8],
"to": [8, 8, 16],
"faces": {
"north": {"uv": [8, 8, 16, 16], "texture": "#top"},
"east": {"uv": [0, 8, 8, 16], "texture": "#top"},
"south": {"uv": [0, 8, 8, 16], "texture": "#side"},
"west": {"uv": [8, 8, 16, 16], "texture": "#side"},
"up": {"uv": [0, 8, 8, 16], "texture": "#top"},
"down": {"uv": [0, 0, 8, 8], "texture": "#top"}
}
},
{
"name": "top_south_west",
"from": [0, 8, 8],
"to": [8, 16, 16],
"faces": {
"north": {"uv": [8, 0, 16, 8], "texture": "#top"},
"east": {"uv": [0, 0, 8, 8], "texture": "#top"},
"south": {"uv": [0, 0, 8, 8], "texture": "#side"},
"west": {"uv": [8, 0, 16, 8], "texture": "#side"},
"up": {"uv": [0, 8, 8, 16], "texture": "#top"},
"down": {"uv": [0, 0, 8, 8], "texture": "#top"}
}
},
{
"name": "bottom_north_west",
"from": [0, 0, 0],
"to": [8, 8, 8],
"faces": {
"north": {"uv": [8, 8, 16, 16], "texture": "#side"},
"east": {"uv": [8, 8, 16, 16], "texture": "#top"},
"south": {"uv": [0, 8, 8, 16], "texture": "#top"},
"west": {"uv": [0, 8, 8, 16], "texture": "#side"},
"up": {"uv": [0, 0, 8, 8], "texture": "#top"},
"down": {"uv": [0, 8, 8, 16], "texture": "#top"}
}
},
{
"name": "top_north_west",
"from": [0, 8, 0],
"to": [8, 16, 8],
"faces": {
"north": {"uv": [8, 0, 16, 8], "texture": "#side"},
"east": {"uv": [8, 0, 16, 8], "texture": "#top"},
"south": {"uv": [0, 0, 8, 8], "texture": "#top"},
"west": {"uv": [0, 0, 8, 8], "texture": "#side"},
"up": {"uv": [0, 0, 8, 8], "texture": "#top"},
"down": {"uv": [0, 8, 8, 16], "texture": "#top"}
}
}
]
}

File diff suppressed because one or more lines are too long

View file

@ -1,46 +0,0 @@
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "unicopia:block/slime_pustule",
"particle": "unicopia:block/slime_pustule"
},
"elements": [
{
"from": [7, 14.9, 7],
"to": [12, 16, 12],
"faces": {
"north": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
"east": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
"south": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
"west": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
"up": {"uv": [0, 0, 6, 6], "texture": "#all"},
"down": {"uv": [0, 0, 6, 6], "texture": "#all"}
}
},
{
"from": [3, 15, 4],
"to": [9, 16, 10],
"faces": {
"north": {"uv": [0, 0, 6, 1], "texture": "#all"},
"east": {"uv": [0, 0, 6, 1], "texture": "#all"},
"south": {"uv": [0, 0, 6, 1], "texture": "#all"},
"west": {"uv": [0, 0, 6, 1], "texture": "#all"},
"up": {"uv": [0, 0, 6, 6], "texture": "#all"},
"down": {"uv": [0, 0, 6, 6], "texture": "#all"}
}
},
{
"name": "rope",
"from": [7, 10, 7],
"to": [9, 15, 9],
"faces": {
"north": {"uv": [1, 7, 5, 13], "texture": "#all"},
"east": {"uv": [1, 7, 5, 13], "texture": "#all"},
"south": {"uv": [1, 7, 5, 13], "texture": "#all"},
"west": {"uv": [1, 7, 5, 13], "texture": "#all"},
"up": {"uv": [2, 2, 4, 4], "texture": "#all"},
"down": {"uv": [2, 2, 4, 4], "texture": "#all"}
}
}
]
}

View file

@ -1,137 +0,0 @@
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "unicopia:block/slime_pustule",
"particle": "unicopia:block/slime_pustule"
},
"elements": [
{
"name": "rope",
"from": [7.5, 0, 7.5],
"to": [8.5, 16, 8.5],
"faces": {
"north": {"uv": [12, 0, 13, 16], "texture": "#all"},
"east": {"uv": [13, 0, 14, 16], "texture": "#all"},
"south": {"uv": [15, 0, 16, 16], "texture": "#all"},
"west": {"uv": [14, 0, 15, 16], "texture": "#all"},
"up": {"uv": [13, 0, 14, 1], "texture": "#all"},
"down": {"uv": [12, 0, 13, 1], "texture": "#all"}
}
},
{
"from": [7, 14.9, 7],
"to": [12, 16, 12],
"faces": {
"north": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
"east": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
"south": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
"west": {"uv": [0, 0, 6, 1.1], "texture": "#all"},
"up": {"uv": [0, 0, 6, 6], "texture": "#all"},
"down": {"uv": [0, 0, 6, 6], "texture": "#all"}
}
},
{
"from": [3, 15, 4],
"to": [9, 16, 10],
"faces": {
"north": {"uv": [0, 0, 6, 1], "texture": "#all"},
"east": {"uv": [0, 0, 6, 1], "texture": "#all"},
"south": {"uv": [0, 0, 6, 1], "texture": "#all"},
"west": {"uv": [0, 0, 6, 1], "texture": "#all"},
"up": {"uv": [0, 0, 6, 6], "texture": "#all"},
"down": {"uv": [0, 0, 6, 6], "texture": "#all"}
}
},
{
"name": "rope",
"from": [7, 10, 7],
"to": [9, 15, 9],
"faces": {
"north": {"uv": [1, 7, 5, 13], "texture": "#all"},
"east": {"uv": [1, 7, 5, 13], "texture": "#all"},
"south": {"uv": [1, 7, 5, 13], "texture": "#all"},
"west": {"uv": [1, 7, 5, 13], "texture": "#all"},
"up": {"uv": [2, 2, 4, 4], "texture": "#all"},
"down": {"uv": [2, 2, 4, 4], "texture": "#all"}
}
},
{
"name": "rope",
"from": [7, 15, 7],
"to": [9, 20, 9],
"faces": {
"north": {"uv": [2, 7, 4, 13], "texture": "#all"},
"east": {"uv": [2, 7, 4, 13], "texture": "#all"},
"south": {"uv": [2, 7, 4, 13], "texture": "#all"},
"west": {"uv": [2, 7, 4, 13], "texture": "#all"},
"up": {"uv": [2, 2, 4, 4], "texture": "#all"},
"down": {"uv": [2, 2, 4, 4], "texture": "#all"}
}
},
{
"name": "drop",
"from": [5, 10, 5],
"to": [11, 13, 11],
"faces": {
"north": {"uv": [0, 6, 6, 9], "texture": "#all"},
"east": {"uv": [0, 6, 6, 9], "texture": "#all"},
"south": {"uv": [0, 6, 6, 9], "texture": "#all"},
"west": {"uv": [0, 6, 6, 9], "texture": "#all"},
"up": {"uv": [0, 0, 6, 6], "texture": "#all"},
"down": {"uv": [0, 0, 6, 6], "texture": "#all"}
}
},
{
"name": "drop",
"from": [6, 13, 6],
"to": [10, 15, 10],
"faces": {
"north": {"uv": [0, 6, 6, 8], "texture": "#all"},
"east": {"uv": [0, 6, 6, 8], "texture": "#all"},
"south": {"uv": [0, 6, 6, 8], "texture": "#all"},
"west": {"uv": [0, 6, 6, 8], "texture": "#all"},
"up": {"uv": [0, 0, 6, 6], "texture": "#all"},
"down": {"uv": [0, 0, 6, 6], "texture": "#all"}
}
},
{
"name": "drop",
"from": [5, 0, 5],
"to": [11, 1, 11],
"faces": {
"north": {"uv": [0, 13, 6, 14], "texture": "#all"},
"east": {"uv": [0, 13, 6, 14], "texture": "#all"},
"south": {"uv": [0, 13, 6, 14], "texture": "#all"},
"west": {"uv": [0, 13, 6, 14], "texture": "#all"},
"up": {"uv": [0, 0, 6, 6], "texture": "#all"},
"down": {"uv": [0, 0, 6, 6], "texture": "#all"}
}
},
{
"name": "drop",
"from": [4, 1, 4],
"to": [12, 10, 12],
"faces": {
"north": {"uv": [0, 6, 6, 14], "texture": "#all"},
"east": {"uv": [0, 6, 6, 14], "texture": "#all"},
"south": {"uv": [0, 6, 6, 14], "texture": "#all"},
"west": {"uv": [0, 6, 6, 14], "texture": "#all"},
"up": {"uv": [0, 0, 6, 6], "texture": "#all"},
"down": {"uv": [0, 0, 6, 6], "texture": "#all"}
}
},
{
"name": "drop",
"from": [5, 2, 5],
"to": [11, 9, 11],
"faces": {
"north": {"uv": [7, 7, 11, 13], "texture": "#all"},
"east": {"uv": [7, 7, 11, 13], "texture": "#all"},
"south": {"uv": [7, 7, 11, 13], "texture": "#all"},
"west": {"uv": [7, 7, 11, 13], "texture": "#all"},
"up": {"uv": [7, 1, 11, 5], "texture": "#all"},
"down": {"uv": [7, 1, 11, 5], "texture": "#all"}
}
}
]
}

View file

@ -1,22 +0,0 @@
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "unicopia:block/slime_pustule",
"particle": "unicopia:block/slime_pustule"
},
"elements": [
{
"name": "rope",
"from": [7.5, 0, 7.5],
"to": [8.5, 16, 8.5],
"faces": {
"north": {"uv": [12, 0, 13, 16], "texture": "#all"},
"east": {"uv": [13, 0, 14, 16], "texture": "#all"},
"south": {"uv": [15, 0, 16, 16], "texture": "#all"},
"west": {"uv": [14, 0, 15, 16], "texture": "#all"},
"up": {"uv": [13, 0, 14, 1], "texture": "#all"},
"down": {"uv": [12, 0, 13, 1], "texture": "#all"}
}
}
]
}

File diff suppressed because one or more lines are too long

BIN
assets/models/tentacle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
assets/spectral_clock_0.xcf Normal file

Binary file not shown.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,22 +1,37 @@
package com.minelittlepony.unicopia.ability; package com.minelittlepony.unicopia.ability;
import java.util.Optional; import java.util.Optional;
import java.util.function.DoubleSupplier;
import java.util.function.Supplier;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.block.UBlocks; import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.TransformCropsRecipe;
import com.minelittlepony.unicopia.item.URecipes;
import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.server.world.BlockDestructionManager;
import com.minelittlepony.unicopia.util.TraceHelper; import com.minelittlepony.unicopia.util.TraceHelper;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.block.CarrotsBlock;
import net.minecraft.block.FarmlandBlock;
import net.minecraft.item.BoneMealItem; import net.minecraft.item.BoneMealItem;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.Items; import net.minecraft.item.Items;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.WorldEvents;
/** /**
* Earth Pony ability to grow crops * Earth Pony ability to grow crops
@ -57,10 +72,14 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
public boolean apply(Pony player, Pos data) { public boolean apply(Pony player, Pos data) {
int count = 0; int count = 0;
for (BlockPos pos : BlockPos.iterate( if (!applyDirectly(player, data.pos())) {
data.pos().add(-2, -2, -2), for (BlockPos pos : BlockPos.iterate(
data.pos().add( 2, 2, 2))) { data.pos().add(-2, -2, -2),
count += applySingle(player.asWorld(), player.asWorld().getBlockState(pos), pos); data.pos().add( 2, 2, 2))) {
count += applySingle(player, player.asWorld(), player.asWorld().getBlockState(pos), pos);
}
} else {
count = 1;
} }
if (count > 0) { if (count > 0) {
@ -69,7 +88,7 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
return true; return true;
} }
protected int applySingle(World w, BlockState state, BlockPos pos) { protected int applySingle(Pony player, World w, BlockState state, BlockPos pos) {
ItemStack stack = new ItemStack(Items.BONE_MEAL); ItemStack stack = new ItemStack(Items.BONE_MEAL);
@ -77,12 +96,33 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
return growable.grow(w, state, pos) ? 1 : 0; return growable.grow(w, state, pos) ? 1 : 0;
} }
if (state.isOf(Blocks.CARROTS)) {
if (state.get(CarrotsBlock.AGE) == CarrotsBlock.MAX_AGE) {
boolean transform = w.random.nextInt(3) == 0;
spawnConversionParticles(w, pos, transform);
if (transform) {
w.setBlockState(pos, UBlocks.GOLD_ROOT.getDefaultState().with(CarrotsBlock.AGE, CarrotsBlock.MAX_AGE));
}
return 5;
}
}
if (w.getBlockState(pos).isIn(UTags.UNAFFECTED_BY_GROW_ABILITY)) {
return 0;
}
if (BoneMealItem.useOnFertilizable(stack, w, pos)) { if (BoneMealItem.useOnFertilizable(stack, w, pos)) {
if (w.random.nextInt(350) == 0) { if (w.random.nextInt(350) == 0) {
if (w.getBlockState(pos.down()).isOf(Blocks.FARMLAND)) { if (w.getBlockState(pos.down()).isOf(Blocks.FARMLAND)) {
w.setBlockState(pos.down(), Blocks.DIRT.getDefaultState()); FarmlandBlock.setToDirt(null, state, w, pos.down());
} }
w.setBlockState(pos, UBlocks.PLUNDER_VINE_BUD.getDefaultState()); w.setBlockState(pos, UBlocks.PLUNDER_VINE_BUD.getDefaultState());
} else if (w.random.nextInt(5000) == 0) {
if (w.getBlockState(pos.down()).isOf(Blocks.FARMLAND)) {
FarmlandBlock.setToDirt(null, state, w, pos.down());
}
UBlocks.CURING_JOKE.grow(w, state, pos);
} }
return 1; return 1;
} }
@ -94,6 +134,56 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
return 0; return 0;
} }
private boolean applyDirectly(Pony player, BlockPos pos) {
return player.asWorld().getRecipeManager()
.getAllMatches(URecipes.GROWING, new TransformCropsRecipe.PlacementArea(player, pos), player.asWorld())
.stream()
.map(recipe -> recipe.checkPattern(player.asWorld(), pos))
.filter(result -> result.matchedLocations().size() + 1 >= TransformCropsRecipe.MINIMUM_INPUT)
.filter(result -> {
boolean transform = result.shoudTransform(player.asWorld().random);
player.playSound(USounds.ENTITY_CRYSTAL_SHARDS_AMBIENT, 1);
result.matchedLocations().forEach(cell -> {
spawnConversionParticles(player.asWorld(), cell.up(), false);
BlockDestructionManager manager = BlockDestructionManager.of(player.asWorld());
if (transform) {
if (manager.damageBlock(cell, 8) >= BlockDestructionManager.MAX_DAMAGE || player.asWorld().random.nextInt(20) == 0) {
player.asWorld().setBlockState(cell, Blocks.DIRT.getDefaultState());
player.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, cell, Block.getRawIdFromState(player.asWorld().getBlockState(cell)));
}
} else {
if (manager.damageBlock(cell, 4) >= BlockDestructionManager.MAX_DAMAGE || player.asWorld().random.nextInt(20) == 0) {
player.asWorld().setBlockState(cell, Blocks.DIRT.getDefaultState());
player.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, cell, Block.getRawIdFromState(player.asWorld().getBlockState(cell)));
}
}
});
spawnConversionParticles(player.asWorld(), pos, transform);
if (transform) {
player.asWorld().setBlockState(pos, result.recipe().getResult(player.asWorld(), pos));
}
return true;
})
.findFirst()
.isPresent();
}
private static void spawnConversionParticles(World w, BlockPos pos, boolean success) {
DoubleSupplier vecComponentFactory = () -> w.random.nextTriangular(0, 0.5);
Supplier<Vec3d> posSupplier = () -> pos.toCenterPos().add(VecHelper.supply(vecComponentFactory));
for (int i = 0; i < 25; i++) {
ParticleUtils.spawnParticle(w, new MagicParticleEffect(0xFFFF00), posSupplier.get(), Vec3d.ZERO);
if (success) {
ParticleUtils.spawnParticle(w, ParticleTypes.CLOUD, posSupplier.get(), Vec3d.ZERO);
}
}
}
@Override @Override
public void warmUp(Pony player, AbilitySlot slot) { public void warmUp(Pony player, AbilitySlot slot) {
player.getMagicalReserves().getExertion().addPercent(30); player.getMagicalReserves().getExertion().addPercent(30);

View file

@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.AwaitTickQueue; import com.minelittlepony.unicopia.AwaitTickQueue;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.Living;
@ -17,6 +18,7 @@ import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.server.world.BlockDestructionManager; import com.minelittlepony.unicopia.server.world.BlockDestructionManager;
import com.minelittlepony.unicopia.util.PosHelper; import com.minelittlepony.unicopia.util.PosHelper;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -26,6 +28,7 @@ import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.particle.BlockStateParticleEffect;
import net.minecraft.registry.tag.BlockTags; import net.minecraft.registry.tag.BlockTags;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@ -33,6 +36,7 @@ import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box; import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.WorldEvents; import net.minecraft.world.WorldEvents;
@ -156,7 +160,10 @@ public class EarthPonyStompAbility implements Ability<Hit> {
spawnEffectAround(player, center, radius, rad); spawnEffectAround(player, center, radius, rad);
ParticleUtils.spawnParticle(player.getWorld(), UParticles.GROUND_POUND, player.getX(), player.getY() - 1, player.getZ(), 0, 0, 0); ParticleUtils.spawnParticle(player.getWorld(), UParticles.GROUND_POUND, player.getX(), player.getY() - 1, player.getZ(), 0, 0, 0);
ParticleUtils.spawnParticle(player.getWorld(), UParticles.SHOCKWAVE, player.getX(), player.getY() - 1, player.getZ(), 0, 0, 0); BlockState steppingState = player.getSteppingBlockState();
if (steppingState.isIn(UTags.KICKS_UP_DUST)) {
ParticleUtils.spawnParticle(player.getWorld(), new BlockStateParticleEffect(UParticles.DUST_CLOUD, steppingState), player.getBlockPos().down().toCenterPos(), Vec3d.ZERO);
}
iplayer.subtractEnergyCost(rad); iplayer.subtractEnergyCost(rad);
iplayer.asEntity().addExhaustion(3); iplayer.asEntity().addExhaustion(3);
@ -218,6 +225,12 @@ public class EarthPonyStompAbility implements Ability<Hit> {
} else { } else {
w.syncWorldEvent(WorldEvents.BLOCK_BROKEN, pos, Block.getRawIdFromState(state)); w.syncWorldEvent(WorldEvents.BLOCK_BROKEN, pos, Block.getRawIdFromState(state));
} }
if (state.isIn(UTags.KICKS_UP_DUST)) {
if (w.random.nextInt(4) == 0 && w.isAir(pos.up()) && w.getFluidState(pos.up()).isEmpty()) {
ParticleUtils.spawnParticle(w, new BlockStateParticleEffect(UParticles.DUST_CLOUD, state), pos.up().toCenterPos(), VecHelper.supply(() -> w.random.nextTriangular(0, 0.1F)));
}
}
} }
@Override @Override

View file

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

View file

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

View file

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

View file

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

View file

@ -1,44 +0,0 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.*;
public interface CasterView {
EntityView getWorld();
default <S extends Spell> Stream<Map.Entry<Caster<?>, S>> findAllSpellsInRange(BlockPos pos, double radius, SpellPredicate<S> type) {
return findAllCastersInRange(pos, radius).flatMap(caster -> {
return caster.getSpellSlot().stream(type, false).map(spell -> {
return Map.entry(caster, spell);
});
});
}
default Stream<Caster<?>> findAllCastersInRange(BlockPos pos, double radius) {
return findAllCastersInRange(pos, radius, null);
}
default Stream<Caster<?>> findAllCastersInRange(BlockPos pos, double radius, @Nullable Predicate<Entity> test) {
return Caster.stream(findAllEntitiesInRange(pos, radius, test == null ? EquinePredicates.IS_CASTER : EquinePredicates.IS_CASTER.and(test)));
}
default Stream<Entity> findAllEntitiesInRange(BlockPos pos, double radius, @Nullable Predicate<Entity> test) {
return VecHelper.findInRange(null, getWorld(), Vec3d.ofCenter(pos), radius, test).stream();
}
default Stream<Entity> findAllEntitiesInRange(BlockPos pos, double radius) {
return findAllEntitiesInRange(pos, radius, null);
}
}

View file

@ -39,6 +39,13 @@ public interface SpellContainer {
*/ */
void put(@Nullable Spell effect); void put(@Nullable Spell effect);
/**
* Cleanly removes a spell from this spell container.
*
* @param spellid ID of the spell to remove.
*/
void remove(UUID spellid);
/** /**
* Removes all active effects that match or contain a matching effect. * Removes all active effects that match or contain a matching effect.
* *

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,42 @@
package com.minelittlepony.unicopia.ability.magic.spell.crafting;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.item.UItems;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
public record AltarRecipeMatch(
ItemEntity target,
List<ItemEntity> ingredients,
ItemStack result
) {
@Nullable
public static AltarRecipeMatch of(List<ItemEntity> inputs) {
ItemEntity clock = inputs.stream().filter(item -> item.getStack().isOf(Items.CLOCK)).findFirst().orElse(null);
if (clock != null) {
return new AltarRecipeMatch(clock, List.of(), UItems.SPECTRAL_CLOCK.getDefaultStack());
}
return null;
}
public boolean isRemoved() {
return target.isRemoved() || ingredients.stream().anyMatch(ItemEntity::isRemoved);
}
public void craft() {
ItemStack clockStack = result.copyWithCount(target.getStack().getCount());
clockStack.setNbt(target.getStack().getNbt());
target.setStack(clockStack);
target.setInvulnerable(true);
ingredients.forEach(Entity::discard);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,7 +13,7 @@ import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.*; import net.minecraft.world.*;
public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock { public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock, ZapStagedBlock {
BaseZapAppleLeavesBlock() { BaseZapAppleLeavesBlock() {
super(Settings.create() super(Settings.create()
@ -29,61 +29,29 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
} }
@Override @Override
public boolean hasRandomTicks(BlockState state) { public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
return !state.get(PERSISTENT); if (state.get(PERSISTENT) || oldState.isOf(state.getBlock())) {
} return;
@Override
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.randomTick(state, world, pos, random);
tryAdvanceStage(state, world, pos, random);
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) {
if (state.get(PERSISTENT)) {
return state;
} }
updateStage(state, world, pos);
if (world instanceof ServerWorld sw) {
ZapAppleStageStore store = ZapAppleStageStore.get(sw);
ZapAppleStageStore.Stage currentStage = store.getStage();
if (currentStage == ZapAppleStageStore.Stage.HIBERNATING) {
return currentStage.getNewState(state);
}
}
return state;
} }
@Override @Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.scheduledTick(state, world, pos, random); super.scheduledTick(state, world, pos, random);
tryAdvanceStage(state, world, pos, random);
if (!state.get(PERSISTENT)) {
world.scheduleBlockTick(pos, this, 1);
}
}
private void tryAdvanceStage(BlockState state, ServerWorld world, BlockPos pos, Random random) {
if (state.get(PERSISTENT)) { if (state.get(PERSISTENT)) {
return; return;
} }
tryAdvanceStage(state, world, pos, random);
ZapAppleStageStore store = ZapAppleStageStore.get(world);
ZapAppleStageStore.Stage newStage = store.getStage();
if (!world.isDay() && getStage(state).mustChangeIntoInstantly(newStage)) {
world.setBlockState(pos, newStage.getNewState(state));
onStageChanged(store, newStage, world, state, pos, random);
}
} }
protected ZapAppleStageStore.Stage getStage(BlockState state) { @Override
public ZapAppleStageStore.Stage getStage(BlockState state) {
return ZapAppleStageStore.Stage.FLOWERING; return ZapAppleStageStore.Stage.FLOWERING;
} }
@Override @Override
protected boolean shouldDecay(BlockState state) { protected final boolean shouldDecay(BlockState state) {
return false; return false;
} }
@ -114,40 +82,10 @@ public class BaseZapAppleLeavesBlock extends LeavesBlock implements TintedBlock
@Override @Override
public int getTint(BlockState state, @Nullable BlockRenderView world, @Nullable BlockPos pos, int foliageColor) { public int getTint(BlockState state, @Nullable BlockRenderView world, @Nullable BlockPos pos, int foliageColor) {
if (pos == null) { if (pos == null) {
return 0x4C7EFA; return 0x4C7EFA;
} }
return TintedBlock.blend(TintedBlock.rotate(foliageColor, 2), 0x0000FF, 0.3F); return TintedBlock.blend(TintedBlock.rotate(foliageColor, 2), 0x0000FF, 0.3F);
} }
static void onStageChanged(ZapAppleStageStore store, ZapAppleStageStore.Stage stage, ServerWorld world, BlockState state, BlockPos pos, Random random) {
boolean mustFruit = Random.create(state.getRenderingSeed(pos)).nextInt(5) < 2;
BlockState below = world.getBlockState(pos.down());
if (world.isAir(pos.down())) {
if (stage == ZapAppleStageStore.Stage.FRUITING && mustFruit) {
world.setBlockState(pos.down(), UBlocks.ZAP_BULB.getDefaultState(), Block.NOTIFY_ALL);
store.triggerLightningStrike(pos);
}
}
if (stage != ZapAppleStageStore.Stage.HIBERNATING && world.getRandom().nextInt(10) == 0) {
store.triggerLightningStrike(pos);
}
if (stage == ZapAppleStageStore.Stage.RIPE) {
if (below.isOf(UBlocks.ZAP_BULB)) {
world.setBlockState(pos.down(), UBlocks.ZAP_APPLE.getDefaultState(), Block.NOTIFY_ALL);
store.playMoonEffect(pos);
}
}
if (mustFruit && stage == ZapAppleStageStore.Stage.HIBERNATING) {
if (below.isOf(UBlocks.ZAP_APPLE) || below.isOf(UBlocks.ZAP_BULB)) {
world.setBlockState(pos.down(), Blocks.AIR.getDefaultState());
}
}
}
} }

View file

@ -0,0 +1,47 @@
package com.minelittlepony.unicopia.block;
import com.minelittlepony.unicopia.ability.EarthPonyGrowAbility.Growable;
import com.minelittlepony.unicopia.entity.mob.IgnominiousBulbEntity;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import net.minecraft.block.BlockState;
import net.minecraft.block.FlowerBlock;
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 {
public CuringJokeBlock(StatusEffect suspiciousStewEffect, int effectDuration, Settings settings) {
super(suspiciousStewEffect, effectDuration, settings);
}
@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,24 @@
package com.minelittlepony.unicopia.block;
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");
public EnchantedFruitBlock(Settings settings, Direction attachmentFace, Block stem, VoxelShape shape) {
super(settings, attachmentFace, stem, shape);
setDefaultState(getDefaultState().with(ENCHANTED, false));
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder);
builder.add(ENCHANTED);
}
}

View file

@ -62,6 +62,14 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
return true; return true;
} }
protected boolean shouldAdvance(Random random) {
return true;
}
protected BlockState getPlacedFruitState(Random random) {
return fruit.get().getDefaultState();
}
@Override @Override
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.randomTick(state, world, pos, random); super.randomTick(state, world, pos, random);
@ -70,10 +78,14 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
return; return;
} }
if (world.isDay()) { if (world.getBaseLightLevel(pos, 0) > 8) {
BlockSoundGroup group = getSoundGroup(state); BlockSoundGroup group = getSoundGroup(state);
int steps = FertilizableUtil.getGrowthSteps(world, pos, state, random); int steps = FertilizableUtil.getGrowthSteps(world, pos, state, random);
while (steps-- > 0) { while (steps-- > 0) {
if (!shouldAdvance(random)) {
continue;
}
if (state.get(STAGE) == Stage.FRUITING) { if (state.get(STAGE) == Stage.FRUITING) {
state = state.cycle(AGE); state = state.cycle(AGE);
if (state.get(AGE) > 20) { if (state.get(AGE) > 20) {
@ -89,7 +101,7 @@ public class FruitBearingBlock extends LeavesBlock implements TintedBlock, Bucka
if (stage == Stage.FRUITING && isPositionValidForFruit(state, pos)) { if (stage == Stage.FRUITING && isPositionValidForFruit(state, pos)) {
if (world.isAir(fruitPosition)) { if (world.isAir(fruitPosition)) {
world.setBlockState(fruitPosition, fruit.get().getDefaultState(), Block.NOTIFY_ALL); world.setBlockState(fruitPosition, getPlacedFruitState(random), Block.NOTIFY_ALL);
} }
} }

View file

@ -0,0 +1,26 @@
package com.minelittlepony.unicopia.block;
import java.util.function.Supplier;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.random.Random;
public class GoldenOakLeavesBlock extends FruitBearingBlock {
public GoldenOakLeavesBlock(Settings settings, int overlay, Supplier<Block> fruit,
Supplier<ItemStack> rottenFruitSupplier) {
super(settings, overlay, fruit, rottenFruitSupplier);
}
@Override
protected boolean shouldAdvance(Random random) {
return random.nextInt(1000) == 0;
}
@Override
protected BlockState getPlacedFruitState(Random random) {
return super.getPlacedFruitState(random).with(EnchantedFruitBlock.ENCHANTED, random.nextInt(1000) == 0);
}
}

View file

@ -21,6 +21,7 @@ import com.minelittlepony.unicopia.block.cloud.SoggyCloudBlock;
import com.minelittlepony.unicopia.block.cloud.SoggyCloudSlabBlock; import com.minelittlepony.unicopia.block.cloud.SoggyCloudSlabBlock;
import com.minelittlepony.unicopia.block.cloud.SoggyCloudStairsBlock; import com.minelittlepony.unicopia.block.cloud.SoggyCloudStairsBlock;
import com.minelittlepony.unicopia.block.cloud.UnstableCloudBlock; import com.minelittlepony.unicopia.block.cloud.UnstableCloudBlock;
import com.minelittlepony.unicopia.entity.effect.UEffects;
import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.item.cloud.CloudBlockItem; import com.minelittlepony.unicopia.item.cloud.CloudBlockItem;
import com.minelittlepony.unicopia.item.group.ItemGroupRegistry; import com.minelittlepony.unicopia.item.group.ItemGroupRegistry;
@ -28,6 +29,7 @@ import com.minelittlepony.unicopia.server.world.UTreeGen;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.registry.FlammableBlockRegistry; import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
import net.fabricmc.fabric.api.registry.StrippableBlockRegistry; import net.fabricmc.fabric.api.registry.StrippableBlockRegistry;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.block.*; import net.minecraft.block.*;
import net.minecraft.block.AbstractBlock.Settings; import net.minecraft.block.AbstractBlock.Settings;
import net.minecraft.block.entity.BlockEntityType; import net.minecraft.block.entity.BlockEntityType;
@ -78,7 +80,7 @@ public interface UBlocks {
Block PALM_STAIRS = register("palm_stairs", new StairsBlock(PALM_PLANKS.getDefaultState(), Settings.copy(PALM_PLANKS).pistonBehavior(PistonBehavior.NORMAL)), ItemGroups.BUILDING_BLOCKS); Block PALM_STAIRS = register("palm_stairs", new StairsBlock(PALM_PLANKS.getDefaultState(), Settings.copy(PALM_PLANKS).pistonBehavior(PistonBehavior.NORMAL)), ItemGroups.BUILDING_BLOCKS);
Block PALM_SLAB = register("palm_slab", new SlabBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).strength(2, 3).sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.NORMAL)), ItemGroups.BUILDING_BLOCKS); Block PALM_SLAB = register("palm_slab", new SlabBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).strength(2, 3).sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.NORMAL)), ItemGroups.BUILDING_BLOCKS);
Block PALM_FENCE = register("palm_fence", new FenceBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).strength(2, 3).sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.NORMAL)), ItemGroups.BUILDING_BLOCKS); Block PALM_FENCE = register("palm_fence", new FenceBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).strength(2, 3).sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.NORMAL)), ItemGroups.BUILDING_BLOCKS);
Block PALM_FENCE_GATE = register("palm_fence_gate", new FenceGateBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).strength(2, 3).sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.NORMAL), WoodType.OAK), ItemGroups.BUILDING_BLOCKS); Block PALM_FENCE_GATE = register("palm_fence_gate", new FenceGateBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).strength(2, 3).sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.NORMAL), UWoodTypes.PALM), ItemGroups.BUILDING_BLOCKS);
Block PALM_DOOR = register("palm_door", new DoorBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).instrument(Instrument.BASS).strength(3.0f).nonOpaque().burnable().pistonBehavior(PistonBehavior.DESTROY), UWoodTypes.PALM.setType()), ItemGroups.FUNCTIONAL); Block PALM_DOOR = register("palm_door", new DoorBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).instrument(Instrument.BASS).strength(3.0f).nonOpaque().burnable().pistonBehavior(PistonBehavior.DESTROY), UWoodTypes.PALM.setType()), ItemGroups.FUNCTIONAL);
Block PALM_TRAPDOOR = register("palm_trapdoor", new TrapdoorBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).instrument(Instrument.BASS).strength(3).nonOpaque().allowsSpawning(BlockConstructionUtils::never).burnable(), UWoodTypes.PALM.setType()), ItemGroups.FUNCTIONAL); Block PALM_TRAPDOOR = register("palm_trapdoor", new TrapdoorBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).instrument(Instrument.BASS).strength(3).nonOpaque().allowsSpawning(BlockConstructionUtils::never).burnable(), UWoodTypes.PALM.setType()), ItemGroups.FUNCTIONAL);
Block PALM_PRESSURE_PLATE = register("palm_pressure_plate", new PressurePlateBlock(PressurePlateBlock.ActivationRule.EVERYTHING, Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).noCollision().strength(0.5f).sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.DESTROY), BlockSetType.OAK), ItemGroups.BUILDING_BLOCKS); Block PALM_PRESSURE_PLATE = register("palm_pressure_plate", new PressurePlateBlock(PressurePlateBlock.ActivationRule.EVERYTHING, Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).noCollision().strength(0.5f).sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.DESTROY), BlockSetType.OAK), ItemGroups.BUILDING_BLOCKS);
@ -126,6 +128,16 @@ public interface UBlocks {
Block SOUR_APPLE = register("sour_apple", new FruitBlock(Settings.create().mapColor(MapColor.GREEN), Direction.DOWN, SOUR_APPLE_LEAVES, FruitBlock.DEFAULT_SHAPE)); Block SOUR_APPLE = register("sour_apple", new FruitBlock(Settings.create().mapColor(MapColor.GREEN), Direction.DOWN, SOUR_APPLE_LEAVES, FruitBlock.DEFAULT_SHAPE));
Block SOUR_APPLE_SPROUT = register("sour_apple_sprout", new SproutBlock(0xE5FFCC88, () -> UItems.SOUR_APPLE_SEEDS, () -> UTreeGen.SOUR_APPLE_TREE.sapling().map(Block::getDefaultState).get())); Block SOUR_APPLE_SPROUT = register("sour_apple_sprout", new SproutBlock(0xE5FFCC88, () -> UItems.SOUR_APPLE_SEEDS, () -> UTreeGen.SOUR_APPLE_TREE.sapling().map(Block::getDefaultState).get()));
Block GOLDEN_OAK_LEAVES = register("golden_oak_leaves", new GoldenOakLeavesBlock(FabricBlockSettings.copy(Blocks.OAK_LEAVES),
MapColor.GOLD.color,
() -> UBlocks.GOLDEN_APPLE,
() -> Items.GOLDEN_APPLE.getDefaultStack()
), ItemGroups.NATURAL);
Block GOLDEN_APPLE = register("golden_apple", new EnchantedFruitBlock(Settings.create().mapColor(MapColor.GOLD), Direction.DOWN, GOLDEN_OAK_LEAVES, FruitBlock.DEFAULT_SHAPE));
Block GOLDEN_OAK_SPROUT = register("golden_oak_sprout", new SproutBlock(0xE5FFCC88, () -> UItems.GOLDEN_OAK_SEEDS, () -> UTreeGen.GOLDEN_APPLE_TREE.sapling().map(Block::getDefaultState).get()));
Block GOLDEN_OAK_LOG = register("golden_oak_log", BlockConstructionUtils.createLogBlock(MapColor.OFF_WHITE, MapColor.GOLD), ItemGroups.BUILDING_BLOCKS);
Block APPLE_PIE = register("apple_pie", new PieBlock(Settings.create().solid().mapColor(MapColor.ORANGE).strength(0.5F).sounds(BlockSoundGroup.WOOL).pistonBehavior(PistonBehavior.DESTROY), Block APPLE_PIE = register("apple_pie", new PieBlock(Settings.create().solid().mapColor(MapColor.ORANGE).strength(0.5F).sounds(BlockSoundGroup.WOOL).pistonBehavior(PistonBehavior.DESTROY),
() -> UItems.APPLE_PIE_SLICE, () -> UItems.APPLE_PIE_SLICE,
() -> UItems.APPLE_PIE, () -> UItems.APPLE_PIE,
@ -138,6 +150,13 @@ public interface UBlocks {
Block PLUNDER_VINE = register("plunder_vine", new ThornBlock(Settings.create().mapColor(MapColor.DARK_CRIMSON).hardness(1).ticksRandomly().sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.DESTROY), () -> UBlocks.PLUNDER_VINE_BUD)); Block PLUNDER_VINE = register("plunder_vine", new ThornBlock(Settings.create().mapColor(MapColor.DARK_CRIMSON).hardness(1).ticksRandomly().sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.DESTROY), () -> UBlocks.PLUNDER_VINE_BUD));
Block PLUNDER_VINE_BUD = register("plunder_vine_bud", new ThornBudBlock(Settings.create().mapColor(MapColor.DARK_CRIMSON).hardness(1).nonOpaque().ticksRandomly().sounds(BlockSoundGroup.GRASS).pistonBehavior(PistonBehavior.DESTROY), PLUNDER_VINE.getDefaultState())); Block PLUNDER_VINE_BUD = register("plunder_vine_bud", new ThornBudBlock(Settings.create().mapColor(MapColor.DARK_CRIMSON).hardness(1).nonOpaque().ticksRandomly().sounds(BlockSoundGroup.GRASS).pistonBehavior(PistonBehavior.DESTROY), PLUNDER_VINE.getDefaultState()));
CuringJokeBlock CURING_JOKE = register("curing_joke", new CuringJokeBlock(UEffects.BUTTER_FINGERS, 7, AbstractBlock.Settings.create().mapColor(MapColor.PALE_PURPLE).noCollision().breakInstantly().sounds(BlockSoundGroup.GRASS).offset(AbstractBlock.OffsetType.XZ).pistonBehavior(PistonBehavior.DESTROY)));
Block GOLD_ROOT = register("gold_root", new CarrotsBlock(AbstractBlock.Settings.create().mapColor(MapColor.GOLD).noCollision().ticksRandomly().breakInstantly().sounds(BlockSoundGroup.CROP).pistonBehavior(PistonBehavior.DESTROY)) {
@Override
protected ItemConvertible getSeedsItem() {
return Items.GOLDEN_CARROT;
}
});
Block CHITIN = register("chitin", new SnowyBlock(Settings.create().mapColor(MapColor.PALE_PURPLE).hardness(5).requiresTool().ticksRandomly().sounds(BlockSoundGroup.CORAL)), ItemGroups.NATURAL); Block CHITIN = register("chitin", new SnowyBlock(Settings.create().mapColor(MapColor.PALE_PURPLE).hardness(5).requiresTool().ticksRandomly().sounds(BlockSoundGroup.CORAL)), ItemGroups.NATURAL);
Block SURFACE_CHITIN = register("surface_chitin", new GrowableBlock(Settings.copy(CHITIN), () -> CHITIN), ItemGroups.NATURAL); Block SURFACE_CHITIN = register("surface_chitin", new GrowableBlock(Settings.copy(CHITIN), () -> CHITIN), ItemGroups.NATURAL);
@ -206,6 +225,8 @@ public interface UBlocks {
Block CRYSTAL_DOOR = register("crystal_door", new CrystalDoorBlock(Settings.copy(Blocks.IRON_DOOR), UWoodTypes.CRYSTAL), ItemGroups.FUNCTIONAL); Block CRYSTAL_DOOR = register("crystal_door", new CrystalDoorBlock(Settings.copy(Blocks.IRON_DOOR), UWoodTypes.CRYSTAL), 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(Settings.copy(CLOUD), CLOUD.getDefaultState(), UWoodTypes.CLOUD), ItemGroups.FUNCTIONAL);
EdibleBlock HAY_BLOCK = register("hay_block", new EdibleBlock(new Identifier("hay_block"), new Identifier("wheat"), true));
private static <T extends Block> T register(String name, T item) { private static <T extends Block> T register(String name, T item) {
return register(Unicopia.id(name), item); return register(Unicopia.id(name), item);
} }
@ -229,10 +250,15 @@ public interface UBlocks {
if (block instanceof CloudLike || block instanceof SlimePustuleBlock || block instanceof PileBlock) { if (block instanceof CloudLike || block instanceof SlimePustuleBlock || block instanceof PileBlock) {
SEMI_TRANSPARENT_BLOCKS.add(block); SEMI_TRANSPARENT_BLOCKS.add(block);
} }
return Registry.register(Registries.BLOCK, id, block); return Registry.register(Registries.BLOCK, id, block);
} }
static void bootstrap() { static void bootstrap() {
if (FabricLoader.getInstance().isModLoaded("farmersdelight")) {
register("rice_block", new EdibleBlock(new Identifier("farmersdelight", "rice_bale"), new Identifier("farmersdelight", "rice_panicle"), true));
register("straw_block", new EdibleBlock(new Identifier("farmersdelight", "straw_bale"), new Identifier("farmersdelight", "straw"), true));
}
BlockEntityTypeSupportHelper.of(BlockEntityType.SIGN).addSupportedBlocks(PALM_SIGN, PALM_WALL_SIGN); BlockEntityTypeSupportHelper.of(BlockEntityType.SIGN).addSupportedBlocks(PALM_SIGN, PALM_WALL_SIGN);
BlockEntityTypeSupportHelper.of(BlockEntityType.HANGING_SIGN).addSupportedBlocks(PALM_HANGING_SIGN, PALM_WALL_HANGING_SIGN); BlockEntityTypeSupportHelper.of(BlockEntityType.HANGING_SIGN).addSupportedBlocks(PALM_HANGING_SIGN, PALM_WALL_HANGING_SIGN);
@ -240,21 +266,24 @@ public interface UBlocks {
StrippableBlockRegistry.register(PALM_LOG, STRIPPED_PALM_LOG); StrippableBlockRegistry.register(PALM_LOG, STRIPPED_PALM_LOG);
StrippableBlockRegistry.register(ZAP_WOOD, STRIPPED_ZAP_WOOD); StrippableBlockRegistry.register(ZAP_WOOD, STRIPPED_ZAP_WOOD);
StrippableBlockRegistry.register(PALM_WOOD, STRIPPED_PALM_WOOD); StrippableBlockRegistry.register(PALM_WOOD, STRIPPED_PALM_WOOD);
Collections.addAll(TRANSLUCENT_BLOCKS, WEATHER_VANE, CHITIN_SPIKES, PLUNDER_VINE, PLUNDER_VINE_BUD, CLAM_SHELL, SCALLOP_SHELL, TURRET_SHELL); Collections.addAll(TRANSLUCENT_BLOCKS, WEATHER_VANE, CHITIN_SPIKES, PLUNDER_VINE, PLUNDER_VINE_BUD, CLAM_SHELL, SCALLOP_SHELL, TURRET_SHELL, CURING_JOKE);
TintedBlock.REGISTRY.add(PALM_LEAVES); TintedBlock.REGISTRY.add(PALM_LEAVES);
FlammableBlockRegistry.getDefaultInstance().add(GREEN_APPLE_LEAVES, 30, 60); FlammableBlockRegistry.getDefaultInstance().add(GREEN_APPLE_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(SWEET_APPLE_LEAVES, 30, 60); FlammableBlockRegistry.getDefaultInstance().add(SWEET_APPLE_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(SOUR_APPLE_LEAVES, 30, 60); FlammableBlockRegistry.getDefaultInstance().add(SOUR_APPLE_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(GOLDEN_OAK_LEAVES, 60, 120);
FlammableBlockRegistry.getDefaultInstance().add(MANGO_LEAVES, 30, 60); FlammableBlockRegistry.getDefaultInstance().add(MANGO_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(PALM_LEAVES, 30, 60); FlammableBlockRegistry.getDefaultInstance().add(PALM_LEAVES, 30, 60);
FlammableBlockRegistry.getDefaultInstance().add(PALM_LOG, 5, 5); FlammableBlockRegistry.getDefaultInstance().add(PALM_LOG, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(PALM_WOOD, 5, 5); FlammableBlockRegistry.getDefaultInstance().add(PALM_WOOD, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(GOLDEN_OAK_LOG, 15, 15);
FlammableBlockRegistry.getDefaultInstance().add(STRIPPED_PALM_LOG, 5, 5); FlammableBlockRegistry.getDefaultInstance().add(STRIPPED_PALM_LOG, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(STRIPPED_PALM_WOOD, 5, 5); FlammableBlockRegistry.getDefaultInstance().add(STRIPPED_PALM_WOOD, 5, 5);
FlammableBlockRegistry.getDefaultInstance().add(PALM_PLANKS, 5, 20); FlammableBlockRegistry.getDefaultInstance().add(PALM_PLANKS, 5, 20);
FlammableBlockRegistry.getDefaultInstance().add(BANANAS, 5, 20); FlammableBlockRegistry.getDefaultInstance().add(BANANAS, 5, 20);
UBlockEntities.bootstrap(); UBlockEntities.bootstrap();
EdibleBlock.bootstrap();
} }
} }

View file

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

View file

@ -4,11 +4,8 @@ import com.minelittlepony.unicopia.server.world.ZapAppleStageStore;
import net.minecraft.block.*; import net.minecraft.block.*;
import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemPlacementContext;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.state.property.*; import net.minecraft.state.property.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock { public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock {
public static final EnumProperty<ZapAppleStageStore.Stage> STAGE = EnumProperty.of("stage", ZapAppleStageStore.Stage.class); public static final EnumProperty<ZapAppleStageStore.Stage> STAGE = EnumProperty.of("stage", ZapAppleStageStore.Stage.class);
@ -17,31 +14,13 @@ public class ZapAppleLeavesBlock extends BaseZapAppleLeavesBlock {
setDefaultState(getDefaultState().with(STAGE, ZapAppleStageStore.Stage.GREENING)); setDefaultState(getDefaultState().with(STAGE, ZapAppleStageStore.Stage.GREENING));
} }
@Override
public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
if (state.get(PERSISTENT)
|| oldState.isOf(state.getBlock())
|| oldState.isOf(UBlocks.ZAP_LEAVES)
|| oldState.isOf(UBlocks.FLOWERING_ZAP_LEAVES)
|| oldState.isOf(UBlocks.ZAP_LEAVES_PLACEHOLDER)
|| !(world instanceof ServerWorld sw)) {
return;
}
ZapAppleStageStore store = ZapAppleStageStore.get(sw);
ZapAppleStageStore.Stage currentStage = store.getStage();
if (currentStage != getStage(state)) {
world.setBlockState(pos, currentStage.getNewState(state));
}
}
@Override @Override
public BlockState getPlacementState(ItemPlacementContext ctx) { public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(STAGE, ZapAppleStageStore.Stage.GREENING); return super.getPlacementState(ctx).with(STAGE, ZapAppleStageStore.Stage.GREENING);
} }
@Override @Override
protected ZapAppleStageStore.Stage getStage(BlockState state) { public ZapAppleStageStore.Stage getStage(BlockState state) {
return state.get(STAGE); return state.get(STAGE);
} }

View file

@ -1,51 +1,34 @@
package com.minelittlepony.unicopia.block; package com.minelittlepony.unicopia.block;
import com.minelittlepony.unicopia.server.world.ZapAppleStageStore; import com.minelittlepony.unicopia.server.world.ZapAppleStageStore;
import com.minelittlepony.unicopia.server.world.ZapAppleStageStore.Stage;
import net.minecraft.block.*; import net.minecraft.block.*;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.*; import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.WorldAccess; import net.minecraft.world.World;
public class ZapAppleLeavesPlaceholderBlock extends AirBlock { public class ZapAppleLeavesPlaceholderBlock extends AirBlock implements ZapStagedBlock {
ZapAppleLeavesPlaceholderBlock() { ZapAppleLeavesPlaceholderBlock() {
super(Settings.create().replaceable().noCollision().dropsNothing().air()); super(Settings.create().replaceable().noCollision().dropsNothing().air());
} }
@Override @Override
public boolean hasRandomTicks(BlockState state) { public Stage getStage(BlockState state) {
return true; return ZapAppleStageStore.Stage.HIBERNATING;
} }
@Override @Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) { public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
updateStage(state, world, pos);
if (world instanceof ServerWorld sw) {
ZapAppleStageStore store = ZapAppleStageStore.get(sw);
ZapAppleStageStore.Stage currentStage = store.getStage();
if (currentStage != ZapAppleStageStore.Stage.HIBERNATING) {
return currentStage.getNewState(state);
}
}
return state;
} }
@Deprecated @Deprecated
@Override @Override
public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.scheduledTick(state, world, pos, random); super.scheduledTick(state, world, pos, random);
tryAdvanceStage(state, world, pos, random);
ZapAppleStageStore store = ZapAppleStageStore.get(world);
ZapAppleStageStore.Stage newStage = store.getStage();
if (!world.isDay() && ZapAppleStageStore.Stage.HIBERNATING.mustChangeIntoInstantly(newStage)) {
state = newStage.getNewState(state);
world.setBlockState(pos, state);
BaseZapAppleLeavesBlock.onStageChanged(store, newStage, world, state, pos, random);
}
world.scheduleBlockTick(pos, state.getBlock(), 1);
} }
} }

View file

@ -0,0 +1,87 @@
package com.minelittlepony.unicopia.block;
import com.minelittlepony.unicopia.server.world.ZapAppleStageStore;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.World;
public interface ZapStagedBlock {
ZapAppleStageStore.Stage getStage(BlockState state);
default void updateStage(BlockState state, World world, BlockPos pos) {
if (!(world instanceof ServerWorld sw)) {
return;
}
ZapAppleStageStore.Stage currentStage = ZapAppleStageStore.get(sw).getStage();
if (currentStage != getStage(state)) {
state = getState(currentStage);
world.setBlockState(pos, state);
}
world.scheduleBlockTick(pos, state.getBlock(), 1);
}
default void tryAdvanceStage(BlockState state, ServerWorld world, BlockPos pos, Random random) {
ZapAppleStageStore store = ZapAppleStageStore.get(world);
ZapAppleStageStore.Stage currentStage = store.getStage();
if (!world.isDay() && currentStage != getStage(state)) {
int transitionRate = getTransitionRate(currentStage);
if (transitionRate == 0 || random.nextInt(transitionRate) == 0) {
state = getState(currentStage);
world.setBlockState(pos, state);
onStageChanged(store, currentStage, world, state, pos, random);
}
}
world.scheduleBlockTick(pos, state.getBlock(), 1);
}
default int getTransitionRate(ZapAppleStageStore.Stage stage) {
if (stage == ZapAppleStageStore.Stage.HIBERNATING || stage == ZapAppleStageStore.Stage.GREENING) {
return 10;
}
return 2500;
}
default BlockState getState(ZapAppleStageStore.Stage stage) {
if (stage == ZapAppleStageStore.Stage.HIBERNATING) {
return UBlocks.ZAP_LEAVES_PLACEHOLDER.getDefaultState();
}
if (stage == ZapAppleStageStore.Stage.FLOWERING) {
return UBlocks.FLOWERING_ZAP_LEAVES.getDefaultState();
}
return UBlocks.ZAP_LEAVES.getDefaultState().with(ZapAppleLeavesBlock.STAGE, stage);
}
private static void onStageChanged(ZapAppleStageStore store, ZapAppleStageStore.Stage stage, ServerWorld world, BlockState state, BlockPos pos, Random random) {
boolean mustFruit = Random.create(state.getRenderingSeed(pos)).nextInt(5) < 2;
BlockState below = world.getBlockState(pos.down());
if (world.isAir(pos.down())) {
if (stage == ZapAppleStageStore.Stage.FRUITING && mustFruit) {
world.setBlockState(pos.down(), UBlocks.ZAP_BULB.getDefaultState(), Block.NOTIFY_ALL);
store.triggerLightningStrike(pos);
}
}
if (stage != ZapAppleStageStore.Stage.HIBERNATING && world.getRandom().nextInt(10) == 0) {
store.triggerLightningStrike(pos);
}
if (stage == ZapAppleStageStore.Stage.RIPE) {
if (below.isOf(UBlocks.ZAP_BULB)) {
world.setBlockState(pos.down(), UBlocks.ZAP_APPLE.getDefaultState(), Block.NOTIFY_ALL);
store.playMoonEffect(pos);
}
}
if (mustFruit && stage == ZapAppleStageStore.Stage.HIBERNATING) {
if (below.isOf(UBlocks.ZAP_APPLE) || below.isOf(UBlocks.ZAP_BULB)) {
world.setBlockState(pos.down(), Blocks.AIR.getDefaultState());
}
}
}
}

View file

@ -4,6 +4,8 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.block.state.StateUtil;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@ -22,7 +24,7 @@ public class PoreousCloudBlock extends CloudBlock implements Soakable {
@Override @Override
public BlockState getStateWithMoisture(BlockState state, int moisture) { public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) { if (moisture <= 0) {
return Soakable.copyProperties(state, getDefaultState()); return StateUtil.copyState(state, getDefaultState());
} }
return soggyBlock == null ? null : soggyBlock.get().getStateWithMoisture(state, moisture); return soggyBlock == null ? null : soggyBlock.get().getStateWithMoisture(state, moisture);
} }

View file

@ -4,6 +4,8 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.block.state.StateUtil;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
public class PoreousCloudStairsBlock extends CloudStairsBlock implements Soakable { public class PoreousCloudStairsBlock extends CloudStairsBlock implements Soakable {
@ -19,7 +21,7 @@ public class PoreousCloudStairsBlock extends CloudStairsBlock implements Soakabl
@Override @Override
public BlockState getStateWithMoisture(BlockState state, int moisture) { public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) { if (moisture <= 0) {
return Soakable.copyProperties(state, getDefaultState()); return StateUtil.copyState(state, getDefaultState());
} }
return soggyBlock.get().getStateWithMoisture(state, moisture); return soggyBlock.get().getStateWithMoisture(state, moisture);
} }

View file

@ -15,7 +15,6 @@ import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents; import net.minecraft.sound.SoundEvents;
import net.minecraft.state.property.IntProperty; import net.minecraft.state.property.IntProperty;
import net.minecraft.state.property.Property;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.minecraft.util.Util; import net.minecraft.util.Util;
@ -105,15 +104,4 @@ public interface Soakable {
world.setBlockState(pos, soakable.getStateWithMoisture(state, newMoisture)); world.setBlockState(pos, soakable.getStateWithMoisture(state, newMoisture));
world.playSound(null, pos, SoundEvents.ENTITY_SALMON_FLOP, SoundCategory.BLOCKS, 1, (float)world.random.nextTriangular(0.5, 0.3F)); world.playSound(null, pos, SoundEvents.ENTITY_SALMON_FLOP, SoundCategory.BLOCKS, 1, (float)world.random.nextTriangular(0.5, 0.3F));
} }
@Nullable
@SuppressWarnings({ "rawtypes", "unchecked" })
static BlockState copyProperties(BlockState from, @Nullable BlockState to) {
if (to != null) {
for (Property property : from.getProperties()) {
to = to.withIfExists(property, from.get(property));
}
}
return to;
}
} }

View file

@ -4,6 +4,8 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.block.state.StateUtil;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
@ -43,9 +45,9 @@ public class SoggyCloudBlock extends CloudBlock implements Soakable {
@Override @Override
public BlockState getStateWithMoisture(BlockState state, int moisture) { public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) { if (moisture <= 0) {
return Soakable.copyProperties(state, dryBlock.get().getDefaultState()); return StateUtil.copyState(state, dryBlock.get().getDefaultState());
} }
return Soakable.copyProperties(state, getDefaultState()).with(MOISTURE, moisture); return StateUtil.copyState(state, getDefaultState()).with(MOISTURE, moisture);
} }
@Override @Override

View file

@ -4,6 +4,8 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.block.state.StateUtil;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
@ -43,9 +45,9 @@ public class SoggyCloudSlabBlock extends CloudSlabBlock {
@Override @Override
public BlockState getStateWithMoisture(BlockState state, int moisture) { public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) { if (moisture <= 0) {
return Soakable.copyProperties(state, dryBlock.get().getDefaultState()); return StateUtil.copyState(state, dryBlock.get().getDefaultState());
} }
return Soakable.copyProperties(state, getDefaultState()).with(MOISTURE, moisture); return StateUtil.copyState(state, getDefaultState()).with(MOISTURE, moisture);
} }
@Override @Override

View file

@ -4,6 +4,8 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.block.state.StateUtil;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
@ -36,8 +38,8 @@ public class SoggyCloudStairsBlock extends CloudStairsBlock implements Soakable
@Override @Override
public BlockState getStateWithMoisture(BlockState state, int moisture) { public BlockState getStateWithMoisture(BlockState state, int moisture) {
if (moisture <= 0) { if (moisture <= 0) {
return Soakable.copyProperties(state, dryBlock.get().getDefaultState()); return StateUtil.copyState(state, dryBlock.get().getDefaultState());
} }
return Soakable.copyProperties(state, getDefaultState()).with(MOISTURE, moisture); return StateUtil.copyState(state, getDefaultState()).with(MOISTURE, moisture);
} }
} }

View file

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

View file

@ -0,0 +1,19 @@
package com.minelittlepony.unicopia.block.state;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.BlockState;
import net.minecraft.state.property.Property;
public interface StateUtil {
@SuppressWarnings({ "unchecked", "rawtypes" })
static BlockState copyState(BlockState from, @Nullable BlockState to) {
if (to == null) {
return to;
}
for (var property : from.getProperties()) {
to = to.withIfExists((Property)property, from.get(property));
}
return to;
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,16 @@
package com.minelittlepony.unicopia.client.gui;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.math.MathHelper;
public interface MagicText {
static int getColor() {
MinecraftClient client = MinecraftClient.getInstance();
float ticks = client.player.age + client.getTickDelta();
float sin = (MathHelper.sin(ticks / 10F) + 1) * 155 * 0.25F;
float cos = (MathHelper.cos((ticks + 10) / 10F) + 1) * 155 * 0.25F;
return (int)(sin + cos);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,19 +4,13 @@ import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.common.client.gui.dimension.Bounds; import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.unicopia.ability.magic.spell.crafting.IngredientWithSpell; import com.minelittlepony.unicopia.ability.magic.spell.crafting.IngredientWithSpell;
import com.minelittlepony.unicopia.client.gui.spellbook.IngredientTree; import com.minelittlepony.unicopia.client.gui.spellbook.IngredientTree;
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookScreen;
record Stack (DynamicContent.Page page, IngredientWithSpell ingredient, Bounds bounds) implements PageElement { record Stack (DynamicContent.Page page, IngredientWithSpell ingredient, Bounds bounds) implements PageElement {
@Override @Override
public void compile(int y, IViewRoot container) { public void compile(int y, IViewRoot container) {
int xx = 0, yy = 0;
if (container instanceof SpellbookScreen book) {
xx = book.getX();
yy = book.getY();
}
IngredientTree tree = new IngredientTree( IngredientTree tree = new IngredientTree(
bounds().left + xx + page().getBounds().left, bounds().left + page().getBounds().left,
bounds().top + yy + page().getBounds().top + y + 10, bounds().top + page().getBounds().top + y - 10,
30 30
); );
tree.input(ingredient.getMatchingStacks()); tree.input(ingredient.getMatchingStacks());

View file

@ -6,6 +6,7 @@ import java.util.function.Supplier;
import com.minelittlepony.common.client.gui.IViewRoot; import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.common.client.gui.dimension.Bounds; import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.unicopia.client.gui.MagicText;
import com.minelittlepony.unicopia.client.gui.ParagraphWrappingVisitor; import com.minelittlepony.unicopia.client.gui.ParagraphWrappingVisitor;
import com.minelittlepony.unicopia.container.SpellbookChapterLoader.Flow; import com.minelittlepony.unicopia.container.SpellbookChapterLoader.Flow;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
@ -52,7 +53,7 @@ class TextBlock implements PageElement {
MatrixStack matrices = context.getMatrices(); MatrixStack matrices = context.getMatrices();
matrices.push(); matrices.push();
wrappedText.forEach(line -> { wrappedText.forEach(line -> {
context.drawText(font, needsMoreXp ? line.text().copy().formatted(Formatting.OBFUSCATED) : line.text().copy(), line.x(), 0, 0, false); context.drawText(font, needsMoreXp ? line.text().copy().formatted(Formatting.OBFUSCATED) : line.text().copy(), line.x(), 0, MagicText.getColor(), false);
matrices.translate(0, font.fontHeight, 0); matrices.translate(0, font.fontHeight, 0);
}); });
matrices.pop(); matrices.pop();

View file

@ -2,6 +2,8 @@ package com.minelittlepony.unicopia.client.particle;
import org.joml.Vector3f; import org.joml.Vector3f;
import com.minelittlepony.unicopia.client.render.RenderUtil;
import net.minecraft.client.particle.Particle; import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.ParticleTextureSheet; import net.minecraft.client.particle.ParticleTextureSheet;
import net.minecraft.client.render.BufferBuilder; import net.minecraft.client.render.BufferBuilder;
@ -9,6 +11,7 @@ import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexFormat; import net.minecraft.client.render.VertexFormat;
import net.minecraft.client.render.VertexFormats; import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld; import net.minecraft.client.world.ClientWorld;
public abstract class AbstractGeometryBasedParticle extends Particle { public abstract class AbstractGeometryBasedParticle extends Particle {
@ -36,6 +39,25 @@ public abstract class AbstractGeometryBasedParticle extends Particle {
te.draw(); te.draw();
} }
protected final void renderQuad(MatrixStack matrices, Tessellator te, BufferBuilder buffer, RenderUtil.Vertex[] corners, float alpha, float tickDelta) {
int light = getBrightness(tickDelta);
buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT);
for (RenderUtil.Vertex corner : corners) {
var position = corner.position(matrices.peek().getPositionMatrix());
buffer.vertex(position.x, position.y, position.z).texture(corner.texture().x, corner.texture().y).color(red, green, blue, alpha).light(light).next();
}
te.draw();
}
protected final void renderQuad(Tessellator te, BufferBuilder buffer, RenderUtil.Vertex[] corners, float alpha, float tickDelta) {
int light = getBrightness(tickDelta);
buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT);
for (RenderUtil.Vertex corner : corners) {
buffer.vertex(corner.position().x, corner.position().y, corner.position().z).texture(corner.texture().x, corner.texture().y).color(red, green, blue, alpha).light(light).next();
}
te.draw();
}
protected final void renderQuad(VertexConsumer buffer, Vector3f[] corners, float alpha, float tickDelta) { protected final void renderQuad(VertexConsumer buffer, Vector3f[] corners, float alpha, float tickDelta) {
int light = getBrightness(tickDelta); int light = getBrightness(tickDelta);

View file

@ -0,0 +1,57 @@
package com.minelittlepony.unicopia.client.particle;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.BooleanSupplier;
import com.minelittlepony.unicopia.particle.ParticleSpawner;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.particle.Particle;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
/**
* A connection class for updating and persisting an attached particle effect.
*/
public class ClientBoundParticleSpawner implements ParticleSpawner {
private static final Map<UUID, Entry> SPAWNED_PARTICLES = new HashMap<>();
private final UUID id;
private WeakReference<BooleanSupplier> attachment = new WeakReference<>(null);
private final MinecraftClient client = MinecraftClient.getInstance();
public ClientBoundParticleSpawner(UUID id) {
this.id = id;
}
@Override
public void addParticle(ParticleEffect effect, Vec3d pos, Vec3d vel) {
BooleanSupplier a = attachment.get();
if ((a == null || !a.getAsBoolean())) {
SPAWNED_PARTICLES.values().removeIf(set -> !set.getAsBoolean());
attachment = new WeakReference<>(SPAWNED_PARTICLES.computeIfAbsent(id, i -> {
return new Entry(
new WeakReference<>(client.world),
new WeakReference<>(client.particleManager.addParticle(effect, pos.x, pos.y, pos.z, vel.x, vel.y, vel.z))
);
}));
}
}
private record Entry (WeakReference<World> world, WeakReference<Particle> particle) implements BooleanSupplier {
@Override
public boolean getAsBoolean() {
if (world.get() == null || world.get() != MinecraftClient.getInstance().world) {
return false;
}
Particle particle = this.particle.get();
return particle != null && particle.isAlive();
}
}
}

View file

@ -30,7 +30,7 @@ public class CloudsEscapingParticle extends GroundPoundParticle {
); );
double columnHeight = 1 + age / 30; double columnHeight = 1 + age / 30;
new Sphere(true, columnHeight, 1, 1, 1) new Sphere(true, columnHeight)
.translate(center) .translate(center)
.randomPoints(random) .randomPoints(random)
.forEach(point -> { .forEach(point -> {

View file

@ -0,0 +1,86 @@
package com.minelittlepony.unicopia.client.particle;
import com.minelittlepony.common.util.Color;
import com.minelittlepony.unicopia.client.render.RenderUtil;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.particle.BlockStateParticleEffect;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis;
public class DustCloudParticle extends AbstractBillboardParticle {
//private static final Identifier TEXTURE = new Identifier("textures/particle/big_smoke_3.png");
protected static final int SEGMENTS = 20;
protected static final int SEPARATION = 270 / SEGMENTS;
private float scaleFactor;
protected Sprite sprite;
private final RenderUtil.Vertex[] vertices;
public DustCloudParticle(BlockStateParticleEffect effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {
super(world, x, y, z, velocityX, velocityY, velocityZ);
maxAge = 1000;
gravityStrength = 1;
red = 0.6F;
green = 0.6F;
blue = 0.6F;
alpha = (float)world.getRandom().nextTriangular(0.6, 0.2);
scaleFactor = (float)world.getRandom().nextTriangular(2, 1.2);
sprite = MinecraftClient.getInstance().getBlockRenderManager().getModels().getModelParticleSprite(effect.getBlockState());
vertices = new RenderUtil.Vertex[]{
new RenderUtil.Vertex(-1, -1, 0, sprite.getMinU(), sprite.getMinV()),
new RenderUtil.Vertex(-1, 1, 0, sprite.getMaxU(), sprite.getMinV()),
new RenderUtil.Vertex( 1, 1, 0, sprite.getMaxU(), sprite.getMaxV()),
new RenderUtil.Vertex( 1, -1, 0, sprite.getMinU(), sprite.getMaxV())
};
if (!effect.getBlockState().isOf(Blocks.GRASS_BLOCK)) {
int i = MinecraftClient.getInstance().getBlockColors().getColor(effect.getBlockState(), world, BlockPos.ofFloored(x, y, z), 0);
red *= Color.r(i);
green *= Color.g(i);
blue *= Color.b(i);
}
}
@Override
protected Identifier getTexture() {
return sprite.getAtlasId();
}
@Override
public void tick() {
super.tick();
scaleFactor += 0.001F;
scale(MathHelper.clamp(age / 5F, 0, 1) * scaleFactor);
}
@Override
protected void renderQuads(Tessellator te, BufferBuilder buffer, float x, float y, float z, float tickDelta) {
float scale = getScale(tickDelta);
float alpha = this.alpha * (1 - ((float)age / maxAge));
MatrixStack matrices = new MatrixStack();
matrices.translate(x, y, z);
matrices.scale(scale, scale * 0.5F, scale);
float angle = ((this.age + tickDelta) % 360) / SEGMENTS;
for (int i = 0; i < SEGMENTS; i++) {
matrices.push();
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees((i * angle) % 360));
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees((SEPARATION * i + angle) % 360));
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees((SEPARATION * i + angle) % 360));
float ringScale = 1 + MathHelper.sin(((i * 10) + age + tickDelta) * 0.05F) * 0.1F;
matrices.scale(ringScale, ringScale, ringScale);
renderQuad(matrices, te, buffer, vertices, alpha, tickDelta);
matrices.pop();
}
}
}

View file

@ -0,0 +1,31 @@
package com.minelittlepony.unicopia.client.particle;
import net.minecraft.client.particle.ParticleTextureSheet;
import net.minecraft.client.particle.SpriteBillboardParticle;
import net.minecraft.client.particle.SpriteProvider;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.particle.ParticleTypes;
public class FloatingBubbleParticle extends SpriteBillboardParticle {
public FloatingBubbleParticle(ParticleEffect effect, SpriteProvider provider, ClientWorld clientWorld, double x, double y, double z, double dX, double dY, double dZ) {
super(clientWorld, x, y, z, dX, dY, dZ);
setSprite(provider);
scale((float)clientWorld.random.nextTriangular(1F, 0.5F));
this.velocityX *= -0.1F;
this.velocityY *= -0.1F;
this.velocityZ *= -0.1F;
this.maxAge *= 3;
}
@Override
public ParticleTextureSheet getType() {
return ParticleTextureSheet.PARTICLE_SHEET_OPAQUE;
}
@Override
public void markDead() {
super.markDead();
world.addParticle(ParticleTypes.BUBBLE_POP, x, y, z, 0, 0, 0);
}
}

View file

@ -22,7 +22,6 @@ public abstract class OrientedBillboardParticle extends AbstractBillboardParticl
fixed = effect.fixed(); fixed = effect.fixed();
if (fixed) { if (fixed) {
// Was hamiltonianProduct (CHECK THIS!!)
rotation.mul(RotationAxis.POSITIVE_Y.rotationDegrees(effect.pitch())); rotation.mul(RotationAxis.POSITIVE_Y.rotationDegrees(effect.pitch()));
rotation.mul(RotationAxis.POSITIVE_X.rotationDegrees(180 - effect.yaw())); rotation.mul(RotationAxis.POSITIVE_X.rotationDegrees(180 - effect.yaw()));
} }

View file

@ -1,39 +1,46 @@
package com.minelittlepony.unicopia.client.particle; package com.minelittlepony.unicopia.client.particle;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import com.minelittlepony.unicopia.EntityConvertable;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.client.render.bezier.BezierSegment; import com.minelittlepony.unicopia.client.render.bezier.BezierSegment;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.client.render.bezier.Trail;
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment; import com.minelittlepony.unicopia.particle.TargetBoundParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleHandle.Link;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BufferBuilder; import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Tessellator; import net.minecraft.client.render.Tessellator;
import net.minecraft.client.world.ClientWorld; import net.minecraft.client.world.ClientWorld;
import net.minecraft.particle.DefaultParticleType; import net.minecraft.entity.Entity;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
public class RainbowTrailParticle extends AbstractBillboardParticle implements Attachment { public class RainbowTrailParticle extends AbstractBillboardParticle {
private static final Identifier TEXTURE = Unicopia.id("textures/particles/rainboom_trail.png"); private static final Identifier TEXTURE = Unicopia.id("textures/particles/rainboom_trail.png");
private final List<Segment> segments = new ArrayList<>(); private final Trail trail;
private Optional<Link> link = Optional.empty(); @Nullable
private Entity target;
private boolean isAbility;
private boolean bound; public RainbowTrailParticle(TargetBoundParticleEffect effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {
public RainbowTrailParticle(DefaultParticleType effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {
super(world, x, y, z, velocityX, velocityY, velocityZ); super(world, x, y, z, velocityX, velocityY, velocityZ);
segments.add(new Segment(new Vec3d(x, y, z))); trail = new Trail(new Vec3d(x, y, z));
setMaxAge(300); setMaxAge(300);
this.velocityX = velocityX;
this.velocityY = velocityY;
this.velocityZ = velocityZ;
if (effect.getTargetId() <= 0) {
this.target = world.getOtherEntities(null, Box.from(trail.pos)).get(0);
} else {
this.target = world.getEntityById(effect.getTargetId());
}
isAbility = Caster.of(target).filter(caster -> SpellType.RAINBOOM.isOn(caster)).isPresent();
} }
@Override @Override
@ -42,98 +49,45 @@ public class RainbowTrailParticle extends AbstractBillboardParticle implements A
} }
@Override @Override
public boolean isStillAlive() { public boolean isAlive() {
return age < getMaxAge() && (!dead || !segments.isEmpty()); return age < getMaxAge() && (!dead || !trail.getSegments().isEmpty());
}
@Override
public void attach(Link link) {
this.link = Optional.of(link);
bound = true;
}
@Override
public void detach() {
link = Optional.empty();
}
@Override
public void setAttribute(int key, Number value) {
} }
@Override @Override
protected void renderQuads(Tessellator te, BufferBuilder buffer, float x, float y, float z, float tickDelta) { protected void renderQuads(Tessellator te, BufferBuilder buffer, float x, float y, float z, float tickDelta) {
float alpha = 1 - (float)age / maxAge; float alpha = this.alpha * (1 - (float)age / maxAge);
List<Trail.Segment> segments = trail.getSegments();
for (int i = 0; i < segments.size() - 1; i++) { for (int i = 0; i < segments.size() - 1; i++) {
BezierSegment corners = segments.get(i).getPlane(segments.get(i + 1)); BezierSegment corners = segments.get(i).getPlane(segments.get(i + 1));
float scale = getScale(tickDelta); float scale = getScale(tickDelta);
corners.forEachCorner(corner -> { corners.forEachCorner(corner -> {
corner.mul(scale); corner.position().mul(scale).add(x, y, z);
corner.add(x, y, z);
}); });
renderQuad(te, buffer, corners.corners(), segments.get(i).getAlpha() * alpha, tickDelta); renderQuad(te, buffer, corners.corners(), segments.get(i).getAlpha() * alpha, tickDelta);
} }
} }
private void follow(EntityConvertable<?> caster) {
Vec3d next = caster.asEntity().getPos();
if (segments.isEmpty()) {
segments.add(new Segment(next));
} else {
Vec3d last = segments.get(segments.size() - 1).position;
if (next.distanceTo(last) > 0.2) {
segments.add(new Segment(next));
}
}
}
@Override @Override
public void tick() { public void tick() {
super.tick(); super.tick();
if (link.isPresent()) { if (target != null && target.isAlive()) {
age = 0; if (isAbility) {
link.flatMap(Link::get).ifPresent(this::follow); age = 0;
} else if (!dead && !bound) { }
follow(Pony.of(MinecraftClient.getInstance().player)); trail.update(target.getEyePos());
if (isAbility && Caster.of(target).filter(caster -> SpellType.RAINBOOM.isOn(caster)).isEmpty()) {
target = null;
}
} }
if (segments.size() > 1) { if (trail.tick()) {
segments.removeIf(Segment::tick);
}
if (segments.isEmpty()) {
markDead(); markDead();
} }
} }
private final class Segment {
Vec3d position;
Vector3f offset;
int age;
int maxAge;
Segment(Vec3d position) {
this.position = position;
this.offset = new Vector3f((float)(position.getX() - x), (float)(position.getY() - y), (float)(position.getZ() - z));
this.maxAge = 90;
}
float getAlpha() {
return alpha * (1 - ((float)age / maxAge));
}
boolean tick() {
return segments.indexOf(this) < segments.size() - 1 && age++ >= maxAge;
}
BezierSegment getPlane(Segment to) {
return new BezierSegment(offset, to.offset, 1);
}
}
} }

View file

@ -1,26 +1,20 @@
package com.minelittlepony.unicopia.client.particle; package com.minelittlepony.unicopia.client.particle;
import java.util.Optional;
import org.joml.Quaternionf; import org.joml.Quaternionf;
import org.joml.Vector3f; import org.joml.Vector3f;
import com.minelittlepony.common.util.Color;
import com.minelittlepony.unicopia.EntityConvertable;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.particle.OrientedBillboardParticleEffect; import com.minelittlepony.unicopia.particle.OrientedBillboardParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.particle.ParticleHandle.Link;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.render.BufferBuilder; import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Tessellator; import net.minecraft.client.render.Tessellator;
import net.minecraft.client.world.ClientWorld; import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.*; import net.minecraft.util.math.*;
public class RunesParticle extends OrientedBillboardParticle implements Attachment { @Deprecated
public class RunesParticle extends OrientedBillboardParticle {
private static final Identifier[] TEXTURES = new Identifier[] { private static final Identifier[] TEXTURES = new Identifier[] {
Unicopia.id("textures/particles/runes_0.png"), Unicopia.id("textures/particles/runes_0.png"),
@ -39,10 +33,6 @@ public class RunesParticle extends OrientedBillboardParticle implements Attachme
private float prevRotationAngle; private float prevRotationAngle;
private float rotationAngle; private float rotationAngle;
private Optional<Link> link = Optional.empty();
private int stasisAge = -1;
public RunesParticle(OrientedBillboardParticleEffect effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) { public RunesParticle(OrientedBillboardParticleEffect effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {
super(effect, world, x, y, z, velocityX, velocityY, velocityZ); super(effect, world, x, y, z, velocityX, velocityY, velocityZ);
setMaxAge(70); setMaxAge(70);
@ -52,52 +42,6 @@ public class RunesParticle extends OrientedBillboardParticle implements Attachme
blue = world.random.nextFloat(); blue = world.random.nextFloat();
} }
@Override
public boolean isStillAlive() {
return age < (maxAge - 1);
}
@Override
public void attach(Link link) {
this.link = Optional.of(link);
velocityX = 0;
velocityY = 0;
velocityZ = 0;
Vec3d pos = link.get().map(EntityConvertable::asEntity).map(Entity::getPos).orElse(Vec3d.ZERO);
setPos(pos.x, pos.y + 0.25, pos.z);
}
@Override
public void detach() {
link = Optional.empty();
if (targetSize > 1) {
this.targetSize = 0;
}
}
@Override
public void setAttribute(int key, Number value) {
if (key == ATTR_COLOR) {
int tint = value.intValue();
red = Color.r(tint);
green = Color.g(tint);
blue = Color.b(tint);
}
if (key == ATTR_OPACITY) {
alpha = value.floatValue();
}
if (key == ATTR_RADIUS) {
targetSize = value.floatValue();
}
if (key == ATTR_PITCH) {
rotation = new Quaternionf(0, 0, 0, 1);
rotation.mul(RotationAxis.POSITIVE_Y.rotationDegrees(value.floatValue()));
}
if (key == ATTR_YAW) {
rotation.mul(RotationAxis.POSITIVE_X.rotationDegrees(180 - value.floatValue()));
}
}
@Override @Override
public float getScale(float tickDelta) { public float getScale(float tickDelta) {
return MathHelper.lerp(tickDelta, prevBaseSize, baseSize) * super.getScale(tickDelta); return MathHelper.lerp(tickDelta, prevBaseSize, baseSize) * super.getScale(tickDelta);
@ -165,15 +109,6 @@ public class RunesParticle extends OrientedBillboardParticle implements Attachme
public void tick() { public void tick() {
super.tick(); super.tick();
link.flatMap(Link::get).map(EntityConvertable::asEntity).ifPresentOrElse(e -> {
if (getAlphaScale() >= 0.9F) {
if (stasisAge < 0) {
stasisAge = age;
}
age = stasisAge;
}
}, this::detach);
prevBaseSize = baseSize; prevBaseSize = baseSize;
if (baseSize < targetSize) { if (baseSize < targetSize) {
baseSize += 0.1F; baseSize += 0.1F;

View file

@ -10,22 +10,13 @@ import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld; import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import com.minelittlepony.unicopia.EntityConvertable;
import com.minelittlepony.unicopia.client.render.RenderLayers; import com.minelittlepony.unicopia.client.render.RenderLayers;
import com.minelittlepony.unicopia.client.render.model.SphereModel; import com.minelittlepony.unicopia.client.render.model.SphereModel;
import com.minelittlepony.unicopia.particle.SphereParticleEffect; import com.minelittlepony.unicopia.particle.SphereParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.particle.ParticleHandle.Link;
import com.minelittlepony.unicopia.util.ColorHelper; import com.minelittlepony.unicopia.util.ColorHelper;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import java.util.Optional; public class SphereParticle extends Particle {
import com.minelittlepony.common.util.Color;
public class SphereParticle extends Particle implements Attachment {
protected float prevRadius; protected float prevRadius;
protected float radius; protected float radius;
@ -34,12 +25,6 @@ public class SphereParticle extends Particle implements Attachment {
protected float lerpIncrement; protected float lerpIncrement;
protected float toRadius; protected float toRadius;
private Optional<Link> link = Optional.empty();
private final SphereParticleEffect parameters;
private boolean bound;
public SphereParticle(SphereParticleEffect parameters, ClientWorld w, double x, double y, double z, double vX, double vY, double vZ) { public SphereParticle(SphereParticleEffect parameters, ClientWorld w, double x, double y, double z, double vX, double vY, double vZ) {
this(parameters, w, x, y, z); this(parameters, w, x, y, z);
@ -50,7 +35,6 @@ public class SphereParticle extends Particle implements Attachment {
public SphereParticle(SphereParticleEffect parameters, ClientWorld w, double x, double y, double z) { public SphereParticle(SphereParticleEffect parameters, ClientWorld w, double x, double y, double z) {
super(w, x, y, z); super(w, x, y, z);
this.parameters = parameters;
this.radius = parameters.radius(); this.radius = parameters.radius();
this.red = parameters.color().x / 255F; this.red = parameters.color().x / 255F;
this.green = parameters.color().y / 255F; this.green = parameters.color().y / 255F;
@ -60,43 +44,6 @@ public class SphereParticle extends Particle implements Attachment {
setMaxAge(10); setMaxAge(10);
} }
@Override
public boolean isStillAlive() {
return age < (maxAge - 1);
}
@Override
public void attach(Link link) {
setMaxAge(50000);
this.link = Optional.of(link);
}
@Override
public void detach() {
markDead();
}
@Override
public void setAttribute(int key, Number value) {
if (key == ATTR_RADIUS) {
toRadius = value.floatValue();
steps = 20;
lerpIncrement = (toRadius - radius) / steps;
}
if (key == ATTR_COLOR) {
int tint = value.intValue();
red = Color.r(tint);
green = Color.g(tint);
blue = Color.b(tint);
}
if (key == ATTR_OPACITY) {
alpha = value.floatValue();
}
if (key == ATTR_BOUND) {
bound = value.intValue() == 1;
}
}
@Override @Override
public ParticleTextureSheet getType() { public ParticleTextureSheet getType() {
return ParticleTextureSheet.CUSTOM; return ParticleTextureSheet.CUSTOM;
@ -106,24 +53,7 @@ public class SphereParticle extends Particle implements Attachment {
public void tick() { public void tick() {
super.tick(); super.tick();
if (link.isPresent()) { radius *= 0.9998281;
link.flatMap(Link::get).map(EntityConvertable::asEntity).ifPresentOrElse(e -> {
if (!bound) {
Vec3d offset = parameters.offset();
setPos(e.getX() + offset.getX(), e.getY() + offset.getY(), e.getZ() + offset.getZ());
prevPosX = e.lastRenderX + offset.getX();
prevPosY = e.lastRenderY + offset.getY();
prevPosZ = e.lastRenderZ + offset.getZ();
}
}, this::detach);
if (steps-- > 0) {
radius += lerpIncrement;
}
} else {
radius *= 0.9998281;
}
} }
@Override @Override

View file

@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.client.FirstPersonRendererOverrides.ArmRenderer; import com.minelittlepony.unicopia.client.FirstPersonRendererOverrides.ArmRenderer;
import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate;
import com.minelittlepony.unicopia.client.render.spell.SpellEffectsRenderDispatcher; import com.minelittlepony.unicopia.client.render.spell.SpellEffectsRenderDispatcher;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
@ -14,17 +15,19 @@ import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.feature.FeatureRenderer; import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.feature.FeatureRendererContext;
import net.minecraft.client.render.entity.model.BipedEntityModel; import net.minecraft.client.render.entity.model.BipedEntityModel;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.util.Arm; import net.minecraft.util.Arm;
public class AccessoryFeatureRenderer< public class AccessoryFeatureRenderer<
T extends LivingEntity, T extends LivingEntity,
M extends BipedEntityModel<T>> extends FeatureRenderer<T, M> { M extends EntityModel<T>> extends FeatureRenderer<T, M> {
private static final List<FeatureFactory<?>> REGISTRY = new ArrayList<>(); private static final List<FeatureFactory<?, ?>> REGISTRY = new ArrayList<>();
public static void register(FeatureFactory<?>...factories) { @SafeVarargs
public static <T extends LivingEntity> void register(FeatureFactory<T, BipedEntityModel<T>>...factories) {
for (var factory : factories) { for (var factory : factories) {
REGISTRY.add(factory); REGISTRY.add(factory);
} }
@ -35,11 +38,15 @@ public class AccessoryFeatureRenderer<
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public AccessoryFeatureRenderer(FeatureRendererContext<T, M> context) { public AccessoryFeatureRenderer(FeatureRendererContext<T, M> context) {
super(context); super(context);
features = REGISTRY.stream().map(f -> ((FeatureFactory<T>)f).create(context)).toList(); features = REGISTRY.stream().map(f -> ((FeatureFactory<T, M>)f).create(context)).toList();
} }
@Override @Override
public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) {
if (MineLPDelegate.getInstance().getRace(entity).isEquine()) {
return;
}
features.forEach(feature -> feature.render(matrices, vertexConsumers, light, entity, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch)); features.forEach(feature -> feature.render(matrices, vertexConsumers, light, entity, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch));
Caster.of(entity).ifPresent(caster -> { Caster.of(entity).ifPresent(caster -> {
@ -52,6 +59,10 @@ public class AccessoryFeatureRenderer<
} }
public boolean beforeRenderArms(ArmRenderer sender, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, T entity, int light) { public boolean beforeRenderArms(ArmRenderer sender, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, T entity, int light) {
Caster<?> caster = Caster.of(entity).orElse(null);
if (caster != null) {
SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertexConsumers, light, caster, 0, 0, tickDelta, entity.age + tickDelta, 0, 0);
}
boolean cancelled = false; boolean cancelled = false;
for (var feature : features) { for (var feature : features) {
cancelled |= feature.beforeRenderArms(sender, tickDelta, matrices, vertexConsumers, entity, light); cancelled |= feature.beforeRenderArms(sender, tickDelta, matrices, vertexConsumers, entity, light);
@ -59,8 +70,8 @@ public class AccessoryFeatureRenderer<
return cancelled; return cancelled;
} }
public interface FeatureFactory<T extends LivingEntity> { public interface FeatureFactory<T extends LivingEntity, M extends EntityModel<T>> {
Feature<T> create(FeatureRendererContext<T, ? extends BipedEntityModel<T>> context); Feature<T> create(FeatureRendererContext<T, M> context);
} }
public interface Feature<T extends LivingEntity> { public interface Feature<T extends LivingEntity> {
@ -75,11 +86,11 @@ public class AccessoryFeatureRenderer<
public interface FeatureRoot< public interface FeatureRoot<
T extends LivingEntity, T extends LivingEntity,
M extends BipedEntityModel<T>> { M extends EntityModel<T>> {
AccessoryFeatureRenderer<T, M> getAccessories(); AccessoryFeatureRenderer<T, M> getAccessories();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Nullable @Nullable
static <T extends LivingEntity, M extends BipedEntityModel<T>> FeatureRoot<T, M> of(T entity) { static <T extends LivingEntity, M extends EntityModel<T>> FeatureRoot<T, M> of(T entity) {
var renderer = MinecraftClient.getInstance().getEntityRenderDispatcher().getRenderer(entity); var renderer = MinecraftClient.getInstance().getEntityRenderDispatcher().getRenderer(entity);
if (renderer instanceof FeatureRoot) { if (renderer instanceof FeatureRoot) {
return (FeatureRoot<T, M>)renderer; return (FeatureRoot<T, M>)renderer;

View file

@ -39,6 +39,15 @@ public final class RenderLayers extends RenderLayer {
.target(TRANSLUCENT_TARGET) .target(TRANSLUCENT_TARGET)
.build(false)); .build(false));
private static final RenderLayer MAGIC_SHIELD = of("magic_shield", VertexFormats.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL,
VertexFormat.DrawMode.QUADS, 256, true, true, MultiPhaseParameters.builder()
.program(COLOR_PROGRAM)
.transparency(TRANSLUCENT_TRANSPARENCY)
.target(TRANSLUCENT_TARGET)
.cull(DISABLE_CULLING)
.writeMaskState(COLOR_MASK)
.build(false));
private static final Function<Integer, RenderLayer> MAGIC_COLORIN_FUNC = Util.memoize(color -> { private static final Function<Integer, RenderLayer> MAGIC_COLORIN_FUNC = Util.memoize(color -> {
return of("magic_colored_" + color, return of("magic_colored_" + color,
VertexFormats.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL, VertexFormats.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL,
@ -75,6 +84,10 @@ public final class RenderLayers extends RenderLayer {
return MAGIC_NO_COLOR; return MAGIC_NO_COLOR;
} }
public static RenderLayer getMagicShield() {
return MAGIC_SHIELD;
}
public static RenderLayer getMagicColored() { public static RenderLayer getMagicColored() {
return MAGIC_COLORED; return MAGIC_COLORED;
} }

View file

@ -11,31 +11,46 @@ import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
public class RenderUtil { public class RenderUtil {
private static final Vector4f TEMP_VECTOR = new Vector4f(); public static final Vector4f TEMP_VECTOR = new Vector4f();
private static final Vector4f TEMP_UV_VECTOR = new Vector4f();
public static final Vertex[] UNIT_FACE = new Vertex[] { public static final Vertex[] UNIT_FACE = new Vertex[] {
new Vertex(new Vector3f(0, 0, 0), 1, 1), new Vertex(0, 0, 0, 1, 1),
new Vertex(new Vector3f(0, 1, 0), 1, 0), new Vertex(0, 1, 0, 1, 0),
new Vertex(new Vector3f(1, 1, 0), 0, 0), new Vertex(1, 1, 0, 0, 0),
new Vertex(new Vector3f(1, 0, 0), 0, 1) new Vertex(1, 0, 0, 0, 1)
};
public static final Vertex[] FRAME_BUFFER_VERTICES = new Vertex[] {
new Vertex(0, 1, 0, 0, 0),
new Vertex(1, 1, 0, 1, 0),
new Vertex(1, 0, 0, 1, 1),
new Vertex(0, 0, 0, 0, 1)
}; };
public static void renderFace(MatrixStack matrices, Tessellator te, BufferBuilder buffer, float r, float g, float b, float a, int light) { public static void renderFace(MatrixStack matrices, Tessellator te, BufferBuilder buffer, float r, float g, float b, float a, int light) {
renderFace(matrices, te, buffer, r, g, b, a, light, 1, 1);
}
public static void renderFace(MatrixStack matrices, Tessellator te, BufferBuilder buffer, float r, float g, float b, float a, int light, float uScale, float vScale) {
buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT); buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT);
Matrix4f positionmatrix = matrices.peek().getPositionMatrix();
Vertex[] UNIT_FACE = new Vertex[] {
new Vertex(new Vector3f(0, 0, 0), 1, 1),
new Vertex(new Vector3f(0, 1, 0), 1, 0),
new Vertex(new Vector3f(1, 1, 0), 0, 0),
new Vertex(new Vector3f(1, 0, 0), 0, 1)
};
Matrix4f transformation = matrices.peek().getPositionMatrix();
for (Vertex vertex : UNIT_FACE) { for (Vertex vertex : UNIT_FACE) {
transformation.transform(TEMP_VECTOR.set(vertex.position(), 1)); Vector4f position = vertex.position(positionmatrix);
buffer.vertex(TEMP_VECTOR.x, TEMP_VECTOR.y, TEMP_VECTOR.z).texture(vertex.u(), vertex.v()).color(r, g, b, a).light(light).next(); buffer.vertex(position.x, position.y, position.z).texture(vertex.texture().x * uScale, vertex.texture().y * vScale).color(r, g, b, a).light(light).next();
} }
te.draw(); te.draw();
} }
record Vertex(Vector3f position, float u, float v) {} public record Vertex(Vector3f position, Vector3f texture) {
public Vertex(float x, float y, float z, float u, float v) {
this(new Vector3f(x, y, z), new Vector3f(u, v, 1));
}
public Vector4f position(Matrix4f mat) {
return mat.transform(TEMP_VECTOR.set(position, 1));
}
public Vector4f texture(Matrix4f mat) {
return mat.transform(TEMP_UV_VECTOR.set(texture, 1));
}
}
} }

View file

@ -4,20 +4,22 @@ import java.util.function.Consumer;
import org.joml.Vector3f; import org.joml.Vector3f;
import com.minelittlepony.unicopia.client.render.RenderUtil;
public record BezierSegment( public record BezierSegment(
Vector3f[] corners RenderUtil.Vertex[] corners
) { ) {
public BezierSegment(Vector3f from, Vector3f to, float height) { public BezierSegment(Vector3f from, Vector3f to, float height) {
this(new Vector3f[] { this(new RenderUtil.Vertex[] {
new Vector3f(from.x, from.y - height/2F, from.z), // bottom left new RenderUtil.Vertex(from.x, from.y - height/2F, from.z, 0, 0), // bottom left
new Vector3f(from.x, from.y + height/2F, from.z), // top left new RenderUtil.Vertex(from.x, from.y + height/2F, from.z, 1, 0), // top left
new Vector3f(to.x, to.y + height/2F, to.z), // top right new RenderUtil.Vertex(to.x, to.y + height/2F, to.z, 1, 1), // top right
new Vector3f(to.x, to.y - height/2F, to.z) // bottom right new RenderUtil.Vertex(to.x, to.y - height/2F, to.z, 0, 1) // bottom right
}); });
} }
public void forEachCorner(Consumer<Vector3f> transformer) { public void forEachCorner(Consumer<RenderUtil.Vertex> transformer) {
for (var corner : corners) { for (var corner : corners) {
transformer.accept(corner); transformer.accept(corner);
} }

View file

@ -1,49 +0,0 @@
package com.minelittlepony.unicopia.client.render.bezier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.joml.Vector3f;
import net.minecraft.util.math.Vec3d;
public class Ribbon implements Iterable<BezierSegment> {
public float width;
public float angle;
private final List<Node> nodes = new ArrayList<>();
private final List<BezierSegment> segments = new ArrayList<>();
private Node lastNode;
public Ribbon(Vec3d position, Vector3f bottom, Vector3f top, float angle) {
this.angle = angle;
lastNode = new Node(position, position.toVector3f().add(bottom), position.toVector3f().add(top));
nodes.add(lastNode);
}
public void addNode(Vec3d position, float angle) {
Vector3f directionVector = position.subtract(lastNode.position()).toVector3f();
Vector3f bottom = lastNode.bottom().add(directionVector).rotateAxis(angle, directionVector.x, directionVector.y, directionVector.z);
Vector3f top = lastNode.top().add(directionVector).rotateAxis(angle, directionVector.x, directionVector.y, directionVector.z);
Node oldNode = lastNode;
lastNode = new Node(position, bottom, top);
nodes.add(lastNode);
segments.add(new BezierSegment(new Vector3f[] {
oldNode.bottom(),
oldNode.top(),
lastNode.bottom(),
lastNode.top()
}));
}
@Override
public Iterator<BezierSegment> iterator() {
return segments.iterator();
}
record Node(Vec3d position, Vector3f bottom, Vector3f top) {
}
}

View file

@ -0,0 +1,67 @@
package com.minelittlepony.unicopia.client.render.bezier;
import java.util.ArrayList;
import java.util.List;
import org.joml.Vector3f;
import net.minecraft.util.math.Vec3d;
public class Trail {
private final List<Segment> segments = new ArrayList<>();
public final Vec3d pos;
public Trail(Vec3d pos) {
this.pos = pos;
segments.add(new Segment(pos));
}
public List<Segment> getSegments() {
return segments;
}
public void update(Vec3d newPosition) {
if (segments.isEmpty()) {
segments.add(new Segment(newPosition));
} else {
Vec3d last = segments.get(segments.size() - 1).position;
if (newPosition.distanceTo(last) > 0.2) {
segments.add(new Segment(newPosition));
}
}
}
public boolean tick() {
if (segments.size() > 1) {
segments.removeIf(Segment::tick);
}
return segments.isEmpty();
}
public final class Segment {
Vec3d position;
Vector3f offset;
int age;
int maxAge;
Segment(Vec3d position) {
this.position = position;
this.offset = position.subtract(pos).toVector3f();
this.maxAge = 90;
}
public float getAlpha() {
return (1 - ((float)age / maxAge));
}
boolean tick() {
return segments.indexOf(this) < segments.size() - 1 && age++ >= maxAge;
}
public BezierSegment getPlane(Segment to) {
return new BezierSegment(offset, to.offset, 1);
}
}
}

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