diff --git a/endercore/src/main/java/com/enderio/core/client/gui/screen/EnderContainerScreen.java b/endercore/src/main/java/com/enderio/core/client/gui/screen/EnderContainerScreen.java index 295462bb38..4302c059ae 100644 --- a/endercore/src/main/java/com/enderio/core/client/gui/screen/EnderContainerScreen.java +++ b/endercore/src/main/java/com/enderio/core/client/gui/screen/EnderContainerScreen.java @@ -5,10 +5,6 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.mojang.blaze3d.systems.RenderSystem; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ComponentPath; import net.minecraft.client.gui.GuiGraphics; @@ -26,6 +22,14 @@ import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + public abstract class EnderContainerScreen extends AbstractContainerScreen { private static final int ITEM_RENDER_Z = 400; @@ -205,7 +209,12 @@ public void mouseMoved(double pMouseX, double pMouseY) { @Override public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { - for (var layer : overlayWidgets.keySet()) { + + // Check the layers in reverse order for correct click propagation (from top to bottom layer) + List sortedLayers = new ArrayList<>(overlayWidgets.keySet()); + sortedLayers.sort(Comparator.reverseOrder()); + + for (int layer : sortedLayers) { for (var overlay : overlayWidgets.get(layer)) { if (!(overlay instanceof AbstractWidget widget) || widget.isActive()) { if (overlay.isMouseOver(pMouseX, pMouseY)) { @@ -221,7 +230,10 @@ public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { @Override public boolean mouseReleased(double pMouseX, double pMouseY, int pButton) { - for (var layer : overlayWidgets.keySet()) { + List sortedLayers = new ArrayList<>(overlayWidgets.keySet()); + sortedLayers.sort(Comparator.reverseOrder()); + + for (int layer : sortedLayers) { for (var overlay : overlayWidgets.get(layer)) { if (!(overlay instanceof AbstractWidget widget) || widget.isActive()) { if (overlay.isMouseOver(pMouseX, pMouseY)) { @@ -234,10 +246,12 @@ public boolean mouseReleased(double pMouseX, double pMouseY, int pButton) { return super.mouseReleased(pMouseX, pMouseY, pButton); } - // Always pass mouse drag event through widgets first. @Override public boolean mouseDragged(double pMouseX, double pMouseY, int pButton, double pDragX, double pDragY) { - for (var layer : overlayWidgets.keySet()) { + List sortedLayers = new ArrayList<>(overlayWidgets.keySet()); + sortedLayers.sort(Comparator.reverseOrder()); + + for (int layer : sortedLayers) { for (var overlay : overlayWidgets.get(layer)) { if (!(overlay instanceof AbstractWidget widget) || widget.isActive()) { if (overlay.isMouseOver(pMouseX, pMouseY)) { @@ -256,7 +270,10 @@ public boolean mouseDragged(double pMouseX, double pMouseY, int pButton, double @Override public boolean mouseScrolled(double pMouseX, double pMouseY, double pScrollX, double pScrollY) { - for (var layer : overlayWidgets.keySet()) { + List sortedLayers = new ArrayList<>(overlayWidgets.keySet()); + sortedLayers.sort(Comparator.reverseOrder()); + + for (int layer : sortedLayers) { for (var overlay : overlayWidgets.get(layer)) { if (!(overlay instanceof AbstractWidget widget) || widget.isActive()) { if (overlay.isMouseOver(pMouseX, pMouseY)) { @@ -269,6 +286,7 @@ public boolean mouseScrolled(double pMouseX, double pMouseY, double pScrollX, do return super.mouseScrolled(pMouseX, pMouseY, pScrollX, pScrollY); } + /** * @deprecated Use {@link #onKeyPressed(int, int, int)} instead. */ diff --git a/enderio-base/src/generated/resources/assets/enderio/lang/en_us.json b/enderio-base/src/generated/resources/assets/enderio/lang/en_us.json index 28d0661c27..33301bbe48 100644 --- a/enderio-base/src/generated/resources/assets/enderio/lang/en_us.json +++ b/enderio-base/src/generated/resources/assets/enderio/lang/en_us.json @@ -220,6 +220,13 @@ "fluid_type.enderio.vapor_of_levity": "Vapor of Levity", "fluid_type.enderio.xp_juice": "XP Juice", "gui.enderio.cancel": "Cancel", + "gui.enderio.channel.add_channel": "Add channel", + "gui.enderio.channel.available": "Available", + "gui.enderio.channel.delete_channel": "Delete channel", + "gui.enderio.channel.private": "Channel is private", + "gui.enderio.channel.public": "Channel is public", + "gui.enderio.channel.receive": "Receive", + "gui.enderio.channel.send": "Send", "gui.enderio.collision.animals_block": "Only solid to animals", "gui.enderio.collision.animals_pass": "Not solid to animals", "gui.enderio.collision.mobs_block": "Only solid to monsters", diff --git a/enderio-base/src/main/java/com/enderio/base/api/collections/RoundRobinList.java b/enderio-base/src/main/java/com/enderio/base/api/collections/RoundRobinList.java new file mode 100644 index 0000000000..2a2f983269 --- /dev/null +++ b/enderio-base/src/main/java/com/enderio/base/api/collections/RoundRobinList.java @@ -0,0 +1,61 @@ +package com.enderio.base.api.collections; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +public class RoundRobinList { + private final List items = new CopyOnWriteArrayList<>(); + private final AtomicInteger currentIndex = new AtomicInteger(0); + + public void add(T item) { + if (!items.contains(item)) { + items.add(item); + } + } + + + public boolean remove(T item) { + boolean removed = items.remove(item); + if (removed && currentIndex.get() >= items.size()) { + currentIndex.set(0); + } + + return removed; + } + + public boolean isEmpty() { + return items.isEmpty(); + } + + public List getAll() { + return new ArrayList<>(items); + } + + public Iterable iterate() { + return () -> new Iterator() { + private int visited = 0; + private final int size = items.size(); + + @Override + public boolean hasNext() { + return visited < size && size > 0; + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + int index = currentIndex.getAndUpdate(i -> (i + 1) % size); + visited++; + + return items.get(index); + } + }; + } +} diff --git a/enderio-base/src/main/java/com/enderio/base/api/collections/package-info.java b/enderio-base/src/main/java/com/enderio/base/api/collections/package-info.java new file mode 100644 index 0000000000..fa06462f41 --- /dev/null +++ b/enderio-base/src/main/java/com/enderio/base/api/collections/package-info.java @@ -0,0 +1,4 @@ +@javax.annotation.ParametersAreNonnullByDefault +@net.minecraft.MethodsReturnNonnullByDefault + +package com.enderio.base.api.collections; diff --git a/enderio-base/src/main/java/com/enderio/base/common/lang/EIOLang.java b/enderio-base/src/main/java/com/enderio/base/common/lang/EIOLang.java index d159b50623..2fe49c990d 100644 --- a/enderio-base/src/main/java/com/enderio/base/common/lang/EIOLang.java +++ b/enderio-base/src/main/java/com/enderio/base/common/lang/EIOLang.java @@ -5,13 +5,14 @@ import com.enderio.base.api.capacitor.CapacitorModifier; import com.enderio.base.common.block.glass.GlassLighting; import com.enderio.core.common.util.TooltipUtil; -import java.util.Locale; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.DyeColor; +import java.util.Locale; + public class EIOLang { public static final Component BLOCK_BLAST_RESISTANT = TooltipUtil .style(addTranslation("tooltip", EnderIO.loc("block.blast_resistant"), "Blast resistant")); @@ -204,6 +205,19 @@ public class EIOLang { public static final Component VISIBLE = addTranslation("gui", EnderIO.loc("visible.true"), "Visible"); public static final Component NOT_VISIBLE = addTranslation("gui", EnderIO.loc("visible.false"), "Hidden"); + + public static final Component CHANNEL_PRIVATE = addTranslation("gui", EnderIO.loc("channel.private"), + "Channel is private"); + public static final Component CHANNEL_PUBLIC = addTranslation("gui", EnderIO.loc("channel.public"), + "Channel is public"); + public static final Component DELETE_CHANNEL = addTranslation("gui", EnderIO.loc("channel.delete_channel"), + "Delete channel"); + public static final Component ADD_CHANNEL = addTranslation("gui", EnderIO.loc("channel.add_channel"), + "Add channel"); + + public static final Component AVAILABLE = addTranslation("gui", EnderIO.loc("channel.available"), "Available"); + public static final Component SEND = addTranslation("gui", EnderIO.loc("channel.send"), "Send"); + public static final Component RECEIVE = addTranslation("gui", EnderIO.loc("channel.receive"), "Receive"); // endregion // region Entity Storage diff --git a/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/arrows.png b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/arrows.png new file mode 100644 index 0000000000..14c3f2cef0 Binary files /dev/null and b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/arrows.png differ diff --git a/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_add.png b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_add.png new file mode 100644 index 0000000000..7153624e03 Binary files /dev/null and b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_add.png differ diff --git a/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_delete.png b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_delete.png new file mode 100644 index 0000000000..bb1b497703 Binary files /dev/null and b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_delete.png differ diff --git a/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_private.png b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_private.png new file mode 100644 index 0000000000..24e139d2fc Binary files /dev/null and b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_private.png differ diff --git a/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_public.png b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_public.png new file mode 100644 index 0000000000..88eb25aca0 Binary files /dev/null and b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/buttons/channel_public.png differ diff --git a/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/icon/conduit_icon/energy.png b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/icon/conduit_icon/energy.png new file mode 100644 index 0000000000..142c967fee Binary files /dev/null and b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/icon/conduit_icon/energy.png differ diff --git a/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/icon/conduit_icon/fluid.png b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/icon/conduit_icon/fluid.png new file mode 100644 index 0000000000..575d6390c4 Binary files /dev/null and b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/icon/conduit_icon/fluid.png differ diff --git a/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/icon/conduit_icon/item.png b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/icon/conduit_icon/item.png new file mode 100644 index 0000000000..485f4772ab Binary files /dev/null and b/enderio-base/src/main/resources/assets/enderio/textures/gui/sprites/icon/conduit_icon/item.png differ diff --git a/enderio-machines/src/generated/resources/assets/enderio/blockstates/transceiver.json b/enderio-machines/src/generated/resources/assets/enderio/blockstates/transceiver.json new file mode 100644 index 0000000000..e6f531709c --- /dev/null +++ b/enderio-machines/src/generated/resources/assets/enderio/blockstates/transceiver.json @@ -0,0 +1,34 @@ +{ + "variants": { + "facing=east,powered=false": { + "model": "enderio:block/transceiver_combined", + "y": 90 + }, + "facing=east,powered=true": { + "model": "enderio:block/transceiver_active_combined", + "y": 90 + }, + "facing=north,powered=false": { + "model": "enderio:block/transceiver_combined" + }, + "facing=north,powered=true": { + "model": "enderio:block/transceiver_active_combined" + }, + "facing=south,powered=false": { + "model": "enderio:block/transceiver_combined", + "y": 180 + }, + "facing=south,powered=true": { + "model": "enderio:block/transceiver_active_combined", + "y": 180 + }, + "facing=west,powered=false": { + "model": "enderio:block/transceiver_combined", + "y": 270 + }, + "facing=west,powered=true": { + "model": "enderio:block/transceiver_active_combined", + "y": 270 + } + } +} \ No newline at end of file diff --git a/enderio-machines/src/generated/resources/assets/enderio/lang/en_us.json b/enderio-machines/src/generated/resources/assets/enderio/lang/en_us.json index 0022de0ca0..25f009d726 100644 --- a/enderio-machines/src/generated/resources/assets/enderio/lang/en_us.json +++ b/enderio-machines/src/generated/resources/assets/enderio/lang/en_us.json @@ -29,6 +29,7 @@ "block.enderio.soul_binder": "Soul Binder", "block.enderio.soul_engine": "Soul Engine", "block.enderio.stirling_generator": "Stirling Generator", + "block.enderio.transceiver": "Dimensional Transceiver", "block.enderio.travel_anchor": "Travel Anchor", "block.enderio.vacuum_chest": "Vacuum Chest", "block.enderio.vat": "VAT", diff --git a/enderio-machines/src/generated/resources/assets/enderio/models/block/transceiver_active_combined.json b/enderio-machines/src/generated/resources/assets/enderio/models/block/transceiver_active_combined.json new file mode 100644 index 0000000000..a3310e2088 --- /dev/null +++ b/enderio-machines/src/generated/resources/assets/enderio/models/block/transceiver_active_combined.json @@ -0,0 +1,19 @@ +{ + "parent": "minecraft:block/block", + "children": { + "machine": { + "parent": "enderio:block/transceiver_active" + }, + "overlay": { + "parent": "enderio:block/io_overlay" + } + }, + "item_render_order": [ + "machine", + "overlay" + ], + "loader": "neoforge:composite", + "textures": { + "particle": "enderio:block/transceiver_front" + } +} \ No newline at end of file diff --git a/enderio-machines/src/generated/resources/assets/enderio/models/block/transceiver_combined.json b/enderio-machines/src/generated/resources/assets/enderio/models/block/transceiver_combined.json new file mode 100644 index 0000000000..20de9f231b --- /dev/null +++ b/enderio-machines/src/generated/resources/assets/enderio/models/block/transceiver_combined.json @@ -0,0 +1,19 @@ +{ + "parent": "minecraft:block/block", + "children": { + "machine": { + "parent": "enderio:block/transceiver" + }, + "overlay": { + "parent": "enderio:block/io_overlay" + } + }, + "item_render_order": [ + "machine", + "overlay" + ], + "loader": "neoforge:composite", + "textures": { + "particle": "enderio:block/transceiver_front" + } +} \ No newline at end of file diff --git a/enderio-machines/src/generated/resources/assets/enderio/models/item/transceiver.json b/enderio-machines/src/generated/resources/assets/enderio/models/item/transceiver.json new file mode 100644 index 0000000000..e331cd1e46 --- /dev/null +++ b/enderio-machines/src/generated/resources/assets/enderio/models/item/transceiver.json @@ -0,0 +1,3 @@ +{ + "parent": "enderio:block/transceiver" +} \ No newline at end of file diff --git a/enderio-machines/src/generated/resources/data/enderio/loot_table/blocks/transceiver.json b/enderio-machines/src/generated/resources/data/enderio/loot_table/blocks/transceiver.json new file mode 100644 index 0000000000..e9a900b357 --- /dev/null +++ b/enderio-machines/src/generated/resources/data/enderio/loot_table/blocks/transceiver.json @@ -0,0 +1,22 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:item", + "functions": [ + { + "function": "minecraft:copy_components", + "source": "block_entity" + } + ], + "name": "enderio:transceiver" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "enderio:blocks/transceiver" +} \ No newline at end of file diff --git a/enderio-machines/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json b/enderio-machines/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json index 517839cb47..974f9bc8df 100644 --- a/enderio-machines/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json +++ b/enderio-machines/src/generated/resources/data/minecraft/tags/block/mineable/pickaxe.json @@ -38,6 +38,7 @@ "enderio:aversion_obelisk", "enderio:relocator_obelisk", "enderio:attractor_obelisk", - "enderio:weather_obelisk" + "enderio:weather_obelisk", + "enderio:transceiver" ] } \ No newline at end of file diff --git a/enderio-machines/src/generated/resources/data/minecraft/tags/block/needs_iron_tool.json b/enderio-machines/src/generated/resources/data/minecraft/tags/block/needs_iron_tool.json index b7ba2d5807..88ac19466c 100644 --- a/enderio-machines/src/generated/resources/data/minecraft/tags/block/needs_iron_tool.json +++ b/enderio-machines/src/generated/resources/data/minecraft/tags/block/needs_iron_tool.json @@ -31,6 +31,7 @@ "enderio:aversion_obelisk", "enderio:relocator_obelisk", "enderio:attractor_obelisk", - "enderio:weather_obelisk" + "enderio:weather_obelisk", + "enderio:transceiver" ] } \ No newline at end of file diff --git a/enderio-machines/src/main/java/com/enderio/machines/client/gui/screen/TransceiverScreen.java b/enderio-machines/src/main/java/com/enderio/machines/client/gui/screen/TransceiverScreen.java new file mode 100644 index 0000000000..4a3039b1c7 --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/client/gui/screen/TransceiverScreen.java @@ -0,0 +1,130 @@ +package com.enderio.machines.client.gui.screen; + +import com.enderio.base.api.EnderIO; +import com.enderio.core.client.gui.widgets.IconButton; +import com.enderio.machines.client.gui.screen.base.MachineScreen; +import com.enderio.machines.client.gui.widget.CapacitorEnergyWidget; +import com.enderio.machines.client.gui.widget.ChannelSelectWidget; +import com.enderio.machines.common.transceiver.Channel; +import com.enderio.machines.common.transceiver.ChannelType; +import com.enderio.machines.common.transceiver.TransceiverMenu; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Inventory; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class TransceiverScreen extends MachineScreen { + + private static final ResourceLocation TRANSCEIVER_GENERAL_TEXTURE = EnderIO.loc("textures/gui/screen/transceiver_general.png"); + private static final ResourceLocation ICON_CONFIGURE = EnderIO.loc("icon/configure"); + + private static final int WIDTH = 255; + private static final int HEIGHT = 166; + private static final int TAB_BUTTON_SIZE = 12; + private static final int TAB_BUTTON_SPACING = 20; + + public TransceiverScreen(TransceiverMenu pMenu, Inventory pPlayerInventory, Component pTitle) { + super(pMenu, pPlayerInventory, pTitle); + imageWidth = WIDTH; + imageHeight = HEIGHT; + } + + @Override + protected void init() { + super.init(); + refreshWidgets(); + } + + private void switchToTab(ChannelType newType) { + menu.setSelectedType(newType); + refreshWidgets(); + } + + private void refreshWidgets() { + clearWidgets(); + addTabButtons(); + + if (menu.getSelectedType() != null) { + createChannelSelectWidget(); + } else { + addRenderableOnly(new CapacitorEnergyWidget(11 + leftPos, 14 + topPos, 9, 58, menu::getEnergyStorage, + menu::isCapacitorInstalled)); + } + } + + private void createChannelSelectWidget() { + ChannelType selectedType = menu.getSelectedType(); + + // Get channels from block entity + Set receiveChannels = menu.getBlockEntity().getReceiveChannels().getChannels(selectedType); + Set sendChannels = menu.getBlockEntity().getSendChannels().getChannels(selectedType); + Set allChannels = menu.getChannelList().get(selectedType); + + // Calculate available channels (not in send or receive) + Set availableChannels = calculateAvailableChannels(allChannels, sendChannels, receiveChannels); + + ChannelSelectWidget.Config config = new ChannelSelectWidget.Config( + this, + availableChannels, + sendChannels, + receiveChannels, + leftPos, + topPos, + imageWidth, + imageHeight + ); + + ChannelSelectWidget channelSelectWidget = new ChannelSelectWidget(config); + registerChannelSelectWidget(channelSelectWidget); + } + + private Set calculateAvailableChannels(Set allChannels, Set sendChannels, Set receiveChannels) { + Set availableChannels = new LinkedHashSet<>(); + + for (Channel channel : allChannels) { + if (!receiveChannels.contains(channel) && !sendChannels.contains(channel)) { + availableChannels.add(channel); + } + } + + return availableChannels; + } + + private void registerChannelSelectWidget(ChannelSelectWidget widget) { + addOverlayRenderable(1, widget); + addRenderableWidget(widget); + + for (var childWidget : widget.getChildren()) { + addOverlayRenderable(2, childWidget); + addRenderableWidget(childWidget); + } + } + + public void addTabButtons() { + int x = this.leftPos + WIDTH; + int y = this.topPos + 10; + + IconButton generalButton = createTabButton(x, y, ICON_CONFIGURE, Component.empty(), () -> switchToTab(null)); + addRenderableWidget(generalButton); + + y += TAB_BUTTON_SPACING; + + for (ChannelType type : ChannelType.values()) { + IconButton button = createTabButton(x, y, type.icon, Component.literal(type.tooltip), () -> switchToTab(type)); + addRenderableWidget(button); + y += TAB_BUTTON_SPACING; + } + } + + private IconButton createTabButton(int x, int y, ResourceLocation icon, Component tooltip, Runnable action) { + return new IconButton(x, y, TAB_BUTTON_SIZE, TAB_BUTTON_SIZE, icon, tooltip, action); + } + + @Override + protected void renderBg(GuiGraphics guiGraphics, float partialTick, int mouseX, int mouseY) { + guiGraphics.blit(TRANSCEIVER_GENERAL_TEXTURE, leftPos, topPos, 0, 0, imageWidth, imageHeight); + } +} diff --git a/enderio-machines/src/main/java/com/enderio/machines/client/gui/widget/ChannelSelectWidget.java b/enderio-machines/src/main/java/com/enderio/machines/client/gui/widget/ChannelSelectWidget.java new file mode 100644 index 0000000000..bdc5de76fd --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/client/gui/widget/ChannelSelectWidget.java @@ -0,0 +1,318 @@ +package com.enderio.machines.client.gui.widget; + +import com.enderio.base.api.EnderIO; +import com.enderio.base.common.lang.EIOLang; +import com.enderio.core.client.gui.widgets.EIOWidget; +import com.enderio.core.client.gui.widgets.IconButton; +import com.enderio.core.client.gui.widgets.ToggleIconButton; +import com.enderio.machines.client.gui.screen.TransceiverScreen; +import com.enderio.machines.common.network.transceiver.AddRemoveGlobalChannelPacket; +import com.enderio.machines.common.network.transceiver.AddRemoveTransceiverChannelPacket; +import com.enderio.machines.common.transceiver.Channel; +import com.enderio.machines.common.transceiver.ChannelListWidget; +import com.enderio.machines.common.transceiver.ChannelType; +import com.enderio.machines.common.transceiver.TransceiverBlockEntity; +import com.mojang.logging.LogUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.network.PacketDistributor; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class ChannelSelectWidget extends EIOWidget { + private static final ResourceLocation BACKGROUND_TEXTURE = EnderIO.loc("textures/gui/screen/transceiver_channel_select.png"); + private static final ResourceLocation ICON_CHANNEL_PRIVATE = EnderIO.loc("buttons/channel_private"); + private static final ResourceLocation ICON_CHANNEL_PUBLIC = EnderIO.loc("buttons/channel_public"); + private static final ResourceLocation ICON_CHANNEL_ADD = EnderIO.loc("buttons/channel_add"); + private static final ResourceLocation ICON_CHANNEL_DELETE = EnderIO.loc("buttons/channel_delete"); + private static final ResourceLocation ICON_ARROWS = EnderIO.loc("buttons/arrows"); + + private static final int CHANNEL_NAME_MAX_LENGTH = 50; + + public record Config(TransceiverScreen screen, Set availableChannels, Set sendChannels, Set receiveChannels, int x, int y, int width, int height) { + public Config(TransceiverScreen screen, Set availableChannels, Set sendChannels, Set receiveChannels, int x, int y, int width, int height) { + this.screen = Objects.requireNonNull(screen, "Screen cannot be null"); + this.availableChannels = Objects.requireNonNull(availableChannels, "Available channels cannot be null"); + this.sendChannels = Objects.requireNonNull(sendChannels, "Send channels cannot be null"); + this.receiveChannels = Objects.requireNonNull(receiveChannels, "Receive channels cannot be null"); + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + } + + public interface ChannelOperations { + void addSendChannel(Channel channel); + void removeSendChannel(Channel channel); + void addReceiveChannel(Channel channel); + void removeReceiveChannel(Channel channel); + ChannelType getSelectedType(); + Supplier isPrivate(); + void setPrivate(boolean isPrivate); + } + + private final ChannelOperations channelOps; + private final ChannelListWidget availableChannelsWidget; + private final ChannelListWidget sendChannelsWidget; + private final ChannelListWidget receiveChannelsWidget; + private final EditBox channelNameBox; + private final ToggleIconButton privateButton; + private final IconButton addChannelButton; + private final IconButton deleteChannelButton; + private final IconButton sendButton; + private final IconButton receiveButton; + private final List children = new ArrayList<>(); + private static final Logger LOGGER = LogUtils.getLogger(); + + public ChannelSelectWidget(Config config) { + super(config.x(), config.y(), config.width(), config.height()); + + this.channelOps = createChannelOperations(config.screen()); + + this.availableChannelsWidget = createChannelListWidget(config.availableChannels(), + config.x() + 7, config.y() + 48, 104, 90); + + int sideX = config.x() + 7 + 104 + 32; + this.sendChannelsWidget = createChannelListWidget(config.sendChannels(), + sideX, config.y() + 48, 104, 35); + this.receiveChannelsWidget = createChannelListWidget(config.receiveChannels(), + sideX, config.y() + 48 + 35 + 20, 104, 35); + + this.channelNameBox = createChannelNameBox(config.x() + 25, config.y() + 12); + this.privateButton = createPrivateButton(config.x() + 118, config.y() + 12); + this.addChannelButton = createAddChannelButton(config.x() + 137, config.y() + 12); + this.deleteChannelButton = createDeleteChannelButton(config.x() + 91, config.y() + 142); + this.sendButton = createSendButton(config.x() + 119, config.y() + 58); + this.receiveButton = createReceiveButton(config.x() + 119, config.y() + 113); + + + Collections.addAll(children, + availableChannelsWidget, sendChannelsWidget, receiveChannelsWidget, + channelNameBox, privateButton, addChannelButton, deleteChannelButton, + sendButton, receiveButton + ); + } + + + private ChannelListWidget createChannelListWidget(Set channels, int x, int y, int width, int height) { + ChannelListWidget widget = new ChannelListWidget(Minecraft.getInstance(), width, height, 0, 17); + widget.setPosition(x, y); + widget.setChannels(channels); + return widget; + } + + private EditBox createChannelNameBox(int x, int y) { + EditBox box = new EditBox(Minecraft.getInstance().font, x, y, 87, 18, Component.literal("ChannelName")); + box.setCanLoseFocus(true); + box.setTextColor(0xFFFFFFFF); + box.setTextColorUneditable(0xFFFFFFFF); + box.setBordered(true); + box.setMaxLength(CHANNEL_NAME_MAX_LENGTH); + box.setEditable(true); + return box; + } + + private ToggleIconButton createPrivateButton(int x, int y) { + return new ToggleIconButton(x, y, 16, 16, + (b) -> b ? ICON_CHANNEL_PRIVATE : ICON_CHANNEL_PUBLIC, + (b) -> b ? EIOLang.CHANNEL_PRIVATE : EIOLang.CHANNEL_PUBLIC, + channelOps.isPrivate(), + channelOps::setPrivate + ); + } + + private IconButton createAddChannelButton(int x, int y) { + return new IconButton(x, y, 16, 16, ICON_CHANNEL_ADD, EIOLang.ADD_CHANNEL, this::addChannel); + } + + private IconButton createDeleteChannelButton(int x, int y) { + return new IconButton(x, y, 16, 16, ICON_CHANNEL_DELETE, EIOLang.DELETE_CHANNEL, this::removeSelectedChannel); + } + + private IconButton createSendButton(int x, int y) { + return new IconButton(x, y, 16, 16, ICON_ARROWS, Component.empty(), this::handleSendButton); + } + + private IconButton createReceiveButton(int x, int y) { + return new IconButton(x, y, 16, 16, ICON_ARROWS, Component.empty(), this::handleReceiveButton); + } + + private ChannelOperations createChannelOperations(TransceiverScreen screen) { + return new ChannelOperations() { + @Override + public void addSendChannel(Channel channel) { + TransceiverBlockEntity blockEntity = screen.getMenu().getBlockEntity(); + PacketDistributor.sendToServer(new AddRemoveTransceiverChannelPacket(blockEntity.getBlockPos(), channel, true, true, false)); + } + + @Override + public void removeSendChannel(Channel channel) { + TransceiverBlockEntity blockEntity = screen.getMenu().getBlockEntity(); + PacketDistributor.sendToServer(new AddRemoveTransceiverChannelPacket(blockEntity.getBlockPos(), channel, false, true, false)); + } + + @Override + public void addReceiveChannel(Channel channel) { + TransceiverBlockEntity blockEntity = screen.getMenu().getBlockEntity(); + PacketDistributor.sendToServer(new AddRemoveTransceiverChannelPacket(blockEntity.getBlockPos(), channel, true, false, true)); + } + + @Override + public void removeReceiveChannel(Channel channel) { + TransceiverBlockEntity blockEntity = screen.getMenu().getBlockEntity(); + PacketDistributor.sendToServer(new AddRemoveTransceiverChannelPacket(blockEntity.getBlockPos(), channel, false, false, true)); + } + + @Override + public ChannelType getSelectedType() { + return screen.getMenu().getSelectedType(); + } + + @Override + public Supplier isPrivate() { + return () -> screen.getMenu().isPrivate().get(); + } + + @Override + public void setPrivate(boolean isPrivate) { + screen.getMenu().setPrivate(isPrivate); + } + }; + } + + private void handleSendButton() { + handleChannelTransfer( + availableChannelsWidget, sendChannelsWidget, + channelOps::addSendChannel, channelOps::removeSendChannel + ); + } + + private void handleReceiveButton() { + handleChannelTransfer( + availableChannelsWidget, receiveChannelsWidget, + channelOps::addReceiveChannel, channelOps::removeReceiveChannel + ); + } + + private void handleChannelTransfer( + ChannelListWidget sourceWidget1, ChannelListWidget targetWidget1, + Consumer addOperation, + Consumer removeOperation) { + + if (transferChannel(sourceWidget1, targetWidget1, addOperation, true)) { + return; + } + + transferChannel(targetWidget1, sourceWidget1, removeOperation, false); + } + + private boolean transferChannel( + ChannelListWidget fromWidget, ChannelListWidget toWidget, + Consumer operation, boolean isAddOperation) { + + ChannelListWidget.Entry selectedEntry = fromWidget.getSelected(); + if (selectedEntry == null) { + return false; + } + + Channel channel = selectedEntry.getChannel(); + if (channel == null) { + return false; + } + + operation.accept(channel); + + if (isAddOperation) { + fromWidget.removeEntry(selectedEntry); + toWidget.addChannel(channel); + } else { + fromWidget.removeChannelEntry(selectedEntry, channel); + toWidget.addEntry(selectedEntry); + } + + return true; + } + + private void addChannel() { + String channelName = channelNameBox.getValue().trim(); + if (channelName.isEmpty()) { + return; + } + + try { + String playerName = Minecraft.getInstance().player.getName().getString(); + boolean isPrivate = channelOps.isPrivate().get(); + ChannelType type = channelOps.getSelectedType(); + + Channel channel = new Channel(channelName, playerName, type, isPrivate); + PacketDistributor.sendToServer(new AddRemoveGlobalChannelPacket(channel, true)); + + availableChannelsWidget.addChannel(channel); + channelNameBox.setValue(""); + } catch (Exception e) { + LOGGER.error("Failed to create channel {}", channelName); + } + } + + private void removeSelectedChannel() { + ChannelListWidget.Entry selectedEntry = availableChannelsWidget.getSelected(); + if (selectedEntry == null) { + return; + } + + Channel channel = selectedEntry.getChannel(); + if (channel == null) { + return; + } + + try { + PacketDistributor.sendToServer(new AddRemoveGlobalChannelPacket(channel, false)); + availableChannelsWidget.removeChannelEntry(selectedEntry, channel); + } catch (Exception e) { + LOGGER.error("Failed to delete channel {}", channel.name()); + } + } + + @Override + protected void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + guiGraphics.blit(BACKGROUND_TEXTURE, getX(), getY(), 0, 0, getWidth(), getHeight()); + + Font font = Minecraft.getInstance().font; + guiGraphics.drawCenteredString(font, EIOLang.AVAILABLE, getX() + 59, getY() + 36, 0xFFFFFFFF); + guiGraphics.drawCenteredString(font, EIOLang.SEND, getX() + 199, getY() + 36, 0xFFFFFFFF); + guiGraphics.drawCenteredString(font, EIOLang.RECEIVE, getX() + 199, getY() + 92, 0xFFFFFFFF); + } + + public List getChildren() { + return children; + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput output) { + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (channelNameBox.isFocused()) { + if (channelNameBox.keyPressed(keyCode, scanCode, modifiers) || channelNameBox.canConsumeInput()) { + return true; + } + } + + return super.keyPressed(keyCode, scanCode, modifiers); + } +} diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/config/common/EnergyConfig.java b/enderio-machines/src/main/java/com/enderio/machines/common/config/common/EnergyConfig.java index 86dcf72666..3640f9507a 100644 --- a/enderio-machines/src/main/java/com/enderio/machines/common/config/common/EnergyConfig.java +++ b/enderio-machines/src/main/java/com/enderio/machines/common/config/common/EnergyConfig.java @@ -55,6 +55,9 @@ public class EnergyConfig { public final ModConfigSpec.ConfigValue RELOCATOR_USAGE; public final ModConfigSpec.ConfigValue ATTRACTOR_CAPACITY; public final ModConfigSpec.ConfigValue ATTRACTOR_USAGE; + public final ModConfigSpec.ConfigValue TRANSCEIVER_CAPACITY; + public final ModConfigSpec.ConfigValue TRANSCEIVER_USAGE; + public EnergyConfig(ModConfigSpec.Builder builder) { builder.push("energy"); @@ -227,6 +230,13 @@ public EnergyConfig(ModConfigSpec.Builder builder) { .defineInRange("usage", 20, 1, Integer.MAX_VALUE); builder.pop(); + builder.push("transceiver"); + TRANSCEIVER_CAPACITY = builder.comment("The base energy capacity in uI.") + .defineInRange("capacity", 250_000, 1, Integer.MAX_VALUE); + TRANSCEIVER_USAGE = builder.comment("The base energy consumption in uI/t.") + .defineInRange("usage", 10000, 1, Integer.MAX_VALUE); + builder.pop(); + builder.pop(); } } diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineBlockEntities.java b/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineBlockEntities.java index 21ad9dae45..4d911ac0b6 100644 --- a/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineBlockEntities.java +++ b/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineBlockEntities.java @@ -46,6 +46,7 @@ import com.enderio.machines.common.blocks.vat.VatBlockEntity; import com.enderio.machines.common.blocks.wired_charger.WiredChargerBlockEntity; import com.enderio.machines.common.blocks.wireless_charger.WirelessChargerBlockEntity; +import com.enderio.machines.common.transceiver.TransceiverBlockEntity; import com.enderio.regilite.holder.RegiliteBlockEntity; import com.enderio.regilite.registry.BlockEntityRegistry; import com.google.common.collect.ImmutableMap; @@ -230,6 +231,11 @@ public class MachineBlockEntities { MindKillerBlockEntity::new, MachineBlocks.MIND_KILLER) .setRenderer(() -> ObeliskBER.factory(() -> Items.ZOMBIE_HEAD)); //TODO Custom model and BER maybe? + public static final RegiliteBlockEntity TRANSCEIVER = register("transceiver", + TransceiverBlockEntity::new, MachineBlocks.TRANSCEIVER) + .apply(MachineBlockEntities::poweredMachineBlockEntityCapabilities) + .apply(MachineBlockEntities::fluidHandlerCapability); + @SafeVarargs private static RegiliteBlockEntity register(String name, BlockEntityType.BlockEntitySupplier beFactory, Supplier... blocks) { diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineBlocks.java b/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineBlocks.java index e119ee066a..ceda795e8e 100644 --- a/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineBlocks.java +++ b/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineBlocks.java @@ -385,6 +385,9 @@ public class MachineBlocks { prov.models().getExistingFile(EnderIO.loc("block/" + ctx.getName())))) .createBlockItem(ITEM_REGISTRY, item -> item.setTab((EIOCreativeTabs.MACHINES))); + public static final RegiliteBlock> TRANSCEIVER = progressMachine("transceiver", + () -> MachineBlockEntities.TRANSCEIVER).setTranslation("Dimensional Transceiver"); + // used when single methods needs to be overridden in the block class private static > RegiliteBlock baseMachine(RegiliteBlock machineBlock, BiConsumer> blockStateProvider) { diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineMenus.java b/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineMenus.java index 85defedde9..08d6b123d0 100644 --- a/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineMenus.java +++ b/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineMenus.java @@ -20,6 +20,7 @@ import com.enderio.machines.client.gui.screen.SoulBinderScreen; import com.enderio.machines.client.gui.screen.SoulEngineScreen; import com.enderio.machines.client.gui.screen.StirlingGeneratorScreen; +import com.enderio.machines.client.gui.screen.TransceiverScreen; import com.enderio.machines.client.gui.screen.TravelAnchorScreen; import com.enderio.machines.client.gui.screen.VacuumChestScreen; import com.enderio.machines.client.gui.screen.VatScreen; @@ -55,6 +56,7 @@ import com.enderio.machines.common.blocks.wired_charger.WiredChargerMenu; import com.enderio.machines.common.blocks.wireless_charger.WirelessChargerMenu; import com.enderio.machines.common.menu.CapacitorBankMenu; +import com.enderio.machines.common.transceiver.TransceiverMenu; import com.enderio.regilite.holder.RegiliteMenu; import com.enderio.regilite.registry.MenuRegistry; import net.neoforged.bus.api.IEventBus; @@ -119,6 +121,8 @@ private MachineMenus() { () -> VatScreen::new); public static final RegiliteMenu WEATHER_OBELISK = MENU_REGISTRY.registerMenu("weather_obelisk", WeatherObeliskMenu::new, () -> WeatherObeliskScreen::new); + public static final RegiliteMenu TRANSCEIVER = MENU_REGISTRY.registerMenu("transceiver", + TransceiverMenu::new, () -> TransceiverScreen::new); public static void register(IEventBus bus) { MENU_REGISTRY.register(bus); diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineNetwork.java b/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineNetwork.java index d473502447..52ed2ea95c 100644 --- a/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineNetwork.java +++ b/enderio-machines/src/main/java/com/enderio/machines/common/init/MachineNetwork.java @@ -10,6 +10,9 @@ import com.enderio.machines.common.network.SoulEngineSoulPacket; import com.enderio.machines.common.network.TransferItemsPacket; import com.enderio.machines.common.network.UpdateCrafterTemplatePacket; +import com.enderio.machines.common.network.transceiver.AddRemoveGlobalChannelPacket; +import com.enderio.machines.common.network.transceiver.AddRemoveTransceiverChannelPacket; +import com.enderio.machines.common.network.transceiver.GlobalChannelsSyncPacket; import com.enderio.machines.common.souldata.EngineSoul; import com.enderio.machines.common.souldata.FarmSoul; import com.enderio.machines.common.souldata.SolarSoul; @@ -41,9 +44,13 @@ public static void register(final RegisterPayloadHandlersEvent event) { registrar.playToClient(FarmStationSoulPacket.TYPE, FarmStationSoulPacket.STREAM_CODEC, MachinePayloadHandler.Client.getInstance()::handleFarmingStationSoul); + registrar.playToClient(SolarSoulPacket.TYPE, SolarSoulPacket.STREAM_CODEC, MachinePayloadHandler.Client.getInstance()::handleSolarSoul); + registrar.playToClient(GlobalChannelsSyncPacket.TYPE, GlobalChannelsSyncPacket.STREAM_CODEC, + MachinePayloadHandler.Client.getInstance()::handleChannelsSync); + registrar.playToServer(UpdateCrafterTemplatePacket.TYPE, UpdateCrafterTemplatePacket.STREAM_CODEC, MachinePayloadHandler.Server.getInstance()::updateCrafterTemplate); @@ -55,5 +62,11 @@ public static void register(final RegisterPayloadHandlersEvent event) { registrar.playToServer(TransferItemsPacket.TYPE, TransferItemsPacket.STREAM_CODEC, MachinePayloadHandler.Server.getInstance()::handleTransferItems); + + registrar.playToServer(AddRemoveGlobalChannelPacket.TYPE, AddRemoveGlobalChannelPacket.STREAM_CODEC, + MachinePayloadHandler.Server.getInstance()::handleAddRemoveGlobalChannel); + + registrar.playToServer(AddRemoveTransceiverChannelPacket.TYPE, AddRemoveTransceiverChannelPacket.STREAM_CODEC, + MachinePayloadHandler.Server.getInstance()::handleAddRemoveTransceiverChannel); } } diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/network/MachinePayloadHandler.java b/enderio-machines/src/main/java/com/enderio/machines/common/network/MachinePayloadHandler.java index 42d8f55a22..0e1500b923 100644 --- a/enderio-machines/src/main/java/com/enderio/machines/common/network/MachinePayloadHandler.java +++ b/enderio-machines/src/main/java/com/enderio/machines/common/network/MachinePayloadHandler.java @@ -1,15 +1,27 @@ package com.enderio.machines.common.network; +import com.enderio.machines.client.gui.screen.TransceiverScreen; import com.enderio.machines.common.blocks.base.blockentity.MachineBlockEntity; import com.enderio.machines.common.blocks.crafter.CrafterMenu; import com.enderio.machines.common.blocks.enderface.EnderfaceBlockEntity; +import com.enderio.machines.common.network.transceiver.AddRemoveGlobalChannelPacket; +import com.enderio.machines.common.network.transceiver.AddRemoveTransceiverChannelPacket; +import com.enderio.machines.common.network.transceiver.GlobalChannelsSyncPacket; import com.enderio.machines.common.souldata.EngineSoul; import com.enderio.machines.common.souldata.FarmSoul; import com.enderio.machines.common.souldata.SolarSoul; import com.enderio.machines.common.souldata.SpawnerSoul; +import com.enderio.machines.common.transceiver.Channel; +import com.enderio.machines.common.transceiver.ChannelSavedData; +import com.enderio.machines.common.transceiver.TransceiverBlockEntity; +import com.enderio.machines.common.transceiver.TransceiverRegistry; +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.neoforged.neoforge.network.handling.IPayloadContext; @@ -36,6 +48,15 @@ public void handleFarmingStationSoul(FarmStationSoulPacket packet, IPayloadConte public void handleSolarSoul(SolarSoulPacket packet, IPayloadContext context) { context.enqueueWork(() -> SolarSoul.SOLAR.map = packet.map()); } + + public void handleChannelsSync(GlobalChannelsSyncPacket packet, IPayloadContext context) { + context.enqueueWork(() -> { + if (Minecraft.getInstance().screen instanceof TransceiverScreen screen) { + screen.getMenu().setChannelList(packet.channels()); + } + }); + } + } public static class Server { @@ -116,5 +137,55 @@ public void handleTransferItems(TransferItemsPacket packet, IPayloadContext cont } }); } + + public void handleAddRemoveGlobalChannel(AddRemoveGlobalChannelPacket packet, IPayloadContext context) { + context.enqueueWork(() -> { + Level level = context.player().level(); + Channel channel = packet.channel(); + + if (packet.isAdd()) { + ChannelSavedData data = ChannelSavedData.get(level); + data.addChannel(channel); + } else { + ChannelSavedData data = ChannelSavedData.get(level); + data.removeChannel(channel); + + // Delete all of existing instances of this channel in loaded transceivers + TransceiverRegistry.INSTANCE.deleteChannel(channel); + } + }); + } + + public void handleAddRemoveTransceiverChannel(AddRemoveTransceiverChannelPacket packet, IPayloadContext context) { + context.enqueueWork(() -> { + Level level = context.player().level(); + + BlockPos pos = packet.pos(); + BlockEntity blockEntity = level.getBlockEntity(pos); + + if (blockEntity instanceof TransceiverBlockEntity transceiverBlockEntity) { + Channel channel = packet.channel(); + + if (packet.isAdd()) { + if (packet.isSend()) { + transceiverBlockEntity.addSendChannel(channel); + } + if (packet.isReceive()) { + transceiverBlockEntity.addReceiveChannel(channel); + } + } else { + if (packet.isSend()) { + transceiverBlockEntity.deleteSendChannel(channel); + } + if (packet.isReceive()) { + transceiverBlockEntity.deleteReceiveChannel(channel); + } + } + + transceiverBlockEntity.setChanged(); + level.sendBlockUpdated(pos, transceiverBlockEntity.getBlockState(), transceiverBlockEntity.getBlockState(), Block.UPDATE_ALL); + } + }); + } } } diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/network/transceiver/AddRemoveGlobalChannelPacket.java b/enderio-machines/src/main/java/com/enderio/machines/common/network/transceiver/AddRemoveGlobalChannelPacket.java new file mode 100644 index 0000000000..d2f183195b --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/network/transceiver/AddRemoveGlobalChannelPacket.java @@ -0,0 +1,27 @@ +package com.enderio.machines.common.network.transceiver; + +import com.enderio.base.api.EnderIO; +import com.enderio.machines.common.transceiver.Channel; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +public record AddRemoveGlobalChannelPacket(Channel channel, boolean isAdd) implements CustomPacketPayload { + + public static final Type TYPE = new Type<>(EnderIO.loc("add_remove_global_channel")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + Channel.STREAM_CODEC, + AddRemoveGlobalChannelPacket::channel, + ByteBufCodecs.BOOL, + AddRemoveGlobalChannelPacket::isAdd, + AddRemoveGlobalChannelPacket::new + ); + + + @Override + public Type type() { + return TYPE; + } +} diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/network/transceiver/AddRemoveTransceiverChannelPacket.java b/enderio-machines/src/main/java/com/enderio/machines/common/network/transceiver/AddRemoveTransceiverChannelPacket.java new file mode 100644 index 0000000000..d2617315f9 --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/network/transceiver/AddRemoveTransceiverChannelPacket.java @@ -0,0 +1,34 @@ +package com.enderio.machines.common.network.transceiver; + +import com.enderio.base.api.EnderIO; +import com.enderio.machines.common.transceiver.Channel; +import net.minecraft.core.BlockPos; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +public record AddRemoveTransceiverChannelPacket(BlockPos pos, Channel channel, boolean isAdd, boolean isSend, boolean isReceive) implements CustomPacketPayload { + + public static final Type TYPE = new Type<>(EnderIO.loc("add_remove_transceiver_channel")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + BlockPos.STREAM_CODEC, + AddRemoveTransceiverChannelPacket::pos, + Channel.STREAM_CODEC, + AddRemoveTransceiverChannelPacket::channel, + ByteBufCodecs.BOOL, + AddRemoveTransceiverChannelPacket::isAdd, + ByteBufCodecs.BOOL, + AddRemoveTransceiverChannelPacket::isSend, + ByteBufCodecs.BOOL, + AddRemoveTransceiverChannelPacket::isReceive, + AddRemoveTransceiverChannelPacket::new + ); + + + @Override + public Type type() { + return TYPE; + } +} diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/network/transceiver/GlobalChannelsSyncPacket.java b/enderio-machines/src/main/java/com/enderio/machines/common/network/transceiver/GlobalChannelsSyncPacket.java new file mode 100644 index 0000000000..2790b2a40a --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/network/transceiver/GlobalChannelsSyncPacket.java @@ -0,0 +1,24 @@ +package com.enderio.machines.common.network.transceiver; + +import com.enderio.base.api.EnderIO; +import com.enderio.machines.common.transceiver.ChannelList; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +public record GlobalChannelsSyncPacket(ChannelList channels) implements CustomPacketPayload { + + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(EnderIO.loc("channels_sync")); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ChannelList.STREAM_CODEC, + GlobalChannelsSyncPacket::channels, + GlobalChannelsSyncPacket::new + ); + + + @Override + public Type type() { + return TYPE; + } +} diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/Channel.java b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/Channel.java new file mode 100644 index 0000000000..e325958259 --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/Channel.java @@ -0,0 +1,53 @@ +package com.enderio.machines.common.transceiver; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.netty.buffer.ByteBuf; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; + +public record Channel(String name, String owner, ChannelType type, boolean isPrivate) { + + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("name").forGetter(Channel::name), + Codec.STRING.fieldOf("owner").forGetter(Channel::owner), + ChannelType.CODEC.fieldOf("type").forGetter(Channel::type), + Codec.BOOL.fieldOf("is_private").forGetter(Channel::isPrivate) + ).apply(instance, Channel::new)); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, + Channel::name, + ByteBufCodecs.STRING_UTF8, + Channel::owner, + ChannelType.STREAM_CODEC, + Channel::type, + ByteBufCodecs.BOOL, + Channel::isPrivate, + Channel::new + ); + + public boolean isPrivate() { + return isPrivate; + } + + @Override + public String toString() { + return "Channel[name=%s, owner=%s, type=%s, private=%s]".formatted(name, owner, type, isPrivate); + } + + public Tag save(HolderLookup.Provider lookupProvider) { + return CODEC.encodeStart(lookupProvider.createSerializationContext(NbtOps.INSTANCE), this).getOrThrow(); + } + + public static Channel parse(HolderLookup.Provider lookupProvider, Tag tag) { + return CODEC.parse(lookupProvider.createSerializationContext(NbtOps.INSTANCE), tag).getOrThrow(); + } + + public boolean canBeDisplayed(String playerName) { + return !isPrivate || owner.equals(playerName); + } +} diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelList.java b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelList.java new file mode 100644 index 0000000000..5677412718 --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelList.java @@ -0,0 +1,100 @@ +package com.enderio.machines.common.transceiver; + +import com.mojang.serialization.Codec; +import io.netty.buffer.ByteBuf; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.network.codec.StreamCodec; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class ChannelList extends EnumMap> { + + public static final Codec CODEC = Codec.unboundedMap( + ChannelType.CODEC, + Channel.CODEC.listOf() + ) + .xmap( + map -> { + ChannelList cl = new ChannelList(); + map.forEach((type, list) -> cl.get(type).addAll(list)); + return cl; + }, + cl -> { + Map> serializedMap = cl.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> new ArrayList<>(e.getValue()) + )); + return serializedMap; + } + ); + + + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + (buf, channels) -> { + buf.writeInt(channels.size()); + channels.forEach((type, list) -> { + buf.writeInt(type.id); + buf.writeInt(list.size()); + list.forEach(c -> Channel.STREAM_CODEC.encode(buf, c)); + }); + }, + buf -> { + ChannelList channels = new ChannelList(); + int size = buf.readInt(); + for (int i = 0; i < size; i++) { + ChannelType type = ChannelType.BY_ID.apply(buf.readInt()); + int listSize = buf.readInt(); + LinkedHashSet list = new LinkedHashSet<>(); + for (int j = 0; j < listSize; j++) { + list.add(Channel.STREAM_CODEC.decode(buf)); + } + channels.put(type, list); + } + return channels; + } + ); + + + public ChannelList() { + super(ChannelType.class); + for (ChannelType type : ChannelType.values()) { + put(type, new LinkedHashSet<>()); + } + } + + public Tag save(HolderLookup.Provider lookupProvider) { + return CODEC.encodeStart(lookupProvider.createSerializationContext(NbtOps.INSTANCE), this).getOrThrow(); + } + + public static ChannelList parse(HolderLookup.Provider lookupProvider, Tag tag) { + return CODEC.parse(lookupProvider.createSerializationContext(NbtOps.INSTANCE), tag).getOrThrow(); + } + + public boolean addChannel(Channel channel) { + ChannelType type = channel.type(); + return this.get(type).add(channel); + } + + public boolean removeChannel(Channel channel) { + ChannelType type = channel.type(); + return this.get(type).remove(channel); + } + + public boolean containsChannel(Channel channel) { + ChannelType type = channel.type(); + return this.get(type).contains(channel); + } + + public Set getChannels(ChannelType type) { + return this.get(type); + } +} diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelListWidget.java b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelListWidget.java new file mode 100644 index 0000000000..607954ae18 --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelListWidget.java @@ -0,0 +1,126 @@ +package com.enderio.machines.common.transceiver; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractSelectionList; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashSet; +import java.util.Set; + + +public class ChannelListWidget extends AbstractSelectionList { + + private Set channels; + + public ChannelListWidget(Minecraft minecraft, int width, int height, int y, int itemHeight) { + super(minecraft, width, height, y, itemHeight); + } + + public void setChannels(Set channels) { + this.clearEntries(); + this.channels = new LinkedHashSet<>(channels); + + for (Channel channel : channels) { + String playerName = Minecraft.getInstance().player.getName().getString(); + if (channel.canBeDisplayed(playerName)) { + this.addEntry(new Entry(channel)); + } + } + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + } + + @Override + protected int getScrollbarPosition() { + return this.getX() + this.getWidth() - 6; + } + + @Override + public int getRowWidth() { + return this.getWidth(); + } + + @Override + public int addEntry(Entry entry) { + return super.addEntry(entry); + } + + @Override + public boolean removeEntry(Entry entry) { + return super.removeEntry(entry); + } + + public void removeChannelEntry(Entry entry, Channel channel) { + channels.remove(channel); + super.removeEntry(entry); + } + + public void addChannel(Channel channel) { + if (!channels.contains(channel)) { + Entry entry = new Entry(channel); + channels.add(channel); + super.addEntry(entry); + } + } + + @Override + protected void renderListBackground(GuiGraphics guiGraphics) { + int x = this.getX(); + int y = this.getY(); + int width = this.getWidth(); + int height = this.getHeight(); + + guiGraphics.fill(x, y, x + width, y + height, 0xFF1A1A1A); + + guiGraphics.fill(x, y, x + width, y + 1, 0xFF404040); // Top + guiGraphics.fill(x, y + height - 1, x + width, y + height, 0xFF404040); // Bottom + guiGraphics.fill(x, y, x + 1, y + height, 0xFF404040); // Left + guiGraphics.fill(x + width - 1, y, x + width, y + height, 0xFF404040); // Right + } + + public class Entry extends AbstractSelectionList.Entry { + private final Channel channel; + + public Entry(Channel channel) { + this.channel = channel; + } + + @Override + public void render(GuiGraphics guiGraphics, int index, int y, int x, int entryWidth, int entryHeight, + int mouseX, int mouseY, boolean hovered, float partialTick) { + + int bgColor; + if (hovered) { + bgColor = 0xFF666666; + } else { + bgColor = 0xFF202020; + } + + guiGraphics.fill(x, y, x + entryWidth - 4, y + entryHeight, bgColor); + + int textColor = 0xFFE0E0E0; + + Font font = Minecraft.getInstance().font; + guiGraphics.drawString(font, channel.name(), x, y + (entryHeight - font.lineHeight)/2, textColor); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (button == 0) { + ChannelListWidget.this.setSelected(this); + return true; + } + return false; + } + + @Nullable + public Channel getChannel() { + return channel; + } + } +} diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelSavedData.java b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelSavedData.java new file mode 100644 index 0000000000..f9edf5385c --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelSavedData.java @@ -0,0 +1,80 @@ +package com.enderio.machines.common.transceiver; + +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.saveddata.SavedData; + +public class ChannelSavedData extends SavedData { + + public static final String DATA_NAME = "channel_registry"; + public static final String NBT_KEY = "channels"; + + private final ChannelList channelList = new ChannelList(); + + private ChannelSavedData() { + + } + + private ChannelSavedData(CompoundTag tag, HolderLookup.Provider lookupProvider) { + this.loadNBT(lookupProvider, tag); + } + + public static ChannelSavedData get(Level level) { + MinecraftServer server = level.getServer(); + if (server == null) { + return new ChannelSavedData(); + } + + // The data is the same across all dimensions. + // To persist a SavedData across levels, a SD should be attached to the Overworld + ServerLevel overworld = server.overworld(); + return overworld.getDataStorage().computeIfAbsent( + new Factory<>(ChannelSavedData::new, ChannelSavedData::new), + DATA_NAME + ); + } + + + public void addChannel(Channel channel) { + channelList.get(channel.type()).add(channel); + setDirty(); + } + + public void removeChannel(Channel channel) { + channelList.get(channel.type()).remove(channel); + setDirty(); + } + + @Override + public CompoundTag save(CompoundTag tag, HolderLookup.Provider lookupProvider) { + Tag encoded = ChannelList.CODEC.encodeStart(lookupProvider.createSerializationContext(NbtOps.INSTANCE), channelList) + .getOrThrow(); + + tag.put(NBT_KEY, encoded); + return tag; + } + + @Override + public boolean isDirty() { + return true; + } + + public void loadNBT(HolderLookup.Provider lookupProvider, CompoundTag tag) { + this.channelList.clear(); + ChannelList parsed = ChannelList.CODEC.parse(lookupProvider.createSerializationContext(NbtOps.INSTANCE), tag.get(NBT_KEY)) + .getOrThrow(); + + this.channelList.putAll(parsed); + } + + public ChannelList getChannelList() { + return channelList; + } +} + + diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelType.java b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelType.java new file mode 100644 index 0000000000..0992da0edd --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/ChannelType.java @@ -0,0 +1,43 @@ +package com.enderio.machines.common.transceiver; + +import com.enderio.base.api.EnderIO; +import com.mojang.serialization.Codec; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ByIdMap; +import net.minecraft.util.StringRepresentable; + +import java.util.function.IntFunction; + +public enum ChannelType implements StringRepresentable { + ITEM(0, "item", "Item", EnderIO.loc("icon/conduit_icon/item")), + FLUID(1, "fluid", "Fluid", EnderIO.loc("icon/conduit_icon/fluid")), + ENERGY(2, "energy", "Energy", EnderIO.loc("icon/conduit_icon/energy")); + + public final int id; + public final String name; + public final String tooltip; + public final ResourceLocation icon; + + ChannelType(int id, String name, String tooltip, ResourceLocation icon) { + this.id = id; + this.name = name; + this.tooltip = tooltip; + this.icon = icon; + } + + public static final Codec CODEC = StringRepresentable.fromEnum(ChannelType::values); + + public static final IntFunction BY_ID = ByIdMap.continuous(key -> key.id, values(), + ByIdMap.OutOfBoundsStrategy.ZERO); + + public static final StreamCodec STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, v -> v.id); + + @Override + public String getSerializedName() { + return name; + } +} + diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/TransceiverBlockEntity.java b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/TransceiverBlockEntity.java new file mode 100644 index 0000000000..f61eaa926c --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/TransceiverBlockEntity.java @@ -0,0 +1,208 @@ +package com.enderio.machines.common.transceiver; + +import com.enderio.base.api.capacitor.CapacitorModifier; +import com.enderio.base.api.capacitor.QuadraticScalable; +import com.enderio.base.api.io.energy.EnergyIOMode; +import com.enderio.machines.common.blocks.base.blockentity.PoweredMachineBlockEntity; +import com.enderio.machines.common.blocks.base.blockentity.flags.CapacitorSupport; +import com.enderio.machines.common.blocks.base.inventory.MachineInventoryLayout; +import com.enderio.machines.common.blocks.base.inventory.MultiSlotAccess; +import com.enderio.machines.common.config.MachinesConfig; +import com.enderio.machines.common.init.MachineBlockEntities; +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +public class TransceiverBlockEntity extends PoweredMachineBlockEntity { + + public static final MultiSlotAccess INPUTS = new MultiSlotAccess(); + public static final MultiSlotAccess OUTPUTS = new MultiSlotAccess(); + + public static final QuadraticScalable CAPACITY = new QuadraticScalable(CapacitorModifier.ENERGY_CAPACITY, + MachinesConfig.COMMON.ENERGY.TRANSCEIVER_CAPACITY); + public static final QuadraticScalable USAGE = new QuadraticScalable(CapacitorModifier.ENERGY_USE, + MachinesConfig.COMMON.ENERGY.TRANSCEIVER_USAGE); + + public ChannelList sendChannels = new ChannelList(); + public ChannelList receiveChannels = new ChannelList(); + + private static final String SEND_CHANNELS_TAG = "send_channels"; + private static final String RECEIVE_CHANNELS_TAG = "receive_channels"; + + private boolean isRegistered = false; + + //todo fluid + + + public TransceiverBlockEntity(BlockPos worldPosition, BlockState blockState) { + super(MachineBlockEntities.TRANSCEIVER.get(), worldPosition, blockState, true, CapacitorSupport.REQUIRED, EnergyIOMode.Input, CAPACITY, USAGE); + } + + @Override + public MachineInventoryLayout createInventoryLayout() { + return MachineInventoryLayout.builder() + .inputSlot(8) + .slotAccess(INPUTS) + .outputSlot(8) + .slotAccess(OUTPUTS) + .capacitor() + .build(); + } + @Override + public boolean isActive() { + return false; + } + + @Nullable + @Override + public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player pPlayer) { + return new TransceiverMenu(containerId, playerInventory, this); + } + + @Override + public void serverTick() { + if (canAct()) { + for (Channel channel : sendChannels.getChannels(ChannelType.ENERGY)) { + TransceiverRegistry.INSTANCE.sendPower(this, 100, channel); + } + + for (Channel channel : sendChannels.getChannels(ChannelType.ITEM)) { + TransceiverRegistry.INSTANCE.sendItems(this, channel); + } + } + } + + public ChannelList getSendChannels() { + return sendChannels; + } + + public ChannelList getReceiveChannels() { + return receiveChannels; + } + + public void addSendChannel(Channel channel) { + sendChannels.addChannel(channel); + TransceiverRegistry.INSTANCE.addSubscription(channel, this); + setChanged(); + } + + public void deleteSendChannel(Channel channel) { + sendChannels.removeChannel(channel); + TransceiverRegistry.INSTANCE.deleteSubscription(channel, this); + setChanged(); + } + + public void addReceiveChannel(Channel channel) { + receiveChannels.addChannel(channel); + TransceiverRegistry.INSTANCE.addSubscription(channel, this); + setChanged(); + } + + public void deleteReceiveChannel(Channel channel) { + receiveChannels.removeChannel(channel); + TransceiverRegistry.INSTANCE.deleteSubscription(channel, this); + setChanged(); + } + + + @Override + public void setRemoved() { + if (isRegistered) { + TransceiverRegistry.INSTANCE.unregister(this); + } + + super.setRemoved(); + } + + + @Override + public void onChunkUnloaded() { + if (isRegistered) { + TransceiverRegistry.INSTANCE.unregister(this); + } + + super.onChunkUnloaded(); + } + + + // region Serialization + + + @Override + public void saveAdditional(CompoundTag pTag, HolderLookup.Provider lookupProvider) { + super.saveAdditional(pTag, lookupProvider); + + pTag.put(SEND_CHANNELS_TAG, this.sendChannels.save(lookupProvider)); + pTag.put(RECEIVE_CHANNELS_TAG, this.receiveChannels.save(lookupProvider)); + } + + @Override + public void loadAdditional(CompoundTag tag, HolderLookup.Provider lookupProvider) { + super.loadAdditional(tag, lookupProvider); + + if (tag.contains(SEND_CHANNELS_TAG, Tag.TAG_COMPOUND)) { + Tag sendChannelsTag = tag.get(SEND_CHANNELS_TAG); + if (sendChannelsTag != null) { + this.sendChannels = ChannelList.parse(lookupProvider, sendChannelsTag); + } + } + + if (tag.contains(RECEIVE_CHANNELS_TAG, Tag.TAG_COMPOUND)) { + Tag receiveChannelsTag = tag.get(RECEIVE_CHANNELS_TAG); + if (receiveChannelsTag != null) { + this.receiveChannels = ChannelList.parse(lookupProvider, receiveChannelsTag); + } + } + + if (!isRegistered) { + TransceiverRegistry.INSTANCE.register(this); + isRegistered = true; + } + } + + @Override + public ClientboundBlockEntityDataPacket getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + @Override + public CompoundTag getUpdateTag(HolderLookup.Provider lookupProvider) { + CompoundTag tag = super.getUpdateTag(lookupProvider); + tag.put(SEND_CHANNELS_TAG, sendChannels.save(lookupProvider)); + tag.put(RECEIVE_CHANNELS_TAG, receiveChannels.save(lookupProvider)); + + return tag; + } + + @Override + public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt, HolderLookup.Provider lookupProvider) { + super.onDataPacket(net, pkt, lookupProvider); + + CompoundTag tag = pkt.getTag(); + if (tag != null) { + if (tag.contains(SEND_CHANNELS_TAG, Tag.TAG_COMPOUND)) { + Tag sendChannelsSubTag = tag.get(SEND_CHANNELS_TAG); + if (sendChannelsSubTag != null) { + this.sendChannels = ChannelList.parse(lookupProvider, sendChannelsSubTag); + } + } + + if (tag.contains(RECEIVE_CHANNELS_TAG, Tag.TAG_COMPOUND)) { + Tag receiveChannelsSubTag = tag.get(RECEIVE_CHANNELS_TAG); + if (receiveChannelsSubTag != null) { + this.receiveChannels = ChannelList.parse(lookupProvider, receiveChannelsSubTag); + } + } + } + } +} + + diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/TransceiverMenu.java b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/TransceiverMenu.java new file mode 100644 index 0000000000..37341a8766 --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/TransceiverMenu.java @@ -0,0 +1,110 @@ +package com.enderio.machines.common.transceiver; + +import com.enderio.core.common.network.menu.BoolSyncSlot; +import com.enderio.machines.common.blocks.base.menu.MachineSlot; +import com.enderio.machines.common.blocks.base.menu.PoweredMachineMenu; +import com.enderio.machines.common.init.MachineBlockEntities; +import com.enderio.machines.common.init.MachineMenus; +import com.enderio.machines.common.network.transceiver.GlobalChannelsSyncPacket; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Inventory; +import net.neoforged.neoforge.network.PacketDistributor; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.function.Supplier; + +public class TransceiverMenu extends PoweredMachineMenu { + private TransceiverBlockEntity blockEntity; + private ChannelList channelList = new ChannelList(); + private boolean isPrivate = false; + private ChannelType selectedType; + + private BoolSyncSlot syncSlotNeedChannelSync; + + public TransceiverMenu(int pContainerId, Inventory inventory, TransceiverBlockEntity blockEntity) { + super(MachineMenus.TRANSCEIVER.get(), pContainerId, inventory, blockEntity); + this.blockEntity = blockEntity; + + addSlots(); + } + + public TransceiverMenu(int containerId, Inventory playerInventory, RegistryFriendlyByteBuf buf) { + super(MachineMenus.TRANSCEIVER.get(), containerId, playerInventory, buf, MachineBlockEntities.TRANSCEIVER.get()); + addSlots(); + } + + private void addSlots() { + addCapacitorSlot(8, 75); + + addInputSlots(); + addOutputSlots(); + + addPlayerInventorySlots(47, 86); + + this.syncSlotNeedChannelSync = addUpdatableSyncSlot(BoolSyncSlot.standalone()); + syncSlotNeedChannelSync.set(true); + } + + private void addInputSlots() { + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 4; x++) { + int index = x + y * 4; + addSlot(new MachineSlot(getMachineInventory(), TransceiverBlockEntity.INPUTS.get(index), 54 + x * 18, 30 + y * 18)); + } + } + } + + private void addOutputSlots() { + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 4; x++) { + int index = x + y * 4; + addSlot(new MachineSlot(getMachineInventory(), TransceiverBlockEntity.OUTPUTS.get(index), 131 + x * 18, 30 + y * 18)); + } + } + } + + public ChannelList getChannelList() { + return channelList; + } + + public void setChannelList(ChannelList channelList) { + this.channelList = channelList; + } + + @Override + public void broadcastChanges() { + super.broadcastChanges(); + + if (syncSlotNeedChannelSync.get()) { + ChannelList serverChannels = ChannelSavedData.get(Objects.requireNonNull(blockEntity.getLevel())).getChannelList(); + PacketDistributor.sendToPlayer((ServerPlayer) getPlayerInventory().player, new GlobalChannelsSyncPacket(serverChannels)); + + setSyncSlotNeedChannelSync(false); + } + } + + public Supplier isPrivate() { + return () -> isPrivate; + } + + public void setPrivate(boolean isPrivate) { + this.isPrivate = isPrivate; + } + + @Nullable + public ChannelType getSelectedType() { + return selectedType; + } + + public void setSelectedType(ChannelType selectedType) { + this.selectedType = selectedType; + } + + public void setSyncSlotNeedChannelSync(boolean value) { + syncSlotNeedChannelSync.set(value); + updateSlot(syncSlotNeedChannelSync); + } + +} diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/TransceiverRegistry.java b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/TransceiverRegistry.java new file mode 100644 index 0000000000..b1b8bb3e0e --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/TransceiverRegistry.java @@ -0,0 +1,173 @@ +package com.enderio.machines.common.transceiver; + +import com.enderio.base.api.collections.RoundRobinList; +import com.enderio.machines.common.blocks.base.inventory.MachineInventory; +import com.enderio.machines.common.blocks.base.inventory.SingleSlotAccess; +import net.minecraft.world.item.ItemStack; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public enum TransceiverRegistry { + INSTANCE; + + private final Map> energyChannels = new ConcurrentHashMap<>(); + private final Map> fluidChannels = new ConcurrentHashMap<>(); + private final Map> itemChannels = new ConcurrentHashMap<>(); + + private final Set activeTransceivers = ConcurrentHashMap.newKeySet(); + + public void register(TransceiverBlockEntity transceiver) { + if (activeTransceivers.add(transceiver)) { + registerForChannels(transceiver.getSendChannels().get(ChannelType.ENERGY), energyChannels, transceiver); + registerForChannels(transceiver.getReceiveChannels().get(ChannelType.ENERGY), energyChannels, transceiver); + + registerForChannels(transceiver.getSendChannels().get(ChannelType.FLUID), fluidChannels, transceiver); + registerForChannels(transceiver.getReceiveChannels().get(ChannelType.FLUID), fluidChannels, transceiver); + + registerForChannels(transceiver.getSendChannels().get(ChannelType.ITEM), itemChannels, transceiver); + registerForChannels(transceiver.getReceiveChannels().get(ChannelType.ITEM), itemChannels, transceiver); + } + } + + public void unregister(TransceiverBlockEntity transceiver) { + if (activeTransceivers.remove(transceiver)) { + removeFromAllChannels(energyChannels, transceiver); + removeFromAllChannels(fluidChannels, transceiver); + removeFromAllChannels(itemChannels, transceiver); + } + } + + private void registerForChannels(Set channels, Map> channelMap, TransceiverBlockEntity transceiver) { + for (Channel channel : channels) { + channelMap.computeIfAbsent(channel, k -> new RoundRobinList<>()).add(transceiver); + } + } + + private void removeFromAllChannels(Map> channelMap, TransceiverBlockEntity transceiver) { + channelMap.entrySet().removeIf(entry -> { + entry.getValue().remove(transceiver); + return entry.getValue().isEmpty(); + }); + } + + public void sendPower(TransceiverBlockEntity sender, int amount, Channel channel) { + if (!sender.canAct()) return; + + RoundRobinList receivers = energyChannels.get(channel); + if (receivers == null) return; + + for (TransceiverBlockEntity receiver : receivers.iterate()) { + if (isValidReceiver(receiver, sender, channel, ChannelType.ENERGY)) { + int received = receiver.getEnergyStorage().receiveEnergy(amount, true); + if (received > 0) { + receiver.getEnergyStorage().receiveEnergy(amount, false); + sender.getEnergyStorage().consumeEnergy(amount); + return; + } + } + } + } + + + public void sendItems(TransceiverBlockEntity sender, Channel channel) { + RoundRobinList receivers = itemChannels.get(channel); + MachineInventory senderInventory = sender.getInventory(); + + for (TransceiverBlockEntity receiver : receivers.iterate()) { + if (isValidReceiver(receiver, sender, channel, ChannelType.ITEM)) { + MachineInventory receiverInventory = receiver.getInventory(); + transferItems(senderInventory, receiverInventory); + + return; + } + } + + } + + private void transferItems(MachineInventory senderInventory, MachineInventory receiverInventory) { + for (SingleSlotAccess inputSlot : TransceiverBlockEntity.INPUTS.getAccesses()) { + ItemStack stackInSenderSlot = inputSlot.getItemStack(senderInventory); + + if (!stackInSenderSlot.isEmpty()) { + ItemStack stackToTransfer = stackInSenderSlot.copy(); + int transferAmount = Math.min(stackToTransfer.getCount(), 64); + stackToTransfer.setCount(transferAmount); + + int insertedAmount = 0; + ItemStack remainingStack = stackToTransfer.copy(); + + for (SingleSlotAccess outputSlot : TransceiverBlockEntity.OUTPUTS.getAccesses()) { + if (remainingStack.isEmpty()) break; + ItemStack simulatedRemaining = outputSlot.insertItem(receiverInventory, remainingStack, true); + + int currentInserted = remainingStack.getCount() - simulatedRemaining.getCount(); + insertedAmount += currentInserted; + remainingStack = simulatedRemaining; + + if (insertedAmount > 0) { + ItemStack extractedStack = stackInSenderSlot.copy(); + extractedStack.setCount(stackInSenderSlot.getCount() - currentInserted); + inputSlot.setStackInSlot(senderInventory, extractedStack); + + + ItemStack actualInsertStack = stackToTransfer.copyWithCount(currentInserted); + outputSlot.insertItem(receiverInventory, actualInsertStack, false); + + return; + } + } + } + } + } + + private boolean isValidReceiver(TransceiverBlockEntity receiver, TransceiverBlockEntity sender, Channel channel, ChannelType type) { + return receiver != sender + && !receiver.isRemoved() + && receiver.getReceiveChannels().get(type).contains(channel); + } + + public void addSubscription(Channel channel, TransceiverBlockEntity transceiver) { + ChannelType type = channel.type(); + var channelMap = getChannelMapForType(type); + + channelMap.computeIfAbsent(channel, k -> new RoundRobinList<>()).add(transceiver); + } + + public void deleteSubscription(Channel channel, TransceiverBlockEntity transceiver) { + ChannelType type = channel.type(); + var channelMap = getChannelMapForType(type); + + RoundRobinList list = channelMap.get(channel); + if (list != null) { + list.remove(transceiver); + + if (list.isEmpty()) { + channelMap.remove(channel); + } + } + } + + + public void deleteChannel(Channel channel) { + ChannelType type = channel.type(); + var channelMap = getChannelMapForType(type); + + for (TransceiverBlockEntity transceiver : channelMap.get(channel).iterate()) { + transceiver.deleteSendChannel(channel); + transceiver.deleteReceiveChannel(channel); + } + + channelMap.remove(channel); + } + + + private Map> getChannelMapForType(ChannelType type) { + return switch (type) { + case ITEM -> itemChannels; + case FLUID -> fluidChannels; + case ENERGY -> energyChannels; + }; + } +} diff --git a/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/package-info.java b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/package-info.java new file mode 100644 index 0000000000..0eab9897eb --- /dev/null +++ b/enderio-machines/src/main/java/com/enderio/machines/common/transceiver/package-info.java @@ -0,0 +1,4 @@ +@javax.annotation.ParametersAreNonnullByDefault +@net.minecraft.MethodsReturnNonnullByDefault + +package com.enderio.machines.common.transceiver; diff --git a/enderio-machines/src/main/resources/assets/enderio/models/block/transceiver.json b/enderio-machines/src/main/resources/assets/enderio/models/block/transceiver.json new file mode 100644 index 0000000000..78cb8e9073 --- /dev/null +++ b/enderio-machines/src/main/resources/assets/enderio/models/block/transceiver.json @@ -0,0 +1,3 @@ +{ + "parent": "minecraft:block/cube" +}