Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -44,7 +48,7 @@

public class GroomingStationBlockEntity extends TickableInventoryBlockEntity<GroomingStationBlockEntity.GroomingStationInventory>
{
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;
Expand All @@ -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<Animal> entities = level.getEntitiesOfClass(Animal.class, (new AABB(pos).inflate(groomStation.range, 1d, groomStation.range)));
if (entities.isEmpty()) return;

// if animals then check inventory
List<ItemStack> stacks = new ArrayList<>();
for (int i = 0; i < groomStation.inventory.getSlots(); i++) {
var stack = groomStation.inventory.getStackInSlot(i);
Expand All @@ -62,39 +70,71 @@ public static void tickServer(Level level, BlockPos pos, GroomingStationBlockEnt
}
}

// Animal feeding
List<Animal> 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 <a href="https://github.com/TerraFirmaCraft/TerraFirmaCraft/issues/2862">TFC Issue 2862</a>}
* @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<ItemStack> 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<ItemStack> currentStacks, Player fakePlayer, boolean breedingEnabled) {
if (animal instanceof TFCAnimalProperties tfcAnimal) {
// 1. query animal for food tags
TagKey<Item> 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;
}
}
}
Expand Down Expand Up @@ -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) {
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/cy/jdkdigital/tfcgroomer/config/ServerConfig.java
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -20,7 +22,7 @@ public class ServerConfig {
public final ForgeConfigSpec.BooleanValue groomingStationEnableAutomation;
public final ForgeConfigSpec.BooleanValue groomingStationRedstoneOutput;

// TODO: Animal blacklist
public final ForgeConfigSpec.ConfigValue<List<? extends String>> animalBlacklist;


ServerConfig(ConfigBuilderWrapper builder) {
Expand All @@ -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();
}

Expand Down