diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ButterflyEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/ButterflyEntity.java index cec52988..6812c111 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/ButterflyEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/ButterflyEntity.java @@ -1,9 +1,15 @@ package com.minelittlepony.unicopia.entity; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; import java.util.Random; +import java.util.function.Predicate; import org.jetbrains.annotations.Nullable; +import com.minelittlepony.unicopia.util.NbtSerialisable; + import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityPose; @@ -17,9 +23,11 @@ import net.minecraft.entity.data.TrackedData; import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.entity.mob.AmbientEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.NbtCompound; import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.sound.SoundEvent; import net.minecraft.sound.SoundEvents; +import net.minecraft.tag.BlockTags; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; @@ -28,13 +36,18 @@ import net.minecraft.world.World; import net.minecraft.world.WorldAccess; public class ButterflyEntity extends AmbientEntity { + private static final int MAX_BREEDING_COOLDOWN = 300; + private static final TrackedData RESTING = DataTracker.registerData(ButterflyEntity.class, TrackedDataHandlerRegistry.BOOLEAN); private static final TrackedData VARIANT = DataTracker.registerData(ButterflyEntity.class, TrackedDataHandlerRegistry.INTEGER); - @Nullable - private BlockPos hoveringPosition; + private Optional hoveringPosition = Optional.empty(); + private Optional flowerPosition = Optional.empty(); + + private final Map visited = new HashMap<>(); private int ticksResting; + private int breedingCooldown; public ButterflyEntity(EntityType type, World world) { super(type, world); @@ -57,6 +70,16 @@ public class ButterflyEntity extends AmbientEntity { return SoundEvents.ENTITY_BAT_HURT; } + @Override + protected Entity.MoveEffect getMoveEffect() { + return Entity.MoveEffect.EVENTS; + } + + @Override + public boolean canAvoidTraps() { + return true; + } + @Nullable @Override protected SoundEvent getDeathSound() { @@ -101,7 +124,8 @@ public class ButterflyEntity extends AmbientEntity { public void setResting(boolean resting) { getDataTracker().set(RESTING, resting); if (!resting) { - hoveringPosition = null; + hoveringPosition = Optional.empty(); + flowerPosition = Optional.empty(); } } @@ -139,67 +163,130 @@ public class ButterflyEntity extends AmbientEntity { public void tickMovement() { super.tickMovement(); + if (breedingCooldown > 0) { + breedingCooldown--; + } + BlockPos below = new BlockPos(getPos().add(0, -0.5, 0)); + visited.entrySet().removeIf(e -> e.getValue() < age - 500); + if (isResting()) { + if (flowerPosition.isPresent() && breed()) { + return; + } + if (world.getBlockState(below).isAir() || !world.getOtherEntities(this, getBoundingBox().expand(7), this::isAggressor).isEmpty() - || (ticksResting++ > 40 && world.random.nextInt(500) == 0) + || (ticksResting++ > 40 || world.random.nextInt(500) == 0) || world.hasRain(below)) { setResting(false); } } else { ticksResting = 0; - // invalidate the hovering position - if (hoveringPosition != null && (!world.isAir(hoveringPosition) || hoveringPosition.getY() < 1)) { - hoveringPosition = null; - } + updateFlowerPosition().map(flower -> { + if (flower.isWithinDistance(getPos(), 1)) { + setResting(true); + visited.put(flower, (long)age); + if (breedingCooldown <= 0) { + breedingCooldown = MAX_BREEDING_COOLDOWN / 10; + } + } - BlockPos pos = getBlockPos(); - - // select a new hovering position - if (hoveringPosition == null || random.nextInt(30) == 0 || hoveringPosition.getSquaredDistance(pos) < 4) { - hoveringPosition = pos.add( - random.nextInt(7) - random.nextInt(7), - random.nextInt(6) - 2, - random.nextInt(7) - random.nextInt(7) - ); - } - - // hover casually towards the chosen position - Vec3d motion = Vec3d.ofCenter(hoveringPosition, 0.1).subtract(getPos()); - Vec3d vel = getVelocity(); - - addVelocity( - (Math.signum(motion.getX()) * 0.5 - vel.x) * 0.1, - (Math.signum(motion.getY()) * 0.7 - vel.y) * 0.1, - (Math.signum(motion.getZ()) * 0.5 - vel.z) * 0.1 - ); - - float direction = (float)(MathHelper.atan2(vel.z, vel.x) * (180 / Math.PI)) - 90; - - forwardSpeed = 0.5F; - headYaw += MathHelper.wrapDegrees(direction - headYaw); + return flower; + }).or(this::findNextHoverPosition).ifPresent(this::moveTowards); if (random.nextInt(100) == 0 && world.getBlockState(below).isOpaque()) { setResting(true); } if (!world.isClient && age % 20 == 0 && world.random.nextInt(200) == 0) { - for (Entity i : world.getOtherEntities(this, getBoundingBox().expand(20))) { - if (i.getType() == getType()) { - return; - } + if (!world.getOtherEntities(this, getBoundingBox().expand(20), i -> i.getType() == getType()).isEmpty()) { + breed(); } - - ButterflyEntity copy = (ButterflyEntity)getType().create(world); - copy.copyPositionAndRotation(this); - world.spawnEntity(copy); } } } + private boolean breed() { + + int others = 0; + for (Entity i : world.getOtherEntities(this, getBoundingBox().expand(20))) { + if (i.getType() == getType() && others++ > 3) { + setResting(false); + return true; + } + } + + if (age < 20 || breedingCooldown > 0) { + return false; + } + breedingCooldown = MAX_BREEDING_COOLDOWN; + + ButterflyEntity copy = (ButterflyEntity)getType().create(world); + copy.copyPositionAndRotation(this); + world.spawnEntity(copy); + setResting(false); + return true; + } + + private Optional findNextHoverPosition() { + // invalidate the hovering position + BlockPos pos = getBlockPos(); + + return hoveringPosition = hoveringPosition.filter(p -> world.isAir(p) + && p.getY() >= 1 + && random.nextInt(30) != 0 + && p.getSquaredDistance(pos) >= 4).or(() -> { + return Optional.of(pos.add( + random.nextInt(7) - random.nextInt(7), + random.nextInt(6) - 2, + random.nextInt(7) - random.nextInt(7) + )); + }); + } + + private Optional updateFlowerPosition() { + + if (flowerPosition.isPresent()) { + return flowerPosition; + } + + if (age % 50 == 0) { + flowerPosition = findFlower(state -> { + return state.isIn(BlockTags.FLOWERS); + }); + flowerPosition.ifPresent(p -> { + visited.put(p, (long)age - 900); + }); + } + + return Optional.empty(); + } + + private Optional findFlower(Predicate predicate) { + return BlockPos.streamOutwards(this.getBlockPos(), 10, 10, 10).filter(p -> { + return !visited.containsKey(p) && predicate.test(world.getBlockState(p)); + }).findFirst(); + } + + private void moveTowards(BlockPos pos) { + Vec3d motion = Vec3d.ofCenter(pos, 0.1).subtract(getPos()); + Vec3d vel = getVelocity(); + + addVelocity( + (Math.signum(motion.getX()) * 0.5 - vel.x) * 0.1, + (Math.signum(motion.getY()) * 0.7 - vel.y) * 0.1, + (Math.signum(motion.getZ()) * 0.5 - vel.z) * 0.1 + ); + + float direction = (float)(MathHelper.atan2(vel.z, vel.x) * (180 / Math.PI)) - 90; + + forwardSpeed = 0.5F; + headYaw += MathHelper.wrapDegrees(direction - headYaw); + } + @Override public boolean shouldRender(double distance) { double d = 64 * getRenderDistanceMultiplier(); @@ -225,6 +312,36 @@ public class ButterflyEntity extends AmbientEntity { return getHeight() / 2; } + @Override + public void writeCustomDataToNbt(NbtCompound nbt) { + super.writeCustomDataToNbt(nbt); + nbt.putInt("ticksResting", ticksResting); + nbt.putInt("breedingCooldown", breedingCooldown); + NbtSerialisable.writeBlockPos("hoveringPosition", hoveringPosition, nbt); + NbtSerialisable.writeBlockPos("flowerPosition", flowerPosition, nbt); + NbtCompound visited = new NbtCompound(); + this.visited.forEach((pos, time) -> { + visited.putLong(String.valueOf(pos.asLong()), time); + }); + nbt.put("visited", visited); + } + + @Override + public void readCustomDataFromNbt(NbtCompound nbt) { + super.readCustomDataFromNbt(nbt); + ticksResting = nbt.getInt("ticksResting"); + breedingCooldown = nbt.getInt("breedingCooldown"); + hoveringPosition = NbtSerialisable.readBlockPos("hoveringPosition", nbt); + flowerPosition = NbtSerialisable.readBlockPos("flowerPosition", nbt); + NbtCompound visited = nbt.getCompound("visited"); + this.visited.clear(); + visited.getKeys().forEach(key -> { + try { + this.visited.put(BlockPos.fromLong(Long.valueOf(key)), visited.getLong(key)); + } catch (NumberFormatException ignore) {} + }); + } + public enum Variant { BUTTERFLY, YELLOW, diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java index 5834fd6d..17b34053 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java @@ -35,7 +35,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.particle.ParticleTypes; import net.minecraft.sound.SoundCategory; -import net.minecraft.sound.SoundEvent; import net.minecraft.sound.SoundEvents; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; diff --git a/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java b/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java index e6f5237f..9d49141b 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java +++ b/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java @@ -1,8 +1,12 @@ package com.minelittlepony.unicopia.util; +import java.util.Optional; + import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtDouble; +import net.minecraft.nbt.NbtHelper; import net.minecraft.nbt.NbtList; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; public interface NbtSerialisable { @@ -41,4 +45,12 @@ public interface NbtSerialisable { static Vec3d readVector(NbtList list) { return new Vec3d(list.getDouble(0), list.getDouble(1), list.getDouble(2)); } + + static void writeBlockPos(String name, Optional pos, NbtCompound nbt) { + pos.map(NbtHelper::fromBlockPos).ifPresent(p -> nbt.put("hoveringPosition", p)); + } + + static Optional readBlockPos(String name, NbtCompound nbt) { + return nbt.contains(name) ? Optional.ofNullable(NbtHelper.toBlockPos(nbt.getCompound(name))) : Optional.empty(); + } }