diff --git a/Xplat/src/main/java/vazkii/botania/common/block/BotaniaFlowerBlocks.java b/Xplat/src/main/java/vazkii/botania/common/block/BotaniaFlowerBlocks.java index 0bc5a2a78b..da2f89d4ba 100644 --- a/Xplat/src/main/java/vazkii/botania/common/block/BotaniaFlowerBlocks.java +++ b/Xplat/src/main/java/vazkii/botania/common/block/BotaniaFlowerBlocks.java @@ -731,11 +731,12 @@ public static void registerWandHudCaps(BotaniaBlockEntities.BECapConsumer new HopperhockBlockEntity.WandHud((HopperhockBlockEntity) be), HOPPERHOCK, HOPPERHOCK_CHIBI); + consumer.accept(be -> new PollidisiacBlockEntity.WandHud((PollidisiacBlockEntity) be), POLLIDISIAC); consumer.accept(be -> new RannuncarpusBlockEntity.WandHud((RannuncarpusBlockEntity) be), RANNUNCARPUS, RANNUNCARPUS_CHIBI); consumer.accept(be -> new BindableSpecialFlowerBlockEntity.BindableFlowerWandHud<>((FunctionalFlowerBlockEntity) be), BELLETHORNE, BELLETHORNE_CHIBI, DREADTHORN, HEISEI_DREAM, TIGERSEYE, JADED_AMARANTHUS, ORECHID, FALLEN_KANADE, EXOFLAME, AGRICARNATION, AGRICARNATION_CHIBI, - TANGLEBERRIE, TANGLEBERRIE_CHIBI, JIYUULIA, JIYUULIA_CHIBI, HYACIDUS, POLLIDISIAC, + TANGLEBERRIE, TANGLEBERRIE_CHIBI, JIYUULIA, JIYUULIA_CHIBI, HYACIDUS, CLAYCONIA, CLAYCONIA_CHIBI, LOONIUM, DAFFOMILL, VINCULOTUS, SPECTRANTHEMUM, MEDUMONE, MARIMORPHOSIS, MARIMORPHOSIS_CHIBI, BUBBELL, BUBBELL_CHIBI, SOLEGNOLIA, SOLEGNOLIA_CHIBI, ORECHID_IGNEM, LABELLIA); diff --git a/Xplat/src/main/java/vazkii/botania/common/block/block_entity/BlockEntityConstants.java b/Xplat/src/main/java/vazkii/botania/common/block/block_entity/BlockEntityConstants.java index a5f446e259..b086cd45b4 100644 --- a/Xplat/src/main/java/vazkii/botania/common/block/block_entity/BlockEntityConstants.java +++ b/Xplat/src/main/java/vazkii/botania/common/block/block_entity/BlockEntityConstants.java @@ -13,7 +13,7 @@ public final class BlockEntityConstants { BotaniaBlockEntities.CRAFT_CRATE, BotaniaBlockEntities.ENCHANTER, BotaniaBlockEntities.HOURGLASS, BotaniaBlockEntities.PLATFORM, BotaniaBlockEntities.POOL, BotaniaBlockEntities.RUNE_ALTAR, BotaniaBlockEntities.SPREADER, BotaniaBlockEntities.TURNTABLE, BotaniaFlowerBlocks.DAFFOMILL, BotaniaFlowerBlocks.HOPPERHOCK, BotaniaFlowerBlocks.HOPPERHOCK_CHIBI, - BotaniaFlowerBlocks.RANNUNCARPUS, BotaniaFlowerBlocks.RANNUNCARPUS_CHIBI + BotaniaFlowerBlocks.POLLIDISIAC, BotaniaFlowerBlocks.RANNUNCARPUS, BotaniaFlowerBlocks.RANNUNCARPUS_CHIBI ); public static final Set> SELF_MANA_TRIGGER_BES = ImmutableSet.of( diff --git a/Xplat/src/main/java/vazkii/botania/common/block/flower/functional/PollidisiacBlockEntity.java b/Xplat/src/main/java/vazkii/botania/common/block/flower/functional/PollidisiacBlockEntity.java index 87c731388b..63a0911996 100644 --- a/Xplat/src/main/java/vazkii/botania/common/block/flower/functional/PollidisiacBlockEntity.java +++ b/Xplat/src/main/java/vazkii/botania/common/block/flower/functional/PollidisiacBlockEntity.java @@ -8,18 +8,30 @@ */ package vazkii.botania.common.block.flower.functional; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.resources.language.I18n; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; import net.minecraft.sounds.SoundEvents; import net.minecraft.tags.ItemTags; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.entity.AgeableMob; import net.minecraft.world.entity.EntityEvent; import net.minecraft.world.entity.animal.Animal; import net.minecraft.world.entity.animal.MushroomCow; import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.SuspiciousEffectHolder; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import vazkii.botania.api.block.Wandable; import vazkii.botania.api.block_entity.FunctionalFlowerBlockEntity; import vazkii.botania.api.block_entity.RadiusDescriptor; import vazkii.botania.common.block.BotaniaFlowerBlocks; @@ -28,14 +40,17 @@ import vazkii.botania.mixin.AnimalAccessor; import vazkii.botania.mixin.MushroomCowAccessor; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.function.Predicate; -public class PollidisiacBlockEntity extends FunctionalFlowerBlockEntity { +public class PollidisiacBlockEntity extends FunctionalFlowerBlockEntity implements Wandable { + private static final String TAG_FEEDING_MODE = "mode"; private static final int RANGE = 6; private static final int MANA_COST = 12; + @NotNull + private Mode mode = Mode.FEED_ADULTS; + public PollidisiacBlockEntity(BlockPos pos, BlockState state) { super(BotaniaFlowerBlocks.POLLIDISIAC, pos, state); } @@ -45,58 +60,87 @@ public void tickFlower() { super.tickFlower(); if (!getLevel().isClientSide && getMana() >= MANA_COST) { - var pickupBounds = new AABB(getBlockPos()).inflate(RANGE); - List items = getLevel().getEntitiesOfClass(ItemEntity.class, pickupBounds, - itemEntity -> DelayHelper.canInteractWith(this, itemEntity)); - if (items.isEmpty()) { - return; + List items = getItems(); + if (!items.isEmpty()) { + List animals = getAnimals(); + feedAnimal(animals, items); } - var bounds = new AABB(getEffectivePos()).inflate(RANGE); - List animals = getLevel().getEntitiesOfClass(Animal.class, bounds, Predicate.not(Animal::isBaby)); - Collections.shuffle(animals); - - for (Animal animal : animals) { - // Note: Empty item stacks are implicitly excluded in Animal::isFood and ItemStack::is(TagKey) - if (animal.getAge() == 0 && !animal.isInLove()) { - for (ItemEntity item : items) { - if (!animal.isFood(item.getItem())) { - continue; - } - consumeFoodItemAndMana(item); + } + } + /** + * Finds items around flower's actual position. + */ + private @NotNull List getItems() { + var pickupBounds = new AABB(getBlockPos()).inflate(RANGE); + return getLevel().getEntitiesOfClass(ItemEntity.class, pickupBounds, + itemEntity -> DelayHelper.canInteractWith(this, itemEntity)); + } + + /** + * Finds animals around flower's effective position. Depending on mode, adults, babies, or both will be selected. + */ + private @NotNull List getAnimals() { + var bounds = new AABB(getEffectivePos()).inflate(RANGE); + return getLevel().getEntitiesOfClass(Animal.class, bounds, mode); + } + + /** + * Attempts to feed an animal with an available item. Only one animal will be fed per call. Feeding adults is + * prioritized, but if babies get their turn, they are prioritized by their age, youngest first. Among brown adult + * mooshrooms, breeding is prioritized over feeding flowers for suspicious stew, if both item types are available. + */ + private void feedAnimal(List animals, List items) { + // randomize animals with same age + Collections.shuffle(animals); + // feed adults first, then babies, youngest to oldest + animals.sort(Comparator.comparing(Animal::isBaby).thenComparingInt(animal -> Math.min(animal.getAge(), 0))); + + for (Animal animal : animals) { + // Note: Empty item stacks are implicitly excluded in Animal::isFood and ItemStack::is(TagKey) + if (animal.getAge() == 0 && !animal.isInLove() || animal.getAge() < -600 && -animal.getAge() % 100 == 0) { + for (ItemEntity item : items) { + if (!animal.isFood(item.getItem())) { + continue; + } + consumeFoodItemAndMana(item); + + if (animal.isBaby()) { + animal.ageUp(AgeableMob.getSpeedUpSecondsWhenFeeding(-animal.getAge()), true); + } else { animal.setInLoveTime(1200); ((AnimalAccessor) animal).botania_setLoveCause(null); - getLevel().broadcastEntityEvent(animal, EntityEvent.IN_LOVE_HEARTS); - break; } + getLevel().broadcastEntityEvent(animal, EntityEvent.IN_LOVE_HEARTS); + break; + } - if (getMana() < MANA_COST) { - break; - } + if (getMana() < MANA_COST) { + break; } + } - if (isBrownMooshroomWithoutEffect(animal)) { - for (ItemEntity item : items) { - ItemStack stack = item.getItem(); - if (!stack.is(ItemTags.SMALL_FLOWERS)) { - continue; - } - var effect = SuspiciousEffectHolder.tryGet(stack.getItem()); - if (effect == null) { - continue; - } - consumeFoodItemAndMana(item); - - MushroomCowAccessor cowAccessor = (MushroomCowAccessor) animal; - cowAccessor.setEffect(effect.getSuspiciousEffect()); - cowAccessor.setEffectDuration(effect.getEffectDuration()); - animal.playSound(SoundEvents.MOOSHROOM_EAT, 2.0F, 1.0F); - break; + if (!animal.isBaby() && isBrownMooshroomWithoutEffect(animal)) { + for (ItemEntity item : items) { + ItemStack stack = item.getItem(); + if (!stack.is(ItemTags.SMALL_FLOWERS)) { + continue; } - - if (getMana() < MANA_COST) { - break; + var effect = SuspiciousEffectHolder.tryGet(stack.getItem()); + if (effect == null) { + continue; } + consumeFoodItemAndMana(item); + + MushroomCowAccessor cowAccessor = (MushroomCowAccessor) animal; + cowAccessor.setEffect(effect.getSuspiciousEffect()); + cowAccessor.setEffectDuration(effect.getEffectDuration()); + animal.playSound(SoundEvents.MOOSHROOM_EAT, 2.0F, 1.0F); + break; + } + + if (getMana() < MANA_COST) { + break; } } } @@ -135,4 +179,91 @@ public int getColor() { return 0xCF4919; } + @NotNull + public Mode getMode() { + return this.mode; + } + + @Override + public boolean onUsedByWand(@Nullable Player player, ItemStack stack, Direction side) { + if (player == null || player.isShiftKeyDown()) { + this.mode = this.mode.getNextMode(); + setChanged(); + sync(); + + return true; + } + return false; + } + + @Override + public void readFromPacketNBT(CompoundTag cmp) { + super.readFromPacketNBT(cmp); + this.mode = Mode.forName(cmp.getString(TAG_FEEDING_MODE)); + } + + @Override + public void writeToPacketNBT(CompoundTag cmp) { + super.writeToPacketNBT(cmp); + cmp.putString(TAG_FEEDING_MODE, this.mode.getSerializedName()); + } + + public enum Mode implements StringRepresentable, Predicate { + FEED_ADULTS("feed_adults", Predicate.not(Animal::isBaby)), + FEED_BABIES("feed_babies", Animal::isBaby), + FEED_ALL("feed_all", animal -> true); + + @SuppressWarnings("deprecation") + private static final EnumCodec CODEC = StringRepresentable.fromEnum(Mode::values); + + public static Mode forName(String name) { + return CODEC.byName(name, FEED_ADULTS); + } + + @NotNull + private final String name; + @NotNull + private final Predicate predicate; + + Mode(@NotNull String name, @NotNull Predicate predicate) { + this.name = name; + this.predicate = predicate; + } + + @Override + public boolean test(Animal animal) { + return predicate.test(animal); + } + + @NotNull + @Override + public String getSerializedName() { + return this.name; + } + + @NotNull + public Mode getNextMode() { + Mode[] modes = values(); + int nextMode = ordinal() + 1; + return modes[nextMode % modes.length]; + } + } + + public static class WandHud extends BindableFlowerWandHud { + public WandHud(PollidisiacBlockEntity flower) { + super(flower); + } + + @Override + public void renderHUD(GuiGraphics gui, Minecraft mc) { + String filter = I18n.get("botaniamisc.pollidisiac." + flower.getMode().getSerializedName()); + int filterWidth = mc.font.width(filter); + int filterTextStart = (mc.getWindow().getGuiScaledWidth() - filterWidth) / 2; + int halfMinWidth = (filterWidth + 4) / 2; + int centerY = mc.getWindow().getGuiScaledHeight() / 2; + + super.renderHUD(gui, mc, halfMinWidth, halfMinWidth, 40); + gui.drawString(mc.font, filter, filterTextStart, centerY + 30, flower.getColor()); + } + } } diff --git a/Xplat/src/main/resources/assets/botania/lang/en_us.json b/Xplat/src/main/resources/assets/botania/lang/en_us.json index 52a62754dc..b8bfec5f3b 100644 --- a/Xplat/src/main/resources/assets/botania/lang/en_us.json +++ b/Xplat/src/main/resources/assets/botania/lang/en_us.json @@ -132,6 +132,9 @@ "botaniamisc.rannuncarpus.state_sensitive": "Match Exact State", "botaniamisc.rannuncarpus.state_insensitive": "Match Block Only", "botaniamisc.lokiRingLimitReached": "Selection limit reached", + "botaniamisc.pollidisiac.feed_adults": "Feeding adult animals", + "botaniamisc.pollidisiac.feed_babies": "Feeding baby animals", + "botaniamisc.pollidisiac.feed_all": "Feeding all animals", "botania.tater_birthday.0": "Wow, is this for me?", "botania.tater_birthday.1": "It's my birthday today; I'm %d years old now!", @@ -2361,6 +2364,7 @@ "botania.tagline.pollidisiac": "Makes animals breed", "botania.page.pollidisiac0": "Animals love eating. That's all they seem to do, really. Strangely enough, though, they only eat things that are fed to them.$(p)The $(item)Pollidisiac$(0) will simply do just that; it uses mana to feed nearby food items on the ground ($(item)Wheat$(0), $(item)Carrots$(0), etc.) to animals within range, putting them in $(o)better moods$().", "botania.page.pollidisiac1": "$(o)Hell's Kitchen$().", + "botania.page.pollidisiac2": "Sneak-right clicking (or using a $(item)Dispenser$(0)) on the flower with a $(l:basics/wand)$(item)Wand of the Forest$(0)$(/l) in $(thing)Function Mode$(0) toggles whether the flower feeds just adults, just babies, or all animals.", "botania.entry.clayconia": "Clayconia", "botania.tagline.clayconia": "Creates clay from sand", diff --git a/Xplat/src/main/resources/assets/botania/patchouli_books/lexicon/en_us/entries/functional_flowers/pollidisiac.json b/Xplat/src/main/resources/assets/botania/patchouli_books/lexicon/en_us/entries/functional_flowers/pollidisiac.json index a8cd53056a..8f49518c94 100644 --- a/Xplat/src/main/resources/assets/botania/patchouli_books/lexicon/en_us/entries/functional_flowers/pollidisiac.json +++ b/Xplat/src/main/resources/assets/botania/patchouli_books/lexicon/en_us/entries/functional_flowers/pollidisiac.json @@ -12,6 +12,10 @@ "type": "botania:petal_apothecary", "text": "botania.page.pollidisiac1", "recipe": "botania:petal_apothecary/pollidisiac" + }, + { + "type": "text", + "text": "botania.page.pollidisiac2" } ], "extra_recipe_mappings": {