Skip to main content

Save System Integration

Merchant state automatically persists via ISaveable interface. Saved Data:
  • Merchant GUID
  • Current stock levels
  • Transaction log
  • Processed nonces (for idempotency)
Save Trigger:
  • Every transaction
  • Restock operations
  • Manual via USaveSubsystem::FlushSaves()

Save Record Structure

struct FMerchantSaveRecord
{
    int32 Version = 1;                      // Save format version
    FGuid MerchantGuid;                     // Unique ID
    TArray<FStockItem> StockItems;          // Stock state
    TArray<FMerchantTransaction> TransactionLog;
    TArray<FGuid> ProcessedNonces;          // Recent nonces
};

Load Behavior

On server restart:
  • Stock levels restore to saved state
  • Transaction history preserved
  • Nonce cache restored (prevents replay attacks)

Error Handling

Error Codes

CodeTriggerResolution
ERR_VENDOR_NOT_FOUNDInvalid merchant referenceCheck merchant exists
ERR_VENDOR_CLOSEDMerchant unavailableFuture: time-of-day restrictions
ERR_NO_STOCKStock depletedWait for restock or buy elsewhere
ERR_QTY_EXCEEDS_STOCKRequested > availableReduce quantity
ERR_INSUFFICIENT_FUNDSNot enough currencyEarn more currency
ERR_INVENTORY_FULLNo space in inventoryFree up inventory space
ERR_NOT_BUYINGMerchant doesn’t buyFind different merchant
ERR_NOT_IN_BUYLISTItem not in whitelistSell different item
ERR_INVALID_QTYZero or negative quantityUse positive quantity
ERR_STALE_QUOTEPrice changed since quoteGet new quote
ERR_DUPLICATE_NONCETransaction already processedIgnore (already completed)
ERR_OUT_OF_RANGEPlayer too far from merchantMove closer
ERR_INVALID_ITEMNull item referenceFix item reference
ERR_INVALID_PLAYERNull player referenceFix player reference

Error Handling Pattern

FPurchaseQuote Quote = Merchant->QuotePurchase(Player, Item, Qty);

if (!Quote.bSuccess)
{
    switch (Quote.ErrorCode)
    {
        case EMerchantErrorCode::ERR_NO_STOCK:
            DisplayToPlayer("Out of stock! Try again later.");
            break;

        case EMerchantErrorCode::ERR_INSUFFICIENT_FUNDS:
            DisplayToPlayer(FString::Printf(
                "Need %d gold (you have %d)",
                Quote.Total,
                PlayerGold
            ));
            break;

        case EMerchantErrorCode::ERR_INVENTORY_FULL:
            DisplayToPlayer("Inventory full! Free up space.");
            break;

        case EMerchantErrorCode::ERR_OUT_OF_RANGE:
            DisplayToPlayer("Too far from merchant!");
            break;

        default:
            DisplayToPlayer("Cannot complete transaction.");
            break;
    }
    return;
}

// Success - display quote to player
DisplayQuote(Quote);