Skip to content
Merged
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 @@ -12,6 +12,7 @@
import org.cyclops.colossalchests2.inventory.ContainerChest;
import org.cyclops.colossalchests2.inventory.ContainerDisplay;
import org.cyclops.colossalchests2.inventory.ContainerInterface;
import org.cyclops.colossalchests2.inventory.ContainerRedstone;
import org.cyclops.colossalchests2.storage.ChestStorage;
import org.cyclops.cyclopscore.config.DeferredHolderCommon;

Expand All @@ -29,6 +30,8 @@ public class RegistryEntries {

public static final DeferredHolderCommon<MenuType<?>, MenuType<ContainerInterface>> MENU_INTERFACE = DeferredHolderCommon.create(Registries.MENU, ResourceLocation.parse("colossalchests2:interface"));

public static final DeferredHolderCommon<MenuType<?>, MenuType<ContainerRedstone>> MENU_REDSTONE = DeferredHolderCommon.create(Registries.MENU, ResourceLocation.parse("colossalchests2:redstone"));

public static final DeferredHolderCommon<MenuType<?>, MenuType<ContainerDisplay>> MENU_DISPLAY = DeferredHolderCommon.create(Registries.MENU, ResourceLocation.parse("colossalchests2:display"));

public static final DeferredHolderCommon<MenuType<?>, MenuType<ContainerChest>> MENU_CHEST = DeferredHolderCommon.create(Registries.MENU, ResourceLocation.parse("colossalchests2:chest"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import com.google.common.collect.Lists;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
Expand Down Expand Up @@ -55,8 +57,9 @@ public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {

@Override
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
// A right-click opens the chest, while sneaking opens an interface's settings. Display walls handle their own clicks.
if (type == WallType.INTERFACE && player.isSecondaryUseActive()) {
// A right-click opens the chest, while sneaking opens the settings of an interface or redstone wall. Display walls
// handle their own clicks.
if ((type == WallType.INTERFACE || type == WallType.REDSTONE) && player.isSecondaryUseActive()) {
if (player instanceof ServerPlayer serverPlayer && level.getBlockEntity(pos) instanceof BlockEntityChestWall wall) {
IModHelpers.get().getMinecraftHelpers().openMenu(serverPlayer, wall, buf -> buf.writeBlockPos(pos));
}
Expand All @@ -82,10 +85,35 @@ protected ItemInteractionResult useItemOn(ItemStack stack, BlockState state, Lev
return super.useItemOn(stack, state, level, pos, player, hand, hit);
}

@Override
protected boolean hasAnalogOutputSignal(BlockState state) {
return type == WallType.REDSTONE;
}

@Override
protected int getAnalogOutputSignal(BlockState state, Level level, BlockPos pos) {
return level.getBlockEntity(pos) instanceof BlockEntityChestWall wall ? wall.getComparatorSignal() : 0;
}

@Override
protected boolean isSignalSource(BlockState state) {
return type == WallType.REDSTONE;
}

/**
* Weak power to every side, like a trapped chest, so dust and lamps work without a comparator.
*/
@Override
protected int getSignal(BlockState state, BlockGetter level, BlockPos pos, Direction direction) {
return type == WallType.REDSTONE && state.getValue(FORMED) && level.getBlockEntity(pos) instanceof BlockEntityChestWall wall
? wall.getRedstoneSignal() : 0;
}

@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
return type == WallType.DISPLAY && !level.isClientSide && blockEntityType == RegistryEntries.BLOCK_ENTITY_CHEST_WALL.value()
return (type == WallType.DISPLAY || type == WallType.REDSTONE) && !level.isClientSide
&& blockEntityType == RegistryEntries.BLOCK_ENTITY_CHEST_WALL.value()
? (l, p, s, be) -> BlockEntityChestWall.serverTick(l, p, s, (BlockEntityChestWall) be) : null;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.cyclops.colossalchests2.block;


import java.util.Locale;

/**
Expand All @@ -18,7 +19,11 @@ public enum WallType {
/**
* Shows one item type and its count on its faces, and lets players take and insert it by clicking.
*/
DISPLAY;
DISPLAY,
/**
* A comparator signal for the whole chest, or for the slots holding one item type set in its own GUI.
*/
REDSTONE;

public static final WallType[] VALUES = values();

Expand All @@ -33,7 +38,7 @@ public String getName() {
* @return If automation can move items through this wall.
*/
public boolean exposesItems() {
return this != DISPLAY;
return this == INTERFACE || this == VOID;
}

public String getRegistryName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@
import org.cyclops.colossalchests2.GeneralConfig;
import org.cyclops.colossalchests2.RegistryEntries;
import org.cyclops.colossalchests2.block.BlockChestCore;
import org.cyclops.colossalchests2.block.BlockChestFunctionalWall;
import org.cyclops.colossalchests2.block.BlockChestWall;
import org.cyclops.colossalchests2.block.ChestMaterial;
import org.cyclops.colossalchests2.block.WallType;
import org.cyclops.colossalchests2.capability.ItemHandlerLogic;
import org.cyclops.colossalchests2.capability.StorageSignals;
import org.cyclops.colossalchests2.config.MaterialProperties;
Expand Down Expand Up @@ -354,6 +356,13 @@ protected void tick() {
contentsChanged = false;
setChanged();
level.updateNeighbourForOutputSignal(worldPosition, getBlockState().getBlock());
for (BlockPos pos : decoratedPositions) {
BlockState state = level.getBlockState(pos);
if (state.getBlock() instanceof BlockChestFunctionalWall wall && wall.getType() == WallType.REDSTONE
&& level.getBlockEntity(pos) instanceof BlockEntityChestWall redstoneWall) {
redstoneWall.updateRedstoneSignal();
}
}
}
if (!viewers.isEmpty() && storage.hasDirtySlots()) {
onDirtySlots(storage.drainDirtySlots());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
import org.cyclops.colossalchests2.block.BlockChestWall;
import org.cyclops.colossalchests2.block.WallType;
import org.cyclops.colossalchests2.capability.ItemHandlerLogic;
import org.cyclops.colossalchests2.capability.StorageSignals;
import org.cyclops.colossalchests2.capability.WallAccess;
import org.cyclops.colossalchests2.inventory.ContainerDisplay;
import org.cyclops.colossalchests2.inventory.ContainerInterface;
import org.cyclops.colossalchests2.inventory.ContainerRedstone;
import org.cyclops.colossalchests2.multiblock.ChestCoreIndex;
import org.cyclops.colossalchests2.multiblock.ChestShape;
import org.cyclops.colossalchests2.storage.DisplayStats;
Expand All @@ -46,8 +48,8 @@

/**
* A functional wall, giving automation access to its chest under the wall's rules.
* Only an Interface has settings: its filter and direction. A Display wall keeps the type it shows on each face,
* what each face shows, and its stats for clients.
* An Interface has a filter and direction. A Display wall keeps the type it shows on each face, what each face shows,
* and its stats for clients. A Redstone wall keeps the item type it signals for.
* @author rubensworks
*/
public class BlockEntityChestWall extends BlockEntity implements MenuProvider {
Expand All @@ -59,6 +61,7 @@ public class BlockEntityChestWall extends BlockEntity implements MenuProvider {
*/
public static final int DOUBLE_CLICK_TICKS = 10;
private static final int DISPLAY_UPDATE_TICKS = 10;
private static final int SIGNAL_UPDATE_TICKS = 10;

private final SimpleContainer settings = new SimpleContainer(FILTER_SLOTS) {
@Override
Expand All @@ -79,6 +82,21 @@ public int getMaxStackSize() {
private int disabledOptions;
private final Map<UUID, InsertClick> lastInserts = Maps.newHashMap();
private final Container displayedContainer = new DisplayedContainer();
// The last signal a Redstone wall emitted, so redstone does not recompute it on every query.
private int redstoneSignal;
private final SimpleContainer redstoneTarget = new SimpleContainer(1) {
@Override
public void setChanged() {
super.setChanged();
BlockEntityChestWall.this.setChanged();
updateRedstoneSignal();
}

@Override
public int getMaxStackSize() {
return 1;
}
};

public BlockEntityChestWall(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
Expand Down Expand Up @@ -119,10 +137,51 @@ public WallAccess getAccess() {
return switch (getWallType()) {
case INTERFACE -> new WallAccess(mode, settings.getItems(), false);
case VOID -> new WallAccess(WallAccess.Mode.BOTH, List.of(), true);
case DISPLAY -> WallAccess.OPEN;
case DISPLAY, REDSTONE -> WallAccess.OPEN;
};
}

/**
* @return The item type a Redstone wall signals for, empty for the whole chest.
*/
public Container getRedstoneTarget() {
return redstoneTarget;
}

/**
* @return The comparator signal of a Redstone wall, 0 while its chest is not formed.
*/
public int getComparatorSignal() {
ItemStack target = redstoneTarget.getItem(0);
return getCore().map(core -> target.isEmpty() ? StorageSignals.getComparatorSignal(core.getStorage())
: StorageSignals.getComparatorSignal(core.getStorage(), target)).orElse(0);
}

/**
* @return The redstone power a Redstone wall emits to its neighbours, as last updated.
*/
public int getRedstoneSignal() {
return redstoneSignal;
}

/**
* Refresh the signal of a Redstone wall. Only when it changed are neighbours and comparators updated, as both
* read the same value.
*/
public void updateRedstoneSignal() {
if (level == null || level.isClientSide) {
return;
}
int signal = getComparatorSignal();
if (signal != redstoneSignal) {
redstoneSignal = signal;
setChanged();
Block block = getBlockState().getBlock();
level.updateNeighborsAt(worldPosition, block);
level.updateNeighbourForOutputSignal(worldPosition, block);
}
}

/**
* @return The formed chest this wall belongs to.
*/
Expand Down Expand Up @@ -270,6 +329,10 @@ public static void serverTick(Level level, BlockPos pos, BlockState state, Block
if (level.getGameTime() % DISPLAY_UPDATE_TICKS == 0 && wall.getWallType() == WallType.DISPLAY) {
wall.updateDisplayStats(false);
}
// Contents changes update the signal right away, this catches the chest forming or breaking.
if (level.getGameTime() % SIGNAL_UPDATE_TICKS == 0 && wall.getWallType() == WallType.REDSTONE) {
wall.updateRedstoneSignal();
}
}

private void onSettingsChanged() {
Expand All @@ -292,6 +355,10 @@ protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries)
tag.putString("mode", mode.name());
ContainerHelper.saveAllItems(tag, settings.getItems(), registries);
saveDisplay(tag, registries, false);
if (!redstoneTarget.getItem(0).isEmpty()) {
tag.put("redstone_target", redstoneTarget.getItem(0).save(registries));
}
tag.putInt("redstone_signal", redstoneSignal);
}

private void saveDisplay(CompoundTag tag, HolderLookup.Provider registries, boolean withStats) {
Expand Down Expand Up @@ -345,6 +412,9 @@ protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries)
settings.getItems().set(i, items.get(i));
}
loadDisplay(tag, registries);
redstoneTarget.getItems().set(0, tag.contains("redstone_target")
? ItemStack.parseOptional(registries, tag.getCompound("redstone_target")) : ItemStack.EMPTY);
redstoneSignal = tag.getInt("redstone_signal");
}

@Override
Expand All @@ -369,7 +439,11 @@ public Component getDisplayName() {
@Nullable
@Override
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) {
return getWallType() == WallType.DISPLAY ? new ContainerDisplay(id, inventory, this) : new ContainerInterface(id, inventory, this);
return switch (getWallType()) {
case DISPLAY -> new ContainerDisplay(id, inventory, this);
case REDSTONE -> new ContainerRedstone(id, inventory, this);
default -> new ContainerInterface(id, inventory, this);
};
}

private record InsertClick(Direction face, long gameTime) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.cyclops.colossalchests2.capability;

import net.minecraft.world.item.ItemStack;
import org.cyclops.colossalchests2.storage.ChestStorage;
import org.cyclops.colossalchests2.storage.DeepSlot;
import org.cyclops.colossalchests2.storage.DisplayStats;

/**
* Redstone signals derived from a {@link ChestStorage}.
Expand Down Expand Up @@ -37,4 +39,16 @@ public static int getComparatorSignal(ChestStorage storage) {
return (int) Math.floor(fill / slots * 14.0) + 1;
}

/**
* Comparator signal for one item type: 0 when the chest holds none, otherwise 1 to 15 by how full the slots
* holding it are.
* @param storage A storage.
* @param type An item type.
* @return A signal from 0 to 15.
*/
public static int getComparatorSignal(ChestStorage storage, ItemStack type) {
DisplayStats stats = DisplayStats.of(storage, type);
return stats.count() <= 0 ? 0 : (int) Math.floor(stats.getFillLevel() * 14.0) + 1;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.cyclops.colossalchests2.client.gui;

import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.Slot;
import org.cyclops.colossalchests2.inventory.ContainerRedstone;

import java.util.List;

/**
* Settings of a Redstone wall.
* @author rubensworks
*/
public class ContainerScreenRedstone extends AbstractContainerScreen<ContainerRedstone> {

private static final int COLOR_LABEL = 0x404040;
private static final int COLOR_SIGNAL = 0xC02010;

public ContainerScreenRedstone(ContainerRedstone menu, Inventory inventory, Component title) {
super(menu, inventory, title);
this.imageWidth = ContainerRedstone.WIDTH;
this.imageHeight = ContainerRedstone.HEIGHT;
this.inventoryLabelY = ContainerRedstone.INVENTORY_Y - 11;
}

@Override
protected void renderBg(GuiGraphics guiGraphics, float partialTick, int mouseX, int mouseY) {
GuiPanels.drawPanel(guiGraphics, leftPos, topPos, imageWidth, imageHeight);
for (Slot slot : menu.slots) {
GuiPanels.drawSlot(guiGraphics, leftPos + slot.x, topPos + slot.y);
}
}

@Override
protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) {
super.renderLabels(guiGraphics, mouseX, mouseY);
// What the slot means when empty, and the signal it gives now.
Component target = menu.getSlot(0).hasItem() ? menu.getSlot(0).getItem().getHoverName()
: Component.translatable("gui.colossalchests2.redstone.whole_chest");
int textX = ContainerRedstone.TARGET_X + 22;
guiGraphics.drawString(font, target, textX, ContainerRedstone.TARGET_Y - 1, COLOR_LABEL, false);
Component signal = Component.translatable("gui.colossalchests2.redstone.signal", menu.getSignal());
guiGraphics.drawString(font, signal, textX, ContainerRedstone.TARGET_Y + 9, COLOR_SIGNAL, false);
}

@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
super.render(guiGraphics, mouseX, mouseY, partialTick);
renderTooltip(guiGraphics, mouseX, mouseY);
}

@Override
protected void renderTooltip(GuiGraphics guiGraphics, int mouseX, int mouseY) {
if (menu.getCarried().isEmpty() && hoveredSlot != null && menu.isGhostSlot(hoveredSlot.index) && !hoveredSlot.hasItem()) {
String key = "gui.colossalchests2.redstone.whole_chest";
guiGraphics.renderComponentTooltip(font, List.of(Component.translatable(key),
Component.translatable(key + ".info").withStyle(ChatFormatting.GRAY)), mouseX, mouseY);
return;
}
super.renderTooltip(guiGraphics, mouseX, mouseY);
}
}
Loading
Loading