Improved butterfly ai, made it so they will visit flowers to breed

This commit is contained in:
Sollace 2021-08-23 13:42:04 +02:00
parent 56643db555
commit cd97867678
3 changed files with 170 additions and 42 deletions

View file

@ -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<Boolean> RESTING = DataTracker.registerData(ButterflyEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
private static final TrackedData<Integer> VARIANT = DataTracker.registerData(ButterflyEntity.class, TrackedDataHandlerRegistry.INTEGER);
@Nullable
private BlockPos hoveringPosition;
private Optional<BlockPos> hoveringPosition = Optional.empty();
private Optional<BlockPos> flowerPosition = Optional.empty();
private final Map<BlockPos, Long> visited = new HashMap<>();
private int ticksResting;
private int breedingCooldown;
public ButterflyEntity(EntityType<ButterflyEntity> 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,36 +163,116 @@ 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;
}
}
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) {
if (!world.getOtherEntities(this, getBoundingBox().expand(20), i -> i.getType() == getType()).isEmpty()) {
breed();
}
}
}
}
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<BlockPos> findNextHoverPosition() {
// invalidate the hovering position
BlockPos pos = getBlockPos();
// select a new hovering position
if (hoveringPosition == null || random.nextInt(30) == 0 || hoveringPosition.getSquaredDistance(pos) < 4) {
hoveringPosition = pos.add(
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)
);
));
});
}
// hover casually towards the chosen position
Vec3d motion = Vec3d.ofCenter(hoveringPosition, 0.1).subtract(getPos());
private Optional<BlockPos> 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<BlockPos> findFlower(Predicate<BlockState> 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(
@ -181,23 +285,6 @@ public class ButterflyEntity extends AmbientEntity {
forwardSpeed = 0.5F;
headYaw += MathHelper.wrapDegrees(direction - headYaw);
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;
}
}
ButterflyEntity copy = (ButterflyEntity)getType().create(world);
copy.copyPositionAndRotation(this);
world.spawnEntity(copy);
}
}
}
@Override
@ -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,

View file

@ -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;

View file

@ -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<BlockPos> pos, NbtCompound nbt) {
pos.map(NbtHelper::fromBlockPos).ifPresent(p -> nbt.put("hoveringPosition", p));
}
static Optional<BlockPos> readBlockPos(String name, NbtCompound nbt) {
return nbt.contains(name) ? Optional.ofNullable(NbtHelper.toBlockPos(nbt.getCompound(name))) : Optional.empty();
}
}