This commit is contained in:
2026-01-20 20:33:59 +01:00
commit b16a40e431
583 changed files with 87339 additions and 0 deletions

View File

@@ -0,0 +1,430 @@
---
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<ItemStackSlotTransaction> slotTransactions = transaction.getSlotTransactions();
for (ItemStackSlotTransaction slotTx : slotTransactions) {
getLogger().at(Level.INFO).log("Modified slot " + slotTx.getSlot());
}
}
```
### MoveTransaction
Tracks moving items between containers:
```java
MoveTransaction<SlotTransaction> 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<ItemStack> items = List.of(
new ItemStack("stone", 64),
new ItemStack("wood", 64)
);
ListTransaction<ItemStackTransaction> transaction = container.addItemStacks(items);
if (transaction.succeeded()) {
List<ItemStackTransaction> 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<ItemStackSlotTransaction>` | All slot transactions |
| Static | Type | Description |
|--------|------|-------------|
| `FAILED_ADD` | `ItemStackTransaction` | Pre-built failed add transaction |
### MoveTransaction<T extends Transaction>
| 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<T>` | Create inverted view for other container |
### ListTransaction<T extends Transaction>
| Method | Returns | Description |
|--------|---------|-------------|
| `getList()` | `List<T>` | 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<T>` | 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 |