--- title: Transactions type: docs weight: 3 --- Transactions track the results of inventory operations in Hytale. Every modification to an `ItemContainer` returns a Transaction object that describes what happened. ## Transaction Interface All transactions implement the base `Transaction` interface: ```java public interface Transaction { // Did the operation succeed? boolean succeeded(); // Was a specific slot modified? boolean wasSlotModified(short slot); } ``` ## Transaction Types ### SlotTransaction Tracks changes to a single slot: ```java SlotTransaction transaction = container.removeItemStackFromSlot((short) 0); if (transaction.succeeded()) { // What was in the slot before ItemStack before = transaction.getSlotBefore(); // What is in the slot now ItemStack after = transaction.getSlotAfter(); // The item that was removed/output ItemStack output = transaction.getOutput(); // Which slot was affected short slot = transaction.getSlot(); // What type of action (ADD, REMOVE, REPLACE) ActionType action = transaction.getAction(); } ``` ### ItemStackSlotTransaction Extended slot transaction with additional details: ```java ItemStackSlotTransaction transaction = container.setItemStackForSlot( (short) 0, new ItemStack("iron_sword", 1) ); if (transaction.succeeded()) { short slot = transaction.getSlot(); ItemStack before = transaction.getSlotBefore(); ItemStack after = transaction.getSlotAfter(); // Check options used boolean wasFiltered = transaction.isFilter(); boolean wasAllOrNothing = transaction.isAllOrNothing(); } ``` ### ItemStackTransaction Tracks operations that may affect multiple slots: ```java ItemStack toAdd = new ItemStack("stone", 128); ItemStackTransaction transaction = container.addItemStack(toAdd); if (transaction.succeeded()) { // Items that couldn't fit (null if all fit) ItemStack remainder = transaction.getRemainder(); // Original item we tried to add ItemStack query = transaction.getQuery(); // List of all slot transactions that occurred List slotTransactions = transaction.getSlotTransactions(); for (ItemStackSlotTransaction slotTx : slotTransactions) { getLogger().at(Level.INFO).log("Modified slot " + slotTx.getSlot()); } } ``` ### MoveTransaction Tracks moving items between containers: ```java MoveTransaction transaction = storage.moveItemStackFromSlotToSlot( (short) 0, 32, hotbar, (short) 0 ); if (transaction.succeeded()) { // Transaction for removing from source SlotTransaction removeTransaction = transaction.getRemoveTransaction(); // Transaction for adding to destination SlotTransaction addTransaction = transaction.getAddTransaction(); // The destination container ItemContainer destination = transaction.getOtherContainer(); // Direction of the move MoveType moveType = transaction.getMoveType(); } ``` ### ListTransaction Wraps multiple transactions: ```java List items = List.of( new ItemStack("stone", 64), new ItemStack("wood", 64) ); ListTransaction transaction = container.addItemStacks(items); if (transaction.succeeded()) { List results = transaction.getList(); for (ItemStackTransaction result : results) { if (result.succeeded()) { ItemStack remainder = result.getRemainder(); // ... } } } ``` ## ActionType Operations are categorized by action type: ```java public enum ActionType { SET, // Items set (add=true, remove=false, destroy=true) ADD, // Items added to slot (add=true, remove=false, destroy=false) REMOVE, // Items removed from slot (add=false, remove=true, destroy=false) REPLACE // Slot contents replaced (add=true, remove=true, destroy=false) } // Check action characteristics if (action.isAdd()) { /* operation adds items */ } if (action.isRemove()) { /* operation removes items */ } if (action.isDestroy()) { /* operation destroys slot contents */ } ``` ## MoveType Direction of move operations: ```java public enum MoveType { MOVE_TO_SELF, // Items being moved TO this container MOVE_FROM_SELF // Items being moved FROM this container } ``` ## Common Patterns ### Check Before Modify ```java // Safe pattern: verify first, then execute public boolean safeTransfer(ItemContainer from, ItemContainer to, String itemId, int amount) { ItemStack toRemove = new ItemStack(itemId, amount); // Check both operations can succeed if (!from.canRemoveItemStack(toRemove)) { return false; // Not enough items } if (!to.canAddItemStack(toRemove)) { return false; // No space } // Execute removal ItemStackTransaction removeResult = from.removeItemStack(toRemove); if (!removeResult.succeeded()) { return false; } // Execute addition ItemStack removed = removeResult.getQuery(); ItemStackTransaction addResult = to.addItemStack(removed); // Handle any remainder ItemStack remainder = addResult.getRemainder(); if (!ItemStack.isEmpty(remainder)) { // Put remainder back from.addItemStack(remainder); } return true; } ``` ### Handling Remainders ```java public void giveItemSafe(Player player, String itemId, int quantity) { ItemContainer storage = player.getInventory().getStorage(); ItemStack item = new ItemStack(itemId, quantity); ItemStackTransaction result = storage.addItemStack(item); if (!result.succeeded()) { player.sendMessage(Message.raw("Inventory full!")); return; } ItemStack remainder = result.getRemainder(); if (!ItemStack.isEmpty(remainder)) { player.sendMessage(Message.raw( "Only received " + (quantity - remainder.getQuantity()) + " items, " + "inventory full!" )); } } ``` ### Atomic Operations ```java // allOrNothing=true ensures partial operations don't happen public boolean buyItem(Player player, String itemId, int price, int quantity) { Inventory inv = player.getInventory(); ItemContainer storage = inv.getStorage(); ItemStack currency = new ItemStack("gold_coin", price); ItemStack item = new ItemStack(itemId, quantity); // Check both operations can succeed fully if (!storage.canRemoveItemStack(currency)) { player.sendMessage(Message.raw("Not enough gold!")); return false; } if (!storage.canAddItemStack(item)) { player.sendMessage(Message.raw("Inventory full!")); return false; } // Remove currency with allOrNothing=true ItemStackTransaction removeResult = storage.removeItemStack(currency, true, true); if (!removeResult.succeeded()) { return false; } // Add item ItemStackTransaction addResult = storage.addItemStack(item, true, false, true); if (!addResult.succeeded()) { // Rollback: return the currency storage.addItemStack(currency); return false; } return true; } ``` ### Tracking Changes ```java // Use wasSlotModified to check specific slots public void onContainerChange(ItemContainer.ItemContainerChangeEvent event) { Transaction transaction = event.transaction(); // Check if hotbar slot 0 was affected if (transaction.wasSlotModified((short) 0)) { getLogger().at(Level.INFO).log("First slot was modified!"); } // Check all hotbar slots for (short i = 0; i < 9; i++) { if (transaction.wasSlotModified(i)) { getLogger().at(Level.INFO).log("Hotbar slot " + i + " modified"); } } } ``` ## Transaction Options Many operations accept optional parameters: | Parameter | Description | Default | |-----------|-------------|---------| | `allOrNothing` | If true, operation fails if not all items can be processed | `false` | | `fullStacks` | If true, only add to empty slots (not partial stacks) | `false` | | `exactAmount` | If true, must remove exact quantity requested | `true` | | `filter` | If true, respect slot filters | `true` | ```java // Default behavior container.addItemStack(item); // With options container.addItemStack(item, true, false, true); // allOrNothing, fullStacks, filter container.removeItemStack(item, true, true); // allOrNothing, filter ``` ## Best Practices {{< callout type="info" >}} **Transaction Tips:** - Always check `succeeded()` before accessing results - Handle remainders when adding items - Use `canAddItemStack()`/`canRemoveItemStack()` for pre-validation - Use `allOrNothing=true` for critical operations - Check `wasSlotModified()` to track specific slot changes {{< /callout >}} ```java // Good: Check success and handle remainder ItemStackTransaction tx = container.addItemStack(item); if (tx.succeeded()) { ItemStack remainder = tx.getRemainder(); if (!ItemStack.isEmpty(remainder)) { // Handle leftover items } } // Bad: Assume success container.addItemStack(item); // Might fail silently! ``` ## Transaction API Reference ### Transaction Interface | Method | Returns | Description | |--------|---------|-------------| | `succeeded()` | `boolean` | True if operation succeeded | | `wasSlotModified(short)` | `boolean` | True if specific slot was modified | ### SlotTransaction | Method | Returns | Description | |--------|---------|-------------| | `getSlot()` | `short` | The affected slot index | | `getAction()` | `ActionType` | Type of action performed | | `getSlotBefore()` | `ItemStack?` | Contents before operation | | `getSlotAfter()` | `ItemStack?` | Contents after operation | | `getOutput()` | `ItemStack?` | Items removed/output | | `isAllOrNothing()` | `boolean` | allOrNothing parameter used | | `isExactAmount()` | `boolean` | exactAmount parameter used | | `isFilter()` | `boolean` | filter parameter used | | Static | Type | Description | |--------|------|-------------| | `FAILED_ADD` | `SlotTransaction` | Pre-built failed add transaction | ### ItemStackSlotTransaction (extends SlotTransaction) | Method | Returns | Description | |--------|---------|-------------| | `isAddToExistingSlot()` | `boolean` | True if added to existing stack | | `getQuery()` | `ItemStack?` | Original item requested | | `getRemainder()` | `ItemStack?` | Items that couldn't fit | ### ItemStackTransaction | Method | Returns | Description | |--------|---------|-------------| | `getAction()` | `ActionType?` | Type of action performed | | `getQuery()` | `ItemStack?` | Original item requested | | `getRemainder()` | `ItemStack?` | Items that couldn't fit | | `isAllOrNothing()` | `boolean` | allOrNothing parameter used | | `isFilter()` | `boolean` | filter parameter used | | `getSlotTransactions()` | `List` | All slot transactions | | Static | Type | Description | |--------|------|-------------| | `FAILED_ADD` | `ItemStackTransaction` | Pre-built failed add transaction | ### MoveTransaction | Method | Returns | Description | |--------|---------|-------------| | `getRemoveTransaction()` | `SlotTransaction` | Transaction for removal | | `getMoveType()` | `MoveType` | Direction of move | | `getOtherContainer()` | `ItemContainer` | The other container involved | | `getAddTransaction()` | `T` | Transaction for addition | | `toInverted(ItemContainer)` | `MoveTransaction` | Create inverted view for other container | ### ListTransaction | Method | Returns | Description | |--------|---------|-------------| | `getList()` | `List` | All transactions in list | | `size()` | `int` | Number of transactions | | Static | Type | Description | |--------|------|-------------| | `EMPTY_SUCCESSFUL_TRANSACTION` | `ListTransaction` | Pre-built empty success | | `EMPTY_FAILED_TRANSACTION` | `ListTransaction` | Pre-built empty failure | | `getEmptyTransaction(boolean)` | `ListTransaction` | Get empty success/failure | ### ActionType Enum | Value | isAdd | isRemove | isDestroy | Description | |-------|-------|----------|-----------|-------------| | `SET` | true | false | true | Set slot contents | | `ADD` | true | false | false | Add items to slot | | `REMOVE` | false | true | false | Remove items from slot | | `REPLACE` | true | true | false | Replace slot contents | ### MoveType Enum | Value | Description | |-------|-------------| | `MOVE_TO_SELF` | Items moving to this container | | `MOVE_FROM_SELF` | Items moving from this container |