mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
Enhance inventory and container management for improved gameplay experience
- Added functionality to include current player inventory in game state for page reload recovery, allowing players to restore their inventory seamlessly. - Implemented filtering of container contents to exclude items already in the player's inventory, enhancing user experience and gameplay clarity. - Updated game mechanics to support both type-based and ID-based matching for inventory items, improving task validation and objectives tracking. - Enhanced logging for better visibility into inventory processing and container content loading, aiding in debugging and game state management. - Updated scenarios to reflect changes in item identification and task requirements, ensuring consistency across gameplay elements.
This commit is contained in:
@@ -111,6 +111,12 @@ module BreakEscape
|
||||
filtered['submittedFlags'] = @game.player_state['submitted_flags']
|
||||
end
|
||||
|
||||
# Include current inventory from player_state for page reload recovery
|
||||
# This allows the client to restore inventory state on reload
|
||||
if @game.player_state['inventory'].present? && @game.player_state['inventory'].is_a?(Array)
|
||||
filtered['playerInventory'] = @game.player_state['inventory']
|
||||
end
|
||||
|
||||
render json: filtered
|
||||
rescue => e
|
||||
Rails.logger.error "[BreakEscape] scenario error: #{e.message}\n#{e.backtrace.first(5).join("\n")}"
|
||||
@@ -226,6 +232,9 @@ module BreakEscape
|
||||
def container
|
||||
authorize @game if defined?(Pundit)
|
||||
|
||||
# Reload game to get latest player_state (in case inventory was updated)
|
||||
@game.reload
|
||||
|
||||
container_id = params[:container_id]
|
||||
return render_error('Missing container_id parameter', :bad_request) unless container_id.present?
|
||||
|
||||
@@ -243,7 +252,8 @@ module BreakEscape
|
||||
# Return filtered contents
|
||||
contents = filter_container_contents(container_data)
|
||||
|
||||
Rails.logger.debug "[BreakEscape] Serving container contents for: #{container_id}"
|
||||
Rails.logger.info "[BreakEscape] Serving container contents for: #{container_id} - returning #{contents.length} items"
|
||||
Rails.logger.debug "[BreakEscape] Container contents: #{contents.map { |c| "#{c['type']}/#{c['id']}/#{c['name']}" }.join(', ')}"
|
||||
|
||||
render json: {
|
||||
container_id: container_id,
|
||||
@@ -658,7 +668,69 @@ module BreakEscape
|
||||
item_copy
|
||||
end || []
|
||||
|
||||
contents
|
||||
# Filter out items that are already in the player's inventory
|
||||
inventory = @game.player_state['inventory'] || []
|
||||
Rails.logger.debug "[BreakEscape] Filtering container contents. Inventory has #{inventory.length} items"
|
||||
Rails.logger.debug "[BreakEscape] Container has #{contents.length} items before filtering"
|
||||
|
||||
filtered_contents = contents.reject do |item|
|
||||
in_inventory = item_in_inventory?(item, inventory)
|
||||
if in_inventory
|
||||
Rails.logger.debug "[BreakEscape] Filtering out item: #{item['type']} / #{item['id']} / #{item['name']} (already in inventory)"
|
||||
end
|
||||
in_inventory
|
||||
end
|
||||
|
||||
Rails.logger.debug "[BreakEscape] Container has #{filtered_contents.length} items after filtering"
|
||||
filtered_contents
|
||||
end
|
||||
|
||||
# Check if an item is already in the player's inventory
|
||||
# Matches by type, id, or name (similar to validation logic)
|
||||
def item_in_inventory?(item, inventory)
|
||||
return false if inventory.blank? || item.blank?
|
||||
|
||||
# Normalize item data (handle both string and symbol keys)
|
||||
item_type = item['type'] || item[:type]
|
||||
item_id = item['key_id'] || item[:key_id] || item['id'] || item[:id]
|
||||
item_name = item['name'] || item[:name]
|
||||
|
||||
Rails.logger.debug "[BreakEscape] Checking if item in inventory: type=#{item_type}, id=#{item_id}, name=#{item_name}"
|
||||
|
||||
inventory.any? do |inv_item|
|
||||
# Inventory items are stored as flat objects (not nested in scenarioData)
|
||||
# Handle both string and symbol keys
|
||||
inv_type = inv_item['type'] || inv_item[:type]
|
||||
inv_id = inv_item['key_id'] || inv_item[:key_id] || inv_item['id'] || inv_item[:id]
|
||||
inv_name = inv_item['name'] || inv_item[:name]
|
||||
|
||||
Rails.logger.debug "[BreakEscape] Comparing with inventory item: type=#{inv_type}, id=#{inv_id}, name=#{inv_name}"
|
||||
|
||||
# Must match type
|
||||
next false unless inv_type == item_type
|
||||
|
||||
# If both have IDs, match by ID (most specific)
|
||||
if item_id.present? && inv_id.present?
|
||||
match = inv_id.to_s == item_id.to_s
|
||||
Rails.logger.debug "[BreakEscape] ID match: #{match} (#{item_id} == #{inv_id})"
|
||||
return true if match
|
||||
end
|
||||
|
||||
# If both have names, match by name (fallback if no ID match)
|
||||
if item_name.present? && inv_name.present?
|
||||
match = inv_name.to_s == item_name.to_s
|
||||
Rails.logger.debug "[BreakEscape] Name match: #{match} (#{item_name} == #{inv_name})"
|
||||
return true if match
|
||||
end
|
||||
|
||||
# If item has no ID or name, match by type only (less specific, but works for generic items)
|
||||
if item_id.blank? && item_name.blank?
|
||||
Rails.logger.debug "[BreakEscape] Type-only match (no ID/name)"
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Items that are always allowed in inventory (core game mechanics)
|
||||
|
||||
@@ -480,13 +480,40 @@ module BreakEscape
|
||||
end
|
||||
|
||||
# Validate collection tasks
|
||||
# Supports both type-based matching (targetItems) and ID-based matching (targetItemIds)
|
||||
def validate_collection(task)
|
||||
inventory = player_state['inventory'] || []
|
||||
target_items = Array(task['targetItems'])
|
||||
target_items = Array(task['targetItems'] || [])
|
||||
target_item_ids = Array(task['targetItemIds'] || [])
|
||||
|
||||
count = inventory.count do |item|
|
||||
item_type = item['type'] || item.dig('scenarioData', 'type')
|
||||
target_items.include?(item_type)
|
||||
item_id = item['id'] || item.dig('scenarioData', 'id')
|
||||
item_name = item['name'] || item.dig('scenarioData', 'name')
|
||||
identifier = item_id || item_name
|
||||
|
||||
matches = false
|
||||
|
||||
# Type-based matching
|
||||
if target_items.any?
|
||||
matches = target_items.include?(item_type)
|
||||
end
|
||||
|
||||
# ID-based matching (more specific)
|
||||
if target_item_ids.any?
|
||||
matches = target_item_ids.include?(identifier)
|
||||
end
|
||||
|
||||
# If both specified, match either
|
||||
if target_items.any? && target_item_ids.any?
|
||||
type_match = target_items.include?(item_type)
|
||||
id_match = target_item_ids.include?(identifier)
|
||||
matches = type_match || id_match
|
||||
end
|
||||
|
||||
matches
|
||||
end
|
||||
|
||||
count >= (task['targetCount'] || 1)
|
||||
end
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ export class ContainerMinigame extends MinigameScene {
|
||||
constructor(container, params) {
|
||||
super(container, params);
|
||||
this.containerItem = params.containerItem;
|
||||
this.contents = params.contents || [];
|
||||
// Don't set contents here - let init() load from server if available
|
||||
// Only use passed contents as fallback for locked containers or local games
|
||||
this.contents = [];
|
||||
this.isTakeable = params.isTakeable || false;
|
||||
|
||||
// NPC mode support
|
||||
@@ -38,17 +40,18 @@ export class ContainerMinigame extends MinigameScene {
|
||||
}
|
||||
|
||||
async loadContainerContents() {
|
||||
const gameId = window.gameId;
|
||||
// Try multiple sources for gameId
|
||||
const gameId = window.gameId || window.breakEscapeConfig?.gameId;
|
||||
const containerId = this.containerItem.scenarioData.id ||
|
||||
this.containerItem.scenarioData.name ||
|
||||
this.containerItem.objectId;
|
||||
|
||||
if (!gameId) {
|
||||
console.error('No gameId available for container loading');
|
||||
console.error('No gameId available for container loading. Checked window.gameId and window.breakEscapeConfig?.gameId');
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log(`Loading contents for container: ${containerId}`);
|
||||
console.log(`Loading contents for container: ${containerId} (gameId: ${gameId})`);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/break_escape/games/${gameId}/container/${containerId}`, {
|
||||
@@ -67,7 +70,7 @@ export class ContainerMinigame extends MinigameScene {
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log(`Loaded ${data.contents?.length || 0} items from container`);
|
||||
console.log(`Loaded ${data.contents?.length || 0} items from container ${containerId}:`, data.contents);
|
||||
return data.contents || [];
|
||||
} catch (error) {
|
||||
console.error('Failed to load container contents:', error);
|
||||
@@ -103,11 +106,18 @@ export class ContainerMinigame extends MinigameScene {
|
||||
// Show loading state
|
||||
this.gameContainer.innerHTML = '<div class="loading" style="text-align: center; padding: 20px;">Loading contents...</div>';
|
||||
|
||||
// Load contents from server (if gameId exists and container is not locked)
|
||||
if (window.gameId && this.containerItem.scenarioData.locked === false) {
|
||||
// Always load contents from server if gameId exists and container is unlocked
|
||||
// This ensures we get the latest contents (with items already in inventory filtered out)
|
||||
// Even if contents were passed in params, reload from server to get accurate state
|
||||
const gameId = window.gameId || window.breakEscapeConfig?.gameId;
|
||||
if (gameId && this.containerItem.scenarioData.locked === false) {
|
||||
console.log('Reloading container contents from server to get latest state');
|
||||
this.contents = await this.loadContainerContents();
|
||||
} else if (this.params.contents && this.params.contents.length > 0) {
|
||||
// Only use passed contents if server loading isn't available (locked container or local game)
|
||||
console.log('Using passed contents (container locked or no server)');
|
||||
this.contents = this.params.contents;
|
||||
}
|
||||
// Otherwise use contents passed in (for unlocked containers or local game)
|
||||
|
||||
// Create the container minigame UI
|
||||
this.createContainerUI();
|
||||
@@ -736,6 +746,25 @@ export function returnToContainerAfterNotes() {
|
||||
if (containerState.itemToTake) {
|
||||
console.log('Removing notes item after notes minigame:', containerState.itemToTake);
|
||||
|
||||
// If the item is takeable, add it to inventory so objectives system can track it
|
||||
if (containerState.itemToTake.takeable && window.addToInventory) {
|
||||
console.log('Adding takeable notes item to inventory for objectives tracking');
|
||||
|
||||
// Create a temporary sprite-like object for the inventory system
|
||||
const tempSprite = {
|
||||
scenarioData: containerState.itemToTake,
|
||||
name: containerState.itemToTake.type,
|
||||
objectId: `temp_${Date.now()}`,
|
||||
setVisible: function(visible) {
|
||||
// Mock setVisible method for inventory compatibility
|
||||
console.log(`Mock setVisible(${visible}) called on temp sprite`);
|
||||
}
|
||||
};
|
||||
|
||||
// Add to inventory - this will emit the item_picked_up event
|
||||
window.addToInventory(tempSprite);
|
||||
}
|
||||
|
||||
// Remove from container display
|
||||
if (containerState.itemElement && containerState.itemElement.parentElement) {
|
||||
containerState.itemElement.parentElement.remove();
|
||||
@@ -750,10 +779,11 @@ export function returnToContainerAfterNotes() {
|
||||
window.gameAlert(`${containerState.itemToTake.name} has been noted`, 'success', 'Added to Notes', 2000);
|
||||
}
|
||||
|
||||
// Start the container minigame with the stored state
|
||||
// Start the container minigame - don't pass contents, let it reload from server
|
||||
// This ensures items already in inventory are filtered out
|
||||
startContainerMinigame(
|
||||
containerState.containerItem,
|
||||
containerState.contents,
|
||||
null, // Don't pass contents - let it reload from server
|
||||
containerState.isTakeable,
|
||||
null, // desktopMode - let it auto-detect or use npcOptions
|
||||
containerState.npcOptions // Restore NPC context if it was saved
|
||||
|
||||
@@ -128,7 +128,23 @@ export function processInitialInventoryItems() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for startItemsInInventory array in scenario
|
||||
// Priority 1: Use server-side inventory if available (for page reload recovery)
|
||||
if (window.gameScenario.playerInventory && Array.isArray(window.gameScenario.playerInventory)) {
|
||||
console.log(`Processing ${window.gameScenario.playerInventory.length} items from server inventory`);
|
||||
|
||||
window.gameScenario.playerInventory.forEach(itemData => {
|
||||
console.log(`Adding ${itemData.name} to inventory from server playerInventory`);
|
||||
|
||||
// Create inventory sprite for this object
|
||||
const inventoryItem = createInventorySprite(itemData);
|
||||
if (inventoryItem) {
|
||||
addToInventory(inventoryItem);
|
||||
}
|
||||
});
|
||||
return; // Don't process startItemsInInventory if we loaded from server
|
||||
}
|
||||
|
||||
// Priority 2: Fall back to startItemsInInventory from scenario (for new games)
|
||||
if (window.gameScenario.startItemsInInventory && Array.isArray(window.gameScenario.startItemsInInventory)) {
|
||||
console.log(`Processing ${window.gameScenario.startItemsInInventory.length} starting inventory items`);
|
||||
|
||||
@@ -370,6 +386,7 @@ export async function addToInventory(sprite) {
|
||||
window.eventDispatcher.emit(`item_picked_up:${sprite.scenarioData.type}`, {
|
||||
itemType: sprite.scenarioData.type,
|
||||
itemName: sprite.scenarioData.name,
|
||||
itemId: sprite.scenarioData.id,
|
||||
roomId: window.currentPlayerRoom
|
||||
});
|
||||
}
|
||||
@@ -445,6 +462,7 @@ function addKeyToInventory(sprite) {
|
||||
window.eventDispatcher.emit(`item_picked_up:key`, {
|
||||
itemType: 'key',
|
||||
itemName: sprite.scenarioData?.name || 'Unknown Key',
|
||||
itemId: sprite.scenarioData?.id || keyId,
|
||||
keyId: keyId,
|
||||
roomId: window.currentPlayerRoom
|
||||
});
|
||||
|
||||
@@ -149,15 +149,65 @@ export class ObjectivesManager {
|
||||
case 'collect_items':
|
||||
const matchingItems = inventoryItems.filter(item => {
|
||||
const itemType = item.scenarioData?.type || item.getAttribute?.('data-type');
|
||||
return task.targetItems.includes(itemType);
|
||||
const itemId = item.scenarioData?.id;
|
||||
const itemName = item.scenarioData?.name;
|
||||
|
||||
let matches = false;
|
||||
|
||||
// Type-based matching
|
||||
if (task.targetItems && task.targetItems.length > 0) {
|
||||
matches = task.targetItems.includes(itemType);
|
||||
}
|
||||
|
||||
// ID-based matching (more specific)
|
||||
if (task.targetItemIds && task.targetItemIds.length > 0) {
|
||||
const identifier = itemId || itemName;
|
||||
matches = task.targetItemIds.includes(identifier);
|
||||
}
|
||||
|
||||
// If both specified, match either
|
||||
if (task.targetItems && task.targetItems.length > 0 &&
|
||||
task.targetItemIds && task.targetItemIds.length > 0) {
|
||||
const typeMatch = task.targetItems.includes(itemType);
|
||||
const identifier = itemId || itemName;
|
||||
const idMatch = task.targetItemIds.includes(identifier);
|
||||
matches = typeMatch || idMatch;
|
||||
}
|
||||
|
||||
return matches;
|
||||
});
|
||||
|
||||
// Also count keys from keyRing
|
||||
const keyRingItems = window.inventory?.keyRing?.keys || [];
|
||||
const matchingKeys = keyRingItems.filter(key =>
|
||||
task.targetItems.includes(key.scenarioData?.type) ||
|
||||
task.targetItems.includes('key')
|
||||
);
|
||||
const matchingKeys = keyRingItems.filter(key => {
|
||||
const keyType = key.scenarioData?.type;
|
||||
const keyId = key.scenarioData?.key_id || key.scenarioData?.id;
|
||||
const keyName = key.scenarioData?.name;
|
||||
|
||||
let matches = false;
|
||||
|
||||
// Type-based matching
|
||||
if (task.targetItems && task.targetItems.length > 0) {
|
||||
matches = task.targetItems.includes(keyType) || task.targetItems.includes('key');
|
||||
}
|
||||
|
||||
// ID-based matching
|
||||
if (task.targetItemIds && task.targetItemIds.length > 0) {
|
||||
const identifier = keyId || keyName;
|
||||
matches = task.targetItemIds.includes(identifier);
|
||||
}
|
||||
|
||||
// If both specified, match either
|
||||
if (task.targetItems && task.targetItems.length > 0 &&
|
||||
task.targetItemIds && task.targetItemIds.length > 0) {
|
||||
const typeMatch = task.targetItems.includes(keyType) || task.targetItems.includes('key');
|
||||
const identifier = keyId || keyName;
|
||||
const idMatch = task.targetItemIds.includes(identifier);
|
||||
matches = typeMatch || idMatch;
|
||||
}
|
||||
|
||||
return matches;
|
||||
});
|
||||
|
||||
const totalCount = matchingItems.length + matchingKeys.length;
|
||||
|
||||
@@ -245,17 +295,45 @@ export class ObjectivesManager {
|
||||
|
||||
/**
|
||||
* Handle item pickup - check collect_items tasks
|
||||
* Supports both type-based matching (targetItems) and ID-based matching (targetItemIds)
|
||||
*/
|
||||
handleItemPickup(data) {
|
||||
if (!this.initialized) return;
|
||||
|
||||
const itemType = data.itemType;
|
||||
const itemId = data.itemId;
|
||||
const itemName = data.itemName;
|
||||
|
||||
// Find all active collect_items tasks that target this item type
|
||||
// Find all active collect_items tasks that target this item
|
||||
Object.values(this.taskIndex).forEach(task => {
|
||||
if (task.type !== 'collect_items') return;
|
||||
if (task.status !== 'active') return;
|
||||
if (!task.targetItems.includes(itemType)) return;
|
||||
|
||||
// Check if item matches task criteria
|
||||
let matches = false;
|
||||
|
||||
// Type-based matching (targetItems array)
|
||||
if (task.targetItems && task.targetItems.length > 0) {
|
||||
matches = task.targetItems.includes(itemType);
|
||||
}
|
||||
|
||||
// ID-based matching (targetItemIds array) - more specific, overrides type matching
|
||||
if (task.targetItemIds && task.targetItemIds.length > 0) {
|
||||
// Match by ID if available, fall back to name
|
||||
const identifier = itemId || itemName;
|
||||
matches = task.targetItemIds.includes(identifier);
|
||||
}
|
||||
|
||||
// If both are specified, item must match at least one
|
||||
if (task.targetItems && task.targetItems.length > 0 &&
|
||||
task.targetItemIds && task.targetItemIds.length > 0) {
|
||||
const typeMatch = task.targetItems.includes(itemType);
|
||||
const identifier = itemId || itemName;
|
||||
const idMatch = task.targetItemIds.includes(identifier);
|
||||
matches = typeMatch || idMatch;
|
||||
}
|
||||
|
||||
if (!matches) return;
|
||||
|
||||
// Increment progress
|
||||
task.currentCount = (task.currentCount || 0) + 1;
|
||||
|
||||
@@ -21,12 +21,11 @@ Welcome to the lockpicking practice room. I'm here to teach you the fundamentals
|
||||
#give_item:lockpick
|
||||
#complete_task:talk_to_locksmith
|
||||
#unlock_task:pick_all_locks
|
||||
Now let me explain how to use it.
|
||||
- else:
|
||||
I see you already have a lockpick set. Let me give you a quick refresher on the basics.
|
||||
I see you already have a lockpick set.
|
||||
}
|
||||
|
||||
-> lockpicking_tutorial
|
||||
-> hub
|
||||
|
||||
// ===========================================
|
||||
// MAIN HUB
|
||||
@@ -36,18 +35,19 @@ Welcome to the lockpicking practice room. I'm here to teach you the fundamentals
|
||||
What would you like to know?
|
||||
|
||||
{not lockpicking_tutorial_given:
|
||||
* [Teach me about lockpicking]
|
||||
* [Can you teach me about lockpicking?]
|
||||
-> lockpicking_tutorial
|
||||
}
|
||||
|
||||
{not all_locks_picked:
|
||||
{lockpicking_tutorial_given and not all_locks_picked:
|
||||
+ [I'm working on picking the locks]
|
||||
You'll find five locked containers in this room. Each one contains a document fragment. Pick all five to complete the practice exercise.
|
||||
-> hub
|
||||
}
|
||||
|
||||
+ [That's all I need]
|
||||
-> end_conversation
|
||||
+ [That's all I need] #exit_conversation
|
||||
Good luck with your practice. Come back if you need any tips!
|
||||
-> hub
|
||||
|
||||
// ===========================================
|
||||
// LOCKPICKING TUTORIAL
|
||||
@@ -58,21 +58,13 @@ What would you like to know?
|
||||
|
||||
Lockpicking is a physical security skill that's essential for field operations. Here's how it works:
|
||||
|
||||
The basic principle: Most locks use pin tumblers. Each pin has two parts - a driver pin and a key pin. When the correct key is inserted, the pins align at the shear line, allowing the lock to turn.
|
||||
Most locks use pin tumblers. Each pin has two parts - a driver pin and a key pin. When the correct key is inserted, the pins align at the shear line, allowing the lock to turn.
|
||||
|
||||
When picking a lock, you need two tools:
|
||||
1. A tension wrench - applies rotational pressure to the lock cylinder
|
||||
2. A pick - manipulates the pins one by one
|
||||
When picking a lock, you need two tools: a tension wrench that applies rotational pressure to the lock cylinder, and a pick that manipulates the pins one by one.
|
||||
|
||||
The technique:
|
||||
- Apply light tension with the wrench in the direction the lock turns
|
||||
- Use the pick to push each pin up until you feel it "bind" (stop moving)
|
||||
- Pins bind in a specific order - work through them systematically
|
||||
- When all pins are set at the shear line, the lock will turn
|
||||
The technique involves applying light tension with the wrench in the direction the lock turns, then using the pick to push each pin up until you feel it "bind" (stop moving). Pins bind in a specific order, so you work through them systematically. When all pins are set at the shear line, the lock will turn.
|
||||
|
||||
Practice makes perfect. Start with the containers in this room - they have different difficulty levels.
|
||||
|
||||
Each container has a different lock configuration. Start with the easier ones and work your way up. When you've picked all five locks and collected all the documents, come back and I'll congratulate you on completing the practice.
|
||||
Practice makes perfect. Start with the containers in this room - they have different difficulty levels. Each container has a different lock configuration. Start with the easier ones and work your way up. When you've picked all five locks and collected all the documents, come back and I'll congratulate you on completing the practice.
|
||||
|
||||
Good luck!
|
||||
|
||||
@@ -88,25 +80,10 @@ Good luck!
|
||||
|
||||
Congratulations! You've successfully picked all five locks and recovered all the lost documents.
|
||||
|
||||
You've demonstrated:
|
||||
- Understanding of lock mechanics
|
||||
- Ability to apply proper tension
|
||||
- Skill in identifying binding order
|
||||
- Patience and precision
|
||||
|
||||
These skills will serve you well in the field. Lockpicking is often the difference between mission success and failure when you need access without leaving evidence of forced entry.
|
||||
You've demonstrated understanding of lock mechanics, ability to apply proper tension, skill in identifying binding order, and patience and precision. These skills will serve you well in the field. Lockpicking is often the difference between mission success and failure when you need access without leaving evidence of forced entry.
|
||||
|
||||
You're ready for real-world operations. Well done, Agent.
|
||||
|
||||
-> hub
|
||||
|
||||
// ===========================================
|
||||
// END CONVERSATION
|
||||
// ===========================================
|
||||
|
||||
=== end_conversation ===
|
||||
Good luck with your practice. Come back if you need any tips!
|
||||
|
||||
#exit_conversation
|
||||
-> hub
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["^Welcome to the lockpicking practice room. I'm here to teach you the fundamentals of lockpicking.","\n","ev",{"VAR?":"has_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Here's a professional lockpick set to get you started.","\n","#","^give_item:lockpick","/#","#","^complete_task:talk_to_locksmith","/#","#","^unlock_task:pick_all_locks","/#","^Now let me explain how to use it.","\n",{"->":"start.7"},null]}],[{"->":".^.b"},{"b":["\n","^I see you already have a lockpick set. Let me give you a quick refresher on the basics.","\n",{"->":"start.7"},null]}],"nop","\n",{"->":"lockpicking_tutorial"},null],"hub":[["^What would you like to know?","\n","ev",{"VAR?":"lockpicking_tutorial_given"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Teach me about lockpicking","/str","/ev",{"*":".^.c-0","flg":20},{"->":"hub.0.7"},{"c-0":["\n",{"->":"lockpicking_tutorial"},{"#f":5}]}]}],"nop","\n","ev",{"VAR?":"all_locks_picked"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'm working on picking the locks","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.14"},{"c-0":["\n","^You'll find five locked containers in this room. Each one contains a document fragment. Pick all five to complete the practice exercise.","\n",{"->":"hub"},null]}]}],"nop","\n","ev","str","^That's all I need","/str","/ev",{"*":".^.c-0","flg":4},{"c-0":["\n",{"->":"end_conversation"},null]}],null],"lockpicking_tutorial":[["ev",true,"/ev",{"VAR=":"lockpicking_tutorial_given","re":true},"^Lockpicking is a physical security skill that's essential for field operations. Here's how it works:","\n","^The basic principle: Most locks use pin tumblers. Each pin has two parts - a driver pin and a key pin. When the correct key is inserted, the pins align at the shear line, allowing the lock to turn.","\n","^When picking a lock, you need two tools:","\n","^1. A tension wrench - applies rotational pressure to the lock cylinder","\n","^2. A pick - manipulates the pins one by one","\n","^The technique:","\n",["^Apply light tension with the wrench in the direction the lock turns","\n",["^Use the pick to push each pin up until you feel it \"bind\" (stop moving)","\n",["^Pins bind in a specific order - work through them systematically","\n",["^When all pins are set at the shear line, the lock will turn","\n","^Practice makes perfect. Start with the containers in this room - they have different difficulty levels.","\n","^Each container has a different lock configuration. Start with the easier ones and work your way up. When you've picked all five locks and collected all the documents, come back and I'll congratulate you on completing the practice.","\n","^Good luck!","\n",{"->":"hub"},{"#n":"g-3"}],{"#n":"g-2"}],{"#n":"g-1"}],{"#n":"g-0"}],null],null],"lockpicking_complete":[["ev",true,"/ev",{"VAR=":"all_locks_picked","re":true},"^Congratulations! You've successfully picked all five locks and recovered all the lost documents.","\n","^You've demonstrated:","\n",["^Understanding of lock mechanics","\n",["^Ability to apply proper tension","\n",["^Skill in identifying binding order","\n",["^Patience and precision","\n","^These skills will serve you well in the field. Lockpicking is often the difference between mission success and failure when you need access without leaving evidence of forced entry.","\n","^You're ready for real-world operations. Well done, Agent.","\n",{"->":"hub"},{"#n":"g-3"}],{"#n":"g-2"}],{"#n":"g-1"}],{"#n":"g-0"}],null],null],"end_conversation":["^Good luck with your practice. Come back if you need any tips!","\n","#","^exit_conversation","/#",{"->":"hub"},null],"global decl":["ev",false,{"VAR=":"has_lockpick"},false,{"VAR=":"lockpicking_tutorial_given"},false,{"VAR=":"all_locks_picked"},"/ev","end",null]}],"listDefs":{}}
|
||||
{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["^Welcome to the lockpicking practice room. I'm here to teach you the fundamentals of lockpicking.","\n","ev",{"VAR?":"has_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Here's a professional lockpick set to get you started.","\n","#","^give_item:lockpick","/#","#","^complete_task:talk_to_locksmith","/#","#","^unlock_task:pick_all_locks","/#",{"->":"start.7"},null]}],[{"->":".^.b"},{"b":["\n","^I see you already have a lockpick set.","\n",{"->":"start.7"},null]}],"nop","\n",{"->":"hub"},null],"hub":[["^What would you like to know?","\n","ev",{"VAR?":"lockpicking_tutorial_given"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Can you teach me about lockpicking?","/str","/ev",{"*":".^.c-0","flg":20},{"->":"hub.0.7"},{"c-0":["\n",{"->":"lockpicking_tutorial"},{"#f":5}]}]}],"nop","\n","ev",{"VAR?":"lockpicking_tutorial_given"},{"VAR?":"all_locks_picked"},"!","&&","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^I'm working on picking the locks","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.16"},{"c-0":["\n","^You'll find five locked containers in this room. Each one contains a document fragment. Pick all five to complete the practice exercise.","\n",{"->":"hub"},null]}]}],"nop","\n","ev","str","^That's all I need","/str","/ev",{"*":".^.c-0","flg":4},{"c-0":["^ ","#","^exit_conversation","/#","\n","^Good luck with your practice. Come back if you need any tips!","\n",{"->":"hub"},null]}],null],"lockpicking_tutorial":["ev",true,"/ev",{"VAR=":"lockpicking_tutorial_given","re":true},"^Lockpicking is a physical security skill that's essential for field operations. Here's how it works:","\n","^Most locks use pin tumblers. Each pin has two parts - a driver pin and a key pin. When the correct key is inserted, the pins align at the shear line, allowing the lock to turn.","\n","^When picking a lock, you need two tools: a tension wrench that applies rotational pressure to the lock cylinder, and a pick that manipulates the pins one by one.","\n","^The technique involves applying light tension with the wrench in the direction the lock turns, then using the pick to push each pin up until you feel it \"bind\" (stop moving). Pins bind in a specific order, so you work through them systematically. When all pins are set at the shear line, the lock will turn.","\n","^Practice makes perfect. Start with the containers in this room - they have different difficulty levels. Each container has a different lock configuration. Start with the easier ones and work your way up. When you've picked all five locks and collected all the documents, come back and I'll congratulate you on completing the practice.","\n","^Good luck!","\n",{"->":"hub"},null],"lockpicking_complete":["ev",true,"/ev",{"VAR=":"all_locks_picked","re":true},"^Congratulations! You've successfully picked all five locks and recovered all the lost documents.","\n","^You've demonstrated understanding of lock mechanics, ability to apply proper tension, skill in identifying binding order, and patience and precision. These skills will serve you well in the field. Lockpicking is often the difference between mission success and failure when you need access without leaving evidence of forced entry.","\n","^You're ready for real-world operations. Well done, Agent.","\n",{"->":"hub"},null],"global decl":["ev",false,{"VAR=":"has_lockpick"},false,{"VAR=":"lockpicking_tutorial_given"},false,{"VAR=":"all_locks_picked"},"/ev","end",null]}],"listDefs":{}}
|
||||
@@ -59,7 +59,7 @@
|
||||
"taskId": "pick_all_locks",
|
||||
"title": "Pick locks to retrieve lost documents",
|
||||
"type": "collect_items",
|
||||
"targetItems": ["notes"],
|
||||
"targetItemIds": ["document_fragment_1", "document_fragment_2", "document_fragment_3", "document_fragment_4", "document_fragment_5"],
|
||||
"targetCount": 5,
|
||||
"currentCount": 0,
|
||||
"showProgress": true,
|
||||
@@ -260,6 +260,7 @@
|
||||
"contents": [
|
||||
{
|
||||
"type": "notes",
|
||||
"id": "document_fragment_1",
|
||||
"name": "Document Fragment 1",
|
||||
"takeable": true,
|
||||
"readable": true,
|
||||
@@ -282,6 +283,7 @@
|
||||
"contents": [
|
||||
{
|
||||
"type": "notes",
|
||||
"id": "document_fragment_2",
|
||||
"name": "Document Fragment 2",
|
||||
"takeable": true,
|
||||
"readable": true,
|
||||
@@ -304,6 +306,7 @@
|
||||
"contents": [
|
||||
{
|
||||
"type": "notes",
|
||||
"id": "document_fragment_3",
|
||||
"name": "Document Fragment 3",
|
||||
"takeable": true,
|
||||
"readable": true,
|
||||
@@ -326,6 +329,7 @@
|
||||
"contents": [
|
||||
{
|
||||
"type": "notes",
|
||||
"id": "document_fragment_4",
|
||||
"name": "Document Fragment 4",
|
||||
"takeable": true,
|
||||
"readable": true,
|
||||
@@ -348,6 +352,7 @@
|
||||
"contents": [
|
||||
{
|
||||
"type": "notes",
|
||||
"id": "document_fragment_5",
|
||||
"name": "Document Fragment 5",
|
||||
"takeable": true,
|
||||
"readable": true,
|
||||
|
||||
Reference in New Issue
Block a user