Add Container Minigame: Introduce a new minigame for interacting with container items, including features for visual display, item interaction, and integration with the unlock system. Add CSS styles and test page for functionality. Update index.html and minigame manager to support the new minigame.

This commit is contained in:
Z. Cliffe Schreuders
2025-10-13 11:53:07 +01:00
parent ea15e7d2c8
commit aa53ce53ea
11 changed files with 882 additions and 5 deletions

123
CONTAINER_MINIGAME_USAGE.md Normal file
View File

@@ -0,0 +1,123 @@
# Container Minigame Usage
## Overview
The Container Minigame allows players to interact with container items (like suitcases, briefcases, etc.) that contain other items. The minigame provides a visual interface similar to the player inventory, showing the container's contents in a grid layout.
## Features
- **Visual Container Display**: Shows an image of the container item with its name and observations
- **Contents Grid**: Displays all items within the container in a grid layout similar to the player inventory
- **Item Interaction**: Players can click on individual items to add them to their inventory
- **Notes Handling**: Notes items automatically trigger the notes minigame instead of being added to inventory
- **Container Collection**: If the container itself is takeable, players can add the entire container to their inventory
- **Unlock Integration**: Automatically launches after successfully unlocking a locked container
## Usage
### Automatic Launch
The container minigame automatically launches when:
1. A player interacts with an unlocked container that has contents
2. A player successfully unlocks a locked container (after the unlock minigame completes)
### Manual Launch
You can manually start the container minigame using:
```javascript
window.startContainerMinigame(containerItem, contents, isTakeable);
```
### Parameters
- `containerItem`: The sprite object representing the container
- `contents`: Array of items within the container
- `isTakeable`: Boolean indicating if the container itself can be taken
## Scenario Data Structure
### Container Item
```json
{
"type": "suitcase",
"name": "CEO Briefcase",
"takeable": false,
"locked": true,
"lockType": "key",
"requires": "briefcase_key:45,35,25,15",
"difficulty": "medium",
"observations": "An expensive leather briefcase with a sturdy lock",
"contents": [
{
"type": "notes",
"name": "Private Note",
"takeable": true,
"readable": true,
"text": "Closet keypad code: 7391 - Must move evidence to safe before audit",
"observations": "A hastily written note on expensive paper"
},
{
"type": "key",
"name": "Safe Key",
"takeable": true,
"key_id": "safe_key:52,29,44,37",
"observations": "A heavy-duty safe key hidden behind server equipment"
}
]
}
```
### Content Items
Each item in the `contents` array should have:
- `type`: The item type (used for image path: `assets/objects/{type}.png`)
- `name`: Display name for the item
- `takeable`: Whether the item can be taken by the player
- Additional properties as needed (observations, text, key_id, etc.)
## Integration with Unlock System
The container minigame integrates seamlessly with the existing unlock system:
1. **Locked Container**: When a player interacts with a locked container, the unlock minigame starts
2. **Successful Unlock**: After successful unlocking, the container minigame automatically launches
3. **Unlock State**: The container's `isUnlockedButNotCollected` flag is set to prevent automatic collection
## Visual Design
- **Container Image**: Large image of the container item at the top
- **Container Info**: Name and observations displayed below the image
- **Contents Grid**: Grid layout showing all items within the container
- **Item Tooltips**: Hover tooltips showing item names
- **Action Buttons**: "Take Container" (if takeable) and "Close" buttons
## Styling
The minigame uses the following CSS classes:
- `.container-minigame`: Main container
- `.container-image-section`: Container image and info
- `.container-contents-grid`: Grid of container contents
- `.container-content-slot`: Individual item slots
- `.container-content-item`: Item images
- `.container-actions`: Action buttons
## Testing
Use the test file `test-container-minigame.html` to test the container minigame functionality with sample data.
## Example Scenario
The CEO Briefcase in the `ceo_exfil.json` scenario demonstrates a complete container implementation:
- Locked with a key requirement
- Contains a private note with important information (triggers notes minigame when clicked)
- Contains a safe key for further progression
- Automatically launches the container minigame after unlocking
### Notes Item Behavior
When a notes item is clicked in the container minigame:
1. The note is immediately removed from the container display
2. A success message shows "Read [Note Name]"
3. The container state is saved for return after reading
4. The container minigame closes
5. The notes minigame opens with the note's text and observations
6. The note is automatically added to the player's notes collection
7. **After closing the notes minigame, the player automatically returns to the container minigame**
8. If the container becomes empty, it shows "This container is empty"
**Special Exception**: Unlike other minigames that close all other minigames, the notes minigame from containers has a special return flow that brings the player back to the container after reading.

BIN
assets/objects/notes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

228
css/container-minigame.css Normal file
View File

@@ -0,0 +1,228 @@
/* Container Minigame Styles */
.container-minigame {
display: flex;
flex-direction: column;
height: 100%;
padding: 20px;
gap: 20px;
}
.container-image-section {
display: flex;
align-items: center;
gap: 20px;
padding: 20px;
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.container-image {
width: 80px;
height: 80px;
object-fit: contain;
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 5px;
background: rgba(0, 0, 0, 0.3);
}
.container-info h4 {
font-family: 'Press Start 2P', monospace;
font-size: 14px;
margin: 0 0 10px 0;
color: #3498db;
}
.container-info p {
font-family: 'VT323', monospace;
font-size: 16px;
margin: 0;
color: #ecf0f1;
line-height: 1.4;
}
.container-contents-section {
flex: 1;
display: flex;
flex-direction: column;
gap: 15px;
}
.container-contents-section h4 {
font-family: 'Press Start 2P', monospace;
font-size: 12px;
margin: 0;
color: #e74c3c;
text-align: center;
}
.container-contents-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
gap: 10px;
padding: 15px;
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.1);
min-height: 120px;
max-height: 200px;
overflow-y: auto;
}
.container-contents-grid::-webkit-scrollbar {
width: 8px;
}
.container-contents-grid::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
.container-contents-grid::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
.container-contents-grid::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
.container-content-slot {
position: relative;
width: 60px;
height: 60px;
border: 1px solid rgba(255, 255, 255, 0.3);
display: flex;
justify-content: center;
align-items: center;
background: rgb(149 157 216 / 80%);
border-radius: 5px;
transition: all 0.2s ease;
}
.container-content-slot:hover {
border-color: rgba(255, 255, 255, 0.6);
background: rgb(149 157 216 / 90%);
transform: scale(1.05);
}
.container-content-item {
max-width: 48px;
max-height: 48px;
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
transition: transform 0.2s ease;
}
.container-content-item:hover {
transform: scale(1.1);
}
.container-content-tooltip {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
background: rgba(0, 0, 0, 0.8);
border: 1px solid rgba(255, 255, 255, 0.3);
font-family: 'VT323', monospace;
z-index: 1000;
}
.container-content-slot:hover .container-content-tooltip {
opacity: 1;
}
.empty-contents {
grid-column: 1 / -1;
text-align: center;
color: #95a5a6;
font-family: 'VT323', monospace;
font-size: 16px;
margin: 20px 0;
font-style: italic;
}
.container-actions {
display: flex;
justify-content: center;
gap: 15px;
padding-top: 10px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.container-actions .minigame-button {
min-width: 120px;
}
.container-message {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
padding: 10px 20px;
border-radius: 5px;
font-family: 'VT323', monospace;
font-size: 14px;
z-index: 10001;
animation: slideDown 0.3s ease;
}
.container-message-success {
background: rgba(46, 204, 113, 0.9);
color: white;
border: 1px solid #27ae60;
}
.container-message-error {
background: rgba(231, 76, 60, 0.9);
color: white;
border: 1px solid #c0392b;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateX(-50%) translateY(-20px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
/* Responsive adjustments */
@media (max-width: 768px) {
.container-image-section {
flex-direction: column;
text-align: center;
}
.container-contents-grid {
grid-template-columns: repeat(auto-fill, minmax(50px, 1fr));
gap: 8px;
}
.container-content-slot {
width: 50px;
height: 50px;
}
.container-content-item {
max-width: 40px;
max-height: 40px;
}
}

View File

@@ -38,6 +38,7 @@
<link rel="stylesheet" href="css/bluetooth-scanner.css">
<link rel="stylesheet" href="css/biometrics-minigame.css">
<link rel="stylesheet" href="css/lockpick-set-minigame.css">
<link rel="stylesheet" href="css/container-minigame.css">
<!-- External JavaScript libraries -->
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>

View File

@@ -0,0 +1,304 @@
// Container Minigame
import { MinigameScene } from '../framework/base-minigame.js';
import { addToInventory, removeFromInventory } from '../../systems/inventory.js';
export class ContainerMinigame extends MinigameScene {
constructor(container, params) {
super(container, params);
this.containerItem = params.containerItem;
this.contents = params.contents || [];
this.isTakeable = params.isTakeable || false;
}
init() {
// Call parent init first
super.init();
// Update header with container name
if (this.headerElement) {
this.headerElement.innerHTML = `
<h3>${this.containerItem.scenarioData.name}</h3>
<p>${this.containerItem.scenarioData.observations || ''}</p>
`;
}
// Create the container minigame UI
this.createContainerUI();
}
createContainerUI() {
this.gameContainer.innerHTML = `
<div class="container-minigame">
<div class="container-image-section">
<img src="assets/objects/${this.containerItem.name}.png"
alt="${this.containerItem.scenarioData.name}"
class="container-image">
<div class="container-info">
<h4>${this.containerItem.scenarioData.name}</h4>
<p>${this.containerItem.scenarioData.observations || ''}</p>
</div>
</div>
<div class="container-contents-section">
<h4>Contents</h4>
<div class="container-contents-grid" id="container-contents-grid">
<!-- Contents will be populated here -->
</div>
</div>
<div class="container-actions">
${this.isTakeable ? '<button class="minigame-button" id="take-container-btn">Take Container</button>' : ''}
<button class="minigame-button" id="close-container-btn">Close</button>
</div>
</div>
`;
// Populate contents
this.populateContents();
// Set up event listeners
this.setupEventListeners();
}
populateContents() {
const contentsGrid = document.getElementById('container-contents-grid');
if (!contentsGrid) return;
if (this.contents.length === 0) {
contentsGrid.innerHTML = '<p class="empty-contents">This container is empty.</p>';
return;
}
this.contents.forEach((item, index) => {
const slot = document.createElement('div');
slot.className = 'container-content-slot';
const itemImg = document.createElement('img');
itemImg.className = 'container-content-item';
itemImg.src = `assets/objects/${item.type}.png`;
itemImg.alt = item.name;
itemImg.title = item.name;
// Add item data
itemImg.scenarioData = item;
itemImg.name = item.type;
itemImg.objectId = `container_${index}`;
// Add click handler for taking items
if (item.takeable) {
itemImg.style.cursor = 'pointer';
// Special handling for notes - trigger notes minigame instead of taking
if (item.type === 'notes' && item.readable && item.text) {
itemImg.addEventListener('click', () => this.handleNotesItem(item, itemImg));
} else {
itemImg.addEventListener('click', () => this.takeItem(item, itemImg));
}
}
// Create tooltip
const tooltip = document.createElement('div');
tooltip.className = 'container-content-tooltip';
tooltip.textContent = item.name;
slot.appendChild(itemImg);
slot.appendChild(tooltip);
contentsGrid.appendChild(slot);
});
}
setupEventListeners() {
// Take container button
const takeContainerBtn = document.getElementById('take-container-btn');
if (takeContainerBtn) {
this.addEventListener(takeContainerBtn, 'click', () => this.takeContainer());
}
// Close button
const closeBtn = document.getElementById('close-container-btn');
if (closeBtn) {
this.addEventListener(closeBtn, 'click', () => this.complete(false));
}
}
handleNotesItem(item, itemElement) {
console.log('Handling notes item from container:', item);
// Remove the note from container display
itemElement.parentElement.remove();
// Remove from contents array
const itemIndex = this.contents.findIndex(content => content === item);
if (itemIndex !== -1) {
this.contents.splice(itemIndex, 1);
}
// Show success message
this.showMessage(`Read ${item.name}`, 'success');
// If container is now empty, update display
if (this.contents.length === 0) {
const contentsGrid = document.getElementById('container-contents-grid');
if (contentsGrid) {
contentsGrid.innerHTML = '<p class="empty-contents">This container is empty.</p>';
}
}
// Store container state for return after notes minigame
const containerState = {
containerItem: this.containerItem,
contents: this.contents,
isTakeable: this.isTakeable
};
// Store the container state globally so we can return to it
window.pendingContainerReturn = containerState;
// Close the container minigame first
this.complete(false);
// Start the notes minigame
if (window.startNotesMinigame) {
// Create a temporary sprite-like object for the notes minigame
const tempSprite = {
scenarioData: item,
name: item.type,
objectId: `temp_${Date.now()}`
};
// Start notes minigame with the item's text
window.startNotesMinigame(tempSprite, item.text, item.observations);
} else {
console.error('Notes minigame not available');
window.gameAlert('Notes minigame not available', 'error', 'Error', 3000);
}
}
takeItem(item, itemElement) {
console.log('Taking item from container:', item);
// Create a temporary sprite-like object for the inventory system
const tempSprite = {
scenarioData: item,
name: item.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
if (addToInventory(tempSprite)) {
// Remove from container display
itemElement.parentElement.remove();
// Remove from contents array
const itemIndex = this.contents.findIndex(content => content === item);
if (itemIndex !== -1) {
this.contents.splice(itemIndex, 1);
}
// Show success message
this.showMessage(`Added ${item.name} to inventory`, 'success');
// If container is now empty, update display
if (this.contents.length === 0) {
const contentsGrid = document.getElementById('container-contents-grid');
if (contentsGrid) {
contentsGrid.innerHTML = '<p class="empty-contents">This container is empty.</p>';
}
}
} else {
this.showMessage(`Failed to add ${item.name} to inventory`, 'error');
}
}
takeContainer() {
console.log('Taking container:', this.containerItem);
// Ensure container item has setVisible method if it doesn't already
if (!this.containerItem.setVisible) {
this.containerItem.setVisible = function(visible) {
console.log(`Mock setVisible(${visible}) called on container item`);
};
}
// Add container to inventory
if (addToInventory(this.containerItem)) {
this.showMessage(`Added ${this.containerItem.scenarioData.name} to inventory`, 'success');
// Close the minigame after a short delay
setTimeout(() => {
this.complete(true);
}, 1500);
} else {
this.showMessage(`Failed to add ${this.containerItem.scenarioData.name} to inventory`, 'error');
}
}
showMessage(message, type) {
const messageElement = document.createElement('div');
messageElement.className = `container-message container-message-${type}`;
messageElement.textContent = message;
this.messageContainer.appendChild(messageElement);
// Remove message after 3 seconds
setTimeout(() => {
if (messageElement.parentElement) {
messageElement.parentElement.removeChild(messageElement);
}
}, 3000);
}
}
// Function to start the container minigame
export function startContainerMinigame(containerItem, contents, isTakeable = false) {
console.log('Starting container minigame', { containerItem, contents, isTakeable });
// Initialize the minigame framework if not already done
if (!window.MinigameFramework) {
console.error('MinigameFramework not available');
return;
}
if (!window.MinigameFramework.mainGameScene) {
window.MinigameFramework.init(window.game);
}
// Start the container minigame
window.MinigameFramework.startMinigame('container', null, {
title: containerItem.scenarioData.name,
containerItem: containerItem,
contents: contents,
isTakeable: isTakeable,
cancelText: 'Close',
showCancel: true,
onComplete: (success, result) => {
console.log('Container minigame completed', { success, result });
}
});
}
// Function to return to container after notes minigame
export function returnToContainerAfterNotes() {
console.log('Returning to container after notes minigame');
// Check if there's a pending container return
if (window.pendingContainerReturn) {
const containerState = window.pendingContainerReturn;
// Clear the pending return state
window.pendingContainerReturn = null;
// Start the container minigame with the stored state
startContainerMinigame(
containerState.containerItem,
containerState.contents,
containerState.isTakeable
);
} else {
console.log('No pending container return found');
}
}

View File

@@ -9,6 +9,7 @@ export { NotesMinigame, startNotesMinigame, showMissionBrief } from './notes/not
export { BluetoothScannerMinigame, startBluetoothScannerMinigame } from './bluetooth/bluetooth-scanner-minigame.js';
export { BiometricsMinigame, startBiometricsMinigame } from './biometrics/biometrics-minigame.js';
export { LockpickSetMinigame, startLockpickSetMinigame } from './lockpick/lockpick-set-minigame.js';
export { ContainerMinigame, startContainerMinigame, returnToContainerAfterNotes } from './container/container-minigame.js';
// Initialize the global minigame framework for backward compatibility
import { MinigameFramework } from './framework/minigame-manager.js';
@@ -32,6 +33,9 @@ import { BiometricsMinigame, startBiometricsMinigame } from './biometrics/biomet
// Import the lockpick set minigame
import { LockpickSetMinigame, startLockpickSetMinigame } from './lockpick/lockpick-set-minigame.js';
// Import the container minigame
import { ContainerMinigame, startContainerMinigame, returnToContainerAfterNotes } from './container/container-minigame.js';
// Register minigames
MinigameFramework.registerScene('lockpicking', LockpickingMinigamePhaser); // Use Phaser version as default
MinigameFramework.registerScene('lockpicking-phaser', LockpickingMinigamePhaser); // Keep explicit phaser name
@@ -40,10 +44,13 @@ MinigameFramework.registerScene('notes', NotesMinigame);
MinigameFramework.registerScene('bluetooth-scanner', BluetoothScannerMinigame);
MinigameFramework.registerScene('biometrics', BiometricsMinigame);
MinigameFramework.registerScene('lockpick-set', LockpickSetMinigame);
MinigameFramework.registerScene('container', ContainerMinigame);
// Make minigame functions available globally
window.startNotesMinigame = startNotesMinigame;
window.showMissionBrief = showMissionBrief;
window.startBluetoothScannerMinigame = startBluetoothScannerMinigame;
window.startBiometricsMinigame = startBiometricsMinigame;
window.startLockpickSetMinigame = startLockpickSetMinigame;
window.startLockpickSetMinigame = startLockpickSetMinigame;
window.startContainerMinigame = startContainerMinigame;
window.returnToContainerAfterNotes = returnToContainerAfterNotes;

View File

@@ -730,6 +730,15 @@ export function startNotesMinigame(item, noteContent, observationText, navigateT
} else {
console.log('NOTES COMPLETED - Not added to inventory');
}
// Check if we need to return to a container after notes minigame
if (window.pendingContainerReturn && window.returnToContainerAfterNotes) {
console.log('Returning to container after notes minigame');
// Small delay to ensure notes minigame cleanup completes
setTimeout(() => {
window.returnToContainerAfterNotes();
}, 100);
}
}
};

View File

@@ -13,23 +13,23 @@ let gameRef = null;
let rooms = null;
// Global toggle for disabling locks during testing
window.DISABLE_LOCKS = false; // Set to true in console to bypass all lock checks
window.DISABLE_LOCKS = false; // Set to true in console to bypass all lock checks (doors and items)
// Console helper functions for testing
window.toggleLocks = function() {
window.DISABLE_LOCKS = !window.DISABLE_LOCKS;
console.log(`Locks ${window.DISABLE_LOCKS ? 'DISABLED' : 'ENABLED'} for testing`);
console.log(`Locks ${window.DISABLE_LOCKS ? 'DISABLED' : 'ENABLED'} for testing (affects doors and items)`);
return window.DISABLE_LOCKS;
};
window.disableLocks = function() {
window.DISABLE_LOCKS = true;
console.log('Locks DISABLED for testing - all doors will open without minigames');
console.log('Locks DISABLED for testing - all doors and items will open/unlock without minigames');
};
window.enableLocks = function() {
window.DISABLE_LOCKS = false;
console.log('Locks ENABLED - doors will require proper unlocking');
console.log('Locks ENABLED - doors and items will require proper unlocking');
};
// Door transition cooldown system

View File

@@ -230,6 +230,23 @@ export function handleObjectInteraction(sprite) {
return;
}
// Handle container items (suitcase, briefcase, etc.)
if (data.type === 'suitcase' || data.type === 'briefcase' || data.contents) {
console.log('CONTAINER ITEM INTERACTION', data);
// Check if container was unlocked but not yet collected
if (data.isUnlockedButNotCollected) {
console.log('CONTAINER UNLOCKED - LAUNCHING MINIGAME', data);
handleContainerInteraction(sprite);
return;
}
// If container is still locked, the unlock system will handle it
// and set isUnlockedButNotCollected flag
console.log('CONTAINER LOCKED - UNLOCK SYSTEM WILL HANDLE', data);
return;
}
let message = `${data.name} `;
if (data.observations) {
message += `Observations: ${data.observations}\n`;
@@ -284,6 +301,27 @@ export function handleObjectInteraction(sprite) {
window.gameAlert(message, 'info', data.name, 0);
}
// Handle container item interactions
function handleContainerInteraction(sprite) {
const data = sprite.scenarioData;
console.log('Handling container interaction:', data);
// Check if container has contents
if (!data.contents || data.contents.length === 0) {
window.gameAlert(`${data.name} is empty.`, 'info', 'Empty Container', 3000);
return;
}
// Start the container minigame
if (window.startContainerMinigame) {
window.startContainerMinigame(sprite, data.contents, data.takeable);
} else {
console.error('Container minigame not available');
window.gameAlert('Container minigame not available', 'error', 'Error', 3000);
}
}
// Export for global access
window.checkObjectInteractions = checkObjectInteractions;
window.handleObjectInteraction = handleObjectInteraction;
window.handleContainerInteraction = handleContainerInteraction;

View File

@@ -23,6 +23,13 @@ function boundsOverlap(rect1, rect2) {
export function handleUnlock(lockable, type) {
console.log('UNLOCK ATTEMPT');
// Check if locks are disabled for testing
if (window.DISABLE_LOCKS) {
console.log('LOCKS DISABLED FOR TESTING - Unlocking directly');
unlockTarget(lockable, type, lockable.layer);
return;
}
// Get lock requirements based on type
const lockRequirements = type === 'door'
? getLockRequirementsForDoor(lockable)
@@ -327,12 +334,30 @@ export function unlockTarget(lockable, type, layer) {
// Set new state for containers with contents
if (lockable.scenarioData.contents) {
lockable.scenarioData.isUnlockedButNotCollected = true;
// Automatically launch container minigame after unlocking
setTimeout(() => {
if (window.handleContainerInteraction) {
console.log('Auto-launching container minigame after unlock');
window.handleContainerInteraction(lockable);
}
}, 500); // Small delay to ensure unlock message is shown
return; // Return early to prevent automatic collection
}
} else {
lockable.locked = false;
if (lockable.contents) {
lockable.isUnlockedButNotCollected = true;
// Automatically launch container minigame after unlocking
setTimeout(() => {
if (window.handleContainerInteraction) {
console.log('Auto-launching container minigame after unlock');
window.handleContainerInteraction(lockable);
}
}, 500); // Small delay to ensure unlock message is shown
return; // Return early to prevent automatic collection
}
}

View File

@@ -0,0 +1,142 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Container Minigame Test</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
<!-- CSS Files -->
<link rel="stylesheet" href="css/minigames-framework.css">
<link rel="stylesheet" href="css/container-minigame.css">
<style>
body {
background: #1a1a1a;
color: white;
font-family: 'VT323', monospace;
margin: 0;
padding: 20px;
}
.test-container {
max-width: 800px;
margin: 0 auto;
}
.test-button {
background: #3498db;
color: white;
border: 4px solid #2980b9;
padding: 15px 30px;
cursor: pointer;
font-family: 'VT323', monospace;
font-size: 18px;
margin: 10px;
transition: background 0.3s;
}
.test-button:hover {
background: #2980b9;
}
.test-info {
background: rgba(255, 255, 255, 0.1);
padding: 20px;
border-radius: 10px;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="test-container">
<h1>Container Minigame Test</h1>
<div class="test-info">
<h2>Test Data</h2>
<p>This test simulates the CEO Briefcase from the ceo_exfil.json scenario:</p>
<ul>
<li><strong>Container:</strong> CEO Briefcase (suitcase)</li>
<li><strong>Takeable:</strong> false</li>
<li><strong>Contents:</strong> Private Note + Safe Key</li>
</ul>
</div>
<button class="test-button" onclick="testContainerMinigame()">
Test Container Minigame
</button>
<button class="test-button" onclick="testTakeableContainer()">
Test Takeable Container
</button>
</div>
<!-- Minigame Framework -->
<script type="module">
import { MinigameFramework } from './js/minigames/framework/minigame-manager.js';
import { ContainerMinigame, startContainerMinigame } from './js/minigames/container/container-minigame.js';
// Make framework available globally
window.MinigameFramework = MinigameFramework;
window.startContainerMinigame = startContainerMinigame;
// Register the container minigame
MinigameFramework.registerScene('container', ContainerMinigame);
// Test data - CEO Briefcase
const testContainerItem = {
name: 'suitcase-1',
scenarioData: {
type: 'suitcase',
name: 'CEO Briefcase',
takeable: false,
observations: 'An expensive leather briefcase with a sturdy lock'
}
};
const testContents = [
{
type: 'notes',
name: 'Private Note',
takeable: true,
readable: true,
text: 'Closet keypad code: 7391 - Must move evidence to safe before audit',
observations: 'A hastily written note on expensive paper'
},
{
type: 'key',
name: 'Safe Key',
takeable: true,
key_id: 'safe_key:52,29,44,37',
observations: 'A heavy-duty safe key hidden behind server equipment'
}
];
const testTakeableContainerItem = {
name: 'suitcase-1',
scenarioData: {
type: 'suitcase',
name: 'Test Takeable Briefcase',
takeable: true,
observations: 'A briefcase that can be taken'
}
};
window.testContainerMinigame = function() {
console.log('Testing container minigame...');
startContainerMinigame(testContainerItem, testContents, false);
};
window.testTakeableContainer = function() {
console.log('Testing takeable container minigame...');
startContainerMinigame(testTakeableContainerItem, testContents, true);
};
console.log('Container minigame test page loaded');
</script>
</body>
</html>