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..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 @@ -11,15 +11,15 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.sounds.SoundSource; -import net.minecraft.world.entity.Entity; +import net.minecraft.world.effect.MobEffect; 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; @@ -35,8 +35,11 @@ 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; +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,23 +70,85 @@ public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, Co return SHAPE; } - 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) { - 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 - )); - itemstack.shrink(1); + public static void gatherProduce(Level world, BlockPos pos) { + 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 mob : mobs) { + if (mob.getType().is(BotaniaTags.Entities.DRUM_MILKABLE) && !mob.isBaby()) { + convertNearby(mob, Items.BUCKET, Items.MILK_BUCKET); + } + if (mob instanceof MushroomCow mooshroom && !mooshroom.isBaby()) { + if (mooshroom.getVariant() == MushroomCow.MushroomType.BROWN && ((MushroomCowAccessor) mooshroom).getEffect() != null) { + fillBowlSuspiciously(mooshroom); } - item.discard(); + convertNearby(mob, Items.BOWL, Items.MUSHROOM_STEW); + } + if (mob instanceof Shearable shearable && !mob.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(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(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, + (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 bowlItemEntities = world.getEntitiesOfClass(ItemEntity.class, mushroomCow.getBoundingBox(), + itemEntity -> itemEntity.getItem().is(Items.BOWL) && !itemEntity.getItem().isEmpty()); + for (ItemEntity bowlItemEntity : bowlItemEntities) { + ItemStack bowlItem = bowlItemEntity.getItem(); + ItemStack stewItem = new ItemStack(Items.SUSPICIOUS_STEW); + SuspiciousStewItem.saveMobEffect(stewItem, effect, effectDuration); + spawnItem(mushroomCow, stewItem); + + EntityHelper.shrinkItem(bowlItemEntity); + if (bowlItem.getCount() == 0) { + bowlItemEntity.discard(); + } + + // only one suspicious stew per flower fed + mushroomCowAccessor.setEffect(null); + mushroomCowAccessor.setEffectDuration(0); + break; } } @@ -104,40 +172,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..efa55d0cc0 --- /dev/null +++ b/Xplat/src/main/java/vazkii/botania/test/block/DrumBlockTest.java @@ -0,0 +1,193 @@ +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.BotaniaFlowerBlocks; +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_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(); + + 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)) + .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"); + }) + .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)) + .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 = 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); + 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")) + .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")) + .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")) + .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)) + .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)) + .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" %}