diff --git a/CONTAINER_MINIGAME_USAGE.md b/CONTAINER_MINIGAME_USAGE.md new file mode 100644 index 0000000..42f607f --- /dev/null +++ b/CONTAINER_MINIGAME_USAGE.md @@ -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. diff --git a/assets/objects/notes.png b/assets/objects/notes.png new file mode 100644 index 0000000..188210a Binary files /dev/null and b/assets/objects/notes.png differ diff --git a/css/container-minigame.css b/css/container-minigame.css new file mode 100644 index 0000000..6dae6c1 --- /dev/null +++ b/css/container-minigame.css @@ -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; + } +} diff --git a/index.html b/index.html index 866e6ec..41da8fb 100644 --- a/index.html +++ b/index.html @@ -38,6 +38,7 @@ + diff --git a/js/minigames/container/container-minigame.js b/js/minigames/container/container-minigame.js new file mode 100644 index 0000000..debacc0 --- /dev/null +++ b/js/minigames/container/container-minigame.js @@ -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 = ` +
${this.containerItem.scenarioData.observations || ''}
+ `; + } + + // Create the container minigame UI + this.createContainerUI(); + } + + createContainerUI() { + this.gameContainer.innerHTML = ` +
+ ${this.containerItem.scenarioData.observations || ''}
+This container is empty.
'; + 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 = 'This container is empty.
'; + } + } + + // 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 = 'This container is empty.
'; + } + } + } 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'); + } +} diff --git a/js/minigames/index.js b/js/minigames/index.js index 667cd11..9de4bfd 100644 --- a/js/minigames/index.js +++ b/js/minigames/index.js @@ -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; \ No newline at end of file +window.startLockpickSetMinigame = startLockpickSetMinigame; +window.startContainerMinigame = startContainerMinigame; +window.returnToContainerAfterNotes = returnToContainerAfterNotes; \ No newline at end of file diff --git a/js/minigames/notes/notes-minigame.js b/js/minigames/notes/notes-minigame.js index b43dded..425aaeb 100644 --- a/js/minigames/notes/notes-minigame.js +++ b/js/minigames/notes/notes-minigame.js @@ -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); + } } }; diff --git a/js/systems/doors.js b/js/systems/doors.js index d4b3bf9..f8c355a 100644 --- a/js/systems/doors.js +++ b/js/systems/doors.js @@ -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 diff --git a/js/systems/interactions.js b/js/systems/interactions.js index 8b881d4..944bc4a 100644 --- a/js/systems/interactions.js +++ b/js/systems/interactions.js @@ -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; diff --git a/js/systems/unlock-system.js b/js/systems/unlock-system.js index 82e6cf5..d6e526b 100644 --- a/js/systems/unlock-system.js +++ b/js/systems/unlock-system.js @@ -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 } } diff --git a/test-container-minigame.html b/test-container-minigame.html new file mode 100644 index 0000000..6c56cec --- /dev/null +++ b/test-container-minigame.html @@ -0,0 +1,142 @@ + + + + +This test simulates the CEO Briefcase from the ceo_exfil.json scenario:
+