431 lines
12 KiB
Markdown
431 lines
12 KiB
Markdown
---
|
|
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 |
|