- Fix hot air balloons not orienting correctly when placed

- add sandbags that you interact with to steer them
- fix various hitbox jankyness
- exclude spectators from hot air balloon collision checks
This commit is contained in:
Sollace 2024-03-29 20:31:49 +00:00
parent f8ff162d6c
commit f500956760
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
16 changed files with 373 additions and 177 deletions

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,33 @@
// Made with Blockbench 4.9.4
// Exported for Minecraft version 1.17+ for Yarn
// Paste this class into your mod and generate all required imports
public class hanging_sandbag extends EntityModel<Entity> {
private final ModelPart root;
private final ModelPart bag;
private final ModelPart cube_r1;
private final ModelPart cube_r2;
public hanging_sandbag(ModelPart root) {
this.root = root.getChild("root");
}
public static TexturedModelData getTexturedModelData() {
ModelData modelData = new ModelData();
ModelPartData modelPartData = modelData.getRoot();
ModelPartData root = modelPartData.addChild("root", ModelPartBuilder.create().uv(16, 19).cuboid(-0.5F, 0.0F, -0.5F, 1.0F, 9.0F, 1.0F, new Dilation(0.0F)), ModelTransform.pivot(0.0F, 24.0F, 0.0F));
ModelPartData bag = root.addChild("bag", ModelPartBuilder.create().uv(0, 0).cuboid(-3.0F, 1.0F, -3.0F, 6.0F, 7.0F, 6.0F, new Dilation(0.0F))
.uv(12, 14).cuboid(-2.0F, 0.0F, -2.0F, 4.0F, 1.0F, 4.0F, new Dilation(0.0F))
.uv(0, 13).cuboid(-2.0F, 8.0F, -2.0F, 4.0F, 1.0F, 4.0F, new Dilation(0.0F)), ModelTransform.pivot(0.0F, 9.0F, 0.0F));
ModelPartData cube_r1 = bag.addChild("cube_r1", ModelPartBuilder.create().uv(0, 14).cuboid(0.0F, 8.0F, -2.0F, 0.0F, 4.0F, 4.0F, new Dilation(0.0F)), ModelTransform.of(0.0F, 1.0F, 0.0F, 0.0F, -0.7854F, 0.0F));
ModelPartData cube_r2 = bag.addChild("cube_r2", ModelPartBuilder.create().uv(0, 14).cuboid(0.0F, 8.0F, -2.0F, 0.0F, 4.0F, 4.0F, new Dilation(0.0F)), ModelTransform.of(0.0F, 1.0F, 0.0F, 0.0F, 0.7854F, 0.0F));
return TexturedModelData.of(modelData, 32, 32);
}
@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) {
root.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,113 +0,0 @@
{
"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"}
}
}
]
}

View file

@ -4,6 +4,7 @@ import java.util.List;
import com.minelittlepony.unicopia.entity.mob.AirBalloonEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.model.*;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.entity.model.EntityModel;
@ -14,26 +15,37 @@ import net.minecraft.util.math.MathHelper;
public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> {
private final ModelPart root;
private ModelPart main;
private float inflation;
private boolean isBurner;
private boolean isBalloon;
private boolean isSandbags;
private final List<ModelPart> ropes;
private final List<ModelPart> sandbags;
public AirBalloonEntityModel(ModelPart root) {
this.root = root;
isBurner = root.hasChild("burner");
isSandbags = root.hasChild("sandbag_ne");
isBalloon = root.hasChild("canopy");
if (isBurner || isBalloon) {
ModelPart part = root.getChild(isBalloon ? "canopy" : "burner");
ropes = List.of(part.getChild("rope_a"), part.getChild("rope_b"), part.getChild("rope_c"),
part.getChild("rope_d"));
main = root.getChild(isBalloon ? "canopy" : "burner");
ropes = List.of(
(isBurner ? root : main).getChild("rope_a"), (isBurner ? root : main).getChild("rope_b"),
(isBurner ? root : main).getChild("rope_c"), (isBurner ? root : main).getChild("rope_d")
);
} else {
ropes = List.of();
}
sandbags = isSandbags ? List.of(
root.getChild("sandbag_nw"), root.getChild("sandbag_sw"),
root.getChild("sandbag_ne"), root.getChild("sandbag_se")
) : List.of();
}
public static TexturedModelData getBasketModelData() {
@ -56,12 +68,34 @@ public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> {
public static TexturedModelData getBurnerModelData() {
ModelData modelData = new ModelData();
ModelPartData root = modelData.getRoot();
root.addChild("burner", ModelPartBuilder.create().uv(8, 0).cuboid(-5.5F, -47, -5.5F, 11, 15, 11, Dilation.NONE), ModelTransform.pivot(0, 24, 0));
float angle = 0.37854F;
float half = MathHelper.HALF_PI;
root.addChild("rope_d", ModelPartBuilder.create().cuboid(0, -68, 0, 2, 66, 2, Dilation.NONE), ModelTransform.of(-0, -20, -0, angle, 0, -angle));
root.addChild("rope_c", ModelPartBuilder.create().cuboid(0, -68, 0, 2, 66, 2, Dilation.NONE), ModelTransform.of(-0, -20, -0, -angle, 0, -angle));
root.addChild("rope_b", ModelPartBuilder.create().cuboid(0, -68, 0, 2, 66, 2, Dilation.NONE), ModelTransform.of( 0, -20, 0, -angle, 0, angle));
root.addChild("rope_a", ModelPartBuilder.create().cuboid(0, -68, 0, 2, 66, 2, Dilation.NONE), ModelTransform.of( 0, -20, 0, angle, 0, angle));
ModelPartData burner = root.addChild("burner", ModelPartBuilder.create().uv(8, 0).cuboid(-6, -47, -6, 11, 15, 11, Dilation.NONE), ModelTransform.pivot(0, 24, 0));
burner.addChild("rope_d", ModelPartBuilder.create().cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of(-5, -46, -6, 0.7854F, 0, -0.7854F));
burner.addChild("rope_c", ModelPartBuilder.create().cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of(-4, -44, 3, -0.7854F, 0, -0.7854F));
burner.addChild("rope_b", ModelPartBuilder.create().cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of(5, -46, 1, -0.7854F, 0, 0.7854F));
burner.addChild("rope_a", ModelPartBuilder.create().cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of(5, -45, -6, 0.7854F, 0, 0.7854F));
root.addChild("strut_a", ModelPartBuilder.create()
.cuboid(-27, -40, -30, 2, 40, 2, Dilation.NONE)
.cuboid(-27, 0, -30, 2, 40, 2, Dilation.NONE)
.cuboid( 27, -40, -30, 2, 40, 2, Dilation.NONE)
.cuboid( 27, 0, -30, 2, 40, 2, Dilation.NONE)
.cuboid(-27, -40, 26, 2, 40, 2, Dilation.NONE)
.cuboid(-27, 0, 26, 2, 40, 2, Dilation.NONE)
.cuboid( 27, -40, 26, 2, 40, 2, Dilation.NONE)
.cuboid( 27, 0, 26, 2, 40, 2, Dilation.NONE), ModelTransform.of(0, -80, 0, half, 0, 0));
root.addChild("strut_b", ModelPartBuilder.create()
.cuboid(-27, -40, -20, 2, 40, 2, Dilation.NONE)
.cuboid(-27, 0, -20, 2, 40, 2, Dilation.NONE)
.cuboid( 27, -40, -20, 2, 40, 2, Dilation.NONE)
.cuboid( 27, 0, -20, 2, 40, 2, Dilation.NONE)
.cuboid(-27, -40, 30, 2, 40, 2, Dilation.NONE)
.cuboid(-27, 0, 30, 2, 40, 2, Dilation.NONE)
.cuboid( 27, -40, 30, 2, 40, 2, Dilation.NONE)
.cuboid( 27, 0, 30, 2, 40, 2, Dilation.NONE), ModelTransform.of(0, -80, 0, half, half, 0));
return TexturedModelData.of(modelData, 64, 128);
}
@ -76,14 +110,40 @@ public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> {
return TexturedModelData.of(modelData, 512, 256);
}
public static TexturedModelData getSandbagsModelData() {
ModelData modelData = new ModelData();
ModelPartData root = modelData.getRoot();
float offset = 40;
getHangingBagModelData("sandbag_ne", root, -offset, -offset);
getHangingBagModelData("sandbag_nw", root, -offset, offset);
getHangingBagModelData("sandbag_se", root, offset, -offset);
getHangingBagModelData("sandbag_sw", root, offset, offset);
return TexturedModelData.of(modelData, 32, 32);
}
public static void getHangingBagModelData(String name, ModelPartData root, float x, float z) {
ModelPartData bag = root.addChild(name, ModelPartBuilder.create()
.uv(16, 19).cuboid(-0.5F, 0, -0.5F, 1, 9, 1, Dilation.NONE), ModelTransform.pivot(x, -35, z));
ModelPartData knot = bag.addChild("knot", ModelPartBuilder.create()
.uv(0, 0).cuboid(-3, 1, -3, 6, 7, 6, Dilation.NONE)
.uv(12, 14).cuboid(-2, 0, -2, 4, 1, 4, Dilation.NONE)
.uv(0, 13).cuboid(-2, 8, -2, 4, 1, 4, Dilation.NONE), ModelTransform.pivot(0, 9, 0));
knot.addChild("cube_r1", ModelPartBuilder.create().uv(8, 14).cuboid(0, 8, -2, 0, 4, 4, Dilation.NONE), ModelTransform.of(0, 1, 0, 0, -0.7854F, 0));
knot.addChild("cube_r2", ModelPartBuilder.create().uv(8, 14).cuboid(0, 8, -2, 0, 4, 4, Dilation.NONE), ModelTransform.of(0, 1, 0, 0, 0.7854F, 0));
}
@Override
public void setAngles(AirBalloonEntity entity, float tickDelta, float limbSwingAmount, float ageInTicks,
float netHeadYaw, float headPitch) {
public void setAngles(AirBalloonEntity entity, float limbDistance, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
float tickDelta = MinecraftClient.getInstance().getTickDelta();
inflation = entity.getInflation(tickDelta);
if (isBurner || isBalloon) {
root.roll = MathHelper.clamp((float) (entity.getX() - entity.lastRenderX), -0.5F, 0.5F);
root.pitch = MathHelper.clamp((float) (entity.getZ() - entity.lastRenderZ), -0.5F, 0.5F);
root.yaw = MathHelper.PI;
float burnerWiggleProgress = entity.getBurner().getPullProgress(tickDelta);
if (isBurner || isBalloon || isSandbags) {
root.roll = MathHelper.clamp(entity.getXVelocity(tickDelta), -0.5F, 0.5F);
root.pitch = MathHelper.clamp(entity.getZVelocity(tickDelta), -0.5F, 0.5F);
if (entity.isLeashed()) {
root.roll *= -1;
root.pitch *= -1;
@ -93,27 +153,44 @@ public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> {
root.roll = 0;
}
for (ModelPart rope : ropes) {
rope.resetTransform();
}
ropes.forEach(ModelPart::resetTransform);
if (isBurner) {
boolean lifted = inflation > 0.8F;
root.pivotY = 32 * (1 - inflation);
root.pivotY = 32 * (1 - inflation) - (9 * inflation);
root.pivotX = inflation * MathHelper.sin(limbSwingAmount + entity.age / 5F) / 4F;
ropes.forEach(rope -> {
rope.visible = lifted;
rope.pitch *= 0.125;
rope.roll *= 0.125;
});
root.pivotX += burnerWiggleProgress * MathHelper.sin((entity.age + tickDelta)) * 2.5F;
root.pivotX += burnerWiggleProgress * MathHelper.cos((entity.age + tickDelta)) * 2.5F;
root.pivotY += burnerWiggleProgress * 7;
}
if (isBalloon) {
root.pivotY = 0;
if (isBalloon || isSandbags) {
root.pivotY = burnerWiggleProgress * 3;
root.pivotX = inflation * MathHelper.cos(limbSwingAmount + entity.age / 5F) / 4F;
if (entity.getBasketType().isOf(BoatEntity.Type.BAMBOO)) {
ropes.forEach(rope -> rope.pivotY = 0);
} else {
ropes.forEach(ModelPart::resetTransform);
}
}
if (isSandbags) {
float cosWiggle = MathHelper.cos(limbSwingAmount + entity.age / 5F) / 80F;
float sinWiggle = MathHelper.sin(limbSwingAmount + entity.age / 5F) / 80F;
for (int i = 0; i < sandbags.size(); i++) {
ModelPart bag = sandbags.get(i);
float pullProgress = entity.getSandbag(i).getPullProgress(tickDelta);
bag.resetTransform();
bag.pitch -= root.pitch * 2.5F * (1 + pullProgress) + cosWiggle;
bag.roll -= root.roll * 2.5F * (1 + pullProgress) + sinWiggle;
if (entity.isLeashed()) {
bag.roll *= -1;
bag.pitch *= -1;
}
float pullAmount = 2 + (2 * pullProgress);
bag.yScale = pullAmount;
bag.getChild("knot").yScale = 1/pullAmount;
}
}
@ -138,6 +215,16 @@ public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> {
if (i == 0 || i == 1) {
rope.pivotX += 5 * rollRatio;
}
if (isBalloon) {
double speed = Math.abs(entity.getVelocity().getY()) * 3F;
rope.zScale = MathHelper.clamp((float)speed, 0.25F, 1F);
rope.xScale = 0.001F;
} else {
rope.xScale = 0.3F;
rope.zScale = 0.3F;
}
}
}

View file

@ -1,9 +1,11 @@
package com.minelittlepony.unicopia.client.render.entity;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.entity.collision.MultiBox;
import com.minelittlepony.unicopia.entity.mob.AirBalloonEntity;
import net.minecraft.client.MinecraftClient;
@ -17,15 +19,22 @@ import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.item.Items;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Box;
public class AirBalloonEntityRenderer extends MobEntityRenderer<AirBalloonEntity, AirBalloonEntityModel> {
public AirBalloonEntityRenderer(EntityRendererFactory.Context context) {
super(context, new AirBalloonEntityModel(AirBalloonEntityModel.getBasketModelData().createModel()), 0);
addFeature(new BalloonFeature(new AirBalloonEntityModel(AirBalloonEntityModel.getBurnerModelData().createModel()), this, AirBalloonEntity::hasBurner, e -> {
addFeature(new BalloonFeature(new AirBalloonEntityModel(AirBalloonEntityModel.getBurnerModelData().createModel()), this,
AirBalloonEntity::hasBurner, e -> {
return getComponentTexture(e.getStackInHand(Hand.MAIN_HAND).isOf(Items.SOUL_LANTERN) ? "soul_burner" : "burner");
}));
addFeature(new BalloonFeature(new AirBalloonEntityModel(AirBalloonEntityModel.getCanopyModelData().createModel()), this, AirBalloonEntity::hasBalloon, e -> getComponentTexture("canopy/" + e.getDesign().asString())));
}, (light, entity) -> entity.isAscending() ? 0xFF00FF : light));
addFeature(new BalloonFeature(new AirBalloonEntityModel(AirBalloonEntityModel.getCanopyModelData().createModel()), this,
AirBalloonEntity::hasBalloon,
e -> getComponentTexture("canopy/" + e.getDesign().asString()),
(light, entity) -> entity.hasBurner() && entity.isAscending() ? light | 0x00005F : light)
);
addFeature(new BalloonFeature(new AirBalloonEntityModel(AirBalloonEntityModel.getSandbagsModelData().createModel()),
this, e -> e.hasBalloon() && e.getInflation(1) >= 1, e -> getComponentTexture("sandbags"),
(light, entity) -> entity.hasBurner() && entity.isAscending() ? light | 0x00003F : light));
}
@Override
@ -33,9 +42,9 @@ public class AirBalloonEntityRenderer extends MobEntityRenderer<AirBalloonEntity
super.render(entity, yaw, tickDelta, matrices, vertices, light);
if (MinecraftClient.getInstance().getEntityRenderDispatcher().shouldRenderHitboxes() && !entity.isInvisible() && !MinecraftClient.getInstance().hasReducedDebugInfo()) {
for (Box box : entity.getBoundingBoxes()) {
WorldRenderer.drawBox(matrices, vertices.getBuffer(RenderLayer.getLines()), box.offset(entity.getPos().multiply(-1)), 1.0f, 1.0f, 1.0f, 1.0f);
}
MultiBox.forEach(entity.getBoundingBox(), box -> {
WorldRenderer.drawBox(matrices, vertices.getBuffer(RenderLayer.getLines()), box.offset(entity.getPos().multiply(-1)), 1, 1, 1, 1);
});
}
}
@ -57,22 +66,30 @@ public class AirBalloonEntityRenderer extends MobEntityRenderer<AirBalloonEntity
private final AirBalloonEntityModel model;
private final Predicate<AirBalloonEntity> visibilityTest;
private final Function<AirBalloonEntity, Identifier> textureFunc;
private final BiFunction<Integer, AirBalloonEntity, Integer> lightFunc;
public BalloonFeature(AirBalloonEntityModel model,
FeatureRendererContext<AirBalloonEntity, AirBalloonEntityModel> context,
Predicate<AirBalloonEntity> visibilityTest,
Function<AirBalloonEntity, Identifier> textureFunc) {
Function<AirBalloonEntity, Identifier> textureFunc,
BiFunction<Integer, AirBalloonEntity, Integer> lightFunc) {
super(context);
this.model = model;
this.visibilityTest = visibilityTest;
this.textureFunc = textureFunc;
this.lightFunc = lightFunc;
}
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertices, int light, AirBalloonEntity entity,
float limbAngle, float limbDistance, float tickDelta, float animationProgress, float yaw, float pitch) {
if (visibilityTest.test(entity)) {
render(getModel(), model, textureFunc.apply(entity), matrices, vertices, light, entity, limbAngle, limbDistance, 0, yaw, pitch, tickDelta, 1, 1, 1);
Identifier texture = textureFunc.apply(entity);
var model = this.model;
if (texture.getPath().indexOf("sandbags") != -1) {
model = new AirBalloonEntityModel(AirBalloonEntityModel.getSandbagsModelData().createModel());
}
render(getModel(), model, texture, matrices, vertices, lightFunc.apply(light, entity), entity, limbAngle, limbDistance, 0, yaw, pitch, tickDelta, 1, 1, 1);
}
}
}

View file

@ -14,6 +14,7 @@ import net.minecraft.block.ShapeContext;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.MovementType;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
@ -124,6 +125,9 @@ public class Transportation<T extends LivingEntity> implements Tickable {
@Nullable
private Box getVehicleBox() {
if (!EntityPredicates.EXCEPT_SPECTATOR.test(living.asEntity())) {
return null;
}
if (vehicle == null) {
return null;
}

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.entity.collision;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -24,6 +25,12 @@ public final class MultiBox extends Box {
return box instanceof MultiBox m ? m.first : box;
}
public static void forEach(Box box, Consumer<Box> consumer) {
if (box instanceof MultiBox m) {
m.children.forEach(consumer);
}
}
private MultiBox(Box first, BoxChildren children) {
super(first.minX, first.minY, first.minZ, first.maxX, first.maxY, first.maxZ);
this.first = unbox(first);
@ -148,6 +155,12 @@ public final class MultiBox extends Box {
return false;
}
public void forEach(Consumer<Box> consumer) {
for (int i = 0; i < children.length; i++) {
consumer.accept(children[i]);
}
}
@Override
public String toString() {
return toString.get();

View file

@ -39,10 +39,10 @@ import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds;
@ -65,6 +65,8 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
private static final TrackedData<String> BASKET_TYPE = DataTracker.registerData(AirBalloonEntity.class, TrackedDataHandlerRegistry.STRING);
private static final TrackedData<Integer> BALLOON_DESIGN = DataTracker.registerData(AirBalloonEntity.class, TrackedDataHandlerRegistry.INTEGER);
public static final byte STATUS_BURNER_INTERACT = (byte)105;
private static final Predicate<Entity> RIDER_PREDICATE = EntityPredicates.EXCEPT_SPECTATOR.and(e -> {
return !(e instanceof PlayerEntity p && p.getAbilities().flying);
});
@ -73,9 +75,17 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
private int prevInflation;
private Vec3d manualVelocity = Vec3d.ZERO;
private int maxFuel = 100;
private int maxFuel = 10000;
private int fuel;
private final Animatable[] sandbags = IntStream.range(0, 5).mapToObj(Animatable::new).toArray(Animatable[]::new);
private final Animatable burner = new Animatable(5);
private double prevXDelta;
private double xDelta;
private double prevZDelta;
private double zDelta;
public AirBalloonEntity(EntityType<? extends AirBalloonEntity> type, World world) {
super(type, world);
intersectionChecked = true;
@ -108,12 +118,20 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
dataTracker.set(BALLOON_DESIGN, design.ordinal());
}
public Animatable getSandbag(int index) {
return sandbags[MathHelper.clamp(index, 0, sandbags.length - 1)];
}
public Animatable getBurner() {
return burner;
}
public boolean hasBalloon() {
return getDesign() != BalloonDesign.NONE;
}
public boolean hasBurner() {
return !getStackInHand(Hand.MAIN_HAND).isEmpty();
return getHandItems() != null && !getStackInHand(Hand.MAIN_HAND).isEmpty();
}
public float getInflation(float tickDelta) {
@ -152,6 +170,14 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
return hasBalloon() && hasBurner() && getInflation() >= getMaxInflation();
}
public float getXVelocity(float tickDelta) {
return (float)MathHelper.lerp(tickDelta, prevXDelta, xDelta);
}
public float getZVelocity(float tickDelta) {
return (float)MathHelper.lerp(tickDelta, prevZDelta, zDelta);
}
@Override
public void tick() {
setAir(getMaxAir());
@ -179,8 +205,8 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
setInflation(inflation);
}
if (fuel > -6 && age % 60 == 0) {
fuel -= boosting ? 10 : 1;
if (fuel > -6 && age % 2 == 0) {
fuel -= boosting ? 50 : 1;
if (fuel <= -6) {
setBoostTicks(0);
setAscending(false);
@ -262,15 +288,26 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
setFireTicks(1);
}
for (Animatable bag : sandbags) {
bag.tick();
}
burner.tick();
super.tick();
prevXDelta = xDelta;
prevZDelta = zDelta;
xDelta = getX() - prevX;
zDelta = getZ() - prevZ;
}
@Override
public ActionResult interactAt(PlayerEntity player, Vec3d hitPos, Hand hand) {
ItemStack stack = player.getStackInHand(hand);
if (hitPos.y > (3 * getInflation(1))) {
if (hasBalloon() && hasBurner()) {
if (hasBalloon() && hasBurner()) {
if (getBurnerBoundingBox().expand(0.7).contains(getPos().add(hitPos))) {
if (stack.isOf(Items.FLINT_AND_STEEL)) {
setAscending(!isAscending());
if (isAscending()) {
@ -281,31 +318,45 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
if (!player.isSneaky()) {
getWorld().emitGameEvent(player, GameEvent.ENTITY_INTERACT, getBlockPos());
}
return ActionResult.SUCCESS;
}
if (stack.isEmpty() && Math.abs(hitPos.x) > 1 && Math.abs(hitPos.z) > 1) {
double xPush = Math.signum(hitPos.x);
double zPush = Math.signum(hitPos.z);
if (!getWorld().isClient) {
manualVelocity = manualVelocity.add(0.3 * xPush, 0, 0.3 * zPush);
}
getWorld().playSound(null, getX() + hitPos.getX(), getY() + hitPos.getY(), getZ() + hitPos.getZ(), USounds.Vanilla.ENTITY_LEASH_KNOT_PLACE, getSoundCategory(), 1, 1);
if (!player.isSneaky()) {
getWorld().emitGameEvent(player, GameEvent.ENTITY_INTERACT, getBlockPos());
}
burner.setPulling();
return ActionResult.SUCCESS;
}
if (stack.isEmpty() && isAscending()) {
setBoostTicks(50);
playSound(USounds.ENTITY_HOT_AIR_BALLOON_BOOST, 1, 1);
burner.setPulling();
if (!player.isSneaky()) {
getWorld().emitGameEvent(player, GameEvent.ENTITY_INTERACT, getBlockPos());
}
return ActionResult.SUCCESS;
}
}
if (getInflation(1) >= 1) {
int xPush = (int)Math.signum(hitPos.x);
int zPush = (int)Math.signum(hitPos.z);
Vec3d absHitPos = getPos().add(hitPos);
if (stack.isEmpty() && MultiBox.unbox(getBoundingBox()).expand(0.5, 1, 0.5).offset(2 * xPush, 3, 2 * zPush).contains(absHitPos)) {
if (!getWorld().isClient) {
manualVelocity = manualVelocity.add(1.7 * xPush, 0, 1.7 * zPush);
}
getWorld().playSound(null, getX() + hitPos.getX(), getY() + hitPos.getY(), getZ() + hitPos.getZ(), USounds.Vanilla.ENTITY_LEASH_KNOT_PLACE, getSoundCategory(), 1, 1);
if (!player.isSneaky()) {
getWorld().emitGameEvent(player, GameEvent.ENTITY_INTERACT, getBlockPos());
}
Vec3d interactCoordinate = new Vec3d(xPush, 0, zPush)
.rotateY((180 + getHorizontalFacing().asRotation()) * MathHelper.RADIANS_PER_DEGREE)
;
getSandbag(MathHelper.clamp((int)interactCoordinate.getX(), 0, 1) + MathHelper.clamp((int)interactCoordinate.getZ(), 0, 1) * 2).setPulling();
return ActionResult.SUCCESS;
}
}
}
return ActionResult.PASS;
@ -368,6 +419,7 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
if (!player.getAbilities().creativeMode) {
stack.decrement(1);
}
burner.setPulling();
playSound(USounds.Vanilla.ENTITY_VILLAGER_YES, 1, 1);
return ActionResult.SUCCESS;
}
@ -486,7 +538,26 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
@Override
protected Box calculateBoundingBox() {
return MultiBox.of(super.calculateBoundingBox(), getBoundingBoxes());
List<Box> boxes = getBoundingBoxes();
Box box = super.calculateBoundingBox();
if (hasBalloon() && getInflation(1) > 0.999F) {
double horScale = -0.5;
// x+ z+
boxes.add(box.expand(horScale, 1, horScale).offset(2, 3, 2));
// x- z+
boxes.add(box.expand(horScale, 1, horScale).offset(-2, 3, 2));
// x+ z-
boxes.add(box.expand(horScale, 1, horScale).offset(2, 3, -2));
// x- z-
boxes.add(box.expand(horScale, 1, horScale).offset(-2, 3, -2));
}
if (hasBurner()) {
boxes.add(getBurnerBoundingBox());
}
return MultiBox.of(box, boxes);
}
@Override
@ -509,6 +580,14 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
.expand(2.25, 3.7 * inflation, 2.25);
}
protected Box getBurnerBoundingBox() {
float inflation = getInflation(1);
float horScale = -0.9F;
return MultiBox.unbox(getBoundingBox())
.offset(0, 2.6F * inflation + 0.4F, 0)
.expand(horScale, 0.4, horScale);
}
@Override
public List<Box> getGravityZoneBoxes() {
Box balloon = getBalloonBoundingBox().expand(0.001);
@ -528,29 +607,55 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
public List<Box> getBoundingBoxes() {
List<Box> boxes = new ArrayList<>();
Box box = getInteriorBoundingBox();
Box mainBox = MultiBox.unbox(getBoundingBox());
boxes.add(box);
double wallheight = box.maxY + 0.72;
double wallThickness = 0.2;
double wallThickness = 0.3;
double halfDoorWidth = 0.5;
if (!getBasketType().isOf(BoatEntity.Type.BAMBOO)) {
// front left (next to door)
boxes.add(new Box(box.minX, box.minY, box.minZ, box.minX + wallThickness + 0.4, wallheight, box.minZ + wallThickness));
boxes.add(new Box(mainBox.minX, mainBox.minY, mainBox.minZ, mainBox.minX + wallThickness + halfDoorWidth, wallheight, box.minZ + wallThickness));
// front right (next to door)
boxes.add(new Box(box.maxX - wallThickness - 0.4, box.minY, box.minZ, box.maxX, wallheight, box.minZ + wallThickness));
boxes.add(new Box(mainBox.maxX - wallThickness - halfDoorWidth, mainBox.minY, mainBox.minZ, mainBox.maxX, wallheight, box.minZ + wallThickness));
// back
boxes.add(new Box(box.minX, box.minY, box.maxZ - wallThickness, box.maxX, wallheight, box.maxZ));
boxes.add(new Box(mainBox.minX, mainBox.minY, box.maxZ - wallThickness, mainBox.maxX, wallheight, mainBox.maxZ));
// left
boxes.add(new Box(box.maxX - wallThickness, box.minY, box.minZ, box.maxX, wallheight, box.maxZ));
boxes.add(new Box(box.maxX - wallThickness, mainBox.minY, mainBox.minZ, mainBox.maxX, wallheight, mainBox.maxZ));
// right
boxes.add(new Box(box.minX, box.minY, box.minZ, box.minX + wallThickness, wallheight, box.maxZ));
boxes.add(new Box(mainBox.minX, mainBox.minY, mainBox.minZ, box.minX + wallThickness, wallheight, mainBox.maxZ));
}
if (hasBalloon() && getInflation(1) > 0.999F) {
boxes.add(getBalloonBoundingBox());
if (hasBalloon() && getInflation(1) >= 1) {
Box balloonBox = getBalloonBoundingBox();
boxes.add(balloonBox.withMinY(balloonBox.maxY - 0.5));
boxes.add(balloonBox.withMaxX(balloonBox.minX + 0.5));
boxes.add(balloonBox.withMinX(balloonBox.maxX - 0.5));
boxes.add(balloonBox.withMaxZ(balloonBox.minZ + 0.5));
boxes.add(balloonBox.withMinZ(balloonBox.maxZ - 0.5));
boxes.add(balloonBox.withMaxX(balloonBox.minX + 2).withMaxY(balloonBox.minY + 0.2));
boxes.add(balloonBox.withMinX(balloonBox.maxX - 2).withMaxY(balloonBox.minY + 0.2));
boxes.add(balloonBox.withMaxZ(balloonBox.minZ + 2).withMaxY(balloonBox.minY + 0.2));
boxes.add(balloonBox.withMinZ(balloonBox.maxZ - 2).withMaxY(balloonBox.minY + 0.2));
}
float yaw = (180 - getHorizontalFacing().asRotation()) * MathHelper.RADIANS_PER_DEGREE;
if (yaw != 0) {
Vec3d center = getPos();
for (int i = 0; i < boxes.size(); i++) {
Box b = boxes.get(i);
Vec3d min = new Vec3d(b.minX, b.minY, b.minZ).subtract(center).rotateY(yaw).add(center);
Vec3d max = new Vec3d(b.maxX, b.maxY, b.maxZ).subtract(center).rotateY(yaw).add(center);
boxes.set(i, new Box(min.x, min.y, min.z, max.x, max.y, max.z));
}
}
return boxes;
}
@ -574,6 +679,10 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
}
private void movePassenger(Entity passenger, Vec3d movement) {
if (!EntityPredicates.EXCEPT_SPECTATOR.test(passenger)) {
return;
}
Living<?> living = Living.living(passenger);
if (living != null) {
if (living.getPhysics().isGravityNegative()) {
@ -625,10 +734,54 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
compound.putInt("fuel", fuel);
}
@Override
public void handleStatus(byte status) {
if (status >= 100 && status < 100 + sandbags.length) {
getSandbag(status % sandbags.length).setPulling();
} else if (status == STATUS_BURNER_INTERACT) {
} else {
super.handleStatus(status);
}
}
static boolean isBetween(double value, double min, double max) {
return value >= min && value <= max;
}
public class Animatable {
private final int id;
private int pullTicks;
private int prevPullTicks;
private boolean pulling;
public Animatable(int id) {
this.id = id;
}
public void setPulling() {
if (!getWorld().isClient) {
getWorld().sendEntityStatus(AirBalloonEntity.this, (byte)(100 + id));
}
pulling = true;
}
public float getPullProgress(float tickDelta) {
return MathHelper.lerp(tickDelta, (float)prevPullTicks, pullTicks) / 6F;
}
public void tick() {
prevPullTicks = pullTicks;
if (pulling && pullTicks < 6) {
pullTicks++;
} else {
pulling = false;
if (pullTicks > 0) {
pullTicks--;
}
}
}
}
@SuppressWarnings("deprecation")
public enum BalloonDesign implements StringIdentifiable {
NONE,

View file

@ -68,7 +68,7 @@ public class BasketItem extends Item implements Dispensable {
}
if (hit.getType() == HitResult.Type.BLOCK) {
return placeEntity(stack, world, hit.getPos().x, hit.getPos().y, hit.getPos().z, user.getYaw() + 180, user);
return placeEntity(stack, world, hit.getPos().x, hit.getPos().y, hit.getPos().z, user.getHorizontalFacing().asRotation(), user);
}
return TypedActionResult.pass(stack);
@ -76,9 +76,11 @@ public class BasketItem extends Item implements Dispensable {
private TypedActionResult<ItemStack> placeEntity(ItemStack stack, World world, double x, double y, double z, float yaw, @Nullable PlayerEntity user) {
AirBalloonEntity entity = UEntities.AIR_BALLOON.create(world);
entity.updatePositionAndAngles(x, y, z, 0, 0);
yaw += 180;
entity.updatePositionAndAngles(x, y, z, yaw, 0);
entity.setHeadYaw(yaw);
entity.setBodyYaw(yaw);
entity.setYaw(yaw);
entity.setBasketType(type);
if (!world.isSpaceEmpty(entity, entity.getBoundingBox())) {
return TypedActionResult.fail(stack);
@ -92,7 +94,6 @@ public class BasketItem extends Item implements Dispensable {
stack.decrement(1);
}
}
return TypedActionResult.success(stack, world.isClient());
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB