Skip to content

ItemKeyAsset — Lock and Key Items

Overview

ItemKeyAsset extends ItemAsset and represents key items used for lock-and-key interactions. It is one of the simplest asset types in the SDK — it introduces exactly one field beyond ItemAsset: exchangeWithTargetItem, a boolean flag controlling whether using the key on a matching lock consumes both the key and the target item (exchanging them for a defined output) or simply consumes the key and unlocks the target without destroying it.

Despite its simplicity at the asset level, the key system has significant runtime complexity in Useable interaction, NPC door integration, locked container resolution, and quest progression. The key's ID is the sole matching mechanism — any key with the same item ID opens any lock configured to accept that ID.

Inheritance Chain

Asset
  └── ItemAsset
        └── ItemKeyAsset

No intermediate base classes. Keys are held as regular inventory items (not equipped), and their use is triggered via the standard item-use pipeline when targeting a locked object.

The exchangeWithTargetItem Flag

This is the only field added by ItemKeyAsset:

csharp
public bool exchangeWithTargetItem;

Parsed from the .dat file by checking for the presence of the key, not by reading a boolean value:

csharp
public override void PopulateAsset(in PopulateAssetParameters p)
{
    base.PopulateAsset(in p);
    exchangeWithTargetItem = p.data.ContainsKey("Exchange_With_Target_Item");
}

This is unusual — most boolean fields use ParseBool, which reads the key's value. Here, the mere presence of the key Exchange_With_Target_Item in the .dat (regardless of its value) sets the flag to true. This means:

  • Exchange_With_Target_Item trueexchangeWithTargetItem = true
  • Exchange_With_Target_Item falseexchangeWithTargetItem = true (still true!)
  • Key absent entirely → exchangeWithTargetItem = false

The value of the key is never read. Modders cannot set Exchange_With_Target_Item false to disable the flag — they must omit the key entirely. This is a binary presence-check pattern rather than a value-check pattern.

Behavior When True

When exchangeWithTargetItem is true, using the key on a compatible locked object:

  1. The key item is removed from the player's inventory.
  2. The locked object (door, container) is unlocked and destroyed/removed.
  3. A defined replacement item or reward is granted.

This is the "quest exchange" pattern: the player finds a locked chest, uses the matching key, both key and chest vanish, and a reward item appears. Common in NPC quests where the player retrieves a key from one location and uses it on a specific container elsewhere.

Behavior When False

When exchangeWithTargetItem is false:

  1. The key is consumed (removed from inventory).
  2. The locked object is unlocked.
  3. The locked object remains in the world (now unlocked).

This is the "reusable lock" pattern: doors that stay open after being unlocked once, or containers that become accessible without being destroyed. The key disappears but the target persists.

Lock Matching

The key system uses a simple ID-matching model. Each lockable object (NPC door, locked container) stores a key ID. When a player uses a key on it, the system checks:

if (keyItem.id == lockedObject.requiredKeyId)

There is no key type, key class, or key tag — just raw ID comparison. This means:

  • Any two items with the same ID are functionally identical keys.
  • A key with ID 58000 opens any lock that requires key ID 58000.
  • There is no master-key or key-ring concept at the asset level (the game mode config layer may implement one in runtime logic).

Locked Object Types

The key system integrates with several locked object types:

NPC Doors: Doors placed in the level or spawned by NPCs can have a Key_ID property. When approached, the interaction prompt checks if the player has an item with a matching ID in their inventory. If so, the door opens (consuming the key based on exchangeWithTargetItem).

Locked Containers: Barricades or world objects with a Locked flag and a Key_ID. Using the key on the container opens it without requiring lockpicking or destruction.

Quest Objects: NPC quest systems can spawn locked objects that require specific keys. The quest system tracks whether the player has used the key and updates quest progress accordingly.

Runtime Interaction Flow

The key use pipeline (simplified from the Useable system):

  1. Player targets a lockable object: Raycast or proximity check identifies a door or container.
  2. Interaction prompt appears: If the object is locked and the player has a key item anywhere in their inventory, the UI shows the unlock prompt.
  3. Player presses use: The game searches the player's inventory for any ItemKeyAsset item whose ID matches the lock's required key ID.
  4. Match found:
    • If exchangeWithTargetItem is true: the key is consumed (stack decremented), the target is removed, and the replacement is granted.
    • If exchangeWithTargetItem is false: the key is consumed, the target is unlocked but not removed.
  5. No match found: Nothing happens. The key is not consumed. The lock remains locked.

The search iterates the player's inventory pages looking for any item with itemAsset is ItemKeyAsset && item.id == lockKeyId. It checks every slot — hotbar, clothing pockets, backpack — not just the currently held item. The first matching key found is used.

If the player has multiple copies of the same key, only one stack decrements. If the stack reaches zero, the item slot is cleared.

Key Consumption Edge Cases

  • Stack of keys: If the player has 3x of a key item, using one decrements the stack to 2. The other 2 remain.
  • Key in clothing pocket: Keys stored in equipped clothing (shirt/pants pockets) are found by the search just like backpack items.
  • Key in storage: Keys in storage containers (crates, vehicles) are NOT searched. The key must be in the player's personal inventory.

Quest Integration

The key system is tightly coupled with NPC quest logic:

Key-as-Quest-Item Pattern

  1. NPC gives quest: "Retrieve the key from the bandit camp."
  2. The bandit camp contains a loot spawn with the key item.
  3. Player picks up the key → quest condition updates ("Key acquired").
  4. NPC gives follow-up: "Use the key on the locked warehouse door."
  5. Player uses key on the warehouse door → quest condition updates ("Door unlocked").
  6. If exchangeWithTargetItem is true, the quest can also trigger on the exchange grant.

Quest Condition Tracking

Quest conditions check for key usage via:

  • Item acquired: Player picks up a key with a specific ID.
  • Item used: Player uses a key on a specific target object.
  • Target unlocked: The locked object's state changes to unlocked.

Key-as-Currency Pattern

In some quests, keys function as abstract currency: "Bring me 3 security keycards" where each keycard is a separate key item. The quest system counts how many of each key ID the player has and consumes them on turn-in.

.dat Configuration

A minimal key asset:

ID 58000
Rarity Uncommon
Size_X 1
Size_Y 1
Slot None

A key with exchange behavior:

ID 58001
Rarity Rare
Size_X 1
Size_Y 1
Slot None
Exchange_With_Target_Item

Note the key is present with no value — its mere presence activates the flag.

Slot Assignment

Key items are typically Slot None because they don't need to be equipped. They're used from the inventory directly when targeting a locked object. However, there is no code restriction — a key with Slot Primary or Slot Secondary works identically; the slotted key is still found by the inventory search.

Size

Most keys are 1×1 or 1×2. There is no maximum size restriction. A 2×2 "key" functions the same as a 1×1 key — the size is purely an inventory management consideration.

Interaction with Other Systems

Lockpicking

Keys and lockpicks coexist. A locked object can have both a Key_ID and a lockpicking difficulty. If the player has the matching key, key use takes priority (instant unlock). If the player has no key, they can attempt lockpicking with a lockpick item and the relevant skill. The game checks for a matching key first; only if no key is found does it offer the lockpicking interaction.

Vehicle Locks

Vehicle doors use a different lock system (the vehicle's owner/group lock system) and do not interact with ItemKeyAsset. Vehicle lockpicking uses ItemVehicleLockpickToolAsset, not key assets. Keys cannot unlock vehicles.

Barricade Ownership

Player-placed barricades use an ownership system (placed-by player ID + group ID). Keys do not override ownership. A locked barricade owned by one player cannot be unlocked by another player's key — the key system only applies to level-placed or NPC-spawned locked objects, not player-placed structures.

Performance Considerations

Inventory Search

The key-matching search is O(n) over the player's inventory where n is the total number of inventory slots. For a player with a full backpack, clothing, and hotbar, this is roughly 50-80 slots. The search stops on the first match, so keys in early slots (hotbar) resolve faster than keys in the last backpack slot.

No Caching

The key system does not cache which keys a player has. Every unlock attempt performs a fresh inventory scan. For a locked door that 50 players run through in quick succession, this is 50 inventory scans. While individually cheap, a high-traffic NPC door in a busy server can generate noticeable per-frame overhead if many players trigger the scan simultaneously.

Asset Lookup

The matching uses item.id directly — there is no Assets.find() call during key use. The key asset is only resolved at asset load time. Runtime key matching is purely integer comparison.

Modding Guide

Creating a Quest Key

ID 58050
ItemName "Rusted Key"
ItemDescription "A rusted iron key found in the old well."
Rarity Rare
Size_X 1
Size_Y 1
Slot None
Exchange_With_Target_Item

Then configure the locked object in the level (or via NPC quest data):

Key_ID 58050

Creating a Reusable Key (Door Unlock)

ID 58051
ItemName "Service Keycard"
ItemDescription "Electronic keycard. Unlocks maintenance doors."
Rarity Common
Size_X 1
Size_Y 1
Slot None

Omit Exchange_With_Target_Item — the key is consumed on use, the door stays.

Multiple Doors Sharing One Key

Multiple level objects can have the same Key_ID. The key opens any of them. Once the key is consumed, the player must find another copy. This is useful for compound locks where one keycard opens all doors in a building.

Common Pitfalls

  1. Exchange flag always on with key present: Remember that ANY presence of Exchange_With_Target_Item in the .dat sets the flag to true, including false. If you want the flag off, omit the key entirely.

  2. Key ID collision with other items: Key IDs share the global item ID namespace. Don't reuse a weapon ID for a key or vice versa — the lock system checks item.id, and a weapon with ID 58050 would also match a lock requiring key ID 58050.

  3. Key not in hotbar: New players often think the key must be in the hotbar or held. The game searches the entire inventory. Keys work from the backpack. No need to equip them.

  4. Quest requiring both pickup AND use: If a quest tracks both "acquired key" and "used key", the player can trade the key and break the quest chain. Consider making the key untradeable (Exchangeable false in the base ItemAsset .dat) to prevent this.

  5. Stack size: Keys default to Amount 1. If a key stack exceeds 1, using one decrements the stack rather than removing the item entirely. This may surprise players who expect the key to vanish.

  6. No master key support: There is no built-in "master key" item that opens all locks. Every lock-key pair is a direct ID match. Modders implementing skeleton keys must do so through custom plugins or code, not through the asset system.

Lock System Architecture

Lock Resolution Pipeline

When a player interacts with a locked object, the following chain executes:

  1. Interaction Detection: Raycast or overlap sphere detects the locked object in range.
  2. Lock State Check: The object's lock state is queried. If already unlocked, skip to interaction.
  3. Inventory Scan: The player's entire inventory is scanned for items matching the lock's Key_ID.
  4. Key Asset Validation: Each candidate item is checked: item.GetAsset() is ItemKeyAsset.
  5. Consumption Mode Selection: The first valid key's exchangeWithTargetItem flag determines the consumption path.
  6. Execution: Either exchange mode (both consumed, replacement granted) or unlock mode (key consumed, target persists).
  7. Quest Notification: Active quests listening for key-use events receive the notification.

Lock State Storage

Locked objects store their state in the world save data:

LockedObjectState {
    bool isLocked;
    ushort requiredKeyId;
    Guid replacementItemGuid;  // Used when exchangeWithTargetItem is true
    bool hasBeenUnlocked;       // Persists across sessions
}

Once unlocked via key, hasBeenUnlocked is set to true. This prevents re-locking — a door unlocked once stays unlocked permanently. The key consumption is tracked via the player's inventory change, not via the lock state.

Anti-Exploit Guards

The key system implements several anti-exploit measures:

Inventory Snapshot: The inventory scan snapshots the player's inventory at interaction time. If the player drops the key mid-interaction, the snapshot still contains it and the unlock proceeds. This prevents race-condition exploits where a player rapidly drops and picks up a key to use it multiple times.

Server Authority: All lock state changes execute on the server. The client sends an interaction request; the server validates the player's inventory, key ID, and lock state before executing. Clients cannot unlock objects by sending forged packets.

Consumption Atomicity: Key consumption and lock state change occur in a single server tick. If the server crashes mid-tick, both changes are rolled back (or both are applied, depending on save granularity). Partial unlocks (key consumed, lock still locked) are not possible.

Lock Re-acquisition Prevention: When exchangeWithTargetItem is true and the target is consumed, the replacement item is generated with EItemOrigin.WORLD or EItemOrigin.ADMIN. This prevents the replacement from being another key that opens the same lock class, avoiding infinite key-exchange loops.

Key ID Namespace Management

Key IDs share the global item ID pool. Best practices for avoiding collisions:

  • Prefix ranges: Reserve ID range 58000-58999 for keys.
  • GUID cross-references: For cross-mod key compatibility, reference keys by GUID in the lock configuration rather than by ID.
  • Namespace registration: Document which ID ranges your mod uses and check for conflicts with other loaded mods.
  • Dynamic ID assignment: For procedurally generated content, allocate key IDs from a reserved dynamic range and track assignments in a runtime registry.

Keychain Design Pattern

While the SDK lacks a native keychain implementation, modders commonly implement one via plugins:

PlayerKeychain {
    Dictionary<ushort, int> keyCounts;  // keyId → count
    
    void AddKey(ushort keyId) { keyCounts[keyId]++; }
    bool HasKey(ushort keyId) => keyCounts.ContainsKey(keyId);
    bool UseKey(ushort keyId) {
        if (!HasKey(keyId)) return false;
        keyCounts[keyId]--;
        if (keyCounts[keyId] <= 0) keyCounts.Remove(keyId);
        return true;
    }
}

A keychain plugin would intercept the lock interaction, check the player's keychain dictionary rather than their inventory, and decrement the keychain count on use. This allows players to carry many key types without filling inventory slots.

Performance: Locked Door High-Traffic Scenarios

NPC doors in busy hub areas can be triggered by dozens of players per minute. The per-interaction inventory scan is O(n) where n is inventory slot count (typically 50-80). At 60 interactions per minute with 50-slot inventories, this is 3,000 slot checks per minute — negligible for modern hardware.

However, if every interaction ALSO triggers a quest condition check (scanning all active quests for key-related conditions), the overhead multiplies. A quest system with 100 active quests checking for key usage on every interaction performs 100 additional scans. At 60 interactions/minute, this becomes 6,000 quest condition evaluations per minute. Quest systems should use event-driven notification (the key-use event fires, quests listen for it) rather than polling.

Serialization and Backup

Key items are serialized identically to other ItemAsset instances. The exchangeWithTargetItem flag is NOT serialized — it's derived from the .dat file at asset load time. This means:

  • Changing a key's Exchange_With_Target_Item flag in the .dat after players already possess the key changes the behavior of existing keys.
  • If a key was created with the flag absent (false) and a server update adds the flag (true), existing keys suddenly trigger exchange behavior.
  • Backward compatibility: test key flag changes against existing player inventories to avoid breaking quest chains.

.dat Reference Table

ItemKeyAsset Complete .dat Schema

KeyTypeDefaultRequiredDescription
IDushortYesUnique item ID in the global namespace
ItemNamestringYesDisplay name in inventory
ItemDescriptionstring""NoTooltip description text
RarityEItemRarityCommonNoItem rarity tier (color)
Size_Xbyte1NoInventory slot width
Size_Ybyte1NoInventory slot height
SlotESlotTypeNoneNoEquipment slot (typically None for keys)
Amountbyte1NoDefault stack size
ExchangeablebooltrueNoWhether the item can be traded
Exchange_With_Target_ItempresenceabsentNoPresence enables exchange mode

Presence-Only Keys

The following key uses a presence-check pattern (not a boolean value):

Exchange_With_Target_Item
  • Key PRESENT in .dat → exchangeWithTargetItem = true
  • Key ABSENT from .dat → exchangeWithTargetItem = false
  • The VALUE of the key (true/false) is IGNORED

Locked Object .dat Reference

Locked objects (level objects, NPC-spawned containers) reference keys:

KeyTypeDescription
Key_IDushortItem ID of the key that unlocks this object
Key_ReplacementGUIDItem GUID to grant when exchange mode is used

Integration Testing Checklist

When adding a new key asset, verify:

  1. [ ] Key spawns correctly in the world (loot table, NPC inventory).
  2. [ ] Key can be picked up and appears in inventory.
  3. [ ] Key can be dropped and picked up again.
  4. [ ] Locked object with matching Key_ID shows unlock prompt.
  5. [ ] Using key on locked object consumes the key.
  6. [ ] Locked object unlocks (door opens, container accessible).
  7. [ ] Exchange_With_Target_Item absent: locked object remains after unlock.
  8. [ ] Exchange_With_Target_Item present: locked object is destroyed/replaced.
  9. [ ] Key stack decrements correctly (not removed entirely if stack > 1).
  10. [ ] Quest conditions tracking key usage update correctly.
  11. [ ] Key cannot unlock objects with non-matching Key_ID.
  12. [ ] Key cannot be used if player has no matching key in inventory.
  13. [ ] Key behavior persists across server restart.
  14. [ ] Key works when in backpack (not just hotbar).
  15. [ ] Multiple keys of the same ID work identically.

Debugging Key Issues

Symptom: "Key doesn't work on the door"

Check in order:

  1. Verify ItemKeyAsset is the assigned asset type (not ItemAsset).
  2. Verify the key's .dat ID matches the door's Key_ID.
  3. Verify the player has the key in inventory (not storage, not dropped).
  4. Verify the door's lock state is isLocked = true.
  5. Verify no plugin is intercepting the key-use event and blocking it.
  6. Enable verbose logging on the lock interaction system.

Symptom: "Key unlocks door but doesn't exchange"

Check:

  1. Verify Exchange_With_Target_Item is present in the key's .dat.
  2. Verify the door's Key_Replacement GUID references a valid item.
  3. Verify the replacement item asset loads successfully.
  4. Check server logs for exchange rejection messages.

Symptom: "Key disappears but door stays locked"

This is a server-client desync issue:

  1. Client predicts key consumption and removes it from inventory.
  2. Server rejects the unlock (range, target validation, cheat detection).
  3. Server sends inventory correction — key reappears.

Enable network logging to identify the rejection reason.

Symptom: "Key works once but not again"

The locked object likely has hasBeenUnlocked = true and isn't resetting:

  1. Check if the object has a reset timer (some NPC doors re-lock after N seconds).
  2. Check if a plugin is toggling the lock state.
  3. Verify the server save data isn't persisting an incorrect unlock state.

Cross-Mod Key Compatibility

GUID-Based Key References

While the vanilla .dat system uses ushort IDs for key matching, modded content can use GUID-based references for cross-mod compatibility. A key from Mod A can unlock a door from Mod B if the door's lock configuration references the key by GUID rather than ID.

The GUID approach requires:

  1. The key asset's GUID is known at door configuration time.
  2. The door's lock system resolves GUIDs to IDs at runtime via Assets.find(EAssetType.ITEM, keyGuid).
  3. If the key mod isn't loaded, the resolution fails and the door remains permanently locked.

ID Collision Avoidance

When multiple mods define keys, ID collisions can cause unintended unlocking:

Mod A: Key ID 58000 opens Door A
Mod B: Key ID 58000 opens Door B
Loaded together: Key 58000 opens BOTH doors

Avoidance strategies:

  • Use unique ID ranges per mod (e.g., Mod A uses 58000-58999, Mod B uses 59000-59999).
  • Use GUID-based references instead of IDs.
  • Register ID ranges in a community namespace registry.
  • Validate ID uniqueness at mod load time.

Plugin Interop

Plugins that intercept key-use events must handle cross-mod scenarios:

  • The key asset may have a different type than ItemKeyAsset in a modded context.
  • The lock target may use a custom lock system rather than the vanilla Key_ID check.
  • The exchange behavior (consume key + target, grant replacement) may be handled differently by the target mod.

Summary Table: Key Asset Lifecycle

PhaseActionAsset Fields Involved
.dat authoringModder writes key .datID, Exchange_With_Target_Item
Asset loadPopulateAsset reads .datexchangeWithTargetItem
World placementLocked object configured with Key_IDObject's Key_ID
Player acquisitionPlayer picks up key from loot/NPCKey's ID
InteractionPlayer uses key on locked objectexchangeWithTargetItem determines mode
ConsumptionKey removed from inventoryItem amount decremented
UnlockLocked object state changesObject's isLocked → false
Exchange (if flag set)Target destroyed, replacement grantedObject's Key_Replacement
Quest updateQuest conditions evaluatedKey use event fired

Key System Limitations and Design Intent

No Key Durability

Keys have no durability or use-count field. Every key is single-use (consumed on successful unlock). A "skeleton key" with unlimited uses requires a plugin. The single-use design drives the key economy: players must continually acquire or craft keys, creating resource sinks and trade demand.

No Partial Unlock States

Locks are binary: locked or unlocked. There is no "partially unlocked" state where multiple keys are needed. A door requiring 3 keys must be implemented as 3 sequential locks, each requiring a different key — this is a level design pattern, not an asset feature.

No Time-Limited Keys

Keys don't expire. Once picked up, a key remains usable indefinitely. Time-limited access must be implemented at the lock level (the door re-locks after N seconds) or via plugin (key is removed after a timer).

No Conditional Key Usage

Keys work unconditionally on their matching lock. There is no "only works at night," "only works if player has X skill," or "only works if quest stage Y is active" condition. Conditional access is implemented by the lock object or quest system, not the key asset.

Cargo Data Export

ItemKeyAsset does not override BuildCargoData. The exchangeWithTargetItem flag is not exported to Cargo tables. Only the base ItemAsset fields are exported for key items.