From 646131bde9eceaa68cb2febf77c772ddd7dfc5b1 Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Mon, 11 Aug 2025 11:12:00 -0400 Subject: [PATCH 01/19] Shopkeeper Integration --- .../64c87e664647124a7b6450bc49fb722594ed5a16 | 6 +- .../assets/numismatics/lang/en_ud.json | 2 + .../assets/numismatics/lang/en_us.json | 2 + .../createnumismatics/Numismatics.java | 2 + .../content/backend/Coin.java | 8 + .../content/checkout/CheckoutMenu.java | 175 ++++++++ .../checkout/CheckoutPaymentMethod.java | 20 + .../content/checkout/CheckoutScreen.java | 176 ++++++++ .../content/checkout/CheckoutUtilities.java | 149 +++++++ .../content/checkout/CoinPaymentUtil.java | 15 + .../checkout/DeferredCheckoutOrder.java | 387 ++++++++++++++++++ .../GlobalDeferredCheckoutOrderManager.java | 58 +++ .../content/coins/DiscreteCoinBag.java | 16 + ...erBlockEntityReceivedPaymentsAccessor.java | 13 + .../MixinStockTickerInteractionHandler.java | 69 ++++ .../registry/NumismaticsGuiTextures.java | 1 + .../registry/NumismaticsMenuTypes.java | 8 + .../registry/NumismaticsPackets.java | 1 + .../DeferredCheckoutResolutionPacket.java | 41 ++ .../createnumismatics/util/Utils.java | 3 + .../numismatics/lang/default/interface.json | 2 + .../textures/gui/checkout_screen.png | Bin 0 -> 2822 bytes .../resources/numismatics-common.mixins.json | 2 + 23 files changed, 1153 insertions(+), 3 deletions(-) create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CoinPaymentUtil.java create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerBlockEntityReceivedPaymentsAccessor.java create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/registry/packets/DeferredCheckoutResolutionPacket.java create mode 100644 common/src/main/resources/assets/numismatics/textures/gui/checkout_screen.png diff --git a/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 b/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 index a273ae21..91045850 100644 --- a/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 +++ b/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 @@ -1,12 +1,12 @@ -// 1.21.1 2025-07-06T23:10:14.258795717 Registrate Provider for numismatics [Registries, Data Maps, Recipes, Advancements, Loot Tables, Tags (enchantments), Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), generic_server_provider, Blockstates, Item models, Lang (en_us/en_ud), generic_client_provider] +// 1.21.1 2025-08-11T14:40:38.617334 Registrate Provider for numismatics [Registries, Data Maps, Recipes, Advancements, Loot Tables, Tags (enchantments), Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), generic_server_provider, Blockstates, Item models, Lang (en_us/en_ud), generic_client_provider] f7f43dd6d567ec8303c73b79409bc92d8b56574a assets/numismatics/blockstates/andesite_depositor.json 3961fdf3030140fc32e0e8c1d440ac395e62f5b6 assets/numismatics/blockstates/bank_terminal.json 06ecd28cd97f4e8200dc396858695cad57b871c8 assets/numismatics/blockstates/blaze_banker.json 160d556c6bfdb651082b39784258f6d06c21ca8f assets/numismatics/blockstates/brass_depositor.json 95ef415a564eba1d212053195d25b199427b94e3 assets/numismatics/blockstates/creative_vendor.json d2b105f0657bad99b8efed45dc0a8df8ff775c10 assets/numismatics/blockstates/vendor.json -6dd2c5d0c4c607aa601fb66217f4f60a5c008489 assets/numismatics/lang/en_ud.json -bb6968537ab305ee37b6268f9037a689b8c8a9c9 assets/numismatics/lang/en_us.json +adc37c6733d275a4f32add3d5ca693ea345013de assets/numismatics/lang/en_ud.json +9218c8f15ade397f8e485497675812f43647bdbc assets/numismatics/lang/en_us.json 265ef24d62bc7580e763e1fb6802bf4e58dc0194 assets/numismatics/models/block/andesite_depositor.json 4f78ca868db20495aa20be7c6a14e2678fb16f9f assets/numismatics/models/block/andesite_depositor_locked.json 411b79f79547a0adcb665bf7440e8169f7dcb24e assets/numismatics/models/block/brass_depositor.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 39c53488..001953a6 100644 --- a/common/src/generated/resources/assets/numismatics/lang/en_ud.json +++ b/common/src/generated/resources/assets/numismatics/lang/en_ud.json @@ -34,6 +34,8 @@ "block.numismatics.vendor.tooltip.trade_item": "ǝpɐɹ⟘ oʇ ɯǝʇI", "command.numismatics.arguments.enum.invalid": "%s :ǝɹɐ sǝnןɐʌ pıןɐΛ ˙,%s, ǝnןɐʌ ɯnuǝ pıןɐʌuI :ɹoɹɹƎ", "gui.numismatics.bank_terminal.balance": "¤%s '%s %s :ǝɔuɐןɐᗺ", + "gui.numismatics.checkout_screen.header": "ʇnoʞɔǝɥƆ", + "gui.numismatics.checkout_screen.total": "¤%s '%s %s :ןɐʇo⟘ ɹǝpɹO", "gui.numismatics.trust_list": "ʇsıꞀ ʇsnɹ⟘", "gui.numismatics.vendor.count": ")x%s( ", "gui.numismatics.vendor.full": "ןןnɟ sı ɹopuǝΛ", 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 ea382dd4..775738eb 100644 --- a/common/src/generated/resources/assets/numismatics/lang/en_us.json +++ b/common/src/generated/resources/assets/numismatics/lang/en_us.json @@ -34,6 +34,8 @@ "block.numismatics.vendor.tooltip.trade_item": "Item to Trade", "command.numismatics.arguments.enum.invalid": "Error: Invalid enum value '%s'. Valid values are: %s", "gui.numismatics.bank_terminal.balance": "Balance: %s %s, %s¤", + "gui.numismatics.checkout_screen.header": "Checkout", + "gui.numismatics.checkout_screen.total": "Order Total: %s %s, %s¤", "gui.numismatics.trust_list": "Trust List", "gui.numismatics.vendor.count": " (%sx)", "gui.numismatics.vendor.full": "Vendor is full", diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/Numismatics.java b/common/src/main/java/dev/ithundxr/createnumismatics/Numismatics.java index 5cf35fdd..cf021148 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/Numismatics.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/Numismatics.java @@ -8,6 +8,7 @@ import com.simibubi.create.foundation.item.TooltipModifier; import dev.architectury.injectables.annotations.ExpectPlatform; import dev.ithundxr.createnumismatics.content.backend.GlobalBankManager; +import dev.ithundxr.createnumismatics.content.checkout.GlobalDeferredCheckoutOrderManager; import dev.ithundxr.createnumismatics.multiloader.Loader; import dev.ithundxr.createnumismatics.registry.NumismaticsCommands; import dev.ithundxr.createnumismatics.registry.NumismaticsCreativeModeTabs.Tabs; @@ -30,6 +31,7 @@ public class Numismatics { public static final String VERSION = findVersion(); public static final Logger LOGGER = LoggerFactory.getLogger(NAME); public static final GlobalBankManager BANK = new GlobalBankManager(); + public static final GlobalDeferredCheckoutOrderManager DEFERRED_ORDERS = new GlobalDeferredCheckoutOrderManager(); private static final CreateRegistrate REGISTRATE = CreateRegistrate.create(MOD_ID); diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/backend/Coin.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/backend/Coin.java index 22096192..69161a3f 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/backend/Coin.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/backend/Coin.java @@ -134,4 +134,12 @@ public static Coin closest(int value) { } return closest; } + + public static final Coin[] byValueAscending = new Coin[] { + SPUR, BEVEL, SPROCKET, COG, CROWN, SUN + }; + + public static final Coin[] byValueDescending = new Coin[] { + SUN, CROWN, COG, SPROCKET, BEVEL, SPUR + }; } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java new file mode 100644 index 00000000..f712ae70 --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java @@ -0,0 +1,175 @@ +package dev.ithundxr.createnumismatics.content.checkout; + +import com.mojang.datafixers.types.templates.Check; +import com.simibubi.create.foundation.gui.menu.MenuBase; +import dev.ithundxr.createnumismatics.Numismatics; +import dev.ithundxr.createnumismatics.content.backend.BankAccount; +import dev.ithundxr.createnumismatics.content.backend.Coin; +import dev.ithundxr.createnumismatics.content.bank.BankMenu; +import dev.ithundxr.createnumismatics.content.bank.CardItem; +import dev.ithundxr.createnumismatics.content.bank.CardSlot; +import dev.ithundxr.createnumismatics.content.coins.CoinItem; +import dev.ithundxr.createnumismatics.content.coins.SlotInputMergingCoinBag; +import dev.ithundxr.createnumismatics.content.coins.SlotOutputMergingCoinBag; +import dev.ithundxr.createnumismatics.registry.NumismaticsTags; +import dev.ithundxr.createnumismatics.util.Utils; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; + +public class CheckoutMenu extends MenuBase +{ + protected ContainerData dataAccess; + private CheckoutMenu.CardSwitchContainer cardSwitchContainer; + protected UUID currentCardUUID = Utils.emptyUUID; + + public CheckoutMenu(MenuType type, int id, Inventory inv, RegistryFriendlyByteBuf extraData) { + super(type, id, inv, extraData); + } + + public CheckoutMenu(MenuType type, int id, Inventory inv, DeferredCheckoutOrder contentHolder, ContainerData dataAccess) { + super(type, id, inv, contentHolder); + this.dataAccess = dataAccess; + addDataSlots(dataAccess); + } + + @Override + protected DeferredCheckoutOrder createOnClient(RegistryFriendlyByteBuf extraData) { + DeferredCheckoutOrder account = DeferredCheckoutOrder.clientSide(extraData); + this.dataAccess = account.dataAccess; + addDataSlots(dataAccess); + return account; + } + + @Override + protected void initAndReadInventory(DeferredCheckoutOrder contentHolder) {} + + @Override + protected void addSlots() { + if (cardSwitchContainer == null) + cardSwitchContainer = new CheckoutMenu.CardSwitchContainer(this::slotsChanged, (id) -> { + currentCardUUID = id; + return true; + }); + + addSlot(new CardSlot.BoundCardSlot(cardSwitchContainer, 0, 148, 73)); + addPlayerSlots(40, 152); + } + + @Override + protected void saveData(DeferredCheckoutOrder contentHolder) {} + + @Override + public void removed(Player playerIn) { + super.removed(playerIn); + if (playerIn instanceof ServerPlayer) { + clearContainer(player, cardSwitchContainer); + } + } + + @Override + public @NotNull ItemStack quickMoveStack(@NotNull Player player, int index) { // index is slot that was clicked + Slot clickedSlot = this.slots.get(index); + + if (!clickedSlot.hasItem()) + return ItemStack.EMPTY; + + ItemStack slotStack = CoinItem.clearDisplayedCount(clickedSlot.getItem()); + ItemStack returnStack = slotStack.copy(); + + if (slotStack.isEmpty()) { + clickedSlot.set(ItemStack.EMPTY); + } else { + clickedSlot.setChanged(); + } + + return returnStack; + } + + private class CardSwitchContainer implements Container { + private final Consumer slotsChangedCallback; + private final Function uuidChangedCallback; // should return success + + @NotNull + protected final List stacks = new ArrayList<>(); + + public CardSwitchContainer(Consumer slotsChangedCallback, Function uuidChangedCallback) { + this.slotsChangedCallback = slotsChangedCallback; + this.uuidChangedCallback = uuidChangedCallback; + stacks.add(ItemStack.EMPTY); + } + + @Override + public int getContainerSize() { + return 1; + } + + protected ItemStack getStack() { + return stacks.get(0); + } + + @Override + public boolean isEmpty() { + return getStack().isEmpty(); + } + + @Override + public @NotNull ItemStack getItem(int slot) { + return getStack(); + } + + @Override + public @NotNull ItemStack removeItem(int slot, int amount) { + ItemStack stack = ContainerHelper.removeItem(this.stacks, 0, amount); + if (!stack.isEmpty()) { + this.slotsChangedCallback.accept(this); + } + return stack; + } + + @Override + public @NotNull ItemStack removeItemNoUpdate(int slot) { + return ContainerHelper.takeItem(this.stacks, 0); + } + + @Override + public void setItem(int slot, @NotNull ItemStack stack) { + this.stacks.set(0, stack); + if (CardItem.isBound(stack) && NumismaticsTags.AllItemTags.CARDS.matches(stack)) { + if (!this.uuidChangedCallback.apply(CardItem.get(stack))) { + // Non-existent account + stacks.set(0, CardItem.clear(stack)); + CheckoutMenu.this.clearContainer(CheckoutMenu.this.player, this); + } + } + this.slotsChangedCallback.accept(this); + } + + @Override + public void setChanged() {} + + @Override + public boolean stillValid(@NotNull Player player) { + return true; + } + + @Override + public void clearContent() { + this.stacks.set(0, ItemStack.EMPTY); + } + } +} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java new file mode 100644 index 00000000..57bb74d3 --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java @@ -0,0 +1,20 @@ +package dev.ithundxr.createnumismatics.content.checkout; + +import dev.ithundxr.createnumismatics.content.backend.Coin; +import io.netty.buffer.ByteBuf; +import net.createmod.catnip.codecs.stream.CatnipStreamCodecBuilders; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.util.ByIdMap; + +import java.util.function.IntFunction; + +public enum CheckoutPaymentMethod +{ + UNDEFINED, + CARD, + COINS; + + public static final StreamCodec STREAM_CODEC = CatnipStreamCodecBuilders.ofEnum(CheckoutPaymentMethod.class); + +} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java new file mode 100644 index 00000000..07f6b565 --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java @@ -0,0 +1,176 @@ +package dev.ithundxr.createnumismatics.content.checkout; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.vertex.PoseStack; +import com.simibubi.create.AllBlocks; +import com.simibubi.create.foundation.gui.AllGuiTextures; +import com.simibubi.create.foundation.gui.AllIcons; +import com.simibubi.create.foundation.gui.menu.AbstractSimiContainerScreen; +import com.simibubi.create.foundation.gui.widget.IconButton; +import dev.ithundxr.createnumismatics.Numismatics; +import dev.ithundxr.createnumismatics.content.backend.Coin; +import dev.ithundxr.createnumismatics.content.coins.CoinItem; +import dev.ithundxr.createnumismatics.registry.NumismaticsBlocks; +import dev.ithundxr.createnumismatics.registry.NumismaticsGuiTextures; +import dev.ithundxr.createnumismatics.registry.packets.DeferredCheckoutResolutionPacket; +import dev.ithundxr.createnumismatics.util.TextUtils; +import dev.ithundxr.createnumismatics.util.Utils; +import net.createmod.catnip.data.Couple; +import net.createmod.catnip.gui.element.GuiGameElement; +import net.createmod.catnip.platform.CatnipServices; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.renderer.Rect2i; +import net.minecraft.core.UUIDUtil; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class CheckoutScreen extends AbstractSimiContainerScreen +{ + private final NumismaticsGuiTextures background = NumismaticsGuiTextures.CHECKOUT_SCREEN; + private final ItemStack renderedItem = AllBlocks.LIT_BLAZE_BURNER.asStack(); + private List extraAreas = Collections.emptyList(); + + public CheckoutScreen(CheckoutMenu container, Inventory inv, Component title) { + super(container, inv, title); + } + + private Button payWithCoinsButton; + private Button payWithCardButton; + + @Override + protected void init() { + setWindowSize(background.width, background.height + 2 + AllGuiTextures.PLAYER_INVENTORY.getHeight()); + setWindowOffset(-20, 0); + super.init(); + + int x = leftPos; + int y = topPos; + + IconButton abortButton = new IconButton(x + background.width - 33, y + background.height - 24, AllIcons.I_MTD_CLOSE); + abortButton.withCallback(this::onCancelTransaction); + addRenderableWidget(abortButton); + + int btnW = 140; + int btnH = 20; + int gap = 6; + int btnX = x + (background.width - btnW - 10) / 2; + + payWithCoinsButton = Button.builder(Component.literal("Pay with Coins"), b -> onPayWithCoins()) + .pos(btnX, y + 45) + .size(btnW, btnH) + .build(); + updatePayWithCoinsButton(); + addRenderableWidget(payWithCoinsButton); + + payWithCardButton = Button.builder(Component.literal("Pay with Card"), b -> onPayWithCard()) + .pos(btnX, y + 45 + btnH + gap) + .size(btnW - 24, btnH) + .build(); + updatePayWithCardButton(); + addRenderableWidget(payWithCardButton); + + extraAreas = ImmutableList.of(new Rect2i(x + background.width, y + background.height - 64, 84, 74)); + } + + @Override + protected void containerTick() + { + super.containerTick(); + updatePayWithCoinsButton(); + updatePayWithCardButton(); + } + + private void updatePayWithCoinsButton() + { + if (minecraft == null || minecraft.player == null) + return; + + var inventory = minecraft.player.getInventory(); + var coinsOnPlayer = coinsInPlayerInventory(inventory); + payWithCoinsButton.active = menu.contentHolder.costInSpurs <= coinsOnPlayer; + } + + private void updatePayWithCardButton() + { + payWithCardButton.active = !menu.currentCardUUID.equals(Utils.emptyUUID); + } + + @Override + public void onClose() { + onCancelTransaction(); + } + + private void onPayWithCoins() { + onConfirmTransaction(CheckoutPaymentMethod.COINS); + } + + private void onPayWithCard() { + onConfirmTransaction(CheckoutPaymentMethod.CARD); + } + + private void onConfirmTransaction(CheckoutPaymentMethod method) + { + Numismatics.LOGGER.info("Submitting resolution (APPROVED) of deferred order {}", this.menu.contentHolder.id); + CatnipServices.NETWORK.sendToServer(new DeferredCheckoutResolutionPacket(this.menu.contentHolder.id, method, menu.currentCardUUID)); + super.onClose(); + } + + private void onCancelTransaction() + { + Numismatics.LOGGER.info("Submitting resolution (DENIED) of deferred order {}", this.menu.contentHolder.id); + CatnipServices.NETWORK.sendToServer(new DeferredCheckoutResolutionPacket(this.menu.contentHolder.id, CheckoutPaymentMethod.UNDEFINED, Utils.emptyUUID)); + super.onClose(); + } + + private int coinsInPlayerInventory(Inventory inv) + { + var tally = 0; + for (var i = 0; i < inv.getContainerSize(); i++) + { + var stack = inv.getItem(i); + if (stack.getItem() instanceof CoinItem ci) + { + tally += ci.coin.toSpurs(stack.getCount()); + } + } + return tally; + } + + @Override + public List getExtraAreas() { + return extraAreas; + } + + @Override + protected void renderBg(@NotNull GuiGraphics graphics, float partialTick, int mouseX, int mouseY) { + int invX = getLeftOfCentered(AllGuiTextures.PLAYER_INVENTORY.getWidth()); + int invY = topPos + background.height + 2; + renderPlayerInventory(graphics, invX, invY); + + int x = leftPos; + int y = topPos; + + background.render(graphics, x, y); + + GuiGameElement.of(renderedItem).at(x + background.width + 6, y + background.height - 64, -200) + .scale(5) + .render(graphics); + + graphics.drawCenteredString(font, title, x + (background.width - 8) / 2, y + 3, 0xFFFFFF); + + Couple cogsAndSpurs = Coin.COG.convert(menu.contentHolder.costInSpurs); + int cogs = cogsAndSpurs.getFirst(); + int spurs = cogsAndSpurs.getSecond(); + Component balanceLabel = Component.translatable("gui.numismatics.checkout_screen.total", + TextUtils.formatInt(cogs), Coin.COG.getName(cogs), spurs); + graphics.drawCenteredString(font, balanceLabel, x + (background.width - 8) / 2, y + 21, 0xFFFFFF); + } +} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java new file mode 100644 index 00000000..7b84a3bb --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java @@ -0,0 +1,149 @@ +package dev.ithundxr.createnumismatics.content.checkout; + +import com.simibubi.create.AllSoundEvents; +import com.simibubi.create.content.logistics.BigItemStack; +import com.simibubi.create.content.logistics.packager.InventorySummary; +import com.simibubi.create.content.logistics.packagerLink.LogisticallyLinkedBehaviour; +import com.simibubi.create.content.logistics.stockTicker.PackageOrder; +import com.simibubi.create.content.logistics.stockTicker.StockTickerBlockEntity; +import com.simibubi.create.content.logistics.tableCloth.ShoppingListItem; +import com.simibubi.create.foundation.item.SmartInventory; +import com.simibubi.create.foundation.utility.CreateLang; +import dev.ithundxr.createnumismatics.content.coins.CoinItem; +import net.createmod.catnip.data.Couple; +import net.createmod.catnip.data.Iterate; +import net.minecraft.ChatFormatting; +import net.minecraft.server.packs.repository.Pack; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.items.ItemHandlerHelper; + +import java.util.ArrayList; +import java.util.List; + +public class CheckoutUtilities +{ + public static int determineCoinCostInSpurs(ShoppingListItem.ShoppingList list, Level level) + { + Couple bakeEntries = list.bakeEntries(level, null); + InventorySummary paymentEntries = bakeEntries.getSecond(); + int cost = 0; + for (var stack : paymentEntries.getStacksByCount()) + { + if (stack.stack.getItem() instanceof CoinItem coinItem) { + cost += coinItem.coin.toSpurs(stack.count); + } + } + return cost; + } + + public static void denyShopInteraction(Level level, Player player) + { + AllSoundEvents.DENY.playOnServer(level, player.blockPosition()); + CreateLang.translate("stock_keeper.stock_level_too_low") + .style(ChatFormatting.RED) + .sendStatus(player); + } + + public static void shopInteractionSubmitToNetwork(StockTickerBlockEntity tickerBE, PackageOrder order, Player player, Level level) + { + var mainHandItem = player.getMainHandItem(); + tickerBE.broadcastPackageRequest(LogisticallyLinkedBehaviour.RequestType.PLAYER, order, null, ShoppingListItem.getAddress(mainHandItem)); + player.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY); + if (!order.isEmpty()) + AllSoundEvents.STOCK_TICKER_TRADE.playOnServer(level, tickerBE.getBlockPos()); + } + + public static boolean checkOrderPreconditions(StockTickerBlockEntity tickerBE, PackageOrder order, Level level, Player player) + { + // Must be up-to-date + tickerBE.getAccurateSummary(); + + // Check stock levels + InventorySummary recentSummary = tickerBE.getRecentSummary(); + for (BigItemStack entry : order.stacks()) { + if (recentSummary.getCountOf(entry.stack) >= entry.count) + continue; + + denyShopInteraction(level, player); + return false; + } + return true; + } + + public static boolean finishShopInteractionStock( + StockTickerBlockEntity tickerBE, + Level level, + Player player, + InventorySummary paymentEntries, + PackageOrder order, + SmartInventory receivedPayments) + { + if (!checkOrderPreconditions(tickerBE, order, level, player)) + return false; + + // Check space in stock ticker + int occupiedSlots = 0; + for (BigItemStack entry : paymentEntries.getStacksByCount()) + occupiedSlots += Mth.ceil(entry.count / (float) entry.stack.getMaxStackSize()); + for (int i = 0; i < receivedPayments.getSlots(); i++) + if (receivedPayments.getStackInSlot(i) + .isEmpty()) + occupiedSlots--; + + if (occupiedSlots > 0) { + denyPurchase(level, player, "stock_keeper.cash_register_full"); + return false; + } + + // Transfer payment to stock ticker + for (boolean simulate : Iterate.trueAndFalse) { + InventorySummary tally = paymentEntries.copy(); + List toTransfer = new ArrayList<>(); + + for (int i = 0; i < player.getInventory().items.size(); i++) { + ItemStack item = player.getInventory() + .getItem(i); + if (item.isEmpty()) + continue; + int countOf = tally.getCountOf(item); + if (countOf == 0) + continue; + int toRemove = Math.min(item.getCount(), countOf); + tally.add(item, -toRemove); + + if (simulate) + continue; + + int newStackSize = item.getCount() - toRemove; + player.getInventory() + .setItem(i, newStackSize == 0 ? ItemStack.EMPTY : item.copyWithCount(newStackSize)); + toTransfer.add(item.copyWithCount(toRemove)); + } + + if (simulate && tally.getTotalCount() != 0) { + denyPurchase(level, player, "stock_keeper.too_broke"); + return false; + } + + if (simulate) + continue; + + toTransfer.forEach(s -> ItemHandlerHelper.insertItemStacked(receivedPayments, s, false)); + } + + shopInteractionSubmitToNetwork(tickerBE, order, player, level); + return true; + } + + public static void denyPurchase(Level level, Player player, String langKey) + { + AllSoundEvents.DENY.playOnServer(level, player.blockPosition()); + CreateLang.translate(langKey) + .style(ChatFormatting.RED) + .sendStatus(player); + } +} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CoinPaymentUtil.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CoinPaymentUtil.java new file mode 100644 index 00000000..e9335c1c --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CoinPaymentUtil.java @@ -0,0 +1,15 @@ +package dev.ithundxr.createnumismatics.content.checkout; + +import dev.ithundxr.createnumismatics.content.backend.Coin; +import dev.ithundxr.createnumismatics.content.coins.CoinItem; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; + +import java.util.EnumMap; +import java.util.Map; + +public final class CoinPaymentUtil { + + +} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java new file mode 100644 index 00000000..f18c2ece --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -0,0 +1,387 @@ +package dev.ithundxr.createnumismatics.content.checkout; + +import com.simibubi.create.content.logistics.packager.InventorySummary; +import com.simibubi.create.content.logistics.stockTicker.PackageOrder; +import com.simibubi.create.content.logistics.stockTicker.StockTickerBlockEntity; +import com.simibubi.create.content.logistics.tableCloth.ShoppingListItem; +import dev.ithundxr.createnumismatics.Numismatics; +import dev.ithundxr.createnumismatics.content.backend.BankAccount; +import dev.ithundxr.createnumismatics.content.backend.Coin; +import dev.ithundxr.createnumismatics.content.coins.CoinItem; +import dev.ithundxr.createnumismatics.content.coins.DiscreteCoinBag; +import dev.ithundxr.createnumismatics.content.depositor.AbstractDepositorBlockEntity; +import dev.ithundxr.createnumismatics.mixin.MixinStockTickerBlockEntityReceivedPaymentsAccessor; +import dev.ithundxr.createnumismatics.registry.NumismaticsItems; +import dev.ithundxr.createnumismatics.registry.NumismaticsMenuTypes; +import dev.ithundxr.createnumismatics.registry.NumismaticsTags; +import dev.ithundxr.createnumismatics.util.Utils; +import net.createmod.catnip.data.Couple; +import net.createmod.catnip.data.Iterate; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.NonNullList; +import net.minecraft.core.UUIDUtil; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class DeferredCheckoutOrder implements MenuProvider +{ + public UUID id; + public InventorySummary itemCost; + public int costInSpurs; + public PackageOrder deferredOrder; + public Level level; + public ServerPlayer player; + public StockTickerBlockEntity stockTicker; + public boolean finalized = false; + public boolean clientSide; + + public final ContainerData dataAccess = new ContainerData() { + @Override + public int get(int index) { + //Numismatics.LOGGER.warn("BankAccount dataAccess#get called with index " + index + " (Account: "+BankAccount.this+"), returning "+balance); + return costInSpurs; + } + + @Override + public void set(int index, int value) { + Numismatics.LOGGER.warn("BankAccount dataAccess#set called with index " + index + " (Account: "+DeferredCheckoutOrder.this+"), setting balance to "+value); + } + + @Override + public int getCount() { + return 1; + } + }; + + public DeferredCheckoutOrder(UUID orderId, ShoppingListItem.ShoppingList list, Level level, ServerPlayer player, StockTickerBlockEntity stockTicker) + { + Couple bakeEntries = list.bakeEntries(level, null); + InventorySummary paymentEntries = bakeEntries.getSecond(); + + // Determine cost of coin component of order + InventorySummary paymentWithoutCoins = new InventorySummary(); + for (var stack : paymentEntries.getStacksByCount()) + { + if (stack.stack.getItem() instanceof CoinItem coinItem) { + costInSpurs += coinItem.coin.toSpurs(stack.count); + } + else { + paymentWithoutCoins.add(stack); + } + } + + this.id = orderId; + this.itemCost = paymentWithoutCoins; + this.deferredOrder = new PackageOrder(bakeEntries.getFirst().getStacksByCount()); + this.level = level; + this.player = player; + this.stockTicker = stockTicker; + this.clientSide = false; + } + + private DeferredCheckoutOrder(UUID orderId, int costInSpurs) + { + this.clientSide = true; + this.id = orderId; + this.costInSpurs = costInSpurs; + } + + public boolean isTransactionValid() + { + if (finalized) + return false; + + if (clientSide) + return false; + + if (level.isClientSide) + return false; + + if (player.hasDisconnected()) + return false; + + if (stockTicker.isRemoved()) + return false; + + if (costInSpurs == 0) + return false; + + var depositor = getDepositor(stockTicker.getBlockPos(), level); + if (depositor == null) + return false; + + return true; + } + + public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAccountId) + { + if (method == CheckoutPaymentMethod.UNDEFINED) + return false; + + BankAccount account = null; + if (method == CheckoutPaymentMethod.CARD) + { + if (purchasingAccountId.equals(Utils.emptyUUID)) + { + Numismatics.LOGGER.warn("Attempted to complete a card transaction {} with default bank account", id); + return false; + } + + account = Numismatics.BANK.getAccount(purchasingAccountId); + if (account == null) + { + Numismatics.LOGGER.warn("Attempted to complete a card transaction {} with an non-empty, but invalid bank account {}", id, purchasingAccountId); + return false; + } + } + + if (!isTransactionValid()) + { + Numismatics.LOGGER.warn("Attempted to complete an invalid transaction with UUID " + id); + return false; + } + + if (itemCost.isEmpty()) + { + if (!CheckoutUtilities.checkOrderPreconditions(stockTicker, deferredOrder, level, player)) + { + CheckoutUtilities.denyPurchase(level, player, "stock_keeper.too_broke"); + return false; + } + + if (method == CheckoutPaymentMethod.CARD && account.getBalance() < costInSpurs) + { + CheckoutUtilities.denyPurchase(level, player, "stock_keeper.too_broke"); + return false; + } + + if (method == CheckoutPaymentMethod.COINS && !playerHasEnoughCoinsInInventory(player.getInventory(), costInSpurs)) + { + CheckoutUtilities.denyPurchase(level, player, "stock_keeper.too_broke"); + return false; + } + + // If there's no item cost, we can skip a lot of the default create interaction, and just submit the order + CheckoutUtilities.shopInteractionSubmitToNetwork(stockTicker, deferredOrder, player, level); + } + else + { + // There are item costs in the shopping list, so we must submit the order through the standard pipeline. + var receivedPayments = ((MixinStockTickerBlockEntityReceivedPaymentsAccessor)stockTicker).getReceivedPayments(); + if (!CheckoutUtilities.finishShopInteractionStock(stockTicker, level, player, itemCost, deferredOrder, receivedPayments)) + { + // stock checkout failed, cancel the transaction + return false; + } + } + + switch (method) + { + case CARD -> account.deduct(costInSpurs); + case COINS -> tryPayInSpurs(player.getInventory(), costInSpurs); + default -> throw new IllegalStateException("Unexpected value: " + method); + } + + depositCoinsToMerchant(); + return true; + } + + /** + * Attempts to remove the specified number of spurs from the player's inventory. + * - Uses the largest denominations first (greedy). + * - If exact change is not possible with available smaller coins, it takes one larger coin + * and gives change back in smaller coins. + * - If still impossible (not enough value in inventory), returns false and makes no changes. + * + * @param player the player + * @param spursToRemove amount to pay, in spurs (must be >= 0) + * @return true if payment succeeded (inventory adjusted and change returned), false otherwise + */ + public boolean tryPayInSpurs(Inventory inventory, int spursToRemove) { + if (spursToRemove <= 0) + { + return true; // nothing to pay + } + + // 1. Count available coins in the player's inventory + DiscreteCoinBag available = new DiscreteCoinBag(); + for (int i = 0; i < inventory.getContainerSize(); i++) + { + ItemStack stack = inventory.getItem(i); + if (stack.getItem() instanceof CoinItem coinItem) + { + available.add(coinItem.coin, stack.getCount()); + } + } + + if (available.getValue() < spursToRemove) + { + return false; + } + + // 2. Plan which coins to remove (greedy from largest to smallest) + DiscreteCoinBag toRemove = new DiscreteCoinBag(); + int remaining = spursToRemove; + for (Coin coin : Coin.byValueDescending) + { + if (remaining <= 0) + break; + + int canUse = Math.min(available.getDiscrete(coin), remaining / coin.value); + if (canUse > 0) + { + toRemove.add(coin, canUse); + remaining -= coin.toSpurs(canUse); + } + } + + // 3. If we still have remaining spurs to cover, try to break one larger coin + // Find the smallest denomination that is strictly larger than 'remaining' and still available + DiscreteCoinBag changeToAdd = new DiscreteCoinBag(); + if (remaining > 0) + { + // Search ascending for the smallest coin whose value >= remaining and still available + Coin breaker = null; + for (Coin coin : Coin.byValueAscending) + { + int availableCount = available.getDiscrete(coin) - toRemove.getDiscrete(coin); + if (availableCount > 0 && coin.value >= remaining) { + breaker = coin; + break; + } + } + + if (breaker == null) { + // Can't cover the remaining with a single larger coin; payment impossible + return false; + } + + // Use one breaker coin + toRemove.add(breaker, 1); + int overpay = breaker.value - remaining; + + // Make change + changeToAdd = DiscreteCoinBag.of(overpay); + overpay -= changeToAdd.getValue(); + // If we couldn't form exact change (shouldn't happen with canonical set), fail safely + if (overpay != 0) { + return false; + } + } + + // 4. Apply Changes + if (!CoinItem.extract(player, InteractionHand.MAIN_HAND, toRemove.asMap(), true, false)) { + return false; + } + if (!CoinItem.extract(player, InteractionHand.MAIN_HAND, toRemove.asMap(), false, false)) { + return false; + } + + // 5. Return change to the player + for (Coin coin : Coin.values()) { + int count = changeToAdd.getDiscrete(coin); + if (count <= 0) + continue; + + // Split into max stack sizes as needed + int max = coin.asStack().getMaxStackSize(); + int left = count; + while (left > 0) { + int n = Math.min(max, left); + ItemStack change = coin.asStack(n); + player.getInventory().placeItemBackInInventory(change); + left -= n; + } + } + + return true; + } + + private boolean playerHasEnoughCoinsInInventory(Inventory inv, int target) + { + var spursInInventory = 0; + for (int slot = 0; slot < inv.getContainerSize(); slot++) { + var stack = inv.getItem(slot); + if (stack.getItem() instanceof CoinItem coin) + { + spursInInventory += coin.coin.toSpurs(stack.getCount()); + if (spursInInventory >= target) + return true; + } + } + return false; + } + + + private static AbstractDepositorBlockEntity getDepositor(BlockPos tickerPos, Level level) + { + for (Direction side : Iterate.horizontalDirections) { + BlockPos pos = tickerPos.relative(side); + var e = level.getBlockEntity(pos); + if (e instanceof AbstractDepositorBlockEntity) + return (AbstractDepositorBlockEntity)e; + } + return null; + } + + private void depositCoinsToMerchant() + { + var depositor = getDepositor(stockTicker.getBlockPos(), level); + if (depositor == null) + return; + + var account = Numismatics.BANK.getAccount(depositor.getDepositAccount()); + if (account != null) + { + account.deposit(costInSpurs); + } + else + { + var coins = DiscreteCoinBag.of(costInSpurs); + for (var c : Coin.values()) + { + depositor.addCoin(c, coins.getDiscrete(c)); + } + } + } + + + // Menu components + + @Override + public Component getDisplayName() { + return Component.translatable("gui.numismatics.checkout_screen.header"); + } + + @Override + public @Nullable AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) { + return new CheckoutMenu(NumismaticsMenuTypes.CHECKOUT.get(), i, inventory, this, dataAccess); + } + + public void sendToMenu(FriendlyByteBuf buf) { + buf.writeUUID(this.id); + buf.writeVarInt(this.costInSpurs); + } + + public static DeferredCheckoutOrder clientSide(FriendlyByteBuf buf) { + return new DeferredCheckoutOrder(buf.readUUID(), buf.readVarInt()); + } + + public static boolean isPowerOfTwo(int x) + { + return (x & (x - 1)) == 0; + } +} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java new file mode 100644 index 00000000..0dbd1e8d --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java @@ -0,0 +1,58 @@ +package dev.ithundxr.createnumismatics.content.checkout; + +import com.simibubi.create.content.logistics.stockTicker.StockTickerBlockEntity; +import com.simibubi.create.content.logistics.tableCloth.ShoppingListItem; +import dev.ithundxr.createnumismatics.Numismatics; +import dev.ithundxr.createnumismatics.util.Utils; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.Level; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class GlobalDeferredCheckoutOrderManager +{ + public Map deferredOrders; + + public GlobalDeferredCheckoutOrderManager() + { + deferredOrders = new HashMap<>(); + } + + private void warnIfClient() { + if (Thread.currentThread().getName().equals("Render thread")) { + long start = System.currentTimeMillis(); + Numismatics.LOGGER.error("Deferred Checkout manager should not be accessed on the client"); // set breakpoint here when developing + if (Utils.isDevEnv()) { + long end = System.currentTimeMillis(); + if (end - start < 50) { // crash if breakpoint wasn't set + throw new RuntimeException("Illegal checkout performed on client, please set a breakpoint above"); + } + } else { + Numismatics.LOGGER.error("Stacktrace: ", new RuntimeException("Illegal checkout access performed on client")); + } + } + } + + public DeferredCheckoutOrder deferOrder(ShoppingListItem.ShoppingList list, Level level, ServerPlayer player, StockTickerBlockEntity stockTicker) + { + warnIfClient(); + var order = new DeferredCheckoutOrder(UUID.randomUUID(), list, level, player, stockTicker); + deferredOrders.put(order.id, order); + return order; + } + + public DeferredCheckoutOrder getDeferredOrder(UUID id) + { + warnIfClient(); + return deferredOrders.get(id); + } + + public void voidOrder(DeferredCheckoutOrder order) + { + warnIfClient(); + order.finalized = true; + deferredOrders.remove(order.id); + } +} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java index ca92678e..17b23516 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java @@ -76,6 +76,8 @@ public ItemStack asStack(Coin coin) { return NumismaticsItems.getCoin(coin).asStack(amt); } + public Map asMap() { return this.coins; } + @Override public int getValue() { return value; @@ -116,6 +118,20 @@ public static DiscreteCoinBag of(Map coins) { return new DiscreteCoinBag(coins); } + public static DiscreteCoinBag of(int totalSpurValue) + { + var bag = new DiscreteCoinBag(); + int spurs = totalSpurValue; + for (var coin : Coin.byValueDescending) + { + var tuple = coin.convert(spurs); + if (tuple.getFirst() != 0) + bag.add(coin, tuple.getFirst()); + spurs = tuple.getSecond(); + } + return bag; + } + public static DiscreteCoinBag of() { return new DiscreteCoinBag(); } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerBlockEntityReceivedPaymentsAccessor.java b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerBlockEntityReceivedPaymentsAccessor.java new file mode 100644 index 00000000..debd5b35 --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerBlockEntityReceivedPaymentsAccessor.java @@ -0,0 +1,13 @@ +package dev.ithundxr.createnumismatics.mixin; + +import com.simibubi.create.content.logistics.stockTicker.StockTickerBlockEntity; +import com.simibubi.create.foundation.item.SmartInventory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(StockTickerBlockEntity.class) +public interface MixinStockTickerBlockEntityReceivedPaymentsAccessor +{ + @Accessor + SmartInventory getReceivedPayments(); +} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java new file mode 100644 index 00000000..dd7ae8e9 --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java @@ -0,0 +1,69 @@ +package dev.ithundxr.createnumismatics.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import com.simibubi.create.AllSoundEvents; +import com.simibubi.create.content.logistics.packager.InventorySummary; +import com.simibubi.create.content.logistics.packagerLink.LogisticallyLinkedBehaviour; +import com.simibubi.create.content.logistics.stockTicker.PackageOrder; +import com.simibubi.create.content.logistics.stockTicker.StockTickerBlockEntity; +import com.simibubi.create.content.logistics.stockTicker.StockTickerInteractionHandler; +import com.simibubi.create.content.logistics.tableCloth.ShoppingListItem; +import com.simibubi.create.foundation.utility.CreateLang; +import dev.ithundxr.createnumismatics.Numismatics; +import dev.ithundxr.createnumismatics.content.backend.BankAccount; +import dev.ithundxr.createnumismatics.content.backend.Coin; +import dev.ithundxr.createnumismatics.content.bank.CardItem; +import dev.ithundxr.createnumismatics.content.checkout.DeferredCheckoutOrder; +import dev.ithundxr.createnumismatics.content.coins.CoinItem; +import dev.ithundxr.createnumismatics.content.depositor.AbstractDepositorBlockEntity; +import dev.ithundxr.createnumismatics.util.Utils; +import net.createmod.catnip.data.Iterate; +import net.minecraft.ChatFormatting; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import javax.smartcardio.Card; +import java.util.Arrays; + +@Mixin(StockTickerInteractionHandler.class) +public class MixinStockTickerInteractionHandler +{ + @Inject(method = "interactWithShop", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/logistics/stockTicker/StockTickerBlockEntity;getAccurateSummary()Lcom/simibubi/create/content/logistics/packager/InventorySummary;"), cancellable = true) + private static void interactWithShop( + Player player, + Level level, + BlockPos targetPos, + ItemStack mainHandItem, + CallbackInfo ci, + @Local ShoppingListItem.ShoppingList shoppingList, + @Local StockTickerBlockEntity tickerBE + ) + { + + // Build the deferred order and check to see if all preconditions are being met + var deferredOrder = Numismatics.DEFERRED_ORDERS.deferOrder(shoppingList, level, (ServerPlayer) player, tickerBE); + if (!deferredOrder.isTransactionValid()) + { + // Transaction isn't valid, backout! + Numismatics.DEFERRED_ORDERS.voidOrder(deferredOrder); + return; + } + + // At this point, we've determined this is a numismatics transaction, + // and we will *not* be allowing the standard trade to complete now. We must defer it + ci.cancel(); + + Utils.openScreen((ServerPlayer) player, deferredOrder, deferredOrder::sendToMenu); + } + +} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsGuiTextures.java b/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsGuiTextures.java index 396f1121..9f34b5bc 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsGuiTextures.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsGuiTextures.java @@ -22,6 +22,7 @@ public enum NumismaticsGuiTextures implements ScreenElement { BLAZE_BANKER("blaze_banker",200, 110), VENDOR("vendor", 236, 145), CREATIVE_VENDOR("creative_vendor", 236, 145), + CHECKOUT_SCREEN("checkout_screen",200, 132), ; public static final int FONT_COLOR = 0x575F7A; diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsMenuTypes.java b/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsMenuTypes.java index f95581f7..876d92db 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsMenuTypes.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsMenuTypes.java @@ -9,6 +9,8 @@ import dev.ithundxr.createnumismatics.content.backend.trust_list.TrustListScreen; import dev.ithundxr.createnumismatics.content.bank.blaze_banker.BlazeBankerMenu; import dev.ithundxr.createnumismatics.content.bank.blaze_banker.BlazeBankerScreen; +import dev.ithundxr.createnumismatics.content.checkout.CheckoutMenu; +import dev.ithundxr.createnumismatics.content.checkout.CheckoutScreen; import dev.ithundxr.createnumismatics.content.depositor.AndesiteDepositorMenu; import dev.ithundxr.createnumismatics.content.depositor.AndesiteDepositorScreen; import dev.ithundxr.createnumismatics.content.bank.BankMenu; @@ -60,6 +62,12 @@ public class NumismaticsMenuTypes { () -> VendorScreen::new ); + public static final MenuEntry CHECKOUT = register( + "checkout", + CheckoutMenu::new, + () -> CheckoutScreen::new + ); + private static > MenuEntry register( String name, MenuBuilder.ForgeMenuFactory factory, NonNullSupplier> screenFactory) { return REGISTRATE 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 6be5a273..b583c386 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsPackets.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsPackets.java @@ -20,6 +20,7 @@ public enum NumismaticsPackets implements PacketTypeProvider { ANDESITE_DEPOSITOR_CONFIGURATION(AndesiteDepositorConfigurationPacket.class, AndesiteDepositorConfigurationPacket.STREAM_CODEC), OPEN_TRUST_LIST(OpenTrustListPacket.class, OpenTrustListPacket.STREAM_CODEC), VENDOR_CONFIGURATION(VendorConfigurationPacket.class, VendorConfigurationPacket.STREAM_CODEC), + DEFERRED_CHECKOUT_RESOLUTION(DeferredCheckoutResolutionPacket.class, DeferredCheckoutResolutionPacket.STREAM_CODEC), // S2C BANK_ACCOUNT_LABEL(BankAccountLabelPacket.class, BankAccountLabelPacket.STREAM_CODEC), diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/registry/packets/DeferredCheckoutResolutionPacket.java b/common/src/main/java/dev/ithundxr/createnumismatics/registry/packets/DeferredCheckoutResolutionPacket.java new file mode 100644 index 00000000..cdc53e18 --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/registry/packets/DeferredCheckoutResolutionPacket.java @@ -0,0 +1,41 @@ +package dev.ithundxr.createnumismatics.registry.packets; + +import dev.ithundxr.createnumismatics.Numismatics; +import dev.ithundxr.createnumismatics.content.checkout.CheckoutPaymentMethod; +import dev.ithundxr.createnumismatics.registry.NumismaticsPackets; +import io.netty.buffer.ByteBuf; +import net.createmod.catnip.net.base.ServerboundPacketPayload; +import net.minecraft.core.UUIDUtil; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.server.level.ServerPlayer; + +import java.util.UUID; + +public record DeferredCheckoutResolutionPacket( + UUID transactionId, CheckoutPaymentMethod method, UUID bankAccount) implements ServerboundPacketPayload +{ + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + UUIDUtil.STREAM_CODEC, DeferredCheckoutResolutionPacket::transactionId, + CheckoutPaymentMethod.STREAM_CODEC, DeferredCheckoutResolutionPacket::method, + UUIDUtil.STREAM_CODEC, DeferredCheckoutResolutionPacket::bankAccount, + DeferredCheckoutResolutionPacket::new + ); + + @Override + public void handle(ServerPlayer player) + { + Numismatics.LOGGER.info("Received checkout packet! Transaction: {}; Method: {}; BankAccount: {}", transactionId, method, bankAccount); + var order = Numismatics.DEFERRED_ORDERS.getDeferredOrder(transactionId); + if (order == null) + return; + + order.completePurchase(method, bankAccount); + Numismatics.DEFERRED_ORDERS.voidOrder(order); + } + + @Override + public PacketTypeProvider getTypeProvider() { + return NumismaticsPackets.DEFERRED_CHECKOUT_RESOLUTION; + } +} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/util/Utils.java b/common/src/main/java/dev/ithundxr/createnumismatics/util/Utils.java index 9fa761f9..947d296e 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/util/Utils.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/util/Utils.java @@ -9,6 +9,7 @@ import net.minecraft.world.entity.player.Player; import org.apache.commons.lang3.mutable.MutableObject; +import java.util.UUID; import java.util.function.Consumer; import java.util.function.Predicate; @@ -28,4 +29,6 @@ public static boolean testClientPlayerOrElse(Predicate predicate, boolea public static void openScreen(ServerPlayer player, MenuProvider factory, Consumer extraDataWriter) { throw new AssertionError(); } + + public static final UUID emptyUUID = new UUID(0L, 0L); } 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 b69e72cc..d6be2fd9 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,8 @@ "gui.numismatics.vendor.full": "Vendor is full", "gui.numismatics.vendor.full.named": "Vendor is full, contact %s to empty it", "gui.numismatics.vendor.no_item_in_hand": "Hold the stack of items you want to sell", + "gui.numismatics.checkout_screen.total": "Order Total: %s %s, %s¤", + "gui.numismatics.checkout_screen.header": "Checkout", "block.numismatics.trusted_block.attempt_break": "Hold shift to break this block" } \ No newline at end of file diff --git a/common/src/main/resources/assets/numismatics/textures/gui/checkout_screen.png b/common/src/main/resources/assets/numismatics/textures/gui/checkout_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..fc0accb6e3bac648df74582997042c89e44cf47f GIT binary patch literal 2822 zcmeH}Sx}Q%7RT@RH3?4J&@AG@0Lqe2wg}2*D~#ChOh)-LRC-A+tkd%)V%b=sq;JM{;STp z_nupIF9!Hg^>L;+0D!*tPLDkRKw5+VcA3_N$CVygis*Z&Zs1|-+CgpL@k5CB5no?m zsbwsH&PM}eNuo7VtpR{3gaAhCp`~0Q{LfVg6k`6xOTwrjQ*QupvECj#_MU-8dnhs8 z?@V+feXhywS->h|9p}SUBDZVB-w%*qNQ-A-{gqX2;EK zdq1SH_10JnJ>9_!e9I3^E)7_}&)nE%(X35_-DKhOb@TK#<-Er)_q;p^mtK`#B0RpYKJK5Q5I>*5qmKC}{X!_I#qu$BiZ_j?*B>lS!E&i-sP z3WB7!e#Rl6jPPLgX|WGB-WU(^U-D2g34*fy!^+S8M-tBC@uDRACoty(@=d}9ist3` zu%HCNdKlf@{mo!IKib82+FxT;WpXn&IPi;{UchYV^XgOzHqu0XE~8Cz(X^nhELm>Y z0b5ByO?X6%OT>ZkrwJWhL0`5@2BWKzueEh{m(w2HxidAxy+bKB8Oj`ay|dnNZSez-DI>=Q;qe7srrok#xCpB+XydZ#(GE-fkSvIZ)ZweRV4I+Omk>{F4zA{Ts3lQ>eBovi(yA>8cPC*n;s_5h= zO^Xd{jeF(7An@DUw{F%|e`EHkPc-sP)OwsCU}u<9-n1AGTd!NV6}N^#DQ%6G_uL3DfF;- z)^w7(B^_8j{C+cxY%wam3KEl&D&o{?^_FjUnIKr+dd1fY zOP}1A`^l<{?E0A@0_Rk3{SCT%MJBpoV&Mx@gjJQ* z_`1m(ekk~eL0~;BTbx-H55p*lj1NHpaw?^G6&}n1ju?i?u^+7R*+8m`MYJ!4^A&9n zLD;S+`4JmnduDvh$v_3c932EzJP+OuL4}6@mr}d&_&N)Yx&IxWuuL;OPDmQ-+0S&c zf0V}tA(EGStZ+q8L+eQk>iF%l>U3B5spHT1 z3meGbP<8FG^UpKOzAqjaICAxe z{ECu5eupajc<4ox2uH6tE8Ir~va%J}ZVXItHO(Mu_PuVSz(w1F<8+Anogs6rt$ve3 zZz`;YS+=yshUdpLQ|*4Ry252dpJTNe4}xEfHe*%V_Ef*8j^<&Gvu%LLm?%;~XwVWe~ef=bVke7d4R6`iRb>59TA6pEPUj*cvuNc?QU2?acvfakQ6 zbKa0-OiP_=IwqG96g4$9&(5*wAbsRnA|rxx|32&b?VG8K8#2TxM|$-QPozSd9xZz! zAehI-$J@z6e#PU#=Zy+K12~K5V%aV)ul;C720^3W<_-%{E=O)%V!%<|m^n&?;li#} zJtQ7VpD0jLbS3yR8uiII>}k#wu;v7}_CvL}K3&w;Do7e15_n$6PsE6ma|E&|4kO!E zBun7pF$2hC2sOscHC?AmQ?D zb!g!>u}thRV_U*U#LbTHUaiL3xfiDKWyFetumsb|d{K=Ns6IPLM)EL?$%_q6q~r#c$H=UPs5+-b(khM}t^Do6l=$=?T^T>&|yl`-Kcn)VjLr4`<*%=v3msLB@&kZsb zUe|lh2Ftngh?pDioKy)fO}- z85;|7;@8%84(C_XXK~JFCdl&ljPU*SZ_WOm(B^UqBS;j&U9<_hu;D;HfM5iRC!_2? j;EzN2H Date: Tue, 12 Aug 2025 18:37:19 -0400 Subject: [PATCH 02/19] Code reformat only --- .../content/checkout/CheckoutMenu.java | 19 +-- .../checkout/CheckoutPaymentMethod.java | 8 +- .../content/checkout/CheckoutScreen.java | 31 ++--- .../content/checkout/CheckoutUtilities.java | 25 ++-- .../content/checkout/CoinPaymentUtil.java | 15 --- .../checkout/DeferredCheckoutOrder.java | 120 ++++++------------ .../GlobalDeferredCheckoutOrderManager.java | 15 +-- .../content/coins/DiscreteCoinBag.java | 3 +- ...erBlockEntityReceivedPaymentsAccessor.java | 3 +- .../MixinStockTickerInteractionHandler.java | 28 +--- .../registry/NumismaticsGuiTextures.java | 2 +- .../DeferredCheckoutResolutionPacket.java | 7 +- 12 files changed, 78 insertions(+), 198 deletions(-) delete mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CoinPaymentUtil.java diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java index f712ae70..3cd9c62a 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java @@ -1,16 +1,9 @@ package dev.ithundxr.createnumismatics.content.checkout; -import com.mojang.datafixers.types.templates.Check; import com.simibubi.create.foundation.gui.menu.MenuBase; -import dev.ithundxr.createnumismatics.Numismatics; -import dev.ithundxr.createnumismatics.content.backend.BankAccount; -import dev.ithundxr.createnumismatics.content.backend.Coin; -import dev.ithundxr.createnumismatics.content.bank.BankMenu; import dev.ithundxr.createnumismatics.content.bank.CardItem; import dev.ithundxr.createnumismatics.content.bank.CardSlot; import dev.ithundxr.createnumismatics.content.coins.CoinItem; -import dev.ithundxr.createnumismatics.content.coins.SlotInputMergingCoinBag; -import dev.ithundxr.createnumismatics.content.coins.SlotOutputMergingCoinBag; import dev.ithundxr.createnumismatics.registry.NumismaticsTags; import dev.ithundxr.createnumismatics.util.Utils; import net.minecraft.network.RegistryFriendlyByteBuf; @@ -31,8 +24,7 @@ import java.util.function.Consumer; import java.util.function.Function; -public class CheckoutMenu extends MenuBase -{ +public class CheckoutMenu extends MenuBase { protected ContainerData dataAccess; private CheckoutMenu.CardSwitchContainer cardSwitchContainer; protected UUID currentCardUUID = Utils.emptyUUID; @@ -56,7 +48,8 @@ protected DeferredCheckoutOrder createOnClient(RegistryFriendlyByteBuf extraData } @Override - protected void initAndReadInventory(DeferredCheckoutOrder contentHolder) {} + protected void initAndReadInventory(DeferredCheckoutOrder contentHolder) { + } @Override protected void addSlots() { @@ -71,7 +64,8 @@ protected void addSlots() { } @Override - protected void saveData(DeferredCheckoutOrder contentHolder) {} + protected void saveData(DeferredCheckoutOrder contentHolder) { + } @Override public void removed(Player playerIn) { @@ -160,7 +154,8 @@ public void setItem(int slot, @NotNull ItemStack stack) { } @Override - public void setChanged() {} + public void setChanged() { + } @Override public boolean stillValid(@NotNull Player player) { diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java index 57bb74d3..dbef6c8f 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java @@ -1,16 +1,10 @@ package dev.ithundxr.createnumismatics.content.checkout; -import dev.ithundxr.createnumismatics.content.backend.Coin; import io.netty.buffer.ByteBuf; import net.createmod.catnip.codecs.stream.CatnipStreamCodecBuilders; -import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; -import net.minecraft.util.ByIdMap; -import java.util.function.IntFunction; - -public enum CheckoutPaymentMethod -{ +public enum CheckoutPaymentMethod { UNDEFINED, CARD, COINS; diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java index 07f6b565..63a95fcb 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java @@ -1,7 +1,6 @@ package dev.ithundxr.createnumismatics.content.checkout; import com.google.common.collect.ImmutableList; -import com.mojang.blaze3d.vertex.PoseStack; import com.simibubi.create.AllBlocks; import com.simibubi.create.foundation.gui.AllGuiTextures; import com.simibubi.create.foundation.gui.AllIcons; @@ -10,7 +9,6 @@ import dev.ithundxr.createnumismatics.Numismatics; import dev.ithundxr.createnumismatics.content.backend.Coin; import dev.ithundxr.createnumismatics.content.coins.CoinItem; -import dev.ithundxr.createnumismatics.registry.NumismaticsBlocks; import dev.ithundxr.createnumismatics.registry.NumismaticsGuiTextures; import dev.ithundxr.createnumismatics.registry.packets.DeferredCheckoutResolutionPacket; import dev.ithundxr.createnumismatics.util.TextUtils; @@ -21,7 +19,6 @@ import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.client.renderer.Rect2i; -import net.minecraft.core.UUIDUtil; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.item.ItemStack; @@ -29,10 +26,8 @@ import java.util.Collections; import java.util.List; -import java.util.UUID; -public class CheckoutScreen extends AbstractSimiContainerScreen -{ +public class CheckoutScreen extends AbstractSimiContainerScreen { private final NumismaticsGuiTextures background = NumismaticsGuiTextures.CHECKOUT_SCREEN; private final ItemStack renderedItem = AllBlocks.LIT_BLAZE_BURNER.asStack(); private List extraAreas = Collections.emptyList(); @@ -80,15 +75,13 @@ protected void init() { } @Override - protected void containerTick() - { + protected void containerTick() { super.containerTick(); updatePayWithCoinsButton(); updatePayWithCardButton(); } - private void updatePayWithCoinsButton() - { + private void updatePayWithCoinsButton() { if (minecraft == null || minecraft.player == null) return; @@ -97,8 +90,7 @@ private void updatePayWithCoinsButton() payWithCoinsButton.active = menu.contentHolder.costInSpurs <= coinsOnPlayer; } - private void updatePayWithCardButton() - { + private void updatePayWithCardButton() { payWithCardButton.active = !menu.currentCardUUID.equals(Utils.emptyUUID); } @@ -115,28 +107,23 @@ private void onPayWithCard() { onConfirmTransaction(CheckoutPaymentMethod.CARD); } - private void onConfirmTransaction(CheckoutPaymentMethod method) - { + private void onConfirmTransaction(CheckoutPaymentMethod method) { Numismatics.LOGGER.info("Submitting resolution (APPROVED) of deferred order {}", this.menu.contentHolder.id); CatnipServices.NETWORK.sendToServer(new DeferredCheckoutResolutionPacket(this.menu.contentHolder.id, method, menu.currentCardUUID)); super.onClose(); } - private void onCancelTransaction() - { + private void onCancelTransaction() { Numismatics.LOGGER.info("Submitting resolution (DENIED) of deferred order {}", this.menu.contentHolder.id); CatnipServices.NETWORK.sendToServer(new DeferredCheckoutResolutionPacket(this.menu.contentHolder.id, CheckoutPaymentMethod.UNDEFINED, Utils.emptyUUID)); super.onClose(); } - private int coinsInPlayerInventory(Inventory inv) - { + private int coinsInPlayerInventory(Inventory inv) { var tally = 0; - for (var i = 0; i < inv.getContainerSize(); i++) - { + for (var i = 0; i < inv.getContainerSize(); i++) { var stack = inv.getItem(i); - if (stack.getItem() instanceof CoinItem ci) - { + if (stack.getItem() instanceof CoinItem ci) { tally += ci.coin.toSpurs(stack.getCount()); } } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java index 7b84a3bb..e12d10f6 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java @@ -13,7 +13,6 @@ import net.createmod.catnip.data.Couple; import net.createmod.catnip.data.Iterate; import net.minecraft.ChatFormatting; -import net.minecraft.server.packs.repository.Pack; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.player.Player; @@ -24,15 +23,12 @@ import java.util.ArrayList; import java.util.List; -public class CheckoutUtilities -{ - public static int determineCoinCostInSpurs(ShoppingListItem.ShoppingList list, Level level) - { +public class CheckoutUtilities { + public static int determineCoinCostInSpurs(ShoppingListItem.ShoppingList list, Level level) { Couple bakeEntries = list.bakeEntries(level, null); InventorySummary paymentEntries = bakeEntries.getSecond(); int cost = 0; - for (var stack : paymentEntries.getStacksByCount()) - { + for (var stack : paymentEntries.getStacksByCount()) { if (stack.stack.getItem() instanceof CoinItem coinItem) { cost += coinItem.coin.toSpurs(stack.count); } @@ -40,16 +36,14 @@ public static int determineCoinCostInSpurs(ShoppingListItem.ShoppingList list, L return cost; } - public static void denyShopInteraction(Level level, Player player) - { + public static void denyShopInteraction(Level level, Player player) { AllSoundEvents.DENY.playOnServer(level, player.blockPosition()); CreateLang.translate("stock_keeper.stock_level_too_low") .style(ChatFormatting.RED) .sendStatus(player); } - public static void shopInteractionSubmitToNetwork(StockTickerBlockEntity tickerBE, PackageOrder order, Player player, Level level) - { + public static void shopInteractionSubmitToNetwork(StockTickerBlockEntity tickerBE, PackageOrder order, Player player, Level level) { var mainHandItem = player.getMainHandItem(); tickerBE.broadcastPackageRequest(LogisticallyLinkedBehaviour.RequestType.PLAYER, order, null, ShoppingListItem.getAddress(mainHandItem)); player.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY); @@ -57,8 +51,7 @@ public static void shopInteractionSubmitToNetwork(StockTickerBlockEntity tickerB AllSoundEvents.STOCK_TICKER_TRADE.playOnServer(level, tickerBE.getBlockPos()); } - public static boolean checkOrderPreconditions(StockTickerBlockEntity tickerBE, PackageOrder order, Level level, Player player) - { + public static boolean checkOrderPreconditions(StockTickerBlockEntity tickerBE, PackageOrder order, Level level, Player player) { // Must be up-to-date tickerBE.getAccurateSummary(); @@ -80,8 +73,7 @@ public static boolean finishShopInteractionStock( Player player, InventorySummary paymentEntries, PackageOrder order, - SmartInventory receivedPayments) - { + SmartInventory receivedPayments) { if (!checkOrderPreconditions(tickerBE, order, level, player)) return false; @@ -139,8 +131,7 @@ public static boolean finishShopInteractionStock( return true; } - public static void denyPurchase(Level level, Player player, String langKey) - { + public static void denyPurchase(Level level, Player player, String langKey) { AllSoundEvents.DENY.playOnServer(level, player.blockPosition()); CreateLang.translate(langKey) .style(ChatFormatting.RED) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CoinPaymentUtil.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CoinPaymentUtil.java deleted file mode 100644 index e9335c1c..00000000 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CoinPaymentUtil.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.ithundxr.createnumismatics.content.checkout; - -import dev.ithundxr.createnumismatics.content.backend.Coin; -import dev.ithundxr.createnumismatics.content.coins.CoinItem; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; - -import java.util.EnumMap; -import java.util.Map; - -public final class CoinPaymentUtil { - - -} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index f18c2ece..2ebd0320 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -11,16 +11,12 @@ import dev.ithundxr.createnumismatics.content.coins.DiscreteCoinBag; import dev.ithundxr.createnumismatics.content.depositor.AbstractDepositorBlockEntity; import dev.ithundxr.createnumismatics.mixin.MixinStockTickerBlockEntityReceivedPaymentsAccessor; -import dev.ithundxr.createnumismatics.registry.NumismaticsItems; import dev.ithundxr.createnumismatics.registry.NumismaticsMenuTypes; -import dev.ithundxr.createnumismatics.registry.NumismaticsTags; import dev.ithundxr.createnumismatics.util.Utils; import net.createmod.catnip.data.Couple; import net.createmod.catnip.data.Iterate; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.core.NonNullList; -import net.minecraft.core.UUIDUtil; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; @@ -34,10 +30,9 @@ import net.minecraft.world.level.Level; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.UUID; -public class DeferredCheckoutOrder implements MenuProvider -{ +public class DeferredCheckoutOrder implements MenuProvider { public UUID id; public InventorySummary itemCost; public int costInSpurs; @@ -57,7 +52,7 @@ public int get(int index) { @Override public void set(int index, int value) { - Numismatics.LOGGER.warn("BankAccount dataAccess#set called with index " + index + " (Account: "+DeferredCheckoutOrder.this+"), setting balance to "+value); + Numismatics.LOGGER.warn("BankAccount dataAccess#set called with index " + index + " (Account: " + DeferredCheckoutOrder.this + "), setting balance to " + value); } @Override @@ -66,19 +61,16 @@ public int getCount() { } }; - public DeferredCheckoutOrder(UUID orderId, ShoppingListItem.ShoppingList list, Level level, ServerPlayer player, StockTickerBlockEntity stockTicker) - { + public DeferredCheckoutOrder(UUID orderId, ShoppingListItem.ShoppingList list, Level level, ServerPlayer player, StockTickerBlockEntity stockTicker) { Couple bakeEntries = list.bakeEntries(level, null); InventorySummary paymentEntries = bakeEntries.getSecond(); // Determine cost of coin component of order InventorySummary paymentWithoutCoins = new InventorySummary(); - for (var stack : paymentEntries.getStacksByCount()) - { + for (var stack : paymentEntries.getStacksByCount()) { if (stack.stack.getItem() instanceof CoinItem coinItem) { costInSpurs += coinItem.coin.toSpurs(stack.count); - } - else { + } else { paymentWithoutCoins.add(stack); } } @@ -92,15 +84,13 @@ public DeferredCheckoutOrder(UUID orderId, ShoppingListItem.ShoppingList list, L this.clientSide = false; } - private DeferredCheckoutOrder(UUID orderId, int costInSpurs) - { + private DeferredCheckoutOrder(UUID orderId, int costInSpurs) { this.clientSide = true; this.id = orderId; this.costInSpurs = costInSpurs; } - public boolean isTransactionValid() - { + public boolean isTransactionValid() { if (finalized) return false; @@ -126,70 +116,57 @@ public boolean isTransactionValid() return true; } - public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAccountId) - { + public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAccountId) { if (method == CheckoutPaymentMethod.UNDEFINED) return false; BankAccount account = null; - if (method == CheckoutPaymentMethod.CARD) - { - if (purchasingAccountId.equals(Utils.emptyUUID)) - { + if (method == CheckoutPaymentMethod.CARD) { + if (purchasingAccountId.equals(Utils.emptyUUID)) { Numismatics.LOGGER.warn("Attempted to complete a card transaction {} with default bank account", id); return false; } account = Numismatics.BANK.getAccount(purchasingAccountId); - if (account == null) - { + if (account == null) { Numismatics.LOGGER.warn("Attempted to complete a card transaction {} with an non-empty, but invalid bank account {}", id, purchasingAccountId); return false; } } - if (!isTransactionValid()) - { + if (!isTransactionValid()) { Numismatics.LOGGER.warn("Attempted to complete an invalid transaction with UUID " + id); return false; } - if (itemCost.isEmpty()) - { - if (!CheckoutUtilities.checkOrderPreconditions(stockTicker, deferredOrder, level, player)) - { + if (itemCost.isEmpty()) { + if (!CheckoutUtilities.checkOrderPreconditions(stockTicker, deferredOrder, level, player)) { CheckoutUtilities.denyPurchase(level, player, "stock_keeper.too_broke"); return false; } - if (method == CheckoutPaymentMethod.CARD && account.getBalance() < costInSpurs) - { + if (method == CheckoutPaymentMethod.CARD && account.getBalance() < costInSpurs) { CheckoutUtilities.denyPurchase(level, player, "stock_keeper.too_broke"); return false; } - if (method == CheckoutPaymentMethod.COINS && !playerHasEnoughCoinsInInventory(player.getInventory(), costInSpurs)) - { + if (method == CheckoutPaymentMethod.COINS && !playerHasEnoughCoinsInInventory(player.getInventory(), costInSpurs)) { CheckoutUtilities.denyPurchase(level, player, "stock_keeper.too_broke"); return false; } // If there's no item cost, we can skip a lot of the default create interaction, and just submit the order CheckoutUtilities.shopInteractionSubmitToNetwork(stockTicker, deferredOrder, player, level); - } - else - { + } else { // There are item costs in the shopping list, so we must submit the order through the standard pipeline. - var receivedPayments = ((MixinStockTickerBlockEntityReceivedPaymentsAccessor)stockTicker).getReceivedPayments(); - if (!CheckoutUtilities.finishShopInteractionStock(stockTicker, level, player, itemCost, deferredOrder, receivedPayments)) - { + var receivedPayments = ((MixinStockTickerBlockEntityReceivedPaymentsAccessor) stockTicker).getReceivedPayments(); + if (!CheckoutUtilities.finishShopInteractionStock(stockTicker, level, player, itemCost, deferredOrder, receivedPayments)) { // stock checkout failed, cancel the transaction return false; } } - switch (method) - { + switch (method) { case CARD -> account.deduct(costInSpurs); case COINS -> tryPayInSpurs(player.getInventory(), costInSpurs); default -> throw new IllegalStateException("Unexpected value: " + method); @@ -203,46 +180,40 @@ public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAcc * Attempts to remove the specified number of spurs from the player's inventory. * - Uses the largest denominations first (greedy). * - If exact change is not possible with available smaller coins, it takes one larger coin - * and gives change back in smaller coins. + * and gives change back in smaller coins. * - If still impossible (not enough value in inventory), returns false and makes no changes. * - * @param player the player + * @param player the player * @param spursToRemove amount to pay, in spurs (must be >= 0) * @return true if payment succeeded (inventory adjusted and change returned), false otherwise */ public boolean tryPayInSpurs(Inventory inventory, int spursToRemove) { - if (spursToRemove <= 0) - { + if (spursToRemove <= 0) { return true; // nothing to pay } // 1. Count available coins in the player's inventory DiscreteCoinBag available = new DiscreteCoinBag(); - for (int i = 0; i < inventory.getContainerSize(); i++) - { + for (int i = 0; i < inventory.getContainerSize(); i++) { ItemStack stack = inventory.getItem(i); - if (stack.getItem() instanceof CoinItem coinItem) - { + if (stack.getItem() instanceof CoinItem coinItem) { available.add(coinItem.coin, stack.getCount()); } } - if (available.getValue() < spursToRemove) - { + if (available.getValue() < spursToRemove) { return false; } // 2. Plan which coins to remove (greedy from largest to smallest) DiscreteCoinBag toRemove = new DiscreteCoinBag(); int remaining = spursToRemove; - for (Coin coin : Coin.byValueDescending) - { + for (Coin coin : Coin.byValueDescending) { if (remaining <= 0) break; int canUse = Math.min(available.getDiscrete(coin), remaining / coin.value); - if (canUse > 0) - { + if (canUse > 0) { toRemove.add(coin, canUse); remaining -= coin.toSpurs(canUse); } @@ -251,12 +222,10 @@ public boolean tryPayInSpurs(Inventory inventory, int spursToRemove) { // 3. If we still have remaining spurs to cover, try to break one larger coin // Find the smallest denomination that is strictly larger than 'remaining' and still available DiscreteCoinBag changeToAdd = new DiscreteCoinBag(); - if (remaining > 0) - { + if (remaining > 0) { // Search ascending for the smallest coin whose value >= remaining and still available Coin breaker = null; - for (Coin coin : Coin.byValueAscending) - { + for (Coin coin : Coin.byValueAscending) { int availableCount = available.getDiscrete(coin) - toRemove.getDiscrete(coin); if (availableCount > 0 && coin.value >= remaining) { breaker = coin; @@ -310,13 +279,11 @@ public boolean tryPayInSpurs(Inventory inventory, int spursToRemove) { return true; } - private boolean playerHasEnoughCoinsInInventory(Inventory inv, int target) - { + private boolean playerHasEnoughCoinsInInventory(Inventory inv, int target) { var spursInInventory = 0; for (int slot = 0; slot < inv.getContainerSize(); slot++) { var stack = inv.getItem(slot); - if (stack.getItem() instanceof CoinItem coin) - { + if (stack.getItem() instanceof CoinItem coin) { spursInInventory += coin.coin.toSpurs(stack.getCount()); if (spursInInventory >= target) return true; @@ -326,33 +293,27 @@ private boolean playerHasEnoughCoinsInInventory(Inventory inv, int target) } - private static AbstractDepositorBlockEntity getDepositor(BlockPos tickerPos, Level level) - { + private static AbstractDepositorBlockEntity getDepositor(BlockPos tickerPos, Level level) { for (Direction side : Iterate.horizontalDirections) { BlockPos pos = tickerPos.relative(side); var e = level.getBlockEntity(pos); if (e instanceof AbstractDepositorBlockEntity) - return (AbstractDepositorBlockEntity)e; + return (AbstractDepositorBlockEntity) e; } return null; } - private void depositCoinsToMerchant() - { + private void depositCoinsToMerchant() { var depositor = getDepositor(stockTicker.getBlockPos(), level); if (depositor == null) return; var account = Numismatics.BANK.getAccount(depositor.getDepositAccount()); - if (account != null) - { + if (account != null) { account.deposit(costInSpurs); - } - else - { + } else { var coins = DiscreteCoinBag.of(costInSpurs); - for (var c : Coin.values()) - { + for (var c : Coin.values()) { depositor.addCoin(c, coins.getDiscrete(c)); } } @@ -380,8 +341,7 @@ public static DeferredCheckoutOrder clientSide(FriendlyByteBuf buf) { return new DeferredCheckoutOrder(buf.readUUID(), buf.readVarInt()); } - public static boolean isPowerOfTwo(int x) - { + public static boolean isPowerOfTwo(int x) { return (x & (x - 1)) == 0; } } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java index 0dbd1e8d..332974a6 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java @@ -11,12 +11,10 @@ import java.util.Map; import java.util.UUID; -public class GlobalDeferredCheckoutOrderManager -{ +public class GlobalDeferredCheckoutOrderManager { public Map deferredOrders; - public GlobalDeferredCheckoutOrderManager() - { + public GlobalDeferredCheckoutOrderManager() { deferredOrders = new HashMap<>(); } @@ -35,22 +33,19 @@ private void warnIfClient() { } } - public DeferredCheckoutOrder deferOrder(ShoppingListItem.ShoppingList list, Level level, ServerPlayer player, StockTickerBlockEntity stockTicker) - { + public DeferredCheckoutOrder deferOrder(ShoppingListItem.ShoppingList list, Level level, ServerPlayer player, StockTickerBlockEntity stockTicker) { warnIfClient(); var order = new DeferredCheckoutOrder(UUID.randomUUID(), list, level, player, stockTicker); deferredOrders.put(order.id, order); return order; } - public DeferredCheckoutOrder getDeferredOrder(UUID id) - { + public DeferredCheckoutOrder getDeferredOrder(UUID id) { warnIfClient(); return deferredOrders.get(id); } - public void voidOrder(DeferredCheckoutOrder order) - { + public void voidOrder(DeferredCheckoutOrder order) { warnIfClient(); order.finalized = true; deferredOrders.remove(order.id); diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java index 17b23516..868b8796 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java @@ -118,8 +118,7 @@ public static DiscreteCoinBag of(Map coins) { return new DiscreteCoinBag(coins); } - public static DiscreteCoinBag of(int totalSpurValue) - { + public static DiscreteCoinBag of(int totalSpurValue) { var bag = new DiscreteCoinBag(); int spurs = totalSpurValue; for (var coin : Coin.byValueDescending) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerBlockEntityReceivedPaymentsAccessor.java b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerBlockEntityReceivedPaymentsAccessor.java index debd5b35..a7b12269 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerBlockEntityReceivedPaymentsAccessor.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerBlockEntityReceivedPaymentsAccessor.java @@ -6,8 +6,7 @@ import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(StockTickerBlockEntity.class) -public interface MixinStockTickerBlockEntityReceivedPaymentsAccessor -{ +public interface MixinStockTickerBlockEntityReceivedPaymentsAccessor { @Accessor SmartInventory getReceivedPayments(); } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java index dd7ae8e9..f54289d3 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java @@ -1,43 +1,23 @@ package dev.ithundxr.createnumismatics.mixin; import com.llamalad7.mixinextras.sugar.Local; -import com.simibubi.create.AllSoundEvents; -import com.simibubi.create.content.logistics.packager.InventorySummary; -import com.simibubi.create.content.logistics.packagerLink.LogisticallyLinkedBehaviour; -import com.simibubi.create.content.logistics.stockTicker.PackageOrder; import com.simibubi.create.content.logistics.stockTicker.StockTickerBlockEntity; import com.simibubi.create.content.logistics.stockTicker.StockTickerInteractionHandler; import com.simibubi.create.content.logistics.tableCloth.ShoppingListItem; -import com.simibubi.create.foundation.utility.CreateLang; import dev.ithundxr.createnumismatics.Numismatics; -import dev.ithundxr.createnumismatics.content.backend.BankAccount; -import dev.ithundxr.createnumismatics.content.backend.Coin; -import dev.ithundxr.createnumismatics.content.bank.CardItem; -import dev.ithundxr.createnumismatics.content.checkout.DeferredCheckoutOrder; -import dev.ithundxr.createnumismatics.content.coins.CoinItem; -import dev.ithundxr.createnumismatics.content.depositor.AbstractDepositorBlockEntity; import dev.ithundxr.createnumismatics.util.Utils; -import net.createmod.catnip.data.Iterate; -import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import javax.smartcardio.Card; -import java.util.Arrays; - @Mixin(StockTickerInteractionHandler.class) -public class MixinStockTickerInteractionHandler -{ +public class MixinStockTickerInteractionHandler { @Inject(method = "interactWithShop", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/logistics/stockTicker/StockTickerBlockEntity;getAccurateSummary()Lcom/simibubi/create/content/logistics/packager/InventorySummary;"), cancellable = true) private static void interactWithShop( Player player, @@ -47,13 +27,11 @@ private static void interactWithShop( CallbackInfo ci, @Local ShoppingListItem.ShoppingList shoppingList, @Local StockTickerBlockEntity tickerBE - ) - { + ) { // Build the deferred order and check to see if all preconditions are being met var deferredOrder = Numismatics.DEFERRED_ORDERS.deferOrder(shoppingList, level, (ServerPlayer) player, tickerBE); - if (!deferredOrder.isTransactionValid()) - { + if (!deferredOrder.isTransactionValid()) { // Transaction isn't valid, backout! Numismatics.DEFERRED_ORDERS.voidOrder(deferredOrder); return; diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsGuiTextures.java b/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsGuiTextures.java index 9f34b5bc..077fdb95 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsGuiTextures.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/registry/NumismaticsGuiTextures.java @@ -22,7 +22,7 @@ public enum NumismaticsGuiTextures implements ScreenElement { BLAZE_BANKER("blaze_banker",200, 110), VENDOR("vendor", 236, 145), CREATIVE_VENDOR("creative_vendor", 236, 145), - CHECKOUT_SCREEN("checkout_screen",200, 132), + CHECKOUT_SCREEN("checkout_screen", 200, 132), ; public static final int FONT_COLOR = 0x575F7A; diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/registry/packets/DeferredCheckoutResolutionPacket.java b/common/src/main/java/dev/ithundxr/createnumismatics/registry/packets/DeferredCheckoutResolutionPacket.java index cdc53e18..0158ca68 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/registry/packets/DeferredCheckoutResolutionPacket.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/registry/packets/DeferredCheckoutResolutionPacket.java @@ -6,15 +6,13 @@ import io.netty.buffer.ByteBuf; import net.createmod.catnip.net.base.ServerboundPacketPayload; import net.minecraft.core.UUIDUtil; -import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.server.level.ServerPlayer; import java.util.UUID; public record DeferredCheckoutResolutionPacket( - UUID transactionId, CheckoutPaymentMethod method, UUID bankAccount) implements ServerboundPacketPayload -{ + UUID transactionId, CheckoutPaymentMethod method, UUID bankAccount) implements ServerboundPacketPayload { public static final StreamCodec STREAM_CODEC = StreamCodec.composite( UUIDUtil.STREAM_CODEC, DeferredCheckoutResolutionPacket::transactionId, CheckoutPaymentMethod.STREAM_CODEC, DeferredCheckoutResolutionPacket::method, @@ -23,8 +21,7 @@ public record DeferredCheckoutResolutionPacket( ); @Override - public void handle(ServerPlayer player) - { + public void handle(ServerPlayer player) { Numismatics.LOGGER.info("Received checkout packet! Transaction: {}; Method: {}; BankAccount: {}", transactionId, method, bankAccount); var order = Numismatics.DEFERRED_ORDERS.getDeferredOrder(transactionId); if (order == null) From 8487774bbc7c35f74d09739a1fb86aa2d6a91571 Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 19:04:06 -0400 Subject: [PATCH 03/19] renamed function to ofGreedy --- .../content/checkout/DeferredCheckoutOrder.java | 4 ++-- .../createnumismatics/content/coins/DiscreteCoinBag.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index 2ebd0320..31ebda54 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -243,7 +243,7 @@ public boolean tryPayInSpurs(Inventory inventory, int spursToRemove) { int overpay = breaker.value - remaining; // Make change - changeToAdd = DiscreteCoinBag.of(overpay); + changeToAdd = DiscreteCoinBag.ofGreedy(overpay); overpay -= changeToAdd.getValue(); // If we couldn't form exact change (shouldn't happen with canonical set), fail safely if (overpay != 0) { @@ -312,7 +312,7 @@ private void depositCoinsToMerchant() { if (account != null) { account.deposit(costInSpurs); } else { - var coins = DiscreteCoinBag.of(costInSpurs); + var coins = DiscreteCoinBag.ofGreedy(costInSpurs); for (var c : Coin.values()) { depositor.addCoin(c, coins.getDiscrete(c)); } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java index 868b8796..51492c57 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java @@ -118,7 +118,7 @@ public static DiscreteCoinBag of(Map coins) { return new DiscreteCoinBag(coins); } - public static DiscreteCoinBag of(int totalSpurValue) { + public static DiscreteCoinBag ofGreedy(int totalSpurValue) { var bag = new DiscreteCoinBag(); int spurs = totalSpurValue; for (var coin : Coin.byValueDescending) From d32d4254c39916206067cc99e3dec043ed236728 Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 19:04:30 -0400 Subject: [PATCH 04/19] Make deferred orders private --- .../content/checkout/GlobalDeferredCheckoutOrderManager.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java index 332974a6..936c0b91 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java @@ -12,11 +12,8 @@ import java.util.UUID; public class GlobalDeferredCheckoutOrderManager { - public Map deferredOrders; - public GlobalDeferredCheckoutOrderManager() { - deferredOrders = new HashMap<>(); - } + private Map deferredOrders = new HashMap<>(); private void warnIfClient() { if (Thread.currentThread().getName().equals("Render thread")) { From 7c9ebbab52888089b17499e8e376a1b95fa949dd Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 19:04:54 -0400 Subject: [PATCH 05/19] refactor getDepositor --- .../content/checkout/DeferredCheckoutOrder.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index 31ebda54..5f1955d7 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -292,19 +292,18 @@ private boolean playerHasEnoughCoinsInInventory(Inventory inv, int target) { return false; } - - private static AbstractDepositorBlockEntity getDepositor(BlockPos tickerPos, Level level) { + private AbstractDepositorBlockEntity getDepositor() { for (Direction side : Iterate.horizontalDirections) { - BlockPos pos = tickerPos.relative(side); + BlockPos pos = stockTicker.getBlockPos().relative(side); var e = level.getBlockEntity(pos); - if (e instanceof AbstractDepositorBlockEntity) - return (AbstractDepositorBlockEntity) e; + if (e instanceof AbstractDepositorBlockEntity depositor) + return depositor; } return null; } private void depositCoinsToMerchant() { - var depositor = getDepositor(stockTicker.getBlockPos(), level); + var depositor = getDepositor(); if (depositor == null) return; From ea425965e5ae32a6eee70af8c7dc354034ef1a8b Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 19:05:14 -0400 Subject: [PATCH 06/19] some var -> int conversions --- .../createnumismatics/content/checkout/CheckoutScreen.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java index 63a95fcb..920a6d92 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java @@ -86,7 +86,7 @@ private void updatePayWithCoinsButton() { return; var inventory = minecraft.player.getInventory(); - var coinsOnPlayer = coinsInPlayerInventory(inventory); + int coinsOnPlayer = coinsInPlayerInventory(inventory); payWithCoinsButton.active = menu.contentHolder.costInSpurs <= coinsOnPlayer; } @@ -120,8 +120,8 @@ private void onCancelTransaction() { } private int coinsInPlayerInventory(Inventory inv) { - var tally = 0; - for (var i = 0; i < inv.getContainerSize(); i++) { + int tally = 0; + for (int i = 0; i < inv.getContainerSize(); i++) { var stack = inv.getItem(i); if (stack.getItem() instanceof CoinItem ci) { tally += ci.coin.toSpurs(stack.getCount()); From 274c877e08ba43058084c7e4e9537ca295acc701 Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 19:05:30 -0400 Subject: [PATCH 07/19] Removed extra comment --- .../content/checkout/DeferredCheckoutOrder.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index 5f1955d7..a180c46f 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -176,17 +176,6 @@ public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAcc return true; } - /** - * Attempts to remove the specified number of spurs from the player's inventory. - * - Uses the largest denominations first (greedy). - * - If exact change is not possible with available smaller coins, it takes one larger coin - * and gives change back in smaller coins. - * - If still impossible (not enough value in inventory), returns false and makes no changes. - * - * @param player the player - * @param spursToRemove amount to pay, in spurs (must be >= 0) - * @return true if payment succeeded (inventory adjusted and change returned), false otherwise - */ public boolean tryPayInSpurs(Inventory inventory, int spursToRemove) { if (spursToRemove <= 0) { return true; // nothing to pay From 81e5a0ea78d636b29e5598261e39f891285fb7e0 Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 19:39:26 -0400 Subject: [PATCH 08/19] Specific menu provider for deferred orders. Fixed shift click --- .../content/checkout/CheckoutMenu.java | 33 ++++----- .../content/checkout/CheckoutScreen.java | 12 +-- .../checkout/DeferredCheckoutOrder.java | 74 ++++--------------- .../DeferredCheckoutOrderMenuProvider.java | 34 +++++++++ .../GlobalDeferredCheckoutOrderManager.java | 4 +- .../MixinStockTickerInteractionHandler.java | 4 +- 6 files changed, 73 insertions(+), 88 deletions(-) create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrderMenuProvider.java diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java index 3cd9c62a..077902d2 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java @@ -24,8 +24,7 @@ import java.util.function.Consumer; import java.util.function.Function; -public class CheckoutMenu extends MenuBase { - protected ContainerData dataAccess; +public class CheckoutMenu extends MenuBase { private CheckoutMenu.CardSwitchContainer cardSwitchContainer; protected UUID currentCardUUID = Utils.emptyUUID; @@ -33,22 +32,17 @@ public CheckoutMenu(MenuType type, int id, Inventory inv, RegistryFriendlyByt super(type, id, inv, extraData); } - public CheckoutMenu(MenuType type, int id, Inventory inv, DeferredCheckoutOrder contentHolder, ContainerData dataAccess) { + public CheckoutMenu(MenuType type, int id, Inventory inv, DeferredCheckoutOrderMenuProvider contentHolder) { super(type, id, inv, contentHolder); - this.dataAccess = dataAccess; - addDataSlots(dataAccess); } @Override - protected DeferredCheckoutOrder createOnClient(RegistryFriendlyByteBuf extraData) { - DeferredCheckoutOrder account = DeferredCheckoutOrder.clientSide(extraData); - this.dataAccess = account.dataAccess; - addDataSlots(dataAccess); - return account; + protected DeferredCheckoutOrderMenuProvider createOnClient(RegistryFriendlyByteBuf extraData) { + return DeferredCheckoutOrderMenuProvider.clientSide(extraData); } @Override - protected void initAndReadInventory(DeferredCheckoutOrder contentHolder) { + protected void initAndReadInventory(DeferredCheckoutOrderMenuProvider contentHolder) { } @Override @@ -64,7 +58,7 @@ protected void addSlots() { } @Override - protected void saveData(DeferredCheckoutOrder contentHolder) { + protected void saveData(DeferredCheckoutOrderMenuProvider contentHolder) { } @Override @@ -82,16 +76,15 @@ public void removed(Player playerIn) { if (!clickedSlot.hasItem()) return ItemStack.EMPTY; - ItemStack slotStack = CoinItem.clearDisplayedCount(clickedSlot.getItem()); - ItemStack returnStack = slotStack.copy(); - - if (slotStack.isEmpty()) { - clickedSlot.set(ItemStack.EMPTY); - } else { - clickedSlot.setChanged(); + if (NumismaticsTags.AllItemTags.CARDS.matches(clickedSlot.getItem())) + { + if (index == 0) // They've clicked the card in the slot + moveItemStackTo(clickedSlot.getItem(), 1, player.getInventory().getContainerSize() + 1, false); + else // They've clicked a card in their inventory + moveItemStackTo(clickedSlot.getItem(), 0, 1, false); } - return returnStack; + return ItemStack.EMPTY; } private class CardSwitchContainer implements Container { diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java index 920a6d92..63f87f86 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java @@ -87,7 +87,7 @@ private void updatePayWithCoinsButton() { var inventory = minecraft.player.getInventory(); int coinsOnPlayer = coinsInPlayerInventory(inventory); - payWithCoinsButton.active = menu.contentHolder.costInSpurs <= coinsOnPlayer; + payWithCoinsButton.active = menu.contentHolder.costInSpurs() <= coinsOnPlayer; } private void updatePayWithCardButton() { @@ -108,14 +108,14 @@ private void onPayWithCard() { } private void onConfirmTransaction(CheckoutPaymentMethod method) { - Numismatics.LOGGER.info("Submitting resolution (APPROVED) of deferred order {}", this.menu.contentHolder.id); - CatnipServices.NETWORK.sendToServer(new DeferredCheckoutResolutionPacket(this.menu.contentHolder.id, method, menu.currentCardUUID)); + Numismatics.LOGGER.info("Submitting resolution (APPROVED) of deferred order {}", this.menu.contentHolder.id()); + CatnipServices.NETWORK.sendToServer(new DeferredCheckoutResolutionPacket(this.menu.contentHolder.id(), method, menu.currentCardUUID)); super.onClose(); } private void onCancelTransaction() { - Numismatics.LOGGER.info("Submitting resolution (DENIED) of deferred order {}", this.menu.contentHolder.id); - CatnipServices.NETWORK.sendToServer(new DeferredCheckoutResolutionPacket(this.menu.contentHolder.id, CheckoutPaymentMethod.UNDEFINED, Utils.emptyUUID)); + Numismatics.LOGGER.info("Submitting resolution (DENIED) of deferred order {}", this.menu.contentHolder.id()); + CatnipServices.NETWORK.sendToServer(new DeferredCheckoutResolutionPacket(this.menu.contentHolder.id(), CheckoutPaymentMethod.UNDEFINED, Utils.emptyUUID)); super.onClose(); } @@ -153,7 +153,7 @@ protected void renderBg(@NotNull GuiGraphics graphics, float partialTick, int mo graphics.drawCenteredString(font, title, x + (background.width - 8) / 2, y + 3, 0xFFFFFF); - Couple cogsAndSpurs = Coin.COG.convert(menu.contentHolder.costInSpurs); + Couple cogsAndSpurs = Coin.COG.convert(menu.contentHolder.costInSpurs()); int cogs = cogsAndSpurs.getFirst(); int spurs = cogsAndSpurs.getSecond(); Component balanceLabel = Component.translatable("gui.numismatics.checkout_screen.total", diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index a180c46f..21134de0 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -32,34 +32,17 @@ import java.util.UUID; -public class DeferredCheckoutOrder implements MenuProvider { +public class DeferredCheckoutOrder{ public UUID id; - public InventorySummary itemCost; public int costInSpurs; - public PackageOrder deferredOrder; - public Level level; - public ServerPlayer player; - public StockTickerBlockEntity stockTicker; - public boolean finalized = false; - public boolean clientSide; - - public final ContainerData dataAccess = new ContainerData() { - @Override - public int get(int index) { - //Numismatics.LOGGER.warn("BankAccount dataAccess#get called with index " + index + " (Account: "+BankAccount.this+"), returning "+balance); - return costInSpurs; - } - @Override - public void set(int index, int value) { - Numismatics.LOGGER.warn("BankAccount dataAccess#set called with index " + index + " (Account: " + DeferredCheckoutOrder.this + "), setting balance to " + value); - } + private boolean closed = false; - @Override - public int getCount() { - return 1; - } - }; + private final StockTickerBlockEntity stockTicker; + private final ServerPlayer player; + private final Level level; + private final InventorySummary itemCost; + private final PackageOrder deferredOrder; public DeferredCheckoutOrder(UUID orderId, ShoppingListItem.ShoppingList list, Level level, ServerPlayer player, StockTickerBlockEntity stockTicker) { Couple bakeEntries = list.bakeEntries(level, null); @@ -81,20 +64,10 @@ public DeferredCheckoutOrder(UUID orderId, ShoppingListItem.ShoppingList list, L this.level = level; this.player = player; this.stockTicker = stockTicker; - this.clientSide = false; - } - - private DeferredCheckoutOrder(UUID orderId, int costInSpurs) { - this.clientSide = true; - this.id = orderId; - this.costInSpurs = costInSpurs; } public boolean isTransactionValid() { - if (finalized) - return false; - - if (clientSide) + if (closed) return false; if (level.isClientSide) @@ -109,8 +82,7 @@ public boolean isTransactionValid() { if (costInSpurs == 0) return false; - var depositor = getDepositor(stockTicker.getBlockPos(), level); - if (depositor == null) + if (getDepositor() == null) return false; return true; @@ -307,29 +279,13 @@ private void depositCoinsToMerchant() { } } - - // Menu components - - @Override - public Component getDisplayName() { - return Component.translatable("gui.numismatics.checkout_screen.header"); - } - - @Override - public @Nullable AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) { - return new CheckoutMenu(NumismaticsMenuTypes.CHECKOUT.get(), i, inventory, this, dataAccess); - } - - public void sendToMenu(FriendlyByteBuf buf) { - buf.writeUUID(this.id); - buf.writeVarInt(this.costInSpurs); - } - - public static DeferredCheckoutOrder clientSide(FriendlyByteBuf buf) { - return new DeferredCheckoutOrder(buf.readUUID(), buf.readVarInt()); + public void close() + { + closed = true; } - public static boolean isPowerOfTwo(int x) { - return (x & (x - 1)) == 0; + public boolean isClosed() + { + return closed; } } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrderMenuProvider.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrderMenuProvider.java new file mode 100644 index 00000000..7f8f27d7 --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrderMenuProvider.java @@ -0,0 +1,34 @@ +package dev.ithundxr.createnumismatics.content.checkout; + +import dev.ithundxr.createnumismatics.registry.NumismaticsMenuTypes; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public record DeferredCheckoutOrderMenuProvider(UUID id, int costInSpurs) implements MenuProvider { + + @Override + public Component getDisplayName() { + return Component.translatable("gui.numismatics.checkout_screen.header"); + } + + @Override + public @Nullable AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) { + return new CheckoutMenu(NumismaticsMenuTypes.CHECKOUT.get(), i, inventory, this); + } + + public static DeferredCheckoutOrderMenuProvider clientSide(FriendlyByteBuf buf) { + return new DeferredCheckoutOrderMenuProvider(buf.readUUID(), buf.readVarInt()); + } + + public void sendToMenu(FriendlyByteBuf buf) { + buf.writeUUID(this.id); + buf.writeVarInt(this.costInSpurs); + } +} diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java index 936c0b91..224ccfd6 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java @@ -13,7 +13,7 @@ public class GlobalDeferredCheckoutOrderManager { - private Map deferredOrders = new HashMap<>(); + private final Map deferredOrders = new HashMap<>(); private void warnIfClient() { if (Thread.currentThread().getName().equals("Render thread")) { @@ -44,7 +44,7 @@ public DeferredCheckoutOrder getDeferredOrder(UUID id) { public void voidOrder(DeferredCheckoutOrder order) { warnIfClient(); - order.finalized = true; + order.close(); deferredOrders.remove(order.id); } } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java index f54289d3..ded8a11b 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java @@ -5,6 +5,7 @@ import com.simibubi.create.content.logistics.stockTicker.StockTickerInteractionHandler; import com.simibubi.create.content.logistics.tableCloth.ShoppingListItem; import dev.ithundxr.createnumismatics.Numismatics; +import dev.ithundxr.createnumismatics.content.checkout.DeferredCheckoutOrderMenuProvider; import dev.ithundxr.createnumismatics.util.Utils; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerPlayer; @@ -41,7 +42,8 @@ private static void interactWithShop( // and we will *not* be allowing the standard trade to complete now. We must defer it ci.cancel(); - Utils.openScreen((ServerPlayer) player, deferredOrder, deferredOrder::sendToMenu); + var deferredOrderModel = new DeferredCheckoutOrderMenuProvider(deferredOrder.id, deferredOrder.costInSpurs); + Utils.openScreen((ServerPlayer) player, deferredOrderModel, deferredOrderModel::sendToMenu); } } From c40b3d51ae961afc39ba7a7e26c457cf1a2244d8 Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 20:20:14 -0400 Subject: [PATCH 09/19] Lock the shopping list in place --- .../content/checkout/CheckoutMenu.java | 18 +++++++--- .../content/checkout/LockableSlot.java | 36 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/LockableSlot.java diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java index 077902d2..96e4d96d 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutMenu.java @@ -3,7 +3,6 @@ import com.simibubi.create.foundation.gui.menu.MenuBase; import dev.ithundxr.createnumismatics.content.bank.CardItem; import dev.ithundxr.createnumismatics.content.bank.CardSlot; -import dev.ithundxr.createnumismatics.content.coins.CoinItem; import dev.ithundxr.createnumismatics.registry.NumismaticsTags; import dev.ithundxr.createnumismatics.util.Utils; import net.minecraft.network.RegistryFriendlyByteBuf; @@ -12,7 +11,6 @@ import net.minecraft.world.ContainerHelper; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.inventory.ContainerData; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; @@ -57,6 +55,19 @@ protected void addSlots() { addPlayerSlots(40, 152); } + @Override + protected void addPlayerSlots(int x, int y) { + for (int hotbarSlot = 0; hotbarSlot < 9; ++hotbarSlot) { + this.addSlot(new LockableSlot(playerInventory, hotbarSlot, x + hotbarSlot * 18, y + 58, hotbarSlot == playerInventory.selected)); + } + for (int row = 0; row < 3; ++row) { + for (int col = 0; col < 9; ++col) { + int slot = col + row * 9 + 9; + this.addSlot(new LockableSlot(playerInventory, slot, x + col * 18, y + row * 18, slot == playerInventory.selected)); + } + } + } + @Override protected void saveData(DeferredCheckoutOrderMenuProvider contentHolder) { } @@ -76,8 +87,7 @@ public void removed(Player playerIn) { if (!clickedSlot.hasItem()) return ItemStack.EMPTY; - if (NumismaticsTags.AllItemTags.CARDS.matches(clickedSlot.getItem())) - { + if (NumismaticsTags.AllItemTags.CARDS.matches(clickedSlot.getItem())) { if (index == 0) // They've clicked the card in the slot moveItemStackTo(clickedSlot.getItem(), 1, player.getInventory().getContainerSize() + 1, false); else // They've clicked a card in their inventory diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/LockableSlot.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/LockableSlot.java new file mode 100644 index 00000000..eca8f120 --- /dev/null +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/LockableSlot.java @@ -0,0 +1,36 @@ +package dev.ithundxr.createnumismatics.content.checkout; + +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +public class LockableSlot extends Slot { + + private boolean locked; + + public LockableSlot(Container inventory, int invSlot, int x, int y, boolean initiallyLocked) { + super(inventory, invSlot, x, y); + locked = initiallyLocked; + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + + @Override + public boolean mayPlace(ItemStack stack) { + if (locked) + return false; + else + return super.mayPlace(stack); + } + + @Override + public boolean mayPickup(Player player) { + if (locked) + return false; + else + return super.mayPickup(player); + } +} \ No newline at end of file From 6e8fb3b2c80e5316dce9571ff1f5d36bc09c64dc Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 20:41:33 -0400 Subject: [PATCH 10/19] Pull out the package address so we don't need to rely on the item --- .../content/checkout/CheckoutUtilities.java | 37 ++++++++++--------- .../checkout/DeferredCheckoutOrder.java | 8 ++-- .../GlobalDeferredCheckoutOrderManager.java | 4 +- .../MixinStockTickerInteractionHandler.java | 3 +- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java index e12d10f6..10f1b1e1 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java @@ -9,6 +9,7 @@ import com.simibubi.create.content.logistics.tableCloth.ShoppingListItem; import com.simibubi.create.foundation.item.SmartInventory; import com.simibubi.create.foundation.utility.CreateLang; +import dev.ithundxr.createnumismatics.Numismatics; import dev.ithundxr.createnumismatics.content.coins.CoinItem; import net.createmod.catnip.data.Couple; import net.createmod.catnip.data.Iterate; @@ -23,18 +24,15 @@ import java.util.ArrayList; import java.util.List; +/* + This class mostly contains code extracted from + com.simibubi.create.content.logistics.stockTicker.StockTickerInteractionHandler.interactWithShop() + in order to facilitate deferring the order placement. We need to call different parts of that one function at different times. + + As such, it has been isolated to its own class. If the interactWithShop() function changes, most of the impact + will be to this file, and the mixin which kicks off the whole deferred checkout process + */ public class CheckoutUtilities { - public static int determineCoinCostInSpurs(ShoppingListItem.ShoppingList list, Level level) { - Couple bakeEntries = list.bakeEntries(level, null); - InventorySummary paymentEntries = bakeEntries.getSecond(); - int cost = 0; - for (var stack : paymentEntries.getStacksByCount()) { - if (stack.stack.getItem() instanceof CoinItem coinItem) { - cost += coinItem.coin.toSpurs(stack.count); - } - } - return cost; - } public static void denyShopInteraction(Level level, Player player) { AllSoundEvents.DENY.playOnServer(level, player.blockPosition()); @@ -43,10 +41,10 @@ public static void denyShopInteraction(Level level, Player player) { .sendStatus(player); } - public static void shopInteractionSubmitToNetwork(StockTickerBlockEntity tickerBE, PackageOrder order, Player player, Level level) { - var mainHandItem = player.getMainHandItem(); - tickerBE.broadcastPackageRequest(LogisticallyLinkedBehaviour.RequestType.PLAYER, order, null, ShoppingListItem.getAddress(mainHandItem)); - player.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY); + public static void shopInteractionSubmitToNetwork(StockTickerBlockEntity tickerBE, PackageOrder order, Player player, Level level, String packageAddress) { + tickerBE.broadcastPackageRequest(LogisticallyLinkedBehaviour.RequestType.PLAYER, order, null, packageAddress); + if (player.getItemInHand(InteractionHand.MAIN_HAND).getItem() instanceof ShoppingListItem) + player.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY); if (!order.isEmpty()) AllSoundEvents.STOCK_TICKER_TRADE.playOnServer(level, tickerBE.getBlockPos()); } @@ -67,13 +65,18 @@ public static boolean checkOrderPreconditions(StockTickerBlockEntity tickerBE, P return true; } + /* + The "Stock" in the name of this function refers to "Standard" as in this is what we'll call to invoke the standard + process of placing an order if the order also contains items, instead of just coins. + */ public static boolean finishShopInteractionStock( StockTickerBlockEntity tickerBE, Level level, Player player, InventorySummary paymentEntries, PackageOrder order, - SmartInventory receivedPayments) { + SmartInventory receivedPayments, + String packageAddress) { if (!checkOrderPreconditions(tickerBE, order, level, player)) return false; @@ -127,7 +130,7 @@ public static boolean finishShopInteractionStock( toTransfer.forEach(s -> ItemHandlerHelper.insertItemStacked(receivedPayments, s, false)); } - shopInteractionSubmitToNetwork(tickerBE, order, player, level); + shopInteractionSubmitToNetwork(tickerBE, order, player, level, packageAddress); return true; } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index 21134de0..4501c75d 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -43,8 +43,9 @@ public class DeferredCheckoutOrder{ private final Level level; private final InventorySummary itemCost; private final PackageOrder deferredOrder; + private final String packageAddress; - public DeferredCheckoutOrder(UUID orderId, ShoppingListItem.ShoppingList list, Level level, ServerPlayer player, StockTickerBlockEntity stockTicker) { + public DeferredCheckoutOrder(UUID orderId, ShoppingListItem.ShoppingList list, Level level, ServerPlayer player, StockTickerBlockEntity stockTicker, String packageAddress) { Couple bakeEntries = list.bakeEntries(level, null); InventorySummary paymentEntries = bakeEntries.getSecond(); @@ -64,6 +65,7 @@ public DeferredCheckoutOrder(UUID orderId, ShoppingListItem.ShoppingList list, L this.level = level; this.player = player; this.stockTicker = stockTicker; + this.packageAddress = packageAddress; } public boolean isTransactionValid() { @@ -128,11 +130,11 @@ public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAcc } // If there's no item cost, we can skip a lot of the default create interaction, and just submit the order - CheckoutUtilities.shopInteractionSubmitToNetwork(stockTicker, deferredOrder, player, level); + CheckoutUtilities.shopInteractionSubmitToNetwork(stockTicker, deferredOrder, player, level, packageAddress); } else { // There are item costs in the shopping list, so we must submit the order through the standard pipeline. var receivedPayments = ((MixinStockTickerBlockEntityReceivedPaymentsAccessor) stockTicker).getReceivedPayments(); - if (!CheckoutUtilities.finishShopInteractionStock(stockTicker, level, player, itemCost, deferredOrder, receivedPayments)) { + if (!CheckoutUtilities.finishShopInteractionStock(stockTicker, level, player, itemCost, deferredOrder, receivedPayments, packageAddress)) { // stock checkout failed, cancel the transaction return false; } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java index 224ccfd6..275f0c72 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/GlobalDeferredCheckoutOrderManager.java @@ -30,9 +30,9 @@ private void warnIfClient() { } } - public DeferredCheckoutOrder deferOrder(ShoppingListItem.ShoppingList list, Level level, ServerPlayer player, StockTickerBlockEntity stockTicker) { + public DeferredCheckoutOrder deferOrder(ShoppingListItem.ShoppingList list, Level level, ServerPlayer player, StockTickerBlockEntity stockTicker, String packageAddress) { warnIfClient(); - var order = new DeferredCheckoutOrder(UUID.randomUUID(), list, level, player, stockTicker); + var order = new DeferredCheckoutOrder(UUID.randomUUID(), list, level, player, stockTicker, packageAddress); deferredOrders.put(order.id, order); return order; } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java index ded8a11b..aec4942c 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java @@ -31,7 +31,8 @@ private static void interactWithShop( ) { // Build the deferred order and check to see if all preconditions are being met - var deferredOrder = Numismatics.DEFERRED_ORDERS.deferOrder(shoppingList, level, (ServerPlayer) player, tickerBE); + var address = ShoppingListItem.getAddress(mainHandItem); + var deferredOrder = Numismatics.DEFERRED_ORDERS.deferOrder(shoppingList, level, (ServerPlayer) player, tickerBE, address); if (!deferredOrder.isTransactionValid()) { // Transaction isn't valid, backout! Numismatics.DEFERRED_ORDERS.voidOrder(deferredOrder); From e8f7f30c79a12ab76d22acb0c80cfad6fc1dfaa6 Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 20:48:34 -0400 Subject: [PATCH 11/19] Check for authorized cards --- .../content/checkout/DeferredCheckoutOrder.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index 4501c75d..6cd1ac39 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -106,6 +106,12 @@ public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAcc Numismatics.LOGGER.warn("Attempted to complete a card transaction {} with an non-empty, but invalid bank account {}", id, purchasingAccountId); return false; } + + if (!account.isAuthorized(player)) { + CheckoutUtilities.denyPurchase(level, player, "stock_keeper.too_broke"); // Unauthorized + return false; + } + } if (!isTransactionValid()) { From 4612946021269eb3f8eea1d696721711e98c4731 Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 20:51:10 -0400 Subject: [PATCH 12/19] Cloning discrete coin bag map --- .../createnumismatics/content/coins/DiscreteCoinBag.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java index 51492c57..296c3342 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java @@ -76,7 +76,7 @@ public ItemStack asStack(Coin coin) { return NumismaticsItems.getCoin(coin).asStack(amt); } - public Map asMap() { return this.coins; } + public Map asMap() { return new HashMap<>(this.coins); } @Override public int getValue() { From 4c3eef79963ad19a528b6b8421a1e89e209589ae Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 21:17:58 -0400 Subject: [PATCH 13/19] Translatable keys for UI elements --- .../64c87e664647124a7b6450bc49fb722594ed5a16 | 10 +++++----- .../assets/numismatics/lang/en_ud.json | 4 ++++ .../assets/numismatics/lang/en_us.json | 4 ++++ .../content/checkout/CheckoutScreen.java | 4 ++-- .../content/checkout/CheckoutUtilities.java | 19 ++++++------------- .../checkout/DeferredCheckoutOrder.java | 14 ++++++++------ .../numismatics/lang/default/interface.json | 5 +++++ 7 files changed, 34 insertions(+), 26 deletions(-) diff --git a/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 b/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 index 91045850..786e4057 100644 --- a/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 +++ b/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 @@ -1,19 +1,19 @@ -// 1.21.1 2025-08-11T14:40:38.617334 Registrate Provider for numismatics [Registries, Data Maps, Recipes, Advancements, Loot Tables, Tags (enchantments), Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), generic_server_provider, Blockstates, Item models, Lang (en_us/en_ud), generic_client_provider] +// 1.21.1 2025-08-12T21:12:26.4579249 Registrate Provider for numismatics [Registries, Data Maps, Recipes, Advancements, Loot Tables, Tags (enchantments), Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), generic_server_provider, Blockstates, Item models, Lang (en_us/en_ud), generic_client_provider] f7f43dd6d567ec8303c73b79409bc92d8b56574a assets/numismatics/blockstates/andesite_depositor.json 3961fdf3030140fc32e0e8c1d440ac395e62f5b6 assets/numismatics/blockstates/bank_terminal.json 06ecd28cd97f4e8200dc396858695cad57b871c8 assets/numismatics/blockstates/blaze_banker.json 160d556c6bfdb651082b39784258f6d06c21ca8f assets/numismatics/blockstates/brass_depositor.json 95ef415a564eba1d212053195d25b199427b94e3 assets/numismatics/blockstates/creative_vendor.json d2b105f0657bad99b8efed45dc0a8df8ff775c10 assets/numismatics/blockstates/vendor.json -adc37c6733d275a4f32add3d5ca693ea345013de assets/numismatics/lang/en_ud.json -9218c8f15ade397f8e485497675812f43647bdbc assets/numismatics/lang/en_us.json +da2ea46b51689cb1062f072ab847b8e164a414e2 assets/numismatics/lang/en_ud.json +56639735048e12545395308eb903659b052b2d9e assets/numismatics/lang/en_us.json 265ef24d62bc7580e763e1fb6802bf4e58dc0194 assets/numismatics/models/block/andesite_depositor.json 4f78ca868db20495aa20be7c6a14e2678fb16f9f assets/numismatics/models/block/andesite_depositor_locked.json 411b79f79547a0adcb665bf7440e8169f7dcb24e assets/numismatics/models/block/brass_depositor.json 74a4c7ca7a48382782e5dba33018dfc8255192c5 assets/numismatics/models/block/brass_depositor_locked.json 2449b7346e1657ef1c6ab4c134aab55b216ec783 assets/numismatics/models/item/andesite_depositor.json -228b67a48aa045bfe809c54c756df80eb0765aad assets/numismatics/models/item/bank_terminal.json 83ce6c9d27970b4c643f0f9f3dfeb58668fca3d4 assets/numismatics/models/item/banking_guide.json +228b67a48aa045bfe809c54c756df80eb0765aad assets/numismatics/models/item/bank_terminal.json 52b48750de8a5a571a08bce3f2f025474153d50b assets/numismatics/models/item/bevel.json 84ab8c91452f94501b3acc31ec1e0bc64417f839 assets/numismatics/models/item/black_card.json 70c481f36a9718ac48632e6939ac6ba785be4c9e assets/numismatics/models/item/black_id_card.json @@ -56,9 +56,9 @@ c1863c2bd08a5910a534aee0dcbc61a352fb9577 assets/numismatics/models/item/white_ca a96d3d02794064cd9be1bca25a9ba6217675e6c5 assets/numismatics/models/item/white_id_card.json 9c20dd40c03605721d0231ffde829d55e36b1c05 assets/numismatics/models/item/yellow_card.json c05836600bd1689f598515841869634b1d709cca assets/numismatics/models/item/yellow_id_card.json -a615f3af71b117b4f5974a64a1c744ff072fba54 data/c/tags/block/relocation_not_supported.json b8a840be34886ce90bc6ebbd48ac70a40060ada1 data/create/tags/block/fan_transparent.json b8a840be34886ce90bc6ebbd48ac70a40060ada1 data/create/tags/block/passive_boiler_heaters.json +a615f3af71b117b4f5974a64a1c744ff072fba54 data/c/tags/block/relocation_not_supported.json 0604bc1712ca30d404c0c27b4c1469f729fdefd6 data/minecraft/tags/block/mineable/axe.json ce83b2be6bbae03794f249386337ee5110241e57 data/minecraft/tags/block/mineable/pickaxe.json 9e6e50d40e3688ae681107e60ac5ff5fc22585f9 data/numismatics/loot_table/blocks/andesite_depositor.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 001953a6..fab5c4c1 100644 --- a/common/src/generated/resources/assets/numismatics/lang/en_ud.json +++ b/common/src/generated/resources/assets/numismatics/lang/en_ud.json @@ -35,6 +35,8 @@ "command.numismatics.arguments.enum.invalid": "%s :ǝɹɐ sǝnןɐʌ pıןɐΛ ˙,%s, ǝnןɐʌ ɯnuǝ pıןɐʌuI :ɹoɹɹƎ", "gui.numismatics.bank_terminal.balance": "¤%s '%s %s :ǝɔuɐןɐᗺ", "gui.numismatics.checkout_screen.header": "ʇnoʞɔǝɥƆ", + "gui.numismatics.checkout_screen.pay_with_card": "pɹɐƆ ɥʇıʍ ʎɐԀ", + "gui.numismatics.checkout_screen.pay_with_coins": "suıoƆ ɥʇıʍ ʎɐԀ", "gui.numismatics.checkout_screen.total": "¤%s '%s %s :ןɐʇo⟘ ɹǝpɹO", "gui.numismatics.trust_list": "ʇsıꞀ ʇsnɹ⟘", "gui.numismatics.vendor.count": ")x%s( ", @@ -108,5 +110,7 @@ "item.numismatics.yellow_id_card": "pɹɐƆ ᗡI ʍoןןǝʎ", "itemGroup.numismatics": "sɔıʇɐɯsıɯnN :ǝʇɐǝɹƆ", "numismatics.andesite_depositor.price": "ǝɔıɹԀ", + "numismatics.checkout.insufficient_funds": "¡spunɟ ʇuǝıɔıɟɟnsuI", + "numismatics.checkout.unauthorized": "pɹɐɔ ʇɐɥʇ ǝsn oʇ pǝzıɹoɥʇnɐ ʇou ǝɹ,noʎ", "numismatics.trust_list.configure": "ʇsıꞀ ʇsnɹ⟘ ǝɹnbıɟuoƆ" } \ No newline at end of file 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 775738eb..9977bf3e 100644 --- a/common/src/generated/resources/assets/numismatics/lang/en_us.json +++ b/common/src/generated/resources/assets/numismatics/lang/en_us.json @@ -35,6 +35,8 @@ "command.numismatics.arguments.enum.invalid": "Error: Invalid enum value '%s'. Valid values are: %s", "gui.numismatics.bank_terminal.balance": "Balance: %s %s, %s¤", "gui.numismatics.checkout_screen.header": "Checkout", + "gui.numismatics.checkout_screen.pay_with_card": "Pay with Card", + "gui.numismatics.checkout_screen.pay_with_coins": "Pay with Coins", "gui.numismatics.checkout_screen.total": "Order Total: %s %s, %s¤", "gui.numismatics.trust_list": "Trust List", "gui.numismatics.vendor.count": " (%sx)", @@ -108,5 +110,7 @@ "item.numismatics.yellow_id_card": "Yellow ID Card", "itemGroup.numismatics": "Create: Numismatics", "numismatics.andesite_depositor.price": "Price", + "numismatics.checkout.insufficient_funds": "Insufficient funds!", + "numismatics.checkout.unauthorized": "You're not authorized to use that card", "numismatics.trust_list.configure": "Configure Trust List" } \ No newline at end of file diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java index 63f87f86..f7739b52 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java @@ -57,14 +57,14 @@ protected void init() { int gap = 6; int btnX = x + (background.width - btnW - 10) / 2; - payWithCoinsButton = Button.builder(Component.literal("Pay with Coins"), b -> onPayWithCoins()) + payWithCoinsButton = Button.builder(Component.translatable("gui.numismatics.checkout_screen.pay_with_coins"), b -> onPayWithCoins()) .pos(btnX, y + 45) .size(btnW, btnH) .build(); updatePayWithCoinsButton(); addRenderableWidget(payWithCoinsButton); - payWithCardButton = Button.builder(Component.literal("Pay with Card"), b -> onPayWithCard()) + payWithCardButton = Button.builder(Component.translatable("gui.numismatics.checkout_screen.pay_with_card"), b -> onPayWithCard()) .pos(btnX, y + 45 + btnH + gap) .size(btnW - 24, btnH) .build(); diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java index 10f1b1e1..bfdf5b39 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutUtilities.java @@ -10,10 +10,12 @@ import com.simibubi.create.foundation.item.SmartInventory; import com.simibubi.create.foundation.utility.CreateLang; import dev.ithundxr.createnumismatics.Numismatics; +import dev.ithundxr.createnumismatics.base.data.lang.NumismaticsLangGen; import dev.ithundxr.createnumismatics.content.coins.CoinItem; import net.createmod.catnip.data.Couple; import net.createmod.catnip.data.Iterate; import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.player.Player; @@ -34,13 +36,6 @@ As such, it has been isolated to its own class. If the interactWithShop() functi */ public class CheckoutUtilities { - public static void denyShopInteraction(Level level, Player player) { - AllSoundEvents.DENY.playOnServer(level, player.blockPosition()); - CreateLang.translate("stock_keeper.stock_level_too_low") - .style(ChatFormatting.RED) - .sendStatus(player); - } - public static void shopInteractionSubmitToNetwork(StockTickerBlockEntity tickerBE, PackageOrder order, Player player, Level level, String packageAddress) { tickerBE.broadcastPackageRequest(LogisticallyLinkedBehaviour.RequestType.PLAYER, order, null, packageAddress); if (player.getItemInHand(InteractionHand.MAIN_HAND).getItem() instanceof ShoppingListItem) @@ -59,7 +54,7 @@ public static boolean checkOrderPreconditions(StockTickerBlockEntity tickerBE, P if (recentSummary.getCountOf(entry.stack) >= entry.count) continue; - denyShopInteraction(level, player); + denyPurchase(level, player, "create.stock_keeper.stock_level_too_low"); return false; } return true; @@ -90,7 +85,7 @@ public static boolean finishShopInteractionStock( occupiedSlots--; if (occupiedSlots > 0) { - denyPurchase(level, player, "stock_keeper.cash_register_full"); + denyPurchase(level, player, "create.stock_keeper.cash_register_full"); return false; } @@ -120,7 +115,7 @@ public static boolean finishShopInteractionStock( } if (simulate && tally.getTotalCount() != 0) { - denyPurchase(level, player, "stock_keeper.too_broke"); + denyPurchase(level, player, "create.stock_keeper.too_broke"); return false; } @@ -136,8 +131,6 @@ public static boolean finishShopInteractionStock( public static void denyPurchase(Level level, Player player, String langKey) { AllSoundEvents.DENY.playOnServer(level, player.blockPosition()); - CreateLang.translate(langKey) - .style(ChatFormatting.RED) - .sendStatus(player); + player.displayClientMessage(Component.translatable(langKey).withStyle(ChatFormatting.RED), true); } } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index 6cd1ac39..52b1fbae 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -108,10 +108,9 @@ public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAcc } if (!account.isAuthorized(player)) { - CheckoutUtilities.denyPurchase(level, player, "stock_keeper.too_broke"); // Unauthorized + CheckoutUtilities.denyPurchase(level, player, "numismatics.checkout.unauthorized"); // Unauthorized return false; } - } if (!isTransactionValid()) { @@ -121,17 +120,17 @@ public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAcc if (itemCost.isEmpty()) { if (!CheckoutUtilities.checkOrderPreconditions(stockTicker, deferredOrder, level, player)) { - CheckoutUtilities.denyPurchase(level, player, "stock_keeper.too_broke"); + // checkOrderPreconditions displays the chat message return false; } if (method == CheckoutPaymentMethod.CARD && account.getBalance() < costInSpurs) { - CheckoutUtilities.denyPurchase(level, player, "stock_keeper.too_broke"); + CheckoutUtilities.denyPurchase(level, player, "numismatics.checkout.insufficient_funds"); return false; } if (method == CheckoutPaymentMethod.COINS && !playerHasEnoughCoinsInInventory(player.getInventory(), costInSpurs)) { - CheckoutUtilities.denyPurchase(level, player, "stock_keeper.too_broke"); + CheckoutUtilities.denyPurchase(level, player, "create.stock_keeper.too_broke"); return false; } @@ -148,7 +147,10 @@ public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAcc switch (method) { case CARD -> account.deduct(costInSpurs); - case COINS -> tryPayInSpurs(player.getInventory(), costInSpurs); + case COINS -> { + if (!tryPayInSpurs(player.getInventory(), costInSpurs)) + Numismatics.LOGGER.warn("Attempted to pay {} spurs from player {} ({}) inventory, but failed!", costInSpurs, player.getName(), player.getUUID()); + } default -> throw new IllegalStateException("Unexpected value: " + method); } 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 d6be2fd9..e7185c0c 100644 --- a/common/src/main/resources/assets/numismatics/lang/default/interface.json +++ b/common/src/main/resources/assets/numismatics/lang/default/interface.json @@ -23,6 +23,11 @@ "gui.numismatics.vendor.no_item_in_hand": "Hold the stack of items you want to sell", "gui.numismatics.checkout_screen.total": "Order Total: %s %s, %s¤", "gui.numismatics.checkout_screen.header": "Checkout", + "gui.numismatics.checkout_screen.pay_with_coins": "Pay with Coins", + "gui.numismatics.checkout_screen.pay_with_card": "Pay with Card", + + "numismatics.checkout.unauthorized": "You're not authorized to use that card", + "numismatics.checkout.insufficient_funds": "Insufficient funds!", "block.numismatics.trusted_block.attempt_break": "Hold shift to break this block" } \ No newline at end of file From 2864bbb398d75c4db6ef84935eb8b30d60701a03 Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 21:23:01 -0400 Subject: [PATCH 14/19] Cancel transaction enum --- .../content/checkout/CheckoutPaymentMethod.java | 2 +- .../createnumismatics/content/checkout/CheckoutScreen.java | 2 +- .../content/checkout/DeferredCheckoutOrder.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java index dbef6c8f..b04023dd 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutPaymentMethod.java @@ -5,7 +5,7 @@ import net.minecraft.network.codec.StreamCodec; public enum CheckoutPaymentMethod { - UNDEFINED, + CANCEL_TRANSACTION, CARD, COINS; diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java index f7739b52..a9a40404 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java @@ -115,7 +115,7 @@ private void onConfirmTransaction(CheckoutPaymentMethod method) { private void onCancelTransaction() { Numismatics.LOGGER.info("Submitting resolution (DENIED) of deferred order {}", this.menu.contentHolder.id()); - CatnipServices.NETWORK.sendToServer(new DeferredCheckoutResolutionPacket(this.menu.contentHolder.id(), CheckoutPaymentMethod.UNDEFINED, Utils.emptyUUID)); + CatnipServices.NETWORK.sendToServer(new DeferredCheckoutResolutionPacket(this.menu.contentHolder.id(), CheckoutPaymentMethod.CANCEL_TRANSACTION, Utils.emptyUUID)); super.onClose(); } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index 52b1fbae..3baa909b 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -91,7 +91,7 @@ public boolean isTransactionValid() { } public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAccountId) { - if (method == CheckoutPaymentMethod.UNDEFINED) + if (method == CheckoutPaymentMethod.CANCEL_TRANSACTION) return false; BankAccount account = null; From 64c7881a11dd0dff8b63b3f3f0a8a0ddaea6dd56 Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 21:23:14 -0400 Subject: [PATCH 15/19] Small menu provider refactor --- .../checkout/DeferredCheckoutOrder.java | 18 ++++++------------ .../MixinStockTickerInteractionHandler.java | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index 3baa909b..b8219773 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -11,33 +11,25 @@ import dev.ithundxr.createnumismatics.content.coins.DiscreteCoinBag; import dev.ithundxr.createnumismatics.content.depositor.AbstractDepositorBlockEntity; import dev.ithundxr.createnumismatics.mixin.MixinStockTickerBlockEntityReceivedPaymentsAccessor; -import dev.ithundxr.createnumismatics.registry.NumismaticsMenuTypes; import dev.ithundxr.createnumismatics.util.Utils; import net.createmod.catnip.data.Couple; import net.createmod.catnip.data.Iterate; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; -import net.minecraft.world.MenuProvider; import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.ContainerData; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; -import org.jetbrains.annotations.Nullable; import java.util.UUID; public class DeferredCheckoutOrder{ public UUID id; - public int costInSpurs; private boolean closed = false; + private final int costInSpurs; private final StockTickerBlockEntity stockTicker; private final ServerPlayer player; private final Level level; @@ -51,13 +43,15 @@ public DeferredCheckoutOrder(UUID orderId, ShoppingListItem.ShoppingList list, L // Determine cost of coin component of order InventorySummary paymentWithoutCoins = new InventorySummary(); + int cost = 0; for (var stack : paymentEntries.getStacksByCount()) { if (stack.stack.getItem() instanceof CoinItem coinItem) { - costInSpurs += coinItem.coin.toSpurs(stack.count); + cost += coinItem.coin.toSpurs(stack.count); } else { paymentWithoutCoins.add(stack); } } + costInSpurs = cost; this.id = orderId; this.itemCost = paymentWithoutCoins; @@ -294,8 +288,8 @@ public void close() closed = true; } - public boolean isClosed() + public DeferredCheckoutOrderMenuProvider createMenuProvider() { - return closed; + return new DeferredCheckoutOrderMenuProvider(id, costInSpurs); } } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java index aec4942c..4851420e 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/mixin/MixinStockTickerInteractionHandler.java @@ -43,7 +43,7 @@ private static void interactWithShop( // and we will *not* be allowing the standard trade to complete now. We must defer it ci.cancel(); - var deferredOrderModel = new DeferredCheckoutOrderMenuProvider(deferredOrder.id, deferredOrder.costInSpurs); + var deferredOrderModel = deferredOrder.createMenuProvider(); Utils.openScreen((ServerPlayer) player, deferredOrderModel, deferredOrderModel::sendToMenu); } From 0bdfe78357953c0a4d63d44064164b9c958215ea Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 21:31:19 -0400 Subject: [PATCH 16/19] Changed the menu buddy to a stock ticker --- .../createnumismatics/content/checkout/CheckoutScreen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java index a9a40404..74eca43e 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/CheckoutScreen.java @@ -29,7 +29,7 @@ public class CheckoutScreen extends AbstractSimiContainerScreen { private final NumismaticsGuiTextures background = NumismaticsGuiTextures.CHECKOUT_SCREEN; - private final ItemStack renderedItem = AllBlocks.LIT_BLAZE_BURNER.asStack(); + private final ItemStack renderedItem = AllBlocks.STOCK_TICKER.asStack(); private List extraAreas = Collections.emptyList(); public CheckoutScreen(CheckoutMenu container, Inventory inv, Component title) { From 83f7ec0301520383eb62d7a933f8edd0194b695d Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 23:11:13 -0400 Subject: [PATCH 17/19] ofChange --- .../content/checkout/DeferredCheckoutOrder.java | 16 +++------------- .../content/coins/DiscreteCoinBag.java | 6 ++++++ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index b8219773..c23d0b26 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -205,15 +205,7 @@ public boolean tryPayInSpurs(Inventory inventory, int spursToRemove) { // Use one breaker coin toRemove.add(breaker, 1); - int overpay = breaker.value - remaining; - - // Make change - changeToAdd = DiscreteCoinBag.ofGreedy(overpay); - overpay -= changeToAdd.getValue(); - // If we couldn't form exact change (shouldn't happen with canonical set), fail safely - if (overpay != 0) { - return false; - } + changeToAdd = DiscreteCoinBag.ofChange(remaining, breaker); } // 4. Apply Changes @@ -283,13 +275,11 @@ private void depositCoinsToMerchant() { } } - public void close() - { + public void close() { closed = true; } - public DeferredCheckoutOrderMenuProvider createMenuProvider() - { + public DeferredCheckoutOrderMenuProvider createMenuProvider() { return new DeferredCheckoutOrderMenuProvider(id, costInSpurs); } } diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java index 296c3342..692e3a0b 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/coins/DiscreteCoinBag.java @@ -131,6 +131,12 @@ public static DiscreteCoinBag ofGreedy(int totalSpurValue) { return bag; } + public static DiscreteCoinBag ofChange(int costInSpurs, Coin coinToBreak) + { + return DiscreteCoinBag.ofGreedy(coinToBreak.value - costInSpurs); + } + + public static DiscreteCoinBag of() { return new DiscreteCoinBag(); } From 12d2f101d846571fc09aa991c80e17934ce68bad Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 23:11:55 -0400 Subject: [PATCH 18/19] Revisited completeTransaction. Combined now checks correctly --- .../checkout/DeferredCheckoutOrder.java | 69 ++++++++++++++----- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java index c23d0b26..d8b11963 100644 --- a/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java +++ b/common/src/main/java/dev/ithundxr/createnumismatics/content/checkout/DeferredCheckoutOrder.java @@ -21,10 +21,11 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; import java.util.UUID; -public class DeferredCheckoutOrder{ +public class DeferredCheckoutOrder { public UUID id; private boolean closed = false; @@ -112,38 +113,74 @@ public boolean completePurchase(CheckoutPaymentMethod method, UUID purchasingAcc return false; } - if (itemCost.isEmpty()) { - if (!CheckoutUtilities.checkOrderPreconditions(stockTicker, deferredOrder, level, player)) { - // checkOrderPreconditions displays the chat message - return false; - } + if (!CheckoutUtilities.checkOrderPreconditions(stockTicker, deferredOrder, level, player)) { + // checkOrderPreconditions displays the chat message + return false; + } - if (method == CheckoutPaymentMethod.CARD && account.getBalance() < costInSpurs) { - CheckoutUtilities.denyPurchase(level, player, "numismatics.checkout.insufficient_funds"); - return false; - } + if (method == CheckoutPaymentMethod.CARD && account.getBalance() < costInSpurs) { + CheckoutUtilities.denyPurchase(level, player, "numismatics.checkout.insufficient_funds"); + return false; + } - if (method == CheckoutPaymentMethod.COINS && !playerHasEnoughCoinsInInventory(player.getInventory(), costInSpurs)) { - CheckoutUtilities.denyPurchase(level, player, "create.stock_keeper.too_broke"); + if (method == CheckoutPaymentMethod.COINS && !playerHasEnoughCoinsInInventory(player.getInventory(), costInSpurs)) { + CheckoutUtilities.denyPurchase(level, player, "create.stock_keeper.too_broke"); + return false; + } + + /* + So below, we do the transaction two different ways: + 1) if its coins only, we can just skip a lot of steps, do the coin transaction, and submit the package order + directly to the system. Less points of failure, everyone wins. + 2) if we need to take coins and money, we'll let Create take the items first, then we'll take the coins. + *in theory* this is safe because: + a) if its a card transaction, the account balance is sufficient, and the player is authorized + b) if its a coin transaction, the player has enough coins in their inventory to cover the cost. + but technically there is a logical flow here that could result in *someone* getting short changed. + If this does somehow occur, the player will get some free items. If this becomes an issue, we can split the order up + into two orders, one for coins and one for items, then submit them either back to back, or merge them and submit. + */ + if (itemCost.isEmpty()) { + if (!tryDoCoinTransaction(method, account)) { + Numismatics.LOGGER.warn("Failed to do a numismatics transaction, even though all the preconditions passed!"); + CheckoutUtilities.denyPurchase(level, player, "numismatics.checkout.failure"); return false; } - // If there's no item cost, we can skip a lot of the default create interaction, and just submit the order + // If there's no item cost, we can skip a lot of the default create interaction. and just submit the order. CheckoutUtilities.shopInteractionSubmitToNetwork(stockTicker, deferredOrder, player, level, packageAddress); } else { // There are item costs in the shopping list, so we must submit the order through the standard pipeline. + // This could potentially fail because of the stock keeper being too full, so we'll submit the order, let it + // take any items as payment. var receivedPayments = ((MixinStockTickerBlockEntityReceivedPaymentsAccessor) stockTicker).getReceivedPayments(); if (!CheckoutUtilities.finishShopInteractionStock(stockTicker, level, player, itemCost, deferredOrder, receivedPayments, packageAddress)) { - // stock checkout failed, cancel the transaction + return false; + } + + if (!tryDoCoinTransaction(method, account)) { + Numismatics.LOGGER.warn("Failed to do a numismatics transaction, even though all the preconditions passed! " + + "Unfortunately, the stock order has already been placed and we cannot unwind it."); + CheckoutUtilities.denyPurchase(level, player, "numismatics.checkout.failure"); return false; } } + return true; + } + private boolean tryDoCoinTransaction(CheckoutPaymentMethod method, @Nullable BankAccount account) { switch (method) { - case CARD -> account.deduct(costInSpurs); + case CARD -> { + if (account == null || !account.isAuthorized(player) || account.getBalance() < costInSpurs) { + return false; + } + account.deduct(costInSpurs); + } case COINS -> { - if (!tryPayInSpurs(player.getInventory(), costInSpurs)) + if (!tryPayInSpurs(player.getInventory(), costInSpurs)) { Numismatics.LOGGER.warn("Attempted to pay {} spurs from player {} ({}) inventory, but failed!", costInSpurs, player.getName(), player.getUUID()); + return false; + } } default -> throw new IllegalStateException("Unexpected value: " + method); } From d71997ed0ab63acbafb1acea3af4320953098656 Mon Sep 17 00:00:00 2001 From: Adam Schiavone Date: Tue, 12 Aug 2025 23:19:22 -0400 Subject: [PATCH 19/19] Resources --- .../.cache/64c87e664647124a7b6450bc49fb722594ed5a16 | 6 +++--- .../generated/resources/assets/numismatics/lang/en_ud.json | 1 + .../generated/resources/assets/numismatics/lang/en_us.json | 1 + .../assets/numismatics/lang/default/interface.json | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 b/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 index 786e4057..585f3f05 100644 --- a/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 +++ b/common/src/generated/resources/.cache/64c87e664647124a7b6450bc49fb722594ed5a16 @@ -1,12 +1,12 @@ -// 1.21.1 2025-08-12T21:12:26.4579249 Registrate Provider for numismatics [Registries, Data Maps, Recipes, Advancements, Loot Tables, Tags (enchantments), Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), generic_server_provider, Blockstates, Item models, Lang (en_us/en_ud), generic_client_provider] +// 1.21.1 2025-08-12T23:13:17.5266632 Registrate Provider for numismatics [Registries, Data Maps, Recipes, Advancements, Loot Tables, Tags (enchantments), Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), generic_server_provider, Blockstates, Item models, Lang (en_us/en_ud), generic_client_provider] f7f43dd6d567ec8303c73b79409bc92d8b56574a assets/numismatics/blockstates/andesite_depositor.json 3961fdf3030140fc32e0e8c1d440ac395e62f5b6 assets/numismatics/blockstates/bank_terminal.json 06ecd28cd97f4e8200dc396858695cad57b871c8 assets/numismatics/blockstates/blaze_banker.json 160d556c6bfdb651082b39784258f6d06c21ca8f assets/numismatics/blockstates/brass_depositor.json 95ef415a564eba1d212053195d25b199427b94e3 assets/numismatics/blockstates/creative_vendor.json d2b105f0657bad99b8efed45dc0a8df8ff775c10 assets/numismatics/blockstates/vendor.json -da2ea46b51689cb1062f072ab847b8e164a414e2 assets/numismatics/lang/en_ud.json -56639735048e12545395308eb903659b052b2d9e assets/numismatics/lang/en_us.json +7876db60df471405f6593ff4e01569bc6e60c852 assets/numismatics/lang/en_ud.json +0807126a759d0f21c95f49e53178463e07a491d8 assets/numismatics/lang/en_us.json 265ef24d62bc7580e763e1fb6802bf4e58dc0194 assets/numismatics/models/block/andesite_depositor.json 4f78ca868db20495aa20be7c6a14e2678fb16f9f assets/numismatics/models/block/andesite_depositor_locked.json 411b79f79547a0adcb665bf7440e8169f7dcb24e assets/numismatics/models/block/brass_depositor.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 fab5c4c1..b885fa3c 100644 --- a/common/src/generated/resources/assets/numismatics/lang/en_ud.json +++ b/common/src/generated/resources/assets/numismatics/lang/en_ud.json @@ -110,6 +110,7 @@ "item.numismatics.yellow_id_card": "pɹɐƆ ᗡI ʍoןןǝʎ", "itemGroup.numismatics": "sɔıʇɐɯsıɯnN :ǝʇɐǝɹƆ", "numismatics.andesite_depositor.price": "ǝɔıɹԀ", + "numismatics.checkout.failure": "˙pǝbɹɐɥɔ uǝǝq ʇou ǝʌɐɥ noʎ ¡uoıʇɔɐsuɐɹʇ buıssǝɔoɹd ɹoɹɹƎ", "numismatics.checkout.insufficient_funds": "¡spunɟ ʇuǝıɔıɟɟnsuI", "numismatics.checkout.unauthorized": "pɹɐɔ ʇɐɥʇ ǝsn oʇ pǝzıɹoɥʇnɐ ʇou ǝɹ,noʎ", "numismatics.trust_list.configure": "ʇsıꞀ ʇsnɹ⟘ ǝɹnbıɟuoƆ" 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 9977bf3e..4c5d770b 100644 --- a/common/src/generated/resources/assets/numismatics/lang/en_us.json +++ b/common/src/generated/resources/assets/numismatics/lang/en_us.json @@ -110,6 +110,7 @@ "item.numismatics.yellow_id_card": "Yellow ID Card", "itemGroup.numismatics": "Create: Numismatics", "numismatics.andesite_depositor.price": "Price", + "numismatics.checkout.failure": "Error processing transaction! You have not been charged.", "numismatics.checkout.insufficient_funds": "Insufficient funds!", "numismatics.checkout.unauthorized": "You're not authorized to use that card", "numismatics.trust_list.configure": "Configure Trust List" 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 e7185c0c..efbc200e 100644 --- a/common/src/main/resources/assets/numismatics/lang/default/interface.json +++ b/common/src/main/resources/assets/numismatics/lang/default/interface.json @@ -28,6 +28,7 @@ "numismatics.checkout.unauthorized": "You're not authorized to use that card", "numismatics.checkout.insufficient_funds": "Insufficient funds!", + "numismatics.checkout.failure": "Error processing transaction! You have not been charged.", "block.numismatics.trusted_block.attempt_break": "Hold shift to break this block" } \ No newline at end of file