diff --git a/build.gradle b/build.gradle index 21c6515..016d7ca 100644 --- a/build.gradle +++ b/build.gradle @@ -133,7 +133,7 @@ dependencies { implementation fg.deobf("curse.maven:jei-238222:4609866") implementation fg.deobf("curse.maven:patchouli-306770:4636277") implementation fg.deobf("curse.maven:jade-324717:4654448") - implementation fg.deobf("curse.maven:terrafirmacraft-302973:5276689") + implementation fg.deobf("curse.maven:terrafirmacraft-302973:6835820") implementation fg.deobf("curse.maven:arborfirmacraft-877545:5242050") } diff --git a/src/main/java/cy/jdkdigital/tfcgroomer/common/block/entity/GroomingStationBlockEntity.java b/src/main/java/cy/jdkdigital/tfcgroomer/common/block/entity/GroomingStationBlockEntity.java index 1dec55b..ab1998d 100644 --- a/src/main/java/cy/jdkdigital/tfcgroomer/common/block/entity/GroomingStationBlockEntity.java +++ b/src/main/java/cy/jdkdigital/tfcgroomer/common/block/entity/GroomingStationBlockEntity.java @@ -10,7 +10,7 @@ import net.dries007.tfc.common.capabilities.InventoryItemHandler; import net.dries007.tfc.common.capabilities.PartialItemHandler; import net.dries007.tfc.common.capabilities.food.FoodCapability; -import net.dries007.tfc.common.capabilities.size.ItemSizeManager; +import net.dries007.tfc.common.entities.livestock.TFCAnimal; import net.dries007.tfc.common.entities.livestock.TFCAnimalProperties; import net.dries007.tfc.util.IntArrayBuilder; import net.minecraft.core.BlockPos; @@ -20,13 +20,17 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.animal.Animal; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; @@ -44,7 +48,7 @@ public class GroomingStationBlockEntity extends TickableInventoryBlockEntity { - private static final Component NAME = Component.translatable("block.tfcgroomer.grooming_station"); // TODO: add localization + private static final Component NAME = Component.translatable("block.tfcgroomer.grooming_station"); static final UUID PLAYER_UUID = UUID.nameUUIDFromBytes("grooming_station".getBytes(StandardCharsets.UTF_8)); private double range = 1; int counter; @@ -53,7 +57,11 @@ public static void tickServer(Level level, BlockPos pos, GroomingStationBlockEnt if (groomStation.counter-- > 0 || !(level instanceof ServerLevel)) return; groomStation.counter = GroomerConfig.SERVER.groomingStationTicks.get(); - // Inventory updates + // check for valid animals + List entities = level.getEntitiesOfClass(Animal.class, (new AABB(pos).inflate(groomStation.range, 1d, groomStation.range))); + if (entities.isEmpty()) return; + + // if animals then check inventory List stacks = new ArrayList<>(); for (int i = 0; i < groomStation.inventory.getSlots(); i++) { var stack = groomStation.inventory.getStackInSlot(i); @@ -62,39 +70,71 @@ public static void tickServer(Level level, BlockPos pos, GroomingStationBlockEnt } } - // Animal feeding - List entities = level.getEntitiesOfClass(Animal.class, (new AABB(pos).inflate(groomStation.range, 1d, groomStation.range))).stream().toList(); - if (entities.isEmpty()) return; + if (stacks.isEmpty()) return; + // Animal feeding Player fakePlayer = FakePlayerFactory.get((ServerLevel) level, new GameProfile(PLAYER_UUID, "grooming_station")); + entities.forEach(animal -> feedAnimalIfConditionsMet(groomStation, animal, stacks, fakePlayer, groomStation.breedingEnabled)); + } - entities.forEach(animal -> feedAnimalIfConditionsMet(animal, stacks, fakePlayer, groomStation.breedingEnabled)); - + /** + * Feeds an animal via fakePlayer proxy, avoiding {@link TFC Issue 2862} + * @param be grooming station block entity + * @param animal animal to be fed + * @param currentStacks provided food itemStacks + * @param fakePlayer fakePlayer attached to grooming station block entity + * @param breedingEnabled whether feeding at max familiarity is enabled + */ + private static void feedAnimalIfConditionsMet(GroomingStationBlockEntity be, Animal animal, List currentStacks, Player fakePlayer, boolean breedingEnabled) { + if (animal instanceof TFCAnimal tfcAnimal) { + boolean animalHungry = tfcAnimal.isHungry(); + if (!animalHungry) return; - } + if (GroomerConfig.SERVER.animalBlacklist.get().contains(EntityType.getKey(animal.getType()).toString())) { + Groomer.LOGGER.info("{} on blacklist, ignoring", animal.getType()); + return; + } - private static void feedAnimalIfConditionsMet(Animal animal, List currentStacks, Player fakePlayer, boolean breedingEnabled) { - if (animal instanceof TFCAnimalProperties tfcAnimal) { + // 1. query animal for food tags + TagKey foodTag = tfcAnimal.getFoodTag(); + // 2. check for feeding preconditions float animalFamiliarity = tfcAnimal.getFamiliarity(); boolean isChild = tfcAnimal.getAgeType() == TFCAnimalProperties.Age.CHILD; - boolean animalHungry = tfcAnimal.isHungry(); - - for (ItemStack stack : currentStacks) { - boolean stackHasItems = !stack.isEmpty(); - boolean stackIsEdible = tfcAnimal.isFood(stack); - // Feeding logic - if (stackHasItems && animalHungry && stackIsEdible) { - if (breedingEnabled) { - tfcAnimal.eatFood(stack, InteractionHand.MAIN_HAND, fakePlayer); - break; - } else { - if ((isChild && animalFamiliarity < 1.0f) || (animalFamiliarity < tfcAnimal.getAdultFamiliarityCap())) { - tfcAnimal.eatFood(stack, InteractionHand.MAIN_HAND, fakePlayer); - break; - } + boolean childCanGrow = isChild && animalFamiliarity < 1.0f; + boolean adultCanFamiliarize = animalFamiliarity < tfcAnimal.getAdultFamiliarityCap(); + + // 3. iterate through usable food items + for (ItemStack itemStack : currentStacks) { + if (!itemStack.is(foodTag) || itemStack.isEmpty()) return; + if (!tfcAnimal.eatsRottenFood() && FoodCapability.isRotten(itemStack)) return; + + if (breedingEnabled || childCanGrow || adultCanFamiliarize) { + int initCount = itemStack.getCount(); + // use fakePlayer as proxy to feed animal, avoiding interacting with any shadowed animal methods + fakePlayer.setItemInHand(InteractionHand.MAIN_HAND, itemStack.split(1)); + InteractionResult res = fakePlayer.interactOn(tfcAnimal, InteractionHand.MAIN_HAND); + + // if for some reason the feeding failed or the proxy has residual items, clear proxy inventory and restore itemstack to initial state + if (res.equals(InteractionResult.FAIL) || !fakePlayer.getItemInHand(InteractionHand.MAIN_HAND).isEmpty()) { + itemStack.setCount(initCount); + fakePlayer.getInventory().clearContent(); + Groomer.LOGGER.warn("Grooming station ({}) tried to feed {} with {} but failed! Returning food to grooming station and clearing proxy inventory.", + be.getBlockPos(), + tfcAnimal.getTypeName().getString(), + itemStack); } + + be.inventory.onContentsChanged(currentStacks.indexOf(itemStack)); + + Groomer.LOGGER.debug("itemStack: {} | INIT stack count: {} | FINAL stack count: {}", + itemStack.getHoverName().getString(), + initCount, + itemStack.getCount()); } + + // if animal was successfully fed then exit loop + if (!tfcAnimal.isHungry()) break; } } } @@ -180,7 +220,7 @@ protected void onContentsChanged(int slot) { itemCount += stack.getCount(); } } - int level = itemCount >= 160 ? 2 : (itemCount > 0 ? 1 : 0); + int level = ((GroomingStationBlockEntity) this.entity).getAnalogOutputSignal() >= 8 ? 2 : (itemCount > 0 ? 1 : 0); if (this.entity.getBlockState().hasProperty(GroomingStation.LEVEL) && this.entity.getLevel() instanceof ServerLevel serverLevel) { var currentLevel = this.entity.getBlockState().getValue(GroomingStation.LEVEL); if (currentLevel != level) { diff --git a/src/main/java/cy/jdkdigital/tfcgroomer/config/ServerConfig.java b/src/main/java/cy/jdkdigital/tfcgroomer/config/ServerConfig.java index 6c6ac8c..376d7ff 100644 --- a/src/main/java/cy/jdkdigital/tfcgroomer/config/ServerConfig.java +++ b/src/main/java/cy/jdkdigital/tfcgroomer/config/ServerConfig.java @@ -1,9 +1,11 @@ package cy.jdkdigital.tfcgroomer.config; import net.dries007.tfc.util.Metal; -import net.minecraftforge.common.ForgeConfigSpec; + import net.minecraft.world.entity.EntityType; + import net.minecraftforge.common.ForgeConfigSpec; -import java.util.EnumMap; + import java.util.EnumMap; + import java.util.List; public class ServerConfig { @@ -20,7 +22,7 @@ public class ServerConfig { public final ForgeConfigSpec.BooleanValue groomingStationEnableAutomation; public final ForgeConfigSpec.BooleanValue groomingStationRedstoneOutput; - // TODO: Animal blacklist + public final ForgeConfigSpec.ConfigValue> animalBlacklist; ServerConfig(ConfigBuilderWrapper builder) { @@ -43,16 +45,19 @@ public class ServerConfig { } } - builder.swap("whitelist"); - - // TODO: Animal Blacklist - builder.swap("automation"); groomingStationEnableAutomation = builder.comment("If true, grooming stations will interact with in-world automation such as hoppers on a side-specific basis.").define("groomingStationEnableAutomation", true); groomingStationRedstoneOutput = builder.comment("If true, the Grooming Station will emit a redstone signal proportional to how full it is.").define("groomingStationRedstoneOutput", true); + builder.swap("blacklist"); + + // TODO: Animal Blacklist + animalBlacklist = builder + .comment("Animals ignored by the grooming station when feeding. Parsed as a resource location. For example: 'tfc:duck'") + .define("animalBlacklist", List.of(), entry -> EntityType.byString(entry).isPresent()); + builder.pop(); }