mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
Update scenario.json.erb: Change NPC sprite and add closing debrief triggers
- Updated spriteSheet for Sarah Martinez from "female_office_worker" to "female_blowse". - Added new event triggers for closing debrief upon entering the main office area and confronting Derek. - Modified Agent 0x99 to use a person type NPC with updated event mappings and properties.
This commit is contained in:
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"cursor.general.disableHttp2": true,
|
||||
"chat.agent.maxRequests": 100
|
||||
"chat.agent.maxRequests": 100,
|
||||
"chat.tools.terminal.autoApprove": {
|
||||
"bin/inklecate": true
|
||||
}
|
||||
}
|
||||
@@ -1134,7 +1134,7 @@ module BreakEscape
|
||||
|
||||
stdout, stderr, status = Open3.capture3(
|
||||
inklecate_path.to_s,
|
||||
'-o', output_path,
|
||||
'-jo', output_path,
|
||||
ink_path.to_s
|
||||
)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 957 B After Width: | Height: | Size: 957 B |
@@ -440,9 +440,9 @@ export function preload() {
|
||||
this.load.atlas('female_scientist',
|
||||
'characters/female_scientist.png',
|
||||
'characters/female_scientist.json');
|
||||
this.load.atlas('woman_blowse',
|
||||
'characters/woman_blowse.png',
|
||||
'characters/woman_blowse.json');
|
||||
this.load.atlas('female_blowse',
|
||||
'characters/female_blowse.png',
|
||||
'characters/female_blowse.json');
|
||||
|
||||
// Male characters
|
||||
this.load.atlas('male_hacker_hood',
|
||||
|
||||
@@ -246,6 +246,41 @@ export function processGameActionTags(tags, ui) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'transition_to_person_chat':
|
||||
{
|
||||
// Format: transition_to_person_chat:npcId|background|knot
|
||||
// Example: # transition_to_person_chat:closing_debrief_trigger|assets/backgrounds/hq1.png|start
|
||||
const [targetNpcId, background, targetKnot] = param ? param.split('|').map(s => s.trim()) : [];
|
||||
|
||||
if (!targetNpcId) {
|
||||
result.message = '⚠️ transition_to_person_chat requires npcId parameter';
|
||||
console.warn(result.message);
|
||||
break;
|
||||
}
|
||||
|
||||
console.log('🔄 Transitioning to person-chat:', { targetNpcId, background, targetKnot });
|
||||
|
||||
// Close current phone-chat minigame
|
||||
if (window.MinigameFramework && window.MinigameFramework.currentMinigame) {
|
||||
window.MinigameFramework.currentMinigame.complete(false);
|
||||
}
|
||||
|
||||
// Small delay before starting person-chat
|
||||
setTimeout(() => {
|
||||
if (window.MinigameFramework) {
|
||||
window.MinigameFramework.startMinigame('person-chat', {
|
||||
npcId: targetNpcId,
|
||||
background: background || null,
|
||||
startKnot: targetKnot || null
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
result.success = true;
|
||||
result.message = `🔄 Transitioning to person-chat with ${targetNpcId}`;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'clone_keycard':
|
||||
// Parameter is the card_id to clone
|
||||
// Look up card data from NPC's rfidCard property
|
||||
|
||||
@@ -365,8 +365,31 @@ export class PhoneChatMinigame extends MinigameScene {
|
||||
// Load conversation history
|
||||
const history = this.history.loadHistory();
|
||||
|
||||
// Filter out bark-only messages to check if there's real conversation history
|
||||
const conversationHistory = history.filter(msg => !msg.metadata?.isBark);
|
||||
// Determine target knot (needed before clearing history)
|
||||
const safeParams = this.params || {};
|
||||
const explicitStartKnot = safeParams.startKnot;
|
||||
const targetKnot = explicitStartKnot || npc.currentKnot || 'start';
|
||||
|
||||
// If navigating to a new knot explicitly (e.g., from timed message),
|
||||
// clear the non-timed history to avoid showing old messages from previous visits to this knot
|
||||
if (explicitStartKnot && history.length > 0) {
|
||||
console.log(`🧹 Explicit knot navigation detected - clearing old conversation messages (keeping timed/bark notifications)`);
|
||||
console.log('📝 History before filtering:', history.map(m => ({ text: m.text.substring(0, 40), timed: m.timed, isBark: m.isBark })));
|
||||
// Keep only timed messages and barks (notifications), remove old Ink dialogue
|
||||
// Note: metadata is spread directly onto message object, not nested
|
||||
const filteredHistory = history.filter(msg => msg.isBark || msg.timed);
|
||||
console.log('📝 History after filtering:', filteredHistory.map(m => ({ text: m.text.substring(0, 40), timed: m.timed, isBark: m.isBark })));
|
||||
|
||||
// Update NPCManager's conversation history directly
|
||||
this.npcManager.conversationHistory.set(this.npcId, filteredHistory);
|
||||
|
||||
// Update what we'll display
|
||||
history.splice(0, history.length, ...filteredHistory);
|
||||
}
|
||||
|
||||
// Filter out bark-only and timed messages to check if there's real conversation history
|
||||
// (timed messages are just notifications, not actual Ink dialogue)
|
||||
const conversationHistory = history.filter(msg => !msg.isBark && !msg.timed);
|
||||
const hasConversationHistory = conversationHistory.length > 0;
|
||||
|
||||
// Show all history (including barks) in the UI
|
||||
@@ -377,11 +400,12 @@ export class PhoneChatMinigame extends MinigameScene {
|
||||
}
|
||||
|
||||
// Load and start Ink story
|
||||
// Support both storyJSON (inline) and storyPath (file)
|
||||
let storySource = npc.storyJSON || npc.inkStoryPath;
|
||||
// Prefer Rails API endpoint if storyPath exists (ensures fresh story after path changes)
|
||||
console.log(`📱 openConversation - npc.storyJSON exists: ${!!npc.storyJSON}, npc.storyPath: ${npc.storyPath}, npc.inkStoryPath: ${npc.inkStoryPath}`);
|
||||
let storySource = null;
|
||||
|
||||
// If no storyJSON but storyPath exists, use Rails API endpoint
|
||||
if (!storySource && npc.storyPath) {
|
||||
// If storyPath exists, use Rails API endpoint (ensures fresh load after story path changes)
|
||||
if (npc.storyPath) {
|
||||
const gameId = window.breakEscapeConfig?.gameId;
|
||||
if (gameId) {
|
||||
storySource = `/break_escape/games/${gameId}/ink?npc=${npcId}`;
|
||||
@@ -389,6 +413,11 @@ export class PhoneChatMinigame extends MinigameScene {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to storyJSON or inkStoryPath
|
||||
if (!storySource) {
|
||||
storySource = npc.storyJSON || npc.inkStoryPath;
|
||||
}
|
||||
|
||||
if (!storySource) {
|
||||
console.error(`❌ No story source found for ${npcId}`);
|
||||
this.ui.showNotification('No conversation available', 'error');
|
||||
@@ -405,20 +434,25 @@ export class PhoneChatMinigame extends MinigameScene {
|
||||
this.isConversationActive = true;
|
||||
|
||||
// Check if we have saved story state to restore
|
||||
if (hasConversationHistory && npc.storyState) {
|
||||
// Restore previous story state
|
||||
// BUT: if startKnot was explicitly provided (e.g., from timed message),
|
||||
// navigate to that knot instead of restoring old state
|
||||
if (hasConversationHistory && npc.storyState && !explicitStartKnot) {
|
||||
// Restore previous story state (only if no explicit knot override)
|
||||
console.log('📚 Restoring story state from previous conversation');
|
||||
this.conversation.restoreState(npc.storyState);
|
||||
|
||||
// Show current choices without continuing
|
||||
this.showCurrentChoices();
|
||||
} else {
|
||||
// Navigate to starting knot for first time
|
||||
const safeParams = this.params || {};
|
||||
const startKnot = safeParams.startKnot || npc.currentKnot || 'start';
|
||||
this.conversation.goToKnot(startKnot);
|
||||
// Navigate to starting knot (either first time, or explicit navigation request)
|
||||
if (explicitStartKnot) {
|
||||
console.log(`📱 Explicit navigation to knot: ${explicitStartKnot} (overriding saved state)`);
|
||||
} else {
|
||||
console.log(`📱 Navigating to knot: ${targetKnot}`);
|
||||
}
|
||||
this.conversation.goToKnot(targetKnot);
|
||||
|
||||
// First time opening - show intro message and choices
|
||||
// Continue story to get fresh content and choices
|
||||
this.continueStory();
|
||||
}
|
||||
}
|
||||
@@ -470,6 +504,9 @@ export class PhoneChatMinigame extends MinigameScene {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🎬 continueStory() called');
|
||||
console.trace('Call stack'); // This will show us where continueStory is being called from
|
||||
|
||||
// Show typing indicator briefly
|
||||
this.ui.showTypingIndicator();
|
||||
|
||||
@@ -530,6 +567,7 @@ export class PhoneChatMinigame extends MinigameScene {
|
||||
|
||||
console.log('📖 Accumulated messages:', accumulatedMessages.length);
|
||||
console.log('🏷️ Accumulated tags:', accumulatedTags);
|
||||
console.log('📝 Messages detail:', accumulatedMessages);
|
||||
|
||||
// If story has ended
|
||||
if (lastResult.hasEnded && accumulatedMessages.length === 0) {
|
||||
|
||||
@@ -317,7 +317,15 @@ export default class PhoneChatUI {
|
||||
// Filter to only allowed NPCs if npcIds was specified
|
||||
if (this.allowedNpcIds && this.allowedNpcIds.length > 0) {
|
||||
console.log(`🔍 Filtering contacts: allowed NPCs = ${this.allowedNpcIds.join(', ')}`);
|
||||
npcs = npcs.filter(npc => this.allowedNpcIds.includes(npc.id));
|
||||
npcs = npcs.filter(npc => {
|
||||
// Include if in allowed list
|
||||
if (this.allowedNpcIds.includes(npc.id)) {
|
||||
return true;
|
||||
}
|
||||
// Include if has conversation history (i.e., has been activated by events)
|
||||
const history = this.npcManager.getConversationHistory(npc.id);
|
||||
return history && history.length > 0;
|
||||
});
|
||||
console.log(`✅ Filtered to ${npcs.length} contacts`);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export default class NPCManager {
|
||||
this.eventListeners = new Map(); // Track registered listeners for cleanup
|
||||
this.triggeredEvents = new Map(); // Track which events have been triggered per NPC
|
||||
this.conversationHistory = new Map(); // Track conversation history per NPC: { npcId: [ {type, text, timestamp, choiceText} ] }
|
||||
this.timedMessages = []; // Scheduled messages: { npcId, text, triggerTime, delivered, phoneId }
|
||||
this.timedMessages = []; // Scheduled messages: { npcId, text, triggerTime, delivered, phoneId, targetKnot }
|
||||
this.timedConversations = []; // Scheduled conversations: { npcId, targetKnot, triggerTime, delivered }
|
||||
this.gameStartTime = Date.now(); // Track when game started for timed messages
|
||||
this.timerInterval = null; // Timer for checking timed messages
|
||||
@@ -315,7 +315,9 @@ export default class NPCManager {
|
||||
cooldown: mapping.cooldown,
|
||||
condition: mapping.condition,
|
||||
maxTriggers: mapping.maxTriggers, // Add max trigger limit
|
||||
conversationMode: mapping.conversationMode // Add conversation mode (e.g., 'person-chat')
|
||||
conversationMode: mapping.conversationMode, // Add conversation mode (e.g., 'person-chat')
|
||||
changeStoryPath: mapping.changeStoryPath, // Change the NPC's story file
|
||||
sendTimedMessage: mapping.sendTimedMessage // Send a timed message when event triggers
|
||||
};
|
||||
|
||||
console.log(` 📌 Registering listener for event: ${eventPattern} → ${config.knot}`);
|
||||
@@ -403,10 +405,41 @@ export default class NPCManager {
|
||||
triggered.lastTime = now;
|
||||
this.triggeredEvents.set(eventKey, triggered);
|
||||
|
||||
// Update NPC's current knot if specified
|
||||
if (config.knot) {
|
||||
npc.currentKnot = config.knot;
|
||||
console.log(`📍 Updated ${npcId} current knot to: ${config.knot}`);
|
||||
// Update NPC's current knot if specified (use targetKnot or knot for backwards compatibility)
|
||||
const knotToSet = config.targetKnot || config.knot;
|
||||
if (knotToSet) {
|
||||
npc.currentKnot = knotToSet;
|
||||
console.log(`📍 Updated ${npcId} current knot to: ${knotToSet}`);
|
||||
}
|
||||
|
||||
// Change NPC's story path if specified (switches conversation to different Ink file)
|
||||
if (config.changeStoryPath) {
|
||||
console.log(`📖 BEFORE changeStoryPath - npc.storyPath: ${npc.storyPath}, npc.storyJSON exists: ${!!npc.storyJSON}`);
|
||||
npc.storyPath = config.changeStoryPath;
|
||||
// Clear cached story state so new story loads fresh
|
||||
delete npc.storyState;
|
||||
delete npc.storyJSON;
|
||||
// Clear cached InkEngine so it reloads with new story
|
||||
if (this.inkEngineCache.has(npcId)) {
|
||||
this.inkEngineCache.delete(npcId);
|
||||
}
|
||||
// Clear ALL conversation history (new timed message will be added fresh)
|
||||
this.conversationHistory.set(npcId, []);
|
||||
console.log(`📖 AFTER changeStoryPath - npc.storyPath: ${npc.storyPath}, npc.storyJSON exists: ${!!npc.storyJSON}`);
|
||||
console.log(`📖 Changed ${npcId} story path to: ${config.changeStoryPath} (cleared all caches and history)`);
|
||||
}
|
||||
|
||||
// Send timed message if specified
|
||||
if (config.sendTimedMessage) {
|
||||
const msgConfig = config.sendTimedMessage;
|
||||
this.scheduleTimedMessage({
|
||||
npcId: npcId,
|
||||
text: msgConfig.message,
|
||||
delay: msgConfig.delay || 0,
|
||||
phoneId: npc.phoneId,
|
||||
targetKnot: msgConfig.targetKnot || null
|
||||
});
|
||||
console.log(`📨 Scheduled timed message for ${npcId}: "${msgConfig.message}" (delay: ${msgConfig.delay}ms, targetKnot: ${msgConfig.targetKnot || 'default'})`);
|
||||
}
|
||||
|
||||
// Debug: Log the full config to see what we're working with
|
||||
@@ -473,9 +506,11 @@ export default class NPCManager {
|
||||
// Start the person-chat minigame
|
||||
if (window.MinigameFramework) {
|
||||
console.log(`✅ Starting person-chat minigame for ${npcId}`);
|
||||
const knotToUse = config.targetKnot || config.knot || npc.currentKnot;
|
||||
window.MinigameFramework.startMinigame('person-chat', null, {
|
||||
npcId: npc.id,
|
||||
startKnot: config.knot || npc.currentKnot,
|
||||
startKnot: knotToUse,
|
||||
background: config.background || null,
|
||||
scenario: window.gameScenario
|
||||
});
|
||||
console.log(`[NPCManager] Event '${eventPattern}' triggered for NPC '${npcId}' → person-chat conversation`);
|
||||
@@ -602,7 +637,7 @@ export default class NPCManager {
|
||||
// Schedule a timed message to be delivered after a delay
|
||||
// opts: { npcId, text, triggerTime (ms from game start) OR delay (ms from now), phoneId }
|
||||
scheduleTimedMessage(opts) {
|
||||
const { npcId, text, triggerTime, delay, phoneId } = opts;
|
||||
const { npcId, text, triggerTime, delay, phoneId, targetKnot } = opts;
|
||||
|
||||
if (!npcId || !text) {
|
||||
console.error('[NPCManager] scheduleTimedMessage requires npcId and text');
|
||||
@@ -617,6 +652,7 @@ export default class NPCManager {
|
||||
text,
|
||||
triggerTime: actualTriggerTime, // milliseconds from game start
|
||||
phoneId: phoneId || 'player_phone',
|
||||
targetKnot: targetKnot || null,
|
||||
delivered: false
|
||||
});
|
||||
|
||||
@@ -722,7 +758,7 @@ export default class NPCManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add message to conversation history
|
||||
// Add message to conversation history (represents the incoming mobile chat message)
|
||||
this.addMessage(message.npcId, 'npc', message.text, {
|
||||
timed: true,
|
||||
phoneId: message.phoneId
|
||||
@@ -741,7 +777,7 @@ export default class NPCManager {
|
||||
message: message.text,
|
||||
avatar: npc.avatar,
|
||||
inkStoryPath: npc.storyPath,
|
||||
startKnot: npc.currentKnot,
|
||||
startKnot: message.targetKnot || npc.currentKnot,
|
||||
phoneId: message.phoneId
|
||||
});
|
||||
}
|
||||
@@ -749,7 +785,7 @@ export default class NPCManager {
|
||||
console.log(`[NPCManager] Delivered timed message from ${message.npcId}:`, message.text);
|
||||
}
|
||||
|
||||
// Deliver a timed conversation (start person-chat minigame at specified knot)
|
||||
// Deliver a timed conversation (start person-chat or phone-chat minigame at specified knot)
|
||||
_deliverTimedConversation(conversation) {
|
||||
const npc = this.getNPC(conversation.npcId);
|
||||
if (!npc) {
|
||||
@@ -760,17 +796,28 @@ export default class NPCManager {
|
||||
// Update NPC's current knot to the target knot
|
||||
npc.currentKnot = conversation.targetKnot;
|
||||
|
||||
// Check if MinigameFramework is available to start the person-chat minigame
|
||||
// Check if MinigameFramework is available to start the appropriate minigame
|
||||
if (window.MinigameFramework && typeof window.MinigameFramework.startMinigame === 'function') {
|
||||
console.log(`🎭 Starting timed conversation for ${conversation.npcId} at knot: ${conversation.targetKnot}`);
|
||||
|
||||
window.MinigameFramework.startMinigame('person-chat', null, {
|
||||
npcId: conversation.npcId,
|
||||
title: npc.displayName || conversation.npcId,
|
||||
background: conversation.background // Optional background image path
|
||||
});
|
||||
// Determine which minigame type to start based on NPC type
|
||||
if (npc.npcType === 'phone') {
|
||||
console.log(`📱 Starting timed phone conversation for ${conversation.npcId} at knot: ${conversation.targetKnot}`);
|
||||
|
||||
window.MinigameFramework.startMinigame('phone-chat', null, {
|
||||
npcId: conversation.npcId,
|
||||
phoneId: npc.phoneId || 'player_phone',
|
||||
title: 'Phone'
|
||||
});
|
||||
} else {
|
||||
console.log(`🎭 Starting timed person conversation for ${conversation.npcId} at knot: ${conversation.targetKnot}`);
|
||||
|
||||
window.MinigameFramework.startMinigame('person-chat', null, {
|
||||
npcId: conversation.npcId,
|
||||
title: npc.displayName || conversation.npcId,
|
||||
background: conversation.background // Optional background image path
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn(`[NPCManager] MinigameFramework not available to start person-chat for timed conversation`);
|
||||
console.warn(`[NPCManager] MinigameFramework not available to start conversation for timed conversation`);
|
||||
}
|
||||
|
||||
console.log(`[NPCManager] Delivered timed conversation from ${conversation.npcId} to knot: ${conversation.targetKnot}`);
|
||||
|
||||
@@ -30,20 +30,7 @@ VAR audit_wrong_answers = 0 // Number of incorrect assessments
|
||||
// ================================================
|
||||
|
||||
=== start ===
|
||||
#speaker:agent_0x99
|
||||
|
||||
Agent 0x99: {player_name}, return to HQ for debrief.
|
||||
|
||||
Agent 0x99: Operation Shatter is neutralized. Let's review what happened.
|
||||
|
||||
+ [On my way]
|
||||
-> debrief_location
|
||||
|
||||
// ================================================
|
||||
// DEBRIEF LOCATION
|
||||
// ================================================
|
||||
|
||||
=== debrief_location ===
|
||||
[SAFETYNET HQ - Agent 0x99's Office]
|
||||
|
||||
#speaker:agent_0x99
|
||||
|
||||
@@ -15,8 +15,22 @@ VAR operation_shatter_reported = false
|
||||
VAR player_name = "Agent 0x00"
|
||||
VAR current_task = ""
|
||||
VAR talked_to_maya = false
|
||||
VAR talked_to_kevin = false
|
||||
VAR discussed_operation = false
|
||||
|
||||
// Closing debrief variables
|
||||
VAR final_choice = ""
|
||||
VAR objectives_completed = 0
|
||||
VAR lore_collected = 0
|
||||
VAR found_casualty_projections = false
|
||||
VAR found_target_database = false
|
||||
VAR maya_identity_protected = true
|
||||
VAR kevin_choice = ""
|
||||
VAR kevin_protected = false
|
||||
VAR security_audit_completed = false
|
||||
VAR audit_correct_answers = 0
|
||||
VAR audit_wrong_answers = 0
|
||||
|
||||
// ================================================
|
||||
// START: PHONE SUPPORT
|
||||
// ================================================
|
||||
@@ -485,3 +499,20 @@ Agent 0x99: Confrontation, silent extraction, or public exposure. Each has conse
|
||||
Agent 0x99: Good luck, {player_name}. You've got this.
|
||||
#exit_conversation
|
||||
-> support_hub
|
||||
|
||||
// ================================================
|
||||
// CLOSING DEBRIEF - Mission Complete
|
||||
// ================================================
|
||||
|
||||
=== closing_debrief ===
|
||||
#speaker:agent_0x99
|
||||
|
||||
Agent 0x99: Operation Shatter is neutralized. Let's review what happened.
|
||||
|
||||
+ [On my way]
|
||||
#set_global:start_debrief_cutscene:true
|
||||
#exit_conversation
|
||||
-> END
|
||||
|
||||
#exit_conversation
|
||||
-> END
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -285,7 +285,7 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A
|
||||
"displayName": "Sarah Martinez",
|
||||
"npcType": "person",
|
||||
"position": { "x": 4, "y": 1.5 },
|
||||
"spriteSheet": "female_office_worker",
|
||||
"spriteSheet": "female_blowse",
|
||||
"spriteTalk": "assets/characters/hacker-red-talk.png",
|
||||
"spriteConfig": {
|
||||
"idleFrameRate": 2,
|
||||
@@ -352,25 +352,56 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A
|
||||
"eventPattern": "item_picked_up:contingency_files",
|
||||
"targetKnot": "event_contingency_found",
|
||||
"onceOnly": true
|
||||
},
|
||||
{
|
||||
"eventPattern": "room_entered:main_office_area",
|
||||
"targetKnot": "closing_debrief",
|
||||
"onceOnly": true,
|
||||
"sendTimedMessage": {
|
||||
"delay": 1000,
|
||||
"message": "Mission complete. Return to HQ for debrief.",
|
||||
"targetKnot": "closing_debrief"
|
||||
}
|
||||
},
|
||||
{
|
||||
"eventPattern": "global_variable_changed:derek_confronted",
|
||||
"targetKnot": "closing_debrief",
|
||||
"condition": "value === true",
|
||||
"onceOnly": true,
|
||||
"sendTimedMessage": {
|
||||
"delay": 1000,
|
||||
"message": "Mission complete. Return to HQ for debrief.",
|
||||
"targetKnot": "closing_debrief"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "closing_debrief_trigger",
|
||||
"id": "closing_debrief_person",
|
||||
"displayName": "Agent 0x99",
|
||||
"npcType": "phone",
|
||||
"storyPath": "scenarios/m01_first_contact/ink/m01_closing_debrief.json",
|
||||
"avatar": "assets/characters/female_spy_headshot.png",
|
||||
"phoneId": "player_phone",
|
||||
"currentKnot": "start",
|
||||
"eventMappings": [
|
||||
"npcType": "person",
|
||||
"eventMapping": [
|
||||
{
|
||||
"eventPattern": "global_variable_changed:derek_confronted",
|
||||
"targetKnot": "start",
|
||||
"eventPattern": "global_variable_changed:start_debrief_cutscene",
|
||||
"condition": "value === true",
|
||||
"conversationMode": "person-chat",
|
||||
"targetKnot": "debrief_location",
|
||||
"background": "assets/backgrounds/hq1.png",
|
||||
"onceOnly": true
|
||||
}
|
||||
]
|
||||
],
|
||||
"position": { "x": 500, "y": 500 },
|
||||
"spriteSheet": "female_spy",
|
||||
"avatar": "assets/characters/female_spy_headshot.png",
|
||||
"spriteConfig": {
|
||||
"idleFrameRate": 6,
|
||||
"walkFrameRate": 10
|
||||
},
|
||||
"storyPath": "scenarios/m01_first_contact/ink/m01_phone_agent0x99.json",
|
||||
"currentKnot": "debrief_location",
|
||||
"behavior": {
|
||||
"initiallyHidden": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"objects": [
|
||||
|
||||
Reference in New Issue
Block a user