Fix janky hot air balloon physics

This commit is contained in:
Sollace 2024-03-28 12:08:06 +00:00
parent 93d11531d6
commit fbe444b56c
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
6 changed files with 313 additions and 233 deletions

View file

@ -167,8 +167,6 @@ public class WorldRenderDelegate {
return true;
}
pony.updateSupportingEntity();
matrices.push();
Entity owner = pony.asEntity();

View file

@ -20,7 +20,6 @@ import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate;
import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance;
import com.minelittlepony.unicopia.entity.behaviour.Guest;
import com.minelittlepony.unicopia.entity.collision.MultiBoundingBoxEntity;
import com.minelittlepony.unicopia.entity.damage.MagicalDamageSource;
import com.minelittlepony.unicopia.entity.duck.LivingEntityDuck;
import com.minelittlepony.unicopia.entity.effect.CorruptInfluenceStatusEffect;
@ -67,7 +66,6 @@ import net.minecraft.sound.SoundCategory;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
@ -88,14 +86,6 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
private boolean invisible = false;
@Nullable
private Entity supportingEntity;
@Nullable
private Vec3d supportPositionOffset;
private int ticksOutsideVehicle;
private int ticksInVehicle;
@Nullable
private Caster<?> attacker;
@Nullable
@ -109,6 +99,8 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
private final Enchantments enchants = addTicker(new Enchantments(this));
private final ItemTracker armour = addTicker(new ItemTracker(this));
//private final Transportation<T> transportation = new Transportation<>(this);
private final Transportation<T> transportation = new Transportation<>(this);
protected Living(T entity, TrackedData<NbtCompound> effect) {
this.entity = entity;
@ -171,6 +163,10 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
return armour;
}
public Transportation<T> getTransportation() {
return transportation;
}
@Override
public final T asEntity() {
return entity;
@ -202,73 +198,6 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
return vehicle != null && getCarrierId().filter(vehicle.getUuid()::equals).isPresent();
}
public boolean setSupportingEntity(@Nullable Entity supportingEntity) {
this.supportingEntity = supportingEntity;
if (supportingEntity != null) {
ticksOutsideVehicle = 0;
}
return true;
}
@Nullable
public Entity getSupportingEntity() {
return supportingEntity;
}
public int getTicksInVehicle() {
return ticksInVehicle;
}
public void setPositionOffset(@Nullable Vec3d positionOffset) {
this.supportPositionOffset = positionOffset;
}
public void updatePositionOffset() {
setPositionOffset(supportingEntity == null ? null : entity.getPos().subtract(supportingEntity.getPos()));
}
public void updateRelativePosition(Box box) {
if (supportingEntity == null || supportPositionOffset == null) {
return;
}
if (getPhysics().isFlying()) {
return;
}
Vec3d newPos = supportingEntity.getPos().add(supportPositionOffset);
Vec3d posChange = entity.getPos().subtract(newPos);
entity.setPosition(newPos);
if (isClient()) {
Vec3d newServerPos = LivingEntityDuck.serverPos(entity);
if (newServerPos.lengthSquared() != 0) {
newServerPos = newServerPos.subtract(posChange);
entity.updateTrackedPositionAndAngles(
newServerPos.x, newServerPos.y, newServerPos.z,
entity.getYaw(), entity.getPitch(), 3, true);
}
} else {
entity.updateTrackedPosition(newPos.x, newPos.y, newPos.z);
}
if (!(entity instanceof PlayerEntity)) {
entity.lastRenderX = supportingEntity.lastRenderX + supportPositionOffset.x;
entity.lastRenderY = supportingEntity.lastRenderY + supportPositionOffset.y;
entity.lastRenderZ = supportingEntity.lastRenderZ + supportPositionOffset.z;
if (entity.getVelocity().length() < 0.1) {
LimbAnimationUtil.resetToZero(entity.limbAnimator);
}
}
entity.horizontalSpeed = 0;
entity.prevHorizontalSpeed = 0;
entity.speed = 0;
entity.setOnGround(true);
entity.verticalCollision = true;
entity.groundCollision = true;
entity.fallDistance = 0;
}
@Override
public boolean beforeUpdate() {
if (EffectUtils.getAmplifier(entity, UEffects.PARALYSIS) > 1 && entity.getVelocity().horizontalLengthSquared() > 0) {
@ -276,44 +205,10 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
updateVelocity();
}
updateSupportingEntity();
//transportation.updateSupportingEntity();
return false;
}
public void updateSupportingEntity() {
if (supportingEntity != null) {
Box ownBox = entity.getBoundingBox()
.stretch(entity.getVelocity())
.expand(0.1, 0.5, 0.1)
.stretch(supportingEntity.getVelocity().multiply(-2));
MultiBoundingBoxEntity.getBoundingBoxes(supportingEntity).stream()
.filter(box -> box.stretch(supportingEntity.getVelocity()).expand(0, 0.5, 0).intersects(ownBox))
.findFirst()
.ifPresentOrElse(box -> {
ticksOutsideVehicle = 0;
if (supportPositionOffset == null) {
updatePositionOffset();
} else {
updateRelativePosition(box);
}
entity.setOnGround(true);
entity.verticalCollision = true;
entity.groundCollision = true;
}, () -> {
// Rubberband passengers to try and prevent players falling out when the velocity changes suddenly
if (ticksOutsideVehicle++ > 30) {
supportingEntity = null;
supportPositionOffset = null;
Unicopia.LOGGER.info("Entity left vehicle");
} else {
supportPositionOffset = supportPositionOffset.multiply(0.25, 1, 0.25);
}
});
}
}
@Override
public void tick() {
tickers.forEach(Tickable::tick);
@ -358,13 +253,7 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
updateDragonBreath();
if (ticksOutsideVehicle == 0) {
updatePositionOffset();
ticksInVehicle++;
} else {
ticksInVehicle = 0;
}
transportation.tick();
}
public void updateAttributeModifier(UUID id, EntityAttribute attribute, float desiredValue, Float2ObjectFunction<EntityAttributeModifier> modifierSupplier, boolean permanent) {

View file

@ -0,0 +1,141 @@
package com.minelittlepony.unicopia.entity;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.entity.collision.MultiBoundingBoxEntity;
import com.minelittlepony.unicopia.entity.duck.EntityDuck;
import com.minelittlepony.unicopia.entity.mob.AirBalloonEntity;
import com.minelittlepony.unicopia.util.Tickable;
import net.minecraft.block.ShapeContext;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.MovementType;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.event.GameEvent;
public class Transportation<T extends LivingEntity> implements Tickable {
private final Living<T> living;
@Nullable
private MultiBoundingBoxEntity vehicle;
@Nullable
private Entity vehicleEntity;
@Nullable
private Box vehicleBox;
private int ticksInVehicle;
private Vec3d lastVehiclePosition = Vec3d.ZERO;
Transportation(Living<T> living) {
this.living = living;
}
public <E extends Entity & MultiBoundingBoxEntity> void setVehicle(@Nullable E vehicle) {
this.vehicle = vehicle;
this.vehicleEntity = vehicle;
updatePreviousPosition();
}
@Override
public void tick() {
if (vehicle != null) {
ticksInVehicle++;
} else {
ticksInVehicle = 0;
}
if (ticksInVehicle > 20 && vehicle instanceof AirBalloonEntity) {
UCriteria.RIDE_BALLOON.trigger(living.asEntity());
}
}
public void updatePreviousPosition() {
vehicleBox = getVehicleBox();
lastVehiclePosition = vehicleEntity == null ? Vec3d.ZERO : vehicleEntity.getPos();
Entity entity = living.asEntity();
if (vehicleBox != null && living.asEntity().getBoundingBox().intersects(vehicleBox.expand(0.001, 0.5001, 0.001))) {
entity.setOnGround(true);
entity.onLanding();
entity.verticalCollision = true;
entity.groundCollision = true;
entity.velocityDirty = true;
entity.velocityModified = true;
}
}
public void onMove(MovementType movementType) {
if (vehicleBox == null || vehicleEntity == null) {
return;
}
Entity entity = living.asEntity();
Box passengerBox = entity.getBoundingBox().expand(0.001);
Vec3d vehicleMovement = vehicleEntity.getPos().subtract(lastVehiclePosition);
List<VoxelShape> shapes = new ArrayList<>();
vehicle.getCollissionShapes(ShapeContext.of(entity), shapes::add);
vehicleMovement = vehicleMovement.add(vehicleEntity.getVelocity());
vehicleMovement = Entity.adjustMovementForCollisions(entity, vehicleMovement, passengerBox, entity.getWorld(), shapes);
Vec3d newPos = entity.getPos().add(vehicleMovement);
if (!vehicleEntity.isOnGround()) {
// surface check to prevent the player from floating
if (newPos.getY() > vehicleBox.minY + 0.1 || newPos.getY() < vehicleBox.minY + 0.1) {
newPos = new Vec3d(newPos.getX(), vehicleBox.minY + 0.01, newPos.getZ());
}
// containment checks to prevent the player from falling out of the basket when in flight
if (newPos.getY() < vehicleEntity.getPos().getY() + 3) {
double maxDeviation = 0.1;
double z = MathHelper.clamp(newPos.getZ(), vehicleBox.minZ + maxDeviation, vehicleBox.maxZ - maxDeviation);
double x = MathHelper.clamp(newPos.getX(), vehicleBox.minX + maxDeviation, vehicleBox.maxX - maxDeviation);
newPos = new Vec3d(x, newPos.getY(), z);
}
entity.setPosition(newPos);
entity.updateTrackedPosition(newPos.x, newPos.y, newPos.z);
entity.setVelocity(Vec3d.ZERO);
}
entity.setOnGround(true);
entity.onLanding();
entity.verticalCollision = true;
entity.groundCollision = true;
if (entity.distanceTraveled > ((EntityDuck)entity).getNextStepSoundDistance()) {
entity.distanceTraveled -= 0.5;
entity.playSound(vehicle.getWalkedOnSound(entity.getY()), 0.5F, 1);
if (!entity.isSneaky()) {
entity.getWorld().emitGameEvent(entity, GameEvent.STEP, entity.getBlockPos());
}
}
}
@Nullable
private Box getVehicleBox() {
if (vehicle == null) {
return null;
}
Box entityBox = living.asEntity().getBoundingBox().stretch(living.asEntity().getVelocity());
for (Box box : vehicle.getGravityZoneBoxes()) {
if (entityBox.intersects(box.expand(0.001).stretch(vehicleEntity.getVelocity().multiply(1)))) {
return box;
}
}
setVehicle(null);
return null;
}
}

View file

@ -1,13 +1,37 @@
package com.minelittlepony.unicopia.entity.collision;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;
import com.minelittlepony.unicopia.entity.collision.EntityCollisions.ComplexCollidable;
import net.minecraft.block.ShapeContext;
import net.minecraft.entity.Entity;
import net.minecraft.sound.SoundEvent;
import net.minecraft.util.math.Box;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
public interface MultiBoundingBoxEntity {
public interface MultiBoundingBoxEntity extends ComplexCollidable {
List<Box> getBoundingBoxes();
default List<Box> getGravityZoneBoxes() {
return getBoundingBoxes();
}
Map<Box, List<Entity>> getCollidingEntities(Stream<Box> boundingBoxes);
SoundEvent getWalkedOnSound(double y);
@Override
default void getCollissionShapes(ShapeContext context, Consumer<VoxelShape> output) {
for (Box box : getBoundingBoxes()) {
output.accept(VoxelShapes.cuboid(box));
}
}
static List<Box> getBoundingBoxes(Entity entity) {
return entity instanceof MultiBoundingBoxEntity multi ? multi.getBoundingBoxes() : List.of(entity.getBoundingBox());
}

View file

@ -13,7 +13,9 @@ import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.registry.RegistryKey;
import net.minecraft.sound.SoundEvent;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
@ -22,20 +24,22 @@ import net.minecraft.util.function.ValueLists;
import net.minecraft.util.math.*;
import net.minecraft.util.math.random.Random;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquineContext;
@ -47,7 +51,6 @@ import com.minelittlepony.unicopia.entity.MagicImmune;
import com.minelittlepony.unicopia.entity.collision.EntityCollisions;
import com.minelittlepony.unicopia.entity.collision.MultiBoundingBoxEntity;
import com.minelittlepony.unicopia.entity.collision.MultiBox;
import com.minelittlepony.unicopia.entity.duck.EntityDuck;
import com.minelittlepony.unicopia.item.BasketItem;
import com.minelittlepony.unicopia.item.HotAirBalloonItem;
import com.minelittlepony.unicopia.item.UItems;
@ -61,13 +64,14 @@ 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);
private static final Predicate<Entity> RIDER_PREDICATE = EntityPredicates.EXCEPT_SPECTATOR.and(e -> {
return !(e instanceof PlayerEntity p && p.getAbilities().flying);
});
private boolean prevBoosting;
private int prevInflation;
private Vec3d oldPosition = Vec3d.ZERO;
private Vec3d manualVelocity = Vec3d.ZERO;
private int ticksFlying;
public AirBalloonEntity(EntityType<? extends AirBalloonEntity> type, World world) {
super(type, world);
intersectionChecked = true;
@ -80,12 +84,12 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
dataTracker.startTracking(ASCENDING, false);
dataTracker.startTracking(BOOSTING, 0);
dataTracker.startTracking(INFLATION, 0);
dataTracker.startTracking(BASKET_TYPE, "");
dataTracker.startTracking(BASKET_TYPE, BasketType.DEFAULT.id().toString());
dataTracker.startTracking(BALLOON_DESIGN, 0);
}
public BasketType getBasketType() {
return BasketType.REGISTRY.get(Identifier.tryParse(dataTracker.get(BASKET_TYPE)));
return BasketType.of(dataTracker.get(BASKET_TYPE));
}
public void setBasketType(BasketType type) {
@ -144,14 +148,6 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
return hasBalloon() && hasBurner() && getInflation() >= getMaxInflation();
}
@Override
public List<Box> getBoundingBoxes() {
if (hasBalloon() && getInflation(1) > 0.999F) {
return List.of(getInteriorBoundingBox(), getBalloonBoundingBox());
}
return List.of(getInteriorBoundingBox());
}
@Override
public void tick() {
setAir(getMaxAir());
@ -248,86 +244,12 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
}
prevBoosting = boosting;
oldPosition = getPos();
if (getFireTicks() > 0) {
setFireTicks(1);
}
if (!isOnGround() && (isAirworthy() || isSubmergedInWater() || isLeashed())) {
ticksFlying++;
} else {
ticksFlying = 0;
}
updatePassengers(false);
super.tick();
setBoundingBox(MultiBox.of(getBoundingBox(), getBoundingBoxes()));
}
private void updatePassengers(boolean move) {
Set<Entity> alreadyTicked = new HashSet<>();
for (Box box : getBoundingBoxes()) {
for (Entity e : getWorld().getOtherEntities(this, box.stretch(getVelocity().multiply(-1)).expand(0, 0.5, 0))) {
if (e instanceof PlayerEntity p && p.getAbilities().flying) {
continue;
}
if (!alreadyTicked.add(e)) {
continue;
}
updatePassenger(e, box, e.getY() > getY() + 3);
}
}
}
private void updatePassenger(Entity e, Box box, boolean inBalloon) {
if (e instanceof AirBalloonEntity) {
return;
}
if (ticksFlying > 0) {
if (Living.getOrEmpty(e).filter(living -> !living.setSupportingEntity(this)).isPresent()) {
return;
}
Vec3d vel = getVelocity();
double height = box.getYLength();
if (height < 3 || e.getBoundingBox().minY > box.minY + height / 2D) {
if (vel.y > 0 && e.getBoundingBox().minY < box.maxY + 0.02) {
e.setPos(e.getX(), box.maxY, e.getZ());
e.setOnGround(true);
}
if (vel.y < 0 && e.getBoundingBox().minY > box.maxY) {
e.setPos(e.getX(), box.maxY, e.getZ());
e.setOnGround(true);
}
}
Living.getOrEmpty(e).ifPresent(living -> {
living.setPositionOffset(e.getPos().subtract(oldPosition));
living.updateRelativePosition(box);
if (ticksFlying > 20 && living.getTicksInVehicle() > 20) {
UCriteria.RIDE_BALLOON.trigger(e);
}
});
}
if (getWorld().isClient) {
if (e.distanceTraveled > ((EntityDuck)e).getNextStepSoundDistance()) {
e.distanceTraveled--;
e.playSound(inBalloon ? USounds.ENTITY_HOT_AIR_BALLOON_STEP : USounds.ENTITY_HOT_AIR_BALLOON_BASKET_STEP, 0.5F, 1);
if (!e.isSneaky()) {
getWorld().emitGameEvent(e, GameEvent.STEP, getBlockPos());
}
}
}
}
@Override
@ -459,12 +381,16 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
}
@Override
protected void fall(double heightDifference, boolean onGround, BlockState state, BlockPos landedPosition) {
public Race getSpecies() {
return Race.UNSET;
}
@Override
public Race getSpecies() {
return Race.UNSET;
public SoundEvent getWalkedOnSound(double y) {
if (y >= getBalloonBoundingBox().minY) {
return USounds.ENTITY_HOT_AIR_BALLOON_STEP;
}
return USounds.ENTITY_HOT_AIR_BALLOON_BASKET_STEP;
}
@Override
@ -497,6 +423,18 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
move(MovementType.SELF, getVelocity());
setVelocity(getVelocity().multiply(slipperyness));
}
} else {
Map<Box, List<Entity>> collidingEntities = getCollidingEntities(getBoundingBoxes().stream());
for (Map.Entry<Box, List<Entity>> passengers : collidingEntities.entrySet()) {
for (Entity passenger : passengers.getValue()) {
Living<?> living = Living.living(passenger);
if (living != null) {
living.getTransportation().setVehicle(this);
}
}
}
}
updateLimbs(false);
}
@ -507,17 +445,22 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
return false;
}
@Override
protected Box calculateBoundingBox() {
return MultiBox.of(super.calculateBoundingBox(), getBoundingBoxes());
}
@Override
public Box getVisibilityBoundingBox() {
if (hasBalloon()) {
return MultiBox.unbox(getBoundingBox()).union(getBalloonBoundingBox());
return getBalloonBoundingBox().withMinY(getY());
}
return MultiBox.unbox(getBoundingBox());
return getInteriorBoundingBox();
}
protected Box getInteriorBoundingBox() {
Box box = MultiBox.unbox(getBoundingBox());
return box.withMinY(box.minY - 0.2).contract(0.2, 0, 0.2);
return box.withMinY(box.minY - 0.05).contract(0.15, 0, 0.15);
}
protected Box getBalloonBoundingBox() {
@ -528,34 +471,96 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
}
@Override
public void getCollissionShapes(ShapeContext context, Consumer<VoxelShape> output) {
public List<Box> getGravityZoneBoxes() {
Box balloon = getBalloonBoundingBox().expand(0.001);
Box interior = getInteriorBoundingBox().expand(0.001);
return List.of(
// interior - basket to top of balloon
interior.withMaxY(balloon.minY).withMinY(interior.maxY),
// balloon
balloon.withMaxY(balloon.maxY + 0.5).withMinY(balloon.maxY)
);
Box box = MultiBox.unbox(getBoundingBox()).expand(0.3, 0, 0.3);
}
double wallheight = box.maxY + 0.7;
double wallThickness = 0.7;
@Override
public List<Box> getBoundingBoxes() {
List<Box> boxes = new ArrayList<>();
Box box = getInteriorBoundingBox();
boxes.add(box);
double wallheight = box.maxY + 0.72;
double wallThickness = 0.2;
if (!getBasketType().isOf(BoatEntity.Type.BAMBOO)) {
// front left (next to door)
output.accept(VoxelShapes.cuboid(new Box(box.minX, box.minY, box.minZ, box.minX + wallThickness + 0.2, wallheight, box.minZ + wallThickness)));
boxes.add(new Box(box.minX, box.minY, box.minZ, box.minX + wallThickness + 0.4, wallheight, box.minZ + wallThickness));
// front right (next to door)
output.accept(VoxelShapes.cuboid(new Box(box.maxX - wallThickness - 0.2, box.minY, box.minZ, box.maxX, wallheight, box.minZ + wallThickness)));
boxes.add(new Box(box.maxX - wallThickness - 0.4, box.minY, box.minZ, box.maxX, wallheight, box.minZ + wallThickness));
// back
output.accept(VoxelShapes.cuboid(new Box(box.minX, box.minY, box.maxZ - wallThickness, box.maxX, wallheight, box.maxZ)));
boxes.add(new Box(box.minX, box.minY, box.maxZ - wallThickness, box.maxX, wallheight, box.maxZ));
// left
output.accept(VoxelShapes.cuboid(new Box(box.maxX - wallThickness, box.minY, box.minZ, box.maxX, wallheight, box.maxZ)));
boxes.add(new Box(box.maxX - wallThickness, box.minY, box.minZ, box.maxX, wallheight, box.maxZ));
// right
output.accept(VoxelShapes.cuboid(new Box(box.minX, box.minY, box.minZ, box.minX + wallThickness, wallheight, box.maxZ)));
boxes.add(new Box(box.minX, box.minY, box.minZ, box.minX + wallThickness, wallheight, box.maxZ));
}
// top of balloon
if (hasBalloon() && getInflation() > 0) {
output.accept(VoxelShapes.cuboid(getBalloonBoundingBox()));
if (hasBalloon() && getInflation(1) > 0.999F) {
boxes.add(getBalloonBoundingBox());
}
return boxes;
}
@Override
public void move(MovementType movementType, Vec3d movement) {
Vec3d oldPos = this.getPos();
List<Box> boundingBoxes = getGravityZoneBoxes();
super.move(movementType, movement);
if (movementType == MovementType.SELF) {
Vec3d actualMovement = getPos().subtract(oldPos);
Map<Box, List<Entity>> collidingEntities = getCollidingEntities(
boundingBoxes.stream().map(box -> box.stretch(actualMovement))
);
for (Map.Entry<Box, List<Entity>> passengers : collidingEntities.entrySet()) {
for (Entity passenger : passengers.getValue()) {
movePassenger(passenger, actualMovement);
}
}
}
}
private void movePassenger(Entity passenger, Vec3d movement) {
Living<?> living = Living.living(passenger);
if (living != null) {
if (living.getPhysics().isGravityNegative()) {
movement = movement.multiply(1, -1, 1);
}
living.getTransportation().setVehicle(this);
}
List<VoxelShape> shapes = new ArrayList<>();
getCollissionShapes(ShapeContext.of(passenger), shapes::add);
movement = Entity.adjustMovementForCollisions(passenger, movement, passenger.getBoundingBox(), getWorld(), shapes);
passenger.setPosition(passenger.getPos().add(movement));
passenger.updateTrackedPosition(passenger.getX(), passenger.getY(), passenger.getZ());
}
@Override
public Map<Box, List<Entity>> getCollidingEntities(Stream<Box> boundingBoxes) {
return boundingBoxes.collect(Collectors.toMap(Function.identity(), box -> {
return getWorld().getOtherEntities(this, box.expand(0.001).stretch(getVelocity().multiply(1)), RIDER_PREDICATE).stream().distinct().toList();
}));
}
@Override
protected void fall(double heightDifference, boolean onGround, BlockState state, BlockPos landedPosition) {
}
@Override
public void readCustomDataFromNbt(NbtCompound compound) {
super.readCustomDataFromNbt(compound);
@ -577,6 +582,10 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
compound.putInt("inflationAmount", getInflation());
}
static boolean isBetween(double value, double min, double max) {
return value >= min && value <= max;
}
@SuppressWarnings("deprecation")
public enum BalloonDesign implements StringIdentifiable {
NONE,
@ -607,6 +616,7 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
public record BasketType(Identifier id, @Nullable BoatEntity.Type boatType) {
private static final Map<Identifier, BasketType> REGISTRY = new HashMap<>();
public static final BasketType DEFAULT = of(BoatEntity.Type.OAK);
static {
Arrays.stream(BoatEntity.Type.values()).forEach(BasketType::of);
}
@ -616,7 +626,7 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp
}
public static BasketType of(String name) {
Identifier id = Identifier.tryParse(name);
Identifier id = name == null || name.isEmpty() ? null : Identifier.tryParse(name);
if (id == null) {
return of(BoatEntity.Type.OAK);
}

View file

@ -21,11 +21,13 @@ import com.minelittlepony.unicopia.entity.duck.EntityDuck;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.MovementType;
import net.minecraft.entity.Entity.PositionUpdater;
import net.minecraft.entity.Entity.RemovalReason;
import net.minecraft.fluid.Fluid;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
@Mixin(Entity.class)
@ -118,4 +120,20 @@ abstract class MixinEntity implements EntityDuck {
info.setReturnValue(null);
}
}
@Inject(method = "move", at = @At("HEAD"))
private void beforeMove(MovementType movementType, Vec3d movement, CallbackInfo info) {
Living<?> living = Living.living((Entity)(Object)this);
if (living != null) {
living.getTransportation().updatePreviousPosition();
}
}
@Inject(method = "move", at = @At("RETURN"))
private void afterMove(MovementType movementType, Vec3d movement, CallbackInfo info) {
Living<?> living = Living.living((Entity)(Object)this);
if (living != null) {
living.getTransportation().onMove(movementType);
}
}
}