Skip to content

Commit

Permalink
Pollidisiac support for feeding baby animals (#4688)
Browse files Browse the repository at this point in the history
The flower can now feed baby animals in addition to (or instead of)
adult animals.

- Wand of the Forest in function mode can toggle the flower between
feeding adult/baby/all animals (default is adult-only, to not break
existing contraptions)
- current mode is shown in wand HUD
- if allowed to feed babies, they are only fed every couple seconds, and
only while they have more than 30 seconds left to grow up

---------

Co-authored-by: Artemis System <[email protected]>
  • Loading branch information
TheRealWormbo and artemisSystem authored Jul 2, 2024
1 parent d5ae1ce commit 3606b93
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -731,11 +731,12 @@ public static void registerWandHudCaps(BotaniaBlockEntities.BECapConsumer<WandHU
DANDELIFEON, RAFFLOWSIA, SHULK_ME_NOT);

consumer.accept(be -> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<BlockEntityType<?>> SELF_MANA_TRIGGER_BES = ImmutableSet.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
Expand All @@ -45,58 +60,87 @@ public void tickFlower() {
super.tickFlower();

if (!getLevel().isClientSide && getMana() >= MANA_COST) {
var pickupBounds = new AABB(getBlockPos()).inflate(RANGE);
List<ItemEntity> items = getLevel().getEntitiesOfClass(ItemEntity.class, pickupBounds,
itemEntity -> DelayHelper.canInteractWith(this, itemEntity));
if (items.isEmpty()) {
return;
List<ItemEntity> items = getItems();
if (!items.isEmpty()) {
List<Animal> animals = getAnimals();
feedAnimal(animals, items);
}
var bounds = new AABB(getEffectivePos()).inflate(RANGE);
List<Animal> 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<ItemEntity> 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<Animal> 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<Animal> animals, List<ItemEntity> 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;
}
}
}
Expand Down Expand Up @@ -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<Animal> {
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<Mode> 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<Animal> predicate;

Mode(@NotNull String name, @NotNull Predicate<Animal> 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<PollidisiacBlockEntity> {
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());
}
}
}
4 changes: 4 additions & 0 deletions Xplat/src/main/resources/assets/botania/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -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!",
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down

0 comments on commit 3606b93

Please sign in to comment.