From ff4770314046bd34f1ea5a4e80d1c5e2fd65f4c0 Mon Sep 17 00:00:00 2001 From: Sollace <sollacea@gmail.com> Date: Wed, 27 Mar 2024 19:43:55 +0000 Subject: [PATCH 01/11] Update blockus --- BlockusAddon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BlockusAddon b/BlockusAddon index 7170edad..2e285380 160000 --- a/BlockusAddon +++ b/BlockusAddon @@ -1 +1 @@ -Subproject commit 7170edad67426756e2383bd9464a8615e9bb4b3a +Subproject commit 2e285380cfa55da1b858c38833f05a56666c219f From 93d11531d68aeaf91355643901024cf4c3ef9495 Mon Sep 17 00:00:00 2001 From: Sollace <sollacea@gmail.com> Date: Wed, 27 Mar 2024 19:44:12 +0000 Subject: [PATCH 02/11] Use newer loom features for datagen --- build.gradle | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/build.gradle b/build.gradle index 285777fc..69922b16 100644 --- a/build.gradle +++ b/build.gradle @@ -28,18 +28,13 @@ archivesBaseName = project.name loom { mixin.defaultRefmapName = 'unicopia.mixin.refmap.json' accessWidenerPath = file('src/main/resources/unicopia.aw') - runs { - datagen { - server() - name "Data Generation" - vmArg "-Dfabric-api.datagen" - vmArg "-Dfabric-api.datagen.modid=unicopia" - vmArg "-Dfabric-api.datagen.output-dir=${file("src/main/generated")}" - runDir "build/datagen" - } +} + +fabricApi { + configureDataGeneration { + modId = 'unicopia' } } -//assemble.dependsOn(runDatagen) reckon { scopeFromProp() @@ -108,14 +103,6 @@ dependencies { } } -sourceSets { - main { - resources { - srcDirs += [ "src/main/generated" ] - } - } -} - processResources { inputs.property "version", project.version.toString() From fbe444b56cd52e30383d50a92629f41c7e8c89a0 Mon Sep 17 00:00:00 2001 From: Sollace <sollacea@gmail.com> Date: Thu, 28 Mar 2024 12:08:06 +0000 Subject: [PATCH 03/11] Fix janky hot air balloon physics --- .../client/render/WorldRenderDelegate.java | 2 - .../unicopia/entity/Living.java | 127 +--------- .../unicopia/entity/Transportation.java | 141 +++++++++++ .../collision/MultiBoundingBoxEntity.java | 26 +- .../unicopia/entity/mob/AirBalloonEntity.java | 232 +++++++++--------- .../unicopia/mixin/MixinEntity.java | 18 ++ 6 files changed, 313 insertions(+), 233 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/entity/Transportation.java diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/WorldRenderDelegate.java b/src/main/java/com/minelittlepony/unicopia/client/render/WorldRenderDelegate.java index 17594ba0..f839fef1 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/WorldRenderDelegate.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/WorldRenderDelegate.java @@ -167,8 +167,6 @@ public class WorldRenderDelegate { return true; } - pony.updateSupportingEntity(); - matrices.push(); Entity owner = pony.asEntity(); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Living.java b/src/main/java/com/minelittlepony/unicopia/entity/Living.java index 84dce958..86ea4261 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Living.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Living.java @@ -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) { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Transportation.java b/src/main/java/com/minelittlepony/unicopia/entity/Transportation.java new file mode 100644 index 00000000..14ad5346 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/Transportation.java @@ -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; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/collision/MultiBoundingBoxEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/collision/MultiBoundingBoxEntity.java index 82ab40ed..ac7b951e 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/collision/MultiBoundingBoxEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/collision/MultiBoundingBoxEntity.java @@ -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()); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/AirBalloonEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/AirBalloonEntity.java index 57cbc2ef..72bf2c6c 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/AirBalloonEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/AirBalloonEntity.java @@ -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); } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java index 6a9c29d3..13644fbb 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java @@ -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); + } + } } From 3ff7466a543efffd232019421c679541d475231f Mon Sep 17 00:00:00 2001 From: Sollace <sollacea@gmail.com> Date: Thu, 28 Mar 2024 13:47:54 +0000 Subject: [PATCH 04/11] Balloons now require fuel to run --- .../unicopia/entity/mob/AirBalloonEntity.java | 75 +++++++++++++++---- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/AirBalloonEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/AirBalloonEntity.java index 72bf2c6c..690c1a6b 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/AirBalloonEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/AirBalloonEntity.java @@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.entity.mob; import net.fabricmc.fabric.api.tag.convention.v1.ConventionalItemTags; import net.minecraft.block.BlockState; import net.minecraft.block.ShapeContext; +import net.minecraft.block.entity.FurnaceBlockEntity; import net.minecraft.entity.*; import net.minecraft.entity.data.*; import net.minecraft.entity.mob.MobEntity; @@ -72,6 +73,9 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp private int prevInflation; private Vec3d manualVelocity = Vec3d.ZERO; + private int maxFuel = 100; + private int fuel; + public AirBalloonEntity(EntityType<? extends AirBalloonEntity> type, World world) { super(type, world); intersectionChecked = true; @@ -174,6 +178,14 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp } setInflation(inflation); } + + if (fuel > -6 && age % 60 == 0) { + fuel -= boosting ? 10 : 1; + if (fuel <= -6) { + setBoostTicks(0); + setAscending(false); + } + } } else { if (inflation < getMaxInflation() && inflation > 0) { setInflation(--inflation); @@ -196,15 +208,16 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp if (hasBurner() && isAscending()) { Vec3d burnerPos = getPos().add(0, 3, 0); for (int i = 0; i < (boosting ? 6 : 1); i++) { - getWorld().addParticle( - getStackInHand(Hand.MAIN_HAND).isOf(Items.SOUL_LANTERN) + getWorld().addParticle(fuel <= 0 + ? ParticleTypes.SMOKE + : getStackInHand(Hand.MAIN_HAND).isOf(Items.SOUL_LANTERN) ? ParticleTypes.SOUL_FIRE_FLAME : 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)), + (boosting ? 0.1 : 0), 0 ); } @@ -236,7 +249,7 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp if (leashPost.distanceTo(pos) >= 5) { Vec3d newVel = leashPost.subtract(pos).multiply(0.01); if (isAirworthy()) { - setVelocity(newVel.lengthSquared() < 0.03 ? Vec3d.ZERO : newVel); + setVelocity(newVel.lengthSquared() < 0.0001 ? Vec3d.ZERO : newVel); } else { setVelocity(getVelocity().multiply(0.9).add(newVel)); } @@ -277,11 +290,20 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp if (!getWorld().isClient) { manualVelocity = manualVelocity.add(0.3 * xPush, 0, 0.3 * zPush); } - } else if (stack.isEmpty() && isAscending()) { - setBoostTicks(50); + 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()); } + return ActionResult.SUCCESS; + } + + if (stack.isEmpty() && isAscending()) { + setBoostTicks(50); + playSound(USounds.ENTITY_HOT_AIR_BALLOON_BOOST, 1, 1); + if (!player.isSneaky()) { + getWorld().emitGameEvent(player, GameEvent.ENTITY_INTERACT, getBlockPos()); + } + return ActionResult.SUCCESS; } } } @@ -309,9 +331,7 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp } if (stack.isIn(ConventionalItemTags.SHEARS) && hasBalloon()) { - if (!player.getAbilities().creativeMode) { - stack.damage(1, player, p -> p.sendToolBreakStatus(hand)); - } + stack.damage(1, player, p -> p.sendToolBreakStatus(hand)); setDesign(BalloonDesign.NONE); dropItem(UItems.GIANT_BALLOON); playSound(USounds.ENTITY_HOT_AIR_BALLOON_EQUIP_CANOPY, 1, 1); @@ -336,6 +356,25 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp return ActionResult.SUCCESS; } + if (hasBurner()) { + int fuel = FurnaceBlockEntity.createFuelTimeMap().getOrDefault(stack.getItem(), 0); + if (fuel > 0) { + if (this.fuel < maxFuel) { + if (this.fuel < 0) { + this.fuel = fuel; + } else { + this.fuel += fuel; + } + if (!player.getAbilities().creativeMode) { + stack.decrement(1); + } + playSound(USounds.Vanilla.ENTITY_VILLAGER_YES, 1, 1); + return ActionResult.SUCCESS; + } + return ActionResult.FAIL; + } + } + return ActionResult.PASS; } @@ -474,13 +513,15 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp 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) - ); - + if (hasBalloon() && getInflation(1) > 0.999F) { + 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) + ); + } + return List.of(interior.withMaxY(balloon.minY).withMinY(interior.maxY)); } @Override @@ -570,6 +611,7 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp setBoostTicks(compound.getInt("boostTicks")); prevInflation = compound.getInt("inflationAmount"); setInflation(prevInflation); + fuel = MathHelper.clamp(compound.getInt("fuel"), 0, maxFuel); } @Override @@ -580,6 +622,7 @@ public class AirBalloonEntity extends MobEntity implements EntityCollisions.Comp compound.putBoolean("burnerActive", isAscending()); compound.putInt("boostTicks", getBoostTicks()); compound.putInt("inflationAmount", getInflation()); + compound.putInt("fuel", fuel); } static boolean isBetween(double value, double min, double max) { From 101a560a012feeabef96eacbd20d4cdf9abd86e1 Mon Sep 17 00:00:00 2001 From: Sollace <sollacea@gmail.com> Date: Thu, 28 Mar 2024 13:48:12 +0000 Subject: [PATCH 05/11] Improve hot air balloon animations and fix formatting --- .../render/entity/AirBalloonEntityModel.java | 168 +++++++++++------- 1 file changed, 103 insertions(+), 65 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/AirBalloonEntityModel.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/AirBalloonEntityModel.java index 5934bd8d..03c14bf9 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/entity/AirBalloonEntityModel.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/AirBalloonEntityModel.java @@ -22,54 +22,50 @@ public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> { private final List<ModelPart> ropes; - public AirBalloonEntityModel(ModelPart root) { - this.root = root; - isBurner = root.hasChild("burner"); - isBalloon = root.hasChild("canopy"); + public AirBalloonEntityModel(ModelPart root) { + this.root = root; + isBurner = root.hasChild("burner"); + 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") - ); - } else { - ropes = List.of(); - } - } + 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")); + } else { + ropes = List.of(); + } + } - public static TexturedModelData getBasketModelData() { - ModelData modelData = new ModelData(); - ModelPartData root = modelData.getRoot(); - ModelPartData basket = root.addChild("basket", ModelPartBuilder.create().uv(0, 0).cuboid(-16, -1, -16, 32, 2, 30, Dilation.NONE), ModelTransform.pivot(0, 24, 0)); - basket.addChild("walls", ModelPartBuilder.create().uv(0, 66).cuboid(-17, -12, -16, 2, 11, 30, Dilation.NONE) - .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.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.NONE); - return TexturedModelData.of(modelData, 256, 128); - } + public static TexturedModelData getBasketModelData() { + ModelData modelData = new ModelData(); + ModelPartData root = modelData.getRoot(); + ModelPartData basket = root.addChild("basket", ModelPartBuilder.create().uv(0, 0).cuboid(-16, -1, -16, 32, 2, 30, Dilation.NONE), ModelTransform.pivot(0, 24, 0)); + basket.addChild("walls", ModelPartBuilder.create().uv(0, 66).cuboid(-17, -12, -16, 2, 11, 30, Dilation.NONE) + .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.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.NONE); + return TexturedModelData.of(modelData, 256, 128); + } - public static TexturedModelData getBurnerModelData() { + public static TexturedModelData getBurnerModelData() { ModelData modelData = new ModelData(); ModelPartData root = modelData.getRoot(); 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)); + 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)); return TexturedModelData.of(modelData, 64, 128); } - public static TexturedModelData getCanopyModelData() { + public static TexturedModelData getCanopyModelData() { ModelData modelData = new ModelData(); ModelPartData root = modelData.getRoot(); ModelPartData balloon = root.addChild("canopy", ModelPartBuilder.create().cuboid(-54, -178, -59, 112, 120, 112, Dilation.NONE), ModelTransform.pivot(0, 24, 0)); @@ -80,40 +76,82 @@ public class AirBalloonEntityModel extends EntityModel<AirBalloonEntity> { return TexturedModelData.of(modelData, 512, 256); } - @Override - public void setAngles(AirBalloonEntity entity, float tickDelta, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) { - inflation = entity.getInflation(tickDelta); - root.roll = MathHelper.sin((float)(entity.getX() - entity.prevX)); - root.pitch = MathHelper.sin((float)(entity.getZ() - entity.prevZ)); + @Override + public void setAngles(AirBalloonEntity entity, float tickDelta, float limbSwingAmount, float ageInTicks, + float netHeadYaw, float headPitch) { + inflation = entity.getInflation(tickDelta); - if (isBurner) { - boolean lifted = inflation > 0.8F; - root.pivotY = 32 * (1 - inflation); - root.pivotX = inflation * MathHelper.sin(limbSwingAmount + entity.age / 5F) / 4F; - ropes.forEach(rope -> rope.visible = lifted); - } + 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); + if (entity.isLeashed()) { + root.roll *= -1; + root.pitch *= -1; + } + } else { + root.pitch = 0; + root.roll = 0; + } - if (isBalloon) { - root.pivotY = 0; - 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); - } - } - } + for (ModelPart rope : ropes) { + rope.resetTransform(); + } - @Override - public void render(MatrixStack matrices, VertexConsumer vertexConsumer, int light, int overlay, float r, float g, float b, float a) { - if (isBalloon) { - matrices.push(); - matrices.translate(0, 1 * (1 - inflation), 0); + if (isBurner) { + boolean lifted = inflation > 0.8F; + root.pivotY = 32 * (1 - 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; + }); + } + if (isBalloon) { + root.pivotY = 0; + 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); + } + } + + for (int i = 0; i < ropes.size(); i++) { + ModelPart rope = ropes.get(i); + float rollRatio = root.roll / rope.roll; + float pitchRatio = root.pitch / rope.pitch; + + rope.pivotY -= 5F * rollRatio; + rope.pivotY -= 5F * pitchRatio; + + if (i == 0 || i == 3) { + rope.pivotZ -= 5 * pitchRatio; + } + if (i == 2 || i == 1) { + rope.pivotZ += 5 * pitchRatio; + } + + if (i == 2 || i == 3) { + rope.pivotX -= 5 * rollRatio; + } + if (i == 0 || i == 1) { + rope.pivotX += 5 * rollRatio; + } + } + + } + + @Override + public void render(MatrixStack matrices, VertexConsumer vertexConsumer, int light, int overlay, float r, float g, float b, float a) { + if (isBalloon) { + matrices.push(); + matrices.translate(0, 1 * (1 - inflation), 0); matrices.scale(1, MathHelper.lerp(inflation, -0.05F, 1), 1); root.render(matrices, vertexConsumer, light, overlay, r, g, b, a); matrices.pop(); } else { root.render(matrices, vertexConsumer, light, overlay, r, g, b, a); } - } + } } \ No newline at end of file From 6b3b5c7c86d817d378a30fe199f647a635255251 Mon Sep 17 00:00:00 2001 From: Sollace <sollacea@gmail.com> Date: Thu, 28 Mar 2024 14:51:55 +0000 Subject: [PATCH 06/11] Added baited fishing rod --- .../unicopia/UConventionalTags.java | 2 +- .../datagen/providers/UItemTagProvider.java | 2 +- .../datagen/providers/UModelProvider.java | 4 ++ .../providers/recipe/URecipeProvider.java | 7 ++++ .../unicopia/entity/mob/UEntities.java | 9 +++- .../unicopia/item/BaitedFishingRodItem.java | 39 ++++++++++++++++++ .../minelittlepony/unicopia/item/UItems.java | 1 + .../resources/assets/unicopia/lang/en_us.json | 1 + .../textures/item/baited_fishing_rod.png | Bin 0 -> 6420 bytes 9 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/item/BaitedFishingRodItem.java create mode 100644 src/main/resources/assets/unicopia/textures/item/baited_fishing_rod.png diff --git a/src/main/java/com/minelittlepony/unicopia/UConventionalTags.java b/src/main/java/com/minelittlepony/unicopia/UConventionalTags.java index e1a0a0b1..44e8571e 100644 --- a/src/main/java/com/minelittlepony/unicopia/UConventionalTags.java +++ b/src/main/java/com/minelittlepony/unicopia/UConventionalTags.java @@ -19,7 +19,7 @@ public interface UConventionalTags { TagKey<Item> MUSHROOMS = item("mushrooms"); TagKey<Item> MUFFINS = item("muffins"); TagKey<Item> MANGOES = item("mangoes"); - TagKey<Item> OEATMEALS = item("oatmeals"); + TagKey<Item> OATMEALS = item("oatmeals"); TagKey<Item> FRUITS = item("fruits"); diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/UItemTagProvider.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/UItemTagProvider.java index 058f636d..65c5d92a 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/UItemTagProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/UItemTagProvider.java @@ -169,7 +169,7 @@ public class UItemTagProvider extends FabricTagProvider.ItemTagProvider { getOrCreateTagBuilder(UConventionalTags.SEEDS).add(Items.BEETROOT_SEEDS, Items.MELON_SEEDS, Items.PUMPKIN_SEEDS, Items.TORCHFLOWER_SEEDS, Items.WHEAT_SEEDS) .add(UItems.OAT_SEEDS) .forceAddTag(UTags.APPLE_SEEDS); - getOrCreateTagBuilder(UConventionalTags.OEATMEALS).add(UItems.OATMEAL); + getOrCreateTagBuilder(UConventionalTags.OATMEALS).add(UItems.OATMEAL); getOrCreateTagBuilder(UConventionalTags.GRAIN).add(Items.WHEAT, UItems.OATS); getOrCreateTagBuilder(UConventionalTags.NUTS).addOptionalTag(UConventionalTags.CROPS_PEANUTS); diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/UModelProvider.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/UModelProvider.java index 1c58e4f7..4a652170 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/UModelProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/UModelProvider.java @@ -19,6 +19,7 @@ import net.minecraft.item.Items; import net.minecraft.registry.Registries; import net.minecraft.data.client.ItemModelGenerator; import net.minecraft.data.client.ModelIds; +import net.minecraft.data.client.Models; import net.minecraft.data.client.TextureKey; import net.minecraft.data.client.TextureMap; @@ -133,5 +134,8 @@ public class UModelProvider extends FabricModelProvider { .addOverride(ModelIds.getItemSubModelId(UItems.GEMSTONE, "_pure"), "affinity", 0) .addOverride(ModelIds.getItemSubModelId(UItems.GEMSTONE, "_corrupted"), "affinity", 1) .upload(UItems.GEMSTONE, itemModelGenerator); + + // fishing rod + ItemModels.register(itemModelGenerator, Models.HANDHELD_ROD, UItems.BAITED_FISHING_ROD); } } diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java index 57340e26..064c6493 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java @@ -76,6 +76,13 @@ public class URecipeProvider extends FabricRecipeProvider { .input(ConventionalItemTags.GLASS_BLOCKS) .input(UItems.SUNGLASSES).criterion("has_broken_sunglasses", conditionsFromItem(UItems.BROKEN_SUNGLASSES)) .offerTo(exporter, convertBetween(UItems.SUNGLASSES, UItems.BROKEN_SUNGLASSES)); + + // fishing + ShapelessRecipeJsonBuilder.create(RecipeCategory.TOOLS, UItems.BAITED_FISHING_ROD) + .input(Items.FISHING_ROD).criterion(hasItem(Items.FISHING_ROD), conditionsFromItem(Items.FISHING_ROD)) + .input(UItems.WHEAT_WORMS) + .group("fishing_rod") + .offerTo(exporter); } private void generateVanillaRecipeExtensions(Consumer<RecipeJsonProvider> exporter) { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java index 9200384f..8d6ea782 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java @@ -30,29 +30,34 @@ public interface UEntities { .dimensions(EntityDimensions.fixed(0.25F, 0.25F))); EntityType<MagicProjectileEntity> THROWN_ITEM = register("thrown_item", FabricEntityTypeBuilder.<MagicProjectileEntity>create(SpawnGroup.MISC, MagicProjectileEntity::new) .trackRangeBlocks(100) + .disableSummon() .trackedUpdateRate(2) .dimensions(EntityDimensions.fixed(0.25F, 0.25F))); EntityType<PhysicsBodyProjectileEntity> MUFFIN = register("muffin", FabricEntityTypeBuilder.<PhysicsBodyProjectileEntity>create(SpawnGroup.MISC, PhysicsBodyProjectileEntity::new) .trackRangeBlocks(100) + .disableSummon() .trackedUpdateRate(2) .dimensions(EntityDimensions.fixed(0.25F, 0.25F))); EntityType<MagicBeamEntity> MAGIC_BEAM = register("magic_beam", FabricEntityTypeBuilder.<MagicBeamEntity>create(SpawnGroup.MISC, MagicBeamEntity::new) .trackRangeBlocks(100) + .disableSummon() .trackedUpdateRate(2) .dimensions(EntityDimensions.fixed(0.25F, 0.25F))); EntityType<FloatingArtefactEntity> FLOATING_ARTEFACT = register("floating_artefact", FabricEntityTypeBuilder.create(SpawnGroup.MISC, FloatingArtefactEntity::new) .trackRangeBlocks(200) + .disableSummon() .dimensions(EntityDimensions.fixed(1, 1))); EntityType<CastSpellEntity> CAST_SPELL = register("cast_spell", FabricEntityTypeBuilder.create(SpawnGroup.MISC, CastSpellEntity::new) .trackRangeBlocks(200) + .disableSummon() .dimensions(EntityDimensions.changing(4, 4))); EntityType<FairyEntity> TWITTERMITE = register("twittermite", FabricEntityTypeBuilder.create(SpawnGroup.MISC, FairyEntity::new) .trackRangeBlocks(200) .dimensions(EntityDimensions.fixed(0.1F, 0.1F))); EntityType<FriendlyCreeperEntity> FRIENDLY_CREEPER = register("friendly_creeper", FabricEntityTypeBuilder.create(SpawnGroup.MISC, FriendlyCreeperEntity::new) .trackRangeChunks(8) - .dimensions(EntityDimensions.fixed(0.6f, 1.7f)) - ); + .disableSummon() + .dimensions(EntityDimensions.fixed(0.6f, 1.7f))); EntityType<SpellbookEntity> SPELLBOOK = register("spellbook", FabricEntityTypeBuilder.create(SpawnGroup.MISC, SpellbookEntity::new) .trackRangeBlocks(200) .dimensions(EntityDimensions.fixed(0.9F, 0.5F))); diff --git a/src/main/java/com/minelittlepony/unicopia/item/BaitedFishingRodItem.java b/src/main/java/com/minelittlepony/unicopia/item/BaitedFishingRodItem.java new file mode 100644 index 00000000..6c0a03b3 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/item/BaitedFishingRodItem.java @@ -0,0 +1,39 @@ +package com.minelittlepony.unicopia.item; + +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.projectile.FishingBobberEntity; +import net.minecraft.item.FishingRodItem; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.world.World; + +public class BaitedFishingRodItem extends FishingRodItem { + + public BaitedFishingRodItem(Settings settings) { + super(settings); + } + + @Override + public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) { + TypedActionResult<ItemStack> result = super.use(world, user, hand); + if (!world.isClient) { + if (user.fishHook != null) { + user.fishHook.discard(); + ItemStack stack = user.getStackInHand(hand); + int lure = (EnchantmentHelper.getLure(stack) + 1) * 2; + int luck = (EnchantmentHelper.getLuckOfTheSea(stack) + 1) * 2; + world.spawnEntity(new FishingBobberEntity(user, world, luck, lure)); + } + + if (result.getValue().isOf(this)) { + ItemStack stack = Items.FISHING_ROD.getDefaultStack(); + stack.setDamage(result.getValue().getDamage()); + return TypedActionResult.success(stack, world.isClient()); + } + } + return result; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/item/UItems.java b/src/main/java/com/minelittlepony/unicopia/item/UItems.java index ce60d14b..5e365031 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/UItems.java +++ b/src/main/java/com/minelittlepony/unicopia/item/UItems.java @@ -89,6 +89,7 @@ public interface UItems { Item HORSE_SHOE_FRIES = register("horse_shoe_fries", new Item(new Item.Settings().maxCount(32).food(UFoodComponents.HAY_FRIES)), ItemGroups.FOOD_AND_DRINK); Item WHEAT_WORMS = register("wheat_worms", new Item(new Item.Settings().maxCount(16).food(UFoodComponents.WORMS)), ItemGroups.NATURAL); + Item BAITED_FISHING_ROD = register("baited_fishing_rod", new BaitedFishingRodItem(new Item.Settings().maxDamage(64)), ItemGroups.TOOLS); Item MUFFIN = register("muffin", new MuffinItem(new Item.Settings().maxCount(32).food(FoodComponents.BREAD), 0), ItemGroups.FOOD_AND_DRINK); Item PINECONE = register("pinecone", new ForageableItem(new Item.Settings().food(UFoodComponents.PINECONE).maxCount(16), () -> Blocks.SPRUCE_LEAVES), ItemGroups.FOOD_AND_DRINK); Item ACORN = register("acorn", new ForageableItem(new Item.Settings().food(UFoodComponents.ACORN).maxCount(16), () -> Blocks.OAK_LEAVES), ItemGroups.FOOD_AND_DRINK); diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 8558c7b3..35f8c79c 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -145,6 +145,7 @@ "item.unicopia.crispy_hay_fries": "Crispy Hay Fries", "item.unicopia.horse_shoe_fries": "Horse Shoe Fries", "item.unicopia.wheat_worms": "Wheat Worms", + "item.unicopia.baited_fishing_rod": "Baited Fishing Rod", "item.unicopia.muffin": "Muffin", "item.unicopia.oatmeal_cookie": "Oatmeal Cookie", "item.unicopia.pinecone_cookie": "Pinecone Cookie", diff --git a/src/main/resources/assets/unicopia/textures/item/baited_fishing_rod.png b/src/main/resources/assets/unicopia/textures/item/baited_fishing_rod.png new file mode 100644 index 0000000000000000000000000000000000000000..90e3a7f69f651bef042d6bc552fcd7dfc54b7602 GIT binary patch literal 6420 zcmeHKdt4J&7M_qmAb^SnMH(dpEvQX0nY>e?2n30WC4$dgE0bh^C`lj*B%y0pMXS>O zthM%KeNwHDcCA*c)<P>PD0S<ruCKOkwN<PlLbWelWzU@?JhWZQ{<iy<li!&+bMLv| zIp25hOy(}i9ycnI6U)IcEK;ADJ|3>UkcEc8Iqqf~iD98d)MS0WUOyBIg*J#}urP){ zj?u{vVlXi#v_A*d8+;UlES3>WUta+KF~7eI{3L%&5cm>C5Ecp76<`GTT)1uqvn=rC z`WXCUV4?Ic6Fh_*xQ^0iW%CJ<1Q$saFdipl8eFO&RD6j{BU5Q`HAFMkbW{XGu`ui& z=EFuv0Zs@uzzO*QPO0EC{q-VcLOS`;ejjmX1o<#C(OAEqz(xrIwYQyV`x@&tIzyiU zi4YAqwS9r5VWA-*?2zD4Hk%z57Rrg>Mn;5(NA&OArzdwnY}~*Bu{<7skSLKacp`zv zOHvMb0++~SvbaRGRz)O>q%s15Fv7yZBElnLA|qpncwRiw?W1ik78QzWVlo6+P!xk1 z#b`T##X+6H4Epd<K}4CX;1G6b7$+P8KIny%Vg|98!NDvR^v;5Qj1?8!J6<v}q>q7Q z3re`eoR`*yCZukwjUIozWsub9m>b6F+b^bn?BK@}lZFhH$rVbKT9cNp%NUiZADxps zVdA98Q>L2CRDMCBWrovLTITk6%PZ#1e{R9TMU_imUiQkXuf6`p@~XGit$%yN#!Z{w z-@fC6ox65_xM$z~19kNW4>f#x;`5XLICc8W*>e{ze)H|6%Z*pQyW0Bw&09bG_|wn7 z+(x_*&%ohhb|0@O;1$GTF<ETHixK2T#G_cj@sg0<BMoe_q>q4@6Ut3}Y3;V!umtJ& zmT05nIH&I**@eNah#Jl8_Ys@>UomSZ*1_u>7Qtk|<T0bL5!f&HDwEa({hzI;xvDaW zxf-30T&b$WlDcU>j!vqYt=1$}Re%0&eO2YKHHY@XdE<fZ;M_tOLyqgBFR#q6;~FN% z@=CdeX^oOOI_@OHLW7Qr{SISXS!U2-IgtD_&5=(llio}IF8Ksp@-AqmCROcu;i)Hq zBlbItc^uWg0dg-dUHJ;v@U-`YcRgHc_vIOM(I<`NgSm#3fo>f)oAD51S3%v3aZrTp z+LJ>;qMPe(0@a#|c^w+j`X$>QGV49lq41!UP=)1GS*}6nZPZ@|y3eq~x@g2`LK=9! zf&zno!v-#bqJw?&bq2&@rY(kZ^<^ALUzKoRA_Arb_15$Z{|&`DtMn$<pvx;PIcd<j zSJXq|X&LeNDZ_prDe;X9s)N`E%ngm+4O6!1xD!Jb3;^Z5fA?vPg3IUMM`hsj`_KdP zz%HRD%~#Gd13;~Yy$Hb*2?)Fia2mK4hO$38?psof3xQ(1v+m0=ppMoW^_yVUkC~sT zLA)NnjzE|QBpRJI0xh&S8BH#ToPj(VvkTq>SUzrLJ75S(P?8HLn&rTQ2y}5fpy!gm ztS*#8?s5%(e!CVzLmm*Du87fm%Mb0*1psa!<ar4BQlJy%{!i8}e0z2udON^h+s<OD zLYWNyMCMu2(z5kwY5Zb`J>OD9VVGydte3Qz`%`#z)Bm(yFo7Lj={l6X^4*F`)v2M} zwW%AUr<UR$-U?qX8#F|;c<#*F+866KS3JLcOOGvc`9Cu*HhsPBieT_P@sJ%0Tfg2N zccWR`e4cSHWB#W6NxW?N;mpK7sft}gS<|CZSa)CFyX3&wS>w1lExhSJwY2^qs^4CH z^sf5$vg!tGbqcRw4;f|eb+l-zrg7Fqe3+ios&3sqvfrR*zunXH{bP+)`=4FaUqO85 zy@+qiZp!<xVoF2yq794Qs@!b&c*M2$_j#Y+K6TT!Q`%p`)4W#djcc-mOk>oTUhydz zkyLJ8e8xK&m5XH86O;dyuz32Q*T!xkV$^-J=W`lbE81#r*go5La^0KU(b!kwop&W1 zmU~YAS35s>(fW}$h_!0Yu)ke@Pci%KQzx!jh2^Y>B4)qS)~xePk$d)q#A(E!QJiD9 z`f>K&=|3x{M@)5a`ROmuPraj>g%KwwTn$mFSoM|5YMNV{R~<QjPy6}R8|*!s9Q)%I z@KWLX&|}G+?3$c4R%5bTMWoqoq(mNTG5x=pJi=2<nr2cizL6@h*tCKhAAKU=Tg+O) zBzYE|Rh&i@S~ANWR8IN0TvPc>liDm8F`Sd^(Exyza*=$GwaDhwc(ei<R|7t377O@v zh-;=+Fd4q_((De3FB8c`xKQV@lo5jA9DcIHoUa+5p5cdpJFTG5<to;Q#csD-<d%x; zjsmentyYV1LQD`sh!8rxHW%p;+MEdp!iSMgIZX~rvCCq&@ew9zw3oWH0s-{%1NB*p zv$ERZZBD-mpoiE)7K<e!Tx_+9JCAU>bY%eIFX&fCICH(llz2Slw3j+el&*}jxe_`< zm`&~Di%T6vG##@^Ocha92z5eMNtcx9Yq)&`lAyq1Ev83-*j*xBmi!K}y3~exXgZw> zf#L1AU7`cr>0pS;%F?9UO{J(jeY#eF@@vd?lf|r|T~cn$mk>sYP(l$3q0Fo_3Q46x z2DeHnX{HELYF2fE(%YOa(q^I%6o88?fI~=3N;Rog2o)+qE|f{7xX`F1)Iy~~Es^Ep za<vkdbb@%wVS!ai7Im%)L74%{Y$W7zT&573<R+O=rdG>^Ml(eTDXHA7A_<i=UuC4B z%qC5S-C-qRIxSYRfD#wm3g`|bxMpOwUMnC(ct=Zi5$Vc@0b0RWi>=ht(UEJhQaLUX zsVPxPBnqjLP^sl|r9z?X2%1PaoUjrRCcUqrd(gCKfD8yrqIC)YbUSd-q&X<kWq0J- z?L}Ha`;Mr+I}08sGwCAJNf!m6?R%u$z#eHBj%#pV`3Nq{Znorm9~O-s9)2>!!<1)Q zoRHs3H~F5Z9IC{3=esSk(2o+IPd^kI(&VGyB+Do>T_<4qx=e+nt$>2Jhkv;S+AY7b z6mXoNaFfz3l$vpgP^Q8aLY0&<2`NH~6Qq(-QYs&-c66sb-{mG9)W`zR5wwEkN$(!{ zN%TYw=~|1skV3Nnf(db{kWk$(nAlHPjNTam#>wIbG)bldI=y6IoUaXDUhpmyx4#Vi znnAsP<l&!-KQaZM|0qef^nE1PBe}YzK(~S)t*%FMbxVP61wUF{|1-Hb9os3&246vL z*eorXcW*UpwL*+zMx|qI=(nb3!^_YTR-8G-iDBV=kTJ03D`KFL?b2uI*jJdm9(dTn zjX{@SHyy1{ADOE<T(@+@%!+Jo-HRuBEY)#>7xZ2^`c#j;drCcH*6zRC<J{4f;T6Z^ zZ(ckn&zZDKx%<r6vz*80L~zDup87&B$+)>;%+XuTm+l_X{?t0VZOM&0Pt421cHa)K zUUhWqxh4#|J$P%cxu<XTFFIUUetU4s4@dj0inA>-4{mN=eMDKfzqOI`{9oQ#xiT-b zjMuoMc3VSYpOagy{cE;bmhTU-sv15$-!NeU*%EiFHvPc$C&`!@HDON=w4dD>QT=gN z>+rj?ukDFUT0d`DZ);jjI5TDGD~Db?TwxyBYfVyRz1coL_TKrXt%r6^<sFQ`EX!x4 zA6V5GUs+XuCFSMTg^Axpx$mSO7(Ha-Xh~GbH1|Kh-8f_Gw9}2#6DGg<R>_z37tt<O NuN#-XB{gsEzX415U*P}% literal 0 HcmV?d00001 From 74a11086d1ef041096532a5e0bdd5f22acef4291 Mon Sep 17 00:00:00 2001 From: Sollace <sollacea@gmail.com> Date: Thu, 28 Mar 2024 15:32:00 +0000 Subject: [PATCH 07/11] Added recipe for getting pegasus feathers from gryphon feathers --- .../providers/UAdvancementsProvider.java | 1 + .../providers/recipe/URecipeProvider.java | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/UAdvancementsProvider.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/UAdvancementsProvider.java index 994082f4..c34a107b 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/UAdvancementsProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/UAdvancementsProvider.java @@ -63,6 +63,7 @@ public class UAdvancementsProvider extends FabricAdvancementProvider { p.child(Items.CHIPPED_ANVIL).hidden().frame(AdvancementFrame.CHALLENGE).criterion("ding_sun", dingCelestia(Set.of(), Set.of(Race.BAT))).build(consumer, "blasphemy"); p.child(Items.CHIPPED_ANVIL).hidden().frame(AdvancementFrame.CHALLENGE).criterion("ding_sun", dingCelestia(Set.of(Race.BAT), Set.of())).build(consumer, "sweet_sweet_revenge"); }); + root.child(UItems.BAITED_FISHING_ROD).showToast().announce().criterion("has_baited_fishing_rod", hasItems(UItems.BAITED_FISHING_ROD)).build(consumer, "bait"); root.child(UItems.OATS).showToast().announce().criterion("has_oats", hasItems(UItems.OATS)).build(consumer, "oats_so_easy"); root.child(Items.HAY_BLOCK).showToast().announce().criterion("eat_hay", ConsumeItemCriterion.Conditions.item(Items.HAY_BLOCK)).build(consumer, "what_the_hay"); root.child(UItems.COPPER_HORSE_SHOE).showToast().announce().criterion("has_horseshoe", hasItems(UTags.HORSE_SHOES)).build(consumer, "blacksmith").children(p -> { diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java index 064c6493..2161b97a 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java @@ -76,13 +76,6 @@ public class URecipeProvider extends FabricRecipeProvider { .input(ConventionalItemTags.GLASS_BLOCKS) .input(UItems.SUNGLASSES).criterion("has_broken_sunglasses", conditionsFromItem(UItems.BROKEN_SUNGLASSES)) .offerTo(exporter, convertBetween(UItems.SUNGLASSES, UItems.BROKEN_SUNGLASSES)); - - // fishing - ShapelessRecipeJsonBuilder.create(RecipeCategory.TOOLS, UItems.BAITED_FISHING_ROD) - .input(Items.FISHING_ROD).criterion(hasItem(Items.FISHING_ROD), conditionsFromItem(Items.FISHING_ROD)) - .input(UItems.WHEAT_WORMS) - .group("fishing_rod") - .offerTo(exporter); } private void generateVanillaRecipeExtensions(Consumer<RecipeJsonProvider> exporter) { @@ -469,6 +462,12 @@ public class URecipeProvider extends FabricRecipeProvider { // worms offerReversibleCompactingRecipes(exporter, RecipeCategory.BUILDING_BLOCKS, UItems.WHEAT_WORMS, RecipeCategory.BUILDING_BLOCKS, UBlocks.WORM_BLOCK); + // fishing + ShapelessRecipeJsonBuilder.create(RecipeCategory.TOOLS, UItems.BAITED_FISHING_ROD) + .input(Items.FISHING_ROD).criterion(hasItem(Items.FISHING_ROD), conditionsFromItem(Items.FISHING_ROD)) + .input(UItems.WHEAT_WORMS) + .group("fishing_rod") + .offerTo(exporter); // utility ShapedRecipeJsonBuilder.create(RecipeCategory.MISC, Items.DIRT) @@ -480,6 +479,15 @@ public class URecipeProvider extends FabricRecipeProvider { offerShapelessRecipe(exporter, Items.BONE_MEAL, UTags.SHELLS, "bonemeal", 3); + // pegasus feathers for non pegasi + ShapedRecipeJsonBuilder.create(RecipeCategory.MISC, UItems.PEGASUS_FEATHER) + .input('*', Items.GHAST_TEAR).criterion("has_ghast_tear", conditionsFromItem(Items.GHAST_TEAR)) + .input('#', UItems.GRYPHON_FEATHER).criterion("has_feather", conditionsFromItem(UItems.GRYPHON_FEATHER)) + .pattern("***") + .pattern("*#*") + .pattern("***") + .offerTo(exporter); + offer2x2CompactingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, Items.COBBLESTONE, UItems.ROCK); offerReversibleCompactingRecipesWithReverseRecipeGroup(exporter, RecipeCategory.MISC, UItems.PEBBLES, RecipeCategory.BUILDING_BLOCKS, Blocks.GRAVEL, convertBetween(UItems.PEBBLES, Blocks.GRAVEL), "pebbles"); offerShapelessRecipe(exporter, UItems.PEBBLES, Blocks.SUSPICIOUS_GRAVEL, "pebbles", 9); From 82b74ecb18befd405ec8c4037aadc011ed3c7339 Mon Sep 17 00:00:00 2001 From: Sollace <sollacea@gmail.com> Date: Thu, 28 Mar 2024 15:32:21 +0000 Subject: [PATCH 08/11] Added feather touch enchantment --- .../unicopia/EquineContext.java | 11 ++++++++- .../unicopia/block/cloud/CloudBlock.java | 2 +- .../block/cloud/CloudPillarBlock.java | 19 +-------------- .../unicopia/entity/Living.java | 7 +++++- .../unicopia/item/cloud/CloudBlockItem.java | 3 +-- .../item/enchantment/UEnchantments.java | 9 ++++++++ .../mixin/MixinEntityShapeContext.java | 23 +++---------------- .../resources/assets/unicopia/lang/en_us.json | 4 ++++ .../tags/items/groups/earth_pony.json | 1 + 9 files changed, 36 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/EquineContext.java b/src/main/java/com/minelittlepony/unicopia/EquineContext.java index 2bd9bf0b..d9dd0a9f 100644 --- a/src/main/java/com/minelittlepony/unicopia/EquineContext.java +++ b/src/main/java/com/minelittlepony/unicopia/EquineContext.java @@ -25,11 +25,16 @@ public interface EquineContext { return getCompositeRace().canInteractWithClouds(); } + default boolean hasFeatherTouch() { + return false; + } + static EquineContext of(ShapeContext context) { if (context == ShapeContext.absent()) { return Unicopia.SIDE.getPony().map(EquineContext.class::cast).orElse(ABSENT); } - return context instanceof EquineContext c ? c : ABSENT; + EquineContext result = context instanceof Container c ? c.get() : ABSENT; + return result == null ? ABSENT : result; } static EquineContext of(ItemUsageContext context) { @@ -42,4 +47,8 @@ public interface EquineContext { } return MoreObjects.firstNonNull(Equine.of(entity).orElse(null), ABSENT); } + + interface Container { + EquineContext get(); + } } diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBlock.java index 490d7e4d..2479021c 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBlock.java +++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudBlock.java @@ -171,7 +171,7 @@ public class CloudBlock extends Block implements CloudLike { } protected boolean canInteract(BlockState state, BlockView world, BlockPos pos, EquineContext context) { - return context.collidesWithClouds(); + return context.collidesWithClouds() || context.hasFeatherTouch(); } @SuppressWarnings("deprecation") diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudPillarBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudPillarBlock.java index baeb2f16..690cbf75 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudPillarBlock.java +++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/CloudPillarBlock.java @@ -63,24 +63,7 @@ public class CloudPillarBlock extends CloudBlock { @Override protected VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context, EquineContext equineContext) { - var axis = state.get(AXIS); - - int[] offsets = { axis.choose(1, 0, 0), axis.choose(0, 1, 0), axis.choose(0, 0, 1) }; - float capOffset = 11F / 16F; - VoxelShape core = Block.createCuboidShape( - axis.choose(0, 1, 1), axis.choose(1, 0, 1), axis.choose(1, 1, 0), - 16 - axis.choose(0, 1, 1), 16 - axis.choose(1, 0, 1), 16 - axis.choose(1, 1, 0) - ); - VoxelShape foot = Block.createCuboidShape(0, 0, 0, 16 - (11 * offsets[0]), 16 - (11 * offsets[1]), 16 - (11 * offsets[2])); - VoxelShape cap = foot.offset(capOffset * offsets[0], capOffset * offsets[1], capOffset * offsets[2]); - var temp = new VoxelShape[] { - core, - VoxelShapes.union(core, foot), - VoxelShapes.union(core, cap), - VoxelShapes.union(core, cap, foot) - }; - return temp[(state.get(TOP) ? 0 : 2) + (state.get(BOTTOM) ? 0 : 1)]; - //return SHAPES.apply(state.get(AXIS))[(state.get(TOP) ? 0 : 2) + (state.get(BOTTOM) ? 0 : 1)]; + return SHAPES.apply(state.get(AXIS))[(state.get(TOP) ? 0 : 2) + (state.get(BOTTOM) ? 0 : 1)]; } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Living.java b/src/main/java/com/minelittlepony/unicopia/entity/Living.java index 86ea4261..fecfe073 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Living.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Living.java @@ -30,6 +30,7 @@ import com.minelittlepony.unicopia.input.Heuristic; import com.minelittlepony.unicopia.input.Interactable; import com.minelittlepony.unicopia.item.GlassesItem; import com.minelittlepony.unicopia.item.UItems; +import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.network.datasync.EffectSync; import com.minelittlepony.unicopia.network.datasync.Transmittable; import com.minelittlepony.unicopia.particle.ParticleUtils; @@ -198,6 +199,11 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste return vehicle != null && getCarrierId().filter(vehicle.getUuid()::equals).isPresent(); } + @Override + public boolean hasFeatherTouch() { + return EnchantmentHelper.getEquipmentLevel(UEnchantments.FEATHER_TOUCH, entity) > 0; + } + @Override public boolean beforeUpdate() { if (EffectUtils.getAmplifier(entity, UEffects.PARALYSIS) > 1 && entity.getVelocity().horizontalLengthSquared() > 0) { @@ -205,7 +211,6 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste updateVelocity(); } - //transportation.updateSupportingEntity(); return false; } diff --git a/src/main/java/com/minelittlepony/unicopia/item/cloud/CloudBlockItem.java b/src/main/java/com/minelittlepony/unicopia/item/cloud/CloudBlockItem.java index f208e723..d4b34d81 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/cloud/CloudBlockItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/cloud/CloudBlockItem.java @@ -19,8 +19,7 @@ import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class CloudBlockItem -extends BlockItem { +public class CloudBlockItem extends BlockItem { public CloudBlockItem(Block block, Item.Settings settings) { super(block, settings); } diff --git a/src/main/java/com/minelittlepony/unicopia/item/enchantment/UEnchantments.java b/src/main/java/com/minelittlepony/unicopia/item/enchantment/UEnchantments.java index 2085f490..a5143773 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/enchantment/UEnchantments.java +++ b/src/main/java/com/minelittlepony/unicopia/item/enchantment/UEnchantments.java @@ -40,6 +40,15 @@ public interface UEnchantments { */ Enchantment PADDED = register("padded", new SimpleEnchantment(Options.armor().rarity(Rarity.UNCOMMON).maxLevel(3).traded().table())); + /** + * Allows non-flying races to mine and interact with cloud blocks + * + * Appears in: + * - Trades + * - Enchanting Table + */ + Enchantment FEATHER_TOUCH = register("feather_touch", new SimpleEnchantment(Options.create(EnchantmentTarget.BREAKABLE, UEnchantmentValidSlots.HANDS).rarity(Rarity.UNCOMMON).traded().table())); + /** * Heavy players move more slowly but are less likely to be flung around wildly. * diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntityShapeContext.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntityShapeContext.java index 9e375403..b0930e75 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntityShapeContext.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntityShapeContext.java @@ -9,15 +9,13 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import com.minelittlepony.unicopia.EquineContext; -import com.minelittlepony.unicopia.Race; - import net.minecraft.block.EntityShapeContext; import net.minecraft.entity.Entity; import net.minecraft.fluid.FluidState; import net.minecraft.item.ItemStack; @Mixin(EntityShapeContext.class) -abstract class MixinEntityShapeContext implements EquineContext { +abstract class MixinEntityShapeContext implements EquineContext.Container { private EquineContext equineContext; @Inject(method = "<init>", at = @At("TAIL")) @@ -26,22 +24,7 @@ abstract class MixinEntityShapeContext implements EquineContext { } @Override - public Race getSpecies() { - return equineContext.getSpecies(); - } - - @Override - public Race.Composite getCompositeRace() { - return equineContext.getCompositeRace(); - } - - @Override - public float getCloudWalkingStrength() { - return equineContext.getCloudWalkingStrength(); - } - - @Override - public boolean collidesWithClouds() { - return equineContext.collidesWithClouds(); + public EquineContext get() { + return equineContext; } } diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 35f8c79c..23edf9ea 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -1339,6 +1339,8 @@ "enchantment.unicopia.heart_bound.desc": "Causes an item to stay with you after you die", "enchantment.unicopia.consumption": "Consumption", "enchantment.unicopia.consumption.desc": "Converts drops mined using a tool into raw experience", + "enchantment.unicopia.feather_touch": "Feather Touch", + "enchantment.unicopia.feather_touch.desc": "Allows breaking and placing cloud blocks when held", "commands.race.success.self": "Set own race to %1$s", "commands.race.success": "%1$s changed race to %2$s", @@ -1615,6 +1617,8 @@ "advancements.unicopia.lightning_bug.description": "Attract 10 lightning strikes as a changeling", "advancements.unicopia.wonder_bolt.title": "Wonder Bolt", "advancements.unicopia.wonder_bolt.description": "Attract 10 lightning strikes", + "advancements.unicopia.bait.title": "Is This Bait?", + "advancements.unicopia.bait.description": "Put some worms on a hook", "advancements.unicopia.jar.title": "Oh wow. What's this?", "advancements.unicopia.jar.description": "Find an empty jar", "advancements.unicopia.gotcha.title": "Got'cha!", diff --git a/src/main/resources/data/unicopia/tags/items/groups/earth_pony.json b/src/main/resources/data/unicopia/tags/items/groups/earth_pony.json index e3ec6fc3..51b9f951 100644 --- a/src/main/resources/data/unicopia/tags/items/groups/earth_pony.json +++ b/src/main/resources/data/unicopia/tags/items/groups/earth_pony.json @@ -58,6 +58,7 @@ "unicopia:crispy_hay_fries", "unicopia:horse_shoe_fries", "unicopia:wheat_worms", + "unicopia:baited_fishing_rod", "unicopia:worm_block", "unicopia:muffin", "unicopia:acorn", From 11f0bbc4e472f3f8119a895ad583b15d1a85b6b7 Mon Sep 17 00:00:00 2001 From: Sollace <sollacea@gmail.com> Date: Thu, 28 Mar 2024 16:23:12 +0000 Subject: [PATCH 09/11] Move advancements back down the tree and add some advancements for seaponies and hippogriffs --- .../unicopia/ability/ChangeFormAbility.java | 2 + .../unicopia/advancement/UCriteria.java | 1 + .../providers/AdvancementDisplayBuilder.java | 5 + .../providers/UAdvancementsProvider.java | 95 ++++++++++--------- .../resources/assets/unicopia/lang/en_us.json | 9 +- 5 files changed, 67 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java index 64fabfa4..67aa667f 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.data.Hit; +import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.FriendshipBraceletItem; @@ -79,6 +80,7 @@ public class ChangeFormAbility implements Ability<Hit> { Race actualRace = isTransforming ? target.getSpecies() : Race.UNSET; target.setSpecies(supressed.or(player.getCompositeRace().potential())); target.setSuppressedRace(actualRace); + UCriteria.SEAPONY_TRANSITION.trigger(target.asEntity()); } }); diff --git a/src/main/java/com/minelittlepony/unicopia/advancement/UCriteria.java b/src/main/java/com/minelittlepony/unicopia/advancement/UCriteria.java index 339c0aac..06adc40c 100644 --- a/src/main/java/com/minelittlepony/unicopia/advancement/UCriteria.java +++ b/src/main/java/com/minelittlepony/unicopia/advancement/UCriteria.java @@ -30,6 +30,7 @@ public interface UCriteria { CustomEventCriterion.Trigger RIDE_BALLOON = CUSTOM_EVENT.createTrigger("ride_balloon"); CustomEventCriterion.Trigger CONSTRUCT_BALLOON = CUSTOM_EVENT.createTrigger("construct_balloon"); CustomEventCriterion.Trigger TELEPORT_ABOVE_WORLD = CUSTOM_EVENT.createTrigger("teleport_above_world"); + CustomEventCriterion.Trigger SEAPONY_TRANSITION = CUSTOM_EVENT.createTrigger("seapony_transition"); static void bootstrap() { } } diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/AdvancementDisplayBuilder.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/AdvancementDisplayBuilder.java index 40f5d832..cbacbcd3 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/AdvancementDisplayBuilder.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/AdvancementDisplayBuilder.java @@ -76,6 +76,11 @@ public class AdvancementDisplayBuilder { return this; } + public AdvancementDisplayBuilder doNotAnnounce() { + this.announce = false; + return this; + } + public AdvancementDisplayBuilder group(String group) { this.group = group; return this; diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/UAdvancementsProvider.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/UAdvancementsProvider.java index c34a107b..e0c2c349 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/UAdvancementsProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/UAdvancementsProvider.java @@ -51,58 +51,21 @@ public class UAdvancementsProvider extends FabricAdvancementProvider { createTribeRootAdvancement(consumer, root, Race.EARTH).children(consumer, this::generateEarthTribeAdvancementsTree); createTribeRootAdvancement(consumer, root, Race.BAT).children(consumer, this::generateBatTribeAdvancementsTree); createTribeRootAdvancement(consumer, root, Race.PEGASUS).children(consumer, this::generatePegasusTribeAdvancementsTree); - createTribeRootAdvancement(consumer, root, Race.UNICORN).children(consumer, this::generateUnicornTribeAdvancementsTree); - - root.child(UItems.DRAGON_BREATH_SCROLL).showToast().announce().criterion("has_scroll", hasItems(UItems.DRAGON_BREATH_SCROLL)).build(consumer, "take_a_note").children(p -> { - p.child(UItems.DRAGON_BREATH_SCROLL).criterion("send_book", dragonScroll(false, Items.WRITTEN_BOOK)).build(consumer, "dear_princess") - .child(UItems.DRAGON_BREATH_SCROLL).criterion("send_scroll", dragonScroll(false, UItems.DRAGON_BREATH_SCROLL)).build(consumer, "i_await_your_reply"); - p.child(UItems.IMPORTED_OATS).hidden().frame(AdvancementFrame.CHALLENGE) - .criterion("send_oats", dragonScroll(false, UItems.OATS, UItems.IMPORTED_OATS)) - .criterion("receieve_oats", dragonScroll(true, UItems.IMPORTED_OATS)) - .criteriaMerger(CriterionMerger.OR).build(consumer, "imported_oats"); - p.child(Items.CHIPPED_ANVIL).hidden().frame(AdvancementFrame.CHALLENGE).criterion("ding_sun", dingCelestia(Set.of(), Set.of(Race.BAT))).build(consumer, "blasphemy"); - p.child(Items.CHIPPED_ANVIL).hidden().frame(AdvancementFrame.CHALLENGE).criterion("ding_sun", dingCelestia(Set.of(Race.BAT), Set.of())).build(consumer, "sweet_sweet_revenge"); - }); - root.child(UItems.BAITED_FISHING_ROD).showToast().announce().criterion("has_baited_fishing_rod", hasItems(UItems.BAITED_FISHING_ROD)).build(consumer, "bait"); - root.child(UItems.OATS).showToast().announce().criterion("has_oats", hasItems(UItems.OATS)).build(consumer, "oats_so_easy"); - root.child(Items.HAY_BLOCK).showToast().announce().criterion("eat_hay", ConsumeItemCriterion.Conditions.item(Items.HAY_BLOCK)).build(consumer, "what_the_hay"); - root.child(UItems.COPPER_HORSE_SHOE).showToast().announce().criterion("has_horseshoe", hasItems(UTags.HORSE_SHOES)).build(consumer, "blacksmith").children(p -> { - p.child(UItems.IRON_HORSE_SHOE).criterion("has_iron_horseshoe", hasItems(UItems.IRON_HORSE_SHOE)).build(consumer, "change_of_shoes") - .child(UItems.GOLDEN_HORSE_SHOE).criterion("has_gold_horseshoe", hasItems(UItems.GOLDEN_HORSE_SHOE)).build(consumer, "fashionably_expensive") - .child(UItems.NETHERITE_HORSE_SHOE).criterion("has_netherite_horseshoe", hasItems(UItems.NETHERITE_HORSE_SHOE)).build(consumer, "overkill"); - p.child(UItems.IRON_HORSE_SHOE).hidden().frame(AdvancementFrame.CHALLENGE).criterion("killed_entity_with_horseshoe", killWithItems(UTags.FROM_HORSESHOES)).build(consumer, "dead_ringer"); - }); - root.child(UItems.PINECONE).showToast().announce().frame(AdvancementFrame.CHALLENGE).criterion("eat_pinecone", ConsumeItemCriterion.Conditions.item(UItems.PINECONE)).build(consumer, "eat_pinecone"); - root.child(UItems.OAK_BASKET).showToast().criterion("has_basket", hasItems(UTags.BASKETS)).build(consumer, "basket_case") - .child(Items.LANTERN).showToast().criterion("construct_balloon", CustomEventCriterion.create("construct_balloon")).build(consumer, "aeronaut") - .child(UItems.GIANT_BALLOON).showToast().announce().frame(AdvancementFrame.CHALLENGE).criterion("ride_balloon", CustomEventCriterion.create("ride_balloon")).build(consumer, "travelling_in_style"); - root.child(UItems.MUFFIN).showToast().announce().hidden().criterion("has_muffin", hasItems(UItems.MUFFIN)).build(consumer, "baked_bads"); - root.child(UItems.HORSE_SHOE_FRIES).showToast().announce().criterion("has_horse_shoe_fries", hasItems(UItems.HORSE_SHOE_FRIES)).build(consumer, "lucky"); - root.child(UItems.TOAST).showToast().announce().criterion("has_toast", hasItems(UItems.TOAST)).build(consumer, "toast") - .child(UItems.BURNED_TOAST).hidden().criterion("has_burned_toast", hasItems(UItems.BURNED_TOAST)).build(consumer, "burn_toast"); - root.child(UItems.GREEN_APPLE).showToast().announce().criterion("has_apple", hasItems(UTags.FRESH_APPLES)).build(consumer, "apple_route").children(p -> { - p.child(UItems.SWEET_APPLE).criterion("has_all_apples", hasItems(Items.APPLE, UItems.GREEN_APPLE, UItems.SWEET_APPLE, UItems.SOUR_APPLE, UItems.ROTTEN_APPLE, UItems.ZAP_APPLE, UItems.COOKED_ZAP_APPLE, Items.GOLDEN_APPLE)).build(consumer, "sweet_apple_acres"); - p.child(UItems.ZAP_BULB).criterion("has_zap_apple", hasItems(UItems.ZAP_APPLE)).build(consumer, "trick_apple").children(pp -> { - pp.child(UItems.ZAP_APPLE).hidden().criterion("eat_trick_apple", CustomEventCriterion.createFlying("eat_trick_apple")).build(consumer, "eat_trick_apple"); - pp.child(UItems.ZAP_APPLE).hidden().criterion("feed_trick_apple", CustomEventCriterion.createFlying("feed_trick_apple")).build(consumer, "feed_trick_apple"); - }); - p.child(UItems.JUICE).criterion("has_juice", hasItems(UItems.JUICE)).build(consumer, "juice") - .child(UItems.BURNED_JUICE).hidden().criterion("has_burned_juice", hasItems(UItems.BURNED_JUICE)).build(consumer, "burn_juice") - .child(UItems.CIDER).visible().criterion("has_cider", hasItems(UItems.CIDER)).rewards(AdvancementRewards.Builder.experience(12)).build(consumer, "brew_cider"); - }); + createTribeRootAdvancement(consumer, root, Race.UNICORN, Race.ALICORN).children(consumer, this::generateUnicornTribeAdvancementsTree); + createTribeRootAdvancement(consumer, root, Race.HIPPOGRIFF, Race.SEAPONY).children(consumer, this::generateHippogrifTribeAdvancementsTree); }); generateEnchantmentsAdvancementsTree(consumer); } - private AdvancementDisplayBuilder.Parent createTribeRootAdvancement(Consumer<Advancement> consumer, AdvancementDisplayBuilder.Parent root, Race race) { + private AdvancementDisplayBuilder.Parent createTribeRootAdvancement(Consumer<Advancement> consumer, AdvancementDisplayBuilder.Parent root, Race race, Race...extra) { AdvancementDisplayBuilder builder = root.child(Registries.ITEM.get(race.getId().withSuffixedPath("_badge"))).showToast().announce().group(race.getId().getPath()) .criterion("be_" + race.getId().getPath(), new RaceChangeCriterion.Conditions(LootContextPredicate.EMPTY, race)); - if (race == Race.UNICORN) { - builder - .criterion("be_alicorn", new RaceChangeCriterion.Conditions(LootContextPredicate.EMPTY, Race.ALICORN)) - .criteriaMerger(CriterionMerger.OR); + if (extra.length > 0) { + for (Race r : extra) { + builder.criterion("be_" + r.getId().getPath(), new RaceChangeCriterion.Conditions(LootContextPredicate.EMPTY, r)); + } } return builder.build(consumer, race.getId().getPath() + "_route"); @@ -113,6 +76,33 @@ public class UAdvancementsProvider extends FabricAdvancementProvider { p.child(UItems.PEBBLES).criterion("killed_entity_with_rock", killWithItems(UTags.FROM_ROCKS)).build(consumer, "sticks_and_stones"); p.child(UItems.WEIRD_ROCK).hidden().criterion("has_rock", hasItems(UItems.WEIRD_ROCK)).build(consumer, "thats_unusual"); }); + + parent.child(UItems.OATS).criterion("has_oats", hasItems(UItems.OATS)).build(consumer, "oats_so_easy"); + parent.child(Items.HAY_BLOCK).criterion("eat_hay", ConsumeItemCriterion.Conditions.item(Items.HAY_BLOCK)).build(consumer, "what_the_hay"); + parent.child(UItems.COPPER_HORSE_SHOE).criterion("has_horseshoe", hasItems(UTags.HORSE_SHOES)).build(consumer, "blacksmith").children(p -> { + p.child(UItems.IRON_HORSE_SHOE).criterion("has_iron_horseshoe", hasItems(UItems.IRON_HORSE_SHOE)).build(consumer, "change_of_shoes") + .child(UItems.GOLDEN_HORSE_SHOE).criterion("has_gold_horseshoe", hasItems(UItems.GOLDEN_HORSE_SHOE)).build(consumer, "fashionably_expensive") + .child(UItems.NETHERITE_HORSE_SHOE).criterion("has_netherite_horseshoe", hasItems(UItems.NETHERITE_HORSE_SHOE)).build(consumer, "overkill"); + p.child(UItems.IRON_HORSE_SHOE).hidden().frame(AdvancementFrame.CHALLENGE).criterion("killed_entity_with_horseshoe", killWithItems(UTags.FROM_HORSESHOES)).build(consumer, "dead_ringer"); + }); + parent.child(UItems.PINECONE).frame(AdvancementFrame.CHALLENGE).criterion("eat_pinecone", ConsumeItemCriterion.Conditions.item(UItems.PINECONE)).build(consumer, "eat_pinecone"); + parent.child(UItems.OAK_BASKET).doNotAnnounce().criterion("has_basket", hasItems(UTags.BASKETS)).build(consumer, "basket_case") + .child(Items.LANTERN).criterion("construct_balloon", CustomEventCriterion.create("construct_balloon")).build(consumer, "aeronaut") + .child(UItems.GIANT_BALLOON).announce().frame(AdvancementFrame.CHALLENGE).criterion("ride_balloon", CustomEventCriterion.create("ride_balloon")).build(consumer, "travelling_in_style"); + parent.child(UItems.MUFFIN).hidden().criterion("has_muffin", hasItems(UItems.MUFFIN)).build(consumer, "baked_bads"); + parent.child(UItems.HORSE_SHOE_FRIES).criterion("has_horse_shoe_fries", hasItems(UItems.HORSE_SHOE_FRIES)).build(consumer, "lucky"); + parent.child(UItems.TOAST).criterion("has_toast", hasItems(UItems.TOAST)).build(consumer, "toast") + .child(UItems.BURNED_TOAST).hidden().criterion("has_burned_toast", hasItems(UItems.BURNED_TOAST)).build(consumer, "burn_toast"); + parent.child(UItems.GREEN_APPLE).criterion("has_apple", hasItems(UTags.FRESH_APPLES)).build(consumer, "apple_route").children(p -> { + p.child(UItems.SWEET_APPLE).criterion("has_all_apples", hasItems(Items.APPLE, UItems.GREEN_APPLE, UItems.SWEET_APPLE, UItems.SOUR_APPLE, UItems.ROTTEN_APPLE, UItems.ZAP_APPLE, UItems.COOKED_ZAP_APPLE, Items.GOLDEN_APPLE)).build(consumer, "sweet_apple_acres"); + p.child(UItems.ZAP_BULB).criterion("has_zap_apple", hasItems(UItems.ZAP_APPLE)).build(consumer, "trick_apple").children(pp -> { + pp.child(UItems.ZAP_APPLE).hidden().criterion("eat_trick_apple", CustomEventCriterion.createFlying("eat_trick_apple")).build(consumer, "eat_trick_apple"); + pp.child(UItems.ZAP_APPLE).hidden().criterion("feed_trick_apple", CustomEventCriterion.createFlying("feed_trick_apple")).build(consumer, "feed_trick_apple"); + }); + p.child(UItems.JUICE).criterion("has_juice", hasItems(UItems.JUICE)).build(consumer, "juice") + .child(UItems.BURNED_JUICE).hidden().criterion("has_burned_juice", hasItems(UItems.BURNED_JUICE)).build(consumer, "burn_juice") + .child(UItems.CIDER).visible().criterion("has_cider", hasItems(UItems.CIDER)).rewards(AdvancementRewards.Builder.experience(12)).build(consumer, "brew_cider"); + }); } private void generatePegasusTribeAdvancementsTree(Consumer<Advancement> consumer, AdvancementDisplayBuilder.Parent parent) { @@ -169,6 +159,17 @@ public class UAdvancementsProvider extends FabricAdvancementProvider { p.child(Items.WATER_BUCKET).criterion("split_sea", CustomEventCriterion.create("split_sea")).rewards(AdvancementRewards.Builder.experience(105)).build(consumer, "split_the_sea"); }); + parent.child(UItems.DRAGON_BREATH_SCROLL).showToast().announce().criterion("has_scroll", hasItems(UItems.DRAGON_BREATH_SCROLL)).build(consumer, "take_a_note").children(p -> { + p.child(UItems.DRAGON_BREATH_SCROLL).criterion("send_book", dragonScroll(false, Items.WRITTEN_BOOK)).build(consumer, "dear_princess") + .child(UItems.DRAGON_BREATH_SCROLL).criterion("send_scroll", dragonScroll(false, UItems.DRAGON_BREATH_SCROLL)).build(consumer, "i_await_your_reply"); + p.child(UItems.IMPORTED_OATS).hidden().frame(AdvancementFrame.CHALLENGE) + .criterion("send_oats", dragonScroll(false, UItems.OATS, UItems.IMPORTED_OATS)) + .criterion("receieve_oats", dragonScroll(true, UItems.IMPORTED_OATS)) + .criteriaMerger(CriterionMerger.OR).build(consumer, "imported_oats"); + p.child(Items.CHIPPED_ANVIL).hidden().frame(AdvancementFrame.CHALLENGE).criterion("ding_sun", dingCelestia(Set.of(), Set.of(Race.BAT))).build(consumer, "blasphemy"); + p.child(Items.CHIPPED_ANVIL).hidden().frame(AdvancementFrame.CHALLENGE).criterion("ding_sun", dingCelestia(Set.of(Race.BAT), Set.of())).build(consumer, "sweet_sweet_revenge"); + }); + parent.child(UItems.PEGASUS_AMULET).hidden().frame(AdvancementFrame.CHALLENGE).criterion("teleport_above_world", CustomEventCriterion.create("teleport_above_world")).rewards(AdvancementRewards.Builder.experience(100)).build(consumer, "a_falling_wizard"); } @@ -182,6 +183,12 @@ public class UAdvancementsProvider extends FabricAdvancementProvider { }); } + private void generateHippogrifTribeAdvancementsTree(Consumer<Advancement> consumer, AdvancementDisplayBuilder.Parent parent) { + parent.child(UItems.BAITED_FISHING_ROD).showToast().announce().criterion("has_baited_fishing_rod", hasItems(UItems.BAITED_FISHING_ROD)).build(consumer, "bait"); + parent.child(UItems.PEARL_NECKLACE).showToast().announce().criterion("seapony_transition", new CustomEventCriterion.Conditions(LootContextPredicate.EMPTY, "seapony_transition", RacePredicate.of(Set.of(Race.SEAPONY), Set.of()), null, 1)).build(consumer, "shoo_be_doo") + .child(UItems.PEARL_NECKLACE).showToast().announce().criterion("seapony_transition", new CustomEventCriterion.Conditions(LootContextPredicate.EMPTY, "seapony_transition", RacePredicate.of(Set.of(), Set.of(Race.SEAPONY)), null, 1)).build(consumer, "shoo_be_done"); + } + private void generateEnchantmentsAdvancementsTree(Consumer<Advancement> consumer) { AdvancementDisplayBuilder.create(Items.NETHERITE_SCRAP).showToast().announce() .criterion("enchant_with_consumption", enchant(UEnchantments.CONSUMPTION)) diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 23edf9ea..6b3a168b 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -1685,7 +1685,7 @@ "advancements.unicopia.blasphemy.title": "Blasphemy!", "advancements.unicopia.blasphemy.description": "Ding Celestia on the noggin. Oops!", - "advancements.unicopia.earth_route.title": "Path of the Pony", + "advancements.unicopia.earth_route.title": "Hearth of the Earth", "advancements.unicopia.earth_route.description": "Join the Apple Clan", "advancements.unicopia.sticks_and_stones.title": "Sticks and Stones", "advancements.unicopia.sticks_and_stones.description": "Kill a mob by throwing rocks at it", @@ -1766,6 +1766,13 @@ "advancements.unicopia.love_is_power.title": "Love is Power", "advancements.unicopia.love_is_power.description": "Banish King Sombra with a crystal heart", + "advancements.unicopia.hippogriff_route.title": "Splash of Seaquestria", + "advancements.unicopia.hippogriff_route.description": "Join the Hippogriff's nest", + "advancements.unicopia.shoo_be_doo.title": "Shoo Be Doo!", + "advancements.unicopia.shoo_be_doo.description": "Use a pearl necklace to turn into a sea creature", + "advancements.unicopia.shoo_be_done.title": "Shoo Be Done!", + "advancements.unicopia.shoo_be_done.description": "Use a pearl necklace to turn back to normal", + "unicopia.toast.discoveries.title": "New Discoveries!", "unicopia.toast.discoveries.description": "Check your spellbook" } From 5109b67de20943d865395e08692f232f7e791d05 Mon Sep 17 00:00:00 2001 From: Sollace <sollacea@gmail.com> Date: Thu, 28 Mar 2024 16:23:42 +0000 Subject: [PATCH 10/11] Revert seaponies back to normal if we're not able to respawn them underwater --- .../com/minelittlepony/unicopia/entity/player/Pony.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java index 0c78666f..10efd22d 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -388,6 +388,13 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update boolean mustAvoidAir = getCompositeRace().includes(Race.SEAPONY) && !sw.getFluidState(getOrigin()).isIn(FluidTags.WATER); if (mustAvoidSun || mustAvoidAir) { SpawnLocator.selectSpawnPosition(sw, entity, mustAvoidAir, mustAvoidSun); + if ((mustAvoidAir && !sw.getFluidState(getOrigin()).isIn(FluidTags.WATER)) + || (mustAvoidSun && MeteorlogicalUtil.isPositionExposedToSun(sw, getOrigin()))) { + Race suppressedRace = getSuppressedRace(); + if (suppressedRace != Race.UNSET) { + setSpecies(suppressedRace); + } + } } } ticksSunImmunity = INITIAL_SUN_IMMUNITY; From 10dc8ce6d76998e0074932d47fb63b6d1497b440 Mon Sep 17 00:00:00 2001 From: Sollace <sollacea@gmail.com> Date: Thu, 28 Mar 2024 17:05:04 +0000 Subject: [PATCH 11/11] Check against offline players when trying to see if a spell can modify blocks at a location. Closes #283 --- .../unicopia/ability/magic/Caster.java | 12 ++++- .../magic/spell/effect/HydrophobicSpell.java | 12 +++-- .../magic/spell/effect/InfernoSpell.java | 3 +- .../server/world/OfflinePlayerCache.java | 44 +++++++++++++++++++ 4 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/server/world/OfflinePlayerCache.java diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java index 2d662cb1..dc6f4ec3 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java @@ -14,12 +14,14 @@ import com.minelittlepony.unicopia.entity.damage.UDamageSources; import com.minelittlepony.unicopia.particle.ParticleSource; import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.server.world.ModificationType; +import com.minelittlepony.unicopia.server.world.OfflinePlayerCache; import com.minelittlepony.unicopia.util.SoundEmitter; import com.minelittlepony.unicopia.util.VecHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.GameRules; @@ -67,10 +69,18 @@ public interface Caster<E extends Entity> extends } if (getMaster() instanceof PlayerEntity player) { - if (!asWorld().canPlayerModifyAt(player, pos)) { + if (!player.canModifyBlocks() || !asWorld().canPlayerModifyAt(player, pos)) { return false; } } else { + if (asWorld() instanceof ServerWorld sw) { + @Nullable + PlayerEntity player = OfflinePlayerCache.getOfflinePlayer(sw, getMasterId().orElse(null)); + if (player != null && !player.canModifyBlocks() || !sw.canPlayerModifyAt(player, pos)) { + return false; + } + } + if (!asWorld().getGameRules().getBoolean(GameRules.DO_MOB_GRIEFING)) { return false; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java index d88d9dd0..ce21955f 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java @@ -61,7 +61,9 @@ public class HydrophobicSpell extends AbstractSpell { storedFluidPositions.removeIf(entry -> { if (!area.isPointInside(Vec3d.ofCenter(entry.pos()))) { - entry.restore(world); + if (source.canModifyAt(entry.pos())) { + entry.restore(world); + } return true; } @@ -72,7 +74,7 @@ public class HydrophobicSpell extends AbstractSpell { pos = new BlockPos(pos); BlockState state = world.getBlockState(pos); - if (state.getFluidState().isIn(affectedFluid)) { + if (source.canModifyAt(pos) && state.getFluidState().isIn(affectedFluid)) { Block block = state.getBlock(); if (block instanceof FluidBlock) { @@ -95,7 +97,7 @@ public class HydrophobicSpell extends AbstractSpell { source.spawnParticles(new Sphere(true, range), 10, pos -> { BlockPos bp = BlockPos.ofFloored(pos); - if (source.asWorld().getFluidState(bp.up()).isIn(affectedFluid)) { + if (source.canModifyAt(bp) && source.asWorld().getFluidState(bp.up()).isIn(affectedFluid)) { source.addParticle(UParticles.RAIN_DROPS, pos, Vec3d.ZERO); } }); @@ -116,7 +118,9 @@ public class HydrophobicSpell extends AbstractSpell { protected void onDestroyed(Caster<?> caster) { Ether.get(caster.asWorld()).remove(this, caster); storedFluidPositions.removeIf(entry -> { - entry.restore(caster.asWorld()); + if (caster.canModifyAt(entry.pos())) { + entry.restore(caster.asWorld()); + } return true; }); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/InfernoSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/InfernoSpell.java index 5dc50a69..8c1869ee 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/InfernoSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/InfernoSpell.java @@ -48,8 +48,9 @@ public class InfernoSpell extends FireSpell { for (int i = 0; i < radius * 2; i++) { if (w.random.nextInt(12) == 0) { Vec3d vec = shape.computePoint(w.random).add(origin); + BlockPos pos = BlockPos.ofFloored(vec); - if (!applyBlocks(w, BlockPos.ofFloored(vec))) { + if (source.canModifyAt(pos) && !applyBlocks(w, pos)) { applyEntities(source, vec); } } diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/OfflinePlayerCache.java b/src/main/java/com/minelittlepony/unicopia/server/world/OfflinePlayerCache.java new file mode 100644 index 00000000..03abf213 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/server/world/OfflinePlayerCache.java @@ -0,0 +1,44 @@ +package com.minelittlepony.unicopia.server.world; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.jetbrains.annotations.Nullable; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.mojang.authlib.GameProfile; + +import net.fabricmc.fabric.api.entity.FakePlayer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; + +public class OfflinePlayerCache { + private static final LoadingCache<Key, Optional<ServerPlayerEntity>> CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.MINUTES) + .build(CacheLoader.from(key -> { + ServerPlayerEntity offlinePlayer = FakePlayer.get(key.world(), new GameProfile(key.playerId(), "[Offline Player]")); + + if (key.world().getServer().getPlayerManager().loadPlayerData(offlinePlayer) != null) { + return Optional.of(offlinePlayer); + } + + return Optional.empty(); + })); + + @Nullable + public static ServerPlayerEntity getOfflinePlayer(ServerWorld world, UUID playerId) { + ServerPlayerEntity player = (ServerPlayerEntity)world.getPlayerByUuid(playerId); + if (player == null) { + player = world.getServer().getPlayerManager().getPlayer(playerId); + } + if (player == null) { + return CACHE.getUnchecked(new Key(world, playerId)).orElse(null); + } + return player; + } + + record Key (ServerWorld world, UUID playerId) {} +}