Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add item frame filter expansion #4796

Draft
wants to merge 1 commit into
base: 1.20.x
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ public interface BECapConsumer<T> {
public static void registerWandHudCaps(BECapConsumer<WandHUD> consumer) {
consumer.accept(be -> new AnimatedTorchBlockEntity.WandHud((AnimatedTorchBlockEntity) be), BotaniaBlockEntities.ANIMATED_TORCH);
consumer.accept(be -> new BreweryBlockEntity.WandHud((BreweryBlockEntity) be), BotaniaBlockEntities.BREWERY);
consumer.accept(be -> new CorporeaFunnelBlockEntity.WandHud((CorporeaFunnelBlockEntity) be), BotaniaBlockEntities.CORPOREA_FUNNEL);
consumer.accept(be -> new CorporeaInterceptorBlockEntity.WandHud((CorporeaInterceptorBlockEntity) be), BotaniaBlockEntities.CORPOREA_INTERCEPTOR);
consumer.accept(be -> new CorporeaRetainerBlockEntity.WandHud((CorporeaRetainerBlockEntity) be), BotaniaBlockEntities.CORPOREA_RETAINER);
consumer.accept(be -> new CraftyCrateBlockEntity.WandHud((CraftyCrateBlockEntity) be), BotaniaBlockEntities.CRAFT_CRATE);
consumer.accept(be -> new ManaEnchanterBlockEntity.WandHud((ManaEnchanterBlockEntity) be), BotaniaBlockEntities.ENCHANTER);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,57 @@
*/
package vazkii.botania.common.block.block_entity.corporea;

import net.minecraft.ChatFormatting;
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.util.random.WeightedRandomList;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.decoration.ItemFrame;
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.state.BlockState;
import net.minecraft.world.phys.AABB;

import org.jetbrains.annotations.Nullable;

import vazkii.botania.api.block.WandHUD;
import vazkii.botania.api.block.Wandable;
import vazkii.botania.api.corporea.CorporeaHelper;
import vazkii.botania.api.corporea.CorporeaRequestMatcher;
import vazkii.botania.api.corporea.CorporeaRequestor;
import vazkii.botania.api.corporea.CorporeaSpark;
import vazkii.botania.api.internal.VanillaPacketDispatcher;
import vazkii.botania.client.core.helper.RenderHelper;
import vazkii.botania.common.block.block_entity.BotaniaBlockEntities;
import vazkii.botania.common.helper.FilterHelper;
import vazkii.botania.common.helper.InventoryHelper;
import vazkii.botania.xplat.XplatAbstractions;

import java.util.ArrayList;
import java.util.List;

public class CorporeaFunnelBlockEntity extends BaseCorporeaBlockEntity implements CorporeaRequestor {
public class CorporeaFunnelBlockEntity extends BaseCorporeaBlockEntity implements CorporeaRequestor, Wandable {
private static final int[] ROTATION_TO_STACK_SIZE = { 1, 2, 4, 8, 16, 32, 48, 64 };

private boolean expandFilters = true;

public CorporeaFunnelBlockEntity(BlockPos pos, BlockState state) {
super(BotaniaBlockEntities.CORPOREA_FUNNEL, pos, state);
}

public void doRequest() {
CorporeaSpark spark = getSpark();
if (spark != null && spark.getMaster() != null) {
List<ItemStack> filter = getFilter();
WeightedRandomList<FilterHelper.WeightedItemStack> filter = getFilter();
if (!filter.isEmpty()) {
ItemStack stack = filter.get(level.random.nextInt(filter.size()));
ItemStack stack = filter.getRandom(level.random)
.map(FilterHelper.WeightedItemStack::stack)
.orElse(ItemStack.EMPTY);

if (!stack.isEmpty()) {
var matcher = CorporeaHelper.instance().createMatcher(stack, true);
Expand All @@ -50,12 +68,8 @@ public void doRequest() {
}
}

public List<ItemStack> getFilter() {
List<ItemStack> filter = new ArrayList<>();

final int[] rotationToStackSize = new int[] {
1, 2, 4, 8, 16, 32, 48, 64
};
public WeightedRandomList<FilterHelper.WeightedItemStack> getFilter() {
List<FilterHelper.WeightedItemStack> filter = new ArrayList<>();

for (Direction dir : Direction.values()) {
List<ItemFrame> frames = level.getEntitiesOfClass(ItemFrame.class, new AABB(worldPosition.relative(dir), worldPosition.relative(dir).offset(1, 1, 1)));
Expand All @@ -64,14 +78,17 @@ public List<ItemStack> getFilter() {
if (orientation == dir) {
ItemStack stack = frame.getItem();
if (!stack.isEmpty()) {
ItemStack copy = stack.copyWithCount(rotationToStackSize[frame.getRotation()]);
filter.add(copy);
List<ItemStack> filterStacks = expandFilters ? FilterHelper.getFilterItems(stack) : List.of(stack);
int stackSize = ROTATION_TO_STACK_SIZE[frame.getRotation()];
filterStacks.stream()
.map(s -> FilterHelper.WeightedItemStack.of(s.copyWithCount(stackSize), s.getCount()))
.forEach(filter::add);
}
}
}
}

return filter;
return WeightedRandomList.create(filter);
}

@Override
Expand Down Expand Up @@ -108,4 +125,50 @@ private BlockPos getInvPos() {
return null;
}

@Override
public void readPacketNBT(CompoundTag cmp) {
super.readPacketNBT(cmp);
if (cmp.contains(FilterHelper.TAG_EXPAND_FILTERS)) {
expandFilters = cmp.getBoolean(FilterHelper.TAG_EXPAND_FILTERS);
} else {
// existing funnels don't expand, newly placed ones do
expandFilters = false;
}
}

@Override
public void writePacketNBT(CompoundTag cmp) {
super.writePacketNBT(cmp);
cmp.putBoolean(FilterHelper.TAG_EXPAND_FILTERS, expandFilters);
}

@Override
public boolean onUsedByWand(@Nullable Player player, ItemStack wand, Direction side) {
if (player == null || player.isShiftKeyDown()) {
expandFilters = !expandFilters;
setChanged();
VanillaPacketDispatcher.dispatchTEToNearbyPlayers(this);
return true;
}
return false;
}

public static class WandHud implements WandHUD {
private final CorporeaFunnelBlockEntity funnel;

public WandHud(CorporeaFunnelBlockEntity funnel) {
this.funnel = funnel;
}

@Override
public void renderHUD(GuiGraphics gui, Minecraft mc) {
String mode = I18n.get("botaniamisc.filter." + (funnel.expandFilters ? "expand_list" : "single_item"));
int strWidth = mc.font.width(mode);
int x = (mc.getWindow().getGuiScaledWidth() - strWidth) / 2;
int y = mc.getWindow().getGuiScaledHeight() / 2 + 8;

RenderHelper.renderHUDBox(gui, x - 2, y, x + strWidth + 2, y + 12);
gui.drawString(mc.font, mode, x, y + 2, ChatFormatting.WHITE.getColor());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,41 @@
*/
package vazkii.botania.common.block.block_entity.corporea;

import net.minecraft.ChatFormatting;
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.world.entity.decoration.ItemFrame;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.phys.AABB;

import org.jetbrains.annotations.Nullable;

import vazkii.botania.api.block.WandHUD;
import vazkii.botania.api.block.Wandable;
import vazkii.botania.api.corporea.CorporeaInterceptor;
import vazkii.botania.api.corporea.CorporeaNode;
import vazkii.botania.api.corporea.CorporeaRequestMatcher;
import vazkii.botania.api.corporea.CorporeaSpark;
import vazkii.botania.api.internal.VanillaPacketDispatcher;
import vazkii.botania.client.core.helper.RenderHelper;
import vazkii.botania.common.block.block_entity.BotaniaBlockEntities;
import vazkii.botania.common.helper.FilterHelper;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class CorporeaInterceptorBlockEntity extends BaseCorporeaBlockEntity implements CorporeaInterceptor {
public class CorporeaInterceptorBlockEntity extends BaseCorporeaBlockEntity implements CorporeaInterceptor, Wandable {
private boolean expandFilters = true;

public CorporeaInterceptorBlockEntity(BlockPos pos, BlockState state) {
super(BotaniaBlockEntities.CORPOREA_INTERCEPTOR, pos, state);
}
Expand Down Expand Up @@ -84,7 +99,11 @@ private List<ItemStack> getFilter() {
if (orientation == dir) {
ItemStack stack = frame.getItem();
if (!stack.isEmpty()) {
filter.add(stack);
if (expandFilters) {
filter.addAll(FilterHelper.getFilterItems(stack));
} else {
filter.add(stack);
}
}
}
}
Expand All @@ -93,4 +112,50 @@ private List<ItemStack> getFilter() {
return filter;
}

@Override
public void readPacketNBT(CompoundTag cmp) {
super.readPacketNBT(cmp);
if (cmp.contains(FilterHelper.TAG_EXPAND_FILTERS)) {
expandFilters = cmp.getBoolean(FilterHelper.TAG_EXPAND_FILTERS);
} else {
// existing interceptors don't expand, newly placed ones do
expandFilters = false;
}
}

@Override
public void writePacketNBT(CompoundTag cmp) {
super.writePacketNBT(cmp);
cmp.putBoolean(FilterHelper.TAG_EXPAND_FILTERS, expandFilters);
}

@Override
public boolean onUsedByWand(@Nullable Player player, ItemStack wand, Direction side) {
if (player == null || player.isShiftKeyDown()) {
expandFilters = !expandFilters;
setChanged();
VanillaPacketDispatcher.dispatchTEToNearbyPlayers(this);
return true;
}
return false;
}

public static class WandHud implements WandHUD {
private final CorporeaInterceptorBlockEntity interceptor;

public WandHud(CorporeaInterceptorBlockEntity interceptor) {
this.interceptor = interceptor;
}

@Override
public void renderHUD(GuiGraphics gui, Minecraft mc) {
String mode = I18n.get("botaniamisc.filter." + (interceptor.expandFilters ? "expand_list" : "single_item"));
int strWidth = mc.font.width(mode);
int x = (mc.getWindow().getGuiScaledWidth() - strWidth) / 2;
int y = mc.getWindow().getGuiScaledHeight() / 2 + 8;

RenderHelper.renderHUDBox(gui, x - 2, y, x + strWidth + 2, y + 12);
gui.drawString(mc.font, mode, x, y + 2, ChatFormatting.WHITE.getColor());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@
import vazkii.botania.api.block_entity.FunctionalFlowerBlockEntity;
import vazkii.botania.api.block_entity.RadiusDescriptor;
import vazkii.botania.common.block.BotaniaFlowerBlocks;
import vazkii.botania.common.helper.DelayHelper;
import vazkii.botania.common.helper.EntityHelper;
import vazkii.botania.common.helper.InventoryHelper;
import vazkii.botania.common.helper.ItemNBTHelper;
import vazkii.botania.common.helper.*;
import vazkii.botania.common.internal_caps.ItemFlagsComponent;
import vazkii.botania.xplat.XplatAbstractions;

Expand Down Expand Up @@ -191,14 +188,20 @@ public static List<ItemStack> getFilterForInventory(Level level, BlockPos pos, b
List<ItemFrame> frames = level.getEntitiesOfClass(ItemFrame.class, aabb);
for (ItemFrame frame : frames) {
if (frame.getDirection() == dir) {
filter.add(frame.getItem());
filter.addAll(isFrameRotatedUpsideDown(frame)
? List.of(frame.getItem())
: FilterHelper.getFilterItems(frame.getItem()));
}
}
}

return filter;
}

private static boolean isFrameRotatedUpsideDown(ItemFrame frame) {
return (frame.getRotation() + 1) % ItemFrame.NUM_ROTATIONS >= ItemFrame.NUM_ROTATIONS / 2;
}

@Override
public boolean acceptsRedstone() {
return true;
Expand Down
88 changes: 88 additions & 0 deletions Xplat/src/main/java/vazkii/botania/common/helper/FilterHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package vazkii.botania.common.helper;

import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.util.random.Weight;
import net.minecraft.util.random.WeightedEntry;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import vazkii.botania.mixin.BundleItemAccessor;

import java.util.ArrayList;
import java.util.List;

public class FilterHelper {

public static final String ITEMS_TAG = "Items";
public static final String TAG_EXPAND_FILTERS = "expandFilters";

/**
* Expands the given filter item into the list of filter items it represents. This is NOT recursive.
* Non-empty container items stand for their contents, not for themselves.
*/
public static List<ItemStack> getFilterItems(@Nullable ItemStack filterStack) {
if (filterStack == null) {
return List.of();
}
if (filterStack.is(Items.BUNDLE)) {
// get bundle content
List<ItemStack> bundledItems = BundleItemAccessor.call_getContents(filterStack).toList();
if (!bundledItems.isEmpty()) {
return bundledItems;
}
} else {
// BlockItems (especially shulker boxes) can contain BlockEntity data, which may include an inventory.
// Otherwise, items may represent an inventory themselves (e.g. Flower Pouch or Bauble Box)
CompoundTag tag = filterStack.getItem() instanceof BlockItem
? BlockItem.getBlockEntityData(filterStack)
: filterStack.getTag();
if (tag != null && tag.contains(ITEMS_TAG, Tag.TAG_LIST)) {
// item might contain an inventory
List<ItemStack> items = getItemStacks(tag);
if (items != null) {
return items;
}
}
}
return List.of(filterStack);
}

@Nullable
private static List<ItemStack> getItemStacks(CompoundTag tag) {
try {
ListTag contents = tag.getList(ITEMS_TAG, CompoundTag.TAG_COMPOUND);
List<ItemStack> items = new ArrayList<>(contents.size());
for (int i = 0; i < contents.size(); i++) {
CompoundTag entry = contents.getCompound(i);
ItemStack stack = ItemStack.of(entry);
if (!stack.isEmpty()) {
items.add(stack);
}
}
if (!items.isEmpty()) {
return items;
}
} catch (ClassCastException ce) {
// apparently not a typical container inventory
}
return null;
}

public record WeightedItemStack(ItemStack stack, Weight weight) implements WeightedEntry {
public static WeightedItemStack of(ItemStack stack, int weight) {
return new WeightedItemStack(stack, Weight.of(weight));
}

@NotNull
@Override
public Weight getWeight() {
return weight;
}
}
}
Loading
Loading