mirror of
https://github.com/Sollace/Unicopia.git
synced 2024-11-30 16:28:00 +01:00
Implement the rest of hot air balloons
This commit is contained in:
parent
8f737e1607
commit
2fd8d0c42f
14 changed files with 388 additions and 88 deletions
|
@ -112,6 +112,8 @@ public class WorldRenderDelegate {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pony.updateRelativePosition();
|
||||||
|
|
||||||
matrices.push();
|
matrices.push();
|
||||||
|
|
||||||
Entity owner = pony.asEntity();
|
Entity owner = pony.asEntity();
|
||||||
|
|
|
@ -51,8 +51,8 @@ public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> {
|
||||||
return TexturedModelData.of(modelData, 512, 512);
|
return TexturedModelData.of(modelData, 512, 512);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void setAngles(AirBalloonEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
|
public void setAngles(AirBalloonEntity entity, float tickDelta, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
|
||||||
root.yaw = entity.bodyYaw;
|
root.yaw = entity.getBodyYaw();
|
||||||
burner.visible = entity.hasBurner();
|
burner.visible = entity.hasBurner();
|
||||||
balloon.visible = entity.hasBalloon();
|
balloon.visible = entity.hasBalloon();
|
||||||
|
|
||||||
|
|
|
@ -5,26 +5,36 @@ import net.minecraft.entity.*;
|
||||||
import net.minecraft.entity.data.*;
|
import net.minecraft.entity.data.*;
|
||||||
import net.minecraft.entity.mob.FlyingEntity;
|
import net.minecraft.entity.mob.FlyingEntity;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.item.Items;
|
||||||
import net.minecraft.nbt.NbtCompound;
|
import net.minecraft.nbt.NbtCompound;
|
||||||
import net.minecraft.network.packet.s2c.play.EntityVelocityUpdateS2CPacket;
|
import net.minecraft.particle.ParticleTypes;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.sound.SoundEvents;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.minecraft.util.Hand;
|
||||||
import net.minecraft.util.math.*;
|
import net.minecraft.util.math.*;
|
||||||
|
import net.minecraft.util.math.random.Random;
|
||||||
import net.minecraft.util.shape.VoxelShape;
|
import net.minecraft.util.shape.VoxelShape;
|
||||||
import net.minecraft.util.shape.VoxelShapes;
|
import net.minecraft.util.shape.VoxelShapes;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import com.minelittlepony.unicopia.entity.collision.EntityCollisions;
|
import com.minelittlepony.unicopia.entity.collision.EntityCollisions;
|
||||||
|
import com.minelittlepony.unicopia.entity.duck.EntityDuck;
|
||||||
|
import com.minelittlepony.unicopia.entity.duck.LivingEntityDuck;
|
||||||
|
import com.minelittlepony.unicopia.item.UItems;
|
||||||
|
import com.minelittlepony.unicopia.server.world.WeatherConditions;
|
||||||
|
|
||||||
public class AirBalloonEntity extends FlyingEntity implements EntityCollisions.ComplexCollidable {
|
public class AirBalloonEntity extends FlyingEntity implements EntityCollisions.ComplexCollidable, MultiBoundingBoxEntity {
|
||||||
private static final byte HAS_BALLOON = 1;
|
private static final byte HAS_BALLOON = 1;
|
||||||
private static final byte HAS_BURNER = 2;
|
private static final byte HAS_BURNER = 2;
|
||||||
private static final byte BURNER_ACTIVE = 4;
|
private static final byte BURNER_ACTIVE = 4;
|
||||||
private static final TrackedData<Integer> FLAGS = DataTracker.registerData(AirBalloonEntity.class, TrackedDataHandlerRegistry.INTEGER);
|
private static final TrackedData<Integer> FLAGS = DataTracker.registerData(AirBalloonEntity.class, TrackedDataHandlerRegistry.INTEGER);
|
||||||
|
private static final TrackedData<Integer> BOOSTING = DataTracker.registerData(AirBalloonEntity.class, TrackedDataHandlerRegistry.INTEGER);
|
||||||
|
|
||||||
private Vec3d prevVehicleVel = Vec3d.ZERO;
|
private boolean prevBoosting;
|
||||||
private Vec3d velocityBeforeTick = Vec3d.ZERO;
|
|
||||||
|
|
||||||
public AirBalloonEntity(EntityType<? extends AirBalloonEntity> type, World world) {
|
public AirBalloonEntity(EntityType<? extends AirBalloonEntity> type, World world) {
|
||||||
super(type, world);
|
super(type, world);
|
||||||
|
@ -34,6 +44,7 @@ public class AirBalloonEntity extends FlyingEntity implements EntityCollisions.C
|
||||||
protected void initDataTracker() {
|
protected void initDataTracker() {
|
||||||
super.initDataTracker();
|
super.initDataTracker();
|
||||||
dataTracker.startTracking(FLAGS, 0);
|
dataTracker.startTracking(FLAGS, 0);
|
||||||
|
dataTracker.startTracking(BOOSTING, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasBalloon() {
|
public boolean hasBalloon() {
|
||||||
|
@ -60,6 +71,14 @@ public class AirBalloonEntity extends FlyingEntity implements EntityCollisions.C
|
||||||
setFlag(BURNER_ACTIVE, burnerActive);
|
setFlag(BURNER_ACTIVE, burnerActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getBoostTicks() {
|
||||||
|
return dataTracker.get(BOOSTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setBoostTicks(int ticks) {
|
||||||
|
dataTracker.set(BOOSTING, ticks);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean getFlag(byte flag) {
|
private boolean getFlag(byte flag) {
|
||||||
return (dataTracker.get(FLAGS).intValue() & flag) == flag;
|
return (dataTracker.get(FLAGS).intValue() & flag) == flag;
|
||||||
}
|
}
|
||||||
|
@ -73,24 +92,70 @@ public class AirBalloonEntity extends FlyingEntity implements EntityCollisions.C
|
||||||
return hasBalloon() && isBurnerActive();
|
return hasBalloon() && isBurnerActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Box> getBoundingBoxes() {
|
||||||
|
if (hasBalloon()) {
|
||||||
|
Box balloonBox = getBalloonBoundingBox();
|
||||||
|
return List.of(getBoundingBox(), balloonBox.withMinY(balloonBox.minY - 0.5));
|
||||||
|
}
|
||||||
|
return List.of(getBoundingBox());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vec3d oldPosition = Vec3d.ZERO;
|
||||||
|
private Vec3d oldServerPosition = Vec3d.ZERO;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
prevVehicleVel = getVelocity();
|
|
||||||
setAir(getMaxAir());
|
setAir(getMaxAir());
|
||||||
|
int boostTicks = getBoostTicks();
|
||||||
|
|
||||||
if (isAirworthy()) {
|
if (boostTicks > 0) {
|
||||||
setVelocity(getVelocity()
|
boostTicks--;
|
||||||
.add(getWind(getWorld(), getBlockPos()))
|
setBoostTicks(boostTicks);
|
||||||
.normalize()
|
}
|
||||||
.multiply(0.2)
|
|
||||||
.add(0, isBurnerActive() ? 0.00F : isTouchingWater() ? 0.02F : -0.06F, 0));
|
|
||||||
} else {
|
|
||||||
addVelocity(0, -0.03, 0);
|
|
||||||
|
|
||||||
if (isSubmergedInWater()) {
|
addVelocity(0, isBurnerActive() ? 0.005 : -0.03, 0);
|
||||||
double yy = getVelocity().y;
|
|
||||||
setVelocity(getVelocity().multiply(0.9, 0.4, 0.9).add(0, Math.abs(yy) / 2F, 0));
|
if (!isAirworthy() && isSubmergedInWater()) {
|
||||||
|
double yy = getVelocity().y;
|
||||||
|
setVelocity(getVelocity().multiply(0.9, 0.4, 0.9).add(0, Math.abs(yy) / 2F, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean boosting = boostTicks > 0;
|
||||||
|
|
||||||
|
Random rng = getWorld().random;
|
||||||
|
|
||||||
|
if (getWorld().isClient()) {
|
||||||
|
if (hasBurner() && isBurnerActive()) {
|
||||||
|
Vec3d burnerPos = getPos().add(0, 3, 0);
|
||||||
|
for (int i = 0; i < (boosting ? 6 : 1); i++) {
|
||||||
|
getWorld().addParticle(ParticleTypes.FLAME,
|
||||||
|
rng.nextTriangular(burnerPos.x, 0.25),
|
||||||
|
rng.nextTriangular(burnerPos.y, 1),
|
||||||
|
rng.nextTriangular(burnerPos.z, 0.25),
|
||||||
|
0,
|
||||||
|
Math.max(0, getVelocity().y + (boosting ? 0.1 : 0)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (hasBurner() && isBurnerActive()) {
|
||||||
|
addVelocity(WeatherConditions.getAirflow(getBlockPos(), getWorld()).multiply(0.2));
|
||||||
|
setVelocity(getVelocity().multiply(0.3, 1, 0.3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boosting) {
|
||||||
|
addVelocity(0, 0.02, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boosting && !prevBoosting) {
|
||||||
|
playSound(SoundEvents.ENTITY_GHAST_SHOOT, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBurnerActive() && age % 15 + rng.nextInt(5) == 0) {
|
||||||
|
playSound(SoundEvents.ENTITY_GHAST_SHOOT, 0.2F, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLeashed()) {
|
if (isLeashed()) {
|
||||||
|
@ -103,82 +168,91 @@ public class AirBalloonEntity extends FlyingEntity implements EntityCollisions.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevBoosting = boosting;
|
||||||
|
oldPosition = getPos();
|
||||||
|
oldServerPosition = LivingEntityDuck.serverPos(this);
|
||||||
|
|
||||||
|
for (Box box : getBoundingBoxes()) {
|
||||||
|
for (Entity e : getWorld().getOtherEntities(this, box.expand(0, 0.5, 0))) {
|
||||||
|
updatePassenger(e, box, e.getY() > getY() + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
super.tick();
|
super.tick();
|
||||||
|
|
||||||
velocityBeforeTick = getVelocity();
|
|
||||||
|
|
||||||
float weight = 0;
|
|
||||||
|
|
||||||
if (velocityBeforeTick.length() > 0.01 && !isSubmergedInWater()) {
|
|
||||||
Box box = getInteriorBoundingBox();
|
|
||||||
|
|
||||||
for (Entity e : getWorld().getOtherEntities(this, box.expand(-0.2, 1, -0.2))) {
|
|
||||||
updatePassenger(e, box, !isOnGround());
|
|
||||||
weight++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasBalloon()) {
|
|
||||||
Box balloonBox = getBalloonBoundingBox();
|
|
||||||
|
|
||||||
for (Entity e : getWorld().getOtherEntities(this, balloonBox.expand(1.0E-7))) {
|
|
||||||
updatePassenger(e, balloonBox, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getVelocity().y > -0.6 && !isTouchingWater()) {
|
|
||||||
if (isBurnerActive()) {
|
|
||||||
weight -= 3;
|
|
||||||
}
|
|
||||||
addVelocity(0, MathHelper.clamp(-weight / 10F, -1, isLeashed() ? 0.2F : 1), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePassenger(Entity e, Box box, boolean checkBasket) {
|
private void updatePassenger(Entity e, Box box, boolean inBalloon) {
|
||||||
Vec3d pos = e.getPos();
|
|
||||||
|
|
||||||
double xx = checkBasket ? MathHelper.clamp(pos.x, box.minX, box.maxX) : pos.x;
|
if (getVelocity().y > 0 && e.getBoundingBox().minY < box.maxY) {
|
||||||
double zz = checkBasket ? MathHelper.clamp(pos.z, box.minZ, box.maxZ) : pos.z;
|
e.setPos(e.getX(), box.maxY, e.getZ());
|
||||||
double yy = pos.y;
|
}
|
||||||
|
if (getVelocity().y < 0 && e.getBoundingBox().minY > box.maxY) {
|
||||||
Box entityBox = e.getBoundingBox();
|
e.setPos(e.getX(), box.maxY, e.getZ());
|
||||||
|
|
||||||
if ((Math.abs(velocityBeforeTick.y) > 0.0001F && entityBox.minY < box.maxY)
|
|
||||||
|| (entityBox.minY > box.maxY && entityBox.minY < box.maxY + 0.01)) {
|
|
||||||
yy = box.maxY - Math.signum(velocityBeforeTick.y) * 0.01;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xx != pos.x || zz != pos.z || yy != pos.y) {
|
if (inBalloon && !e.isSneaky() && Math.abs(e.getVelocity().y) > 0.079) {
|
||||||
e.setPos(xx + velocityBeforeTick.x, yy, zz + velocityBeforeTick.z);
|
e.setVelocity(e.getVelocity().multiply(1, e.getVelocity().y < 0 ? -0.9 : 1.2, 1).add(0, 0.8, 0));
|
||||||
|
if (Math.abs(e.getVelocity().y) > 2) {
|
||||||
|
e.setVelocity(e.getVelocity().x, MathHelper.clamp(e.getVelocity().y, -2, 2), e.getVelocity().z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3d vel = e.getVelocity();
|
Living.getOrEmpty(e).ifPresent(living -> {
|
||||||
if (vel.lengthSquared() >= prevVehicleVel.lengthSquared()) {
|
living.setSupportingEntity(this);
|
||||||
vel = vel.subtract(prevVehicleVel);
|
living.setPositionOffset(
|
||||||
}
|
e.getPos().subtract(oldPosition),
|
||||||
|
LivingEntityDuck.serverPos(living.asEntity()).subtract(oldServerPosition)
|
||||||
|
);
|
||||||
|
living.updateRelativePosition();
|
||||||
|
});
|
||||||
|
|
||||||
e.setVelocity(vel.multiply(0.5).add(velocityBeforeTick.multiply(0.65)));
|
if (getWorld().isClient) {
|
||||||
|
if (e.distanceTraveled > ((EntityDuck)e).getNextStepSoundDistance()) {
|
||||||
|
e.distanceTraveled--;
|
||||||
|
|
||||||
Living.updateVelocity(e);
|
e.playSound(inBalloon ? SoundEvents.BLOCK_WOOL_STEP : SoundEvents.BLOCK_BAMBOO_STEP, 0.5F, 1);
|
||||||
if (e instanceof ServerPlayerEntity ply) {
|
|
||||||
ply.networkHandler.sendPacket(new EntityVelocityUpdateS2CPacket(ply));
|
|
||||||
}
|
|
||||||
|
|
||||||
e.setOnGround(true);
|
|
||||||
|
|
||||||
if (horizontalSpeed != 0) {
|
|
||||||
e.distanceTraveled = 0;
|
|
||||||
e.horizontalSpeed = 0;
|
|
||||||
if (e instanceof LivingEntity l) {
|
|
||||||
l.limbAnimator.setSpeed(0);
|
|
||||||
l.limbAnimator.updateLimbs(0, 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerCollision(PlayerEntity player) {
|
protected ActionResult interactMob(PlayerEntity player, Hand hand) {
|
||||||
// updatePassenger(player, getInteriorBoundingBox(), false);
|
ItemStack stack = player.getStackInHand(hand);
|
||||||
|
|
||||||
|
if (hasBalloon() && hasBurner()) {
|
||||||
|
if (stack.isOf(Items.FLINT_AND_STEEL)) {
|
||||||
|
setBurnerActive(!isBurnerActive());
|
||||||
|
if (isBurnerActive()) {
|
||||||
|
playSound(SoundEvents.ENTITY_GHAST_SHOOT, 1, 1);
|
||||||
|
}
|
||||||
|
stack.damage(1, player, p -> p.sendEquipmentBreakStatus(hand == Hand.MAIN_HAND ? EquipmentSlot.MAINHAND : EquipmentSlot.OFFHAND));
|
||||||
|
playSound(SoundEvents.ITEM_FLINTANDSTEEL_USE, 1, 1);
|
||||||
|
return ActionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.isEmpty() && isBurnerActive()) {
|
||||||
|
setBoostTicks(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.isOf(UItems.LARGE_BALLOON) && !hasBalloon()) {
|
||||||
|
if (!player.getAbilities().creativeMode) {
|
||||||
|
stack.decrement(1);
|
||||||
|
}
|
||||||
|
playSound(SoundEvents.ITEM_ARMOR_EQUIP_LEATHER, 1, 1);
|
||||||
|
setHasBalloon(true);
|
||||||
|
return ActionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.isOf(Items.LANTERN) && !hasBurner()) {
|
||||||
|
if (!player.getAbilities().creativeMode) {
|
||||||
|
stack.decrement(1);
|
||||||
|
}
|
||||||
|
playSound(SoundEvents.ENTITY_IRON_GOLEM_DAMAGE, 0.2F, 1);
|
||||||
|
setHasBurner(true);
|
||||||
|
return ActionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionResult.PASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -224,7 +298,6 @@ public class AirBalloonEntity extends FlyingEntity implements EntityCollisions.C
|
||||||
double wallheight = box.maxY + 0.7;
|
double wallheight = box.maxY + 0.7;
|
||||||
double wallThickness = 0.7;
|
double wallThickness = 0.7;
|
||||||
|
|
||||||
|
|
||||||
// front left (next to door)
|
// 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)));
|
output.accept(VoxelShapes.cuboid(new Box(box.minX, box.minY, box.minZ, box.minX + wallThickness + 0.2, wallheight, box.minZ + wallThickness)));
|
||||||
// front right (next to door)
|
// front right (next to door)
|
||||||
|
@ -240,7 +313,9 @@ public class AirBalloonEntity extends FlyingEntity implements EntityCollisions.C
|
||||||
|
|
||||||
// top of balloon
|
// top of balloon
|
||||||
if (hasBalloon()) {
|
if (hasBalloon()) {
|
||||||
output.accept(VoxelShapes.cuboid(getBoundingBox().offset(0.125, 6, 0).expand(2.25, 3, 2)));
|
output.accept(VoxelShapes.cuboid(
|
||||||
|
getBoundingBox().offset(0.12, 7.5, 0.12).expand(2.4, 3.5, 2.4)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,6 +325,7 @@ public class AirBalloonEntity extends FlyingEntity implements EntityCollisions.C
|
||||||
setHasBalloon(compound.getBoolean("hasBalloon"));
|
setHasBalloon(compound.getBoolean("hasBalloon"));
|
||||||
setHasBurner(compound.getBoolean("hasBurner"));
|
setHasBurner(compound.getBoolean("hasBurner"));
|
||||||
setBurnerActive(compound.getBoolean("burnerActive"));
|
setBurnerActive(compound.getBoolean("burnerActive"));
|
||||||
|
setBoostTicks(compound.getInt("boostTicks"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -258,6 +334,7 @@ public class AirBalloonEntity extends FlyingEntity implements EntityCollisions.C
|
||||||
compound.putBoolean("hasBalloon", hasBalloon());
|
compound.putBoolean("hasBalloon", hasBalloon());
|
||||||
compound.putBoolean("hasBurner", hasBurner());
|
compound.putBoolean("hasBurner", hasBurner());
|
||||||
compound.putBoolean("burnerActive", isBurnerActive());
|
compound.putBoolean("burnerActive", isBurnerActive());
|
||||||
|
compound.putInt("boostTicks", getBoostTicks());
|
||||||
}
|
}
|
||||||
|
|
||||||
static Vec3d getWind(World world, BlockPos pos) {
|
static Vec3d getWind(World world, BlockPos pos) {
|
||||||
|
|
|
@ -152,8 +152,6 @@ public class Creature extends Living<LivingEntity> implements WeaklyOwned.Mutabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void initMinionAi(GoalSelector targets) {
|
private void initMinionAi(GoalSelector targets) {
|
||||||
clearGoals(targets);
|
clearGoals(targets);
|
||||||
targets.add(2, new ActiveEnemyGoal<>(PlayerEntity.class));
|
targets.add(2, new ActiveEnemyGoal<>(PlayerEntity.class));
|
||||||
|
@ -197,9 +195,7 @@ public class Creature extends Living<LivingEntity> implements WeaklyOwned.Mutabl
|
||||||
initDiscordedAi();
|
initDiscordedAi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return super.beforeUpdate();
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearGoals(GoalSelector t) {
|
private void clearGoals(GoalSelector t) {
|
||||||
|
|
|
@ -52,6 +52,7 @@ import net.minecraft.server.world.ServerWorld;
|
||||||
import net.minecraft.sound.SoundEvents;
|
import net.minecraft.sound.SoundEvents;
|
||||||
import net.minecraft.util.Hand;
|
import net.minecraft.util.Hand;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
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.util.math.Vec3d;
|
||||||
|
|
||||||
|
@ -172,6 +173,93 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
|
||||||
return vehicle != null && getCarrierId().filter(vehicle.getUuid()::equals).isPresent();
|
return vehicle != null && getCarrierId().filter(vehicle.getUuid()::equals).isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Entity supportingEntity;
|
||||||
|
@Nullable
|
||||||
|
private Vec3d supportPositionOffset;
|
||||||
|
@Nullable
|
||||||
|
private Vec3d serverPositionOffset;
|
||||||
|
|
||||||
|
public void setSupportingEntity(Entity supportingEntity) {
|
||||||
|
this.supportingEntity = supportingEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPositionOffset(@Nullable Vec3d positionOffset, @Nullable Vec3d serverPositionOffset) {
|
||||||
|
this.supportPositionOffset = positionOffset;
|
||||||
|
this.serverPositionOffset = serverPositionOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePositionOffset() {
|
||||||
|
setPositionOffset(
|
||||||
|
supportingEntity == null ? null : entity.getPos().subtract(supportingEntity.getPos()),
|
||||||
|
supportingEntity instanceof LivingEntity l ? LivingEntityDuck.serverPos(entity).subtract(LivingEntityDuck.serverPos(l)) : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateRelativePosition() {
|
||||||
|
if (supportingEntity == null || supportPositionOffset == null) {
|
||||||
|
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.distanceTraveled = 0;
|
||||||
|
entity.fallDistance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean beforeUpdate() {
|
||||||
|
if (supportingEntity != null) {
|
||||||
|
Box ownBox = entity.getBoundingBox().expand(0.1);
|
||||||
|
if (MultiBoundingBoxEntity.getBoundingBoxes(supportingEntity).stream().noneMatch(box -> {
|
||||||
|
return box.expand(0, 0.5, 0).intersects(ownBox);
|
||||||
|
})) {
|
||||||
|
supportingEntity = null;
|
||||||
|
supportPositionOffset = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (supportingEntity != null) {
|
||||||
|
if (supportPositionOffset == null) {
|
||||||
|
updatePositionOffset();
|
||||||
|
} else {
|
||||||
|
updateRelativePosition();
|
||||||
|
}
|
||||||
|
entity.setOnGround(true);
|
||||||
|
entity.verticalCollision = true;
|
||||||
|
entity.groundCollision = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
tickers.forEach(Tickable::tick);
|
tickers.forEach(Tickable::tick);
|
||||||
|
@ -220,6 +308,7 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDragonBreath();
|
updateDragonBreath();
|
||||||
|
updatePositionOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAttributeModifier(UUID id, EntityAttribute attribute, float desiredValue, Float2ObjectFunction<EntityAttributeModifier> modifierSupplier, boolean permanent) {
|
public void updateAttributeModifier(UUID id, EntityAttribute attribute, float desiredValue, Float2ObjectFunction<EntityAttributeModifier> modifierSupplier, boolean permanent) {
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.minelittlepony.unicopia.entity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.util.math.Box;
|
||||||
|
|
||||||
|
public interface MultiBoundingBoxEntity {
|
||||||
|
List<Box> getBoundingBoxes();
|
||||||
|
|
||||||
|
static List<Box> getBoundingBoxes(Entity entity) {
|
||||||
|
return entity instanceof MultiBoundingBoxEntity multi ? multi.getBoundingBoxes() : List.of(entity.getBoundingBox());
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,10 +6,13 @@ import net.minecraft.entity.Entity;
|
||||||
import net.minecraft.entity.Entity.RemovalReason;
|
import net.minecraft.entity.Entity.RemovalReason;
|
||||||
|
|
||||||
public interface EntityDuck extends LavaAffine, PehkuiEntityExtensions {
|
public interface EntityDuck extends LavaAffine, PehkuiEntityExtensions {
|
||||||
|
|
||||||
void setRemovalReason(RemovalReason reason);
|
void setRemovalReason(RemovalReason reason);
|
||||||
|
|
||||||
void setVehicle(Entity vehicle);
|
void setVehicle(Entity vehicle);
|
||||||
|
|
||||||
|
float getNextStepSoundDistance();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void setLavaAffine(boolean lavaAffine) {
|
default void setLavaAffine(boolean lavaAffine) {
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package com.minelittlepony.unicopia.entity.duck;
|
package com.minelittlepony.unicopia.entity.duck;
|
||||||
|
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.util.Hand;
|
import net.minecraft.util.Hand;
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
|
||||||
public interface LivingEntityDuck {
|
public interface LivingEntityDuck {
|
||||||
void updateItemUsage(Hand hand, ItemStack stack, int time);
|
void updateItemUsage(Hand hand, ItemStack stack, int time);
|
||||||
|
@ -16,8 +18,18 @@ public interface LivingEntityDuck {
|
||||||
|
|
||||||
void setLastLeaningPitch(float pitch);
|
void setLastLeaningPitch(float pitch);
|
||||||
|
|
||||||
|
double getServerX();
|
||||||
|
|
||||||
|
double getServerY();
|
||||||
|
|
||||||
|
double getServerZ();
|
||||||
|
|
||||||
default void copyLeaningAnglesFrom(LivingEntityDuck other) {
|
default void copyLeaningAnglesFrom(LivingEntityDuck other) {
|
||||||
setLeaningPitch(other.getLeaningPitch());
|
setLeaningPitch(other.getLeaningPitch());
|
||||||
setLastLeaningPitch(other.getLastLeaningPitch());
|
setLastLeaningPitch(other.getLastLeaningPitch());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Vec3d serverPos(LivingEntity entity) {
|
||||||
|
return new Vec3d(((LivingEntityDuck)entity).getServerX(), ((LivingEntityDuck)entity).getServerY(), ((LivingEntityDuck)entity).getServerZ());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -435,7 +435,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
|
||||||
distanceClimbed = 0;
|
distanceClimbed = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return super.beforeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFaceClimbable(World world, BlockPos pos, Direction direction) {
|
private boolean isFaceClimbable(World world, BlockPos pos, Direction direction) {
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
package com.minelittlepony.unicopia.item;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import com.minelittlepony.unicopia.entity.AirBalloonEntity;
|
||||||
|
import com.minelittlepony.unicopia.entity.UEntities;
|
||||||
|
import com.minelittlepony.unicopia.util.Dispensable;
|
||||||
|
|
||||||
|
import net.minecraft.block.DispenserBlock;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.item.BoatItem;
|
||||||
|
import net.minecraft.item.Item;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.predicate.entity.EntityPredicates;
|
||||||
|
import net.minecraft.stat.Stats;
|
||||||
|
import net.minecraft.util.Hand;
|
||||||
|
import net.minecraft.util.TypedActionResult;
|
||||||
|
import net.minecraft.util.hit.BlockHitResult;
|
||||||
|
import net.minecraft.util.hit.HitResult;
|
||||||
|
import net.minecraft.util.math.BlockPointer;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
import net.minecraft.world.RaycastContext;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
|
public class BasketItem extends Item implements Dispensable {
|
||||||
|
private static final Predicate<Entity> RIDERS = EntityPredicates.EXCEPT_SPECTATOR.and(Entity::canHit);
|
||||||
|
private static final double REACH = 5;
|
||||||
|
|
||||||
|
public BasketItem(Item.Settings settings) {
|
||||||
|
super(settings);
|
||||||
|
DispenserBlock.registerBehavior(this, createDispenserBehaviour());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TypedActionResult<ItemStack> dispenseStack(BlockPointer source, ItemStack stack) {
|
||||||
|
Direction facing = source.getBlockState().get(DispenserBlock.FACING);
|
||||||
|
BlockPos pos = source.getPos().offset(facing);
|
||||||
|
float yaw = facing.getOpposite().asRotation();
|
||||||
|
return placeEntity(stack, source.getWorld(), pos.getX(), pos.getY(), pos.getZ(), yaw, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
|
||||||
|
ItemStack stack = user.getStackInHand(hand);
|
||||||
|
BlockHitResult hit = BoatItem.raycast(world, user, RaycastContext.FluidHandling.ANY);
|
||||||
|
|
||||||
|
if (hit.getType() == HitResult.Type.MISS) {
|
||||||
|
return TypedActionResult.pass(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d eyePos = user.getEyePos();
|
||||||
|
if (world.getOtherEntities(user, user.getBoundingBox().stretch(user.getRotationVec(1).multiply(REACH)).expand(1), RIDERS).stream()
|
||||||
|
.anyMatch(entity -> entity.getBoundingBox().expand(entity.getTargetingMargin()).contains(eyePos))) {
|
||||||
|
return TypedActionResult.pass(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hit.getType() == HitResult.Type.BLOCK) {
|
||||||
|
return placeEntity(stack, world, hit.getPos().x, hit.getPos().y, hit.getPos().z, user.getYaw() + 180, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TypedActionResult.pass(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
entity.setHeadYaw(yaw);
|
||||||
|
entity.setBodyYaw(yaw);
|
||||||
|
if (!world.isSpaceEmpty(entity, entity.getBoundingBox())) {
|
||||||
|
return TypedActionResult.fail(stack);
|
||||||
|
}
|
||||||
|
if (!world.isClient) {
|
||||||
|
world.spawnEntity(entity);
|
||||||
|
if (user != null) {
|
||||||
|
user.incrementStat(Stats.USED.getOrCreateStat(this));
|
||||||
|
}
|
||||||
|
if (user == null || !user.getAbilities().creativeMode) {
|
||||||
|
stack.decrement(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TypedActionResult.success(stack, world.isClient());
|
||||||
|
}
|
||||||
|
}
|
|
@ -118,6 +118,8 @@ public interface UItems {
|
||||||
Item BUTTERFLY = register("butterfly", new Item(new Item.Settings().food(UFoodComponents.INSECTS)), ItemGroups.FOOD_AND_DRINK);
|
Item BUTTERFLY = register("butterfly", new Item(new Item.Settings().food(UFoodComponents.INSECTS)), ItemGroups.FOOD_AND_DRINK);
|
||||||
|
|
||||||
Item SPELLBOOK = register("spellbook", new SpellbookItem(new Item.Settings().maxCount(1).rarity(Rarity.UNCOMMON)), ItemGroups.TOOLS);
|
Item SPELLBOOK = register("spellbook", new SpellbookItem(new Item.Settings().maxCount(1).rarity(Rarity.UNCOMMON)), ItemGroups.TOOLS);
|
||||||
|
Item BASKET = register("basket", new BasketItem(new Item.Settings().maxCount(1)), ItemGroups.FUNCTIONAL);
|
||||||
|
Item LARGE_BALLOON = register("large_balloon", new Item(new Item.Settings().maxCount(1)), ItemGroups.FUNCTIONAL);
|
||||||
|
|
||||||
AmuletItem PEGASUS_AMULET = register("pegasus_amulet", new PegasusAmuletItem(new FabricItemSettings()
|
AmuletItem PEGASUS_AMULET = register("pegasus_amulet", new PegasusAmuletItem(new FabricItemSettings()
|
||||||
.maxCount(1)
|
.maxCount(1)
|
||||||
|
|
|
@ -25,6 +25,10 @@ abstract class MixinEntity implements EntityDuck {
|
||||||
@Accessor
|
@Accessor
|
||||||
public abstract void setVehicle(Entity vehicle);
|
public abstract void setVehicle(Entity vehicle);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Accessor
|
||||||
|
public abstract float getNextStepSoundDistance();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLavaAffine() {
|
public boolean isLavaAffine() {
|
||||||
Entity self = (Entity)(Object)this;
|
Entity self = (Entity)(Object)this;
|
||||||
|
|
|
@ -77,6 +77,18 @@ abstract class MixinLivingEntity extends Entity implements LivingEntityDuck, Equ
|
||||||
@Accessor("lastLeaningPitch")
|
@Accessor("lastLeaningPitch")
|
||||||
public abstract void setLastLeaningPitch(float pitch);
|
public abstract void setLastLeaningPitch(float pitch);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Accessor
|
||||||
|
public abstract double getServerX();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Accessor
|
||||||
|
public abstract double getServerY();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Accessor
|
||||||
|
public abstract double getServerZ();
|
||||||
|
|
||||||
@Inject(method = "createLivingAttributes()Lnet/minecraft/entity/attribute/DefaultAttributeContainer$Builder;", at = @At("RETURN"))
|
@Inject(method = "createLivingAttributes()Lnet/minecraft/entity/attribute/DefaultAttributeContainer$Builder;", at = @At("RETURN"))
|
||||||
private static void onCreateAttributes(CallbackInfoReturnable<DefaultAttributeContainer.Builder> info) {
|
private static void onCreateAttributes(CallbackInfoReturnable<DefaultAttributeContainer.Builder> info) {
|
||||||
Creature.registerAttributes(info.getReturnValue());
|
Creature.registerAttributes(info.getReturnValue());
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 12 KiB |
Loading…
Reference in a new issue