diff --git a/assets/backgrounds/background1.png b/assets/backgrounds/background1.png new file mode 100644 index 0000000..b6dad6c Binary files /dev/null and b/assets/backgrounds/background1.png differ diff --git a/assets/backgrounds/hq1.png b/assets/backgrounds/hq1.png new file mode 100644 index 0000000..c599ca8 Binary files /dev/null and b/assets/backgrounds/hq1.png differ diff --git a/assets/backgrounds/hq2.png b/assets/backgrounds/hq2.png new file mode 100644 index 0000000..8e4a6a6 Binary files /dev/null and b/assets/backgrounds/hq2.png differ diff --git a/assets/backgrounds/hq3.png b/assets/backgrounds/hq3.png new file mode 100644 index 0000000..e78c24a Binary files /dev/null and b/assets/backgrounds/hq3.png differ diff --git a/js/minigames/container/container-minigame.js b/js/minigames/container/container-minigame.js index bb8fd54..2121c12 100644 --- a/js/minigames/container/container-minigame.js +++ b/js/minigames/container/container-minigame.js @@ -9,8 +9,14 @@ export class ContainerMinigame extends MinigameScene { this.contents = params.contents || []; this.isTakeable = params.isTakeable || false; - // Auto-detect desktop mode for PC/tablet containers - this.desktopMode = params.desktopMode || this.shouldUseDesktopMode(); + // NPC mode support + this.mode = params.mode || 'container'; // 'container', 'pc', or 'npc' + this.npcId = params.npcId || null; + this.npcDisplayName = params.npcDisplayName || null; + this.npcAvatar = params.npcAvatar || null; + + // Auto-detect desktop mode for PC/tablet containers (not used in NPC mode) + this.desktopMode = (this.mode !== 'npc') && (params.desktopMode || this.shouldUseDesktopMode()); } shouldUseDesktopMode() { @@ -58,7 +64,9 @@ export class ContainerMinigame extends MinigameScene { } createContainerUI() { - if (this.desktopMode) { + if (this.mode === 'npc') { + this.createNPCUI(); + } else if (this.desktopMode) { this.createDesktopUI(); } else { this.createStandardUI(); @@ -71,6 +79,27 @@ export class ContainerMinigame extends MinigameScene { this.setupEventListeners(); } + createNPCUI() { + // NPC mode - show NPC avatar and offer items + let avatarHtml = ''; + if (this.npcAvatar) { + avatarHtml = `${this.npcDisplayName}`; + } + + this.gameContainer.innerHTML = ` +
+ ${avatarHtml} +

${this.npcDisplayName || 'NPC'} offers you items

+
+

Available Items

+
+ +
+
+
+ `; + } + createStandardUI() { this.gameContainer.innerHTML = `
@@ -455,6 +484,24 @@ export class ContainerMinigame extends MinigameScene { const itemIndex = this.contents.findIndex(content => content === item); if (itemIndex !== -1) { this.contents.splice(itemIndex, 1); + + // If in NPC mode, also remove from NPC's itemsHeld + if (this.mode === 'npc' && this.npcId && window.npcManager) { + const npc = window.npcManager.getNPC(this.npcId); + if (npc && npc.itemsHeld) { + const npcItemIndex = npc.itemsHeld.findIndex(i => i === item); + if (npcItemIndex !== -1) { + npc.itemsHeld.splice(npcItemIndex, 1); + + // Emit event to update Ink variables + if (window.eventDispatcher) { + window.eventDispatcher.emit('npc_items_changed', { + npcId: this.npcId + }); + } + } + } + } } // Show success message diff --git a/js/minigames/helpers/chat-helpers.js b/js/minigames/helpers/chat-helpers.js index 04a813c..636592e 100644 --- a/js/minigames/helpers/chat-helpers.js +++ b/js/minigames/helpers/chat-helpers.js @@ -79,27 +79,56 @@ export function processGameActionTags(tags, ui) { case 'give_item': if (param) { - // Parse item properties from param (could be "keycard" or "keycard|CEO Keycard") - const [itemType, itemName] = param.split('|').map(s => s.trim()); - const giveResult = window.NPCGameBridge.giveItem(itemType, { - name: itemName || itemType - }); + const [itemType] = param.split('|').map(s => s.trim()); + const npcId = window.currentConversationNPCId; + + if (!npcId) { + result.message = '⚠️ No NPC context available'; + console.warn(result.message); + break; + } + + const giveResult = window.NPCGameBridge.giveItem(npcId, itemType); if (giveResult.success) { result.success = true; - result.message = `πŸ“¦ Received: ${itemName || itemType}`; + result.message = `πŸ“¦ Received: ${giveResult.item.name}`; if (ui) ui.showNotification(result.message, 'success'); console.log('βœ… Item given successfully:', giveResult); } else { - result.message = `⚠️ Failed to give item: ${itemType}`; + result.message = `⚠️ ${giveResult.error}`; if (ui) ui.showNotification(result.message, 'warning'); console.warn('⚠️ Item give failed:', giveResult); } } else { - result.message = '⚠️ give_item tag missing item parameter'; + result.message = '⚠️ give_item requires item type parameter'; console.warn(result.message); } break; + case 'give_npc_inventory_items': + const npcId = window.currentConversationNPCId; + + if (!npcId) { + result.message = '⚠️ No NPC context available'; + console.warn(result.message); + break; + } + + // Parse filter types (comma-separated) + const filterTypes = param ? param.split(',').map(s => s.trim()).filter(s => s) : null; + + const showResult = window.NPCGameBridge.showNPCInventory(npcId, filterTypes); + if (showResult.success) { + result.success = true; + result.message = `πŸ“¦ Opening inventory with ${showResult.itemCount} items`; + console.log('βœ… NPC inventory opened:', showResult); + } else { + result.message = `⚠️ ${showResult.error}`; + if (ui) ui.showNotification(result.message, 'warning'); + console.warn('⚠️ Show inventory failed:', showResult); + } + break; + case 'set_objective': if (param) { window.NPCGameBridge.setObjective(param); diff --git a/js/minigames/person-chat/person-chat-conversation.js b/js/minigames/person-chat/person-chat-conversation.js index 2a57323..b9c38d7 100644 --- a/js/minigames/person-chat/person-chat-conversation.js +++ b/js/minigames/person-chat/person-chat-conversation.js @@ -98,6 +98,53 @@ export default class PersonChatConversation { // Set variables in the Ink engine using setVariable instead of bindVariable this.inkEngine.setVariable('last_interaction_type', 'person'); this.inkEngine.setVariable('player_name', 'Player'); + + // Sync NPC items to Ink variables + this.syncItemsToInk(); + + // Set up event listener for item changes + if (window.eventDispatcher) { + this._itemsChangedListener = (data) => { + if (data.npcId === this.npc.id) { + this.syncItemsToInk(); + } + }; + window.eventDispatcher.on('npc_items_changed', this._itemsChangedListener); + } + } + + /** + * Sync NPC's held items to Ink variables + * Sets has_ based on itemsHeld array + */ + syncItemsToInk() { + if (!this.inkEngine || !this.inkEngine.story) return; + + const npc = this.npc; + if (!npc || !npc.itemsHeld) return; + + const varState = this.inkEngine.story.variablesState; + if (!varState._defaultGlobalVariables) return; + + // Count items by type + const itemCounts = {}; + npc.itemsHeld.forEach(item => { + itemCounts[item.type] = (itemCounts[item.type] || 0) + 1; + }); + + // Set has_ variables based on inventory + Object.keys(itemCounts).forEach(type => { + const varName = `has_${type}`; + if (varState._defaultGlobalVariables && varState._defaultGlobalVariables.has(varName)) { + const hasItem = itemCounts[type] > 0; + try { + this.inkEngine.setVariable(varName, hasItem); + console.log(`βœ… Synced ${varName} = ${hasItem} for NPC ${npc.id}`); + } catch (err) { + console.warn(`⚠️ Could not sync ${varName}:`, err.message); + } + } + }); } /** @@ -337,6 +384,11 @@ export default class PersonChatConversation { */ end() { try { + // Remove event listener + if (window.eventDispatcher && this._itemsChangedListener) { + window.eventDispatcher.off('npc_items_changed', this._itemsChangedListener); + } + if (this.inkEngine) { // Don't destroy - keep for history/dual identity this.inkEngine = null; diff --git a/js/minigames/person-chat/person-chat-minigame.js b/js/minigames/person-chat/person-chat-minigame.js index 210e532..a503253 100644 --- a/js/minigames/person-chat/person-chat-minigame.js +++ b/js/minigames/person-chat/person-chat-minigame.js @@ -281,6 +281,9 @@ export class PersonChatMinigame extends MinigameScene { console.log('🎭 PersonChatMinigame started'); + // Track NPC context for tag processing + window.currentConversationNPCId = this.npcId; + // Start conversation with Ink this.startConversation(); } @@ -856,6 +859,9 @@ export class PersonChatMinigame extends MinigameScene { npcConversationStateManager.saveNPCState(this.npcId, this.inkEngine.story); } + // Clear NPC context + window.currentConversationNPCId = null; + // Call parent cleanup super.cleanup(); } diff --git a/js/minigames/person-chat/person-chat-portraits.js b/js/minigames/person-chat/person-chat-portraits.js index 5a1858b..fadb011 100644 --- a/js/minigames/person-chat/person-chat-portraits.js +++ b/js/minigames/person-chat/person-chat-portraits.js @@ -417,7 +417,7 @@ export default class PersonChatPortraits { if (!this.canvas || !this.ctx) return; try { - console.log(`🎨 render() called - useSpriteTalk: ${this.useSpriteTalk}, spriteSheet: ${this.spriteSheet}`); + // console.log(`🎨 render() called - useSpriteTalk: ${this.useSpriteTalk}, spriteSheet: ${this.spriteSheet}`); // Clear canvas this.ctx.fillStyle = '#000'; @@ -425,7 +425,7 @@ export default class PersonChatPortraits { // If using spriteTalk image, render that instead if (this.useSpriteTalk) { - console.log(`🎨 Rendering spriteTalk image path`); + // console.log(`🎨 Rendering spriteTalk image path`); // Calculate sprite scale for spriteTalk const spriteTalkScale = this.calculateSpriteTalkScale(); // Draw background with sprite scale if loaded diff --git a/js/minigames/phone-chat/phone-chat-conversation.js b/js/minigames/phone-chat/phone-chat-conversation.js index 6894307..1f8176d 100644 --- a/js/minigames/phone-chat/phone-chat-conversation.js +++ b/js/minigames/phone-chat/phone-chat-conversation.js @@ -74,6 +74,20 @@ export default class PhoneChatConversation { this.storyLoaded = true; this.storyEnded = false; + + // Sync NPC items to Ink variables + this.syncItemsToInk(); + + // Set up event listener for item changes + if (window.eventDispatcher) { + this._itemsChangedListener = (data) => { + if (data.npcId === this.npcId) { + this.syncItemsToInk(); + } + }; + window.eventDispatcher.on('npc_items_changed', this._itemsChangedListener); + } + console.log(`βœ… Story loaded successfully for ${this.npcId}`); return true; @@ -117,6 +131,40 @@ export default class PhoneChatConversation { } } + /** + * Sync NPC's held items to Ink variables + * Sets has_ based on itemsHeld array + */ + syncItemsToInk() { + if (!this.engine || !this.engine.story) return; + + const npc = this.npcManager.getNPC(this.npcId); + if (!npc || !npc.itemsHeld) return; + + const varState = this.engine.story.variablesState; + if (!varState._defaultGlobalVariables) return; + + // Count items by type + const itemCounts = {}; + npc.itemsHeld.forEach(item => { + itemCounts[item.type] = (itemCounts[item.type] || 0) + 1; + }); + + // Set has_ variables based on inventory + Object.keys(itemCounts).forEach(type => { + const varName = `has_${type}`; + if (varState._defaultGlobalVariables && varState._defaultGlobalVariables.has(varName)) { + const hasItem = itemCounts[type] > 0; + try { + this.engine.setVariable(varName, hasItem); + console.log(`βœ… Synced ${varName} = ${hasItem} for NPC ${npc.id}`); + } catch (err) { + console.warn(`⚠️ Could not sync ${varName}:`, err.message); + } + } + }); + } + /** * Continue the story and get the next text/choices * @returns {Object} Story result { text, choices, tags, canContinue, hasEnded } @@ -331,6 +379,16 @@ export default class PhoneChatConversation { } } + /** + * Clean up resources (event listeners, etc.) + */ + cleanup() { + // Remove event listener + if (window.eventDispatcher && this._itemsChangedListener) { + window.eventDispatcher.off('npc_items_changed', this._itemsChangedListener); + } + } + /** * Get conversation metadata (variables, state) * @returns {Object} Metadata about the conversation diff --git a/js/minigames/phone-chat/phone-chat-minigame.js b/js/minigames/phone-chat/phone-chat-minigame.js index 9b4f3bc..c951623 100644 --- a/js/minigames/phone-chat/phone-chat-minigame.js +++ b/js/minigames/phone-chat/phone-chat-minigame.js @@ -207,6 +207,8 @@ export class PhoneChatMinigame extends MinigameScene { // If NPC ID provided, open that conversation directly if (this.currentNPCId) { + // Track NPC context for tag processing + window.currentConversationNPCId = this.currentNPCId; this.openConversation(this.currentNPCId); } else { // Show contact list for this phone @@ -299,6 +301,9 @@ export class PhoneChatMinigame extends MinigameScene { // Update current NPC this.currentNPCId = npcId; + // Track NPC context for tag processing + window.currentConversationNPCId = npcId; + // Initialize conversation modules this.history = new PhoneChatHistory(npcId, this.npcManager); this.conversation = new PhoneChatConversation(npcId, this.npcManager, this.inkEngine); @@ -735,6 +740,9 @@ export class PhoneChatMinigame extends MinigameScene { this.conversation = null; this.history = null; + // Clear NPC context + window.currentConversationNPCId = null; + // Call parent cleanup super.cleanup(); } diff --git a/js/systems/ink/ink-engine.js b/js/systems/ink/ink-engine.js index fd48d51..f2372a3 100644 --- a/js/systems/ink/ink-engine.js +++ b/js/systems/ink/ink-engine.js @@ -116,7 +116,13 @@ export default class InkEngine { setVariable(name, value) { if (!this.story) throw new Error('Story not loaded'); - // inkjs VariableState.SetGlobal expects a RuntimeObject; it's forgiving for primitives - this.story.variablesState.SetGlobal(name, value); + + // Let Ink handle the value type conversion through the indexer + // which properly wraps values in Runtime.Value objects + try { + this.story.variablesState[name] = value; + } catch (err) { + console.warn(`⚠️ Failed to set variable ${name}:`, err.message); + } } } diff --git a/js/systems/npc-conversation-state.js b/js/systems/npc-conversation-state.js index 5c30b2d..d0476e0 100644 --- a/js/systems/npc-conversation-state.js +++ b/js/systems/npc-conversation-state.js @@ -35,14 +35,30 @@ class NPCConversationStateManager { // Always save the variables (favour, items earned, flags, etc.) // These persist across conversations even when story ends if (story.variablesState) { - state.variables = { ...story.variablesState }; + // Filter out has_* variables (derived from itemsHeld, will be re-synced on load) + const filteredVariables = {}; + for (const [key, value] of Object.entries(story.variablesState)) { + // Skip dynamically-synced item inventory variables + if (!key.startsWith('has_lockpick') && + !key.startsWith('has_workstation') && + !key.startsWith('has_phone') && + !key.startsWith('has_keycard')) { + filteredVariables[key] = value; + } + } + state.variables = filteredVariables; console.log(`πŸ’Ύ Saved variables for ${npcId}:`, state.variables); } // Only save full story state if story is still active OR if explicitly forced if (!story.state.hasEnded || forceFullState) { - state.storyState = story.state.ToJson(); - console.log(`πŸ’Ύ Saved full story state for ${npcId} (active story)`); + try { + state.storyState = story.state.ToJson(); + console.log(`πŸ’Ύ Saved full story state for ${npcId} (active story)`); + } catch (serializeError) { + // If serialization fails (due to dynamic variables), just save variables + console.warn(`⚠️ Could not serialize full story state for ${npcId}, saving variables only:`, serializeError.message); + } } else { console.log(`πŸ’Ύ Saved variables only for ${npcId} (story ended - will restart fresh)`); } diff --git a/js/systems/npc-game-bridge.js b/js/systems/npc-game-bridge.js index 8a0e9c6..4a87584 100644 --- a/js/systems/npc-game-bridge.js +++ b/js/systems/npc-game-bridge.js @@ -100,99 +100,142 @@ export class NPCGameBridge { } /** - * Give an item to the player - * @param {string} itemType - Type of item to give - * @param {Object} properties - Optional item properties - * @returns {Object} Result object with success status + * Give an item from NPC's inventory to the player immediately + * @param {string} npcId - NPC identifier + * @param {string} itemType - Type of item to give (optional - gives first if null) + * @returns {Object} Result with success status */ - giveItem(itemType, properties = {}) { - if (!itemType) { - const result = { success: false, error: 'No itemType provided' }; - this._logAction('giveItem', { itemType, properties }, result); + giveItem(npcId, itemType = null) { + if (!npcId) { + const result = { success: false, error: 'No npcId provided' }; + this._logAction('giveItem', { npcId, itemType }, result); return result; } + // Get NPC from manager + const npc = window.npcManager?.getNPC(npcId); + if (!npc) { + const result = { success: false, error: `NPC ${npcId} not found` }; + this._logAction('giveItem', { npcId, itemType }, result); + return result; + } + + if (!npc.itemsHeld || npc.itemsHeld.length === 0) { + const result = { success: false, error: `NPC ${npcId} has no items to give` }; + this._logAction('giveItem', { npcId, itemType }, result); + return result; + } + + // Find item in NPC's inventory + let itemIndex = -1; + if (itemType) { + // Find first item matching type + itemIndex = npc.itemsHeld.findIndex(item => item.type === itemType); + if (itemIndex === -1) { + const result = { success: false, error: `NPC ${npcId} doesn't have ${itemType}` }; + this._logAction('giveItem', { npcId, itemType }, result); + return result; + } + } else { + // Give first item + itemIndex = 0; + } + + const item = npc.itemsHeld[itemIndex]; + if (!window.addToInventory) { const result = { success: false, error: 'Inventory system not available' }; - this._logAction('giveItem', { itemType, properties }, result); + this._logAction('giveItem', { npcId, itemType }, result); return result; } try { - // Default names for common items - const defaultNames = { - 'lockpick': 'Lock Pick Kit', - 'bluetooth_scanner': 'Bluetooth Scanner', - 'fingerprint_kit': 'Fingerprint Kit', - 'pin-cracker': 'PIN Cracker', - 'workstation': 'Crypto Analysis Station', - 'keycard': 'Access Keycard', - 'key': 'Key' - }; - - // Default observations for common items - const defaultObservations = { - 'lockpick': 'A professional lock picking kit with various picks and tension wrenches', - 'bluetooth_scanner': 'A device for scanning and connecting to nearby Bluetooth devices', - 'fingerprint_kit': 'A forensic kit for collecting and analyzing fingerprints', - 'pin-cracker': 'A tool for cracking numeric PIN codes', - 'workstation': 'A powerful workstation for cryptographic analysis', - 'keycard': 'An access keycard for secured areas', - 'key': 'A key that opens a specific lock' - }; - - // Create a basic item structure - const itemName = (properties.name && properties.name !== itemType) - ? properties.name - : (defaultNames[itemType] || itemType); - const itemObservations = properties.observations || defaultObservations[itemType] || `A ${itemName} given by an NPC`; - - const item = { - type: itemType, - name: itemName, - takeable: true, - observations: itemObservations, - scenarioData: { - ...properties, // Spread properties first - type: itemType, // Then override with correct values - name: itemName, - observations: itemObservations, - takeable: true - } + // Create sprite using container pattern + const tempSprite = { + scenarioData: item, + name: item.type, + objectId: `npc_gift_${npcId}_${item.type}_${Date.now()}`, + texture: { key: item.type } }; - // Create a pseudo-sprite for the inventory system - const sprite = { - name: item.name, - scenarioData: item.scenarioData, - texture: { key: itemType }, - objectId: `npc_gift_${itemType}_${Date.now()}` - }; + // Add to player inventory + window.addToInventory(tempSprite); - console.log('🎁 NPCGameBridge: Creating item sprite:', { - itemType, - name: sprite.name, - scenarioDataName: sprite.scenarioData.name, - scenarioDataType: sprite.scenarioData.type, - fullScenarioData: sprite.scenarioData - }); + // Remove from NPC's inventory + npc.itemsHeld.splice(itemIndex, 1); - window.addToInventory(sprite); - - // Emit event + // Emit event to update Ink variables if (window.eventDispatcher) { - window.eventDispatcher.emit('item_given_by_npc', { - itemType, - source: 'npc' - }); + window.eventDispatcher.emit('npc_items_changed', { npcId }); } - const result = { success: true, itemType, item }; - this._logAction('giveItem', { itemType, properties }, result); + const result = { success: true, item, npcId }; + this._logAction('giveItem', { npcId, itemType }, result); return result; } catch (error) { const result = { success: false, error: error.message }; - this._logAction('giveItem', { itemType, properties }, result); + this._logAction('giveItem', { npcId, itemType }, result); + return result; + } + } + + /** + * Show NPC's inventory items in container UI + * @param {string} npcId - NPC identifier + * @param {string[]} filterTypes - Array of item types to show (null = all) + * @returns {Object} Result with success status + */ + showNPCInventory(npcId, filterTypes = null) { + if (!npcId) { + const result = { success: false, error: 'No npcId provided' }; + this._logAction('showNPCInventory', { npcId, filterTypes }, result); + return result; + } + + const npc = window.npcManager?.getNPC(npcId); + if (!npc) { + const result = { success: false, error: `NPC ${npcId} not found` }; + this._logAction('showNPCInventory', { npcId, filterTypes }, result); + return result; + } + + if (!npc.itemsHeld || npc.itemsHeld.length === 0) { + const result = { success: false, error: `NPC ${npcId} has no items` }; + this._logAction('showNPCInventory', { npcId, filterTypes }, result); + return result; + } + + // Filter items if types specified + let itemsToShow = npc.itemsHeld; + if (filterTypes && filterTypes.length > 0) { + itemsToShow = npc.itemsHeld.filter(item => + filterTypes.includes(item.type) + ); + } + + if (itemsToShow.length === 0) { + const result = { success: false, error: 'No matching items to show' }; + this._logAction('showNPCInventory', { npcId, filterTypes }, result); + return result; + } + + // Open container minigame in NPC mode + if (window.startContainerMinigame) { + window.startContainerMinigame({ + name: `${npc.displayName}'s Items`, + contents: itemsToShow, + mode: 'npc', + npcId: npcId, + npcDisplayName: npc.displayName, + npcAvatar: npc.avatar + }); + + const result = { success: true, npcId, itemCount: itemsToShow.length }; + this._logAction('showNPCInventory', { npcId, filterTypes }, result); + return result; + } else { + const result = { success: false, error: 'Container minigame not available' }; + this._logAction('showNPCInventory', { npcId, filterTypes }, result); return result; } } @@ -414,7 +457,8 @@ if (typeof window !== 'undefined') { // Register convenience methods globally for Ink window.npcUnlockDoor = (roomId) => bridge.unlockDoor(roomId); - window.npcGiveItem = (itemType, properties) => bridge.giveItem(itemType, properties); + window.npcGiveItem = (npcId, itemType) => bridge.giveItem(npcId, itemType); + window.npcShowInventory = (npcId, filterTypes) => bridge.showNPCInventory(npcId, filterTypes); window.npcSetObjective = (text) => bridge.setObjective(text); window.npcRevealSecret = (secretId, data) => bridge.revealSecret(secretId, data); window.npcAddNote = (title, content) => bridge.addNote(title, content); diff --git a/js/systems/npc-manager.js b/js/systems/npc-manager.js index 3a7ba16..84181d7 100644 --- a/js/systems/npc-manager.js +++ b/js/systems/npc-manager.js @@ -65,7 +65,8 @@ export default class NPCManager { metadata: {}, eventMappings: {}, phoneId: 'player_phone', // Default to player's phone - npcType: 'phone' // Default to phone-based NPC + npcType: 'phone', // Default to phone-based NPC + itemsHeld: [] // Initialize empty inventory for NPC item giving }, realOpts); this.npcs.set(realId, entry); diff --git a/planning_notes/rails-engine-migration/progress/CLIENT_SERVER_SEPARATION_PLAN.md b/planning_notes/rails-engine-migration/progress/CLIENT_SERVER_SEPARATION_PLAN.md index a78f100..54a834b 100644 --- a/planning_notes/rails-engine-migration/progress/CLIENT_SERVER_SEPARATION_PLAN.md +++ b/planning_notes/rails-engine-migration/progress/CLIENT_SERVER_SEPARATION_PLAN.md @@ -1020,3 +1020,5 @@ Room Loading β†’ Container System β†’ Unlock System β†’ Inventory System **Confidence:** High - architecture already supports this model (see ARCHITECTURE_COMPARISON.md) + + diff --git a/planning_notes/rails-engine-migration/progress/NPC_MIGRATION_OPTIONS.md b/planning_notes/rails-engine-migration/progress/NPC_MIGRATION_OPTIONS.md index 6699c61..0149a6a 100644 --- a/planning_notes/rails-engine-migration/progress/NPC_MIGRATION_OPTIONS.md +++ b/planning_notes/rails-engine-migration/progress/NPC_MIGRATION_OPTIONS.md @@ -839,3 +839,5 @@ end This approach balances security, UX, and development effort. + + diff --git a/planning_notes/rails-engine-migration/progress/RAILS_ENGINE_MIGRATION_PLAN.md b/planning_notes/rails-engine-migration/progress/RAILS_ENGINE_MIGRATION_PLAN.md index e7c6180..42e00e4 100644 --- a/planning_notes/rails-engine-migration/progress/RAILS_ENGINE_MIGRATION_PLAN.md +++ b/planning_notes/rails-engine-migration/progress/RAILS_ENGINE_MIGRATION_PLAN.md @@ -1971,3 +1971,5 @@ This comprehensive plan provides: The architecture supports both standalone operation and mounting in host applications, making it flexible and maintainable. + + diff --git a/planning_notes/rails-engine-migration/progress/README.md b/planning_notes/rails-engine-migration/progress/README.md index 13f876c..e1c9f2f 100644 --- a/planning_notes/rails-engine-migration/progress/README.md +++ b/planning_notes/rails-engine-migration/progress/README.md @@ -621,3 +621,5 @@ For questions about this migration plan, contact the development team or file an **Happy migrating! πŸš€** + + diff --git a/scenarios/ink/helper-npc.ink b/scenarios/ink/helper-npc.ink index e875151..37c3416 100644 --- a/scenarios/ink/helper-npc.ink +++ b/scenarios/ink/helper-npc.ink @@ -13,6 +13,12 @@ VAR asked_about_self = false VAR asked_about_ceo = false VAR asked_for_items = false +// NPC item inventory variables (synced from itemsHeld array) +VAR has_lockpick = false +VAR has_workstation = false +VAR has_phone = false +VAR has_keycard = false + === start === # speaker:npc Hey there! I'm here to help you out if you need it. πŸ‘‹ @@ -40,16 +46,11 @@ What can I do for you? } // Items - changes based on state -{asked_about_self and not has_given_lockpick: +{asked_about_self and (has_lockpick or has_workstation or has_phone or has_keycard): + [Do you have any items for me?] -> give_items } -{has_given_lockpick: - + [Got any other items for me?] - -> other_items -} - // Feedback option appears after using lockpick {saw_lockpick_used: + [Thanks for the lockpick! It worked great.] @@ -80,7 +81,7 @@ What would you like to do? {has_unlocked_ceo: I already unlocked the CEO's office for you! Just head on in. -> hub -- else: +|- else: The CEO's office? That's a tough one... {trust_level >= 1: Alright, I trust you enough. Let me unlock that door for you. @@ -105,38 +106,26 @@ Let me know! === give_items === # speaker:npc -{has_given_lockpick: - I already gave you a lockpick set. Check your inventory - it should be there! +{not has_lockpick and not has_workstation and not has_phone and not has_keycard: + Sorry, I don't have any items to give you right now. -> hub -- else: - Let me see what I have... +|- else: {trust_level >= 2: - Here's a lockpick set. Use it to open locked doors and containers! πŸ”“ - ~ has_given_lockpick = true + Let me show you what I have for you! + #give_npc_inventory_items ~ asked_for_items = true - #give_item:lockpick - ~ trust_level = trust_level + 1 - Good luck out there! -> hub - else: - I need to trust you more before I give you something like that. - Build up some trust first - ask me questions or help me out! + I have some items, but I need to trust you more first. + Build up some trust - ask me questions! -> hub } } === other_items === # speaker:npc -{trust_level >= 4: - I've got a keycard for restricted areas. Think you can use it responsibly? - #give_item:keycard - ~ trust_level = trust_level + 1 - Use it wisely! - -> hub -- else: - That's all I have right now. The lockpick set is your best tool for now. - -> hub -} +I think I gave you most of what I had. Check your inventory! +-> hub === lockpick_feedback === Great! I'm glad it helped you out. That's what I'm here for. @@ -150,8 +139,8 @@ What else do you need? {has_unlocked_ceo: The CEO's office has evidence you're looking for. Search the desk thoroughly. Also, check any computers for sensitive files. -- else: - {has_given_lockpick: +|- else: + {has_lockpick: Try using that lockpick set on locked doors and containers around the building. You never know what secrets people hide behind locked doors! - else: @@ -170,29 +159,29 @@ Good luck! // Triggered when player picks up the lockpick === on_lockpick_pickup === -{has_given_lockpick: +{has_lockpick: Great! You found the lockpick I gave you. Try it on a locked door or container! -- else: - Nice find! That lockpick set looks professional. Could be very useful. πŸ”“ +|- else: + Nice find! That lockpick set looks professional. Could be very useful. } -> hub // Triggered when player completes any lockpicking minigame === on_lockpick_success === ~ saw_lockpick_used = true -{has_given_lockpick: - Excellent! Glad I could help you get through that. 🎯 -- else: - Nice work getting through that lock! πŸ”“ +{has_lockpick: + Excellent! Glad I could help you get through that. +|- else: + Nice work getting through that lock! } -> hub // Triggered when player fails a lockpicking attempt === on_lockpick_failed === -{has_given_lockpick: - Don't give up! Lockpicking takes practice. Try adjusting the tension. πŸ”§ +{has_lockpick: + Don't give up! Lockpicking takes practice. Try adjusting the tension. Want me to help you with anything else? -- else: +|- else: Tough break. Lockpicking isn't easy without the right tools... I might be able to help with that if you ask. } @@ -202,18 +191,18 @@ Good luck! === on_door_unlocked === ~ saw_door_unlock = true {has_unlocked_ceo: - Another door open! You're making great progress. πŸšͺβœ“ -- else: + Another door open! You're making great progress. +|- else: Nice! You found a way through that door. Keep going! } -> hub // Triggered when player tries a locked door === on_door_attempt === -That door's locked tight. You'll need to find a way to unlock it. πŸ”’ +That door's locked tight. You'll need to find a way to unlock it. {trust_level >= 2: Want me to help you out? Just ask! -- else: +|- else: {trust_level >= 1: I might be able to help if you get to know me better first. } @@ -223,9 +212,9 @@ That door's locked tight. You'll need to find a way to unlock it. πŸ”’ // Triggered when player interacts with the CEO desk === on_ceo_desk_interact === {has_unlocked_ceo: - The CEO's desk - you made it! Nice work. πŸ“‹ + The CEO's desk - you made it! Nice work. That's where the important evidence is kept. -- else: +|- else: Trying to get into the CEO's office? I might be able to help with that... } -> hub @@ -233,20 +222,20 @@ That door's locked tight. You'll need to find a way to unlock it. πŸ”’ // Triggered when player picks up any item === on_item_found === {trust_level >= 1: - Good find! Every item could be important for your mission. πŸ“¦ + Good find! Every item could be important for your mission. } -> hub // Triggered when player enters any room (general progress check) === on_room_entered === {has_unlocked_ceo: - Keep searching for that evidence! πŸ” -- else: + Keep searching for that evidence! +|- else: {trust_level >= 1: - You're making progress through the building. 🚢 + You're making progress through the building. Let me know if you need help with anything. - else: - Exploring new areas... 🚢 + Exploring new areas... } } -> hub @@ -254,13 +243,13 @@ That door's locked tight. You'll need to find a way to unlock it. πŸ”’ // Triggered when player discovers a new room for the first time === on_room_discovered === {trust_level >= 2: - Great find! This new area might have what we need. πŸ—ΊοΈβœ¨ + Great find! This new area might have what we need. Search it thoroughly! -- else: +|- else: {trust_level >= 1: - Interesting! You've found a new area. Be careful exploring. πŸ—ΊοΈ + Interesting! You've found a new area. Be careful exploring. - else: - A new room... wonder what's inside. πŸšͺ + A new room... wonder what's inside. } } -> hub @@ -268,12 +257,11 @@ That door's locked tight. You'll need to find a way to unlock it. πŸ”’ // Triggered when player enters the CEO office === on_ceo_office_entered === {has_unlocked_ceo: - You're in! Remember, you're looking for evidence of the data breach. πŸ•΅οΈ + You're in! Remember, you're looking for evidence of the data breach. Check the desk, computer, and any drawers. -- else: - Whoa, you got into the CEO's office! That's impressive! πŸŽ‰ +|- else: + Whoa, you got into the CEO's office! That's impressive! ~ trust_level = trust_level + 1 Maybe I underestimated you. Impressive work! } -> hub - diff --git a/scenarios/ink/helper-npc.json b/scenarios/ink/helper-npc.json index 0d7f694..7a940bc 100644 --- a/scenarios/ink/helper-npc.json +++ b/scenarios/ink/helper-npc.json @@ -1 +1 @@ -{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["#","^speaker:npc","/#","^Hey there! I'm here to help you out if you need it. πŸ‘‹","\n","^What can I do for you?","\n","ev",true,"/ev",{"VAR=":"has_greeted","re":true},{"->":"hub"},null],"hub":[["ev",{"VAR?":"asked_about_self"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Who are you?","/str","/ev",{"*":".^.c-0","flg":20},{"->":"hub.0.5"},{"c-0":["\n","ev",true,"/ev",{"VAR=":"asked_about_self","re":true},{"->":"who_are_you"},{"#f":5}]}]}],"nop","\n","ev",{"VAR?":"asked_about_self"},{"VAR?":"has_unlocked_ceo"},"!","&&","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Can you help me get into the CEO's office?","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.14"},{"c-0":["\n",{"->":"help_ceo_office"},null]}]}],"nop","\n","ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Any other doors you need help with?","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.20"},{"c-0":["\n",{"->":"other_doors"},null]}]}],"nop","\n","ev",{"VAR?":"asked_about_self"},{"VAR?":"has_given_lockpick"},"!","&&","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Do you have any items for me?","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.29"},{"c-0":["\n",{"->":"give_items"},null]}]}],"nop","\n","ev",{"VAR?":"has_given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Got any other items for me?","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.35"},{"c-0":["\n",{"->":"other_items"},null]}]}],"nop","\n","ev",{"VAR?":"saw_lockpick_used"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Thanks for the lockpick! It worked great.","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.41"},{"c-0":["\n",{"->":"lockpick_feedback"},null]}]}],"nop","\n","ev",{"VAR?":"trust_level"},3,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^What hints do you have for me?","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.49"},{"c-0":["\n",{"->":"give_hints"},null]}]}],"nop","\n","ev","str","^Thanks, I'm good for now.","/str","/ev",{"*":".^.c-0","flg":4},{"c-0":["^ ","#","^exit_conversation","/#","\n","#","^speaker:npc","/#","^Alright then. Let me know if you need anything else!","\n",{"->":"hub"},null]}],null],"who_are_you":["^I'm a friendly NPC who can help you progress through the mission.","\n","^I can unlock doors, give you items, and provide hints when you need them.","\n","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^What would you like to do?","\n",{"->":"hub"},null],"help_ceo_office":["#","^speaker:npc","/#","ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^I already unlocked the CEO's office for you! Just head on in.","\n",{"->":"hub"},{"->":".^.^.^.8"},null]}],[{"->":".^.b"},{"b":["\n","^The CEO's office? That's a tough one...","\n","ev",{"VAR?":"trust_level"},1,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Alright, I trust you enough. Let me unlock that door for you.","\n","ev",true,"/ev",{"VAR=":"has_unlocked_ceo","re":true},"ev",true,"/ev",{"VAR=":"asked_about_ceo","re":true},"^There you go! The door to the CEO's office is now unlocked. ","#","^unlock_door:ceo","/#","\n","ev",{"VAR?":"trust_level"},2,"+","/ev",{"VAR=":"trust_level","re":true},"^What else can I help with?","\n",{"->":"hub"},{"->":".^.^.^.10"},null]}],[{"->":".^.b"},{"b":["\n","^I don't know you well enough yet. Ask me some questions first and we can build some trust.","\n",{"->":"hub"},{"->":".^.^.^.10"},null]}],"nop","\n",{"->":".^.^.^.8"},null]}],"nop","\n",null],"other_doors":["#","^speaker:npc","/#","^What other doors do you need help with? I can try to unlock them if you tell me which ones.","\n","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Let me know!","\n",{"->":"hub"},null],"give_items":["#","^speaker:npc","/#","ev",{"VAR?":"has_given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^I already gave you a lockpick set. Check your inventory - it should be there!","\n",{"->":"hub"},{"->":".^.^.^.8"},null]}],[{"->":".^.b"},{"b":["\n","^Let me see what I have...","\n","ev",{"VAR?":"trust_level"},2,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Here's a lockpick set. Use it to open locked doors and containers! πŸ”“","\n","ev",true,"/ev",{"VAR=":"has_given_lockpick","re":true},"ev",true,"/ev",{"VAR=":"asked_for_items","re":true},"#","^give_item:lockpick","/#","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Good luck out there!","\n",{"->":"hub"},{"->":".^.^.^.10"},null]}],[{"->":".^.b"},{"b":["\n","^I need to trust you more before I give you something like that.","\n","^Build up some trust first - ask me questions or help me out!","\n",{"->":"hub"},{"->":".^.^.^.10"},null]}],"nop","\n",{"->":".^.^.^.8"},null]}],"nop","\n",null],"other_items":["#","^speaker:npc","/#","ev",{"VAR?":"trust_level"},4,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^I've got a keycard for restricted areas. Think you can use it responsibly?","\n","#","^give_item:keycard","/#","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Use it wisely!","\n",{"->":"hub"},{"->":".^.^.^.10"},null]}],[{"->":".^.b"},{"b":["\n","^That's all I have right now. The lockpick set is your best tool for now.","\n",{"->":"hub"},{"->":".^.^.^.10"},null]}],"nop","\n",null],"lockpick_feedback":["^Great! I'm glad it helped you out. That's what I'm here for.","\n","^You're doing excellent work on this mission.","\n","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"ev",false,"/ev",{"VAR=":"saw_lockpick_used","re":true},"^What else do you need?","\n",{"->":"hub"},null],"give_hints":["ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^The CEO's office has evidence you're looking for. Search the desk thoroughly.","\n","^Also, check any computers for sensitive files.","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","ev",{"VAR?":"has_given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Try using that lockpick set on locked doors and containers around the building.","\n","^You never know what secrets people hide behind locked doors!","\n",{"->":".^.^.^.6"},null]}],[{"->":".^.b"},{"b":["\n","^Explore every room carefully. Items are often hidden in places you'd least expect.","\n",{"->":".^.^.^.6"},null]}],"nop","\n",{"->":".^.^.^.5"},null]}],"nop","\n","^Good luck!","\n",{"->":"hub"},null],"on_lockpick_pickup":["ev",{"VAR?":"has_given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Great! You found the lockpick I gave you. Try it on a locked door or container!","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","^Nice find! That lockpick set looks professional. Could be very useful. πŸ”“","\n",{"->":".^.^.^.5"},null]}],"nop","\n",{"->":"hub"},null],"on_lockpick_success":["ev",true,"/ev",{"VAR=":"saw_lockpick_used","re":true},"ev",{"VAR?":"has_given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Excellent! Glad I could help you get through that. 🎯","\n",{"->":".^.^.^.9"},null]}],[{"->":".^.b"},{"b":["\n","^Nice work getting through that lock! πŸ”“","\n",{"->":".^.^.^.9"},null]}],"nop","\n",{"->":"hub"},null],"on_lockpick_failed":["ev",{"VAR?":"has_given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Don't give up! Lockpicking takes practice. Try adjusting the tension. πŸ”§","\n","^Want me to help you with anything else?","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","^Tough break. Lockpicking isn't easy without the right tools...","\n","^I might be able to help with that if you ask.","\n",{"->":".^.^.^.5"},null]}],"nop","\n",{"->":"hub"},null],"on_door_unlocked":["ev",true,"/ev",{"VAR=":"saw_door_unlock","re":true},"ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Another door open! You're making great progress. πŸšͺβœ“","\n",{"->":".^.^.^.9"},null]}],[{"->":".^.b"},{"b":["\n","^Nice! You found a way through that door. Keep going!","\n",{"->":".^.^.^.9"},null]}],"nop","\n",{"->":"hub"},null],"on_door_attempt":["^That door's locked tight. You'll need to find a way to unlock it. πŸ”’","\n","ev",{"VAR?":"trust_level"},2,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Want me to help you out? Just ask!","\n",{"->":".^.^.^.9"},null]}],[{"->":".^.b"},{"b":["\n","ev",{"VAR?":"trust_level"},1,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^I might be able to help if you get to know me better first.","\n",{"->":".^.^.^.7"},null]}],"nop","\n",{"->":".^.^.^.9"},null]}],"nop","\n",{"->":"hub"},null],"on_ceo_desk_interact":["ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^The CEO's desk - you made it! Nice work. πŸ“‹","\n","^That's where the important evidence is kept.","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","^Trying to get into the CEO's office? I might be able to help with that...","\n",{"->":".^.^.^.5"},null]}],"nop","\n",{"->":"hub"},null],"on_item_found":["ev",{"VAR?":"trust_level"},1,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Good find! Every item could be important for your mission. πŸ“¦","\n",{"->":".^.^.^.6"},null]}],"nop","\n",{"->":"hub"},null],"on_room_entered":["ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Keep searching for that evidence! πŸ”","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","ev",{"VAR?":"trust_level"},1,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^You're making progress through the building. 🚢","\n","^Let me know if you need help with anything.","\n",{"->":".^.^.^.8"},null]}],[{"->":".^.b"},{"b":["\n","^Exploring new areas... 🚢","\n",{"->":".^.^.^.8"},null]}],"nop","\n",{"->":".^.^.^.5"},null]}],"nop","\n",{"->":"hub"},null],"on_room_discovered":["ev",{"VAR?":"trust_level"},2,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Great find! This new area might have what we need. πŸ—ΊοΈβœ¨","\n","^Search it thoroughly!","\n",{"->":".^.^.^.7"},null]}],[{"->":".^.b"},{"b":["\n","ev",{"VAR?":"trust_level"},1,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Interesting! You've found a new area. Be careful exploring. πŸ—ΊοΈ","\n",{"->":".^.^.^.8"},null]}],[{"->":".^.b"},{"b":["\n","^A new room... wonder what's inside. πŸšͺ","\n",{"->":".^.^.^.8"},null]}],"nop","\n",{"->":".^.^.^.7"},null]}],"nop","\n",{"->":"hub"},null],"on_ceo_office_entered":["ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^You're in! Remember, you're looking for evidence of the data breach. πŸ•΅οΈ","\n","^Check the desk, computer, and any drawers.","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","^Whoa, you got into the CEO's office! That's impressive! πŸŽ‰","\n","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Maybe I underestimated you. Impressive work!","\n",{"->":".^.^.^.5"},null]}],"nop","\n",{"->":"hub"},null],"global decl":["ev",0,{"VAR=":"trust_level"},false,{"VAR=":"has_unlocked_ceo"},false,{"VAR=":"has_given_lockpick"},false,{"VAR=":"saw_lockpick_used"},false,{"VAR=":"saw_door_unlock"},false,{"VAR=":"has_greeted"},false,{"VAR=":"asked_about_self"},false,{"VAR=":"asked_about_ceo"},false,{"VAR=":"asked_for_items"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["#","^speaker:npc","/#","^Hey there!! I'm here to help you out if you need it. πŸ‘‹","\n","^What can I do for you?","\n","ev",true,"/ev",{"VAR=":"has_greeted","re":true},{"->":"hub"},null],"hub":[["ev",{"VAR?":"asked_about_self"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Who are you?","/str","/ev",{"*":".^.c-0","flg":20},{"->":"hub.0.5"},{"c-0":["\n","ev",true,"/ev",{"VAR=":"asked_about_self","re":true},{"->":"who_are_you"},{"#f":5}]}]}],"nop","\n","ev",{"VAR?":"asked_about_self"},{"VAR?":"has_unlocked_ceo"},"!","&&","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Can you help me get into the CEO's office?","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.14"},{"c-0":["\n",{"->":"help_ceo_office"},null]}]}],"nop","\n","ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Any other doors you need help with?","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.20"},{"c-0":["\n",{"->":"other_doors"},null]}]}],"nop","\n","ev",{"VAR?":"asked_about_self"},{"VAR?":"has_given_lockpick"},"!","&&","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Do you have any items for me?","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.29"},{"c-0":["\n",{"->":"give_items"},null]}]}],"nop","\n","ev",{"VAR?":"has_given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Got any other items for me?","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.35"},{"c-0":["\n",{"->":"other_items"},null]}]}],"nop","\n","ev",{"VAR?":"saw_lockpick_used"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Thanks for the lockpick! It worked great.","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.41"},{"c-0":["\n",{"->":"lockpick_feedback"},null]}]}],"nop","\n","ev",{"VAR?":"trust_level"},3,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^What hints do you have for me?","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.49"},{"c-0":["\n",{"->":"give_hints"},null]}]}],"nop","\n","ev","str","^Thanks, I'm good for now.","/str","/ev",{"*":".^.c-0","flg":4},{"c-0":["^ ","#","^exit_conversation","/#","\n","#","^speaker:npc","/#","^Alright then. Let me know if you need anything else!","\n",{"->":"hub"},null]}],null],"who_are_you":["^I'm a friendly NPC who can help you progress through the mission.","\n","^I can unlock doors, give you items, and provide hints when you need them.","\n","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^What would you like to do?","\n",{"->":"hub"},null],"help_ceo_office":["#","^speaker:npc","/#","ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^I already unlocked the CEO's office for you! Just head on in.","\n",{"->":"hub"},{"->":".^.^.^.8"},null]}],[{"->":".^.b"},{"b":["\n","^The CEO's office? That's a tough one...","\n","ev",{"VAR?":"trust_level"},1,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Alright, I trust you enough. Let me unlock that door for you.","\n","ev",true,"/ev",{"VAR=":"has_unlocked_ceo","re":true},"ev",true,"/ev",{"VAR=":"asked_about_ceo","re":true},"^There you go! The door to the CEO's office is now unlocked. ","#","^unlock_door:ceo","/#","\n","ev",{"VAR?":"trust_level"},2,"+","/ev",{"VAR=":"trust_level","re":true},"^What else can I help with?","\n",{"->":"hub"},{"->":".^.^.^.10"},null]}],[{"->":".^.b"},{"b":["\n","^I don't know you well enough yet. Ask me some questions first and we can build some trust.","\n",{"->":"hub"},{"->":".^.^.^.10"},null]}],"nop","\n",{"->":".^.^.^.8"},null]}],"nop","\n",null],"other_doors":["#","^speaker:npc","/#","^What other doors do you need help with? I can try to unlock them if you tell me which ones.","\n","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Let me know!","\n",{"->":"hub"},null],"give_items":["#","^speaker:npc","/#","ev",{"VAR?":"trust_level"},2,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Let me see what I have available...","\n","ev",{"VAR?":"has_lockpick"},{"VAR?":"has_workstation"},"||",{"VAR?":"has_phone"},"||",{"VAR?":"has_bluetooth_scanner"},"||",{"VAR?":"has_fingerprint_kit"},"||",{"VAR?":"has_pin_cracker"},"||",{"VAR?":"has_keycard"},"||",{"VAR?":"has_key"},"||","/ev",[{"->":".^.b","c":true},{"b":["\n","^Here's what I can offer you:","\n","ev",{"VAR?":"has_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Lock Pick Kit - for opening locked doors and containers πŸ”“",{"->":".^.^.^.7"},null]}],"nop","\n","ev",{"VAR?":"has_workstation"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Crypto Analysis Station - for cryptographic challenges πŸ’»",{"->":".^.^.^.13"},null]}],"nop","\n","ev",{"VAR?":"has_phone"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Phone - with interesting contacts πŸ“±",{"->":".^.^.^.19"},null]}],"nop","\n","ev",{"VAR?":"has_bluetooth_scanner"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Bluetooth Scanner - for finding nearby devices πŸ“‘",{"->":".^.^.^.25"},null]}],"nop","\n","ev",{"VAR?":"has_fingerprint_kit"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Fingerprint Kit - for forensic analysis πŸ”",{"->":".^.^.^.31"},null]}],"nop","\n","ev",{"VAR?":"has_pin_cracker"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ PIN Cracker - for bypassing numeric locks πŸ”’",{"->":".^.^.^.37"},null]}],"nop","\n","ev",{"VAR?":"has_keycard"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Keycard - for restricted areas 🎫",{"->":".^.^.^.43"},null]}],"nop","\n","ev",{"VAR?":"has_key"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Key - might be useful πŸ”‘",{"->":".^.^.^.49"},null]}],"nop","\n","^What would you like?","\n","ev",{"VAR?":"has_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Lock Pick Kit","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.57"},{"c-0":["\n",{"->":"give_lockpick"},null]}]}],"nop","\n","ev",{"VAR?":"has_workstation"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Crypto Analysis Station","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.63"},{"c-0":["\n",{"->":"give_workstation"},null]}]}],"nop","\n","ev",{"VAR?":"has_phone"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Phone","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.69"},{"c-0":["\n",{"->":"give_phone"},null]}]}],"nop","\n","ev",{"VAR?":"has_bluetooth_scanner"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Bluetooth Scanner","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.75"},{"c-0":["\n",{"->":"give_bluetooth_scanner"},null]}]}],"nop","\n","ev",{"VAR?":"has_fingerprint_kit"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Fingerprint Kit","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.81"},{"c-0":["\n",{"->":"give_fingerprint_kit"},null]}]}],"nop","\n","ev",{"VAR?":"has_pin_cracker"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the PIN Cracker","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.87"},{"c-0":["\n",{"->":"give_pin_cracker"},null]}]}],"nop","\n","ev",{"VAR?":"has_keycard"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Keycard","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.93"},{"c-0":["\n",{"->":"give_keycard"},null]}]}],"nop","\n","ev",{"VAR?":"has_key"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Key","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.99"},{"c-0":["\n",{"->":"give_key"},null]}]}],"nop","\n","ev","str","^Actually, never mind","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.22"},{"c-0":["\n",{"->":"hub"},null]}]}],[{"->":".^.b"},{"b":["\n","^I don't have any items to give you right now. Sorry!","\n",{"->":"hub"},{"->":".^.^.^.22"},null]}],"nop","\n",{"->":".^.^.^.10"},null]}],[{"->":".^.b"},{"b":["\n","^I need to trust you more before I give you something like that.","\n","^Build up some trust first - ask me questions or help me out!","\n",{"->":"hub"},{"->":".^.^.^.10"},null]}],"nop","\n",null],"give_lockpick":["#","^speaker:npc","/#","^Here's a lockpick set. Use it to open locked doors and containers! πŸ”“","\n","ev",true,"/ev",{"VAR=":"has_given_lockpick","re":true},"ev",true,"/ev",{"VAR=":"asked_for_items","re":true},"#","^give_item:lockpick","/#","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Good luck out there!","\n",{"->":"hub"},null],"give_workstation":["#","^speaker:npc","/#","^I've got a crypto analysis workstation for you. This should help with any cryptographic challenges you encounter.","\n","ev",true,"/ev",{"VAR=":"asked_for_items","re":true},"#","^give_item:workstation","/#","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Use it well!","\n",{"->":"hub"},null],"give_phone":["#","^speaker:npc","/#","^Here's a phone. It has some interesting contacts you might find useful.","\n","ev",true,"/ev",{"VAR=":"asked_for_items","re":true},"#","^give_item:phone","/#","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Keep in touch!","\n",{"->":"hub"},null],"give_bluetooth_scanner":["#","^speaker:npc","/#","^I have a Bluetooth scanner. Use it to find and connect to nearby devices.","\n","ev",true,"/ev",{"VAR=":"asked_for_items","re":true},"#","^give_item:bluetooth_scanner","/#","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Happy scanning!","\n",{"->":"hub"},null],"give_fingerprint_kit":["#","^speaker:npc","/#","^Here's a fingerprint kit. Useful for forensic analysis.","\n","ev",true,"/ev",{"VAR=":"asked_for_items","re":true},"#","^give_item:fingerprint_kit","/#","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Handle with care!","\n",{"->":"hub"},null],"give_pin_cracker":["#","^speaker:npc","/#","^I've got a PIN cracker tool. This can help you bypass numeric locks.","\n","ev",true,"/ev",{"VAR=":"asked_for_items","re":true},"#","^give_item:pin-cracker","/#","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Use responsibly!","\n",{"->":"hub"},null],"give_keycard":["#","^speaker:npc","/#","^Here's a keycard for restricted areas. Think you can use it responsibly?","\n","ev",true,"/ev",{"VAR=":"asked_for_items","re":true},"#","^give_item:keycard","/#","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Use it wisely!","\n",{"->":"hub"},null],"give_key":["#","^speaker:npc","/#","^I have a key that might be useful. Take it!","\n","ev",true,"/ev",{"VAR=":"asked_for_items","re":true},"#","^give_item:key","/#","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},{"->":"hub"},null],"other_items":["#","^speaker:npc","/#","ev",{"VAR?":"trust_level"},4,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Let me see what else I have available...","\n","ev",{"VAR?":"has_workstation"},{"VAR?":"has_phone"},"||",{"VAR?":"has_bluetooth_scanner"},"||",{"VAR?":"has_fingerprint_kit"},"||",{"VAR?":"has_pin_cracker"},"||",{"VAR?":"has_keycard"},"||",{"VAR?":"has_key"},"||","/ev",[{"->":".^.b","c":true},{"b":["\n","^Here's what else I can offer:","\n","ev",{"VAR?":"has_workstation"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Crypto Analysis Station - for cryptographic challenges πŸ’»",{"->":".^.^.^.7"},null]}],"nop","\n","ev",{"VAR?":"has_phone"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Phone - with interesting contacts πŸ“±",{"->":".^.^.^.13"},null]}],"nop","\n","ev",{"VAR?":"has_bluetooth_scanner"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Bluetooth Scanner - for finding nearby devices πŸ“‘",{"->":".^.^.^.19"},null]}],"nop","\n","ev",{"VAR?":"has_fingerprint_kit"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Fingerprint Kit - for forensic analysis πŸ”",{"->":".^.^.^.25"},null]}],"nop","\n","ev",{"VAR?":"has_pin_cracker"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ PIN Cracker - for bypassing numeric locks πŸ”’",{"->":".^.^.^.31"},null]}],"nop","\n","ev",{"VAR?":"has_keycard"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Keycard - for restricted areas 🎫",{"->":".^.^.^.37"},null]}],"nop","\n","ev",{"VAR?":"has_key"},"/ev",[{"->":".^.b","c":true},{"b":["^ β€’ Key - might be useful πŸ”‘",{"->":".^.^.^.43"},null]}],"nop","\n","^What would you like?","\n","ev",{"VAR?":"has_workstation"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Crypto Analysis Station","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.51"},{"c-0":["\n",{"->":"give_workstation"},null]}]}],"nop","\n","ev",{"VAR?":"has_phone"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Phone","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.57"},{"c-0":["\n",{"->":"give_phone"},null]}]}],"nop","\n","ev",{"VAR?":"has_bluetooth_scanner"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Bluetooth Scanner","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.63"},{"c-0":["\n",{"->":"give_bluetooth_scanner"},null]}]}],"nop","\n","ev",{"VAR?":"has_fingerprint_kit"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Fingerprint Kit","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.69"},{"c-0":["\n",{"->":"give_fingerprint_kit"},null]}]}],"nop","\n","ev",{"VAR?":"has_pin_cracker"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the PIN Cracker","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.75"},{"c-0":["\n",{"->":"give_pin_cracker"},null]}]}],"nop","\n","ev",{"VAR?":"has_keycard"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Keycard","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.81"},{"c-0":["\n",{"->":"give_keycard"},null]}]}],"nop","\n","ev",{"VAR?":"has_key"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'll take the Key","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.87"},{"c-0":["\n",{"->":"give_key"},null]}]}],"nop","\n","ev","str","^Actually, never mind","/str","/ev",{"*":".^.c-0","flg":4},{"->":".^.^.^.20"},{"c-0":["\n",{"->":"hub"},null]}]}],[{"->":".^.b"},{"b":["\n","^That's all I have right now. Sorry!","\n",{"->":"hub"},{"->":".^.^.^.20"},null]}],"nop","\n",{"->":".^.^.^.10"},null]}],[{"->":".^.b"},{"b":["\n","^That's all I have right now. The lockpick set is your best tool for now.","\n",{"->":"hub"},{"->":".^.^.^.10"},null]}],"nop","\n",null],"lockpick_feedback":["^Great! I'm glad it helped you out. That's what I'm here for.","\n","^You're doing excellent work on this mission.","\n","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"ev",false,"/ev",{"VAR=":"saw_lockpick_used","re":true},"^What else do you need?","\n",{"->":"hub"},null],"give_hints":["ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^The CEO's office has evidence you're looking for. Search the desk thoroughly.","\n","^Also, check any computers for sensitive files.","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","ev",{"VAR?":"has_given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Try using that lockpick set on locked doors and containers around the building.","\n","^You never know what secrets people hide behind locked doors!","\n",{"->":".^.^.^.6"},null]}],[{"->":".^.b"},{"b":["\n","^Explore every room carefully. Items are often hidden in places you'd least expect.","\n",{"->":".^.^.^.6"},null]}],"nop","\n",{"->":".^.^.^.5"},null]}],"nop","\n","^Good luck!","\n",{"->":"hub"},null],"on_lockpick_pickup":["ev",{"VAR?":"has_given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Great! You found the lockpick I gave you. Try it on a locked door or container!","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","^Nice find! That lockpick set looks professional. Could be very useful. πŸ”“","\n",{"->":".^.^.^.5"},null]}],"nop","\n",{"->":"hub"},null],"on_lockpick_success":["ev",true,"/ev",{"VAR=":"saw_lockpick_used","re":true},"ev",{"VAR?":"has_given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Excellent! Glad I could help you get through that. 🎯","\n",{"->":".^.^.^.9"},null]}],[{"->":".^.b"},{"b":["\n","^Nice work getting through that lock! πŸ”“","\n",{"->":".^.^.^.9"},null]}],"nop","\n",{"->":"hub"},null],"on_lockpick_failed":["ev",{"VAR?":"has_given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Don't give up! Lockpicking takes practice. Try adjusting the tension. πŸ”§","\n","^Want me to help you with anything else?","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","^Tough break. Lockpicking isn't easy without the right tools...","\n","^I might be able to help with that if you ask.","\n",{"->":".^.^.^.5"},null]}],"nop","\n",{"->":"hub"},null],"on_door_unlocked":["ev",true,"/ev",{"VAR=":"saw_door_unlock","re":true},"ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Another door open! You're making great progress. πŸšͺβœ“","\n",{"->":".^.^.^.9"},null]}],[{"->":".^.b"},{"b":["\n","^Nice! You found a way through that door. Keep going!","\n",{"->":".^.^.^.9"},null]}],"nop","\n",{"->":"hub"},null],"on_door_attempt":["^That door's locked tight. You'll need to find a way to unlock it. πŸ”’","\n","ev",{"VAR?":"trust_level"},2,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Want me to help you out? Just ask!","\n",{"->":".^.^.^.9"},null]}],[{"->":".^.b"},{"b":["\n","ev",{"VAR?":"trust_level"},1,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^I might be able to help if you get to know me better first.","\n",{"->":".^.^.^.7"},null]}],"nop","\n",{"->":".^.^.^.9"},null]}],"nop","\n",{"->":"hub"},null],"on_ceo_desk_interact":["ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^The CEO's desk - you made it! Nice work. πŸ“‹","\n","^That's where the important evidence is kept.","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","^Trying to get into the CEO's office? I might be able to help with that...","\n",{"->":".^.^.^.5"},null]}],"nop","\n",{"->":"hub"},null],"on_item_found":["ev",{"VAR?":"trust_level"},1,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Good find! Every item could be important for your mission. πŸ“¦","\n",{"->":".^.^.^.6"},null]}],"nop","\n",{"->":"hub"},null],"on_room_entered":["ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Keep searching for that evidence! πŸ”","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","ev",{"VAR?":"trust_level"},1,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^You're making progress through the building. 🚢","\n","^Let me know if you need help with anything.","\n",{"->":".^.^.^.8"},null]}],[{"->":".^.b"},{"b":["\n","^Exploring new areas... 🚢","\n",{"->":".^.^.^.8"},null]}],"nop","\n",{"->":".^.^.^.5"},null]}],"nop","\n",{"->":"hub"},null],"on_room_discovered":["ev",{"VAR?":"trust_level"},2,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Great find! This new area might have what we need. πŸ—ΊοΈβœ¨","\n","^Search it thoroughly!","\n",{"->":".^.^.^.7"},null]}],[{"->":".^.b"},{"b":["\n","ev",{"VAR?":"trust_level"},1,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Interesting! You've found a new area. Be careful exploring. πŸ—ΊοΈ","\n",{"->":".^.^.^.8"},null]}],[{"->":".^.b"},{"b":["\n","^A new room... wonder what's inside. πŸšͺ","\n",{"->":".^.^.^.8"},null]}],"nop","\n",{"->":".^.^.^.7"},null]}],"nop","\n",{"->":"hub"},null],"on_ceo_office_entered":["ev",{"VAR?":"has_unlocked_ceo"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^You're in! Remember, you're looking for evidence of the data breach. πŸ•΅οΈ","\n","^Check the desk, computer, and any drawers.","\n",{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","^Whoa, you got into the CEO's office! That's impressive! πŸŽ‰","\n","ev",{"VAR?":"trust_level"},1,"+","/ev",{"VAR=":"trust_level","re":true},"^Maybe I underestimated you. Impressive work!","\n",{"->":".^.^.^.5"},null]}],"nop","\n",{"->":"hub"},null],"global decl":["ev",0,{"VAR=":"trust_level"},false,{"VAR=":"has_unlocked_ceo"},false,{"VAR=":"has_given_lockpick"},false,{"VAR=":"saw_lockpick_used"},false,{"VAR=":"saw_door_unlock"},false,{"VAR=":"has_greeted"},false,{"VAR=":"asked_about_self"},false,{"VAR=":"asked_about_ceo"},false,{"VAR=":"asked_for_items"},false,{"VAR=":"has_phone"},false,{"VAR=":"has_lockpick"},false,{"VAR=":"has_workstation"},false,{"VAR=":"has_bluetooth_scanner"},false,{"VAR=":"has_fingerprint_kit"},false,{"VAR=":"has_pin_cracker"},false,{"VAR=":"has_keycard"},false,{"VAR=":"has_key"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/ink/test2.ink b/scenarios/ink/test2.ink index da46fcd..d29f6cd 100644 --- a/scenarios/ink/test2.ink +++ b/scenarios/ink/test2.ink @@ -10,42 +10,42 @@ Woop! Welcome! This is a group conversation test. Let me introduce you to my col === group_meeting === # speaker:npc:test_npc_back Agent, meet my colleague from the back office. BACK -+ [Continue] -> colleague_introduction +-> colleague_introduction === colleague_introduction === # speaker:npc:test_npc_front Nice to meet you! I'm the lead technician here. FRONT. -+ [Ask about their work] -> player_question +-> player_question === player_question === # speaker:player What kind of work do you both do here? -+ [Listen] -> front_npc_explains +-> front_npc_explains === front_npc_explains === # speaker:npc:test_npc_back Well, I handle the front desk operations and guest interactions. But my colleague here... -+ [Continue listening] -> colleague_responds +-> colleague_responds === colleague_responds === # speaker:npc:test_npc_front I manage all the backend systems and security infrastructure. Together, we keep everything running smoothly. -+ [Respond] -> player_follow_up +-> player_follow_up === player_follow_up === # speaker:player That sounds like a well-coordinated operation! -+ [Listen more] -> front_npc_agrees +-> front_npc_agrees === front_npc_agrees === # speaker:npc:test_npc_back It really is! We've been working together for several years now. Communication is key. -+ [Hear more] -> colleague_adds +-> colleague_adds === colleague_adds === # speaker:npc:test_npc_front Exactly. And we're always looking for talented people like you to join our team. -+ [Respond] -> player_closing +-> player_closing === player_closing === # speaker:player diff --git a/scenarios/npc-sprite-test2.json b/scenarios/npc-sprite-test2.json index d7ef4f4..3488321 100644 --- a/scenarios/npc-sprite-test2.json +++ b/scenarios/npc-sprite-test2.json @@ -22,7 +22,7 @@ "npcs": [ { "id": "test_npc_front", - "displayName": "Front NPC", + "displayName": "Helper NPC", "npcType": "person", "position": { "x": 5, "y": 3 }, "spriteSheet": "hacker-red", @@ -32,7 +32,29 @@ "idleFrameEnd": 23 }, "storyPath": "scenarios/ink/helper-npc.json", - "currentKnot": "start" + "currentKnot": "start", + "itemsHeld": [ + { + "type": "phone", + "name": "Your Phone", + "takeable": true, + "phoneId": "player_phone", + "npcIds": ["neye_eve", "gossip_girl", "helper_npc"], + "observations": "Your personal phone with some interesting contacts" + }, + { + "type": "workstation", + "name": "Crypto Analysis Station", + "takeable": true, + "observations": "A powerful workstation for cryptographic analysis" + }, + { + "type": "lockpick", + "name": "Lock Pick Kit", + "takeable": true, + "observations": "A professional lock picking kit with various picks and tension wrenches" + } + ] }, { "id": "test_npc_back", @@ -47,9 +69,9 @@ "storyPath": "scenarios/ink/test2.json", "currentKnot": "hub", "timedConversation": { - "delay": 3000, + "delay": 100, "targetKnot": "group_meeting", - "background": "assets/mini-games/desktop-wallpaper.png" + "background": "assets/backgrounds/hq1.png" } } ]