diff --git a/src/main/java/com/minelittlepony/unicopia/block/CuringJokeBlock.java b/src/main/java/com/minelittlepony/unicopia/block/CuringJokeBlock.java index 8118b1d9..61658a29 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/CuringJokeBlock.java +++ b/src/main/java/com/minelittlepony/unicopia/block/CuringJokeBlock.java @@ -2,7 +2,6 @@ package com.minelittlepony.unicopia.block; import com.minelittlepony.unicopia.ability.EarthPonyGrowAbility.Growable; import com.minelittlepony.unicopia.entity.mob.IgnominiousBulbEntity; -import com.minelittlepony.unicopia.entity.mob.TentacleEntity; import com.minelittlepony.unicopia.particle.MagicParticleEffect; import net.minecraft.block.BlockState; @@ -33,21 +32,16 @@ public class CuringJokeBlock extends FlowerBlock implements Growable { .map(BlockPos::toImmutable) .toList(); - if (otherFlowers.size() >= 8) { - IgnominiousBulbEntity bulb = new IgnominiousBulbEntity(world); - bulb.updatePositionAndAngles(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0); + IgnominiousBulbEntity bulb = new IgnominiousBulbEntity(world); + bulb.setBaby(true); + bulb.updatePositionAndAngles(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0); - if (Dismounting.canPlaceEntityAt(world, bulb, bulb.getBoundingBox())) { - otherFlowers.forEach(p -> world.removeBlock(p, false)); - world.spawnEntity(bulb); - return true; - } + if (Dismounting.canPlaceEntityAt(world, bulb, bulb.getBoundingBox())) { + otherFlowers.forEach(p -> world.breakBlock(p, false)); + world.spawnEntity(bulb); + return true; } - world.removeBlock(pos, false); - TentacleEntity tentacle = new TentacleEntity(world, pos); - tentacle.updatePositionAndAngles(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0); - world.spawnEntity(tentacle); - return true; + return false; } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/IgnominiousBulbEntityModel.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/IgnominiousBulbEntityModel.java index 186d2359..b8bb4c20 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/entity/IgnominiousBulbEntityModel.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/IgnominiousBulbEntityModel.java @@ -57,6 +57,13 @@ public class IgnominiousBulbEntityModel extends EntityModel ANGRY = DataTracker.registerData(IgnominiousBulbEntity.class, TrackedDataHandlerRegistry.BOOLEAN); + private static final TrackedData AGE = DataTracker.registerData(IgnominiousBulbEntity.class, TrackedDataHandlerRegistry.INTEGER); + private static final int BABY_AGE = PassiveEntity.BABY_AGE; + private static final int HAPPY_TICKS = 40; private static final List TENTACLE_OFFSETS = List.of( new BlockPos(-3, 0, -3), new BlockPos(0, 0, -4), new BlockPos(3, 0, -3), new BlockPos(-4, 0, 0), new BlockPos(4, 0, 0), @@ -40,6 +60,10 @@ public class IgnominiousBulbEntity extends MobEntity { @Nullable private Map tentacles; + private int prevAge; + private int happyTicks; + private int angryTicks; + public IgnominiousBulbEntity(EntityType type, World world) { super(type, world); } @@ -52,6 +76,29 @@ public class IgnominiousBulbEntity extends MobEntity { protected void initDataTracker() { super.initDataTracker(); dataTracker.startTracking(ANGRY, false); + dataTracker.startTracking(AGE, 0); + } + + @Override + public boolean isBaby() { + return getAge() < 0; + } + + @Override + public void setBaby(boolean baby) { + setAge(BABY_AGE); + } + + protected int getAge() { + return dataTracker.get(AGE); + } + + protected void setAge(int age) { + dataTracker.set(AGE, age); + } + + public float getScale(float tickDelta) { + return Math.max(0.2F, 1 - (MathHelper.clamp(MathHelper.lerp(tickDelta, prevAge, getAge()), BABY_AGE, 0F) / BABY_AGE)); } public boolean isAngry() { @@ -59,7 +106,56 @@ public class IgnominiousBulbEntity extends MobEntity { } public void setAngry(boolean angry) { - dataTracker.set(ANGRY, angry); + if (angry != isAngry()) { + dataTracker.set(ANGRY, angry); + } + } + + public void setAngryFor(int angryTicks) { + this.angryTicks = angryTicks; + } + + @Override + protected ActionResult interactMob(PlayerEntity player, Hand hand) { + + ItemStack stack = player.getStackInHand(hand); + if (isBaby() && stack.isOf(Items.BONE_MEAL)) { + if (!player.isCreative()) { + stack.decrement(1); + } + growUp(10); + if (!getWorld().isClient) { + getWorld().syncWorldEvent(WorldEvents.BONE_MEAL_USED, getBlockPos(), 0); + } + return ActionResult.SUCCESS; + } + + return ActionResult.PASS; + } + + @Override + protected void onPlayerSpawnedChild(PlayerEntity player, MobEntity child) { + Vec3d center = getPos(); + Supplier offset = VecHelper.supplier(() -> (getWorld().random.nextBoolean() ? 1 : -1) * 3); + setPosition(center.add(offset.get().multiply(1, 0, 1))); + child.setPosition(center.add(offset.get().multiply(1, 0, 1))); + } + + @Override + public void setPosition(double x, double y, double z) { + Vec3d change = new Vec3d(x, y, z).subtract(getPos()); + super.setPosition(x, y, z); + getTentacles().values().forEach(tentacle -> { + tentacle.setPosition(tentacle.getPos().add(change)); + }); + } + + public void growUp(int age) { + int currentAge = getAge(); + if (currentAge < 0) { + setAge((age * 20) + currentAge); + happyTicks = HAPPY_TICKS; + } } @Override @@ -68,20 +164,27 @@ public class IgnominiousBulbEntity extends MobEntity { var center = new BlockPos.Mutable(); var tentacles = getTentacles(); - TENTACLE_OFFSETS.forEach(offset -> { - tentacles.compute(adjustForTerrain(center, offset), this::updateTentacle); - }); + if (!isBaby() && !firstUpdate) { + TENTACLE_OFFSETS.forEach(offset -> { + tentacles.compute(adjustForTerrain(center, offset), this::updateTentacle); + }); + } - if (getWorld().random.nextInt(isAngry() ? 12 : 1200) == 0) { - for (TentacleEntity tentacle : tentacles.values()) { + for (TentacleEntity tentacle : tentacles.values()) { + if (getWorld().random.nextInt(isAngry() ? 12 : 1200) == 0) { tentacle.addActiveTicks(120); } } LivingEntity target = getAttacker(); - setAngry(target != null); + if (angryTicks > 0) { + angryTicks--; + } - if (isAngry() && getWorld().random.nextInt(30) == 0) { + setAngry(!isBaby() && (angryTicks > 0 || target != null)); + + float healthPercentage = getHealth() / getMaxHealth(); + if (isAngry() && target != null && getWorld().random.nextInt(1 + (int)(healthPercentage * 30)) == 0) { if (target instanceof PlayerEntity player) { Pony.of(player).getMagicalReserves().getEnergy().add(6); } @@ -94,16 +197,41 @@ public class IgnominiousBulbEntity extends MobEntity { tentacle.setTarget(target); }); } + + if (target != null) { + lookAtEntity(target, 10, 10); + } } super.tick(); } + @Override + public void tickMovement() { + super.tickMovement(); + prevAge = getAge(); + + if (getWorld().isClient) { + if (happyTicks > 0 && --happyTicks % 4 == 0) { + getWorld().addParticle(ParticleTypes.HAPPY_VILLAGER, getParticleX(1), getRandomBodyY() + 0.5, getParticleZ(1), 0, 0, 0); + } + } else { + if (prevAge < 0) { + setAge(prevAge + 1); + } else if (prevAge > 0) { + setAge(prevAge - 1); + } + } + } + private Map getTentacles() { if (tentacles == null) { tentacles = getWorld().getEntitiesByClass(TentacleEntity.class, this.getBoundingBox().expand(5, 0, 5), EntityPredicates.VALID_ENTITY) .stream() - .collect(Collectors.toMap(TentacleEntity::getBlockPos, Function.identity())); + .collect(Collectors.toMap(TentacleEntity::getBlockPos, tentacle -> { + tentacle.setBulb(this); + return tentacle; + })); } return tentacles; } @@ -112,7 +240,11 @@ public class IgnominiousBulbEntity extends MobEntity { if (tentacle == null || tentacle.isRemoved()) { tentacle = new TentacleEntity(getWorld(), pos); tentacle.updatePositionAndAngles(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, getWorld().random.nextFloat() * 360, 0); - getWorld().spawnEntity(tentacle); + tentacle.setBulb(this); + + if (getWorld().isTopSolid(pos.down(), this)) { + getWorld().spawnEntity(tentacle); + } } return tentacle; } @@ -157,6 +289,9 @@ public class IgnominiousBulbEntity extends MobEntity { @Override @Nullable protected SoundEvent getHurtSound(DamageSource source) { + if (isBaby()) { + return SoundEvents.ENTITY_HORSE_BREATHE; + } return USounds.ENTITY_IGNIMEOUS_BULB_HURT; } @@ -166,15 +301,60 @@ public class IgnominiousBulbEntity extends MobEntity { return USounds.ENTITY_IGNIMEOUS_BULB_DEATH; } + @Override + @Nullable + protected SoundEvent getAmbientSound() { + if (!isBaby() && getWorld().random.nextInt(2) == 0) { + return SoundEvents.ENTITY_CAMEL_AMBIENT; + } + return SoundEvents.ITEM_BONE_MEAL_USE; + } + @Override public void writeCustomDataToNbt(NbtCompound nbt) { super.writeCustomDataToNbt(nbt); nbt.putBoolean("angry", isAngry()); + nbt.putInt("age", getAge()); + NbtList tentacles = new NbtList(); + getTentacles().forEach((pos, tentacle) -> { + var compound = new NbtCompound(); + compound.put("pos", NbtSerialisable.BLOCK_POS.write(pos)); + compound.putUuid("uuid", tentacle.getUuid()); + tentacles.add(compound); + }); + nbt.put("tentacles", tentacles); } @Override public void readCustomDataFromNbt(NbtCompound nbt) { super.readCustomDataFromNbt(nbt); setAngry(nbt.getBoolean("angry")); + setAge(nbt.getInt("age")); + if (!getWorld().isClient) { + if (nbt.contains("tentacles", NbtElement.LIST_TYPE)) { + var tentacles = new HashMap(); + nbt.getList("tentacles", NbtElement.COMPOUND_TYPE).forEach(tag -> { + var compound = (NbtCompound)tag; + if (((ServerWorld)getWorld()).getEntity(compound.getUuid("uuid")) instanceof TentacleEntity tentacle) { + tentacle.setBulb(this); + tentacles.put(NbtSerialisable.BLOCK_POS.read(compound.getCompound("pos")), tentacle); + } + }); + this.tentacles = tentacles; + } + } + } + + @Override + public void onTrackedDataSet(TrackedData data) { + if (AGE.equals(data)) { + calculateDimensions(); + } + super.onTrackedDataSet(data); + } + + @Override + public EntityDimensions getDimensions(EntityPose pose) { + return EntityDimensions.changing(3, 2).scaled(getScale(1)); } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/TentacleEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/TentacleEntity.java index d20eb3a7..16d4a459 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/TentacleEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/TentacleEntity.java @@ -10,6 +10,7 @@ import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.util.shape.Sphere; +import net.minecraft.command.argument.EntityAnchorArgumentType.EntityAnchor; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; @@ -55,6 +56,9 @@ public class TentacleEntity extends AbstractDecorationEntity { private LivingEntity target; private final Comparator targetSorting = Comparator.comparing(this::distanceTo); + @Nullable + private IgnominiousBulbEntity bulb; + public TentacleEntity(EntityType type, World world) { super(type, world); } @@ -70,6 +74,10 @@ public class TentacleEntity extends AbstractDecorationEntity { dataTracker.startTracking(MOTION_OFFSET, 0); } + public void setBulb(IgnominiousBulbEntity bulb) { + this.bulb = bulb; + } + public void attack(BlockPos pos) { var offset = pos.toCenterPos().subtract(getBlockPos().toCenterPos()); @@ -82,6 +90,10 @@ public class TentacleEntity extends AbstractDecorationEntity { setYaw(MathHelper.wrapDegrees((float)(MathHelper.atan2(dZ, dX) * MathHelper.DEGREES_PER_RADIAN) - 90)); getWorld().sendEntityStatus(this, ATTACK_STATUS); attackingTicks = 30; + if (bulb != null) { + bulb.setAngryFor(10); + bulb.lookAt(EntityAnchor.FEET, pos.toCenterPos()); + } } public float getAttackProgress(float tickDelta) { @@ -144,7 +156,7 @@ public class TentacleEntity extends AbstractDecorationEntity { } public void addActiveTicks(int ticks) { - ticksActive += ticks; + ticksActive = Math.min(200, ticksActive + ticks); } @Override