mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
- Created a generic NPC script with conversation handling. - Developed an Alice NPC script demonstrating branching dialogue and state tracking. - Implemented a test NPC script for development purposes. - Added JSON representations for the NPC scripts. - Created an HTML test interface for NPC integration testing. - Included event handling and bark systems for NPC interactions.
651 lines
25 KiB
HTML
651 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>NPC Ink Integration Test</title>
|
|
<link rel="stylesheet" href="css/main.css">
|
|
<link rel="stylesheet" href="css/npc-barks.css">
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
padding: 20px;
|
|
background: #2a2a2a;
|
|
color: #fff;
|
|
font-family: 'VT323', monospace;
|
|
}
|
|
|
|
#test-container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
background: #1a1a1a;
|
|
border: 2px solid #444;
|
|
padding: 20px;
|
|
}
|
|
|
|
h1 {
|
|
color: #4a9eff;
|
|
margin-top: 0;
|
|
}
|
|
|
|
.test-section {
|
|
margin: 20px 0;
|
|
padding: 15px;
|
|
background: #2a2a2a;
|
|
border: 2px solid #666;
|
|
}
|
|
|
|
.test-section h2 {
|
|
color: #6acc6a;
|
|
margin-top: 0;
|
|
}
|
|
|
|
button {
|
|
background: #4a9eff;
|
|
color: #fff;
|
|
border: 2px solid #fff;
|
|
padding: 10px 20px;
|
|
font-family: 'VT323', monospace;
|
|
font-size: 18px;
|
|
cursor: pointer;
|
|
margin: 5px;
|
|
}
|
|
|
|
button:hover {
|
|
background: #6abd6a;
|
|
}
|
|
|
|
button:active {
|
|
background: #2a7adf;
|
|
}
|
|
|
|
#output {
|
|
background: #000;
|
|
color: #0f0;
|
|
padding: 10px;
|
|
font-family: monospace;
|
|
min-height: 100px;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
border: 2px solid #0f0;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.success {
|
|
color: #0f0;
|
|
}
|
|
|
|
.error {
|
|
color: #f00;
|
|
}
|
|
|
|
.info {
|
|
color: #4a9eff;
|
|
}
|
|
|
|
#story-output {
|
|
background: #000;
|
|
color: #fff;
|
|
padding: 15px;
|
|
border: 2px solid #666;
|
|
min-height: 150px;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.choice-button {
|
|
display: block;
|
|
margin: 5px 0;
|
|
width: 100%;
|
|
text-align: left;
|
|
}
|
|
|
|
.story-text {
|
|
margin: 10px 0;
|
|
line-height: 1.5;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="test-container">
|
|
<h1>🧪 NPC Ink Integration Test</h1>
|
|
|
|
<div class="test-section">
|
|
<h2>1. Library Check</h2>
|
|
<button onclick="testInkLibrary()">Test ink.js Library</button>
|
|
<div id="library-status"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>2. InkEngine Test</h2>
|
|
<button onclick="testInkEngine()">Load Test Story</button>
|
|
<button onclick="testContinueStory()">Continue Story</button>
|
|
<button onclick="testGoToKnot()">Go to Knot: hub</button>
|
|
<button onclick="testVariables()">Test Variables</button>
|
|
<div id="engine-status"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>3. Story Display</h2>
|
|
<div id="story-output"></div>
|
|
<div id="choices-container"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>4. Event System Test</h2>
|
|
<button onclick="testEventEmit()">Emit Test Event</button>
|
|
<button onclick="testEventCooldown()">Test Cooldown</button>
|
|
<button onclick="testEventPattern()">Test Pattern Matching</button>
|
|
<div id="event-status"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>5. Bark System Test</h2>
|
|
<button onclick="testBark()">Show Test Bark</button>
|
|
<button onclick="testMultipleBarks()">Show 3 Barks</button>
|
|
<button onclick="testBarkClick()">Show Clickable Bark</button>
|
|
<button onclick="testBarkPhoneOpen()">Test Bark → Phone Chat</button>
|
|
<div id="bark-status"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>6. NPC Manager Test</h2>
|
|
<button onclick="testNPCRegistration()">Register Test NPC</button>
|
|
<button onclick="testNPCEvent()">Trigger NPC Event</button>
|
|
<button onclick="testAutoTrigger()">Test Auto-Trigger (Event Mapping)</button>
|
|
<div id="npc-status"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>📊 Console Output</h2>
|
|
<div id="output"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Load ink.js -->
|
|
<script src="assets/vendor/ink.js"></script>
|
|
|
|
<script>
|
|
// Define logging function first
|
|
function log(message, type = 'info') {
|
|
const output = document.getElementById('output');
|
|
const line = document.createElement('div');
|
|
line.className = type;
|
|
line.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
|
output.appendChild(line);
|
|
output.scrollTop = output.scrollHeight;
|
|
console.log(message);
|
|
}
|
|
|
|
function updateStatus(elementId, message, isSuccess = true) {
|
|
const el = document.getElementById(elementId);
|
|
el.innerHTML = `<p class="${isSuccess ? 'success' : 'error'}">${message}</p>`;
|
|
}
|
|
</script>
|
|
|
|
<!-- Load NPC systems -->
|
|
<script type="module">
|
|
import InkEngine from './js/systems/ink/ink-engine.js';
|
|
import NPCEventDispatcher from './js/systems/npc-events.js';
|
|
import NPCManager from './js/systems/npc-manager.js';
|
|
import NPCBarkSystem from './js/systems/npc-barks.js';
|
|
|
|
// Make modules globally available
|
|
window.InkEngine = InkEngine;
|
|
window.NPCEventDispatcher = NPCEventDispatcher;
|
|
window.NPCManager = NPCManager;
|
|
window.NPCBarkSystem = NPCBarkSystem;
|
|
|
|
// Initialize systems
|
|
window.npcEvents = new NPCEventDispatcher({ debug: true });
|
|
window.npcBarkSystem = new NPCBarkSystem(null);
|
|
window.npcManager = new NPCManager(window.npcEvents, window.npcBarkSystem);
|
|
window.npcBarkSystem.npcManager = window.npcManager; // Link bark system to manager
|
|
window.npcBarkSystem.init();
|
|
window.npcBarkSystem.init();
|
|
|
|
// Test engine instance
|
|
window.testEngine = null;
|
|
|
|
// Mark as initialized
|
|
window.npcSystemsReady = true;
|
|
|
|
// Log initialization (use setTimeout to ensure log() is available)
|
|
setTimeout(() => {
|
|
if (typeof log === 'function') {
|
|
log('✅ Systems initialized', 'success');
|
|
}
|
|
console.log('✅ NPC Systems loaded and initialized');
|
|
}, 0);
|
|
</script>
|
|
|
|
<script>
|
|
|
|
// Test 1: ink.js library
|
|
function testInkLibrary() {
|
|
try {
|
|
if (typeof inkjs === 'undefined') {
|
|
throw new Error('ink.js library not loaded');
|
|
}
|
|
updateStatus('library-status', '✅ ink.js library loaded successfully');
|
|
log('✅ ink.js library available', 'success');
|
|
log(`ink.js Story constructor: ${typeof inkjs.Story}`, 'info');
|
|
} catch (error) {
|
|
updateStatus('library-status', `❌ ${error.message}`, false);
|
|
log(`❌ ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// Test 2: Load Ink story
|
|
async function testInkEngine() {
|
|
try {
|
|
log('Loading test story...', 'info');
|
|
const response = await fetch('scenarios/compiled/test2.json');
|
|
const storyJson = await response.json();
|
|
|
|
window.testEngine = new window.InkEngine('test-npc');
|
|
window.testEngine.loadStory(storyJson);
|
|
|
|
// Navigate to 'start' knot (test story root immediately exits)
|
|
window.testEngine.goToKnot('start');
|
|
|
|
updateStatus('engine-status', '✅ Story loaded successfully');
|
|
log('✅ Story loaded and initialized', 'success');
|
|
log(`Current text: ${window.testEngine.currentText}`, 'info');
|
|
|
|
// Display initial story
|
|
displayStory();
|
|
} catch (error) {
|
|
updateStatus('engine-status', `❌ ${error.message}`, false);
|
|
log(`❌ Error loading story: ${error.message}`, 'error');
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
function testContinueStory() {
|
|
if (!window.testEngine) {
|
|
log('❌ No story loaded. Load story first.', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
window.testEngine.continue();
|
|
log('✅ Story continued', 'success');
|
|
displayStory();
|
|
} catch (error) {
|
|
log(`❌ Error continuing story: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
function testGoToKnot() {
|
|
if (!window.testEngine) {
|
|
log('❌ No story loaded. Load story first.', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
window.testEngine.goToKnot('hub');
|
|
log('✅ Navigated to knot: hub', 'success');
|
|
displayStory();
|
|
} catch (error) {
|
|
log(`❌ Error going to knot: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
function testVariables() {
|
|
if (!window.testEngine) {
|
|
log('❌ No story loaded. Load story first.', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const counter = window.testEngine.getVariable('test_counter');
|
|
log(`Current test_counter: ${counter}`, 'info');
|
|
|
|
window.testEngine.setVariable('test_counter', counter + 1);
|
|
const newCounter = window.testEngine.getVariable('test_counter');
|
|
log(`Updated test_counter: ${newCounter}`, 'success');
|
|
|
|
updateStatus('engine-status', `✅ Variable test passed (counter: ${newCounter})`);
|
|
} catch (error) {
|
|
log(`❌ Error testing variables: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
function displayStory() {
|
|
const storyOutput = document.getElementById('story-output');
|
|
const choicesContainer = document.getElementById('choices-container');
|
|
|
|
if (!window.testEngine) return;
|
|
|
|
// Display story text
|
|
storyOutput.innerHTML = `<div class="story-text">${window.testEngine.currentText || '(no text)'}</div>`;
|
|
|
|
// Display choices
|
|
choicesContainer.innerHTML = '';
|
|
const choices = window.testEngine.currentChoices;
|
|
|
|
if (choices.length > 0) {
|
|
choices.forEach((choice, index) => {
|
|
const button = document.createElement('button');
|
|
button.className = 'choice-button';
|
|
button.textContent = `${index + 1}. ${choice.text}`;
|
|
button.onclick = () => makeChoice(index);
|
|
choicesContainer.appendChild(button);
|
|
});
|
|
}
|
|
|
|
log(`📖 Story displayed. Choices: ${choices.length}`, 'info');
|
|
}
|
|
|
|
function makeChoice(index) {
|
|
if (!window.testEngine) return;
|
|
|
|
try {
|
|
window.testEngine.choose(index);
|
|
log(`✅ Choice ${index} selected`, 'success');
|
|
displayStory();
|
|
} catch (error) {
|
|
log(`❌ Error making choice: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// Test 3: Event system
|
|
function testEventEmit() {
|
|
try {
|
|
window.npcEvents.emit('test_event', { data: 'test' });
|
|
log('✅ Event emitted: test_event', 'success');
|
|
updateStatus('event-status', '✅ Event emitted successfully');
|
|
} catch (error) {
|
|
log(`❌ Error emitting event: ${error.message}`, 'error');
|
|
updateStatus('event-status', `❌ ${error.message}`, false);
|
|
}
|
|
}
|
|
|
|
function testEventCooldown() {
|
|
try {
|
|
window.npcEvents.emit('room_entered:test_room');
|
|
log('✅ Event 1 emitted', 'success');
|
|
|
|
// Try immediate re-emit (should be blocked by cooldown)
|
|
setTimeout(() => {
|
|
window.npcEvents.emit('room_entered:test_room');
|
|
log('✅ Event 2 emitted (cooldown should prevent this)', 'info');
|
|
}, 100);
|
|
|
|
updateStatus('event-status', '✅ Cooldown test initiated. Check console.');
|
|
} catch (error) {
|
|
log(`❌ Error: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
function testEventPattern() {
|
|
try {
|
|
let received = false;
|
|
window.npcEvents.on('item_picked_up:*', (data) => {
|
|
received = true;
|
|
log(`✅ Pattern matched: item_picked_up:* received event`, 'success');
|
|
});
|
|
|
|
window.npcEvents.emit('item_picked_up:keycard', { itemType: 'keycard' });
|
|
|
|
setTimeout(() => {
|
|
if (received) {
|
|
updateStatus('event-status', '✅ Pattern matching works');
|
|
} else {
|
|
updateStatus('event-status', '❌ Pattern matching failed', false);
|
|
}
|
|
}, 200);
|
|
} catch (error) {
|
|
log(`❌ Error: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// Test 4: Bark system
|
|
function testBark() {
|
|
try {
|
|
// Register alice if not already registered
|
|
if (!window.npcManager.getNPC('alice')) {
|
|
window.npcManager.registerNPC({
|
|
id: 'alice',
|
|
displayName: 'Alice',
|
|
storyPath: 'scenarios/compiled/alice-chat.json',
|
|
avatar: 'assets/npc/avatars/npc_alice.png'
|
|
});
|
|
}
|
|
|
|
// Add message to history manually
|
|
window.npcManager.addMessage('alice', 'npc', 'This is a test bark notification!');
|
|
|
|
window.npcBarkSystem.showBark({
|
|
npcId: 'alice',
|
|
npcName: 'Alice',
|
|
message: 'This is a test bark notification!',
|
|
avatar: 'assets/npc/avatars/npc_alice.png',
|
|
inkStoryPath: 'scenarios/compiled/alice-chat.json',
|
|
startKnot: 'start'
|
|
});
|
|
log('✅ Bark displayed (with history tracking)', 'success');
|
|
updateStatus('bark-status', '✅ Bark displayed successfully');
|
|
} catch (error) {
|
|
log(`❌ Error showing bark: ${error.message}`, 'error');
|
|
updateStatus('bark-status', `❌ ${error.message}`, false);
|
|
}
|
|
}
|
|
|
|
function testMultipleBarks() {
|
|
try {
|
|
// Register Alice with her story
|
|
if (!window.npcManager.getNPC('alice')) {
|
|
window.npcManager.registerNPC({
|
|
id: 'alice',
|
|
displayName: 'Alice',
|
|
storyPath: 'scenarios/compiled/alice-chat.json',
|
|
avatar: 'assets/npc/avatars/npc_alice.png'
|
|
});
|
|
}
|
|
|
|
// Register Bob with generic story
|
|
if (!window.npcManager.getNPC('bob')) {
|
|
window.npcManager.registerNPC({
|
|
id: 'bob',
|
|
displayName: 'Bob',
|
|
storyPath: 'scenarios/compiled/generic-npc.json',
|
|
avatar: 'assets/npc/avatars/npc_bob.png'
|
|
});
|
|
}
|
|
|
|
const messages = [
|
|
{ npcId: 'alice', npcName: 'Alice', message: 'First bark!' },
|
|
{ npcId: 'bob', npcName: 'Bob', message: 'Second bark!' },
|
|
{ npcId: 'alice', npcName: 'Alice', message: 'Third bark!' }
|
|
];
|
|
|
|
messages.forEach((msg, i) => {
|
|
setTimeout(() => {
|
|
// Add to history
|
|
window.npcManager.addMessage(msg.npcId, 'npc', msg.message);
|
|
|
|
window.npcBarkSystem.showBark({
|
|
...msg,
|
|
avatar: `assets/npc/avatars/npc_${msg.npcId}.png`
|
|
// No inkStoryPath - will use NPC's registered storyPath
|
|
});
|
|
}, i * 500);
|
|
});
|
|
|
|
log('✅ Multiple barks queued (with history tracking)', 'success');
|
|
updateStatus('bark-status', '✅ 3 barks will appear');
|
|
} catch (error) {
|
|
log(`❌ Error: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
function testBarkClick() {
|
|
try {
|
|
// Register alice if not already registered
|
|
if (!window.npcManager.getNPC('alice')) {
|
|
window.npcManager.registerNPC({
|
|
id: 'alice',
|
|
displayName: 'Alice',
|
|
storyPath: 'scenarios/compiled/alice-chat.json',
|
|
avatar: 'assets/npc/avatars/npc_alice.png'
|
|
});
|
|
}
|
|
|
|
// Add to history
|
|
window.npcManager.addMessage('alice', 'npc', 'Click me to open phone!');
|
|
|
|
window.npcBarkSystem.showBark({
|
|
npcId: 'alice',
|
|
npcName: 'Alice',
|
|
message: 'Click me to open phone!',
|
|
avatar: 'assets/npc/avatars/npc_alice.png',
|
|
inkStoryPath: 'scenarios/compiled/alice-chat.json',
|
|
startKnot: 'start'
|
|
});
|
|
log('✅ Clickable bark displayed (click it!)', 'success');
|
|
updateStatus('bark-status', '✅ Click the bark to test');
|
|
} catch (error) {
|
|
log(`❌ Error: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
function testBarkPhoneOpen() {
|
|
try {
|
|
// Register NPC with story first
|
|
window.npcManager.registerNPC({
|
|
id: 'alice',
|
|
displayName: 'Alice - Security Consultant',
|
|
storyPath: 'scenarios/compiled/alice-chat.json',
|
|
avatar: 'assets/npc/avatars/npc_alice.png'
|
|
});
|
|
|
|
// Show bark that will open phone when clicked
|
|
window.npcBarkSystem.showBark({
|
|
npcId: 'alice',
|
|
npcName: 'Alice',
|
|
message: 'Hey! Click to chat with me.',
|
|
avatar: 'assets/npc/avatars/npc_alice.png',
|
|
inkStoryPath: 'scenarios/compiled/alice-chat.json',
|
|
startKnot: 'start'
|
|
});
|
|
|
|
log('✅ Bark with phone integration shown - click it!', 'success');
|
|
updateStatus('bark-status', '✅ Click bark to open phone chat');
|
|
} catch (error) {
|
|
log(`❌ Error: ${error.message}`, 'error');
|
|
updateStatus('bark-status', `❌ ${error.message}`, false);
|
|
}
|
|
}
|
|
|
|
// Test 5: NPC Manager
|
|
function testNPCRegistration() {
|
|
try {
|
|
window.npcManager.registerNPC({
|
|
id: 'test-bob',
|
|
displayName: 'Bob - IT Manager',
|
|
storyPath: 'scenarios/compiled/generic-npc.json',
|
|
avatar: 'assets/npc/avatars/npc_bob.png',
|
|
eventMappings: {
|
|
'room_entered:server_room': {
|
|
knot: 'start',
|
|
bark: 'Hey! You\'re not supposed to be in here!',
|
|
once: true
|
|
},
|
|
'item_picked_up:*': {
|
|
knot: 'hub',
|
|
bark: 'Did you find something interesting?',
|
|
cooldown: 10000
|
|
}
|
|
}
|
|
});
|
|
|
|
log('✅ NPC registered: test-bob with event mappings', 'success');
|
|
updateStatus('npc-status', '✅ NPC registered successfully');
|
|
} catch (error) {
|
|
log(`❌ Error registering NPC: ${error.message}`, 'error');
|
|
updateStatus('npc-status', `❌ ${error.message}`, false);
|
|
}
|
|
}
|
|
|
|
async function testNPCEvent() {
|
|
try {
|
|
// Ensure NPC is registered first
|
|
if (!window.npcManager.npcs.has('test-bob')) {
|
|
testNPCRegistration();
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
}
|
|
|
|
// Emit mapped event
|
|
window.npcEvents.emit('room_entered:server_room', {
|
|
room: 'server_room',
|
|
firstVisit: true
|
|
});
|
|
|
|
log('✅ Event emitted: room_entered:server_room', 'success');
|
|
log('📱 Check for bark notification!', 'info');
|
|
updateStatus('npc-status', '✅ Event triggered. Watch for bark!');
|
|
} catch (error) {
|
|
log(`❌ Error: ${error.message}`, 'error');
|
|
updateStatus('npc-status', `❌ ${error.message}`, false);
|
|
}
|
|
}
|
|
|
|
async function testAutoTrigger() {
|
|
try {
|
|
log('🎯 Testing auto-trigger system with conversation history...', 'info');
|
|
|
|
// Register an NPC with event mappings
|
|
window.npcManager.registerNPC({
|
|
id: 'auto-alice',
|
|
displayName: 'Alice (Auto)',
|
|
storyPath: 'scenarios/compiled/alice-chat.json',
|
|
avatar: 'assets/npc/avatars/npc_alice.png',
|
|
phoneId: 'player_phone',
|
|
npcType: 'phone',
|
|
eventMappings: {
|
|
'player_moved:*': {
|
|
knot: 'start',
|
|
bark: 'I see you moving around...',
|
|
cooldown: 3000
|
|
},
|
|
'test_trigger': {
|
|
knot: 'start',
|
|
bark: '🎉 First message! Click me to chat.',
|
|
once: false
|
|
},
|
|
'test_trigger_2': {
|
|
knot: 'hub',
|
|
bark: '📬 Second message! Your history should show both.',
|
|
once: false
|
|
}
|
|
}
|
|
});
|
|
|
|
log('✅ Registered auto-alice with event mappings', 'success');
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
// Emit first test event - should automatically show bark and add to history
|
|
log('📤 Emitting test_trigger event (first message)...', 'info');
|
|
window.npcEvents.emit('test_trigger', { source: 'test' });
|
|
|
|
log('✅ First bark sent and added to history', 'success');
|
|
|
|
// Send a second message after delay
|
|
setTimeout(() => {
|
|
log('📤 Emitting test_trigger_2 event (second message)...', 'info');
|
|
window.npcEvents.emit('test_trigger_2', { source: 'test_2' });
|
|
log('💡 Now click either bark to open phone - you should see BOTH messages in history!', 'info');
|
|
}, 2000);
|
|
|
|
updateStatus('npc-status', '✅ Auto-trigger test initiated. Multiple barks will appear. Click any to see full history!');
|
|
|
|
} catch (error) {
|
|
log(`❌ Error: ${error.message}`, 'error');
|
|
updateStatus('npc-status', `❌ ${error.message}`, false);
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|