More work on hot air balloons

This commit is contained in:
Sollace 2022-09-15 19:53:45 +02:00
parent 2e472a7982
commit 5448db4bdd
8 changed files with 274 additions and 81 deletions

View file

@ -6,6 +6,7 @@ import net.minecraft.client.model.*;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.math.MathHelper;
public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> {
@ -13,13 +14,11 @@ public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> {
private final ModelPart burner;
private final ModelPart balloon;
private final ModelPart basket;
public AirBalloonEntityModel(ModelPart root) {
this.root = root;
burner = root.getChild("burner");
balloon = root.getChild("balloon");
basket = root.getChild("basket");
}
public static TexturedModelData getTexturedModelData() {
@ -27,10 +26,10 @@ public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> {
ModelPartData root = modelData.getRoot();
ModelPartData burner = root.addChild("burner", ModelPartBuilder.create().uv(8, 107).cuboid(-6, -47, -6, 11, 15, 11, Dilation.NONE), ModelTransform.pivot(0, 24, 0));
burner.addChild("rope_d_r1", ModelPartBuilder.create().uv(0, 107).cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of(-5, -46, -6, 0.7854F, 0, -0.7854F));
burner.addChild("rope_c_r1", ModelPartBuilder.create().uv(0, 107).cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of(-4, -44, 3, -0.7854F, 0, -0.7854F));
burner.addChild("rope_b_r1", ModelPartBuilder.create().uv(0, 107).cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of(5, -46, 1, -0.7854F, 0, 0.7854F));
burner.addChild("rope_a_r1", ModelPartBuilder.create().uv(0, 107).cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of(5, -45, -6, 0.7854F, 0, 0.7854F));
burner.addChild("rope_d_r1", ModelPartBuilder.create().uv(0, 107).cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of(-5, -46, -6, 0.7854F, 0, -0.7854F));
burner.addChild("rope_c_r1", ModelPartBuilder.create().uv(0, 107).cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of(-4, -44, 3, -0.7854F, 0, -0.7854F));
burner.addChild("rope_b_r1", ModelPartBuilder.create().uv(0, 107).cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of( 5, -46, 1, -0.7854F, 0, 0.7854F));
burner.addChild("rope_a_r1", ModelPartBuilder.create().uv(0, 107).cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of( 5, -45, -6, 0.7854F, 0, 0.7854F));
ModelPartData balloon = root.addChild("balloon", ModelPartBuilder.create().uv(64, 1).cuboid(-54, -178, -59, 112, 120, 112, Dilation.NONE), ModelTransform.pivot(0, 24, 0));
balloon.addChild("rope_d_r2", ModelPartBuilder.create().uv(0, 107).cuboid(-2, -68, 0, 2, 68, 2, Dilation.NONE), ModelTransform.of(-14, -11, -16, 0.4363F, 0, -0.4363F));
@ -43,16 +42,23 @@ public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> {
.uv(64, 68).cuboid(15, -12, -16, 2, 11, 30, Dilation.NONE)
.uv(80, 38).cuboid(-16, -12, -17, 32, 11, 2, Dilation.NONE)
.uv(0, 32).cuboid(8, -12, 13, 8, 11, 2, Dilation.NONE)
.uv(0, 6).cuboid(-16, -12, 13, 8, 11, 2, Dilation.NONE), ModelTransform.pivot(0, 0, 0));
.uv(0, 6).cuboid(-16, -12, 13, 8, 11, 2, Dilation.NONE), ModelTransform.NONE);
basket.addChild("rim", ModelPartBuilder.create().uv(40, 34).cuboid(-18, -13, -17, 4, 2, 32, Dilation.NONE)
.uv(0, 32).cuboid(14, -13, -17, 4, 2, 32, Dilation.NONE)
.uv(80, 32).cuboid(-17, -13, -18, 34, 2, 4, Dilation.NONE)
.uv(0, 19).cuboid(7, -13, 12, 10, 2, 4, Dilation.NONE)
.uv(0, 0).cuboid(-17, -13, 12, 10, 2, 4, Dilation.NONE), ModelTransform.pivot(0, 0, 0));
.uv(0, 0).cuboid(-17, -13, 12, 10, 2, 4, Dilation.NONE), ModelTransform.NONE);
return TexturedModelData.of(modelData, 512, 512);
}
@Override
public void setAngles(AirBalloonEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
root.yaw = entity.bodyYaw;
burner.visible = entity.hasBurner();
balloon.visible = entity.hasBalloon();
float xSpeed = (float)(entity.getX() - entity.prevX);
root.pitch = MathHelper.sin(-xSpeed);
}
@Override

View file

@ -2,12 +2,12 @@ package com.minelittlepony.unicopia.client.render.entity;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.entity.AirBalloonEntity;
import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.*;
import net.minecraft.util.Identifier;
public class AirBalloonEntityRenderer extends LivingEntityRenderer<AirBalloonEntity, AirBalloonEntityModel> {
private static final Identifier TEXTURE = Unicopia.id("textures/entity/spellbook/normal.png");
public class AirBalloonEntityRenderer extends MobEntityRenderer<AirBalloonEntity, AirBalloonEntityModel> {
private static final Identifier TEXTURE = Unicopia.id("textures/entity/air_balloon.png");
public AirBalloonEntityRenderer(EntityRendererFactory.Context context) {
super(context, new AirBalloonEntityModel(AirBalloonEntityModel.getTexturedModelData().createModel()), 0);

View file

@ -1,13 +1,25 @@
package com.minelittlepony.unicopia.entity;
import net.minecraft.block.ShapeContext;
import net.minecraft.entity.*;
import net.minecraft.entity.data.*;
import net.minecraft.entity.mob.FlyingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.*;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.World;
public class AirBalloonEntity extends FlyingEntity {
import java.util.function.Consumer;
import com.minelittlepony.unicopia.entity.collision.EntityCollisions;
public class AirBalloonEntity extends FlyingEntity implements EntityCollisions.ComplexCollidable {
private static final byte HAS_BALLOON = 1;
private static final byte HAS_BURNER = 2;
private static final byte BURNER_ACTIVE = 4;
private static final TrackedData<Integer> FLAGS = DataTracker.registerData(AirBalloonEntity.class, TrackedDataHandlerRegistry.INTEGER);
public AirBalloonEntity(EntityType<? extends AirBalloonEntity> type, World world) {
super(type, world);
@ -16,50 +28,152 @@ public class AirBalloonEntity extends FlyingEntity {
@Override
protected void initDataTracker() {
super.initDataTracker();
dataTracker.startTracking(FLAGS, 0);
}
public boolean hasBalloon() {
return getFlag(HAS_BALLOON);
}
public void setHasBalloon(boolean hasBalloon) {
setFlag(HAS_BALLOON, hasBalloon);
}
public boolean hasBurner() {
return getFlag((byte)(HAS_BURNER | HAS_BALLOON));
}
public void setHasBurner(boolean hasBurner) {
setFlag(HAS_BURNER, hasBurner);
}
public boolean isBurnerActive() {
return getFlag((byte)(HAS_BURNER | BURNER_ACTIVE));
}
public void setBurnerActive(boolean burnerActive) {
setFlag(BURNER_ACTIVE, burnerActive);
}
private boolean getFlag(byte flag) {
return (dataTracker.get(FLAGS).intValue() & flag) == flag;
}
private void setFlag(byte flag, boolean val) {
int v = dataTracker.get(FLAGS);
dataTracker.set(FLAGS, val ? (v | flag) : (v & ~flag));
}
private boolean isAirworthy() {
return hasBalloon() && (!onGround || isBurnerActive());
}
@Override
public void tick() {
this.setHasBalloon(true);
this.setHasBurner(true);
this.setBurnerActive(false);
if (!world.isClient) {
setAir(getMaxAir());
float xSpeed = 0;//-0.015F * (this.age % 1000 < 500 ? -1 : 1);
addVelocity(xSpeed, 0, 0);
if (isAirworthy()) {
setVelocity(getVelocity()
.add(getWind(world, getBlockPos()))
.normalize()
.multiply(0.2)
.add(0, isBurnerActive() ? 0.09F : isTouchingWater() ? 0.02F : -0.06F, 0));
} else {
addVelocity(0, isTouchingWater() ? 0.02F : -0.02F, 0);
}
if (isLeashed()) {
Vec3d leashPost = getHoldingEntity().getPos();
Vec3d pos = getPos();
for (var e : this.world.getOtherEntities(this, getBoundingBox().expand(0.2, 1.0E-7, 0.2))) {
if (!(e instanceof PlayerEntity)) {
e.setVelocity(e.getVelocity().multiply(0.3).add(getVelocity().multiply(0.84)));
if (leashPost.distanceTo(pos) >= 5) {
Vec3d newVel = leashPost.subtract(pos).multiply(0.01);
setVelocity(newVel.lengthSquared() < 0.03 ? Vec3d.ZERO : newVel);
}
}
double diff = (getBoundingBox().maxY + getVelocity().y) - e.getBoundingBox().minY;
if (age % 20 < 10) {
if (getVelocity().y < 0.1) {
addVelocity(0, 0.01, 0);
}
} else {
if (getVelocity().y > -0.1) {
addVelocity(0, -0.01, 0);
}
}
if (diff > 0) {
e.addVelocity(0, diff, 0);
//setVelocity(Vec3d.ZERO);
float weight = 0;
if (getVelocity().length() > 0) {
Box box = getBoundingBox();
for (var e : this.world.getOtherEntities(this, box.expand(1, 1.0E-7, 1))) {
Vec3d vel = e.getVelocity();
if (getVelocity().y > 0 && box.maxY > e.getBoundingBox().minY) {
e.setPosition(e.getX(), box.maxY + 0.1, e.getZ());
}
if (!(e instanceof PlayerEntity)) {
e.setVelocity(vel.multiply(0.3).add(getVelocity().multiply(0.786)));
}
e.setOnGround(true);
if (horizontalSpeed != 0) {
e.distanceTraveled = 0;
e.horizontalSpeed = 0;
if (e instanceof LivingEntity l) {
l.limbAngle = 0;
l.limbDistance = 0;
}
}
weight++;
}
e.distanceTraveled = 0;
e.horizontalSpeed = 0;
if (e instanceof LivingEntity l) {
l.limbAngle = 0;
l.limbDistance = 0;
Box balloonTopBox = getBoundingBox().offset(0.125, 11, 0).expand(2.25, 0, 2);
for (var e : this.world.getOtherEntities(this, balloonTopBox.expand(1.0E-7))) {
Vec3d vel = e.getVelocity();
double yVel = vel.y + Math.max(balloonTopBox.maxY - e.getBoundingBox().minY, 0);
yVel /= 8;
yVel += 0.3;
e.setVelocity(vel.getX(), yVel, vel.getZ());
e.setVelocity(e.getVelocity().multiply(0.3).add(getVelocity().multiply(0.786)));
e.setOnGround(true);
}
}
if (getVelocity().y > -0.6 && !isTouchingWater()) {
if (isBurnerActive()) {
weight -= 3;
}
addVelocity(0, MathHelper.clamp(-weight / 10F, -1, isLeashed() ? 0.2F : 1), 0);
}
super.tick();
}
@Override
public void onPlayerCollision(PlayerEntity player) {
player.setVelocity(player.getVelocity().multiply(0.9).add(getVelocity().multiply(0.56)));
if (getVelocity().lengthSquared() > 0) {
// player.setVelocity(getVelocity().multiply(1.3));
player.setVelocity(player.getVelocity().multiply(0.3).add(getVelocity().multiply(
getVelocity().y < 0 ? 0.828 : 0.728
)));
double diff = (getBoundingBox().maxY + getVelocity().y) - player.getBoundingBox().minY;
double diff = (getBoundingBox().maxY + getVelocity().y) - player.getBoundingBox().minY;
if (diff > 0) {
player.addVelocity(0, diff, 0);
if (diff > 0) {
player.addVelocity(0, diff, 0);
}
}
}
@ -87,14 +201,53 @@ public class AirBalloonEntity extends FlyingEntity {
return getBoundingBox().expand(30, 100, 30);
}
@Override
public void getCollissionShapes(ShapeContext context, Consumer<VoxelShape> output) {
Box box = getBoundingBox().expand(0.3, 0, 0.3);
double wallheight = box.maxY + 1;
double wallThickness = 0.7;
output.accept(VoxelShapes.cuboid(new Box(box.minX, box.minY, box.minZ, box.minX + wallThickness + 0.2, wallheight, box.minZ + wallThickness)));
output.accept(VoxelShapes.cuboid(new Box(box.maxX - wallThickness - 0.2, box.minY, box.minZ, box.maxX, wallheight, box.minZ + wallThickness)));
output.accept(VoxelShapes.cuboid(new Box(box.minX, box.minY, box.maxZ - wallThickness, box.maxX, wallheight, box.maxZ)));
output.accept(VoxelShapes.cuboid(new Box(box.minX, box.minY, box.minZ, box.minX + wallThickness, wallheight, box.maxZ)));
output.accept(VoxelShapes.cuboid(new Box(box.maxX - wallThickness, box.minY, box.minZ, box.maxX, wallheight, box.maxZ)));
// top of balloon
if (hasBalloon()) {
output.accept(VoxelShapes.cuboid(getBoundingBox().offset(0.125, 11, 0).expand(2.25, 0, 2)));
}
}
@Override
public void readCustomDataFromNbt(NbtCompound compound) {
super.readCustomDataFromNbt(compound);
setHasBalloon(compound.getBoolean("hasBalloon"));
setHasBurner(compound.getBoolean("hasBurner"));
setBurnerActive(compound.getBoolean("burnerActive"));
}
@Override
public void writeCustomDataToNbt(NbtCompound compound) {
super.writeCustomDataToNbt(compound);
compound.putBoolean("hasBalloon", hasBalloon());
compound.putBoolean("hasBurner", hasBurner());
compound.putBoolean("burnerActive", isBurnerActive());
}
static Vec3d getWind(World world, BlockPos pos) {
return Vec3d.ofCenter(pos).normalize().multiply(1, 0, 1).multiply(0.2);//.multiply((world.getRandom().nextFloat() - 0.5) * 0.2);
}
}

View file

@ -49,7 +49,7 @@ public interface UEntities {
.dimensions(EntityDimensions.fixed(0.9F, 0.5F)));
EntityType<AirBalloonEntity> AIR_BALLOON = register("air_balloon", FabricEntityTypeBuilder.create(SpawnGroup.MISC, AirBalloonEntity::new)
.trackRangeBlocks(1000)
.dimensions(EntityDimensions.fixed(3, 0.1F)));
.dimensions(EntityDimensions.fixed(2.5F, 0.1F)));
static <T extends Entity> EntityType<T> register(String name, FabricEntityTypeBuilder<T> builder) {
EntityType<T> type = builder.build();

View file

@ -14,8 +14,8 @@ import com.minelittlepony.unicopia.FlightType;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Owned;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.entity.UEntityAttributes;
import com.minelittlepony.unicopia.entity.collision.EntityCollisions;
import com.minelittlepony.unicopia.entity.player.PlayerDimensions;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.projectile.ProjectileUtil;
@ -42,14 +42,9 @@ import net.minecraft.entity.mob.VexEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.projectile.ShulkerBulletEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.WorldAccess;
public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provider, FlightType.Provider {
public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provider, FlightType.Provider, EntityCollisions.ComplexCollidable {
private static final Optional<Float> BLOCK_HEIGHT = Optional.of(0.5F);
@NotNull
@ -358,43 +353,10 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi
return entityNbt;
}
void getCollissionShapes(ShapeContext context, Consumer<VoxelShape> output) {
getCollissionShapes(getAppearance(), context, output);
getAttachments().forEach(e -> getCollissionShapes(e, context, output));
@Override
public void getCollissionShapes(ShapeContext context, Consumer<VoxelShape> output) {
EntityCollisions.getCollissionShapes(getAppearance(), context, output);
getAttachments().forEach(e -> EntityCollisions.getCollissionShapes(e, context, output));
}
private static void getCollissionShapes(@Nullable Entity entity, ShapeContext context, Consumer<VoxelShape> output) {
if (entity == null) {
return;
}
if (entity.isCollidable()) {
output.accept(VoxelShapes.cuboid(entity.getBoundingBox()));
} else if (entity instanceof FallingBlockEntity) {
BlockPos pos = entity.getBlockPos();
output.accept(((FallingBlockEntity) entity).getBlockState()
.getCollisionShape(entity.world, entity.getBlockPos(), context)
.offset(pos.getX(), pos.getY(), pos.getZ())
);
}
}
public static List<VoxelShape> getColissonShapes(@Nullable Entity entity, WorldAccess world, Box box) {
List<VoxelShape> shapes = new ArrayList<>();
ShapeContext ctx = entity == null ? ShapeContext.absent() : ShapeContext.of(entity);
VoxelShape entityShape = VoxelShapes.cuboid(box.expand(1.0E-6D));
world.getOtherEntities(entity, box.expand(0.5), e -> {
Caster.of(e).flatMap(c -> c.getSpellSlot().get(SpellPredicate.IS_DISGUISE, false)).ifPresent(p -> {
p.getDisguise().getCollissionShapes(ctx, shape -> {
if (!shape.isEmpty() && VoxelShapes.matchesAnywhere(shape, entityShape, BooleanBiFunction.AND)) {
shapes.add(shape);
}
});
});
return false;
});
return shapes;
}
}

View file

@ -0,0 +1,72 @@
package com.minelittlepony.unicopia.entity.collision;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import net.minecraft.block.ShapeContext;
import net.minecraft.entity.Entity;
import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.WorldAccess;
public class EntityCollisions {
public static void getCollissionShapes(@Nullable Entity entity, ShapeContext context, Consumer<VoxelShape> output) {
if (entity == null) {
return;
}
if (entity.isCollidable()) {
output.accept(VoxelShapes.cuboid(entity.getBoundingBox()));
if (entity instanceof ComplexCollidable collidable) {
collidable.getCollissionShapes(context, output);
}
} else if (entity instanceof FallingBlockEntity) {
BlockPos pos = entity.getBlockPos();
output.accept(((FallingBlockEntity) entity).getBlockState()
.getCollisionShape(entity.world, entity.getBlockPos(), context)
.offset(pos.getX(), pos.getY(), pos.getZ())
);
}
}
public static List<VoxelShape> getColissonShapes(@Nullable Entity entity, WorldAccess world, Box box) {
ShapeContext ctx = entity == null ? ShapeContext.absent() : ShapeContext.of(entity);
return collectCollisionBoxes(box, collector -> {
world.getOtherEntities(entity, box.expand(50), e -> {
Caster.of(e).flatMap(c -> c.getSpellSlot().get(SpellPredicate.IS_DISGUISE, false)).ifPresent(p -> {
p.getDisguise().getCollissionShapes(ctx, collector);
});
if (e instanceof ComplexCollidable collidable) {
collidable.getCollissionShapes(ctx, collector);
}
return false;
});
});
}
static List<VoxelShape> collectCollisionBoxes(Box box, Consumer<Consumer<VoxelShape>> generator) {
List<VoxelShape> shapes = new ArrayList<>();
VoxelShape entityShape = VoxelShapes.cuboid(box.expand(1.0E-6D));
generator.accept(shape -> {
if (!shape.isEmpty() && VoxelShapes.matchesAnywhere(shape, entityShape, BooleanBiFunction.AND)) {
shapes.add(shape);
}
});
return shapes;
}
public interface ComplexCollidable {
void getCollissionShapes(ShapeContext context, Consumer<VoxelShape> output);
}
}

View file

@ -14,7 +14,7 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.minelittlepony.unicopia.BlockDestructionManager;
import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance;
import com.minelittlepony.unicopia.entity.collision.EntityCollisions;
import com.minelittlepony.unicopia.entity.duck.RotatedView;
import net.minecraft.block.BlockState;
@ -51,7 +51,7 @@ abstract class MixinWorld implements WorldAccess, BlockDestructionManager.Source
@Override
public List<VoxelShape> getEntityCollisions(@Nullable Entity entity, Box box) {
if (box.getAverageSideLength() >= 1.0E-7D) {
List<VoxelShape> shapes = EntityAppearance.getColissonShapes(entity, this, box);
List<VoxelShape> shapes = EntityCollisions.getColissonShapes(entity, this, box);
if (!shapes.isEmpty()) {
return Stream.concat(shapes.stream(), WorldAccess.super.getEntityCollisions(entity, box).stream()).toList();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB