From 7e2135d257ae25f04bde6f502c12e0a31714656c Mon Sep 17 00:00:00 2001 From: TheRealWormbo Date: Wed, 27 Sep 2023 16:14:54 +0200 Subject: [PATCH 1/2] Tag support for Drum of the Gathering (closing #4453) The drum now uses entity tags to determine which mobs can fill milk buckets and which shearable mobs can NOT be sheared: `botania:drum/milkable` (defaults to cow, mooshroom, and goat) and `botania:drum/no_shearing` (defaults to only mooshroom) Additionally, if a brown mooshroom was fed a flower, the Drum of the Gathering will fill the first bowl for that mooshroom with the appropriate type of suspicious stew. Any additional bowls will be filled with mushroom stew as usual. And finally, baby animals are excluded from filling buckets or bowls. (Whether they can be sheared is already determined by the mob's shearing logic, which usually excludes babies as well.) --- Fabric/src/main/resources/fabric.mod.json | 1 + .../653944ae1edeea7f4ecf5f993d31cab4d4d4eca0 | 2 + .../tags/entity_types/drum/milkable.json | 8 + .../tags/entity_types/drum/no_shearing.json | 6 + .../botania/common/block/mana/DrumBlock.java | 124 +++++++----- .../botania/common/lib/BotaniaTags.java | 10 + .../botania/data/EntityTagProvider.java | 3 + .../botania/mixin/MushroomCowAccessor.java | 24 +++ .../botania/test/block/DrumBlockTest.java | 177 ++++++++++++++++++ .../main/resources/botania_xplat.mixins.json | 1 + .../structures/block/drum_gathering.snbt | 33 ++++ web/changelog.md | 2 +- 12 files changed, 348 insertions(+), 43 deletions(-) create mode 100644 Xplat/src/generated/resources/data/botania/tags/entity_types/drum/milkable.json create mode 100644 Xplat/src/generated/resources/data/botania/tags/entity_types/drum/no_shearing.json create mode 100644 Xplat/src/main/java/vazkii/botania/mixin/MushroomCowAccessor.java create mode 100644 Xplat/src/main/java/vazkii/botania/test/block/DrumBlockTest.java create mode 100644 Xplat/src/main/resources/data/botania/gametest/structures/block/drum_gathering.snbt diff --git a/Fabric/src/main/resources/fabric.mod.json b/Fabric/src/main/resources/fabric.mod.json index 8a19c7cc62..c9c5f2cd92 100644 --- a/Fabric/src/main/resources/fabric.mod.json +++ b/Fabric/src/main/resources/fabric.mod.json @@ -45,6 +45,7 @@ "vazkii.botania.test.block.TargetBlockTest", "vazkii.botania.test.block.ApothecaryRecipeTest", "vazkii.botania.test.block.EntropinnyumUnethicalTntDetectionTest", + "vazkii.botania.test.block.DrumBlockTest", "vazkii.botania.test.item.AstrolabeTest", "vazkii.botania.test.item.CacophoniumTest", "vazkii.botania.test.item.FlowerPouchTest", diff --git a/Xplat/src/generated/resources/.cache/653944ae1edeea7f4ecf5f993d31cab4d4d4eca0 b/Xplat/src/generated/resources/.cache/653944ae1edeea7f4ecf5f993d31cab4d4d4eca0 index 22befb06ed..ef56157487 100644 --- a/Xplat/src/generated/resources/.cache/653944ae1edeea7f4ecf5f993d31cab4d4d4eca0 +++ b/Xplat/src/generated/resources/.cache/653944ae1edeea7f4ecf5f993d31cab4d4d4eca0 @@ -3,6 +3,8 @@ ffb8bea773fc90c54cb94c895cc5f096428f1f8b data/botania/tags/entity_types/cocoon/common_aquatic.json a46ef61d2e442ecb1c477406cd1987f243197d77 data/botania/tags/entity_types/cocoon/rare.json 1fbe94085d6760f4ab5055f72ed127a4568c9487 data/botania/tags/entity_types/cocoon/rare_aquatic.json +9c33b0f41d9e77eac0ff1738fb5f441becda9b72 data/botania/tags/entity_types/drum/milkable.json +f446787c83bb0ab6ff1c18d8bfed8ef578e71003 data/botania/tags/entity_types/drum/no_shearing.json 8c959685fc06d7f6136dda4b884691469e9ed32a data/botania/tags/entity_types/key_immune.json 36ad4c64bb218684802f4951c644eca848834ccc data/botania/tags/entity_types/shaded_mesa_blacklist.json 8eb95c79e26cf634dcf3c8bc3e1f0b1c9664931c data/c/tags/entity_types/bosses.json diff --git a/Xplat/src/generated/resources/data/botania/tags/entity_types/drum/milkable.json b/Xplat/src/generated/resources/data/botania/tags/entity_types/drum/milkable.json new file mode 100644 index 0000000000..adde6465a5 --- /dev/null +++ b/Xplat/src/generated/resources/data/botania/tags/entity_types/drum/milkable.json @@ -0,0 +1,8 @@ +{ + "replace": false, + "values": [ + "minecraft:cow", + "minecraft:mooshroom", + "minecraft:goat" + ] +} \ No newline at end of file diff --git a/Xplat/src/generated/resources/data/botania/tags/entity_types/drum/no_shearing.json b/Xplat/src/generated/resources/data/botania/tags/entity_types/drum/no_shearing.json new file mode 100644 index 0000000000..fc9da2e81f --- /dev/null +++ b/Xplat/src/generated/resources/data/botania/tags/entity_types/drum/no_shearing.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:mooshroom" + ] +} \ No newline at end of file diff --git a/Xplat/src/main/java/vazkii/botania/common/block/mana/DrumBlock.java b/Xplat/src/main/java/vazkii/botania/common/block/mana/DrumBlock.java index dee4439ce1..d2dd7b9566 100644 --- a/Xplat/src/main/java/vazkii/botania/common/block/mana/DrumBlock.java +++ b/Xplat/src/main/java/vazkii/botania/common/block/mana/DrumBlock.java @@ -11,15 +11,16 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.sounds.SoundSource; +import net.minecraft.world.effect.MobEffect; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.Shearable; -import net.minecraft.world.entity.animal.Cow; import net.minecraft.world.entity.animal.MushroomCow; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; +import net.minecraft.world.item.SuspiciousStewItem; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; @@ -37,6 +38,8 @@ import vazkii.botania.common.handler.BotaniaSounds; import vazkii.botania.common.item.BotaniaItems; import vazkii.botania.common.item.HornItem; +import vazkii.botania.common.lib.BotaniaTags; +import vazkii.botania.mixin.MushroomCowAccessor; import java.util.ArrayList; import java.util.Collections; @@ -44,6 +47,9 @@ public class DrumBlock extends BotaniaWaterloggedBlock { + public static final int MAX_NUM_SHEARED = 4; + public static final int GATHER_RANGE = 10; + public enum Variant { WILD, GATHERING, @@ -64,19 +70,47 @@ public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, Co return SHAPE; } + public static void gatherProduce(Level world, BlockPos pos) { + List entities = world.getEntitiesOfClass(Mob.class, new AABB(pos.offset(-GATHER_RANGE, -GATHER_RANGE, -GATHER_RANGE), pos.offset( + GATHER_RANGE + 1, GATHER_RANGE + 1, GATHER_RANGE + 1)), e -> !BergamuteBlockEntity.isBergamuteNearby(world, e.getX(), e.getY(), e.getZ())); + List shearables = new ArrayList<>(); + + for (Mob entity : entities) { + if (entity.getType().is(BotaniaTags.Entities.DRUM_MILKABLE) && !entity.isBaby()) { + convertNearby(entity, Items.BUCKET, Items.MILK_BUCKET); + } + if (entity instanceof MushroomCow mooshroom && !mooshroom.isBaby()) { + if (mooshroom.getVariant() == MushroomCow.MushroomType.BROWN && ((MushroomCowAccessor) mooshroom).getEffect() != null) { + fillBowlSuspiciously(mooshroom); + } + convertNearby(entity, Items.BOWL, Items.MUSHROOM_STEW); + } + if (entity instanceof Shearable shearable && !entity.getType().is(BotaniaTags.Entities.DRUM_NO_SHEARING) && shearable.readyForShearing()) { + shearables.add(shearable); + } + } + + Collections.shuffle(shearables); + int sheared = 0; + + for (Shearable shearable : shearables) { + if (sheared > MAX_NUM_SHEARED) { + break; + } + + shearable.shear(SoundSource.BLOCKS); + ++sheared; + } + } + private static void convertNearby(Entity entity, Item from, Item to) { Level world = entity.level(); List items = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox()); for (ItemEntity item : items) { ItemStack itemstack = item.getItem(); - if (!itemstack.isEmpty() && itemstack.is(from) && !world.isClientSide) { + if (!itemstack.isEmpty() && itemstack.is(from)) { while (itemstack.getCount() > 0) { - ItemEntity ent = entity.spawnAtLocation(new ItemStack(to), 1.0F); - ent.setDeltaMovement(ent.getDeltaMovement().add( - world.random.nextFloat() * 0.05F, - (world.random.nextFloat() - world.random.nextFloat()) * 0.1F, - (world.random.nextFloat() - world.random.nextFloat()) * 0.1F - )); + spawnItem(entity, new ItemStack(to)); itemstack.shrink(1); } item.discard(); @@ -84,6 +118,42 @@ private static void convertNearby(Entity entity, Item from, Item to) { } } + private static void spawnItem(Entity entity, ItemStack to) { + Level world = entity.level(); + ItemEntity ent = entity.spawnAtLocation(to, 1.0F); + ent.setDeltaMovement(ent.getDeltaMovement().add( + world.random.nextFloat() * 0.05F, + (world.random.nextFloat() - world.random.nextFloat()) * 0.1F, + (world.random.nextFloat() - world.random.nextFloat()) * 0.1F + )); + } + + private static void fillBowlSuspiciously(MushroomCow mushroomCow) { + MushroomCowAccessor mushroomCowAccessor = (MushroomCowAccessor) mushroomCow; + MobEffect effect = mushroomCowAccessor.getEffect(); + int effectDuration = mushroomCowAccessor.getEffectDuration(); + + Level world = mushroomCow.level(); + List items = world.getEntitiesOfClass(ItemEntity.class, mushroomCow.getBoundingBox(), + itemEntity -> itemEntity.getItem().is(Items.BOWL) && !itemEntity.getItem().isEmpty()); + for (ItemEntity item : items) { + ItemStack itemstack = item.getItem(); + ItemStack stewItem = new ItemStack(Items.SUSPICIOUS_STEW); + SuspiciousStewItem.saveMobEffect(stewItem, effect, effectDuration); + spawnItem(mushroomCow, stewItem); + + itemstack.shrink(1); + if (itemstack.getCount() == 0) { + item.discard(); + } + + // only one suspicious stew per flower fed + mushroomCowAccessor.setEffect(null); + mushroomCowAccessor.setEffectDuration(0); + break; + } + } + public static class ManaTriggerImpl implements ManaTrigger { private final Level world; private final BlockPos pos; @@ -104,40 +174,10 @@ public void onBurstCollision(ManaBurst burst) { world.addParticle(ParticleTypes.NOTE, pos.getX() + 0.5, pos.getY() + 1.2, pos.getZ() + 0.5D, 1.0 / 24.0, 0, 0); return; } - if (variant == Variant.WILD) { - HornItem.breakGrass(world, new ItemStack(BotaniaItems.grassHorn), pos, null); - } else if (variant == Variant.CANOPY) { - HornItem.breakGrass(world, new ItemStack(BotaniaItems.leavesHorn), pos, null); - } else { - int range = 10; - List entities = world.getEntitiesOfClass(Mob.class, new AABB(pos.offset(-range, -range, -range), pos.offset(range + 1, range + 1, range + 1)), e -> !BergamuteBlockEntity.isBergamuteNearby(world, e.getX(), e.getY(), e.getZ())); - List shearables = new ArrayList<>(); - - for (Mob entity : entities) { - if (entity instanceof Cow) { - convertNearby(entity, Items.BUCKET, Items.MILK_BUCKET); - if (entity instanceof MushroomCow) { - convertNearby(entity, Items.BOWL, Items.MUSHROOM_STEW); - } - } else if (entity instanceof Shearable shearable && shearable.readyForShearing()) { - shearables.add(entity); - } - } - - Collections.shuffle(shearables); - int sheared = 0; - - for (Mob entity : shearables) { - if (sheared > 4) { - break; - } - - if (entity instanceof Shearable shearable) { - shearable.shear(SoundSource.BLOCKS); - } - - ++sheared; - } + switch (variant) { + case WILD -> HornItem.breakGrass(world, new ItemStack(BotaniaItems.grassHorn), pos, null); + case CANOPY -> HornItem.breakGrass(world, new ItemStack(BotaniaItems.leavesHorn), pos, null); + case GATHERING -> gatherProduce(world, pos); } for (int i = 0; i < 10; i++) { diff --git a/Xplat/src/main/java/vazkii/botania/common/lib/BotaniaTags.java b/Xplat/src/main/java/vazkii/botania/common/lib/BotaniaTags.java index cdda70f4b6..1ca5b32146 100644 --- a/Xplat/src/main/java/vazkii/botania/common/lib/BotaniaTags.java +++ b/Xplat/src/main/java/vazkii/botania/common/lib/BotaniaTags.java @@ -247,6 +247,16 @@ public static class Entities { public static final TagKey> COCOON_COMMON_AQUATIC = tag("cocoon/common_aquatic"); public static final TagKey> COCOON_RARE_AQUATIC = tag("cocoon/rare_aquatic"); + /** + * The Drum of the Gathering fills milk buckets for mobs in this tag. + */ + public static final TagKey> DRUM_MILKABLE = tag("drum/milkable"); + + /** + * The Drum of the Gathering will not shear mobs in this tag, even if they could be sheared. + */ + public static final TagKey> DRUM_NO_SHEARING = tag("drum/no_shearing"); + /** * Entities in this tag are immune to damage from the Key of the King's Law */ diff --git a/Xplat/src/main/java/vazkii/botania/data/EntityTagProvider.java b/Xplat/src/main/java/vazkii/botania/data/EntityTagProvider.java index 8dfb7d66ce..0dd88c404e 100644 --- a/Xplat/src/main/java/vazkii/botania/data/EntityTagProvider.java +++ b/Xplat/src/main/java/vazkii/botania/data/EntityTagProvider.java @@ -42,6 +42,9 @@ protected void addTags(HolderLookup.Provider provider) { .addOptional(new ResourceLocation("quark", "crab")); tag(BotaniaTags.Entities.COCOON_RARE_AQUATIC).add(EntityType.DOLPHIN, EntityType.GLOW_SQUID, EntityType.AXOLOTL); + tag(BotaniaTags.Entities.DRUM_MILKABLE).add(EntityType.COW, EntityType.MOOSHROOM, EntityType.GOAT); + tag(BotaniaTags.Entities.DRUM_NO_SHEARING).add(EntityType.MOOSHROOM); + tag(BotaniaTags.Entities.SHADED_MESA_BLACKLIST).add(EntityType.ENDER_DRAGON, EntityType.WITHER, EntityType.ITEM_FRAME, EntityType.GLOW_ITEM_FRAME, EntityType.END_CRYSTAL, EntityType.PAINTING, EntityType.COMMAND_BLOCK_MINECART, EntityType.MARKER, EntityType.AREA_EFFECT_CLOUD, diff --git a/Xplat/src/main/java/vazkii/botania/mixin/MushroomCowAccessor.java b/Xplat/src/main/java/vazkii/botania/mixin/MushroomCowAccessor.java new file mode 100644 index 0000000000..23a8964132 --- /dev/null +++ b/Xplat/src/main/java/vazkii/botania/mixin/MushroomCowAccessor.java @@ -0,0 +1,24 @@ +package vazkii.botania.mixin; + +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.entity.animal.MushroomCow; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(MushroomCow.class) +public interface MushroomCowAccessor { + @Accessor + @Nullable + MobEffect getEffect(); + + @Accessor + void setEffect(@Nullable MobEffect effect); + + @Accessor + int getEffectDuration(); + + @Accessor + void setEffectDuration(int effectDuration); +} diff --git a/Xplat/src/main/java/vazkii/botania/test/block/DrumBlockTest.java b/Xplat/src/main/java/vazkii/botania/test/block/DrumBlockTest.java new file mode 100644 index 0000000000..f383b81172 --- /dev/null +++ b/Xplat/src/main/java/vazkii/botania/test/block/DrumBlockTest.java @@ -0,0 +1,177 @@ +package vazkii.botania.test.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.nbt.Tag; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.animal.MushroomCow; +import net.minecraft.world.item.*; +import net.minecraft.world.phys.Vec3; + +import org.jetbrains.annotations.Nullable; + +import vazkii.botania.common.block.block_entity.BotaniaBlockEntities; +import vazkii.botania.common.item.BotaniaItems; +import vazkii.botania.mixin.MushroomCowAccessor; +import vazkii.botania.test.TestingUtil; + +public class DrumBlockTest { + private static final String TEMPLATE = "botania:block/drum_gathering"; + + private static final BlockPos POSITION_BUTTON = new BlockPos(10, 10, 9); + private static final BlockPos POSITION_SPREADER = new BlockPos(10, 10, 10); + private static final BlockPos POSITION_DRUM = new BlockPos(10, 11, 10); + private static final BlockPos POSITION_MOB = new BlockPos(10, 2, 10); + private static final Vec3 VECTOR_MOB = POSITION_MOB.getCenter(); + + private static T setup(GameTestHelper helper, EntityType entityType, @Nullable Item item) { + var player = helper.makeMockPlayer(); + var spreader = TestingUtil.assertBlockEntity(helper, POSITION_SPREADER, BotaniaBlockEntities.SPREADER); + TestingUtil.assertThat(spreader.bindTo(player, new ItemStack(BotaniaItems.twigWand), + helper.absolutePos(POSITION_DRUM), Direction.UP), + () -> "Failed to bind spreader"); + if (item != null) { + helper.spawnItem(item, (float) VECTOR_MOB.x(), (float) VECTOR_MOB.y(), (float) VECTOR_MOB.z()); + } + return helper.spawn(entityType, VECTOR_MOB); + } + + private static void testMilkingAdultAnimal(GameTestHelper helper, EntityType entityType, Item inputItem, Item outputItem) { + setup(helper, entityType, inputItem); + helper.startSequence() + .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) + .thenWaitUntil(() -> helper.assertItemEntityPresent(outputItem, POSITION_MOB, 1.0)) + .thenExecute(helper::killAllEntities) + .thenSucceed(); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 20) + public void testMilkingGoat(GameTestHelper helper) { + testMilkingAdultAnimal(helper, EntityType.GOAT, Items.BUCKET, Items.MILK_BUCKET); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 20) + public void testMilkingCow(GameTestHelper helper) { + testMilkingAdultAnimal(helper, EntityType.COW, Items.BUCKET, Items.MILK_BUCKET); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 20) + public void testMilkingMooshroom(GameTestHelper helper) { + testMilkingAdultAnimal(helper, EntityType.MOOSHROOM, Items.BUCKET, Items.MILK_BUCKET); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 20) + public void testMilkingMooshroomSoup(GameTestHelper helper) { + testMilkingAdultAnimal(helper, EntityType.MOOSHROOM, Items.BOWL, Items.MUSHROOM_STEW); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 20) + public void testMilkingBrownMooshroomSuspiciousStew(GameTestHelper helper) { + var cow = setup(helper, EntityType.MOOSHROOM, Items.BOWL); + cow.setVariant(MushroomCow.MushroomType.BROWN); + var cowAccessor = (MushroomCowAccessor) cow; + cowAccessor.setEffect(MobEffects.BLINDNESS); + cowAccessor.setEffectDuration(15); + helper.startSequence() + .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) + .thenWaitUntil(() -> helper.assertItemEntityPresent(Items.SUSPICIOUS_STEW, POSITION_MOB, 1.0)) + .thenExecute(() -> { + final var item = helper.getEntities(EntityType.ITEM, POSITION_MOB, 1.0).stream().findFirst(); + helper.assertTrue(item.isPresent() && item.get().getItem().is(Items.SUSPICIOUS_STEW), "Item not found or not suspicious stew"); + final var nbt = item.get().getItem().getTag(); + // TODO: update for 1.20.2 (effect will be stored as string and tag names might change) + helper.assertTrue(nbt.contains(SuspiciousStewItem.EFFECTS_TAG, Tag.TAG_LIST), "Missing effects list tag"); + final var effects = nbt.getList(SuspiciousStewItem.EFFECTS_TAG, Tag.TAG_COMPOUND); + helper.assertTrue(effects.size() == 1, "Exactly one effect expected"); + final var effectTag = effects.getCompound(0); + helper.assertTrue(effectTag.contains(SuspiciousStewItem.EFFECT_ID_TAG, Tag.TAG_INT) && effectTag.contains(SuspiciousStewItem.EFFECT_DURATION_TAG, Tag.TAG_INT), "Missing ID and/or duration tag for effect"); + final var effect = MobEffect.byId(effectTag.getInt(SuspiciousStewItem.EFFECT_ID_TAG)); + final var effectDuration = effectTag.getInt(SuspiciousStewItem.EFFECT_DURATION_TAG); + helper.assertTrue(effect == MobEffects.BLINDNESS && effectDuration == 15, "Unexpected effect type or duration"); + }) + .thenExecute(helper::killAllEntities) + .thenSucceed(); + } + + private static void testMilkingBabyAnimal(GameTestHelper helper, EntityType entityType, Item item) { + var baby = setup(helper, entityType, item); + baby.setBaby(true); + helper.startSequence() + .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) + // ensure the empty container item is still there: + .thenExecuteAfter(20, () -> helper.assertItemEntityPresent(item, POSITION_MOB, 1.0)) + .thenExecute(helper::killAllEntities) + .thenSucceed(); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 25) + public void testMilkingBabyGoat(GameTestHelper helper) { + testMilkingBabyAnimal(helper, EntityType.GOAT, Items.BUCKET); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 25) + public void testMilkingBabyMooshroom(GameTestHelper helper) { + testMilkingBabyAnimal(helper, EntityType.MOOSHROOM, Items.BOWL); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 20) + public void testShearingSheep(GameTestHelper helper) { + var sheep = setup(helper, EntityType.SHEEP, null); + sheep.setColor(DyeColor.LIME); + helper.startSequence() + .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) + .thenWaitUntil(() -> helper.assertItemEntityPresent(Items.LIME_WOOL, POSITION_MOB, 1.0)) + .thenExecute(() -> helper.assertTrue(sheep.isAlive() && sheep.isSheared(), "Sheep should be sheared")) + .thenExecute(helper::killAllEntities) + .thenSucceed(); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 25) + public void testShearingBabySheep(GameTestHelper helper) { + var sheep = setup(helper, EntityType.SHEEP, null); + sheep.setBaby(true); + helper.startSequence() + .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) + .thenExecuteAfter(20, () -> helper.assertTrue(sheep.isAlive() && !sheep.isSheared(), "Baby sheep should not be sheared")) + .thenExecute(helper::killAllEntities) + .thenSucceed(); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 20) + public void testShearingSnowGolem(GameTestHelper helper) { + var golem = setup(helper, EntityType.SNOW_GOLEM, null); + helper.startSequence() + .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) + .thenWaitUntil(() -> helper.assertItemEntityPresent(Items.CARVED_PUMPKIN, POSITION_MOB, 1.0)) + .thenExecute(() -> helper.assertTrue(golem.isAlive() && !golem.hasPumpkin(), "Snow golem should not wear a pumpkin")) + .thenExecute(helper::killAllEntities) + .thenSucceed(); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 20) + public void testShearingPumpkinlessSnowGolem(GameTestHelper helper) { + var golem = setup(helper, EntityType.SNOW_GOLEM, null); + golem.setPumpkin(false); + helper.startSequence() + .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) + .thenExecuteAfter(20, () -> helper.assertTrue(golem.isAlive() && !golem.hasPumpkin(), "Snow golem should not wear a pumpkin")) + .thenExecute(() -> helper.assertItemEntityNotPresent(Items.CARVED_PUMPKIN, POSITION_MOB, 1.0)) + .thenExecute(helper::killAllEntities) + .thenSucceed(); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 25) + public void testShearingMooshroom(GameTestHelper helper) { + setup(helper, EntityType.MOOSHROOM, null); + helper.startSequence() + .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) + .thenExecuteAfter(20, () -> helper.assertEntityNotPresent(EntityType.COW)) + .thenExecute(helper::killAllEntities) + .thenSucceed(); + } +} diff --git a/Xplat/src/main/resources/botania_xplat.mixins.json b/Xplat/src/main/resources/botania_xplat.mixins.json index 1fbf5434fe..eb0c0bac8b 100644 --- a/Xplat/src/main/resources/botania_xplat.mixins.json +++ b/Xplat/src/main/resources/botania_xplat.mixins.json @@ -34,6 +34,7 @@ "LoomMenuPatternSlotMixin", "LootTableMixin", "MobAccessor", + "MushroomCowAccessor", "NaturalSpawnerMixin", "NearestAttackableTargetGoalAccessor", "PistonBaseBlockAccessor", diff --git a/Xplat/src/main/resources/data/botania/gametest/structures/block/drum_gathering.snbt b/Xplat/src/main/resources/data/botania/gametest/structures/block/drum_gathering.snbt new file mode 100644 index 0000000000..3d4b26e8c9 --- /dev/null +++ b/Xplat/src/main/resources/data/botania/gametest/structures/block/drum_gathering.snbt @@ -0,0 +1,33 @@ +{ + DataVersion: 3465, + size: [21, 21, 21], + data: [ + {pos: [10, 0, 10], state: "minecraft:polished_andesite"}, + {pos: [9, 1, 10], state: "minecraft:glass"}, + {pos: [10, 1, 9], state: "minecraft:glass"}, + {pos: [10, 1, 11], state: "minecraft:glass"}, + {pos: [11, 1, 10], state: "minecraft:glass"}, + {pos: [9, 2, 10], state: "minecraft:glass"}, + {pos: [10, 2, 9], state: "minecraft:glass"}, + {pos: [10, 2, 11], state: "minecraft:glass"}, + {pos: [11, 2, 10], state: "minecraft:glass"}, + {pos: [9, 3, 10], state: "minecraft:glass"}, + {pos: [10, 3, 9], state: "minecraft:glass"}, + {pos: [10, 3, 11], state: "minecraft:glass"}, + {pos: [11, 3, 10], state: "minecraft:glass"}, + {pos: [10, 8, 9], state: "minecraft:glass"}, + {pos: [10, 8, 10], state: "botania:creative_pool{color:none,waterlogged:false}"}, + {pos: [10, 9, 9], state: "minecraft:polished_blackstone_button{face:floor,facing:east,powered:false}"}, + {pos: [10, 9, 10], state: "botania:redstone_spreader{has_scaffolding:false,waterlogged:false}"}, + {pos: [10, 10, 10], state: "botania:drum_gathering{waterlogged:false}"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:glass", + "minecraft:polished_blackstone_button{face:floor,facing:east,powered:false}", + "botania:drum_gathering{waterlogged:false}", + "botania:creative_pool{color:none,waterlogged:false}", + "botania:redstone_spreader{has_scaffolding:false,waterlogged:false}" + ] +} diff --git a/web/changelog.md b/web/changelog.md index 02a8542445..fe2f53ed9c 100644 --- a/web/changelog.md +++ b/web/changelog.md @@ -23,9 +23,9 @@ and start a new "Upcoming" section. * Add: Tuff now convertable by Marimorphosis * Change: Key of the King's Law no longer hurts items or most other nonliving entities * Change: Removed desu gun and its associated advancement. The reference has overstayed its welcome. +* Change: Drum of the Gathering now uses entity tags to determine mobs that can be milked (`botania:drum/milkable`, which now also includes goats by default) and which mobs NOT to shear even though that's technically possible (`botania:drum/no_shearing`, by default that only includes mooshrooms); it can also produce suspicious stew from brown mooshrooms, and no longer finds a way to somehow milk baby cows (Wormbo) * Fix: Ender Air emission not being mentioned in Pure Daisy entry (Wormbo) - --- {% include changelog_header.html version="1.19.2-440" %} From e16194c74a858d4bddf7cac501e1216e43f40f13 Mon Sep 17 00:00:00 2001 From: TheRealWormbo Date: Thu, 28 Sep 2023 11:14:56 +0200 Subject: [PATCH 2/2] Improved variable/parameter names, added check for dead animals Also added tests for dead animals and Bergamute interaction and removed redundant killAllEntities calls from test sequences --- .../botania/common/block/mana/DrumBlock.java | 56 +++++++++---------- .../botania/test/block/DrumBlockTest.java | 32 ++++++++--- 2 files changed, 51 insertions(+), 37 deletions(-) diff --git a/Xplat/src/main/java/vazkii/botania/common/block/mana/DrumBlock.java b/Xplat/src/main/java/vazkii/botania/common/block/mana/DrumBlock.java index d2dd7b9566..55164e3880 100644 --- a/Xplat/src/main/java/vazkii/botania/common/block/mana/DrumBlock.java +++ b/Xplat/src/main/java/vazkii/botania/common/block/mana/DrumBlock.java @@ -12,7 +12,6 @@ import net.minecraft.core.particles.ParticleTypes; import net.minecraft.sounds.SoundSource; import net.minecraft.world.effect.MobEffect; -import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.Shearable; import net.minecraft.world.entity.animal.MushroomCow; @@ -36,6 +35,7 @@ import vazkii.botania.common.block.BotaniaWaterloggedBlock; import vazkii.botania.common.block.flower.functional.BergamuteBlockEntity; import vazkii.botania.common.handler.BotaniaSounds; +import vazkii.botania.common.helper.EntityHelper; import vazkii.botania.common.item.BotaniaItems; import vazkii.botania.common.item.HornItem; import vazkii.botania.common.lib.BotaniaTags; @@ -71,21 +71,21 @@ public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, Co } public static void gatherProduce(Level world, BlockPos pos) { - List entities = world.getEntitiesOfClass(Mob.class, new AABB(pos.offset(-GATHER_RANGE, -GATHER_RANGE, -GATHER_RANGE), pos.offset( - GATHER_RANGE + 1, GATHER_RANGE + 1, GATHER_RANGE + 1)), e -> !BergamuteBlockEntity.isBergamuteNearby(world, e.getX(), e.getY(), e.getZ())); + List mobs = world.getEntitiesOfClass(Mob.class, new AABB(pos.offset(-GATHER_RANGE, -GATHER_RANGE, -GATHER_RANGE), pos.offset(GATHER_RANGE + 1, GATHER_RANGE + 1, GATHER_RANGE + 1)), + mob -> mob.isAlive() && !BergamuteBlockEntity.isBergamuteNearby(world, mob.getX(), mob.getY(), mob.getZ())); List shearables = new ArrayList<>(); - for (Mob entity : entities) { - if (entity.getType().is(BotaniaTags.Entities.DRUM_MILKABLE) && !entity.isBaby()) { - convertNearby(entity, Items.BUCKET, Items.MILK_BUCKET); + for (Mob mob : mobs) { + if (mob.getType().is(BotaniaTags.Entities.DRUM_MILKABLE) && !mob.isBaby()) { + convertNearby(mob, Items.BUCKET, Items.MILK_BUCKET); } - if (entity instanceof MushroomCow mooshroom && !mooshroom.isBaby()) { + if (mob instanceof MushroomCow mooshroom && !mooshroom.isBaby()) { if (mooshroom.getVariant() == MushroomCow.MushroomType.BROWN && ((MushroomCowAccessor) mooshroom).getEffect() != null) { fillBowlSuspiciously(mooshroom); } - convertNearby(entity, Items.BOWL, Items.MUSHROOM_STEW); + convertNearby(mob, Items.BOWL, Items.MUSHROOM_STEW); } - if (entity instanceof Shearable shearable && !entity.getType().is(BotaniaTags.Entities.DRUM_NO_SHEARING) && shearable.readyForShearing()) { + if (mob instanceof Shearable shearable && !mob.getType().is(BotaniaTags.Entities.DRUM_NO_SHEARING) && shearable.readyForShearing()) { shearables.add(shearable); } } @@ -103,24 +103,22 @@ public static void gatherProduce(Level world, BlockPos pos) { } } - private static void convertNearby(Entity entity, Item from, Item to) { - Level world = entity.level(); - List items = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox()); - for (ItemEntity item : items) { - ItemStack itemstack = item.getItem(); - if (!itemstack.isEmpty() && itemstack.is(from)) { - while (itemstack.getCount() > 0) { - spawnItem(entity, new ItemStack(to)); - itemstack.shrink(1); - } - item.discard(); + private static void convertNearby(Mob mob, Item from, Item to) { + Level world = mob.level(); + List fromEntities = world.getEntitiesOfClass(ItemEntity.class, mob.getBoundingBox(), + itemEntity -> itemEntity.isAlive() && itemEntity.getItem().is(from)); + for (ItemEntity fromEntity : fromEntities) { + ItemStack fromStack = fromEntity.getItem(); + for (int i = fromStack.getCount(); i > 0; i--) { + spawnItem(mob, new ItemStack(to)); } + fromEntity.discard(); } } - private static void spawnItem(Entity entity, ItemStack to) { - Level world = entity.level(); - ItemEntity ent = entity.spawnAtLocation(to, 1.0F); + private static void spawnItem(Mob mob, ItemStack to) { + Level world = mob.level(); + ItemEntity ent = mob.spawnAtLocation(to, 1.0F); ent.setDeltaMovement(ent.getDeltaMovement().add( world.random.nextFloat() * 0.05F, (world.random.nextFloat() - world.random.nextFloat()) * 0.1F, @@ -134,17 +132,17 @@ private static void fillBowlSuspiciously(MushroomCow mushroomCow) { int effectDuration = mushroomCowAccessor.getEffectDuration(); Level world = mushroomCow.level(); - List items = world.getEntitiesOfClass(ItemEntity.class, mushroomCow.getBoundingBox(), + List bowlItemEntities = world.getEntitiesOfClass(ItemEntity.class, mushroomCow.getBoundingBox(), itemEntity -> itemEntity.getItem().is(Items.BOWL) && !itemEntity.getItem().isEmpty()); - for (ItemEntity item : items) { - ItemStack itemstack = item.getItem(); + for (ItemEntity bowlItemEntity : bowlItemEntities) { + ItemStack bowlItem = bowlItemEntity.getItem(); ItemStack stewItem = new ItemStack(Items.SUSPICIOUS_STEW); SuspiciousStewItem.saveMobEffect(stewItem, effect, effectDuration); spawnItem(mushroomCow, stewItem); - itemstack.shrink(1); - if (itemstack.getCount() == 0) { - item.discard(); + EntityHelper.shrinkItem(bowlItemEntity); + if (bowlItem.getCount() == 0) { + bowlItemEntity.discard(); } // only one suspicious stew per flower fed diff --git a/Xplat/src/main/java/vazkii/botania/test/block/DrumBlockTest.java b/Xplat/src/main/java/vazkii/botania/test/block/DrumBlockTest.java index f383b81172..efa55d0cc0 100644 --- a/Xplat/src/main/java/vazkii/botania/test/block/DrumBlockTest.java +++ b/Xplat/src/main/java/vazkii/botania/test/block/DrumBlockTest.java @@ -15,6 +15,7 @@ import org.jetbrains.annotations.Nullable; +import vazkii.botania.common.block.BotaniaFlowerBlocks; import vazkii.botania.common.block.block_entity.BotaniaBlockEntities; import vazkii.botania.common.item.BotaniaItems; import vazkii.botania.mixin.MushroomCowAccessor; @@ -26,6 +27,7 @@ public class DrumBlockTest { private static final BlockPos POSITION_BUTTON = new BlockPos(10, 10, 9); private static final BlockPos POSITION_SPREADER = new BlockPos(10, 10, 10); private static final BlockPos POSITION_DRUM = new BlockPos(10, 11, 10); + private static final BlockPos POSITION_BERGAMUTE = new BlockPos(11, 2, 11); private static final BlockPos POSITION_MOB = new BlockPos(10, 2, 10); private static final Vec3 VECTOR_MOB = POSITION_MOB.getCenter(); @@ -46,7 +48,6 @@ private static void testMilkingAdultAnimal(GameTestHelper helper helper.startSequence() .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) .thenWaitUntil(() -> helper.assertItemEntityPresent(outputItem, POSITION_MOB, 1.0)) - .thenExecute(helper::killAllEntities) .thenSucceed(); } @@ -94,7 +95,6 @@ public void testMilkingBrownMooshroomSuspiciousStew(GameTestHelper helper) { final var effectDuration = effectTag.getInt(SuspiciousStewItem.EFFECT_DURATION_TAG); helper.assertTrue(effect == MobEffects.BLINDNESS && effectDuration == 15, "Unexpected effect type or duration"); }) - .thenExecute(helper::killAllEntities) .thenSucceed(); } @@ -105,7 +105,6 @@ private static void testMilkingBabyAnimal(GameTestHelper helper, .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) // ensure the empty container item is still there: .thenExecuteAfter(20, () -> helper.assertItemEntityPresent(item, POSITION_MOB, 1.0)) - .thenExecute(helper::killAllEntities) .thenSucceed(); } @@ -119,6 +118,28 @@ public void testMilkingBabyMooshroom(GameTestHelper helper) { testMilkingBabyAnimal(helper, EntityType.MOOSHROOM, Items.BOWL); } + @GameTest(template = TEMPLATE, timeoutTicks = 25) + public void testMilkingDeadAnimal(GameTestHelper helper) { + var cow = setup(helper, EntityType.COW, Items.BUCKET); + helper.startSequence() + // mobs play 20 ticks of dying animation when killed, so the cow is still there when the drum goes off + .thenExecute(cow::kill) + .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) + .thenExecuteAfter(20, () -> helper.assertItemEntityPresent(Items.BUCKET, POSITION_MOB, 1.0)) + .thenSucceed(); + } + + @GameTest(template = TEMPLATE, timeoutTicks = 25) + public void testMilkingNearBergamute(GameTestHelper helper) { + setup(helper, EntityType.COW, Items.BUCKET); + // Bergamute should protect from drum interactions + helper.setBlock(POSITION_BERGAMUTE, BotaniaFlowerBlocks.bergamuteFloating); + helper.startSequence() + .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) + .thenExecuteAfter(20, () -> helper.assertItemEntityPresent(Items.BUCKET, POSITION_MOB, 1.0)) + .thenSucceed(); + } + @GameTest(template = TEMPLATE, timeoutTicks = 20) public void testShearingSheep(GameTestHelper helper) { var sheep = setup(helper, EntityType.SHEEP, null); @@ -127,7 +148,6 @@ public void testShearingSheep(GameTestHelper helper) { .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) .thenWaitUntil(() -> helper.assertItemEntityPresent(Items.LIME_WOOL, POSITION_MOB, 1.0)) .thenExecute(() -> helper.assertTrue(sheep.isAlive() && sheep.isSheared(), "Sheep should be sheared")) - .thenExecute(helper::killAllEntities) .thenSucceed(); } @@ -138,7 +158,6 @@ public void testShearingBabySheep(GameTestHelper helper) { helper.startSequence() .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) .thenExecuteAfter(20, () -> helper.assertTrue(sheep.isAlive() && !sheep.isSheared(), "Baby sheep should not be sheared")) - .thenExecute(helper::killAllEntities) .thenSucceed(); } @@ -149,7 +168,6 @@ public void testShearingSnowGolem(GameTestHelper helper) { .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) .thenWaitUntil(() -> helper.assertItemEntityPresent(Items.CARVED_PUMPKIN, POSITION_MOB, 1.0)) .thenExecute(() -> helper.assertTrue(golem.isAlive() && !golem.hasPumpkin(), "Snow golem should not wear a pumpkin")) - .thenExecute(helper::killAllEntities) .thenSucceed(); } @@ -161,7 +179,6 @@ public void testShearingPumpkinlessSnowGolem(GameTestHelper helper) { .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) .thenExecuteAfter(20, () -> helper.assertTrue(golem.isAlive() && !golem.hasPumpkin(), "Snow golem should not wear a pumpkin")) .thenExecute(() -> helper.assertItemEntityNotPresent(Items.CARVED_PUMPKIN, POSITION_MOB, 1.0)) - .thenExecute(helper::killAllEntities) .thenSucceed(); } @@ -171,7 +188,6 @@ public void testShearingMooshroom(GameTestHelper helper) { helper.startSequence() .thenExecute(() -> helper.pressButton(POSITION_BUTTON)) .thenExecuteAfter(20, () -> helper.assertEntityNotPresent(EntityType.COW)) - .thenExecute(helper::killAllEntities) .thenSucceed(); } }