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
└── ItemKeyAssetNo 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 true→exchangeWithTargetItem = trueExchange_With_Target_Item false→exchangeWithTargetItem = 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:
- The key item is removed from the player's inventory.
- The locked object (door, container) is unlocked and destroyed/removed.
- 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:
- The key is consumed (removed from inventory).
- The locked object is unlocked.
- 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):
- Player targets a lockable object: Raycast or proximity check identifies a door or container.
- 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.
- Player presses use: The game searches the player's inventory for any
ItemKeyAssetitem whose ID matches the lock's required key ID. - Match found:
- If
exchangeWithTargetItemis true: the key is consumed (stack decremented), the target is removed, and the replacement is granted. - If
exchangeWithTargetItemis false: the key is consumed, the target is unlocked but not removed.
- If
- No match found: Nothing happens. The key is not consumed. The lock remains locked.
Inventory Search
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
- NPC gives quest: "Retrieve the key from the bandit camp."
- The bandit camp contains a loot spawn with the key item.
- Player picks up the key → quest condition updates ("Key acquired").
- NPC gives follow-up: "Use the key on the locked warehouse door."
- Player uses key on the warehouse door → quest condition updates ("Door unlocked").
- If
exchangeWithTargetItemis 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 NoneA key with exchange behavior:
ID 58001
Rarity Rare
Size_X 1
Size_Y 1
Slot None
Exchange_With_Target_ItemNote 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_ItemThen configure the locked object in the level (or via NPC quest data):
Key_ID 58050Creating 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 NoneOmit 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
Exchange flag always on with key present: Remember that ANY presence of
Exchange_With_Target_Itemin the .dat sets the flag to true, includingfalse. If you want the flag off, omit the key entirely.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.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.
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 falsein the baseItemAsset.dat) to prevent this.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.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:
- Interaction Detection: Raycast or overlap sphere detects the locked object in range.
- Lock State Check: The object's lock state is queried. If already unlocked, skip to interaction.
- Inventory Scan: The player's entire inventory is scanned for items matching the lock's
Key_ID. - Key Asset Validation: Each candidate item is checked:
item.GetAsset() is ItemKeyAsset. - Consumption Mode Selection: The first valid key's
exchangeWithTargetItemflag determines the consumption path. - Execution: Either exchange mode (both consumed, replacement granted) or unlock mode (key consumed, target persists).
- 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_Itemflag in the.datafter 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
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
ID | ushort | — | Yes | Unique item ID in the global namespace |
ItemName | string | — | Yes | Display name in inventory |
ItemDescription | string | "" | No | Tooltip description text |
Rarity | EItemRarity | Common | No | Item rarity tier (color) |
Size_X | byte | 1 | No | Inventory slot width |
Size_Y | byte | 1 | No | Inventory slot height |
Slot | ESlotType | None | No | Equipment slot (typically None for keys) |
Amount | byte | 1 | No | Default stack size |
Exchangeable | bool | true | No | Whether the item can be traded |
Exchange_With_Target_Item | presence | absent | No | Presence 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:
| Key | Type | Description |
|---|---|---|
Key_ID | ushort | Item ID of the key that unlocks this object |
Key_Replacement | GUID | Item GUID to grant when exchange mode is used |
Integration Testing Checklist
When adding a new key asset, verify:
- [ ] Key spawns correctly in the world (loot table, NPC inventory).
- [ ] Key can be picked up and appears in inventory.
- [ ] Key can be dropped and picked up again.
- [ ] Locked object with matching
Key_IDshows unlock prompt. - [ ] Using key on locked object consumes the key.
- [ ] Locked object unlocks (door opens, container accessible).
- [ ]
Exchange_With_Target_Itemabsent: locked object remains after unlock. - [ ]
Exchange_With_Target_Itempresent: locked object is destroyed/replaced. - [ ] Key stack decrements correctly (not removed entirely if stack > 1).
- [ ] Quest conditions tracking key usage update correctly.
- [ ] Key cannot unlock objects with non-matching
Key_ID. - [ ] Key cannot be used if player has no matching key in inventory.
- [ ] Key behavior persists across server restart.
- [ ] Key works when in backpack (not just hotbar).
- [ ] Multiple keys of the same ID work identically.
Debugging Key Issues
Symptom: "Key doesn't work on the door"
Check in order:
- Verify
ItemKeyAssetis the assigned asset type (notItemAsset). - Verify the key's
.datID matches the door'sKey_ID. - Verify the player has the key in inventory (not storage, not dropped).
- Verify the door's lock state is
isLocked = true. - Verify no plugin is intercepting the key-use event and blocking it.
- Enable verbose logging on the lock interaction system.
Symptom: "Key unlocks door but doesn't exchange"
Check:
- Verify
Exchange_With_Target_Itemis present in the key's.dat. - Verify the door's
Key_ReplacementGUID references a valid item. - Verify the replacement item asset loads successfully.
- Check server logs for exchange rejection messages.
Symptom: "Key disappears but door stays locked"
This is a server-client desync issue:
- Client predicts key consumption and removes it from inventory.
- Server rejects the unlock (range, target validation, cheat detection).
- 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:
- Check if the object has a reset timer (some NPC doors re-lock after N seconds).
- Check if a plugin is toggling the lock state.
- 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:
- The key asset's GUID is known at door configuration time.
- The door's lock system resolves GUIDs to IDs at runtime via
Assets.find(EAssetType.ITEM, keyGuid). - 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 doorsAvoidance 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
ItemKeyAssetin a modded context. - The lock target may use a custom lock system rather than the vanilla
Key_IDcheck. - The exchange behavior (consume key + target, grant replacement) may be handled differently by the target mod.
Summary Table: Key Asset Lifecycle
| Phase | Action | Asset Fields Involved |
|---|---|---|
| .dat authoring | Modder writes key .dat | ID, Exchange_With_Target_Item |
| Asset load | PopulateAsset reads .dat | exchangeWithTargetItem |
| World placement | Locked object configured with Key_ID | Object's Key_ID |
| Player acquisition | Player picks up key from loot/NPC | Key's ID |
| Interaction | Player uses key on locked object | exchangeWithTargetItem determines mode |
| Consumption | Key removed from inventory | Item amount decremented |
| Unlock | Locked object state changes | Object's isLocked → false |
| Exchange (if flag set) | Target destroyed, replacement granted | Object's Key_Replacement |
| Quest update | Quest conditions evaluated | Key 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.
