Files
BreakEscape/test-npc-ink.html
Z. Cliffe Schreuders 9fffb6b4e4 Add NPC dialogue and interaction scripts
- 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.
2025-10-29 13:48:22 +00:00

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>