Files
Documentation/content/world/entities/inventory/transactions.en.md
2026-01-20 20:33:59 +01:00

12 KiB

title, type, weight
title type weight
Transactions docs 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:

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:

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:

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:

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:

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:

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:

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:

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

// 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

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

// 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

// 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
// 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 >}}
// 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

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

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