diff --git a/common/src/generated/resources/.cache/630af4bded938901e0e1fd57c58a2ac245292828 b/common/src/generated/resources/.cache/630af4bded938901e0e1fd57c58a2ac245292828
index 87b0f4c9..5b7280f2 100644
--- a/common/src/generated/resources/.cache/630af4bded938901e0e1fd57c58a2ac245292828
+++ b/common/src/generated/resources/.cache/630af4bded938901e0e1fd57c58a2ac245292828
@@ -1,4 +1,4 @@
-// 1.20.1 2024-08-04T19:37:15.035886867 Create: Numismatics/Numismatics' Advancements
+// 1.20.1 2024-08-05T17:31:05.743168034 Create: Numismatics/Numismatics' Advancements
4ab84595dc44460d6d89ff3608940dd8694b93d6 data/numismatics/advancements/is_this_legal.json
645c92e6e449889ed4cf617432fd6fa87ffe355b data/numismatics/advancements/questionable_investment.json
8f32fb49ef81058e2e660fac983dbef2f554db38 data/numismatics/advancements/root.json
diff --git a/common/src/generated/resources/.cache/6ba62358bf8e130d42215f5f9edbedd611809677 b/common/src/generated/resources/.cache/6ba62358bf8e130d42215f5f9edbedd611809677
index 1b9b97e0..1cba9f94 100644
--- a/common/src/generated/resources/.cache/6ba62358bf8e130d42215f5f9edbedd611809677
+++ b/common/src/generated/resources/.cache/6ba62358bf8e130d42215f5f9edbedd611809677
@@ -1,4 +1,4 @@
-// 1.20.1 2024-08-04T19:37:15.034619492 Create: Numismatics/Numismatics Standard Recipes
+// 1.20.1 2024-08-05T17:31:05.737487381 Create: Numismatics/Numismatics Standard Recipes
0e1680b878cfa51c04809504b3542cb6312027a1 data/numismatics/recipes/crafting/gray_id_card.json
61954216f844eecdbee266da7e517a983993d2f8 data/numismatics/advancements/recipes/misc/crafting/brown_card.json
2c732f9c3bc02a29a4d86a0552f4dbbae986a34a data/numismatics/recipes/crafting/blue_authorized_card.json
@@ -26,8 +26,8 @@ ce32a3a7c2a5564e84b30bb1b71007ca4adc985d data/numismatics/recipes/crafting/lime_
501f27ee03faeb675a05b63b0c8058f1473f02d5 data/numismatics/advancements/recipes/misc/crafting/purple_id_card.json
8d863132d01d516db98d228e9038425923b7354d data/numismatics/recipes/crafting/black_card.json
78af0a7f17e04133eae7172df3255aaa34034537 data/numismatics/advancements/recipes/misc/crafting/red_id_card.json
-86ccf10cd1549f633f99674b002c543bd46aefd0 data/numismatics/recipes/crafting/yellow_card.json
e16d9a5134c226db94bb389c44e55a8b393ca6c4 data/numismatics/recipes/crafting/brass_depositor.json
+86ccf10cd1549f633f99674b002c543bd46aefd0 data/numismatics/recipes/crafting/yellow_card.json
1d0c5ff8b555ba922b0699a93dceb4cb9d49c4f9 data/numismatics/advancements/recipes/misc/crafting/lime_card.json
3cfbeee014050f945e5fa2299dbbcfbf120bf29a data/numismatics/recipes/crafting/red_authorized_card.json
6d90141ba018612413149776136ccb11b26add7c data/numismatics/advancements/recipes/misc/crafting/light_gray_authorized_card.json
diff --git a/common/src/generated/resources/.cache/816056b233c115f7af92c9ed2c207f096721f5cf b/common/src/generated/resources/.cache/816056b233c115f7af92c9ed2c207f096721f5cf
index 2472653a..c72f27cc 100644
--- a/common/src/generated/resources/.cache/816056b233c115f7af92c9ed2c207f096721f5cf
+++ b/common/src/generated/resources/.cache/816056b233c115f7af92c9ed2c207f096721f5cf
@@ -1,2 +1,2 @@
-// 1.20.1 2024-08-04T19:37:15.031552342 Create: Numismatics/Numismatics EMI excluded tags
+// 1.20.1 2024-08-05T17:31:05.725976583 Create: Numismatics/Numismatics EMI excluded tags
b57edab6f7e7a6e1b1211daa4c3b217ffd09ce62 assets/emi/tag/exclusions/numismatics.json
diff --git a/common/src/generated/resources/.cache/c6e4c19894bc5aece2976a0277ba8e1dbf023865 b/common/src/generated/resources/.cache/c6e4c19894bc5aece2976a0277ba8e1dbf023865
index 1dcddd2b..db363452 100644
--- a/common/src/generated/resources/.cache/c6e4c19894bc5aece2976a0277ba8e1dbf023865
+++ b/common/src/generated/resources/.cache/c6e4c19894bc5aece2976a0277ba8e1dbf023865
@@ -1 +1 @@
-// 1.20.1 2024-08-04T19:37:15.035644417 Create: Numismatics/Numismatics' Sequenced Assembly Recipes
+// 1.20.1 2024-08-05T17:31:05.741816291 Create: Numismatics/Numismatics' Sequenced Assembly Recipes
diff --git a/common/src/generated/resources/.cache/d6a1ec2d08c6d6d7facbde77dda6f0158c00bbd6 b/common/src/generated/resources/.cache/d6a1ec2d08c6d6d7facbde77dda6f0158c00bbd6
index 907cb1b4..993e65dc 100644
--- a/common/src/generated/resources/.cache/d6a1ec2d08c6d6d7facbde77dda6f0158c00bbd6
+++ b/common/src/generated/resources/.cache/d6a1ec2d08c6d6d7facbde77dda6f0158c00bbd6
@@ -1,4 +1,4 @@
-// 1.20.1 2024-08-04T19:37:15.03270554 Create: Numismatics/Registrate Provider for numismatics [Recipes, Advancements, Loot Tables, Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), Blockstates, Item models, Lang (en_us/en_ud)]
+// 1.20.1 2024-08-05T17:31:05.729416827 Create: Numismatics/Registrate Provider for numismatics [Recipes, Advancements, Loot Tables, Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), Blockstates, Item models, Lang (en_us/en_ud)]
2f1dad2a2e0086d54cf4b88378feacc04341d87f data/numismatics/tags/items/internal/ingots/iron_ingots.json
dc5c60bbbaf3a5d7bc1f9bc0c9377757dbd8de49 data/numismatics/loot_tables/blocks/bank_terminal.json
5b0244502972f49d063d98fa0cb3f6fc65af82b2 data/numismatics/tags/items/internal/string.json
@@ -8,7 +8,7 @@ a96d3d02794064cd9be1bca25a9ba6217675e6c5 assets/numismatics/models/item/white_id
bb2a77462e6213eddde134f3cc3e9a3f07f07f3f assets/numismatics/models/item/yellow_authorized_card.json
74a4c7ca7a48382782e5dba33018dfc8255192c5 assets/numismatics/models/block/brass_depositor_locked.json
3f0d912779200aaaf55bef102d9b96acead1a636 data/numismatics/tags/items/internal/dyes/purple_dyes.json
-7792957fb81474e92aea29bb69c435866fcc25ae assets/numismatics/lang/en_us.json
+3346bd25775d94025b9cc65c03d9a7151d316355 assets/numismatics/lang/en_us.json
377e460c0dcf6d7de1b7ae235959105a7c45e4c4 assets/numismatics/models/item/cyan_authorized_card.json
bde18ccd9c21484154597c6271750c0406082f61 data/forge/tags/blocks/relocation_not_supported.json
eca751589c40725750e2c2baa6607e83255fd5f4 assets/numismatics/models/item/brown_authorized_card.json
@@ -18,7 +18,7 @@ ad712dd2a2a7268dfa773f38a50d526952758d5c data/numismatics/tags/items/internal/dy
8550097149cebbfd50bdeac2003327b60a4aee9a assets/numismatics/models/item/light_gray_card.json
95b492bd9230dc90fca9395c823cef39e644d8f2 assets/numismatics/models/item/sprocket.json
70c481f36a9718ac48632e6939ac6ba785be4c9e assets/numismatics/models/item/black_id_card.json
-d780766e58a270fc111ee7517399d209168b8e8f assets/numismatics/lang/en_ud.json
+8343a58fb3d5ce9eeecabdeaa911eccae29409d1 assets/numismatics/lang/en_ud.json
1e78f650091a4a2c43e36fb815f23d0591e058a6 assets/numismatics/models/item/magenta_card.json
909f5d14f23199c064f6b91a421bb7b15e0f1a7d assets/numismatics/models/item/orange_authorized_card.json
d6f017479b3cc538f73d7fb0a1e65d1742bab266 assets/numismatics/models/item/light_blue_id_card.json
@@ -56,8 +56,8 @@ d048d04208faa63f0014d614d6026a66fe118c11 data/numismatics/loot_tables/blocks/bra
790ff3c5da6a67a5de1ceb7138fa3e1c0fe97f80 data/numismatics/tags/items/internal/dyes/lime_dyes.json
5cfb64f42dbeaa0720a7dd952e47d638c17a1056 assets/numismatics/models/block/salepoint.json
a8cb82f19034a0e724e12df45c883e9cd469c210 assets/numismatics/models/item/green_card.json
-e1087e56db7b4d8812ab4617344f0ac8b0f0e6c7 assets/numismatics/models/item/light_gray_authorized_card.json
390db78c5393fca4f90018df51d1a79d11a64f72 assets/numismatics/models/item/light_blue_authorized_card.json
+e1087e56db7b4d8812ab4617344f0ac8b0f0e6c7 assets/numismatics/models/item/light_gray_authorized_card.json
c09892d2d189f147997d77f4ce39b0570729f030 assets/numismatics/models/item/lime_authorized_card.json
06ecd28cd97f4e8200dc396858695cad57b871c8 assets/numismatics/blockstates/blaze_banker.json
95ef415a564eba1d212053195d25b199427b94e3 assets/numismatics/blockstates/creative_vendor.json
diff --git a/common/src/generated/resources/assets/numismatics/lang/en_ud.json b/common/src/generated/resources/assets/numismatics/lang/en_ud.json
index df1f8458..1e646614 100644
--- a/common/src/generated/resources/assets/numismatics/lang/en_ud.json
+++ b/common/src/generated/resources/assets/numismatics/lang/en_ud.json
@@ -66,10 +66,14 @@
"gui.numismatics.limit.none": "ʇıɯıן oN",
"gui.numismatics.salepoint.cancel": "uoıʇɔɐsuɐɹʇ ןǝɔuɐƆ",
"gui.numismatics.salepoint.count": "sʇıu∩",
+ "gui.numismatics.salepoint.fluid_empty": "ʎʇdɯƎ",
+ "gui.numismatics.salepoint.fluid_filter_empty.0": "ɹǝʇןıℲ pınןℲ",
+ "gui.numismatics.salepoint.fluid_filter_empty.1": "ǝןʇʇoq ɐ ɹo ʇǝʞɔnq ɐ sɐ ɥɔns ɯǝʇı buıpןoɥ-pınןɟ ɐ ɥʇıʍ ʞɔıןƆ",
"gui.numismatics.salepoint.go": "uoıʇɔɐsuɐɹʇ ʇɹɐʇS",
"gui.numismatics.salepoint.insufficient_space": "ןןnɟ sı ʇǝbɹɐ⟘",
"gui.numismatics.salepoint.invalid_state": "ǝʇɐʇs pıןɐʌuI",
"gui.numismatics.salepoint.no_card": "pɹɐɔ oN",
+ "gui.numismatics.salepoint.no_filter": "ɹǝʇןıɟ oN",
"gui.numismatics.salepoint.no_state": "pǝɹnbıɟuoɔ ʇoN",
"gui.numismatics.salepoint.no_target": "punoɟ ʇou ʇǝbɹɐ⟘",
"gui.numismatics.salepoint.price": "¤%s '%s %s :ʇıu∩/ǝɔıɹԀ",
diff --git a/common/src/generated/resources/assets/numismatics/lang/en_us.json b/common/src/generated/resources/assets/numismatics/lang/en_us.json
index db19c046..1d65fe92 100644
--- a/common/src/generated/resources/assets/numismatics/lang/en_us.json
+++ b/common/src/generated/resources/assets/numismatics/lang/en_us.json
@@ -66,10 +66,14 @@
"gui.numismatics.limit.none": "No limit",
"gui.numismatics.salepoint.cancel": "Cancel transaction",
"gui.numismatics.salepoint.count": "Units",
+ "gui.numismatics.salepoint.fluid_empty": "Empty",
+ "gui.numismatics.salepoint.fluid_filter_empty.0": "Fluid Filter",
+ "gui.numismatics.salepoint.fluid_filter_empty.1": "Click with a fluid-holding item such as a bucket or a bottle",
"gui.numismatics.salepoint.go": "Start transaction",
"gui.numismatics.salepoint.insufficient_space": "Target is full",
"gui.numismatics.salepoint.invalid_state": "Invalid state",
"gui.numismatics.salepoint.no_card": "No card",
+ "gui.numismatics.salepoint.no_filter": "No filter",
"gui.numismatics.salepoint.no_state": "Not configured",
"gui.numismatics.salepoint.no_target": "Target not found",
"gui.numismatics.salepoint.price": "Price/Unit: %s %s, %s¤",
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/base/client/rendering/IItemApplicableWidget.java b/common/src/main/java/dev/ithundxr/createnumismatics/base/client/rendering/IItemApplicableWidget.java
new file mode 100644
index 00000000..fb01429d
--- /dev/null
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/base/client/rendering/IItemApplicableWidget.java
@@ -0,0 +1,25 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.base.client.rendering;
+
+import net.minecraft.world.item.ItemStack;
+
+public interface IItemApplicableWidget {
+ void onItemApplied(ItemStack stack);
+}
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/base/client/rendering/ISalepointStateUpdatingWidget.java b/common/src/main/java/dev/ithundxr/createnumismatics/base/client/rendering/ISalepointStateUpdatingWidget.java
new file mode 100644
index 00000000..9b287e04
--- /dev/null
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/base/client/rendering/ISalepointStateUpdatingWidget.java
@@ -0,0 +1,25 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.base.client.rendering;
+
+import dev.ithundxr.createnumismatics.content.salepoint.states.ISalepointState;
+
+public interface ISalepointStateUpdatingWidget {
+ void updateState(ISalepointState> state);
+}
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointConfigMenu.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointConfigMenu.java
index bb8a877c..1a646dc1 100644
--- a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointConfigMenu.java
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointConfigMenu.java
@@ -61,6 +61,7 @@ public SalepointConfigMenu(MenuType> type, int id, Inventory inv, SalepointBlo
}
@Override
+ @SuppressWarnings("DataFlowIssue")
protected SalepointBlockEntity createOnClient(FriendlyByteBuf extraData) {
ClientLevel world = Minecraft.getInstance().level;
BlockEntity blockEntity = world.getBlockEntity(extraData.readBlockPos());
@@ -122,7 +123,7 @@ public boolean canTakeItemForPickAll(@NotNull ItemStack stack, Slot slot) {
}
@Override
- public void clicked(int slotId, int button, ClickType clickType, Player player) {
+ public void clicked(int slotId, int button, @NotNull ClickType clickType, Player player) {
ItemStack held = getCarried();
Inventory inventory = player.getInventory();
ISalepointState> salepointState = getSalepointState();
@@ -217,7 +218,7 @@ public void setSynchronizer(@NotNull ContainerSynchronizer synchronizer) {
super.setSynchronizer(synchronizer);
}
- protected @Nullable ISalepointState> getSalepointState() {
+ public @Nullable ISalepointState> getSalepointState() {
if (contentHolder.salepointState == null)
return null;
return contentHolder.salepointState.state();
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointConfigScreen.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointConfigScreen.java
index f23ac541..1f12c423 100644
--- a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointConfigScreen.java
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointConfigScreen.java
@@ -29,6 +29,8 @@
import com.simibubi.create.foundation.gui.widget.ScrollInput;
import com.simibubi.create.foundation.utility.Components;
import com.simibubi.create.foundation.utility.Couple;
+import dev.ithundxr.createnumismatics.base.client.rendering.IItemApplicableWidget;
+import dev.ithundxr.createnumismatics.base.client.rendering.ISalepointStateUpdatingWidget;
import dev.ithundxr.createnumismatics.config.NumismaticsConfig;
import dev.ithundxr.createnumismatics.content.backend.Coin;
import dev.ithundxr.createnumismatics.content.backend.behaviours.SliderStylePriceConfigurationPacket;
@@ -39,6 +41,10 @@
import dev.ithundxr.createnumismatics.registry.packets.ScrollSlotPacket;
import dev.ithundxr.createnumismatics.util.TextUtils;
import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.components.AbstractWidget;
+import net.minecraft.client.gui.components.Renderable;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
@@ -62,6 +68,8 @@ public class SalepointConfigScreen extends AbstractSimiContainerScreen extraAreas = Collections.emptyList();
public SalepointConfigScreen(SalepointConfigMenu container, Inventory inv, Component title) {
@@ -82,9 +90,7 @@ protected void init() {
int y = topPos;
trustListButton = new IconButton(x + 16, y + background.height - 24, AllIcons.I_VIEW_SCHEDULE);
- trustListButton.withCallback(() -> {
- menu.contentHolder.openTrustList();
- });
+ trustListButton.withCallback(() -> menu.contentHolder.openTrustList());
addRenderableWidget(trustListButton);
confirmButton = new IconButton(x + background.width - 33, y + background.height - 24, AllIcons.I_CONFIRM);
@@ -118,11 +124,19 @@ protected void init() {
ISalepointState> salepointState = getSalepointState();
if (salepointState != null)
- salepointState.createConfigWidgets(this::addRenderableWidget);
+ salepointState.createConfigWidgets(this::addRenderableWidgetOffset);
extraAreas = ImmutableList.of(new Rect2i(x + background.width, y + background.height - 68, 84, 84));
}
+ private T addRenderableWidgetOffset(T widget) {
+ if (widget instanceof AbstractWidget abstractWidget) {
+ abstractWidget.setX(abstractWidget.getX() + leftPos);
+ abstractWidget.setY(abstractWidget.getY() + topPos);
+ }
+ return addRenderableWidget(widget);
+ }
+
@Override
public List getExtraAreas() {
return extraAreas;
@@ -185,6 +199,50 @@ public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
return super.mouseScrolled(mouseX, mouseY, delta);
}
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (button == 0 && !menu.getCarried().isEmpty()) {
+ for (GuiEventListener widget : this.children()) {
+ if (widget.isMouseOver(mouseX, mouseY) && widget instanceof IItemApplicableWidget itemApplicableWidget) {
+ itemApplicableWidget.onItemApplied(menu.getCarried());
+ }
+ }
+ }
+
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+
+ @Override
+ public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) {
+ wasDragging = true;
+ return super.mouseDragged(mouseX, mouseY, button, dragX, dragY);
+ }
+
+ @Override
+ public boolean mouseReleased(double mouseX, double mouseY, int button) {
+ if (button == 0 && wasDragging && !menu.getCarried().isEmpty()) {
+ for (GuiEventListener widget : this.children()) {
+ if (widget.isMouseOver(mouseX, mouseY) && widget instanceof IItemApplicableWidget itemApplicableWidget) {
+ itemApplicableWidget.onItemApplied(menu.getCarried());
+ }
+ }
+ }
+
+ wasDragging = false;
+
+ return super.mouseReleased(mouseX, mouseY, button);
+ }
+
+ @Override
+ protected void containerTick() {
+ super.containerTick();
+ for (GuiEventListener widget : this.children()) {
+ if (widget instanceof ISalepointStateUpdatingWidget salepointStateUpdatingWidget) {
+ salepointStateUpdatingWidget.updateState(getSalepointState());
+ }
+ }
+ }
+
@Override
public void removed() {
NumismaticsPackets.PACKETS.send(new SliderStylePriceConfigurationPacket(menu.contentHolder));
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointPurchaseScreen.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointPurchaseScreen.java
index 2f4b888a..bfbe5c0a 100644
--- a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointPurchaseScreen.java
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/SalepointPurchaseScreen.java
@@ -40,6 +40,10 @@
import dev.ithundxr.createnumismatics.registry.packets.SalepointPurchasePacket;
import dev.ithundxr.createnumismatics.util.TextUtils;
import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.components.AbstractWidget;
+import net.minecraft.client.gui.components.Renderable;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.network.chat.Component;
import net.minecraft.util.FormattedCharSequence;
@@ -107,12 +111,20 @@ protected void init() {
ISalepointState> salepointState = getSalepointState();
if (salepointState != null)
- salepointState.createPurchaseWidgets(this::addRenderableWidget);
+ salepointState.createPurchaseWidgets(this::addRenderableWidgetOffset);
extraAreas = ImmutableList.of(new Rect2i(x + background.width, y + background.height - 68, 84, 84));
updateAction();
}
+ private T addRenderableWidgetOffset(T widget) {
+ if (widget instanceof AbstractWidget abstractWidget) {
+ abstractWidget.setX(abstractWidget.getX() + leftPos);
+ abstractWidget.setY(abstractWidget.getY() + topPos);
+ }
+ return addRenderableWidget(widget);
+ }
+
@Override
public List getExtraAreas() {
return extraAreas;
@@ -233,7 +245,6 @@ private void onAction() {
}
}
- @SuppressWarnings("DataFlowIssue")
private void updateAction() {
action = Action.GO;
Component alert = Components.translatable("gui.numismatics.salepoint.go");
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/behaviours/FluidSalepointTargetBehaviour.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/behaviours/FluidSalepointTargetBehaviour.java
new file mode 100644
index 00000000..688c58fd
--- /dev/null
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/behaviours/FluidSalepointTargetBehaviour.java
@@ -0,0 +1,42 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.content.salepoint.behaviours;
+
+import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
+import dev.ithundxr.createnumismatics.content.salepoint.states.ISalepointState;
+import dev.ithundxr.createnumismatics.content.salepoint.states.SalepointTypes;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class FluidSalepointTargetBehaviour extends SalepointTargetBehaviour {
+ public FluidSalepointTargetBehaviour(SmartBlockEntity be) {
+ super(be);
+ }
+
+ @Override
+ protected @Nullable ISalepointState> tryBindToSalepointInternal() {
+ return SalepointTypes.FLUID.create();
+ }
+
+ @Override
+ protected @NotNull Class getContentType() {
+ return MultiloaderFluidStack.class;
+ }
+}
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/behaviours/ItemSalepointTargetBehaviour.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/behaviours/ItemSalepointTargetBehaviour.java
index ea3fc790..e970a964 100644
--- a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/behaviours/ItemSalepointTargetBehaviour.java
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/behaviours/ItemSalepointTargetBehaviour.java
@@ -22,6 +22,7 @@
import dev.ithundxr.createnumismatics.content.salepoint.states.ISalepointState;
import dev.ithundxr.createnumismatics.content.salepoint.states.SalepointTypes;
import net.minecraft.world.item.ItemStack;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class ItemSalepointTargetBehaviour extends SalepointTargetBehaviour {
@@ -36,7 +37,7 @@ public ItemSalepointTargetBehaviour(SmartBlockEntity be) {
}
@Override
- protected Class getContentType() {
+ protected @NotNull Class getContentType() {
return ItemStack.class;
}
}
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/containers/InvalidatableAbstractBuffer.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/containers/InvalidatableAbstractBuffer.java
index 1021810a..73d2c160 100644
--- a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/containers/InvalidatableAbstractBuffer.java
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/containers/InvalidatableAbstractBuffer.java
@@ -68,4 +68,14 @@ public final int copyToBuffer(C source, boolean simulate) {
* @return The number of elements copied.
*/
protected abstract int copyToBufferInternal(C source, boolean simulate);
+
+ /**
+ * Remove as much as possible of the source element from the buffer, up to maxAmount, i.e. ignoring source's size.
+ * Note: It is the caller's responsibility to modify the source,
+ * the implementor MUST NOT modify the source argument
+ * @param source The source to remove.
+ * @param simulate If true, the caller MUST NOT actually remove the element from its buffer
+ * @return The number of elements removed.
+ */
+ protected abstract int removeFromBufferInternal(C source, boolean simulate, final int maxAmount);
}
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/containers/InvalidatableWrappingItemBuffer.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/containers/InvalidatableWrappingItemBuffer.java
index 3466448d..094dde35 100644
--- a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/containers/InvalidatableWrappingItemBuffer.java
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/containers/InvalidatableWrappingItemBuffer.java
@@ -67,6 +67,7 @@ public boolean canPlaceItem(int index, @NotNull ItemStack stack) {
return 0;
}
+ @Override
protected int removeFromBufferInternal(ItemStack source, boolean simulate, final int maxAmount) {
SimpleContainer buffer = this.buffer;
if (simulate) { // lazy but it works
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/FluidSalepointState.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/FluidSalepointState.java
new file mode 100644
index 00000000..c44a7bf6
--- /dev/null
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/FluidSalepointState.java
@@ -0,0 +1,233 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.content.salepoint.states;
+
+import com.simibubi.create.foundation.utility.Components;
+import dev.architectury.injectables.annotations.ExpectPlatform;
+import dev.ithundxr.createnumismatics.content.backend.ReasonHolder;
+import dev.ithundxr.createnumismatics.content.salepoint.behaviours.SalepointTargetBehaviour;
+import dev.ithundxr.createnumismatics.content.salepoint.widgets.SalepointFluidConfigWidget;
+import dev.ithundxr.createnumismatics.content.salepoint.widgets.SalepointFluidDisplayWidget;
+import dev.ithundxr.createnumismatics.multiloader.fluid.FluidUnits;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.MethodsReturnNonnullByDefault;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.level.Level;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.util.List;
+import java.util.UUID;
+
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+public abstract class FluidSalepointState implements ISalepointState {
+
+ private UUID uuid;
+ private @NotNull MultiloaderFluidStack filter = MultiloaderFluidStack.EMPTY;
+ private @Nullable Runnable changedCallback;
+
+ @ExpectPlatform
+ public static FluidSalepointState create() {
+ throw new AssertionError();
+ }
+
+ @Override
+ public void init() {
+ uuid = UUID.randomUUID();
+ }
+
+ @Override
+ public final SalepointTypes getType() {
+ return SalepointTypes.FLUID;
+ }
+
+ @Override
+ public final UUID getId() {
+ return uuid;
+ }
+
+ @Override
+ public final boolean canChangeFilterTo(MultiloaderFluidStack filter) {
+ return (this.filter.isFluidEqual(filter) && !filter.isEmpty()) || canChangeFilterToInternal(filter);
+ }
+
+ protected abstract boolean canChangeFilterToInternal(MultiloaderFluidStack filter);
+
+ @Override
+ public final boolean setFilter(MultiloaderFluidStack filter, Level salepointLevel, BlockPos salepointPos, @Nullable Player player) {
+ if (!canChangeFilterTo(filter))
+ return false;
+
+ setFilterInternal(filter, salepointLevel, salepointPos, player);
+ this.filter = filter.copy();
+
+ setChanged();
+
+ return true;
+ }
+
+ protected abstract void setFilterInternal(MultiloaderFluidStack filter, Level salepointLevel, BlockPos salepointPos, @Nullable Player player);
+
+ @Override
+ public final MultiloaderFluidStack getFilter() {
+ return filter.copy();
+ }
+
+ @Override
+ public boolean filterMatches(MultiloaderFluidStack object) {
+ return filter.isFluidEqual(object);
+ }
+
+ @Override
+ public final CompoundTag save() {
+ CompoundTag tag = new CompoundTag();
+ tag.putString("id", getType().getId());
+ tag.putUUID("UUID", uuid);
+
+ saveInternal(tag);
+
+ if (!filter.isEmpty())
+ tag.put("Filter", filter.writeToNBT(new CompoundTag()));
+
+ return tag;
+ }
+
+ protected abstract void saveInternal(CompoundTag tag);
+
+ @Override
+ public final void load(CompoundTag tag) {
+ uuid = tag.getUUID("UUID");
+
+ loadInternal(tag);
+
+ if (tag.contains("Filter", CompoundTag.TAG_COMPOUND))
+ filter = MultiloaderFluidStack.loadFluidStackFromNBT(tag.getCompound("Filter"));
+ else
+ filter = MultiloaderFluidStack.EMPTY;
+ }
+
+ protected abstract void loadInternal(CompoundTag tag);
+
+ @Override
+ public final boolean isValidForPurchase(Level level, BlockPos targetedPos, ReasonHolder reasonHolder) {
+ SalepointTargetBehaviour behaviour = getBehaviour(level, targetedPos);
+ return isValidForPurchase(behaviour, reasonHolder);
+ }
+
+ protected abstract boolean hasBufferFluidForPurchase();
+
+ protected abstract List removeBufferFluidForPurchase();
+
+ private boolean isValidForPurchase(@Nullable SalepointTargetBehaviour behaviour, ReasonHolder reasonHolder) {
+ if (behaviour == null) {
+ reasonHolder.setMessage(Components.translatable("gui.numismatics.salepoint.no_target"));
+ return false;
+ }
+
+ if (!behaviour.isUnderControl(this)) {
+ reasonHolder.setMessage(Components.translatable("gui.numismatics.salepoint.target_not_controlled"));
+ return false;
+ }
+
+ if (filter.isEmpty()) {
+ reasonHolder.setMessage(Components.translatable("gui.numismatics.salepoint.no_filter"));
+ return false;
+ }
+
+ if (!hasBufferFluidForPurchase()) {
+ reasonHolder.setMessage(Components.translatable("gui.numismatics.vendor.out_of_stock"));
+ return false;
+ }
+
+ if (!behaviour.hasSpaceFor(filter.copy())) {
+ reasonHolder.setMessage(Components.translatable("gui.numismatics.salepoint.insufficient_space"));
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public final boolean doPurchase(Level level, BlockPos targetedPos, ReasonHolder reasonHolder) {
+ SalepointTargetBehaviour behaviour = getBehaviour(level, targetedPos);
+ if (behaviour == null) {
+ reasonHolder.setMessage(Components.translatable("gui.numismatics.salepoint.no_target"));
+ return false;
+ }
+
+ if (!isValidForPurchase(behaviour, reasonHolder))
+ return false;
+
+ if (!behaviour.doPurchase(filter.copy(), this::removeBufferFluidForPurchase)) {
+ reasonHolder.setMessage(Components.translatable("gui.numismatics.salepoint.target_failed_purchase"));
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public final void ensureUnderControl(Level level, BlockPos targetedPos) {
+ SalepointTargetBehaviour behaviour = getBehaviour(level, targetedPos);
+ if (behaviour == null)
+ return;
+
+ behaviour.ensureUnderControl(this);
+ }
+
+ @Override
+ public final void relinquishControl(Level level, BlockPos targetedPos) {
+ SalepointTargetBehaviour behaviour = getBehaviour(level, targetedPos);
+ if (behaviour == null)
+ return;
+
+ behaviour.relinquishControl(this);
+ }
+
+ @Override
+ public void setChangedCallback(Runnable callback) {
+ this.changedCallback = callback;
+ }
+
+ protected void setChanged() {
+ if (changedCallback != null)
+ changedCallback.run();
+ }
+
+ @Override
+ @Environment(EnvType.CLIENT)
+ public void createConfigWidgets(WidgetConsumer widgetConsumer) {
+ widgetConsumer.addRenderableWidget(new SalepointFluidConfigWidget(100, 54, this));
+ }
+
+ @Override
+ public void createPurchaseWidgets(WidgetConsumer widgetConsumer) {
+ widgetConsumer.addRenderableWidget(new SalepointFluidDisplayWidget(68, 55, this));
+ }
+
+ public static long getFilterCapacity() {
+ return FluidUnits.bucket() * 4;
+ }
+}
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/ItemSalepointState.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/ItemSalepointState.java
index e9b3ad73..8a35602f 100644
--- a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/ItemSalepointState.java
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/ItemSalepointState.java
@@ -70,9 +70,7 @@ public boolean canPlaceItem(int index, ItemStack stack) {
private @Nullable Runnable changedCallback;
ItemSalepointState() {
- buffer.addListener($ -> {
- setChanged();
- });
+ buffer.addListener($ -> setChanged());
}
@ExpectPlatform
@@ -139,7 +137,7 @@ public boolean setFilter(ItemStack filter, Level salepointLevel, BlockPos salepo
}
setChanged();
- return false;
+ return true;
}
@Override
@@ -390,6 +388,11 @@ private boolean isValidForPurchase(@Nullable SalepointTargetBehaviour
return false;
}
+ if (filter.isEmpty()) {
+ reasonHolder.setMessage(Components.translatable("gui.numismatics.salepoint.no_filter"));
+ return false;
+ }
+
if (!hasBufferItemsForPurchase()) {
reasonHolder.setMessage(Components.translatable("gui.numismatics.vendor.out_of_stock"));
return false;
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/SalepointTypes.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/SalepointTypes.java
index 25736d3d..e462f29c 100644
--- a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/SalepointTypes.java
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/SalepointTypes.java
@@ -18,6 +18,7 @@
package dev.ithundxr.createnumismatics.content.salepoint.states;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Contract;
@@ -26,6 +27,7 @@
public enum SalepointTypes {
ITEM(ItemSalepointState::new, ItemStack.class),
+ FLUID(FluidSalepointState::create, MultiloaderFluidStack.class),
;
@NotNull
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/SalepointFluidConfigWidget.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/SalepointFluidConfigWidget.java
new file mode 100644
index 00000000..45a85ece
--- /dev/null
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/SalepointFluidConfigWidget.java
@@ -0,0 +1,138 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.content.salepoint.widgets;
+
+import com.simibubi.create.AllKeys;
+import com.simibubi.create.AllSoundEvents;
+import com.simibubi.create.foundation.utility.Components;
+import dev.architectury.injectables.annotations.ExpectPlatform;
+import dev.ithundxr.createnumismatics.base.client.rendering.IItemApplicableWidget;
+import dev.ithundxr.createnumismatics.content.salepoint.states.FluidSalepointState;
+import dev.ithundxr.createnumismatics.multiloader.fluid.FluidUnits;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import dev.ithundxr.createnumismatics.registry.NumismaticsGuiTextures;
+import dev.ithundxr.createnumismatics.registry.NumismaticsPackets;
+import dev.ithundxr.createnumismatics.registry.packets.SalepointFluidFilterPacket;
+import dev.ithundxr.createnumismatics.util.TextUtils;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.resources.sounds.SimpleSoundInstance;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.item.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+public class SalepointFluidConfigWidget extends SalepointFluidDisplayWidget implements IItemApplicableWidget {
+
+ private boolean soundPlayed;
+
+ public SalepointFluidConfigWidget(int x, int y, @NotNull FluidSalepointState state) {
+ super(x, y, state);
+ }
+
+ @Override
+ protected NumismaticsGuiTextures getBackground() {
+ return NumismaticsGuiTextures.SALEPOINT_CONFIG_FLUID_BG;
+ }
+
+ @Override
+ protected NumismaticsGuiTextures getForeground() {
+ return NumismaticsGuiTextures.SALEPOINT_CONFIG_FLUID_FG;
+ }
+
+ @Override
+ public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
+ MultiloaderFluidStack filter = state.getFilter();
+ if (filter.isEmpty())
+ return false;
+
+ int offset = delta > 0 ? 1 : -1;
+
+ if (AllKeys.shiftDown() && AllKeys.ctrlDown())
+ //noinspection DataFlowIssue
+ offset *= 1;
+ else if (AllKeys.ctrlDown())
+ offset *= 10;
+ else if (AllKeys.shiftDown())
+ offset *= 1000;
+ else
+ offset *= 100;
+
+ offset = (int) ((long) offset * FluidUnits.bucket() / 1000);
+
+ long oldAmount = filter.getAmount();
+ long min = 0;
+ long max = FluidSalepointState.getFilterCapacity();
+ long amount = Math.max(min, Math.min(oldAmount + offset, max));
+ if (oldAmount != amount) {
+ NumismaticsPackets.PACKETS.send(new SalepointFluidFilterPacket(filter.copy().setAmount(amount)));
+ if (!soundPlayed) {
+ Minecraft.getInstance()
+ .getSoundManager()
+ .play(SimpleSoundInstance.forUI(AllSoundEvents.SCROLL_VALUE.getMainEvent(),
+ 1.5f + 0.1f * (amount - min) / (max - min)));
+ soundPlayed = true;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ soundPlayed = false;
+ }
+
+ @Override
+ public void onItemApplied(ItemStack stack) {
+ MultiloaderFluidStack fluidStack = getFluidFrom(stack);
+ if (fluidStack != null) {
+ NumismaticsPackets.PACKETS.send(new SalepointFluidFilterPacket(fluidStack));
+ this.playDownSound(Minecraft.getInstance().getSoundManager());
+ }
+ }
+
+ @ExpectPlatform
+ @Environment(EnvType.CLIENT)
+ protected static @Nullable MultiloaderFluidStack getFluidFrom(@NotNull ItemStack stack) {
+ throw new AssertionError();
+ }
+
+ @Override
+ public List getToolTip() {
+ MultiloaderFluidStack filter = state.getFilter();
+ if (filter.isEmpty())
+ return List.of(
+ Components.translatable("gui.numismatics.salepoint.fluid_filter_empty.0"),
+ Components.translatable("gui.numismatics.salepoint.fluid_filter_empty.1")
+ );
+
+ return List.of(
+ filter.getDisplayName(),
+ Components.literal(TextUtils.formatFluid(filter.getAmount())),
+ Components.translatable("create.gui.scrollInput.scrollToAdjustAmount"),
+ Components.translatable("create.gui.scrollInput.shiftScrollsFaster")
+ );
+ }
+}
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/SalepointFluidDisplayWidget.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/SalepointFluidDisplayWidget.java
new file mode 100644
index 00000000..b337d6ad
--- /dev/null
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/SalepointFluidDisplayWidget.java
@@ -0,0 +1,178 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.content.salepoint.widgets;
+
+import com.mojang.blaze3d.platform.Lighting;
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.simibubi.create.foundation.gui.CustomLightingSettings;
+import com.simibubi.create.foundation.gui.ILightingSettings;
+import com.simibubi.create.foundation.gui.widget.AbstractSimiWidget;
+import com.simibubi.create.foundation.utility.Components;
+import dev.architectury.injectables.annotations.ExpectPlatform;
+import dev.ithundxr.createnumismatics.base.client.rendering.ISalepointStateUpdatingWidget;
+import dev.ithundxr.createnumismatics.content.salepoint.states.FluidSalepointState;
+import dev.ithundxr.createnumismatics.content.salepoint.states.ISalepointState;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import dev.ithundxr.createnumismatics.registry.NumismaticsGuiTextures;
+import dev.ithundxr.createnumismatics.util.TextUtils;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.renderer.LightTexture;
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.network.chat.Component;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class SalepointFluidDisplayWidget extends AbstractSimiWidget implements ISalepointStateUpdatingWidget {
+
+ public static final ILightingSettings DEFAULT_LIGHTING = CustomLightingSettings.builder()
+ .firstLightRotation(12.5f, 135.0f)
+ .secondLightRotation(-20.0f, 140.0f)
+ .build();
+
+ protected @NotNull FluidSalepointState state;
+
+ public SalepointFluidDisplayWidget(int x, int y, @NotNull FluidSalepointState state) {
+ super(x, y, 28, 28);
+ this.state = state;
+ }
+
+ protected NumismaticsGuiTextures getBackground() {
+ return NumismaticsGuiTextures.SALEPOINT_PURCHASE_FLUID_BG;
+ }
+
+ protected NumismaticsGuiTextures getForeground() {
+ return NumismaticsGuiTextures.SALEPOINT_PURCHASE_FLUID_FG;
+ }
+
+ @Override
+ protected void doRender(@NotNull GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
+ int x = getX();
+ int y = getY();
+ PoseStack ms = graphics.pose();
+
+ getBackground().render(graphics, x, y);
+
+ MultiloaderFluidStack filter = state.getFilter();
+ if (!filter.isEmpty()) {
+ ms.pushPose();
+ ms.translate(0, 0, 10);
+
+ boolean top = filter.isLighterThanAir();
+
+ float level = (float) filter.getAmount() / (float) FluidSalepointState.getFilterCapacity();
+
+ float xMin = x;
+ float xMax = x + getWidth();
+ float yMin = y + (1.0f - level) * (getHeight() - 4) + 2;
+ float yMax = y + getHeight() - 2;
+
+ if (top) {
+ yMin = y + 2;
+ yMax = y + level * (getHeight() - 4) - 2;
+ }
+
+ float zMin = 1;
+ float zMax = 9;
+
+ xMin /= 16f;
+ xMax /= 16f;
+ yMin /= 16f;
+ yMax /= 16f;
+ zMin /= 16f;
+ zMax /= 16f;
+
+ {
+ DEFAULT_LIGHTING.applyLighting();
+
+ ms.pushPose();
+ ms.scale(16, 16, 16);
+
+ float xOffset = (xMin + xMax) / 2;
+ float yOffset = (yMin + yMax) / 2;
+ float zOffset = (zMin + zMax) / 2;
+
+ ms.translate(xOffset, yOffset, zOffset);
+
+ xMin -= xOffset;
+ xMax -= xOffset;
+ yMin -= yOffset;
+ yMax -= yOffset;
+ zMin -= zOffset;
+ zMax -= zOffset;
+
+ DEFAULT_LIGHTING.applyLighting();
+
+ renderFluidBox(filter, xMin, yMin, zMin, xMax, yMax, zMax, graphics.bufferSource(), ms, LightTexture.FULL_BRIGHT, false);
+ graphics.bufferSource().endBatch();
+
+ ms.popPose();
+
+ Lighting.setupFor3DItems();
+ }
+
+ ms.popPose();
+ }
+
+ {
+ ms.pushPose();
+ ms.translate(0, 0, 20);
+
+ getForeground().render(graphics, x, y);
+
+ ms.popPose();
+ }
+ }
+
+ @Override
+ public void updateState(ISalepointState> state) {
+ if (state instanceof FluidSalepointState fluidSalepointState)
+ this.state = fluidSalepointState;
+ }
+
+ @Override
+ protected boolean clicked(double mouseX, double mouseY) {
+ return false;
+ }
+
+ @Override
+ public List getToolTip() {
+ MultiloaderFluidStack filter = state.getFilter();
+ if (filter.isEmpty())
+ return List.of(
+ Components.translatable("gui.numismatics.salepoint.fluid_empty")
+ );
+
+ return List.of(
+ filter.getDisplayName(),
+ Components.literal(TextUtils.formatFluid(filter.getAmount()))
+ );
+ }
+
+ @ExpectPlatform
+ @Environment(EnvType.CLIENT)
+ @SuppressWarnings("SameParameterValue")
+ protected static void renderFluidBox(@NotNull MultiloaderFluidStack fluidStack, float xMin, float yMin, float zMin,
+ float xMax, float yMax, float zMax, @NotNull MultiBufferSource buffer,
+ @NotNull PoseStack ms, int light, boolean renderBottom) {
+ throw new AssertionError();
+ }
+}
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/FluidUnits.java b/common/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/FluidUnits.java
new file mode 100644
index 00000000..cd6db110
--- /dev/null
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/FluidUnits.java
@@ -0,0 +1,28 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.multiloader.fluid;
+
+import dev.architectury.injectables.annotations.ExpectPlatform;
+
+public class FluidUnits {
+ @ExpectPlatform
+ public static long bucket() {
+ throw new AssertionError();
+ }
+}
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/MultiloaderFluidStack.java b/common/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/MultiloaderFluidStack.java
new file mode 100644
index 00000000..bcee4017
--- /dev/null
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/MultiloaderFluidStack.java
@@ -0,0 +1,162 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.multiloader.fluid;
+
+import com.mojang.serialization.Codec;
+import dev.architectury.injectables.annotations.ExpectPlatform;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.material.Fluid;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class MultiloaderFluidStack {
+
+ @ExpectPlatform
+ private static Codec makeCodec() {
+ throw new AssertionError();
+ }
+
+ @ExpectPlatform
+ private static MultiloaderFluidStack makeEmpty() {
+ throw new AssertionError();
+ }
+
+ public static final Codec CODEC = makeCodec();
+
+ public static final MultiloaderFluidStack EMPTY = makeEmpty();
+
+ public static MultiloaderFluidStack create(Fluid fluid, long amount) {
+ return create(fluid, amount, null);
+ }
+
+ @ExpectPlatform
+ public static MultiloaderFluidStack create(Fluid fluid, long amount, @Nullable CompoundTag nbt) {
+ throw new AssertionError();
+ }
+
+ public abstract MultiloaderFluidStack setAmount(long amount);
+
+ public void grow(long amount) {
+ setAmount(getAmount() + amount);
+ }
+
+ public abstract Fluid getFluid();
+
+ public abstract long getAmount();
+
+ public abstract boolean isEmpty();
+
+ public void shrink(int amount) {
+ setAmount(getAmount() - amount);
+ }
+
+ public void shrink(long amount) {
+ setAmount(getAmount() - amount);
+ }
+
+ /**
+ * Determines if the FluidIDs and NBT Tags are equal. This does not check amounts.
+ *
+ * @param other
+ * The FluidStack for comparison
+ * @return true if the Fluids (IDs and NBT Tags) are the same
+ */
+ public abstract boolean isFluidEqual(MultiloaderFluidStack other);
+
+ public abstract CompoundTag writeToNBT(CompoundTag nbt);
+
+ @ExpectPlatform
+ public static MultiloaderFluidStack loadFluidStackFromNBT(CompoundTag tag) {
+ throw new AssertionError();
+ }
+
+ public abstract void setTag(CompoundTag tag);
+
+ @Nullable
+ public abstract CompoundTag getTag();
+
+ public CompoundTag getOrCreateTag() {
+ if (getTag() == null) setTag(new CompoundTag());
+ return getTag();
+ }
+
+ public void removeChildTag(String key) {
+ if (getTag() == null) return;
+ getTag().remove(key);
+ }
+
+ public abstract Component getDisplayName();
+
+ public boolean hasTag() {
+ return getTag() != null;
+ }
+
+ @ExpectPlatform
+ public static MultiloaderFluidStack readFromPacket(FriendlyByteBuf buffer) {
+ throw new AssertionError();
+ }
+
+ public abstract FriendlyByteBuf writeToPacket(FriendlyByteBuf buffer);
+
+ public abstract MultiloaderFluidStack copy();
+
+ private boolean isFluidStackTagEqual(MultiloaderFluidStack other) {
+ CompoundTag tag = getTag();
+ CompoundTag other$tag = other.getTag();
+ return tag == null ? other$tag == null : other$tag != null && tag.equals(other$tag);
+ }
+
+ /**
+ * Determines if the NBT Tags are equal. Useful if the FluidIDs are known to be equal.
+ */
+ public static boolean areFluidStackTagsEqual(@NotNull MultiloaderFluidStack stack1, @NotNull MultiloaderFluidStack stack2) {
+ return stack1.isFluidStackTagEqual(stack2);
+ }
+
+ /**
+ * Determines if the Fluids are equal and this stack is larger.
+ *
+ * @return true if this FluidStack contains the other FluidStack (same fluid and >= amount)
+ */
+ public abstract boolean containsFluid(@NotNull MultiloaderFluidStack other);
+
+ /**
+ * Determines if the FluidIDs, Amounts, and NBT Tags are all equal.
+ *
+ * @param other
+ * - the FluidStack for comparison
+ * @return true if the two FluidStacks are exactly the same
+ */
+ public abstract boolean isFluidStackIdentical(MultiloaderFluidStack other);
+
+ /**
+ * Determines if the FluidIDs and NBT Tags are equal compared to a registered container
+ * ItemStack. This does not check amounts.
+ *
+ * @param other
+ * The ItemStack for comparison
+ * @return true if the Fluids (IDs and NBT Tags) are the same
+ */
+ public abstract boolean isFluidEqual(@NotNull ItemStack other);
+
+ public abstract boolean isLighterThanAir();
+}
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsPackets.java b/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsPackets.java
index 1546a91b..ec3e306a 100644
--- a/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsPackets.java
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsPackets.java
@@ -42,6 +42,7 @@ public class NumismaticsPackets {
.c2s(ScrollSlotPacket.class, ScrollSlotPacket::new)
.c2s(GhostItemSubmitPacket.class, GhostItemSubmitPacket::new)
.c2s(SalepointPurchasePacket.class, SalepointPurchasePacket::new)
+ .c2s(SalepointFluidFilterPacket.class, SalepointFluidFilterPacket::new)
.s2c(BankAccountLabelPacket.class, BankAccountLabelPacket::new)
.s2c(VarIntContainerSetDataPacket.class, VarIntContainerSetDataPacket::new)
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/registry/packets/SalepointFluidFilterPacket.java b/common/src/main/java/dev/ithundxr/createnumismatics/registry/packets/SalepointFluidFilterPacket.java
new file mode 100644
index 00000000..472902e4
--- /dev/null
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/registry/packets/SalepointFluidFilterPacket.java
@@ -0,0 +1,48 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.registry.packets;
+
+import dev.ithundxr.createnumismatics.content.salepoint.SalepointConfigMenu;
+import dev.ithundxr.createnumismatics.content.salepoint.states.FluidSalepointState;
+import dev.ithundxr.createnumismatics.multiloader.C2SPacket;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.server.level.ServerPlayer;
+
+public record SalepointFluidFilterPacket(MultiloaderFluidStack filter) implements C2SPacket {
+
+ public SalepointFluidFilterPacket(FriendlyByteBuf buf) {
+ this(MultiloaderFluidStack.readFromPacket(buf));
+ }
+
+ @Override
+ public void write(FriendlyByteBuf buffer) {
+ filter.writeToPacket(buffer);
+ }
+
+ @Override
+ @SuppressWarnings("DataFlowIssue")
+ public void handle(ServerPlayer sender) {
+ if (sender.containerMenu instanceof SalepointConfigMenu salepointConfigMenu) {
+ if (salepointConfigMenu.getSalepointState() instanceof FluidSalepointState fluidSalepointState) {
+ fluidSalepointState.setFilter(filter, salepointConfigMenu.contentHolder.getLevel(), salepointConfigMenu.contentHolder.getBlockPos(), sender);
+ }
+ }
+ }
+}
diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/util/TextUtils.java b/common/src/main/java/dev/ithundxr/createnumismatics/util/TextUtils.java
index b813617e..4a49f093 100644
--- a/common/src/main/java/dev/ithundxr/createnumismatics/util/TextUtils.java
+++ b/common/src/main/java/dev/ithundxr/createnumismatics/util/TextUtils.java
@@ -20,6 +20,7 @@
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.foundation.utility.Components;
+import dev.architectury.injectables.annotations.ExpectPlatform;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.renderer.MultiBufferSource;
@@ -138,4 +139,9 @@ public static boolean isLeftToRight() {
.toLowerCase(Locale.ROOT)
.equals("true");
}
+
+ @ExpectPlatform
+ public static String formatFluid(long amount) {
+ throw new AssertionError();
+ }
}
diff --git a/common/src/main/resources/assets/numismatics/lang/default/interface.json b/common/src/main/resources/assets/numismatics/lang/default/interface.json
index 1840a44c..765913de 100644
--- a/common/src/main/resources/assets/numismatics/lang/default/interface.json
+++ b/common/src/main/resources/assets/numismatics/lang/default/interface.json
@@ -21,6 +21,9 @@
"gui.numismatics.salepoint.price": "Price/Unit: %s %s, %s¤",
"gui.numismatics.salepoint.count": "Units",
+ "gui.numismatics.salepoint.fluid_filter_empty.0": "Fluid Filter",
+ "gui.numismatics.salepoint.fluid_filter_empty.1": "Click with a fluid-holding item such as a bucket or a bottle",
+ "gui.numismatics.salepoint.fluid_empty": "Empty",
"gui.numismatics.salepoint.go": "Start transaction",
"gui.numismatics.salepoint.cancel": "Cancel transaction",
@@ -30,6 +33,7 @@
"gui.numismatics.salepoint.invalid_state": "Invalid state",
"gui.numismatics.salepoint.no_target": "Target not found",
"gui.numismatics.salepoint.target_not_controlled": "Target not under salepoint control",
+ "gui.numismatics.salepoint.no_filter": "No filter",
"gui.numismatics.salepoint.insufficient_space": "Target is full",
"gui.numismatics.salepoint.target_failed_purchase": "Target could not execute the purchase",
@@ -66,5 +70,139 @@
"error.numismatics.card.account_not_found": "Account not found",
"error.numismatics.card.not_authorized": "Not authorized",
"error.numismatics.authorized_card.limit_reached": "Spending limit reached",
- "error.numismatics.authorized_card.account_not_found": "Sub Account not found"
+ "error.numismatics.authorized_card.account_not_found": "Sub Account not found",
+ "advancement.numismatics.is_this_legal": "Is This Legal?",
+ "advancement.numismatics.is_this_legal.desc": "Buy coins for less than they are worth\n§7(Hidden Advancement)",
+ "advancement.numismatics.money_laundering": "Money Laundering",
+ "advancement.numismatics.money_laundering.desc": "Buy coins in a vendor\n§7(Hidden Advancement)",
+ "advancement.numismatics.questionable_investment": "Questionable Investment",
+ "advancement.numismatics.questionable_investment.desc": "Buy coins for more than they are worth\n§7(Hidden Advancement)",
+ "advancement.numismatics.root": "Welcome to Numismatics",
+ "advancement.numismatics.root.desc": "Here Be Riches",
+ "block.numismatics.andesite_depositor": "Andesite Depositor",
+ "block.numismatics.andesite_depositor.tooltip": "ANDESITE DEPOSITOR",
+ "block.numismatics.andesite_depositor.tooltip.price": "Price: %s %s (%s¤)",
+ "block.numismatics.andesite_depositor.tooltip.summary": "_Shift click_ the _top_ to configure, _right click_ to pay the given amount either with either the card or coins in your hand, at which point a redstone pulse is emmited.",
+ "block.numismatics.bank_terminal": "Bank Terminal",
+ "block.numismatics.bank_terminal.tooltip": "BANK TERMINAL",
+ "block.numismatics.bank_terminal.tooltip.summary": "Allows you to access your own bank account or any account you are _Trusted_ with.",
+ "block.numismatics.blaze_banker": "Blaze Banker",
+ "block.numismatics.blaze_banker.tooltip": "BLAZE BANKER",
+ "block.numismatics.blaze_banker.tooltip.summary": "Creates a bank account in the Banker's name, useful for sharing an account with multiple people. You can assign a _Bank Card_ to the banker by placing it in the appropriate slot.",
+ "block.numismatics.brass_depositor": "Brass Depositor",
+ "block.numismatics.brass_depositor.tooltip": "BRASS DEPOSITOR",
+ "block.numismatics.brass_depositor.tooltip.price": "Price: %s %s, %s¤",
+ "block.numismatics.brass_depositor.tooltip.summary": "_Shift click_ the _top_ to configure, _right click_ to pay the given amount either with either the card in your hand or the coins in your inventory, at which point a redstone pulse is emmited.",
+ "block.numismatics.creative_vendor": "Creative Vendor",
+ "block.numismatics.creative_vendor.tooltip": "CREATIVE VENDOR",
+ "block.numismatics.creative_vendor.tooltip.summary": "_Creative_ version of the vendor, once configured will never fill up or run out of stock.",
+ "block.numismatics.salepoint": "Salepoint",
+ "block.numismatics.salepoint.tooltip.clear": "Cleared interface selection",
+ "block.numismatics.salepoint.tooltip.missing": "Right-click the targeted interface first",
+ "block.numismatics.salepoint.tooltip.not_found": "Targeted interface not found",
+ "block.numismatics.salepoint.tooltip.set": "Interface selected",
+ "block.numismatics.salepoint.tooltip.success": "Successfully bound to targeted interface",
+ "block.numismatics.salepoint.tooltip.too_far": "Targeted interface is too far from here",
+ "block.numismatics.vendor": "Vendor",
+ "block.numismatics.vendor.tooltip": "VENDOR",
+ "block.numismatics.vendor.tooltip.mode": "Mode",
+ "block.numismatics.vendor.tooltip.price": "For: %s %s, %s¤",
+ "block.numismatics.vendor.tooltip.stock": "Stock",
+ "block.numismatics.vendor.tooltip.summary": "Allows for the trading of items through player made shops. _Right click_ to trade, or _shift click_ to configure it.",
+ "block.numismatics.vendor.tooltip.trade_item": "Item to Trade",
+ "item.numismatics.authorized_bank_card.tooltip": "AUTHORIZED BANK CARD",
+ "item.numismatics.authorized_bank_card.tooltip.summary": "Put in Bank Terminal _Sub Account menu_ to bind, _shift click_ to clear. Allows providing limited Bank Account access to trusted players or automation. Can be placed in a _Vendor/Depositor_ to automatically collect the coins and add them to the bound account.",
+ "item.numismatics.authorized_card.tooltip.bound.no_label": "Sub Account: Unknown",
+ "item.numismatics.authorized_card.tooltip.bound.with_label": "Sub Account: %s",
+ "item.numismatics.authorized_card.tooltip.hold_shift": "Hold sneak to clear. Bind in a Bank Terminal",
+ "item.numismatics.bank_card.tooltip": "BANK CARD",
+ "item.numismatics.bank_card.tooltip.summary": "_Right Click_ to bind, _shift click_ to clear. Allows access to the bound Bank Account when used in a _Bank Terminal_, or can be placed in a _Vendor/Depositor_ to automatically collect the coins and add them to the bound account.",
+ "item.numismatics.banking_guide": "Banking Guide",
+ "item.numismatics.banking_guide.tooltip": "BANKING GUIDE",
+ "item.numismatics.banking_guide.tooltip.summary": "Use on a _Blaze Burner_ to convert it into a _Blaze Banker_.",
+ "item.numismatics.bevel": "Bevel",
+ "item.numismatics.bevel.plural": "Bevels",
+ "item.numismatics.black_authorized_card": "Black Authorized Card",
+ "item.numismatics.black_card": "Black Bank Card",
+ "item.numismatics.black_id_card": "Black ID Card",
+ "item.numismatics.blue_authorized_card": "Blue Authorized Card",
+ "item.numismatics.blue_card": "Blue Bank Card",
+ "item.numismatics.blue_id_card": "Blue ID Card",
+ "item.numismatics.brown_authorized_card": "Brown Authorized Card",
+ "item.numismatics.brown_card": "Brown Bank Card",
+ "item.numismatics.brown_id_card": "Brown ID Card",
+ "item.numismatics.card.tooltip.blank": "Blank",
+ "item.numismatics.card.tooltip.bound": "Bound",
+ "item.numismatics.card.tooltip.bound.to": "Bound to: %s",
+ "item.numismatics.cog": "Cog",
+ "item.numismatics.cog.plural": "Cogs",
+ "item.numismatics.coin.tooltip.count": "Count: %s %s",
+ "item.numismatics.coin.tooltip.value": "Value: %s %s (%s¤)",
+ "item.numismatics.coin.tooltip.value.basic": "Value: %s¤",
+ "item.numismatics.crown": "Crown",
+ "item.numismatics.crown.plural": "Crowns",
+ "item.numismatics.cyan_authorized_card": "Cyan Authorized Card",
+ "item.numismatics.cyan_card": "Cyan Bank Card",
+ "item.numismatics.cyan_id_card": "Cyan ID Card",
+ "item.numismatics.gray_authorized_card": "Gray Authorized Card",
+ "item.numismatics.gray_card": "Gray Bank Card",
+ "item.numismatics.gray_id_card": "Gray ID Card",
+ "item.numismatics.green_authorized_card": "Green Authorized Card",
+ "item.numismatics.green_card": "Green Bank Card",
+ "item.numismatics.green_id_card": "Green ID Card",
+ "item.numismatics.id_card.tooltip": "ID CARD",
+ "item.numismatics.id_card.tooltip.already_bound": "Already bound, Sneak + Use to Unbind",
+ "item.numismatics.id_card.tooltip.bound": "Bound to self",
+ "item.numismatics.id_card.tooltip.cleared": "Cleared",
+ "item.numismatics.id_card.tooltip.summary": "_Right Click_ to bind, _shift click_ to clear. Can be placed in the _Trusted Menu_ of many Numismatics blocks to give the bound player access to that block.",
+ "item.numismatics.light_blue_authorized_card": "Light Blue Authorized Card",
+ "item.numismatics.light_blue_card": "Light Blue Bank Card",
+ "item.numismatics.light_blue_id_card": "Light Blue ID Card",
+ "item.numismatics.light_gray_authorized_card": "Light Gray Authorized Card",
+ "item.numismatics.light_gray_card": "Light Gray Bank Card",
+ "item.numismatics.light_gray_id_card": "Light Gray ID Card",
+ "item.numismatics.lime_authorized_card": "Lime Authorized Card",
+ "item.numismatics.lime_card": "Lime Bank Card",
+ "item.numismatics.lime_id_card": "Lime ID Card",
+ "item.numismatics.magenta_authorized_card": "Magenta Authorized Card",
+ "item.numismatics.magenta_card": "Magenta Bank Card",
+ "item.numismatics.magenta_id_card": "Magenta ID Card",
+ "item.numismatics.orange_authorized_card": "Orange Authorized Card",
+ "item.numismatics.orange_card": "Orange Bank Card",
+ "item.numismatics.orange_id_card": "Orange ID Card",
+ "item.numismatics.pink_authorized_card": "Pink Authorized Card",
+ "item.numismatics.pink_card": "Pink Bank Card",
+ "item.numismatics.pink_id_card": "Pink ID Card",
+ "item.numismatics.purple_authorized_card": "Purple Authorized Card",
+ "item.numismatics.purple_card": "Purple Bank Card",
+ "item.numismatics.purple_id_card": "Purple ID Card",
+ "item.numismatics.red_authorized_card": "Red Authorized Card",
+ "item.numismatics.red_card": "Red Bank Card",
+ "item.numismatics.red_id_card": "Red ID Card",
+ "item.numismatics.sprocket": "Sprocket",
+ "item.numismatics.sprocket.plural": "Sprockets",
+ "item.numismatics.spur": "Spur",
+ "item.numismatics.spur.plural": "Spurs",
+ "item.numismatics.sun": "Sun",
+ "item.numismatics.sun.plural": "Suns",
+ "item.numismatics.white_authorized_card": "White Authorized Card",
+ "item.numismatics.white_card": "White Bank Card",
+ "item.numismatics.white_id_card": "White ID Card",
+ "item.numismatics.yellow_authorized_card": "Yellow Authorized Card",
+ "item.numismatics.yellow_card": "Yellow Bank Card",
+ "item.numismatics.yellow_id_card": "Yellow ID Card",
+ "numismatics.authorization_type.anybody": "Anybody",
+ "numismatics.authorization_type.anybody.description": "Anybody with the ID, including automation such as ComputerCraft computers",
+ "numismatics.authorization_type.trusted_automation": "Trusted Players + Automation",
+ "numismatics.authorization_type.trusted_automation.description": "Players on the trust list and automation placed by them (e.g. Deployers)",
+ "numismatics.authorization_type.trusted_players": "Trusted Players Only",
+ "numismatics.authorization_type.trusted_players.description": "Only players on the trust list",
+ "numismatics.special.ltr": "true",
+ "tag.block.numismatics.numismatics_blocks": "Numismatics Blocks",
+ "tag.item.forge.string": "String",
+ "tag.item.numismatics.authorized_cards": "Authorized Cards",
+ "tag.item.numismatics.cards": "Cards",
+ "tag.item.numismatics.coins": "Coins",
+ "tag.item.numismatics.id_cards": "Id Cards",
+ "tag.item.numismatics.numismatics_items": "Numismatics Items"
}
\ No newline at end of file
diff --git a/common/src/main/resources/assets/numismatics/textures/gui/salepoint_config.png b/common/src/main/resources/assets/numismatics/textures/gui/salepoint_config.png
index dec58117..d0fae384 100644
Binary files a/common/src/main/resources/assets/numismatics/textures/gui/salepoint_config.png and b/common/src/main/resources/assets/numismatics/textures/gui/salepoint_config.png differ
diff --git a/common/src/main/resources/assets/numismatics/textures/gui/salepoint_config.xcf b/common/src/main/resources/assets/numismatics/textures/gui/salepoint_config.xcf
index f3e4d49c..d744cb59 100644
Binary files a/common/src/main/resources/assets/numismatics/textures/gui/salepoint_config.xcf and b/common/src/main/resources/assets/numismatics/textures/gui/salepoint_config.xcf differ
diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts
index b58312cd..11547e5d 100644
--- a/fabric/build.gradle.kts
+++ b/fabric/build.gradle.kts
@@ -81,6 +81,7 @@ dependencies {
// Development QOL
modLocalRuntime("maven.modrinth:lazydfu:${"lazydfu_version"()}")
modLocalRuntime("com.terraformersmc:modmenu:${"modmenu_version"()}")
+ modLocalRuntime("maven.modrinth:jade:${"jade_version"()}")
modCompileOnly("dev.emi:emi-fabric:${"emi_version"()}:api")
modLocalRuntime("dev.emi:emi-fabric:${"emi_version"()}")
diff --git a/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/containers/fabric/InvalidatableWrappingFluidBufferTank.java b/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/containers/fabric/InvalidatableWrappingFluidBufferTank.java
new file mode 100644
index 00000000..47a8a788
--- /dev/null
+++ b/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/containers/fabric/InvalidatableWrappingFluidBufferTank.java
@@ -0,0 +1,95 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.content.salepoint.containers.fabric;
+
+import dev.ithundxr.createnumismatics.content.salepoint.containers.InvalidatableAbstractBuffer;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import dev.ithundxr.createnumismatics.multiloader.fluid.fabric.MultiloaderFluidStackImpl;
+import io.github.fabricators_of_create.porting_lib.transfer.fluid.FluidTank;
+import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
+import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
+import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
+import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
+import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+@SuppressWarnings("UnstableApiUsage")
+public class InvalidatableWrappingFluidBufferTank extends InvalidatableAbstractBuffer implements Storage {
+
+ protected FluidTank buffer;
+
+ public InvalidatableWrappingFluidBufferTank(FluidTank buffer) {
+ this.buffer = buffer;
+ }
+
+ @Override
+ protected void afterInvalidate() {
+ super.afterInvalidate();
+ buffer = null;
+ }
+
+ @Override
+ protected int copyToBufferInternal(MultiloaderFluidStack source, boolean simulate) {
+ try (Transaction transaction = Transaction.openOuter()) {
+ long inserted = buffer.insert(((MultiloaderFluidStackImpl) source).getType(), source.getAmount(), transaction);
+ if (!simulate) {
+ transaction.commit();
+ }
+ return (int) inserted;
+ }
+ }
+
+ @Override
+ protected int removeFromBufferInternal(MultiloaderFluidStack source, boolean simulate, int maxAmount) {
+ try (Transaction transaction = Transaction.openOuter()) {
+ long extracted = buffer.extract(((MultiloaderFluidStackImpl) source).getType(), maxAmount, transaction);
+ if (!simulate) {
+ transaction.commit();
+ }
+ return (int) extracted;
+ }
+ }
+
+ @Override
+ public long insert(FluidVariant resource, long maxAmount, TransactionContext transaction) {
+ if (!isValid())
+ return 0;
+
+ return buffer.insert(resource, maxAmount, transaction);
+ }
+
+ @Override
+ public long extract(FluidVariant resource, long maxAmount, TransactionContext transaction) {
+ if (!isValid())
+ return 0;
+
+ return buffer.extract(resource, maxAmount, transaction);
+ }
+
+ @Override
+ public @NotNull Iterator> iterator() {
+ if (!isValid())
+ return Collections.emptyListIterator();
+
+ return buffer.iterator();
+ }
+}
diff --git a/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/fabric/FluidSalepointStateImpl.java b/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/fabric/FluidSalepointStateImpl.java
new file mode 100644
index 00000000..c1751754
--- /dev/null
+++ b/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/fabric/FluidSalepointStateImpl.java
@@ -0,0 +1,126 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.content.salepoint.states.fabric;
+
+import com.simibubi.create.foundation.fluid.SmartFluidTank;
+import dev.ithundxr.createnumismatics.content.salepoint.containers.InvalidatableAbstractBuffer;
+import dev.ithundxr.createnumismatics.content.salepoint.containers.fabric.InvalidatableWrappingFluidBufferTank;
+import dev.ithundxr.createnumismatics.content.salepoint.states.FluidSalepointState;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import dev.ithundxr.createnumismatics.multiloader.fluid.fabric.MultiloaderFluidStackImpl;
+import io.github.fabricators_of_create.porting_lib.fluids.FluidStack;
+import io.github.fabricators_of_create.porting_lib.transfer.fluid.FluidTank;
+import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
+import net.minecraft.MethodsReturnNonnullByDefault;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.Tag;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.level.Level;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.util.List;
+
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+@SuppressWarnings("UnstableApiUsage")
+public class FluidSalepointStateImpl extends FluidSalepointState {
+
+ private final @NotNull FluidTank buffer = new SmartFluidTank(4 * getFilterCapacity(), $ -> this.setChanged())
+ .setValidator(fs -> this.filterMatches(new MultiloaderFluidStackImpl(fs)));
+ private @NotNull InvalidatableAbstractBuffer bufferWrapper = createBufferWrapper(buffer);
+
+ private static InvalidatableAbstractBuffer createBufferWrapper(FluidTank buffer) {
+ return new InvalidatableWrappingFluidBufferTank(buffer);
+ }
+
+ public static FluidSalepointState create() {
+ return new FluidSalepointStateImpl();
+ }
+
+ @Override
+ protected boolean canChangeFilterToInternal(MultiloaderFluidStack filter) {
+ return buffer.isEmpty();
+ }
+
+ @Override
+ protected void setFilterInternal(MultiloaderFluidStack filter, Level salepointLevel, BlockPos salepointPos, @Nullable Player player) {
+ // buffer gets cleared when filter is set, but the filter *should* only be set when the buffer is empty
+ if (!getFilter().isFluidEqual(filter))
+ buffer.setFluid(FluidStack.EMPTY);
+ }
+
+ @Override
+ protected void saveInternal(CompoundTag tag) {
+ CompoundTag bufferTag = new CompoundTag();
+ buffer.writeToNBT(bufferTag);
+ tag.put("Buffer", bufferTag);
+ }
+
+ @Override
+ protected void loadInternal(CompoundTag tag) {
+ buffer.setFluid(FluidStack.EMPTY);
+
+ if (tag.contains("Buffer", Tag.TAG_COMPOUND)) {
+ buffer.readFromNBT(tag.getCompound("Buffer"));
+ }
+ }
+
+ @Override
+ protected boolean hasBufferFluidForPurchase() {
+ return getFilter().isFluidEqual(new MultiloaderFluidStackImpl(buffer.getFluid()))
+ && buffer.getAmount() >= getFilter().getAmount();
+ }
+
+ @Override
+ protected List removeBufferFluidForPurchase() {
+ try (Transaction transaction = Transaction.openOuter()) {
+ long amount = buffer.extract(((MultiloaderFluidStackImpl) getFilter()).getType(), getFilter().getAmount(), transaction);
+ transaction.commit();
+
+ return List.of(
+ new MultiloaderFluidStackImpl(buffer.getFluid().copy().setAmount(amount))
+ );
+ }
+ }
+
+ @Override
+ public InvalidatableAbstractBuffer getBuffer() {
+ return bufferWrapper;
+ }
+
+ @Override
+ public void onDestroy(Level level, BlockPos pos) {
+ onUnload();
+ buffer.setFluid(FluidStack.EMPTY);
+ }
+
+ @Override
+ public void onUnload() {
+ bufferWrapper.invalidate();
+ }
+
+ @Override
+ public void keepAlive() {
+ if (!bufferWrapper.isValid())
+ bufferWrapper = createBufferWrapper(buffer);
+ }
+}
diff --git a/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/fabric/SalepointFluidConfigWidgetImpl.java b/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/fabric/SalepointFluidConfigWidgetImpl.java
new file mode 100644
index 00000000..009844c5
--- /dev/null
+++ b/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/fabric/SalepointFluidConfigWidgetImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.content.salepoint.widgets.fabric;
+
+import com.simibubi.create.content.fluids.transfer.GenericItemEmptying;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import dev.ithundxr.createnumismatics.multiloader.fluid.fabric.MultiloaderFluidStackImpl;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.Minecraft;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Level;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class SalepointFluidConfigWidgetImpl {
+ @Environment(EnvType.CLIENT)
+ public static @Nullable MultiloaderFluidStack getFluidFrom(@NotNull ItemStack stack) {
+ Level level = Minecraft.getInstance().level;
+ if (level == null)
+ return null;
+
+ if (!GenericItemEmptying.canItemBeEmptied(level, stack))
+ return null;
+
+ return new MultiloaderFluidStackImpl(GenericItemEmptying.emptyItem(level, stack, true).getFirst());
+ }
+}
diff --git a/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/fabric/SalepointFluidDisplayWidgetImpl.java b/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/fabric/SalepointFluidDisplayWidgetImpl.java
new file mode 100644
index 00000000..e91fd612
--- /dev/null
+++ b/fabric/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/fabric/SalepointFluidDisplayWidgetImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.content.salepoint.widgets.fabric;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.simibubi.create.foundation.fluid.FluidRenderer;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import dev.ithundxr.createnumismatics.multiloader.fluid.fabric.MultiloaderFluidStackImpl;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.renderer.MultiBufferSource;
+import org.jetbrains.annotations.NotNull;
+
+public class SalepointFluidDisplayWidgetImpl {
+ @Environment(EnvType.CLIENT)
+ public static void renderFluidBox(@NotNull MultiloaderFluidStack fluidStack, float xMin, float yMin, float zMin,
+ float xMax, float yMax, float zMax, @NotNull MultiBufferSource buffer,
+ @NotNull PoseStack ms, int light, boolean renderBottom) {
+ FluidRenderer.renderFluidBox(((MultiloaderFluidStackImpl) fluidStack).getWrapped(), xMin, yMin, zMin, xMax, yMax, zMax, buffer, ms, light, renderBottom);
+ }
+}
diff --git a/fabric/src/main/java/dev/ithundxr/createnumismatics/fabric/mixin/PortableFluidInterfaceBlockEntityMixin.java b/fabric/src/main/java/dev/ithundxr/createnumismatics/fabric/mixin/PortableFluidInterfaceBlockEntityMixin.java
new file mode 100644
index 00000000..a5ea0518
--- /dev/null
+++ b/fabric/src/main/java/dev/ithundxr/createnumismatics/fabric/mixin/PortableFluidInterfaceBlockEntityMixin.java
@@ -0,0 +1,212 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.fabric.mixin;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.simibubi.create.content.contraptions.actors.psi.PortableFluidInterfaceBlockEntity;
+import com.simibubi.create.content.contraptions.actors.psi.PortableFluidInterfaceBlockEntity.InterfaceFluidHandler;
+import com.simibubi.create.content.contraptions.actors.psi.PortableStorageInterfaceBlockEntity;
+import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour;
+import dev.ithundxr.createnumismatics.Numismatics;
+import dev.ithundxr.createnumismatics.content.salepoint.behaviours.FluidSalepointTargetBehaviour;
+import dev.ithundxr.createnumismatics.content.salepoint.behaviours.SalepointTargetBehaviour;
+import dev.ithundxr.createnumismatics.content.salepoint.containers.fabric.InvalidatableWrappingFluidBufferTank;
+import dev.ithundxr.createnumismatics.content.salepoint.states.ISalepointState;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import dev.ithundxr.createnumismatics.multiloader.fluid.fabric.MultiloaderFluidStackImpl;
+import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
+import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
+import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.level.block.entity.BlockEntityType;
+import net.minecraft.world.level.block.state.BlockState;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+
+import java.util.List;
+
+@Mixin(PortableFluidInterfaceBlockEntity.class)
+@SuppressWarnings("UnstableApiUsage")
+public abstract class PortableFluidInterfaceBlockEntityMixin extends PortableStorageInterfaceBlockEntity {
+
+ @Shadow protected InterfaceFluidHandler capability;
+ @Unique
+ private FluidSalepointTargetBehaviour railway$salepointBehaviour;
+
+ @Unique
+ @Nullable
+ private Storage railway$contraptionStorage;
+
+ private PortableFluidInterfaceBlockEntityMixin(BlockEntityType> type, BlockPos pos, BlockState state) {
+ super(type, pos, state);
+ }
+
+ @WrapOperation(
+ method = "startTransferringTo",
+ at = @At(
+ value = "INVOKE",
+ target = "Lcom/simibubi/create/content/contraptions/actors/psi/PortableFluidInterfaceBlockEntity$InterfaceFluidHandler;setWrapped(Lnet/fabricmc/fabric/api/transfer/v1/storage/Storage;)V"
+ ),
+ remap = false
+ )
+ @SuppressWarnings("unchecked")
+ private void keepControl(InterfaceFluidHandler instance, Storage wrapped, Operation original) {
+ Storage existingWrapped = ((WrappedStorageAccessor) capability).getWrapped();
+ if (!(existingWrapped instanceof InvalidatableWrappingFluidBufferTank)) {
+ original.call(instance, wrapped);
+ }
+ railway$contraptionStorage = wrapped;
+ }
+
+ @WrapOperation(
+ method = "stopTransferring",
+ at = @At(
+ value = "INVOKE",
+ target = "Lcom/simibubi/create/content/contraptions/actors/psi/PortableFluidInterfaceBlockEntity$InterfaceFluidHandler;setWrapped(Lnet/fabricmc/fabric/api/transfer/v1/storage/Storage;)V"
+ ),
+ remap = false
+ )
+ @SuppressWarnings("unchecked")
+ private void keepControl2(InterfaceFluidHandler instance, Storage wrapped, Operation original) {
+ Storage existingWrapped = ((WrappedStorageAccessor) capability).getWrapped();
+ if (!(existingWrapped instanceof InvalidatableWrappingFluidBufferTank)) {
+ original.call(instance, wrapped);
+ }
+ railway$contraptionStorage = null;
+ }
+
+ @Override
+ public boolean canTransfer() {
+ return super.canTransfer() || railway$salepointBehaviour.isControlledBySalepoint();
+ }
+
+ @Override
+ public void addBehaviours(List behaviours) {
+ super.addBehaviours(behaviours);
+ railway$salepointBehaviour = new FluidSalepointTargetBehaviour(this) {
+ private boolean underControl = false;
+
+ @Override
+ protected boolean isUnderControlInternal(@NotNull ISalepointState state) {
+ return underControl; // id checks done by super
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void ensureUnderControlInternal(@NotNull ISalepointState state) {
+ ((WrappedStorageAccessor) capability).setWrapped((InvalidatableWrappingFluidBufferTank) state.getBuffer());
+
+ if (!underControl) {
+ underControl = true;
+ notifyUpdate();
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void relinquishControlInternal(@NotNull ISalepointState state) {
+ if (railway$contraptionStorage != null) {
+ ((WrappedStorageAccessor) capability).setWrapped(railway$contraptionStorage);
+ } else {
+ ((WrappedStorageAccessor) capability).setWrapped(Storage.empty());
+ }
+
+ if (underControl) {
+ underControl = false;
+ notifyUpdate();
+ }
+ }
+
+ @Override
+ public boolean hasSpaceFor(@NotNull MultiloaderFluidStack object) {
+ if (railway$contraptionStorage == null)
+ return false;
+
+ if (!railway$contraptionStorage.supportsInsertion())
+ return false;
+
+ try (Transaction transaction = Transaction.openOuter()) {
+ if (railway$contraptionStorage.insert(((MultiloaderFluidStackImpl) object).getType(), object.getAmount(), transaction) != object.getAmount()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean doPurchase(@NotNull MultiloaderFluidStack object, @NotNull PurchaseProvider purchaseProvider) {
+ if (railway$contraptionStorage == null)
+ return false;
+
+ if (!hasSpaceFor(object))
+ return false;
+
+ List extracted = purchaseProvider.extract();
+ try (Transaction transaction = Transaction.openOuter()) {
+ for (MultiloaderFluidStack stack : extracted) {
+ if (railway$contraptionStorage.insert(((MultiloaderFluidStackImpl) stack).getType(), stack.getAmount(), transaction) != stack.getAmount()) {
+ Numismatics.LOGGER.error("Failed to insert fluid into contraption storage, despite having space.");
+ return false;
+ }
+ }
+ transaction.commit();
+ }
+
+ return true;
+ }
+
+ @Override
+ public void read(@NotNull CompoundTag nbt, boolean clientPacket) {
+ super.read(nbt, clientPacket);
+
+ underControl = nbt.getBoolean("SalepointUnderControl");
+ }
+
+ @Override
+ public void write(@NotNull CompoundTag nbt, boolean clientPacket) {
+ super.write(nbt, clientPacket);
+
+ nbt.putBoolean("SalepointUnderControl", underControl);
+ }
+ };
+
+ behaviours.add(railway$salepointBehaviour);
+ }
+
+ @Mixin(InterfaceFluidHandler.class)
+ private static class InterfaceFluidHandlerMixin {
+ @WrapOperation(
+ method = "insert(Lnet/fabricmc/fabric/api/transfer/v1/fluid/FluidVariant;JLnet/fabricmc/fabric/api/transfer/v1/transaction/TransactionContext;)J",
+ at = @At(
+ value = "INVOKE",
+ target = "Lcom/simibubi/create/content/contraptions/actors/psi/PortableFluidInterfaceBlockEntity;isConnected()Z"
+ )
+ )
+ private boolean fakeConnect(PortableFluidInterfaceBlockEntity instance, Operation original) {
+ return original.call(instance) || instance.getBehaviour(SalepointTargetBehaviour.TYPE).isControlledBySalepoint();
+ }
+ }
+}
diff --git a/fabric/src/main/java/dev/ithundxr/createnumismatics/fabric/mixin/WrappedStorageAccessor.java b/fabric/src/main/java/dev/ithundxr/createnumismatics/fabric/mixin/WrappedStorageAccessor.java
new file mode 100644
index 00000000..e0f524a6
--- /dev/null
+++ b/fabric/src/main/java/dev/ithundxr/createnumismatics/fabric/mixin/WrappedStorageAccessor.java
@@ -0,0 +1,34 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.fabric.mixin;
+
+import io.github.fabricators_of_create.porting_lib.transfer.WrappedStorage;
+import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(WrappedStorage.class)
+@SuppressWarnings("UnstableApiUsage")
+public interface WrappedStorageAccessor extends Storage {
+ @Accessor("wrapped")
+ Storage getWrapped();
+
+ @Accessor("wrapped")
+ void setWrapped(Storage wrapped);
+}
diff --git a/fabric/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/fabric/FluidUnitsImpl.java b/fabric/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/fabric/FluidUnitsImpl.java
new file mode 100644
index 00000000..f73be2fa
--- /dev/null
+++ b/fabric/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/fabric/FluidUnitsImpl.java
@@ -0,0 +1,28 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.multiloader.fluid.fabric;
+
+import net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants;
+
+public class FluidUnitsImpl {
+ @SuppressWarnings("UnstableApiUsage")
+ public static long bucket() {
+ return FluidConstants.BUCKET;
+ }
+}
diff --git a/fabric/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/fabric/MultiloaderFluidStackImpl.java b/fabric/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/fabric/MultiloaderFluidStackImpl.java
new file mode 100644
index 00000000..6399f2a9
--- /dev/null
+++ b/fabric/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/fabric/MultiloaderFluidStackImpl.java
@@ -0,0 +1,213 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.multiloader.fluid.fabric;
+
+import com.mojang.serialization.Codec;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import io.github.fabricators_of_create.porting_lib.fluids.FluidStack;
+import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
+import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariantAttributes;
+import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
+import net.fabricmc.fabric.api.transfer.v1.storage.base.ResourceAmount;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.material.Fluid;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+@SuppressWarnings("UnstableApiUsage")
+public class MultiloaderFluidStackImpl extends MultiloaderFluidStack {
+
+ public static Codec makeCodec() {
+ return FluidStack.CODEC.xmap(MultiloaderFluidStackImpl::new, fs -> ((MultiloaderFluidStackImpl) fs).wrapped);
+ }
+
+ public static MultiloaderFluidStack makeEmpty() {
+ return new MultiloaderFluidStackImpl(FluidStack.EMPTY);
+ }
+
+ private final FluidStack wrapped;
+
+ public MultiloaderFluidStackImpl(FluidStack wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ public MultiloaderFluidStackImpl(FluidVariant type, long amount) {
+ this(new FluidStack(type, amount));
+ }
+
+ public MultiloaderFluidStackImpl(FluidVariant type, long amount, @Nullable CompoundTag tag) {
+ this(new FluidStack(type, amount, tag));
+ }
+
+ public MultiloaderFluidStackImpl(StorageView view) {
+ this(new FluidStack(view));
+ }
+
+ public MultiloaderFluidStackImpl(ResourceAmount resource) {
+ this(new FluidStack(resource));
+ }
+
+ /**
+ * Avoid this constructor when possible, may result in NBT loss
+ */
+ public MultiloaderFluidStackImpl(Fluid fluid, long amount) {
+ this(new FluidStack(fluid, amount));
+ }
+
+ public MultiloaderFluidStackImpl(Fluid fluid, long amount, @Nullable CompoundTag nbt) {
+ this(new FluidStack(fluid, amount, nbt));
+ }
+
+ public MultiloaderFluidStackImpl(FluidStack copy, long amount) {
+ this(new FluidStack(copy, amount));
+ }
+
+ public MultiloaderFluidStackImpl(MultiloaderFluidStack copy, long amount) {
+ this(((MultiloaderFluidStackImpl) copy).wrapped, amount);
+ }
+
+ @Override
+ public MultiloaderFluidStack setAmount(long amount) {
+ wrapped.setAmount(amount);
+ return this;
+ }
+
+ public FluidVariant getType() {
+ return wrapped.getType();
+ }
+
+ @Override
+ public Fluid getFluid() {
+ return wrapped.getFluid();
+ }
+
+ @Override
+ public long getAmount() {
+ return wrapped.getAmount();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return wrapped.isEmpty();
+ }
+
+ @Override
+ public boolean isFluidEqual(MultiloaderFluidStack other) {
+ return wrapped.isFluidEqual(((MultiloaderFluidStackImpl) other).wrapped);
+ }
+
+ public boolean isFluidEqual(FluidVariant other) {
+ return wrapped.isFluidEqual(other);
+ }
+
+ public static boolean isFluidEqual(FluidVariant mine, FluidVariant other) {
+ return FluidStack.isFluidEqual(mine, other);
+ }
+
+ public boolean canFill(FluidVariant var) {
+ return wrapped.canFill(var);
+ }
+
+ @Override
+ public CompoundTag writeToNBT(CompoundTag nbt) {
+ return wrapped.writeToNBT(nbt);
+ }
+
+ public static MultiloaderFluidStack loadFluidStackFromNBT(CompoundTag tag) {
+ return new MultiloaderFluidStackImpl(FluidStack.loadFluidStackFromNBT(tag));
+ }
+
+ @Override
+ public void setTag(CompoundTag tag) {
+ wrapped.setTag(tag);
+ }
+
+ @Override
+ public @Nullable CompoundTag getTag() {
+ return wrapped.getTag();
+ }
+
+ @Override
+ public Component getDisplayName() {
+ return wrapped.getDisplayName();
+ }
+
+ public static MultiloaderFluidStack readFromPacket(FriendlyByteBuf buffer) {
+ return new MultiloaderFluidStackImpl(FluidStack.readFromPacket(buffer));
+ }
+
+ @Override
+ public FriendlyByteBuf writeToPacket(FriendlyByteBuf buffer) {
+ return wrapped.writeToPacket(buffer);
+ }
+
+ @Override
+ public MultiloaderFluidStack copy() {
+ return new MultiloaderFluidStackImpl(wrapped.copy());
+ }
+
+ @Override
+ public boolean containsFluid(@NotNull MultiloaderFluidStack other) {
+ return wrapped.containsFluid(((MultiloaderFluidStackImpl) other).wrapped);
+ }
+
+ @Override
+ public boolean isFluidStackIdentical(MultiloaderFluidStack other) {
+ return wrapped.isFluidStackIdentical(((MultiloaderFluidStackImpl) other).wrapped);
+ }
+
+ @Override
+ public boolean isFluidEqual(@NotNull ItemStack other) {
+ return wrapped.isFluidEqual(other);
+ }
+
+ @Override
+ public boolean isLighterThanAir() {
+ return FluidVariantAttributes.isLighterThanAir(wrapped.getType());
+ }
+
+ public FluidStack getWrapped() {
+ return wrapped;
+ }
+
+ @Override
+ public final int hashCode() {
+ return wrapped.hashCode();
+ }
+
+ /**
+ * Default equality comparison for a FluidStack. Same functionality as isFluidEqual().
+ *
+ * This is included for use in data structures.
+ */
+ @Override
+ public final boolean equals(Object obj) {
+ if (!(obj instanceof MultiloaderFluidStack fs))
+ return false;
+
+ return isFluidEqual(fs);
+ }
+
+ public static MultiloaderFluidStack create(Fluid fluid, long amount, @Nullable CompoundTag nbt) {
+ return new MultiloaderFluidStackImpl(fluid, amount, nbt);
+ }
+}
diff --git a/fabric/src/main/java/dev/ithundxr/createnumismatics/util/fabric/TextUtilsImpl.java b/fabric/src/main/java/dev/ithundxr/createnumismatics/util/fabric/TextUtilsImpl.java
new file mode 100644
index 00000000..48d3348b
--- /dev/null
+++ b/fabric/src/main/java/dev/ithundxr/createnumismatics/util/fabric/TextUtilsImpl.java
@@ -0,0 +1,39 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.util.fabric;
+
+import com.simibubi.create.foundation.utility.Lang;
+import com.simibubi.create.foundation.utility.LangBuilder;
+import com.simibubi.create.infrastructure.config.AllConfigs;
+import io.github.fabricators_of_create.porting_lib.util.FluidTextUtil;
+import io.github.fabricators_of_create.porting_lib.util.FluidUnit;
+
+public class TextUtilsImpl {
+ public static String formatFluid(long amount) {
+ FluidUnit unit = AllConfigs.client().fluidUnitType.get();
+ boolean simplify = AllConfigs.client().simplifyFluidUnit.get();
+ LangBuilder mb = Lang.translate(unit.getTranslationKey());
+
+ String amountStr = FluidTextUtil.getUnicodeMillibuckets(amount, unit, simplify);
+
+ return Lang.text(amountStr)
+ .add(mb)
+ .string();
+ }
+}
diff --git a/fabric/src/main/resources/numismatics.mixins.json b/fabric/src/main/resources/numismatics.mixins.json
index cbb80188..ccae7e84 100644
--- a/fabric/src/main/resources/numismatics.mixins.json
+++ b/fabric/src/main/resources/numismatics.mixins.json
@@ -9,7 +9,10 @@
],
"mixins": [
"ItemHandlerWrapperAccessor",
- "PortableItemInterfaceBlockEntityMixin"
+ "PortableFluidInterfaceBlockEntityMixin",
+ "PortableFluidInterfaceBlockEntityMixin$InterfaceFluidHandlerMixin",
+ "PortableItemInterfaceBlockEntityMixin",
+ "WrappedStorageAccessor"
],
"injectors": {
"defaultRequire": 1
diff --git a/forge/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/forge/FluidSalepointStateImpl.java b/forge/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/forge/FluidSalepointStateImpl.java
new file mode 100644
index 00000000..a29d037f
--- /dev/null
+++ b/forge/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/states/forge/FluidSalepointStateImpl.java
@@ -0,0 +1,27 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.content.salepoint.states.forge;
+
+import dev.ithundxr.createnumismatics.content.salepoint.states.FluidSalepointState;
+
+public class FluidSalepointStateImpl {
+ public static FluidSalepointState create() {
+ throw new AssertionError();
+ }
+}
diff --git a/forge/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/forge/SalepointFluidConfigWidgetImpl.java b/forge/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/forge/SalepointFluidConfigWidgetImpl.java
new file mode 100644
index 00000000..aec9cc8c
--- /dev/null
+++ b/forge/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/forge/SalepointFluidConfigWidgetImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.content.salepoint.widgets.forge;
+
+import com.simibubi.create.content.fluids.transfer.GenericItemEmptying;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import dev.ithundxr.createnumismatics.multiloader.fluid.forge.MultiloaderFluidStackImpl;
+import net.minecraft.client.Minecraft;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Level;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class SalepointFluidConfigWidgetImpl {
+ @OnlyIn(Dist.CLIENT)
+ @Nullable
+ public static MultiloaderFluidStack getFluidFrom(@NotNull ItemStack stack) {
+ Level level = Minecraft.getInstance().level;
+ if (level == null)
+ return null;
+
+ if (!GenericItemEmptying.canItemBeEmptied(level, stack))
+ return null;
+
+ return new MultiloaderFluidStackImpl(GenericItemEmptying.emptyItem(level, stack, true).getFirst());
+ }
+}
diff --git a/forge/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/forge/SalepointFluidDisplayWidgetImpl.java b/forge/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/forge/SalepointFluidDisplayWidgetImpl.java
new file mode 100644
index 00000000..5426ac85
--- /dev/null
+++ b/forge/src/main/java/dev/ithundxr/createnumismatics/content/salepoint/widgets/forge/SalepointFluidDisplayWidgetImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.content.salepoint.widgets.forge;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.simibubi.create.foundation.fluid.FluidRenderer;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import dev.ithundxr.createnumismatics.multiloader.fluid.forge.MultiloaderFluidStackImpl;
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+import org.jetbrains.annotations.NotNull;
+
+public class SalepointFluidDisplayWidgetImpl {
+ @OnlyIn(Dist.CLIENT)
+ public static void renderFluidBox(@NotNull MultiloaderFluidStack fluidStack, float xMin, float yMin, float zMin,
+ float xMax, float yMax, float zMax, @NotNull MultiBufferSource buffer,
+ @NotNull PoseStack ms, int light, boolean renderBottom) {
+ FluidRenderer.renderFluidBox(((MultiloaderFluidStackImpl) fluidStack).getWrapped(), xMin, yMin, zMin, xMax, yMax, zMax, buffer, ms, light, renderBottom);
+ }
+}
diff --git a/forge/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/forge/FluidUnitsImpl.java b/forge/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/forge/FluidUnitsImpl.java
new file mode 100644
index 00000000..8dae0573
--- /dev/null
+++ b/forge/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/forge/FluidUnitsImpl.java
@@ -0,0 +1,25 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.multiloader.fluid.forge;
+
+public class FluidUnitsImpl {
+ public static long bucket() {
+ return 1000L;
+ }
+}
diff --git a/forge/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/forge/MultiloaderFluidStackImpl.java b/forge/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/forge/MultiloaderFluidStackImpl.java
new file mode 100644
index 00000000..eb25f4c9
--- /dev/null
+++ b/forge/src/main/java/dev/ithundxr/createnumismatics/multiloader/fluid/forge/MultiloaderFluidStackImpl.java
@@ -0,0 +1,190 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.multiloader.fluid.forge;
+
+import com.mojang.serialization.Codec;
+import dev.ithundxr.createnumismatics.multiloader.fluid.MultiloaderFluidStack;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class MultiloaderFluidStackImpl extends MultiloaderFluidStack {
+
+ public static Codec makeCodec() {
+ return FluidStack.CODEC.xmap(MultiloaderFluidStackImpl::new, fs -> ((MultiloaderFluidStackImpl) fs).wrapped);
+ }
+
+ public static MultiloaderFluidStack makeEmpty() {
+ return new MultiloaderFluidStackImpl(FluidStack.EMPTY);
+ }
+
+ private final FluidStack wrapped;
+
+ public MultiloaderFluidStackImpl(FluidStack wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ public MultiloaderFluidStackImpl(Fluid fluid, int amount) {
+ this(new FluidStack(fluid, amount));
+ }
+
+ public MultiloaderFluidStackImpl(Fluid fluid, int amount, CompoundTag nbt) {
+ this(new FluidStack(fluid, amount, nbt));
+ }
+
+ public MultiloaderFluidStackImpl(FluidStack stack, int amount) {
+ this(new FluidStack(stack, amount));
+ }
+
+ public MultiloaderFluidStackImpl(MultiloaderFluidStack stack, int amount) {
+ this(((MultiloaderFluidStackImpl) stack).wrapped, amount);
+ }
+
+ @Override
+ public MultiloaderFluidStack setAmount(long amount) {
+ wrapped.setAmount((int) amount);
+ return this;
+ }
+
+ @Override
+ public Fluid getFluid() {
+ return wrapped.getFluid();
+ }
+
+ public final Fluid getRawFluid() {
+ return wrapped.getRawFluid();
+ }
+
+ @Override
+ public long getAmount() {
+ return wrapped.getAmount();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return wrapped.isEmpty();
+ }
+
+ @Override
+ public boolean isFluidEqual(MultiloaderFluidStack other) {
+ return wrapped.isFluidEqual(((MultiloaderFluidStackImpl) other).wrapped);
+ }
+
+ @Override
+ public CompoundTag writeToNBT(CompoundTag nbt) {
+ return wrapped.writeToNBT(nbt);
+ }
+
+ public static MultiloaderFluidStack loadFluidStackFromNBT(CompoundTag tag) {
+ return new MultiloaderFluidStackImpl(FluidStack.loadFluidStackFromNBT(tag));
+ }
+
+ @Override
+ public void setTag(CompoundTag tag) {
+ wrapped.setTag(tag);
+ }
+
+ @Override
+ public @Nullable CompoundTag getTag() {
+ return wrapped.getTag();
+ }
+
+ public @Nullable CompoundTag getChildTag(String childName) {
+ return wrapped.getChildTag(childName);
+ }
+
+ public @Nullable CompoundTag getOrCreateChildTag(String childName) {
+ return wrapped.getOrCreateChildTag(childName);
+ }
+
+ @Override
+ public Component getDisplayName() {
+ return wrapped.getDisplayName();
+ }
+
+ public String getTranslationKey() {
+ return wrapped.getTranslationKey();
+ }
+
+ public static MultiloaderFluidStack readFromPacket(FriendlyByteBuf buffer) {
+ return new MultiloaderFluidStackImpl(FluidStack.readFromPacket(buffer));
+ }
+
+ @Override
+ public FriendlyByteBuf writeToPacket(FriendlyByteBuf buffer) {
+ wrapped.writeToPacket(buffer);
+ return buffer;
+ }
+
+ @Override
+ public MultiloaderFluidStack copy() {
+ return new MultiloaderFluidStackImpl(wrapped.copy());
+ }
+
+ @Override
+ public boolean containsFluid(@NotNull MultiloaderFluidStack other) {
+ return wrapped.containsFluid(((MultiloaderFluidStackImpl) other).wrapped);
+ }
+
+ @Override
+ public boolean isFluidStackIdentical(MultiloaderFluidStack other) {
+ return wrapped.isFluidStackIdentical(((MultiloaderFluidStackImpl) other).wrapped);
+ }
+
+ @Override
+ public boolean isFluidEqual(@NotNull ItemStack other) {
+ return wrapped.isFluidEqual(other);
+ }
+
+ @Override
+ public boolean isLighterThanAir() {
+ return getFluid().getFluidType().isLighterThanAir();
+ }
+
+ @Override
+ public final int hashCode() {
+ return wrapped.hashCode();
+ }
+
+ /**
+ * Default equality comparison for a FluidStack. Same functionality as isFluidEqual().
+ *
+ * This is included for use in data structures.
+ */
+ @Override
+ public final boolean equals(Object obj) {
+ if (!(obj instanceof MultiloaderFluidStack fs))
+ return false;
+
+ return isFluidEqual(fs);
+ }
+
+ public static MultiloaderFluidStack create(Fluid fluid, long amount, @Nullable CompoundTag nbt) {
+ return new MultiloaderFluidStackImpl(fluid, (int) amount, nbt);
+ }
+
+ public FluidStack getWrapped() {
+ return wrapped;
+ }
+}
diff --git a/forge/src/main/java/dev/ithundxr/createnumismatics/util/forge/TextUtilsImpl.java b/forge/src/main/java/dev/ithundxr/createnumismatics/util/forge/TextUtilsImpl.java
new file mode 100644
index 00000000..c5d1880f
--- /dev/null
+++ b/forge/src/main/java/dev/ithundxr/createnumismatics/util/forge/TextUtilsImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Numismatics
+ * Copyright (c) 2024 The Railways Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.ithundxr.createnumismatics.util.forge;
+
+import com.simibubi.create.foundation.utility.Lang;
+import com.simibubi.create.foundation.utility.LangBuilder;
+
+public class TextUtilsImpl {
+ public static String formatFluid(long amount) {
+ LangBuilder mb = Lang.translate("generic.unit.millibuckets");
+
+ return Lang.number(amount)
+ .add(mb)
+ .string();
+ }
+}
diff --git a/gradle.properties b/gradle.properties
index 925f8bc8..f925e402 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -48,6 +48,8 @@ jei_version = 15.8.0.16
modmenu_version = 7.2.2
# LazyDFU - https://modrinth.com/mod/lazydfu/versions
lazydfu_version = 0.1.3
+# Jade - https://modrinth.com/mod/jade/versions
+jade_version = 11.10.0+fabric
# Carry On - https://github.com/Tschipp/CarryOn
carryon_forge_version = 2.1.2.7