mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
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:
123
CONTAINER_MINIGAME_USAGE.md
Normal file
123
CONTAINER_MINIGAME_USAGE.md
Normal 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
BIN
assets/objects/notes.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 411 B |
228
css/container-minigame.css
Normal file
228
css/container-minigame.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
304
js/minigames/container/container-minigame.js
Normal file
304
js/minigames/container/container-minigame.js
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
142
test-container-minigame.html
Normal file
142
test-container-minigame.html
Normal 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>
|
||||
Reference in New Issue
Block a user