1
Inventory
minjaesong edited this page 2025-11-24 21:24:45 +09:00

Inventory

The inventory system manages storage and organisation of items for actors, fixtures, and the world itself. It handles item stacking, equipment, encumbrance, and item persistence.

Overview

Terrarum uses a flexible inventory system with several specialisations:

  1. FixtureInventory — Base inventory for storage containers
  2. ActorInventory — Extended inventory for actors with equipment and quickslots
  3. ItemTable — Dynamic item storage for worlds

GameItem

All items in the game extend the GameItem base class.

Item Identification

Items use string-based IDs:

typealias ItemID = String

Item ID formats:

  • Static items: item@<module>:<id> (e.g., item@basegame:1)
  • Blocks as items: <module>:<id> (e.g., basegame:32)
  • Dynamic items: item@<module>:D<number> (e.g., item@basegame:D4096)
  • Actor items: Special prefix for actors in pockets

Original ID vs. Dynamic ID

Items have two ID fields:

val originalID: ItemID    // The base item type (e.g., "item@basegame:1")
var dynamicID: ItemID     // The specific instance (may be same as originalID)

Static items have originalID == dynamicID and can stack.

Dynamic items get unique dynamicID values and represent individual instances (e.g., a worn pickaxe).

Key Properties

// Basic Properties
var baseMass: Double                 // Mass in kilograms
var baseToolSize: Double?            // Physical size for tools
var inventoryCategory: String        // "weapon", "tool", "armour", etc.

// Display
var originalName: String             // Translation key
var name: String                     // Displayed name (with custom names)
var nameColour: Color                // Item name colour (rarity, etc.)
var nameSecondary: String            // Additional description

// Behaviour
val canBeDynamic: Boolean            // Can create unique instances
val isCurrentlyDynamic: Boolean      // Is this a dynamic instance
var stackable: Boolean               // Can stack in inventory
val isConsumable: Boolean            // Destroyed on use
var isUnique: Boolean                // Only one can exist
var equipPosition: Int               // Where to equip (EquipPosition enum)

Item Categories

The inventoryCategory classifies items:

  • weapon — Melee and ranged weapons
  • tool — Pickaxes, axes, hammers
  • armour — Protective equipment
  • block — Placeable blocks
  • wire — Wires and conduits
  • fixture — Furniture and fixtures
  • generic — Miscellaneous items
  • quest — Quest-related items

Equipment Positions

Items can be equipped in specific slots defined by EquipPosition:

EquipPosition.HAND_PRIMARY      // Main hand
EquipPosition.HAND_SECONDARY    // Off hand
EquipPosition.HEADGEAR          // Helmet
EquipPosition.ARMOUR            // Body armour
EquipPosition.LEGGINGS          // Leg armour
EquipPosition.BOOTS             // Footwear
EquipPosition.ACCESSORY_1       // Ring, amulet, etc.
EquipPosition.ACCESSORY_2
// ... more accessory slots
EquipPosition.NULL              // Not equippable

Dynamic vs. Static Items

Static items are identical instances that stack:

  • Blocks
  • Consumables
  • Resources
  • Stackable materials

Dynamic items have individual state:

  • Tools with durability
  • Weapons with enchantments
  • Armour with damage
  • Custom-named items

Creating a dynamic instance:

val dynamicItem = staticItem.makeDynamic(inventory)

Dynamic items get assigned IDs in the range 32768..1048575.

Durability

Dynamic items can have durability:

var durability: Float    // Current durability (0.0 = broken)
val maxDurability: Float // Maximum durability when new

When durability reaches 0, the item is destroyed.

Material System

Items are made from materials:

val materialId: String       // Material identifier
val material: Material       // Full material object

Materials affect item properties like mass, durability, and value. See MaterialCodex for available materials.

Combustibility

Items with the COMBUSTIBLE tag can be used as fuel:

var calories: Double                 // Energy content (game-calories)
var smokiness: Float                 // Smoke emission rate

1 game-calorie ≈ 5 watt-hours. An item burning for 80 seconds at 60 FPS has 4800 calories.

Tags

Items use tags to mark special properties:

interface TaggedProp {
    fun hasTag(tag: String): Boolean
    fun addTag(tag: String)
}

Common tags:

  • TOOL — Can be used as a tool
  • WEAPON — Can be used as a weapon
  • COMBUSTIBLE — Can be burned as fuel
  • MAGIC — Has magical properties
  • TREASURE — Valuable loot

FixtureInventory

The base inventory class for storage containers.

Creating an Inventory

val inventory = FixtureInventory(
    maxCapacity = 1000L,           // Maximum capacity
    capacityMode = CAPACITY_MODE_COUNT
)

Capacity Modes

CAPACITY_MODE_COUNT        // Limit by item count
CAPACITY_MODE_WEIGHT       // Limit by total weight
CAPACITY_MODE_NO_ENCUMBER  // Unlimited capacity

Adding Items

// Add an item
inventory.add(item: GameItem, count: Long = 1L)

// Add by ID
inventory.add(itemID: ItemID, count: Long)

Items automatically stack if they're stackable and have matching IDs.

Removing Items

// Remove by ID
val removedCount = inventory.remove(itemID: ItemID, count: Long): Long

// Remove by item
val removedCount = inventory.remove(item: GameItem, count: Long): Long

Returns the actual number removed (may be less than requested).

Querying Items

// Check if item exists
val has: Boolean = inventory.has(itemID: ItemID, minCount: Long = 1L)

// Search by ID
val pair: InventoryPair? = inventory.searchByID(itemID: ItemID)

// Get item count
val count: Long = inventory.count(itemID: ItemID)

InventoryPair

Inventory items are stored as pairs:

data class InventoryPair(
    val itm: ItemID,      // Item ID
    var qty: Long         // Quantity
)

Item List

Access all items in the inventory:

val itemList: List<InventoryPair> = inventory.itemList

Important: Don't modify the item list directly. Use add() and remove() methods.

Capacity Tracking

val capacity: Long        // Current used capacity
val maxCapacity: Long     // Maximum capacity
val encumberment: Double  // 0.0-1.0+ (over 1.0 = overencumbered)

Clearing Inventory

val removedItems: List<InventoryPair> = inventory.clear()

Returns all items that were removed.

ActorInventory

Extended inventory for actors (players, NPCs) with equipment and quickslots.

Creating an Actor Inventory

val inventory = ActorInventory(
    actor = playerActor,
    maxCapacity = 10000L,
    capacityMode = CAPACITY_MODE_WEIGHT
)

Equipment System

Actors can equip items in specific slots:

// Equipment array (indexed by EquipPosition)
val itemEquipped: Array<ItemID?> = inventory.itemEquipped

// Equip an item
actor.equipItem(itemID: ItemID)

// Unequip an item
actor.unequipItem(itemID: ItemID)

Important: Equipped items must also exist in the main itemList. The equipment array stores references (dynamicID), not separate copies.

Quickslots

Actors have a quickslot bar for fast access:

val quickSlot: Array<ItemID?> = inventory.quickSlot  // 10 slots by default

// Set quickslot
inventory.setQuickslotItem(slot: Int, dynamicID: ItemID?)

// Get quickslot item
val pair: InventoryPair? = inventory.getQuickslotItem(slot: Int?)

Quickslots reference items in the main inventory by their dynamicID.

Dynamic Capacity

For actors, capacity can scale with actor size:

val maxCapacityByActor: Double  // Scaled by actor's scale squared

This means larger actors can carry more.

Consuming Items

inventory.consumeItem(item: GameItem, amount: Long = 1L)

This method:

  1. Removes consumable items
  2. Applies durability damage to tools/weapons
  3. Auto-equips replacements when tools break
  4. Handles dynamic item unpacking

Item Durability Management

When using tools/weapons, durability decreases:

// Damage calculation
val baseDamage = actor.avStrength / 1000.0
item.durability -= damageAmount

// When durability reaches 0
if (item.durability <= 0) {
    // Item is removed and auto-replaced if available
}

ItemCodex

The global registry of all items:

object ItemCodex {
    // Look up item by ID
    operator fun get(itemID: ItemID): GameItem?

    // Register a new item
    fun registerItem(item: GameItem)
}

Access items via the global codex:

val item = ItemCodex[itemID]

Item Images

Items have associated sprites:

// Get item image (use this, not item.itemImage directly)
val texture: TextureRegion = ItemCodex.getItemImage(item)

Never read item.itemImage directly due to initialization order issues. Always use ItemCodex.getItemImage().

Inventory UI

ActorInventory integrates with several UI components:

UIQuickslotBar

Displays and manages quickslot items.

UIInventoryFull

Full inventory screen with equipment, storage, and crafting.

InventoryTransactionNegotiator

Handles item transfers between inventories (drag-and-drop, shift-click, etc.).

Best Practises

  1. Use dynamic IDs for equipment — Store dynamicID in equipment/quickslot arrays, not originalID
  2. Check canBeDynamic before creating instances — Only dynamic items can have individual state
  3. Clean up equipment on item removal — ActorInventory does this automatically
  4. Validate item existence — Always check ItemCodex[id] returns non-null
  5. Use ItemCodex.getItemImage() — Never access item.itemImage directly
  6. Scale capacity by actor size — Use maxCapacityByActor for realistic encumbrance
  7. Handle item consumption properly — Use consumeItem() for tools to manage durability
  8. Respect capacity modes — Check encumberment to limit player movement when overencumbered

Common Patterns

Adding a Block to Inventory

// Blocks use module:id format, not item@module:id
val blockID = "basegame:32"
inventory.add(blockID, 64L)  // Add 64 blocks

Equipping a Tool

val pickaxe = ItemCodex["item@basegame:pickaxe_iron"]
if (pickaxe != null && pickaxe.canBeDynamic) {
    // Create dynamic instance
    val dynamicPickaxe = pickaxe.makeDynamic(actor.inventory)
    actor.inventory.add(dynamicPickaxe)

    // Equip it
    actor.equipItem(dynamicPickaxe.dynamicID)

    // Add to quickslot
    actor.inventory.setQuickslotItem(0, dynamicPickaxe.dynamicID)
}

Checking Crafting Requirements

fun hasRequiredItems(inventory: FixtureInventory, requirements: Map<ItemID, Long>): Boolean {
    return requirements.all { (itemID, count) ->
        inventory.has(itemID, count)
    }
}

Transferring Items Between Inventories

fun transferItem(from: FixtureInventory, to: FixtureInventory, itemID: ItemID, count: Long): Long {
    val removed = from.remove(itemID, count)
    if (removed > 0) {
        to.add(itemID, removed)
    }
    return removed
}

Finding All Tools

val tools = actor.inventory.itemList
    .mapNotNull { ItemCodex[it.itm] }
    .filter { it.hasTag("TOOL") }

Serialisation

Inventories are serialisable for save games. However:

  • actor field is @Transient — Reconstructed on reload
  • Equipment and quickslots are saved
  • Dynamic items preserve their unique state

On reload, call:

inventory.actor = restoredActor

Advanced Topics

Custom Item Classes

Create custom items by extending GameItem:

class MyCustomItem : GameItem("item@mymod:custom_item") {
    override var baseMass = 1.0
    override var baseToolSize: Double? = null
    override val canBeDynamic = true
    override val materialId = "iron"
    override var inventoryCategory = "generic"

    init {
        originalName = "ITEM_CUSTOM_NAME"
        equipPosition = EquipPosition.HAND_PRIMARY
    }

    override fun startPrimaryUse(actor: ActorWithBody, delta: Float) {
        // Custom usage logic
    }
}

Inventory Filtering

Filter items by category or tag:

val weapons = inventory.itemList
    .mapNotNull { ItemCodex[it.itm] }
    .filter { it.inventoryCategory == "weapon" }

Weight Calculation

Calculate total inventory weight:

val totalWeight = inventory.itemList.sumOf { (itemID, quantity) ->
    (ItemCodex[itemID]?.baseMass ?: 0.0) * quantity
}

See Also