mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
Add static file serving and enhance game UI
- Implement StaticFilesController to serve CSS, JS, and asset files for the BreakEscape engine. - Update routes to include static file paths for CSS, JS, and assets. - Refactor game show view to load multiple CSS files and include Google Fonts. - Remove application stylesheet link from the layout. - Modify various CSS files to improve layout and styling, including HUD and inventory. - Update JavaScript files to ensure asset paths are correctly prefixed with /break_escape/. - Enhance minigame UI components, including notifications, modals, and overlays. - Adjust game-over screen and health UI to use correct asset paths. - Update constants and crypto workstation utility to reflect new asset paths.
This commit is contained in:
88
app/controllers/break_escape/static_files_controller.rb
Normal file
88
app/controllers/break_escape/static_files_controller.rb
Normal file
@@ -0,0 +1,88 @@
|
||||
module BreakEscape
|
||||
class StaticFilesController < BreakEscape::ApplicationController
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
def serve
|
||||
# Use the BreakEscape engine's root, not Rails.root
|
||||
engine_root = BreakEscape::Engine.root
|
||||
|
||||
# Determine the actual file path based on the request URL
|
||||
request_path = request.path
|
||||
|
||||
# Map different URL patterns to their file locations
|
||||
# Remember: request_path will be /break_escape/css/... when mounted at /break_escape
|
||||
file_path = case request_path
|
||||
when %r{^/break_escape/css/}
|
||||
engine_root.join('public', 'break_escape', 'css', params[:path])
|
||||
when %r{^/break_escape/js/}
|
||||
engine_root.join('public', 'break_escape', 'js', params[:path])
|
||||
when %r{^/break_escape/assets/}
|
||||
engine_root.join('public', 'break_escape', 'assets', params[:path])
|
||||
when %r{^/break_escape/stylesheets/}
|
||||
engine_root.join('public', 'break_escape', 'css', params[:path])
|
||||
else
|
||||
# Fallback for any other pattern
|
||||
engine_root.join('public', 'break_escape', params[:path])
|
||||
end
|
||||
|
||||
# Security: prevent directory traversal
|
||||
base_path = engine_root.join('public', 'break_escape').to_s
|
||||
unless file_path.to_s.start_with?(base_path)
|
||||
return render_not_found
|
||||
end
|
||||
|
||||
unless File.file?(file_path)
|
||||
return render_not_found
|
||||
end
|
||||
|
||||
# Determine content type
|
||||
content_type = determine_content_type(file_path.to_s)
|
||||
|
||||
send_file file_path, type: content_type, disposition: 'inline'
|
||||
rescue Errno::ENOENT
|
||||
render_not_found
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def determine_content_type(file_path)
|
||||
case File.extname(file_path).downcase
|
||||
when '.css'
|
||||
'text/css'
|
||||
when '.js'
|
||||
'application/javascript'
|
||||
when '.json'
|
||||
'application/json'
|
||||
when '.png'
|
||||
'image/png'
|
||||
when '.jpg', '.jpeg'
|
||||
'image/jpeg'
|
||||
when '.gif'
|
||||
'image/gif'
|
||||
when '.svg'
|
||||
'image/svg+xml'
|
||||
when '.woff'
|
||||
'font/woff'
|
||||
when '.woff2'
|
||||
'font/woff2'
|
||||
when '.ttf'
|
||||
'font/ttf'
|
||||
when '.eot'
|
||||
'application/vnd.ms-fontobject'
|
||||
when '.mp3'
|
||||
'audio/mpeg'
|
||||
when '.wav'
|
||||
'audio/wav'
|
||||
when '.ogg'
|
||||
'audio/ogg'
|
||||
else
|
||||
'application/octet-stream'
|
||||
end
|
||||
end
|
||||
|
||||
def render_not_found
|
||||
render plain: 'Not Found', status: :not_found
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,18 +5,110 @@
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
|
||||
<%# Load game CSS %>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes">
|
||||
|
||||
<%# Google Fonts - Press Start 2P, VT323, Pixelify Sans %>
|
||||
<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">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Pixelify+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<%# Web Font Loader script to ensure fonts load properly %>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
|
||||
<script nonce="<%= content_security_policy_nonce %>">
|
||||
WebFont.load({
|
||||
google: {
|
||||
families: ['Press Start 2P', 'VT323', 'Pixelify Sans']
|
||||
},
|
||||
active: function() {
|
||||
console.log('Fonts loaded successfully');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<%# Load game CSS files %>
|
||||
<link rel="stylesheet" href="/break_escape/css/main.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/utilities.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/notifications.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/panels.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/hud.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/minigames-framework.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/dusting.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/lockpicking.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/modals.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/notes.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/bluetooth-scanner.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/biometrics-minigame.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/container-minigame.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/phone-chat-minigame.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/person-chat-minigame.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/rfid-minigame.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/npc-interactions.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/pin.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/password-minigame.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/text-file-minigame.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/npc-barks.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/title-screen.css">
|
||||
<link rel="stylesheet" href="/break_escape/css/inventory.css">
|
||||
</head>
|
||||
<body>
|
||||
<%# Game container - Phaser will render here %>
|
||||
<div id="break-escape-game"></div>
|
||||
<div id="game-container"></div>
|
||||
|
||||
<%# Loading indicator %>
|
||||
<div id="loading" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #00ff00; font-size: 24px; display: block;">
|
||||
Loading game...
|
||||
</div>
|
||||
|
||||
<%# Notification System %>
|
||||
<div id="notification-container"></div>
|
||||
|
||||
<%# Toggle Buttons Container %>
|
||||
<div id="toggle-buttons-container">
|
||||
<!-- Biometrics is now handled as a minigame -->
|
||||
</div>
|
||||
|
||||
<%# Inventory Container %>
|
||||
<div id="inventory-container"></div>
|
||||
|
||||
<%# Laptop Popup %>
|
||||
<div id="laptop-popup">
|
||||
<div class="laptop-frame">
|
||||
<div class="laptop-screen">
|
||||
<div class="title-bar">
|
||||
<span>Crypto Workstation</span>
|
||||
<button class="minigame-close-button" onclick="closeLaptop()">×</button>
|
||||
</div>
|
||||
<div id="cyberchef-container">
|
||||
<iframe id="cyberchef-frame" src=""></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Password Modal %>
|
||||
<div id="password-modal">
|
||||
<div class="password-modal-content">
|
||||
<div class="password-modal-title">
|
||||
Enter Password
|
||||
</div>
|
||||
<input id="password-modal-input" type="password" autocomplete="off" autofocus>
|
||||
<div class="password-modal-checkbox-container">
|
||||
<input type="checkbox" id="password-modal-show">
|
||||
<label for="password-modal-show" class="password-modal-checkbox-label">Show password</label>
|
||||
</div>
|
||||
<div class="password-modal-buttons">
|
||||
<button id="password-modal-ok" class="password-modal-button">OK</button>
|
||||
<button id="password-modal-cancel" class="password-modal-button">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Popup Overlay %>
|
||||
<div class="popup-overlay"></div>
|
||||
|
||||
<%# Bootstrap configuration for client %>
|
||||
<script nonce="<%= content_security_policy_nonce %>">
|
||||
window.breakEscapeConfig = {
|
||||
@@ -27,6 +119,11 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<%# Load required libraries before the game module %>
|
||||
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js" nonce="<%= content_security_policy_nonce %>"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/easystarjs@0.4.4/bin/easystar-0.4.4.js" nonce="<%= content_security_policy_nonce %>"></script>
|
||||
<script src="/break_escape/assets/vendor/ink.js" nonce="<%= content_security_policy_nonce %>"></script>
|
||||
|
||||
<%# Load game JavaScript (ES6 module) %>
|
||||
<script type="module" src="/break_escape/js/main.js" nonce="<%= content_security_policy_nonce %>"></script>
|
||||
</body>
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
<%= csp_meta_tag %>
|
||||
|
||||
<%= yield :head %>
|
||||
|
||||
<%= stylesheet_link_tag "break_escape/application", media: "all" %>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
BreakEscape::Engine.routes.draw do
|
||||
# Static files - match only static file paths (must come BEFORE resource routes)
|
||||
# Note: These routes are automatically prefixed with /break_escape by the mount in the parent app
|
||||
get '/css/*path', to: 'static_files#serve', constraints: { path: /.*/ }
|
||||
get '/js/*path', to: 'static_files#serve', constraints: { path: /.*/ }
|
||||
get '/assets/*path', to: 'static_files#serve', constraints: { path: /.*/ }
|
||||
get '/stylesheets/*path', to: 'static_files#serve', constraints: { path: /.*/ }
|
||||
|
||||
# Mission selection
|
||||
resources :missions, only: [:index, :show]
|
||||
|
||||
|
||||
@@ -42,14 +42,16 @@
|
||||
#inventory-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
z-index: 1000;
|
||||
font-family: 'VT323';
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#inventory-container::-webkit-scrollbar {
|
||||
@@ -74,7 +76,7 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
background: rgb(149 157 216 / 80%);
|
||||
background: transparent; /* keep slot outlines but no filled background per user request */
|
||||
}
|
||||
|
||||
/* Pulse animation for newly added items */
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
display: block;
|
||||
background: #333;
|
||||
font-smooth: never;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#game-container {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
z-index: 2000;
|
||||
/* Raised so minigames appear above other UI (inventory, NPC barks, HUD, etc.) */
|
||||
z-index: 11000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
@@ -439,25 +439,33 @@ export function preload() {
|
||||
window.soundManagerPreload = new SoundManager(this);
|
||||
window.soundManagerPreload.preloadSounds();
|
||||
|
||||
// Get scenario from URL parameter or use default
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
let scenarioFile = urlParams.get('scenario') || 'scenarios/ceo_exfil.json';
|
||||
|
||||
// Ensure scenario file has proper path prefix
|
||||
if (!scenarioFile.startsWith('scenarios/')) {
|
||||
scenarioFile = `scenarios/${scenarioFile}`;
|
||||
// Load scenario from Rails API endpoint if available, otherwise try URL parameter
|
||||
if (window.breakEscapeConfig?.apiBasePath) {
|
||||
// Load scenario from Rails API endpoint
|
||||
// Use absolute URL with origin to prevent Phaser baseURL from interfering
|
||||
const scenarioUrl = `${window.location.origin}${window.breakEscapeConfig.apiBasePath}/scenario`;
|
||||
this.load.json('gameScenarioJSON', scenarioUrl);
|
||||
} else {
|
||||
// Fallback to old behavior for standalone HTML files
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
let scenarioFile = urlParams.get('scenario') || 'scenarios/ceo_exfil.json';
|
||||
|
||||
// Ensure scenario file has proper path prefix
|
||||
if (!scenarioFile.startsWith('scenarios/')) {
|
||||
scenarioFile = `scenarios/${scenarioFile}`;
|
||||
}
|
||||
|
||||
// Ensure .json extension
|
||||
if (!scenarioFile.endsWith('.json')) {
|
||||
scenarioFile = `${scenarioFile}.json`;
|
||||
}
|
||||
|
||||
// Add cache buster query parameter to prevent browser caching
|
||||
scenarioFile = `${scenarioFile}${scenarioFile.includes('?') ? '&' : '?'}v=${Date.now()}`;
|
||||
|
||||
// Load the specified scenario
|
||||
this.load.json('gameScenarioJSON', scenarioFile);
|
||||
}
|
||||
|
||||
// Ensure .json extension
|
||||
if (!scenarioFile.endsWith('.json')) {
|
||||
scenarioFile = `${scenarioFile}.json`;
|
||||
}
|
||||
|
||||
// Add cache buster query parameter to prevent browser caching
|
||||
scenarioFile = `${scenarioFile}${scenarioFile.includes('?') ? '&' : '?'}v=${Date.now()}`;
|
||||
|
||||
// Load the specified scenario
|
||||
this.load.json('gameScenarioJSON', scenarioFile);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ export class BiometricsMinigame extends MinigameScene {
|
||||
scannerHeader.className = 'biometrics-scanner-header';
|
||||
scannerHeader.innerHTML = `
|
||||
<div class="biometrics-scanner-title">
|
||||
<img src="assets/objects/fingerprint.png" alt="Biometric Samples" class="scanner-icon">
|
||||
<img src="/break_escape/assets/objects/fingerprint.png" alt="Biometric Samples" class="scanner-icon">
|
||||
<span>Biometric Samples</span>
|
||||
<span class="samples-count-header">0 samples</span>
|
||||
</div>
|
||||
@@ -81,7 +81,7 @@ export class BiometricsMinigame extends MinigameScene {
|
||||
searchRoomContainer.className = 'biometrics-search-room-container';
|
||||
searchRoomContainer.innerHTML = `
|
||||
<button id="search-room-btn" class="biometrics-action-btn">
|
||||
<span class="btn-icon"><img src="assets/icons/search.png" alt="Search" class="icon"></span>
|
||||
<span class="btn-icon"><img src="/break_escape/assets/icons/search.png" alt="Search" class="icon"></span>
|
||||
<span class="btn-text">Search Room for Fingerprints</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
@@ -63,7 +63,7 @@ export class BluetoothScannerMinigame extends MinigameScene {
|
||||
scannerHeader.className = 'bluetooth-scanner-header';
|
||||
scannerHeader.innerHTML = `
|
||||
<div class="bluetooth-scanner-title">
|
||||
<img src="assets/objects/bluetooth_scanner.png" alt="Bluetooth Scanner" class="scanner-icon">
|
||||
<img src="/break_escape/assets/objects/bluetooth_scanner.png" alt="Bluetooth Scanner" class="scanner-icon">
|
||||
<span>Bluetooth Scanner</span>
|
||||
</div>
|
||||
<div class="bluetooth-scanner-status">
|
||||
@@ -490,15 +490,15 @@ export class BluetoothScannerMinigame extends MinigameScene {
|
||||
deviceContent += `</div></div>`;
|
||||
} else if (device.nearby) {
|
||||
// Fallback if signal strength not available
|
||||
deviceContent += `<span class="bluetooth-device-icon"><img src="assets/icons/signal.png" alt="Signal" class="icon"></span>`;
|
||||
deviceContent += `<span class="bluetooth-device-icon"><img src="/break_escape/assets/icons/signal.png" alt="Signal" class="icon"></span>`;
|
||||
}
|
||||
|
||||
if (device.saved) {
|
||||
deviceContent += `<span class="bluetooth-device-icon"><img src="assets/icons/disk.png" alt="Disk" class="icon"></span>`;
|
||||
deviceContent += `<span class="bluetooth-device-icon"><img src="/break_escape/assets/icons/disk.png" alt="Disk" class="icon"></span>`;
|
||||
}
|
||||
|
||||
if (device.inInventory) {
|
||||
deviceContent += `<span class="bluetooth-device-icon"><img src="assets/icons/backpack.png" alt="Backpack" class="icon"></span>`;
|
||||
deviceContent += `<span class="bluetooth-device-icon"><img src="/break_escape/assets/icons/backpack.png" alt="Backpack" class="icon"></span>`;
|
||||
}
|
||||
|
||||
deviceContent += `</div></div>`;
|
||||
|
||||
@@ -54,7 +54,7 @@ export class ContainerMinigame extends MinigameScene {
|
||||
const notebookBtn = document.createElement('button');
|
||||
notebookBtn.className = 'minigame-button';
|
||||
notebookBtn.id = 'minigame-notebook-postit';
|
||||
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
|
||||
notebookBtn.innerHTML = '<img src="/break_escape/assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
|
||||
// Insert before the cancel button (first child in controls)
|
||||
this.controlsElement.insertBefore(notebookBtn, this.controlsElement.firstChild);
|
||||
}
|
||||
@@ -104,7 +104,7 @@ export class ContainerMinigame extends MinigameScene {
|
||||
this.gameContainer.innerHTML = `
|
||||
<div class="container-minigame">
|
||||
<div class="container-image-section">
|
||||
<img src="assets/objects/${this.containerItem.texture.key}.png"
|
||||
<img src="/break_escape/assets/objects/${this.containerItem.texture.key}.png"
|
||||
alt="${this.containerItem.scenarioData.name}"
|
||||
class="container-image">
|
||||
<div class="container-info">
|
||||
@@ -130,7 +130,7 @@ export class ContainerMinigame extends MinigameScene {
|
||||
createDesktopUI() {
|
||||
this.gameContainer.innerHTML = `
|
||||
<div class="container-image-section">
|
||||
<img src="assets/objects/${this.containerItem.texture.key}.png"
|
||||
<img src="/break_escape/assets/objects/${this.containerItem.texture.key}.png"
|
||||
alt="${this.containerItem.scenarioData.name}"
|
||||
class="container-image">
|
||||
<div class="container-info">
|
||||
@@ -184,7 +184,7 @@ export class ContainerMinigame extends MinigameScene {
|
||||
|
||||
const itemImg = document.createElement('img');
|
||||
itemImg.className = 'container-content-item';
|
||||
itemImg.src = `assets/objects/${item.type}.png`;
|
||||
itemImg.src = `/break_escape/assets/objects/${item.type}.png`;
|
||||
itemImg.alt = item.name;
|
||||
itemImg.title = item.name;
|
||||
|
||||
@@ -230,7 +230,7 @@ export class ContainerMinigame extends MinigameScene {
|
||||
|
||||
const iconImg = document.createElement('img');
|
||||
iconImg.className = 'desktop-icon-image';
|
||||
iconImg.src = `assets/objects/${item.type}.png`;
|
||||
iconImg.src = `/break_escape/assets/objects/${item.type}.png`;
|
||||
iconImg.alt = item.name;
|
||||
|
||||
const iconLabel = document.createElement('div');
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MinigameScene } from '../framework/base-minigame.js';
|
||||
// Load dusting-specific CSS
|
||||
const dustingCSS = document.createElement('link');
|
||||
dustingCSS.rel = 'stylesheet';
|
||||
dustingCSS.href = 'css/dusting.css';
|
||||
dustingCSS.href = '/break_escape/css/dusting.css';
|
||||
dustingCSS.id = 'dusting-css';
|
||||
if (!document.getElementById('dusting-css')) {
|
||||
document.head.appendChild(dustingCSS);
|
||||
|
||||
@@ -85,6 +85,12 @@ export const MinigameFramework = {
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// Show the popup overlay to darken the background
|
||||
const popupOverlay = document.querySelector('.popup-overlay');
|
||||
if (popupOverlay) {
|
||||
popupOverlay.classList.add('active');
|
||||
}
|
||||
|
||||
// Create and start the minigame
|
||||
const MinigameClass = this.registeredScenes[sceneType];
|
||||
this.currentMinigame = new MinigameClass(container, params);
|
||||
@@ -101,6 +107,12 @@ export const MinigameFramework = {
|
||||
console.log('Cleaning up current minigame');
|
||||
this.currentMinigame.cleanup();
|
||||
|
||||
// Hide the popup overlay
|
||||
const popupOverlay = document.querySelector('.popup-overlay');
|
||||
if (popupOverlay) {
|
||||
popupOverlay.classList.remove('active');
|
||||
}
|
||||
|
||||
// Remove minigame container only if it was auto-created
|
||||
const container = document.querySelector('.minigame-container');
|
||||
if (container && !container.hasAttribute('data-external')) {
|
||||
|
||||
@@ -302,7 +302,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
const switchModeBtn = document.createElement('button');
|
||||
switchModeBtn.className = 'minigame-button';
|
||||
switchModeBtn.id = 'lockpicking-switch-mode-btn';
|
||||
switchModeBtn.innerHTML = '<img src="assets/objects/lockpick.png" alt="Lockpick" class="icon-large"> Switch to Lockpicking';
|
||||
switchModeBtn.innerHTML = '<img src="/break_escape/assets/objects/lockpick.png" alt="Lockpick" class="icon-large"> Switch to Lockpicking';
|
||||
switchModeBtn.onclick = () => this.toolMgr.switchToPickMode();
|
||||
|
||||
buttonContainer.appendChild(switchModeBtn);
|
||||
@@ -320,7 +320,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
const switchModeBtn = document.createElement('button');
|
||||
switchModeBtn.className = 'minigame-button';
|
||||
switchModeBtn.id = 'lockpicking-switch-to-keys-btn';
|
||||
switchModeBtn.innerHTML = '<img src="assets/objects/key.png" alt="Key" class="icon-large"> Switch to Key Mode';
|
||||
switchModeBtn.innerHTML = '<img src="/break_escape/assets/objects/key.png" alt="Key" class="icon-large"> Switch to Key Mode';
|
||||
switchModeBtn.onclick = () => this.toolMgr.switchToKeyMode();
|
||||
|
||||
buttonContainer.appendChild(switchModeBtn);
|
||||
|
||||
@@ -238,7 +238,7 @@ export class ToolManager {
|
||||
const switchModeBtn = document.createElement('button');
|
||||
switchModeBtn.className = 'minigame-button';
|
||||
switchModeBtn.id = 'lockpicking-switch-mode-btn';
|
||||
switchModeBtn.innerHTML = '<img src="assets/objects/lockpick.png" alt="Lockpick" class="icon-large"> Switch to Lockpicking';
|
||||
switchModeBtn.innerHTML = '<img src="/break_escape/assets/objects/lockpick.png" alt="Lockpick" class="icon-large"> Switch to Lockpicking';
|
||||
switchModeBtn.onclick = () => this.switchToPickMode();
|
||||
|
||||
buttonContainer.appendChild(switchModeBtn);
|
||||
|
||||
@@ -100,7 +100,7 @@ export class NotesMinigame extends MinigameScene {
|
||||
// Add star icon for important notes
|
||||
if (isImportant) {
|
||||
const starIcon = document.createElement('img');
|
||||
starIcon.src = 'assets/icons/star.png';
|
||||
starIcon.src = '/break_escape/assets/icons/star.png';
|
||||
starIcon.alt = 'Important';
|
||||
starIcon.className = 'notes-minigame-star';
|
||||
noteTitle.appendChild(starIcon);
|
||||
@@ -135,7 +135,7 @@ export class NotesMinigame extends MinigameScene {
|
||||
|
||||
// Add pencil icon
|
||||
const pencilIcon = document.createElement('img');
|
||||
pencilIcon.src = 'assets/icons/pencil.png';
|
||||
pencilIcon.src = '/break_escape/assets/icons/pencil.png';
|
||||
pencilIcon.alt = 'Edit';
|
||||
editBtn.appendChild(pencilIcon);
|
||||
|
||||
@@ -163,7 +163,7 @@ export class NotesMinigame extends MinigameScene {
|
||||
|
||||
// Add pencil icon
|
||||
const pencilIcon = document.createElement('img');
|
||||
pencilIcon.src = 'assets/icons/pencil.png';
|
||||
pencilIcon.src = '/break_escape/assets/icons/pencil.png';
|
||||
pencilIcon.alt = 'Edit';
|
||||
editBtn.appendChild(pencilIcon);
|
||||
|
||||
@@ -322,7 +322,7 @@ export class NotesMinigame extends MinigameScene {
|
||||
// Add star icon for important notes
|
||||
if (isImportant) {
|
||||
const starIcon = document.createElement('img');
|
||||
starIcon.src = 'assets/icons/star.png';
|
||||
starIcon.src = '/break_escape/assets/icons/star.png';
|
||||
starIcon.alt = 'Important';
|
||||
starIcon.className = 'notes-minigame-star';
|
||||
noteTitle.appendChild(starIcon);
|
||||
|
||||
@@ -41,7 +41,7 @@ export class PasswordMinigame extends MinigameScene {
|
||||
const notebookBtn = document.createElement('button');
|
||||
notebookBtn.className = 'minigame-button';
|
||||
notebookBtn.id = 'minigame-notebook-postit';
|
||||
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
|
||||
notebookBtn.innerHTML = '<img src="/break_escape/assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
|
||||
// Insert before the cancel button (first child in controls)
|
||||
this.controlsElement.insertBefore(notebookBtn, this.controlsElement.firstChild);
|
||||
}
|
||||
@@ -80,7 +80,7 @@ export class PasswordMinigame extends MinigameScene {
|
||||
<div class="password-minigame-area">
|
||||
${imageData ? `
|
||||
<div class="password-image-section">
|
||||
<img src="assets/objects/${imageData.imageFile}.png"
|
||||
<img src="/break_escape/assets/objects/${imageData.imageFile}.png"
|
||||
alt="${imageData.deviceName}"
|
||||
class="password-image">
|
||||
<div class="password-info">
|
||||
@@ -100,7 +100,7 @@ export class PasswordMinigame extends MinigameScene {
|
||||
placeholder="Enter password..."
|
||||
maxlength="50">
|
||||
<button type="button" class="toggle-password-btn" id="toggle-password">
|
||||
<img class="icon-small" src="${this.gameData.showPassword ? 'assets/icons/visible.png' : 'assets/icons/hidden.png'}" alt="Toggle password visibility">
|
||||
<img class="icon-small" src="/break_escape/assets/icons/${this.gameData.showPassword ? 'visible.png' : 'hidden.png'}" alt="Toggle password visibility">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,7 +128,7 @@ export class PasswordMinigame extends MinigameScene {
|
||||
<div class="password-controls">
|
||||
${this.gameData.showKeyboard ? `
|
||||
<button type="button" class="keyboard-toggle-btn" id="keyboard-toggle">
|
||||
<img class="icon-keyboard" src="assets/objects/keyboard1.png" alt="Toggle keyboard">
|
||||
<img class="icon-keyboard" src="/break_escape/assets/objects/keyboard1.png" alt="Toggle keyboard">
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@ export class PhoneChatMinigame extends MinigameScene {
|
||||
const notebookBtn = document.createElement('button');
|
||||
notebookBtn.className = 'minigame-button';
|
||||
notebookBtn.id = 'minigame-notebook';
|
||||
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
|
||||
notebookBtn.innerHTML = '<img src="/break_escape/assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
|
||||
// Insert before the cancel/close button
|
||||
const cancelBtn = this.controlsElement.querySelector('#minigame-cancel');
|
||||
if (cancelBtn) {
|
||||
|
||||
@@ -268,11 +268,11 @@ export default class PhoneChatUI {
|
||||
|
||||
if (playing) {
|
||||
// Show stop icon
|
||||
playButton.innerHTML = '<img src="assets/icons/stop.png" alt="Stop" class="icon">';
|
||||
playButton.innerHTML = '<img src="/break_escape/assets/icons/stop.png" alt="Stop" class="icon">';
|
||||
playButton.title = 'Stop';
|
||||
} else {
|
||||
// Show play icon
|
||||
playButton.innerHTML = '<img src="assets/icons/play.png" alt="Play" class="icon">';
|
||||
playButton.innerHTML = '<img src="/break_escape/assets/icons/play.png" alt="Play" class="icon">';
|
||||
playButton.title = 'Play';
|
||||
}
|
||||
}
|
||||
@@ -489,13 +489,13 @@ export default class PhoneChatUI {
|
||||
const playButton = document.createElement('div');
|
||||
playButton.className = 'play-button';
|
||||
const playIcon = document.createElement('img');
|
||||
playIcon.src = 'assets/icons/play.png';
|
||||
playIcon.src = '/break_escape/assets/icons/play.png';
|
||||
playIcon.alt = 'Play';
|
||||
playIcon.className = 'icon';
|
||||
playButton.appendChild(playIcon);
|
||||
|
||||
const audioSprite = document.createElement('img');
|
||||
audioSprite.src = 'assets/mini-games/audio.png';
|
||||
audioSprite.src = '/break_escape/assets/mini-games/audio.png';
|
||||
audioSprite.alt = 'Audio';
|
||||
audioSprite.className = 'audio-sprite';
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ export class PinMinigame extends MinigameScene {
|
||||
|
||||
// Add pin-cracker icon
|
||||
this.pinCrackerIconElement = document.createElement('img');
|
||||
this.pinCrackerIconElement.src = 'assets/objects/pin-cracker.png';
|
||||
this.pinCrackerIconElement.src = '/break_escape/assets/objects/pin-cracker.png';
|
||||
this.pinCrackerIconElement.alt = 'Pin Cracker';
|
||||
this.pinCrackerIconElement.className = 'pin-minigame-cracker-icon';
|
||||
this.pinCrackerIconElement.style.display = 'inline-block'; // Show by default when pin-cracker is available
|
||||
|
||||
@@ -23,7 +23,7 @@ export class TextFileMinigame extends MinigameScene {
|
||||
|
||||
// Customize the header
|
||||
this.headerElement.innerHTML = `
|
||||
<h3><img src="assets/icons/text-file.png" alt="Document" class="icon"> ${this.textFileData.fileName}</h3>
|
||||
<h3><img src="/break_escape/assets/icons/text-file.png" alt="Document" class="icon"> ${this.textFileData.fileName}</h3>
|
||||
<p>Viewing text file contents</p>
|
||||
`;
|
||||
|
||||
@@ -32,7 +32,7 @@ export class TextFileMinigame extends MinigameScene {
|
||||
const notebookBtn = document.createElement('button');
|
||||
notebookBtn.className = 'minigame-button';
|
||||
notebookBtn.id = 'minigame-notebook';
|
||||
notebookBtn.innerHTML = '<img src="assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
|
||||
notebookBtn.innerHTML = '<img src="/break_escape/assets/icons/notes-sm.png" alt="Notepad" class="icon-small"> Add to Notepad';
|
||||
this.controlsElement.appendChild(notebookBtn);
|
||||
|
||||
// Change cancel button text to "Close"
|
||||
@@ -64,7 +64,7 @@ export class TextFileMinigame extends MinigameScene {
|
||||
</div>
|
||||
|
||||
<div class="file-header">
|
||||
<div class="file-icon"><img src="assets/objects/text_file.png" alt="Document" class="icon-large"></div>
|
||||
<div class="file-icon"><img src="/break_escape/assets/objects/text_file.png" alt="Document" class="icon-large"></div>
|
||||
<div class="file-info">
|
||||
<div class="file-name">${this.textFileData.fileName}</div>
|
||||
<div class="file-meta">
|
||||
@@ -77,7 +77,7 @@ export class TextFileMinigame extends MinigameScene {
|
||||
<div class="file-content-area">
|
||||
<div class="content-header">
|
||||
<div class="content-actions">
|
||||
<button class="action-btn" id="copy-btn" title="Copy to clipboard"><img src="assets/icons/copy-sm.png" alt="Clipboard" class="icon-small"> Copy</button>
|
||||
<button class="action-btn" id="copy-btn" title="Copy to clipboard"><img src="/break_escape/assets/icons/copy-sm.png" alt="Clipboard" class="icon-small"> Copy</button>
|
||||
<button class="action-btn" id="select-all-btn" title="Select all text">Select All</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,7 +88,7 @@ export class TextFileMinigame extends MinigameScene {
|
||||
|
||||
${this.textFileData.observations ? `
|
||||
<div class="file-observations">
|
||||
<h4><img src="assets/icons/copy-sm.png" alt="Clipboard" class="icon-small"> Observations:</h4>
|
||||
<h4><img src="/break_escape/assets/icons/copy-sm.png" alt="Clipboard" class="icon-small"> Observations:</h4>
|
||||
<p>${this.textFileData.observations}</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MinigameScene } from '../framework/base-minigame.js';
|
||||
// Load title screen CSS
|
||||
const titleScreenCSS = document.createElement('link');
|
||||
titleScreenCSS.rel = 'stylesheet';
|
||||
titleScreenCSS.href = 'css/title-screen.css';
|
||||
titleScreenCSS.href = '/break_escape/css/title-screen.css';
|
||||
titleScreenCSS.id = 'title-screen-css';
|
||||
if (!document.getElementById('title-screen-css')) {
|
||||
document.head.appendChild(titleScreenCSS);
|
||||
@@ -26,7 +26,7 @@ export class TitleScreenMinigame extends MinigameScene {
|
||||
|
||||
this.container.innerHTML = `
|
||||
<div class="title-screen-container">
|
||||
<img src="assets/logos/hacktivity-logo.svg" alt="Hacktivity Logo" class="title-screen-logo">
|
||||
<img src="/break_escape/assets/logos/hacktivity-logo.svg" alt="Hacktivity Logo" class="title-screen-logo">
|
||||
<div class="title-screen-title">BreakEscape</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -242,7 +242,7 @@ export function addToInventory(sprite) {
|
||||
// Create inventory item
|
||||
const itemImg = document.createElement('img');
|
||||
itemImg.className = 'inventory-item';
|
||||
itemImg.src = `assets/objects/${sprite.texture?.key || sprite.name || sprite.scenarioData?.type}.png`;
|
||||
itemImg.src = `/break_escape/assets/objects/${sprite.texture?.key || sprite.name || sprite.scenarioData?.type}.png`;
|
||||
itemImg.alt = sprite.scenarioData.name;
|
||||
|
||||
// Create tooltip
|
||||
@@ -440,7 +440,7 @@ function updateKeyRingDisplay() {
|
||||
// Create key ring item
|
||||
const itemImg = document.createElement('img');
|
||||
itemImg.className = 'inventory-item';
|
||||
itemImg.src = keyRing.keys.length === 1 ? `assets/objects/key.png` : `assets/objects/key-ring.png`;
|
||||
itemImg.src = keyRing.keys.length === 1 ? `/break_escape/assets/objects/key.png` : `/break_escape/assets/objects/key-ring.png`;
|
||||
itemImg.alt = keyRing.keys.length === 1 ? keyRing.keys[0].scenarioData.name : 'Key Ring';
|
||||
|
||||
// Add data attributes for styling
|
||||
|
||||
@@ -94,7 +94,7 @@ export function startLockpickingMinigame(lockable, scene, difficulty = 'medium',
|
||||
} else {
|
||||
// This is a regular item - use scenarioData
|
||||
itemName = lockable?.scenarioData?.name || lockable?.name || 'Locked Item';
|
||||
itemImage = lockable?.texture?.key ? `assets/objects/${lockable.texture.key}.png` : null;
|
||||
itemImage = lockable?.texture?.key ? `/break_escape/assets/objects/${lockable.texture.key}.png` : null;
|
||||
itemObservations = lockable?.scenarioData?.observations || '';
|
||||
}
|
||||
|
||||
@@ -397,7 +397,7 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe
|
||||
} else {
|
||||
// This is a regular item - use scenarioData
|
||||
itemName = lockable?.scenarioData?.name || lockable?.name || 'Locked Item';
|
||||
itemImage = lockable?.texture?.key ? `assets/objects/${lockable.texture.key}.png` : null;
|
||||
itemImage = lockable?.texture?.key ? `/break_escape/assets/objects/${lockable.texture.key}.png` : null;
|
||||
itemObservations = lockable?.scenarioData?.observations || '';
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ export class GameOverScreen {
|
||||
|
||||
mainMenu() {
|
||||
// Navigate to scenario select or main menu
|
||||
window.location.href = 'scenario_select.html';
|
||||
window.location.href = '/break_escape/missions';
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
||||
@@ -34,7 +34,7 @@ export class HealthUI {
|
||||
for (let i = 0; i < COMBAT_CONFIG.ui.maxHearts; i++) {
|
||||
const heart = document.createElement('img');
|
||||
heart.className = 'health-heart';
|
||||
heart.src = 'assets/icons/heart.png';
|
||||
heart.src = '/break_escape/assets/icons/heart.png';
|
||||
heart.alt = 'HP';
|
||||
heartsContainer.appendChild(heart);
|
||||
this.hearts.push(heart);
|
||||
@@ -84,15 +84,15 @@ export class HealthUI {
|
||||
this.hearts.forEach((heart, index) => {
|
||||
if (index < fullHearts) {
|
||||
// Full heart
|
||||
heart.src = 'assets/icons/heart.png';
|
||||
heart.src = '/break_escape/assets/icons/heart.png';
|
||||
heart.style.opacity = '1';
|
||||
} else if (index === fullHearts && halfHeart) {
|
||||
// Half heart
|
||||
heart.src = 'assets/icons/heart-half.png';
|
||||
heart.src = '/break_escape/assets/icons/heart-half.png';
|
||||
heart.style.opacity = '1';
|
||||
} else {
|
||||
// Empty heart
|
||||
heart.src = 'assets/icons/heart.png';
|
||||
heart.src = '/break_escape/assets/icons/heart.png';
|
||||
heart.style.opacity = '0.2';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -48,6 +48,9 @@ export const GAME_CONFIG = typeof Phaser !== 'undefined' ? {
|
||||
height: 480, // Classic pixel art base resolution (scales cleanly: 1x=240, 2x=480, 3x=720, 4x=960)
|
||||
parent: 'game-container',
|
||||
pixelArt: true,
|
||||
loader: {
|
||||
baseURL: '/break_escape/'
|
||||
},
|
||||
scale: {
|
||||
mode: Phaser.Scale.ENVELOP, // Fill entire container while maintaining aspect ratio
|
||||
autoCenter: Phaser.Scale.CENTER_BOTH,
|
||||
|
||||
@@ -16,7 +16,7 @@ export function openCryptoWorkstation() {
|
||||
const cyberchefFrame = document.getElementById('cyberchef-frame');
|
||||
|
||||
// Set the iframe source to the CyberChef HTML file
|
||||
cyberchefFrame.src = 'assets/cyberchef/CyberChef_v10.19.4.html';
|
||||
cyberchefFrame.src = '/break_escape/assets/cyberchef/CyberChef_v10.19.4.html';
|
||||
|
||||
// Show the laptop popup
|
||||
laptopPopup.style.display = 'block';
|
||||
|
||||
Reference in New Issue
Block a user