From 4dc3629f5dcd8894047ca78cafbad12edd227fe4 Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:14:38 +0200 Subject: [PATCH] Packing Up, Part II - Toolboxes keep nbt on pickup - Toolbox radial menu supports mouse wheel - Added Dispose-all buttons - Synced slots detach when moving stacks back into the toolbox - Fixed missing data on client on login --- src/generated/resources/.cache/cache | 26 ++-- .../resources/assets/create/lang/en_us.json | 2 + .../assets/create/lang/unfinished/de_de.json | 4 +- .../assets/create/lang/unfinished/es_es.json | 4 +- .../assets/create/lang/unfinished/fr_fr.json | 4 +- .../assets/create/lang/unfinished/it_it.json | 4 +- .../assets/create/lang/unfinished/ja_jp.json | 4 +- .../assets/create/lang/unfinished/ko_kr.json | 4 +- .../assets/create/lang/unfinished/nl_nl.json | 4 +- .../assets/create/lang/unfinished/pl_pl.json | 4 +- .../assets/create/lang/unfinished/pt_br.json | 4 +- .../assets/create/lang/unfinished/ru_ru.json | 4 +- .../assets/create/lang/unfinished/zh_cn.json | 4 +- .../assets/create/lang/unfinished/zh_tw.json | 4 +- .../toolbox/RadialToolboxMenu.java | 130 ++++++++++++++---- .../curiosities/toolbox/ToolboxBlock.java | 34 ++++- .../curiosities/toolbox/ToolboxContainer.java | 6 +- .../toolbox/ToolboxDisposeAllPacket.java | 81 +++++++++++ .../toolbox/ToolboxEquipPacket.java | 28 ++-- .../curiosities/toolbox/ToolboxHandler.java | 82 ++++++++++- .../toolbox/ToolboxHandlerClient.java | 15 +- .../curiosities/toolbox/ToolboxInventory.java | 68 +++++++-- .../curiosities/toolbox/ToolboxScreen.java | 13 +- .../toolbox/ToolboxTileEntity.java | 113 ++++++++++++--- .../simibubi/create/events/CommonEvents.java | 13 +- .../foundation/config/CCuriosities.java | 3 + .../create/foundation/gui/AllIcons.java | 1 + .../foundation/networking/AllPackets.java | 2 + .../assets/create/lang/default/messages.json | 2 + .../assets/create/textures/gui/icons.png | Bin 4737 -> 4865 bytes 30 files changed, 554 insertions(+), 113 deletions(-) create mode 100644 src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxDisposeAllPacket.java diff --git a/src/generated/resources/.cache/cache b/src/generated/resources/.cache/cache index 9c30843cb..68fbd2312 100644 --- a/src/generated/resources/.cache/cache +++ b/src/generated/resources/.cache/cache @@ -427,19 +427,19 @@ a3a11524cd3515fc01d905767b4b7ea782adaf03 assets/create/blockstates/yellow_seat.j 7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json 337227971382d97fbaa69170e0b6bcc621bdc494 assets/create/lang/en_ud.json -307e88153d5288781e906e566f73ccd59fbcd945 assets/create/lang/en_us.json -5268c9117961dabecdae27b3190d02a8c0ea1077 assets/create/lang/unfinished/de_de.json -2e2002aefd045f492b7232884bd63c7a68d41480 assets/create/lang/unfinished/es_es.json -78836cc1527a95e7e40bc7f50faa95f8cdf5cd9d assets/create/lang/unfinished/fr_fr.json -b83a58ff24f2eca59c628c9a49e286d2feeece35 assets/create/lang/unfinished/it_it.json -5e3e878fe3fd58ba7911877609bf4f0be5a7c71d assets/create/lang/unfinished/ja_jp.json -ff6560eef8744197ab55334392df0a91898d80b2 assets/create/lang/unfinished/ko_kr.json -d0637cc0fa640b328dcdbaf74ae6967a7e15b980 assets/create/lang/unfinished/nl_nl.json -a23c910164b9d295f7c1f5a5e4d9a08094f1f00c assets/create/lang/unfinished/pl_pl.json -bb56417e9607b45350f329f865d2caa7038ebcec assets/create/lang/unfinished/pt_br.json -b643778faf6706b85ad567066de7176272df73d9 assets/create/lang/unfinished/ru_ru.json -45f4a765689a28da8fa267c2845fcf59135c6034 assets/create/lang/unfinished/zh_cn.json -3ac73d1173b450a4fa4eb5622b903cf4b52fbd0b assets/create/lang/unfinished/zh_tw.json +4da832d10fb5bacf10c564bc4f828cdc6592d56c assets/create/lang/en_us.json +31064dd15c813975de6dbe771a94b2fff87d7c84 assets/create/lang/unfinished/de_de.json +785119499208f5a35c4f77a1d189ac78394a133a assets/create/lang/unfinished/es_es.json +7fb3d9f560b08e7d7e4b0e51f7f789ac2eeb3af8 assets/create/lang/unfinished/fr_fr.json +e418df14fbcd15e4b87024dbe4bda97ef1544f47 assets/create/lang/unfinished/it_it.json +95d0baec2d79b914b3aecbfdc6dde970722c552c assets/create/lang/unfinished/ja_jp.json +4fa09f736a742db2d843b0880edc5d607f28d80e assets/create/lang/unfinished/ko_kr.json +757ac709807bdb1322da17f27e0853de552638d1 assets/create/lang/unfinished/nl_nl.json +9b5bc245e4795d86eb27fa35670cd4f261c07576 assets/create/lang/unfinished/pl_pl.json +aaa1e873003b446858fc8bae839db4d0b293472e assets/create/lang/unfinished/pt_br.json +9745c2d6a2e8fc50648399785eb10cc8b125bd1d assets/create/lang/unfinished/ru_ru.json +b04454180b5b4af323009e33c3ba5b163b4e63c1 assets/create/lang/unfinished/zh_cn.json +ce4759ba4aa4ec9e0c24242796ac04b43f4b9a04 assets/create/lang/unfinished/zh_tw.json 487a511a01b2a4531fb672f917922312db78f958 assets/create/models/block/acacia_window.json b48060cba1a382f373a05bf0039054053eccf076 assets/create/models/block/acacia_window_pane_noside.json 3066db1bf03cffa1a9c7fbacf47ae586632f4eb3 assets/create/models/block/acacia_window_pane_noside_alt.json diff --git a/src/generated/resources/assets/create/lang/en_us.json b/src/generated/resources/assets/create/lang/en_us.json index 7b6b6dca1..627f017ce 100644 --- a/src/generated/resources/assets/create/lang/en_us.json +++ b/src/generated/resources/assets/create/lang/en_us.json @@ -795,6 +795,8 @@ "create.toolbox.unequip": "Unequip: %1$s", "create.toolbox.outOfRange": "Toolbox of held item not in Range", "create.toolbox.detach": "Stop tracking and keep item", + "create.toolbox.depositAll": "Return items to nearby Toolboxes", + "create.toolbox.depositBox": "Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "Mirror", "create.gui.symmetryWand.orientation": "Orientation", diff --git a/src/generated/resources/assets/create/lang/unfinished/de_de.json b/src/generated/resources/assets/create/lang/unfinished/de_de.json index 5f096580d..64ff7100e 100644 --- a/src/generated/resources/assets/create/lang/unfinished/de_de.json +++ b/src/generated/resources/assets/create/lang/unfinished/de_de.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 1138", + "_": "Missing Localizations: 1140", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "Spiegeln", "create.gui.symmetryWand.orientation": "Orientierung", diff --git a/src/generated/resources/assets/create/lang/unfinished/es_es.json b/src/generated/resources/assets/create/lang/unfinished/es_es.json index 44d35f9f9..827e5c1ba 100644 --- a/src/generated/resources/assets/create/lang/unfinished/es_es.json +++ b/src/generated/resources/assets/create/lang/unfinished/es_es.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 4", + "_": "Missing Localizations: 6", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "Espejado", "create.gui.symmetryWand.orientation": "Orientación", diff --git a/src/generated/resources/assets/create/lang/unfinished/fr_fr.json b/src/generated/resources/assets/create/lang/unfinished/fr_fr.json index 78e9c4a2a..2b6bcbc93 100644 --- a/src/generated/resources/assets/create/lang/unfinished/fr_fr.json +++ b/src/generated/resources/assets/create/lang/unfinished/fr_fr.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 1390", + "_": "Missing Localizations: 1392", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "Mirroir", "create.gui.symmetryWand.orientation": "Orientation", diff --git a/src/generated/resources/assets/create/lang/unfinished/it_it.json b/src/generated/resources/assets/create/lang/unfinished/it_it.json index e8def88f1..dbd9a681f 100644 --- a/src/generated/resources/assets/create/lang/unfinished/it_it.json +++ b/src/generated/resources/assets/create/lang/unfinished/it_it.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 918", + "_": "Missing Localizations: 920", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "Specchio", "create.gui.symmetryWand.orientation": "Orientamento", diff --git a/src/generated/resources/assets/create/lang/unfinished/ja_jp.json b/src/generated/resources/assets/create/lang/unfinished/ja_jp.json index 5c6496fab..2ae28fc30 100644 --- a/src/generated/resources/assets/create/lang/unfinished/ja_jp.json +++ b/src/generated/resources/assets/create/lang/unfinished/ja_jp.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 13", + "_": "Missing Localizations: 15", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "ミラーの種類", "create.gui.symmetryWand.orientation": "方向", diff --git a/src/generated/resources/assets/create/lang/unfinished/ko_kr.json b/src/generated/resources/assets/create/lang/unfinished/ko_kr.json index 987018999..363d949ec 100644 --- a/src/generated/resources/assets/create/lang/unfinished/ko_kr.json +++ b/src/generated/resources/assets/create/lang/unfinished/ko_kr.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 28", + "_": "Missing Localizations: 30", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "거울의 형태", "create.gui.symmetryWand.orientation": "거울의 방향", diff --git a/src/generated/resources/assets/create/lang/unfinished/nl_nl.json b/src/generated/resources/assets/create/lang/unfinished/nl_nl.json index 39010143c..fd3a9ee6f 100644 --- a/src/generated/resources/assets/create/lang/unfinished/nl_nl.json +++ b/src/generated/resources/assets/create/lang/unfinished/nl_nl.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 1770", + "_": "Missing Localizations: 1772", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "Spiegelen", "create.gui.symmetryWand.orientation": "Orientatie", diff --git a/src/generated/resources/assets/create/lang/unfinished/pl_pl.json b/src/generated/resources/assets/create/lang/unfinished/pl_pl.json index 45022fd4f..06ba15521 100644 --- a/src/generated/resources/assets/create/lang/unfinished/pl_pl.json +++ b/src/generated/resources/assets/create/lang/unfinished/pl_pl.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 260", + "_": "Missing Localizations: 262", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "Odbicie lustrzane", "create.gui.symmetryWand.orientation": "Orientacja", diff --git a/src/generated/resources/assets/create/lang/unfinished/pt_br.json b/src/generated/resources/assets/create/lang/unfinished/pt_br.json index 8538ed5fa..09cbfe400 100644 --- a/src/generated/resources/assets/create/lang/unfinished/pt_br.json +++ b/src/generated/resources/assets/create/lang/unfinished/pt_br.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 1811", + "_": "Missing Localizations: 1813", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "Espelhar", "create.gui.symmetryWand.orientation": "Orientação", diff --git a/src/generated/resources/assets/create/lang/unfinished/ru_ru.json b/src/generated/resources/assets/create/lang/unfinished/ru_ru.json index 8374dffca..ca208f43b 100644 --- a/src/generated/resources/assets/create/lang/unfinished/ru_ru.json +++ b/src/generated/resources/assets/create/lang/unfinished/ru_ru.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 9", + "_": "Missing Localizations: 11", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "Зеркало", "create.gui.symmetryWand.orientation": "Ориентация", diff --git a/src/generated/resources/assets/create/lang/unfinished/zh_cn.json b/src/generated/resources/assets/create/lang/unfinished/zh_cn.json index 318b583d9..55ef615ae 100644 --- a/src/generated/resources/assets/create/lang/unfinished/zh_cn.json +++ b/src/generated/resources/assets/create/lang/unfinished/zh_cn.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 8", + "_": "Missing Localizations: 10", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "镜子类型", "create.gui.symmetryWand.orientation": "方向", diff --git a/src/generated/resources/assets/create/lang/unfinished/zh_tw.json b/src/generated/resources/assets/create/lang/unfinished/zh_tw.json index 644d74902..508e4d3b8 100644 --- a/src/generated/resources/assets/create/lang/unfinished/zh_tw.json +++ b/src/generated/resources/assets/create/lang/unfinished/zh_tw.json @@ -1,5 +1,5 @@ { - "_": "Missing Localizations: 23", + "_": "Missing Localizations: 25", "_": "->------------------------] Game Elements [------------------------<-", @@ -796,6 +796,8 @@ "create.toolbox.unequip": "UNLOCALIZED: Unequip: %1$s", "create.toolbox.outOfRange": "UNLOCALIZED: Toolbox of held item not in Range", "create.toolbox.detach": "UNLOCALIZED: Stop tracking and keep item", + "create.toolbox.depositAll": "UNLOCALIZED: Return items to nearby Toolboxes", + "create.toolbox.depositBox": "UNLOCALIZED: Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "鏡子類型", "create.gui.symmetryWand.orientation": "方向", diff --git a/src/main/java/com/simibubi/create/content/curiosities/toolbox/RadialToolboxMenu.java b/src/main/java/com/simibubi/create/content/curiosities/toolbox/RadialToolboxMenu.java index d0156fbd7..4a878883d 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/toolbox/RadialToolboxMenu.java +++ b/src/main/java/com/simibubi/create/content/curiosities/toolbox/RadialToolboxMenu.java @@ -33,10 +33,13 @@ public class RadialToolboxMenu extends AbstractSimiScreen { private State state; private int ticksOpen; private int hoveredSlot; - + private boolean scrollMode; + private int scrollSlot = 0; private List toolboxes; private ToolboxTileEntity selectedBox; - final int UNEQUIP = -5; + + private static final int DEPOSIT = -7; + private static final int UNEQUIP = -5; public RadialToolboxMenu(List toolboxes, State state) { this.toolboxes = toolboxes; @@ -47,6 +50,10 @@ public class RadialToolboxMenu extends AbstractSimiScreen { selectedBox = toolboxes.get(0); } + public void prevSlot(int slot) { + scrollSlot = slot; + } + @Override protected void renderWindow(MatrixStack ms, int mouseX, int mouseY, float partialTicks) { float fade = MathHelper.clamp((ticksOpen + AnimationTickHolder.getPartialTicks()) / 10f, 1 / 512f, 1); @@ -57,11 +64,13 @@ public class RadialToolboxMenu extends AbstractSimiScreen { float hoveredY = mouseY - window.getGuiScaledHeight() / 2; float distance = hoveredX * hoveredX + hoveredY * hoveredY; - if (distance > 25 && distance < 20000) + if (distance > 25 && distance < 10000) hoveredSlot = (MathHelper.floor((AngleHelper.deg(MathHelper.atan2(hoveredY, hoveredX)) + 360 + 180 - 22.5f)) % 360) / 45; boolean renderCenterSlot = state == State.SELECT_ITEM_UNEQUIP; + if (scrollMode && distance > 150) + scrollMode = false; if (renderCenterSlot && distance <= 150) hoveredSlot = UNEQUIP; @@ -86,7 +95,7 @@ public class RadialToolboxMenu extends AbstractSimiScreen { ms.translate(-0.5, 0.5, 0); AllIcons.I_DISABLE.draw(ms, this, -9, -9); ms.translate(0.5, -0.5, 0); - if (hoveredSlot == UNEQUIP) { + if (!scrollMode && hoveredSlot == UNEQUIP) { AllGuiTextures.TOOLBELT_SLOT_HIGHLIGHT.draw(ms, this, -13, -13); tip = Lang.translate("toolbox.detach") .withStyle(TextFormatting.GOLD); @@ -94,6 +103,23 @@ public class RadialToolboxMenu extends AbstractSimiScreen { ms.popPose(); } else { + + if (hoveredX > 60 && hoveredX < 100 && hoveredY > -20 && hoveredY < 20) + hoveredSlot = DEPOSIT; + + ms.pushPose(); + ms.translate(80 + (-5 * (1 - fade) * (1 - fade)), 0, 0); + AllGuiTextures.TOOLBELT_SLOT.draw(ms, this, -12, -12); + ms.translate(-0.5, 0.5, 0); + AllIcons.I_TOOLBOX.draw(ms, this, -9, -9); + ms.translate(0.5, -0.5, 0); + if (!scrollMode && hoveredSlot == DEPOSIT) { + AllGuiTextures.TOOLBELT_SLOT_HIGHLIGHT.draw(ms, this, -13, -13); + tip = Lang.translate(state == State.SELECT_BOX ? "toolbox.depositAll" : "toolbox.depositBox") + .withStyle(TextFormatting.GOLD); + } + ms.popPose(); + for (int slot = 0; slot < 8; slot++) { ms.pushPose(); MatrixTransformStack.of(ms) @@ -116,7 +142,7 @@ public class RadialToolboxMenu extends AbstractSimiScreen { .at(3, 3) .render(ms); - if (slot == hoveredSlot && !empty) { + if (slot == (scrollMode ? scrollSlot : hoveredSlot) && !empty) { AllGuiTextures.TOOLBELT_SLOT_HIGHLIGHT.draw(ms, this, -1, -1); tip = stackInSlot.getHoverName(); } @@ -131,7 +157,7 @@ public class RadialToolboxMenu extends AbstractSimiScreen { .at(3, 3) .render(ms); - if (slot == hoveredSlot) { + if (slot == (scrollMode ? scrollSlot : hoveredSlot)) { AllGuiTextures.TOOLBELT_SLOT_HIGHLIGHT.draw(ms, this, -1, -1); tip = toolboxes.get(slot) .getDisplayName(); @@ -147,8 +173,8 @@ public class RadialToolboxMenu extends AbstractSimiScreen { if (renderCenterSlot) { ms.pushPose(); AllGuiTextures.TOOLBELT_SLOT.draw(ms, this, -12, -12); - AllIcons.I_TRASH.draw(ms, this, -9, -9); - if (UNEQUIP == hoveredSlot) { + (scrollMode ? AllIcons.I_REFRESH : AllIcons.I_FLIP).draw(ms, this, -9, -9); + if (!scrollMode && UNEQUIP == hoveredSlot) { AllGuiTextures.TOOLBELT_SLOT_HIGHLIGHT.draw(ms, this, -13, -13); tip = Lang.translate("toolbox.unequip", minecraft.player.getMainHandItem() .getHoverName()) @@ -172,7 +198,6 @@ public class RadialToolboxMenu extends AbstractSimiScreen { int k1 = 16777215; int k = i1 << 24 & -16777216; int l = font.width(tip); -// this.drawBackdrop(ms, font, -4, l, 16777215 | k); font.draw(ms, tip, (float) (-l / 2), -4.0F, k1 | k); RenderSystem.disableBlend(); ms.popPose(); @@ -197,46 +222,103 @@ public class RadialToolboxMenu extends AbstractSimiScreen { public void removed() { super.removed(); + int selected = (scrollMode ? scrollSlot : hoveredSlot); + + if (selected == DEPOSIT) { + if (state == State.DETACH) + return; + else if (state == State.SELECT_BOX) + toolboxes.forEach(te -> AllPackets.channel.sendToServer(new ToolboxDisposeAllPacket(te.getBlockPos()))); + else + AllPackets.channel.sendToServer(new ToolboxDisposeAllPacket(selectedBox.getBlockPos())); + return; + } + if (state == State.SELECT_BOX) return; if (state == State.DETACH) { - if (hoveredSlot == UNEQUIP) + if (selected == UNEQUIP) AllPackets.channel.sendToServer( - new ToolboxEquipPacket(null, hoveredSlot, Minecraft.getInstance().player.inventory.selected)); + new ToolboxEquipPacket(null, selected, Minecraft.getInstance().player.inventory.selected)); return; } - if (hoveredSlot == UNEQUIP) - AllPackets.channel.sendToServer(new ToolboxEquipPacket(selectedBox.getBlockPos(), hoveredSlot, + if (selected == UNEQUIP) + AllPackets.channel.sendToServer(new ToolboxEquipPacket(selectedBox.getBlockPos(), selected, Minecraft.getInstance().player.inventory.selected)); - if (hoveredSlot < 0) + if (selected < 0) return; ToolboxInventory inv = selectedBox.inventory; - ItemStack stackInSlot = inv.filters.get(hoveredSlot); + ItemStack stackInSlot = inv.filters.get(selected); if (stackInSlot.isEmpty()) return; - if (inv.getStackInSlot(hoveredSlot * ToolboxInventory.STACKS_PER_COMPARTMENT) + if (inv.getStackInSlot(selected * ToolboxInventory.STACKS_PER_COMPARTMENT) .isEmpty()) return; - AllPackets.channel.sendToServer(new ToolboxEquipPacket(selectedBox.getBlockPos(), hoveredSlot, + AllPackets.channel.sendToServer(new ToolboxEquipPacket(selectedBox.getBlockPos(), selected, Minecraft.getInstance().player.inventory.selected)); } @Override - public boolean mouseClicked(double x, double y, int button) { - if (state == State.SELECT_BOX && hoveredSlot >= 0 && hoveredSlot < toolboxes.size()) { - state = State.SELECT_ITEM; - selectedBox = toolboxes.get(hoveredSlot); + public boolean mouseScrolled(double mouseX, double mouseY, double delta) { + MainWindow window = getMinecraft().getWindow(); + double hoveredX = mouseX - window.getGuiScaledWidth() / 2; + double hoveredY = mouseY - window.getGuiScaledHeight() / 2; + double distance = hoveredX * hoveredX + hoveredY * hoveredY; + if (distance <= 150) { + scrollMode = true; + scrollSlot = (((int) (scrollSlot - delta)) + 8) % 8; + for (int i = 0; i < 10; i++) { + + if (state == State.SELECT_ITEM || state == State.SELECT_ITEM_UNEQUIP) { + ToolboxInventory inv = selectedBox.inventory; + ItemStack stackInSlot = inv.filters.get(scrollSlot); + if (!stackInSlot.isEmpty() + && !inv.getStackInSlot(scrollSlot * ToolboxInventory.STACKS_PER_COMPARTMENT) + .isEmpty()) + break; + } + + if (state == State.SELECT_BOX) + if (scrollSlot < toolboxes.size()) + break; + + if (state == State.DETACH) + break; + + scrollSlot -= MathHelper.sign(delta); + scrollSlot = (scrollSlot + 8) % 8; + } return true; } - if (state == State.SELECT_ITEM || state == State.SELECT_ITEM_UNEQUIP) { - if (hoveredSlot == UNEQUIP || hoveredSlot >= 0) { + return super.mouseScrolled(mouseX, mouseY, delta); + } + + @Override + public boolean mouseClicked(double x, double y, int button) { + int selected = (scrollMode ? scrollSlot : hoveredSlot); + + if (selected == DEPOSIT) { + onClose(); + ToolboxHandlerClient.COOLDOWN = 2; + return true; + } + + if (state == State.SELECT_BOX && selected >= 0 && selected < toolboxes.size()) { + state = State.SELECT_ITEM; + selectedBox = toolboxes.get(selected); + return true; + } + + if (state == State.DETACH || state == State.SELECT_ITEM || state == State.SELECT_ITEM_UNEQUIP) { + if (selected == UNEQUIP || selected >= 0) { onClose(); - ToolboxHandlerClient.COOLDOWN = 10; + ToolboxHandlerClient.COOLDOWN = 2; + return true; } } diff --git a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxBlock.java b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxBlock.java index c56351184..37c33a5fc 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxBlock.java +++ b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxBlock.java @@ -2,6 +2,9 @@ package com.simibubi.create.content.curiosities.toolbox; import static net.minecraft.state.properties.BlockStateProperties.WATERLOGGED; +import java.util.Optional; + +import com.simibubi.create.AllBlocks; import com.simibubi.create.AllShapes; import com.simibubi.create.AllTileEntities; import com.simibubi.create.foundation.block.ITE; @@ -17,6 +20,7 @@ import net.minecraft.fluid.FluidState; import net.minecraft.fluid.Fluids; import net.minecraft.item.BlockItemUseContext; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; import net.minecraft.state.StateContainer.Builder; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ActionResultType; @@ -26,6 +30,7 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.math.shapes.ISelectionContext; import net.minecraft.util.math.shapes.VoxelShape; +import net.minecraft.util.text.ITextComponent; import net.minecraft.world.IBlockReader; import net.minecraft.world.IWorld; import net.minecraft.world.World; @@ -66,19 +71,44 @@ public class ToolboxBlock extends HorizontalBlock implements IWaterLoggable, ITE }); } + @Override + public void onRemove(BlockState state, World world, BlockPos pos, BlockState newState, boolean moving) { + if (state.hasTileEntity() && (!state.is(newState.getBlock()) || !newState.hasTileEntity())) + world.removeBlockEntity(pos); + } + @Override public void attack(BlockState state, World world, BlockPos pos, PlayerEntity player) { if (player instanceof FakePlayer) return; if (world.isClientSide) return; + withTileEntityDo(world, pos, ToolboxTileEntity::unequipTracked); if (world instanceof ServerWorld) { - for (ItemStack itemStack : Block.getDrops(state, (ServerWorld) world, pos, world.getBlockEntity(pos))) - player.inventory.placeItemBackInInventory(world, itemStack); + ItemStack cloneItemStack = getCloneItemStack(world, pos, state); world.destroyBlock(pos, false); + if (world.getBlockState(pos) != state) + player.inventory.placeItemBackInInventory(world, cloneItemStack); } } + @Override + public ItemStack getCloneItemStack(IBlockReader world, BlockPos pos, BlockState state) { + ItemStack item = AllBlocks.TOOLBOX.asStack(); + Optional tileEntityOptional = getTileEntityOptional(world, pos); + + CompoundNBT tag = item.getOrCreateTag(); + CompoundNBT inv = tileEntityOptional.map(tb -> tb.inventory.serializeNBT()) + .orElse(new CompoundNBT()); + tag.put("Inventory", inv); + + ITextComponent customName = tileEntityOptional.map(ToolboxTileEntity::getCustomName) + .orElse(null); + if (customName != null) + item.setHoverName(customName); + return item; + } + @Override public BlockState updateShape(BlockState state, Direction direction, BlockState neighbourState, IWorld world, BlockPos pos, BlockPos neighbourPos) { diff --git a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxContainer.java b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxContainer.java index c97fe1fad..7856d3bec 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxContainer.java +++ b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxContainer.java @@ -17,7 +17,6 @@ import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.PacketBuffer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; -import net.minecraftforge.items.ItemHandlerHelper; import net.minecraftforge.items.SlotItemHandler; public class ToolboxContainer extends ContainerBase { @@ -84,15 +83,12 @@ public class ToolboxContainer extends ContainerBase { ItemStack carried = playerInv.getCarried(); if (type == ClickType.PICKUP && !carried.isEmpty() && !itemInClickedSlot.isEmpty() - && ItemHandlerHelper.canItemStacksStack(itemInClickedSlot, carried)) { + && ToolboxInventory.canItemsShareCompartment(itemInClickedSlot, carried)) { int subIndex = index % STACKS_PER_COMPARTMENT; if (subIndex != STACKS_PER_COMPARTMENT - 1) return clicked(index - subIndex + STACKS_PER_COMPARTMENT - 1, flags, type, player); } - if (type == ClickType.PICKUP && !carried.isEmpty() && itemInClickedSlot.isEmpty()) - contentHolder.inventory.filters.set(index / STACKS_PER_COMPARTMENT, carried); - if (type == ClickType.PICKUP && carried.isEmpty() && itemInClickedSlot.isEmpty()) if (!player.level.isClientSide) { contentHolder.inventory.filters.set(index / STACKS_PER_COMPARTMENT, ItemStack.EMPTY); diff --git a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxDisposeAllPacket.java b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxDisposeAllPacket.java new file mode 100644 index 000000000..071c61df6 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxDisposeAllPacket.java @@ -0,0 +1,81 @@ +package com.simibubi.create.content.curiosities.toolbox; + +import java.util.function.Supplier; + +import org.apache.commons.lang3.mutable.MutableBoolean; + +import com.simibubi.create.foundation.networking.SimplePacketBase; + +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.network.PacketBuffer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.fml.network.NetworkEvent.Context; +import net.minecraftforge.items.ItemHandlerHelper; + +public class ToolboxDisposeAllPacket extends SimplePacketBase { + + private BlockPos toolboxPos; + + public ToolboxDisposeAllPacket(BlockPos toolboxPos) { + this.toolboxPos = toolboxPos; + } + + public ToolboxDisposeAllPacket(PacketBuffer buffer) { + toolboxPos = buffer.readBlockPos(); + } + + @Override + public void write(PacketBuffer buffer) { + buffer.writeBlockPos(toolboxPos); + } + + @Override + public void handle(Supplier context) { + Context ctx = context.get(); + ctx.enqueueWork(() -> { + ServerPlayerEntity player = ctx.getSender(); + World world = player.level; + TileEntity blockEntity = world.getBlockEntity(toolboxPos); + + double maxRange = ToolboxHandler.getMaxRange(player); + if (player.distanceToSqr(toolboxPos.getX() + 0.5, toolboxPos.getY(), toolboxPos.getZ() + 0.5) > maxRange + * maxRange) + return; + if (!(blockEntity instanceof ToolboxTileEntity)) + return; + ToolboxTileEntity toolbox = (ToolboxTileEntity) blockEntity; + + CompoundNBT compound = player.getPersistentData() + .getCompound("CreateToolboxData"); + MutableBoolean sendData = new MutableBoolean(false); + + toolbox.inventory.inLimitedMode(inventory -> { + for (int i = 0; i < 36; i++) { + String key = String.valueOf(i); + if (compound.contains(key) && NBTUtil.readBlockPos(compound.getCompound(key) + .getCompound("Pos")) + .equals(toolboxPos)) { + ToolboxHandler.unequip(player, i, true); + sendData.setTrue(); + } + + ItemStack itemStack = player.inventory.getItem(i); + ItemStack remainder = ItemHandlerHelper.insertItemStacked(toolbox.inventory, itemStack, false); + if (remainder.getCount() != itemStack.getCount()) + player.inventory.setItem(i, remainder); + } + }); + + if (sendData.booleanValue()) + ToolboxHandler.syncData(player); + + }); + ctx.setPacketHandled(true); + } + +} diff --git a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxEquipPacket.java b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxEquipPacket.java index 078d23ff8..db63a5daa 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxEquipPacket.java +++ b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxEquipPacket.java @@ -2,8 +2,6 @@ package com.simibubi.create.content.curiosities.toolbox; import java.util.function.Supplier; -import com.simibubi.create.foundation.networking.AllPackets; -import com.simibubi.create.foundation.networking.ISyncPersistentData; import com.simibubi.create.foundation.networking.SimplePacketBase; import net.minecraft.entity.player.ServerPlayerEntity; @@ -15,7 +13,6 @@ import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.fml.network.NetworkEvent.Context; -import net.minecraftforge.fml.network.PacketDistributor; import net.minecraftforge.items.ItemHandlerHelper; public class ToolboxEquipPacket extends SimplePacketBase { @@ -54,35 +51,32 @@ public class ToolboxEquipPacket extends SimplePacketBase { World world = player.level; if (toolboxPos == null) { - player.getPersistentData() - .getCompound("CreateToolboxData") - .remove(String.valueOf(hotbarSlot)); - sendData(player); + ToolboxHandler.unequip(player, hotbarSlot, false); + ToolboxHandler.syncData(player); return; } TileEntity blockEntity = world.getBlockEntity(toolboxPos); double maxRange = ToolboxHandler.getMaxRange(player); - if (ctx.getSender() - .distanceToSqr(toolboxPos.getX() + 0.5, toolboxPos.getY(), - toolboxPos.getZ() + 0.5) > maxRange * maxRange) + if (player.distanceToSqr(toolboxPos.getX() + 0.5, toolboxPos.getY(), toolboxPos.getZ() + 0.5) > maxRange + * maxRange) return; if (!(blockEntity instanceof ToolboxTileEntity)) return; - ToolboxHandler.unequip(player, hotbarSlot); + ToolboxHandler.unequip(player, hotbarSlot, false); if (slot < 0 || slot >= 8) { - sendData(player); + ToolboxHandler.syncData(player); return; } ToolboxTileEntity toolboxTileEntity = (ToolboxTileEntity) blockEntity; ItemStack playerStack = player.inventory.getItem(hotbarSlot); - if (!playerStack.isEmpty() - && !ItemHandlerHelper.canItemStacksStack(playerStack, toolboxTileEntity.inventory.filters.get(slot))) { + if (!playerStack.isEmpty() && !ToolboxInventory.canItemsShareCompartment(playerStack, + toolboxTileEntity.inventory.filters.get(slot))) { ItemStack remainder = ItemHandlerHelper.insertItemStacked(toolboxTileEntity.inventory, playerStack, false); if (!remainder.isEmpty()) @@ -103,15 +97,11 @@ public class ToolboxEquipPacket extends SimplePacketBase { player.getPersistentData() .put("CreateToolboxData", compound); - sendData(player); toolboxTileEntity.connectPlayer(slot, player, hotbarSlot); + ToolboxHandler.syncData(player); }); ctx.setPacketHandled(true); } - private void sendData(ServerPlayerEntity player) { - AllPackets.channel.send(PacketDistributor.PLAYER.with(() -> player), new ISyncPersistentData.Packet(player)); - } - } diff --git a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxHandler.java b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxHandler.java index 9daef8f34..e21a775be 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxHandler.java +++ b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxHandler.java @@ -4,9 +4,14 @@ import java.util.List; import java.util.WeakHashMap; import java.util.stream.Collectors; +import com.simibubi.create.foundation.config.AllConfigs; +import com.simibubi.create.foundation.networking.AllPackets; +import com.simibubi.create.foundation.networking.ISyncPersistentData; import com.simibubi.create.foundation.utility.WorldAttached; +import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.NBTUtil; import net.minecraft.tileentity.TileEntity; @@ -14,6 +19,8 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.IWorld; import net.minecraft.world.World; +import net.minecraft.world.server.ServerWorld; +import net.minecraftforge.fml.network.PacketDistributor; public class ToolboxHandler { @@ -30,6 +37,70 @@ public class ToolboxHandler { .remove(te.getBlockPos()); } + static int validationTimer = 20; + + public static void entityTick(Entity entity, World world) { + if (world.isClientSide) + return; + if (!(world instanceof ServerWorld)) + return; + if (!(entity instanceof ServerPlayerEntity)) + return; + if (entity.tickCount % validationTimer != 0) + return; + + ServerPlayerEntity player = (ServerPlayerEntity) entity; + if (!player.getPersistentData() + .contains("CreateToolboxData")) + return; + + boolean sendData = false; + CompoundNBT compound = player.getPersistentData() + .getCompound("CreateToolboxData"); + for (int i = 0; i < 9; i++) { + String key = String.valueOf(i); + if (!compound.contains(key)) + continue; + + CompoundNBT data = compound.getCompound(key); + BlockPos pos = NBTUtil.readBlockPos(data.getCompound("Pos")); + int slot = data.getInt("Slot"); + + if (!world.isAreaLoaded(pos, 0)) + continue; + if (!(world.getBlockState(pos) + .getBlock() instanceof ToolboxBlock)) { + compound.remove(key); + sendData = true; + continue; + } + + TileEntity prevBlockEntity = world.getBlockEntity(pos); + if (prevBlockEntity instanceof ToolboxTileEntity) + ((ToolboxTileEntity) prevBlockEntity).connectPlayer(slot, player, i); + } + + if (sendData) + syncData(player); + } + + public static void playerLogin(PlayerEntity player) { + if (!(player instanceof ServerPlayerEntity)) + return; + if (player.getPersistentData() + .contains("CreateToolboxData") + && !player.getPersistentData() + .getCompound("CreateToolboxData") + .isEmpty()) { + syncData(player); + } + } + + public static void syncData(PlayerEntity player) { + AllPackets.channel.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) player), + new ISyncPersistentData.Packet(player)); + } + public static List getNearest(IWorld world, PlayerEntity player, int maxAmount) { Vector3d location = player.position(); double maxRange = getMaxRange(player); @@ -43,7 +114,7 @@ public class ToolboxHandler { .collect(Collectors.toList()); } - public static void unequip(PlayerEntity player, int hotbarSlot) { + public static void unequip(PlayerEntity player, int hotbarSlot, boolean keepItems) { CompoundNBT compound = player.getPersistentData() .getCompound("CreateToolboxData"); World world = player.level; @@ -56,8 +127,10 @@ public class ToolboxHandler { int prevSlot = prevData.getInt("Slot"); TileEntity prevBlockEntity = world.getBlockEntity(prevPos); - if (prevBlockEntity instanceof ToolboxTileEntity) - ((ToolboxTileEntity) prevBlockEntity).unequip(prevSlot, player, hotbarSlot); + if (prevBlockEntity instanceof ToolboxTileEntity) { + ToolboxTileEntity toolbox = (ToolboxTileEntity) prevBlockEntity; + toolbox.unequip(prevSlot, player, hotbarSlot, keepItems || !ToolboxHandler.withinRange(player, toolbox)); + } compound.remove(key); } @@ -73,7 +146,8 @@ public class ToolboxHandler { } public static double getMaxRange(PlayerEntity player) { - return 10; + return AllConfigs.SERVER.curiosities.toolboxRange.get() + .doubleValue(); } } diff --git a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxHandlerClient.java b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxHandlerClient.java index 7ac4b05d9..1997912d6 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxHandlerClient.java +++ b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxHandlerClient.java @@ -33,11 +33,11 @@ public class ToolboxHandlerClient { if (COOLDOWN > 0 && !AllKeys.TOOLBELT.isPressed()) COOLDOWN--; } - + public static void onKeyInput(int key, boolean pressed) { if (key != AllKeys.TOOLBELT.getBoundCode()) return; - if (COOLDOWN > 0) + if (COOLDOWN > 0) return; ClientPlayerEntity player = Minecraft.getInstance().player; if (player == null) @@ -60,8 +60,11 @@ public class ToolboxHandlerClient { if (canReachToolbox) { TileEntity blockEntity = level.getBlockEntity(pos); if (blockEntity instanceof ToolboxTileEntity) { - ScreenOpener.open(new RadialToolboxMenu(ImmutableList.of((ToolboxTileEntity) blockEntity), - RadialToolboxMenu.State.SELECT_ITEM_UNEQUIP)); + RadialToolboxMenu screen = new RadialToolboxMenu(ImmutableList.of((ToolboxTileEntity) blockEntity), + RadialToolboxMenu.State.SELECT_ITEM_UNEQUIP); + screen.prevSlot(compound.getCompound(slotKey) + .getInt("Slot")); + ScreenOpener.open(screen); return; } } @@ -73,10 +76,6 @@ public class ToolboxHandlerClient { if (toolboxes.isEmpty()) return; -// ItemStack itemStackFromSlot = player.getItemStackFromSlot(EquipmentSlotType.LEGS); -// if (!AllItems.TOOLBELT.isIn(itemStackFromSlot)) -// return; - if (toolboxes.size() == 1) ScreenOpener.open(new RadialToolboxMenu(toolboxes, RadialToolboxMenu.State.SELECT_ITEM)); else diff --git a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxInventory.java b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxInventory.java index 741c77eb1..4581bef86 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxInventory.java +++ b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxInventory.java @@ -2,9 +2,11 @@ package com.simibubi.create.content.curiosities.toolbox; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import javax.annotation.Nonnull; +import com.simibubi.create.AllBlocks; import com.simibubi.create.foundation.utility.NBTHelper; import net.minecraft.item.ItemStack; @@ -19,15 +21,24 @@ public class ToolboxInventory extends ItemStackHandler { List filters; boolean settling; private ToolboxTileEntity te; + + private boolean limitedMode; public ToolboxInventory(ToolboxTileEntity te) { super(8 * STACKS_PER_COMPARTMENT); this.te = te; + limitedMode = false; filters = new ArrayList<>(); settling = false; for (int i = 0; i < 8; i++) filters.add(ItemStack.EMPTY); } + + public void inLimitedMode(Consumer action) { + limitedMode = true; + action.accept(this); + limitedMode = false; + } public void settle(int compartment) { int totalCount = 0; @@ -50,11 +61,26 @@ public class ToolboxInventory extends ItemStackHandler { return; settling = true; - for (int i = 0; i < STACKS_PER_COMPARTMENT; i++) { - ItemStack copy = totalCount <= 0 ? ItemStack.EMPTY - : ItemHandlerHelper.copyStackWithSize(sample, Math.min(totalCount, sample.getMaxStackSize())); - setStackInSlot(compartment * STACKS_PER_COMPARTMENT + i, copy); - totalCount -= copy.getCount(); + if (!sample.isStackable()) { + for (int i = 0; i < STACKS_PER_COMPARTMENT; i++) { + if (!getStackInSlot(compartment * STACKS_PER_COMPARTMENT + i).isEmpty()) + continue; + for (int j = i + 1; j < STACKS_PER_COMPARTMENT; j++) { + ItemStack stackInSlot = getStackInSlot(compartment * STACKS_PER_COMPARTMENT + j); + if (stackInSlot.isEmpty()) + continue; + setStackInSlot(compartment * STACKS_PER_COMPARTMENT + i, stackInSlot); + setStackInSlot(compartment * STACKS_PER_COMPARTMENT + j, ItemStack.EMPTY); + break; + } + } + } else { + for (int i = 0; i < STACKS_PER_COMPARTMENT; i++) { + ItemStack copy = totalCount <= 0 ? ItemStack.EMPTY + : ItemHandlerHelper.copyStackWithSize(sample, Math.min(totalCount, sample.getMaxStackSize())); + setStackInSlot(compartment * STACKS_PER_COMPARTMENT + i, copy); + totalCount -= copy.getCount(); + } } settling = false; te.sendData(); @@ -62,11 +88,15 @@ public class ToolboxInventory extends ItemStackHandler { @Override public boolean isItemValid(int slot, ItemStack stack) { + if (AllBlocks.TOOLBOX.isIn(stack)) + return false; if (slot < 0 || slot >= getSlots()) return false; int compartment = slot / STACKS_PER_COMPARTMENT; ItemStack filter = filters.get(compartment); - if (!filter.isEmpty() && ItemHandlerHelper.canItemStacksStack(filter, stack)) + if (limitedMode && filter.isEmpty()) + return false; + if (filter.isEmpty() || ToolboxInventory.canItemsShareCompartment(filter, stack)) return super.isItemValid(slot, stack); return false; } @@ -76,8 +106,24 @@ public class ToolboxInventory extends ItemStackHandler { super.setStackInSlot(slot, stack); int compartment = slot / STACKS_PER_COMPARTMENT; if (!stack.isEmpty() && filters.get(compartment) - .isEmpty()) + .isEmpty()) { filters.set(compartment, ItemHandlerHelper.copyStackWithSize(stack, 1)); + te.sendData(); + } + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + ItemStack insertItem = super.insertItem(slot, stack, simulate); + if (insertItem.getCount() != stack.getCount()) { + int compartment = slot / STACKS_PER_COMPARTMENT; + if (!stack.isEmpty() && filters.get(compartment) + .isEmpty()) { + filters.set(compartment, ItemHandlerHelper.copyStackWithSize(stack, 1)); + te.sendData(); + } + } + return insertItem; } @Override @@ -128,7 +174,7 @@ public class ToolboxInventory extends ItemStackHandler { for (int i = STACKS_PER_COMPARTMENT - 1; i >= 0; i--) { int slot = compartment * STACKS_PER_COMPARTMENT + i; - ItemStack extracted = extractItem(slot, amount, simulate); + ItemStack extracted = extractItem(slot, remaining, simulate); remaining -= extracted.getCount(); if (!extracted.isEmpty()) lastValid = extracted; @@ -142,4 +188,10 @@ public class ToolboxInventory extends ItemStackHandler { return ItemHandlerHelper.copyStackWithSize(lastValid, amount - remaining); } + public static boolean canItemsShareCompartment(ItemStack stack1, ItemStack stack2) { + if (!stack1.isStackable() && !stack2.isStackable() && stack1.isDamageableItem() && stack2.isDamageableItem()) + return stack1.getItem() == stack2.getItem(); + return ItemHandlerHelper.canItemStacksStack(stack1, stack2); + } + } diff --git a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxScreen.java b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxScreen.java index 986d043ec..7a7d66bcb 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxScreen.java +++ b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxScreen.java @@ -14,7 +14,9 @@ import com.simibubi.create.foundation.gui.AllGuiTextures; import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.gui.GuiGameElement; import com.simibubi.create.foundation.gui.widgets.IconButton; +import com.simibubi.create.foundation.networking.AllPackets; import com.simibubi.create.foundation.utility.Iterate; +import com.simibubi.create.foundation.utility.Lang; import net.minecraft.client.renderer.Rectangle2d; import net.minecraft.entity.player.PlayerInventory; @@ -28,6 +30,7 @@ public class ToolboxScreen extends AbstractSimiContainerScreen AllGuiTextures PLAYER = AllGuiTextures.PLAYER_INVENTORY; protected Slot hoveredToolboxSlot; private IconButton confirmButton; + private IconButton disposeButton; private List extraAreas = Collections.emptyList(); @@ -42,7 +45,10 @@ public class ToolboxScreen extends AbstractSimiContainerScreen widgets.clear(); setWindowSize(BG.width, 256); confirmButton = new IconButton(getGuiLeft() + BG.width - 23, getGuiTop() + BG.height - 24, AllIcons.I_CONFIRM); + disposeButton = new IconButton(getGuiLeft() + 91, getGuiTop() + 69, AllIcons.I_TOOLBOX); + disposeButton.setToolTip(Lang.translate("toolbox.depositBox")); widgets.add(confirmButton); + widgets.add(disposeButton); extraAreas = ImmutableList.of(new Rectangle2d(118, 155, 80, 100), new Rectangle2d(308, 125, 100, 70)); } @@ -125,7 +131,8 @@ public class ToolboxScreen extends AbstractSimiContainerScreen for (int offset : Iterate.zeroAndOne) { ms.pushPose(); - ms.translate(0, -offset * 1 / 8f, menu.contentHolder.drawers.getValue(partialTicks) * -.175f * (2 - offset)); + ms.translate(0, -offset * 1 / 8f, + menu.contentHolder.drawers.getValue(partialTicks) * -.175f * (2 - offset)); GuiGameElement.of(AllBlockPartials.TOOLBOX_DRAWER) .render(ms); ms.popPose(); @@ -149,6 +156,10 @@ public class ToolboxScreen extends AbstractSimiContainerScreen minecraft.player.closeContainer(); return true; } + if (disposeButton.isHovered()) { + AllPackets.channel.sendToServer(new ToolboxDisposeAllPacket(menu.contentHolder.getBlockPos())); + return true; + } } return mouseClicked; diff --git a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxTileEntity.java b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxTileEntity.java index 91bdd62d9..644d34fd2 100644 --- a/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxTileEntity.java +++ b/src/main/java/com/simibubi/create/content/curiosities/toolbox/ToolboxTileEntity.java @@ -1,14 +1,14 @@ package com.simibubi.create.content.curiosities.toolbox; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.WeakHashMap; -import com.simibubi.create.foundation.networking.AllPackets; -import com.simibubi.create.foundation.networking.ISyncPersistentData; import com.simibubi.create.foundation.tileEntity.SmartTileEntity; import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import com.simibubi.create.foundation.utility.VecHelper; @@ -34,7 +34,6 @@ import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TranslationTextComponent; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.fml.network.PacketDistributor; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.ItemHandlerHelper; @@ -93,6 +92,8 @@ public class ToolboxTileEntity extends SmartTileEntity implements INamedContaine } private void tickPlayers() { + boolean update = false; + for (Iterator>> toolboxSlots = connectedPlayers.entrySet() .iterator(); toolboxSlots.hasNext();) { @@ -115,15 +116,14 @@ public class ToolboxTileEntity extends SmartTileEntity implements INamedContaine ItemStack playerStack = player.inventory.getItem(hotbarSlot); - if (clear - || !playerStack.isEmpty() && !ItemHandlerHelper.canItemStacksStack(playerStack, referenceItem)) { + if (clear || !playerStack.isEmpty() + && !ToolboxInventory.canItemsShareCompartment(playerStack, referenceItem)) { player.getPersistentData() .getCompound("CreateToolboxData") .remove(String.valueOf(hotbarSlot)); playerEntries.remove(); if (player instanceof ServerPlayerEntity) - AllPackets.channel.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) player), - new ISyncPersistentData.Packet(player)); + ToolboxHandler.syncData(player); continue; } @@ -132,34 +132,99 @@ public class ToolboxTileEntity extends SmartTileEntity implements INamedContaine if (count < targetAmount) { int amountToReplenish = targetAmount - count; + + if (isOpenInContainer(player)) { + ItemStack extracted = inventory.takeFromCompartment(amountToReplenish, slot, true); + if (!extracted.isEmpty()) { + ToolboxHandler.unequip(player, hotbarSlot, false); + ToolboxHandler.syncData(player); + continue; + } + } + ItemStack extracted = inventory.takeFromCompartment(amountToReplenish, slot, false); - if (!extracted.isEmpty()) + if (!extracted.isEmpty()) { + update = true; player.inventory.setItem(hotbarSlot, ItemHandlerHelper.copyStackWithSize(extracted, count + extracted.getCount())); + } } if (count > targetAmount) { int amountToDeposit = count - targetAmount; ItemStack toDistribute = ItemHandlerHelper.copyStackWithSize(playerStack, amountToDeposit); + + if (isOpenInContainer(player)) { + int deposited = amountToDeposit - inventory.distributeToCompartment(toDistribute, slot, true) + .getCount(); + if (deposited > 0) { + ToolboxHandler.unequip(player, hotbarSlot, true); + ToolboxHandler.syncData(player); + continue; + } + } + int deposited = amountToDeposit - inventory.distributeToCompartment(toDistribute, slot, false) .getCount(); - if (deposited > 0) + if (deposited > 0) { + update = true; player.inventory.setItem(hotbarSlot, ItemHandlerHelper.copyStackWithSize(playerStack, count - deposited)); + } } } if (clear) toolboxSlots.remove(); } + + if (update) + + sendData(); + } - public void unequip(int slot, PlayerEntity player, int hotbarSlot) { + private boolean isOpenInContainer(PlayerEntity player) { + return player.containerMenu instanceof ToolboxContainer + && ((ToolboxContainer) player.containerMenu).contentHolder == this; + } + + public void unequipTracked() { + if (level.isClientSide) + return; + + Set affected = new HashSet<>(); + + for (Iterator>> toolboxSlots = connectedPlayers.entrySet() + .iterator(); toolboxSlots.hasNext();) { + + Entry> toolboxSlotEntry = toolboxSlots.next(); + WeakHashMap set = toolboxSlotEntry.getValue(); + + for (Iterator> playerEntries = set.entrySet() + .iterator(); playerEntries.hasNext();) { + Entry playerEntry = playerEntries.next(); + + PlayerEntity player = playerEntry.getKey(); + int hotbarSlot = playerEntry.getValue(); + + ToolboxHandler.unequip(player, hotbarSlot, false); + if (player instanceof ServerPlayerEntity) + affected.add((ServerPlayerEntity) player); + } + } + + for (ServerPlayerEntity player : affected) + ToolboxHandler.syncData(player); + connectedPlayers.clear(); + } + + public void unequip(int slot, PlayerEntity player, int hotbarSlot, boolean keepItems) { if (!connectedPlayers.containsKey(slot)) return; connectedPlayers.get(slot) .remove(player); - if (!ToolboxHandler.withinRange(player, this)) + if (keepItems) return; ItemStack playerStack = player.inventory.getItem(hotbarSlot); @@ -220,11 +285,6 @@ public class ToolboxTileEntity extends SmartTileEntity implements INamedContaine return ToolboxContainer.create(id, inv, this); } - @Override - public ITextComponent getDisplayName() { - return customName != null ? customName : new TranslationTextComponent("block.create.toolbox"); - } - @Override public void lazyTick() { updateOpenCount(); @@ -272,6 +332,12 @@ public class ToolboxTileEntity extends SmartTileEntity implements INamedContaine if (level.isClientSide) return; WeakHashMap map = connectedPlayers.computeIfAbsent(slot, WeakHashMap::new); + Integer previous = map.get(player); + if (previous != null) { + if (previous == hotbarSlot) + return; + ToolboxHandler.unequip(player, previous, false); + } map.put(player, hotbarSlot); } @@ -283,6 +349,21 @@ public class ToolboxTileEntity extends SmartTileEntity implements INamedContaine this.customName = customName; } + @Override + public ITextComponent getDisplayName() { + return customName != null ? customName : new TranslationTextComponent("block.create.toolbox"); + } + + @Override + public ITextComponent getCustomName() { + return customName; + } + + @Override + public boolean hasCustomName() { + return customName != null; + } + @Override public ITextComponent getName() { return customName; diff --git a/src/main/java/com/simibubi/create/events/CommonEvents.java b/src/main/java/com/simibubi/create/events/CommonEvents.java index 3631e9b0b..b2d29b6e2 100644 --- a/src/main/java/com/simibubi/create/events/CommonEvents.java +++ b/src/main/java/com/simibubi/create/events/CommonEvents.java @@ -8,6 +8,7 @@ import com.simibubi.create.content.contraptions.components.structureMovement.tra import com.simibubi.create.content.contraptions.fluids.recipe.FluidTransferRecipes; import com.simibubi.create.content.contraptions.fluids.recipe.PotionMixingRecipeManager; import com.simibubi.create.content.contraptions.wrench.WrenchItem; +import com.simibubi.create.content.curiosities.toolbox.ToolboxHandler; import com.simibubi.create.content.curiosities.zapper.ZapperInteractionHandler; import com.simibubi.create.content.curiosities.zapper.ZapperItem; import com.simibubi.create.content.logistics.item.LinkedControllerServerHandler; @@ -21,6 +22,7 @@ import com.simibubi.create.foundation.utility.recipe.RecipeFinder; import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.fluid.FluidState; import net.minecraft.item.ItemStack; @@ -39,6 +41,7 @@ import net.minecraftforge.event.entity.EntityJoinWorldEvent; import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; import net.minecraftforge.event.entity.player.AttackEntityEvent; import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.event.world.BlockEvent.FluidPlaceBlockEvent; import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.event.world.WorldEvent; @@ -64,6 +67,12 @@ public class CommonEvents { CapabilityMinecartController.onChunkUnloaded(event); } + @SubscribeEvent + public static void playerLoggedIn(PlayerLoggedInEvent event) { + PlayerEntity player = event.getPlayer(); + ToolboxHandler.playerLogin(player); + } + @SubscribeEvent public static void whenFluidsMeet(FluidPlaceBlockEvent event) { BlockState blockState = event.getOriginalState(); @@ -75,7 +84,8 @@ public class CommonEvents { return; for (Direction direction : Iterate.directions) { - FluidState metFluidState = fluidState.isSource() ? fluidState : world.getFluidState(pos.relative(direction)); + FluidState metFluidState = + fluidState.isSource() ? fluidState : world.getFluidState(pos.relative(direction)); if (!metFluidState.is(FluidTags.WATER)) continue; BlockState lavaInteraction = AllFluids.getLavaInteraction(metFluidState); @@ -104,6 +114,7 @@ public class CommonEvents { if (world == null) return; ContraptionHandler.entitiesWhoJustDismountedGetSentToTheRightLocation(entityLiving, world); + ToolboxHandler.entityTick(entityLiving, world); } @SubscribeEvent diff --git a/src/main/java/com/simibubi/create/foundation/config/CCuriosities.java b/src/main/java/com/simibubi/create/foundation/config/CCuriosities.java index 2ba15eaea..24f38f7b9 100644 --- a/src/main/java/com/simibubi/create/foundation/config/CCuriosities.java +++ b/src/main/java/com/simibubi/create/foundation/config/CCuriosities.java @@ -4,6 +4,7 @@ public class CCuriosities extends ConfigBase { public ConfigInt maxSymmetryWandRange = i(50, 10, "maxSymmetryWandRange", Comments.symmetryRange); public ConfigInt placementAssistRange = i(12, 3, "placementAssistRange", Comments.placementRange); + public ConfigInt toolboxRange = i(10, 1, "toolboxRange", Comments.toolboxRange); public ConfigInt airInBacktank = i(900, 1, "airInBacktank", Comments.maxAirInBacktank); public ConfigInt enchantedBacktankCapacity = i(300, 1, "enchantedBacktankCapacity", Comments.enchantedBacktankCapacity); @@ -25,6 +26,8 @@ public class CCuriosities extends ConfigBase { "The volume of Air added by each level of the backtanks Capacity Enchantment"; static String placementRange = "The Maximum Distance a Block placed by Create's placement assist will have to its interaction point."; + static String toolboxRange = + "The Maximum Distance at which a Toolbox can interact with Players' Inventories."; static String maxExtendoGripActions = "Amount of free Extendo Grip actions provided by one filled Copper Backtank. Set to 0 makes Extendo Grips unbreakable"; static String maxPotatoCannonShots = diff --git a/src/main/java/com/simibubi/create/foundation/gui/AllIcons.java b/src/main/java/com/simibubi/create/foundation/gui/AllIcons.java index 59799ab63..6a07a5f57 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/AllIcons.java +++ b/src/main/java/com/simibubi/create/foundation/gui/AllIcons.java @@ -86,6 +86,7 @@ public class AllIcons implements IScreenRenderable { I_TUNNEL_PREFER_NEAREST = next(), I_TUNNEL_RANDOMIZE = next(), I_TUNNEL_SYNCHRONIZE = next(), + I_TOOLBOX = next(), I_TOOL_MOVE_XZ = newRow(), I_TOOL_MOVE_Y = next(), diff --git a/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java b/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java index e26663161..20fffe6bb 100644 --- a/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java +++ b/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java @@ -23,6 +23,7 @@ import com.simibubi.create.content.contraptions.fluids.actors.FluidSplashPacket; import com.simibubi.create.content.contraptions.relays.advanced.sequencer.ConfigureSequencedGearshiftPacket; import com.simibubi.create.content.curiosities.bell.SoulPulseEffectPacket; import com.simibubi.create.content.curiosities.symmetry.SymmetryEffectPacket; +import com.simibubi.create.content.curiosities.toolbox.ToolboxDisposeAllPacket; import com.simibubi.create.content.curiosities.toolbox.ToolboxEquipPacket; import com.simibubi.create.content.curiosities.tools.BlueprintAssignCompleteRecipePacket; import com.simibubi.create.content.curiosities.tools.ExtendoGripInteractionPacket; @@ -98,6 +99,7 @@ public enum AllPackets { SUBMIT_GHOST_ITEM(GhostItemSubmitPacket.class, GhostItemSubmitPacket::new, PLAY_TO_SERVER), BLUEPRINT_COMPLETE_RECIPE(BlueprintAssignCompleteRecipePacket.class, BlueprintAssignCompleteRecipePacket::new, PLAY_TO_SERVER), TOOLBOX_EQUIP(ToolboxEquipPacket.class, ToolboxEquipPacket::new, PLAY_TO_SERVER), + TOOLBOX_DISPOSE_ALL(ToolboxDisposeAllPacket.class, ToolboxDisposeAllPacket::new, PLAY_TO_SERVER), // Server to Client SYMMETRY_EFFECT(SymmetryEffectPacket.class, SymmetryEffectPacket::new, PLAY_TO_CLIENT), diff --git a/src/main/resources/assets/create/lang/default/messages.json b/src/main/resources/assets/create/lang/default/messages.json index 24ae8105f..1d7bd4148 100644 --- a/src/main/resources/assets/create/lang/default/messages.json +++ b/src/main/resources/assets/create/lang/default/messages.json @@ -108,6 +108,8 @@ "create.toolbox.unequip": "Unequip: %1$s", "create.toolbox.outOfRange": "Toolbox of held item not in Range", "create.toolbox.detach": "Stop tracking and keep item", + "create.toolbox.depositAll": "Return items to nearby Toolboxes", + "create.toolbox.depositBox": "Return items to Toolbox", "create.gui.symmetryWand.mirrorType": "Mirror", "create.gui.symmetryWand.orientation": "Orientation", diff --git a/src/main/resources/assets/create/textures/gui/icons.png b/src/main/resources/assets/create/textures/gui/icons.png index 9c862ea807efd75c7ea0dad2f5d1343ed01d1980..503558349f5ba7de42a0f2f2e849e7f344c36455 100644 GIT binary patch delta 3599 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 N80wkoHfSTG{{h{zV_5(I