Update key-demo.html and lockpicking minigame: Enhance key mode functionality by introducing a random key selection feature, allowing players to choose from three random keys. Update UI instructions for clarity and improve feedback messages based on key selection outcomes. Adjust game parameters to streamline the key selection process and ensure a smoother gameplay experience.

WiP updates to the main game including improved doors, animations, and player depth. Locks currently disabled.
This commit is contained in:
Z. Cliffe Schreuders
2025-08-31 23:10:58 +01:00
parent d9b5f309d2
commit 3940d04a62
14 changed files with 2901 additions and 602 deletions

View File

@@ -5,7 +5,7 @@ Break Escape is an escape room-inspired games-based learning framework that simu
**Note: Break Escape is currently in development. Please report any issues or feedback via GitHub.**
## Live Demo
## Live Demo -- Early Beta Playtesting
You can try Break Escape directly from your browser by visiting:
https://hacktivity.co.uk/break-escape-beta/scenario_select.html

View File

@@ -3,15 +3,15 @@
"infinite":false,
"layers":[
{
"data":[3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 0, 0, 26, 27, 28, 29, 0, 0, 32,
33, 0, 0, 0, 0, 0, 0, 0, 0, 42,
43, 0, 0, 0, 0, 0, 0, 0, 0, 52,
53, 0, 0, 0, 0, 0, 0, 0, 0, 62,
63, 0, 0, 0, 0, 0, 0, 0, 0, 72,
73, 0, 0, 0, 0, 0, 0, 0, 0, 82,
83, 84, 85, 86, 87, 88, 89, 90, 91, 92],
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 0, 12, 24, 12, 12, 27, 28, 0, 30,
31, 0, 0, 0, 0, 0, 0, 0, 0, 40,
41, 0, 0, 0, 0, 0, 0, 0, 0, 50,
51, 0, 0, 0, 0, 0, 0, 0, 0, 60,
61, 0, 0, 0, 0, 0, 0, 0, 0, 70,
71, 0, 0, 0, 0, 0, 0, 0, 0, 80,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90],
"height":9,
"id":8,
"name":"walls",
@@ -23,15 +23,15 @@
"y":0
},
{
"data":[3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
83, 84, 85, 86, 87, 88, 89, 90, 91, 92],
"data":[1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90],
"height":9,
"id":12,
"name":"ROOM",
@@ -43,13 +43,13 @@
"y":0
},
{
"data":[0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
0, 2, 0, 0, 0, 0, 0, 0, 2, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
"data":[0, 101, 0, 0, 0, 0, 0, 0, 101, 0,
0, 103, 0, 0, 0, 0, 0, 0, 103, 0,
102, 0, 0, 0, 0, 0, 0, 0, 0, 104,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
102, 0, 0, 0, 0, 0, 0, 0, 0, 104,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":9,
@@ -149,44 +149,43 @@
"width":48,
"x":143.838518642089,
"y":191.623488197514
},
},
{
"gid":12329,
"height":48,
"id":13,
"name":"safe",
"rotation":0,
"type":"",
"visible":true,
"width":48,
"x":192,
"y":96
},
{
"gid":12329,
"height":48,
"id":14,
"name":"safe2",
"rotation":0,
"type":"",
"visible":true,
"width":48,
"x":140,
"y":300
},
{
"gid":12329,
"height":48,
"id":15,
"name":"safe3",
"rotation":0,
"type":"",
"visible":true,
"width":48,
"x":350,
"y":250
}
],
"gid":12327,
"height":48,
"id":13,
"name":"safe",
"rotation":0,
"type":"",
"visible":true,
"width":48,
"x":192,
"y":96
},
{
"gid":12327,
"height":48,
"id":14,
"name":"safe2",
"rotation":0,
"type":"",
"visible":true,
"width":48,
"x":140,
"y":300
},
{
"gid":12327,
"height":48,
"id":15,
"name":"safe3",
"rotation":0,
"type":"",
"visible":true,
"width":48,
"x":350,
"y":250
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
@@ -197,25 +196,12 @@
"nextobjectid":13,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.11.0",
"tiledversion":"1.11.2",
"tileheight":48,
"tilesets":[
{
"columns":1,
"firstgid":1,
"image":"..\/tiles\/door.png",
"imageheight":96,
"imagewidth":48,
"margin":0,
"name":"door",
"spacing":0,
"tilecount":2,
"tileheight":48,
"tilewidth":48
},
{
"columns":10,
"firstgid":3,
"firstgid":1,
"image":"room_reception_l.png",
"imageheight":480,
"imagewidth":480,
@@ -224,10 +210,543 @@
"spacing":0,
"tilecount":100,
"tileheight":48,
"tiles":[
{
"id":11,
"objectgroup":
{
"draworder":"index",
"id":2,
"name":"",
"objects":[
{
"height":2.75,
"id":1,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":47.625,
"x":0.25,
"y":45.25
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":12,
"objectgroup":
{
"draworder":"index",
"id":3,
"name":"",
"objects":[
{
"height":2.875,
"id":5,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":47.75,
"x":0.25,
"y":45.125
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":13,
"objectgroup":
{
"draworder":"index",
"id":2,
"name":"",
"objects":[
{
"height":2.875,
"id":3,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":47.75,
"x":0.125,
"y":45.0625
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":14,
"objectgroup":
{
"draworder":"index",
"id":2,
"name":"",
"objects":[
{
"height":1.5,
"id":1,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":47.625,
"x":0.25,
"y":46.5
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":15,
"objectgroup":
{
"draworder":"index",
"id":2,
"name":"",
"objects":[
{
"height":2.875,
"id":2,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":47.75,
"x":0.125,
"y":44.8125
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":16,
"objectgroup":
{
"draworder":"index",
"id":2,
"name":"",
"objects":[
{
"height":2.875,
"id":1,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":47.75,
"x":0.125,
"y":39.0625
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":17,
"objectgroup":
{
"draworder":"index",
"id":2,
"name":"",
"objects":[
{
"height":2.875,
"id":1,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":47.875,
"x":0.25,
"y":45.25
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":18,
"objectgroup":
{
"draworder":"index",
"id":3,
"name":"",
"objects":[
{
"height":2.875,
"id":2,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":47.75,
"x":0.25,
"y":45.25
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":22,
"objectgroup":
{
"draworder":"index",
"id":2,
"name":"",
"objects":[
{
"height":0,
"id":3,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":28.6021787636173,
"y":9.70206063787899
},
{
"height":0,
"id":6,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":28.7281795511222,
"y":18.0181126132038
},
{
"height":0,
"id":7,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":30.7441921512009,
"y":20.0341252132826
},
{
"height":0,
"id":8,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":33.2642079012994,
"y":21.6721354508466
},
{
"height":0,
"id":9,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":34.7762173513584,
"y":24.0661504134401
},
{
"height":0,
"id":10,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":38.0522378264864,
"y":26.0821630135188
},
{
"height":0,
"id":11,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":42.8402677516735,
"y":26.8381677385484
},
{
"height":0,
"id":12,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":46.9982937393359,
"y":28.8541803386271
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":23,
"objectgroup":
{
"draworder":"index",
"id":2,
"name":"",
"objects":[
{
"height":0,
"id":1,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":0.882005512534453,
"y":28.8541803386271
},
{
"height":0,
"id":2,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":20.6641291508072,
"y":26.2081638010238
},
{
"height":0,
"id":3,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":40.320252001575,
"y":26.0821630135188
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":24,
"objectgroup":
{
"draworder":"index",
"id":3,
"name":"",
"objects":[
{
"height":17.3881086756792,
"id":3,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":48.7623047644048,
"x":0.252001575009844,
"y":18.3961149757186
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":25,
"objectgroup":
{
"draworder":"index",
"id":2,
"name":"",
"objects":[
{
"height":17.2621078881743,
"id":1,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":49.6443102769392,
"x":0.126000787504922,
"y":18.5221157632235
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":26,
"objectgroup":
{
"draworder":"index",
"id":3,
"name":"",
"objects":[
{
"height":21.4201338758367,
"id":3,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":17.2621078881743,
"x":30.618191363696,
"y":17.8921118256989
},
{
"height":17.8921118256989,
"id":4,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":40.4462527890799,
"x":-9.82806142538391,
"y":18.2701141882137
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
},
{
"id":27,
"objectgroup":
{
"draworder":"index",
"id":2,
"name":"",
"objects":[
{
"height":20.916130725817,
"id":1,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":8.56805355033469,
"x":0.126000787504922,
"y":18.2701141882137
},
{
"height":0,
"id":2,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":6.3000393752461,
"y":26.4601653760336
},
{
"height":0,
"id":3,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":10.7100669379184,
"y":23.6881480509253
},
{
"height":0,
"id":4,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":14.7420921380759,
"y":20.7901299383121
},
{
"height":0,
"id":5,
"name":"",
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":14.7420921380759,
"y":6.55204095025594
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}
}],
"tilewidth":48
},
{
"columns":2,
"firstgid":101,
"image":"..\/tiles\/door_tiles.png",
"imageheight":96,
"imagewidth":96,
"margin":0,
"name":"door_tiles",
"spacing":0,
"tilecount":4,
"tileheight":48,
"tilewidth":48
}],
"tilewidth":48,
"type":"map",
"version":"1.10",
"width":10
}
}

BIN
assets/tiles/door_sheet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -151,7 +151,7 @@
<div class="popup-overlay"></div>
<!-- Main Game JavaScript Module -->
<script type="module" src="js/main.js?v=32"></script>
<script type="module" src="js/main.js?v=33"></script>
<!-- Mobile touch handling -->
<script>

View File

@@ -1,8 +1,8 @@
import { initializeRooms, validateDoorsByRoomOverlap, calculateWorldBounds, calculateRoomPositions, createRoom, revealRoom, updatePlayerRoom, rooms } from './rooms.js?v=16';
import { initializeRooms, calculateWorldBounds, calculateRoomPositions, createRoom, revealRoom, updatePlayerRoom, rooms } from './rooms.js?v=16';
import { createPlayer, updatePlayerMovement, movePlayerToPoint, player } from './player.js?v=7';
import { initializePathfinder } from './pathfinding.js?v=7';
import { initializeInventory, processInitialInventoryItems } from '../systems/inventory.js?v=8';
import { checkObjectInteractions, processAllDoorCollisions, setGameInstance, setupDoorOverlapChecks } from '../systems/interactions.js?v=23';
import { checkObjectInteractions, setGameInstance } from '../systems/interactions.js?v=23';
import { introduceScenario } from '../utils/helpers.js?v=19';
import '../minigames/index.js?v=2';
@@ -28,6 +28,10 @@ export function preload() {
this.load.image('room_ceo_l', 'assets/rooms/room_ceo_l.png');
this.load.image('room_spooky_basement_l', 'assets/rooms/room_spooky_basement_l.png');
this.load.image('door', 'assets/tiles/door.png');
this.load.spritesheet('door_sheet', 'assets/tiles/door_sheet.png', {
frameWidth: 48,
frameHeight: 96
});
// Load object sprites
this.load.image('pc', 'assets/objects/pc.png');
@@ -91,25 +95,36 @@ export function create() {
// Store player globally for access from other modules
window.player = player;
// Create door opening animation
this.anims.create({
key: 'door_open',
frames: this.anims.generateFrameNumbers('door_sheet', { start: 0, end: 4 }),
frameRate: 8,
repeat: 0
});
// Create door top animation (6th frame)
this.anims.create({
key: 'door_top',
frames: [{ key: 'door_sheet', frame: 5 }],
frameRate: 1,
repeat: 0
});
// Initialize rooms system after player exists
initializeRooms(this);
// Calculate room positions
// Create only the starting room initially
const roomPositions = calculateRoomPositions(this);
const startingRoomData = gameScenario.rooms[gameScenario.startRoom];
const startingRoomPosition = roomPositions[gameScenario.startRoom];
// Create all rooms
Object.entries(gameScenario.rooms).forEach(([roomId, roomData]) => {
const position = roomPositions[roomId];
if (position) {
createRoom(roomId, roomData, position);
}
});
// Validate doors by checking room overlaps
validateDoorsByRoomOverlap();
// Reveal starting room early like in original
revealRoom(gameScenario.startRoom);
if (startingRoomData && startingRoomPosition) {
createRoom(gameScenario.startRoom, startingRoomData, startingRoomPosition);
revealRoom(gameScenario.startRoom);
} else {
console.error('Failed to create starting room');
}
// Position player in the starting room
const startingRoom = rooms[gameScenario.startRoom];
@@ -124,11 +139,7 @@ export function create() {
this.cameras.main.startFollow(player);
this.cameras.main.setZoom(1);
// Process door collisions after rooms are revealed
processAllDoorCollisions();
// Setup door overlap checks
setupDoorOverlapChecks();
// Door interactions are now handled by the door sprites themselves
// Initialize pathfinder
initializePathfinder(this);

View File

@@ -43,8 +43,8 @@ export function createPlayer(gameInstance) {
player.body.setDrag(0);
player.body.setFriction(0);
// Set player depth to ensure it renders above most objects
player.setDepth(2000);
// Set initial player depth (will be updated dynamically during movement)
updatePlayerDepth(startRoomPosition.x, startRoomPosition.y);
// Track player direction and movement state
player.direction = 'down'; // Initial direction
@@ -149,6 +149,38 @@ export function movePlayerToPoint(x, y) {
isMoving = true;
}
function updatePlayerDepth(x, y) {
// Calculate dynamic depth based on Y position
// This creates the effect where player appears behind objects when north of them
// and in front when south of them
// Get the bottom of the player sprite (feet position)
// Since player origin is at center, bottom is y + half the scaled height
const playerBottomY = y + (player.height * player.scaleY) / 2;
// Calculate room depth based on player position with finer granularity
// Use 50-pixel boundaries instead of 100 for smoother depth transitions
const roomDepth = Math.floor(playerBottomY / 50) * 50;
// Player should use the same depth calculation as objects for proper layering
// This allows player and objects to layer relative to each other based on Y position
const playerDepth = roomDepth + 500; // Same as objects - above all room layers
// Set the player depth (always update, no threshold)
if (player) {
player.setDepth(playerDepth);
// Debug logging - only show when depth actually changes significantly
const lastDepth = player.lastDepth || 0;
if (Math.abs(playerDepth - lastDepth) > 25) { // Reduced threshold for finer granularity
console.log(`Player depth: ${playerDepth} (Y: ${y}, BottomY: ${playerBottomY}, RoomDepth: ${roomDepth})`);
console.log(` Player uses same depth calculation as objects: roomDepth + 500`);
console.log(` Room layer depths: floor=${roomDepth + 100}, collision=${roomDepth + 150}, walls=${roomDepth + 200}, props=${roomDepth + 300}, other=${roomDepth + 400}`);
player.lastDepth = playerDepth;
}
}
}
function createClickIndicator(x, y) {
// Create a circle at the click position
const indicator = gameRef.add.circle(x, y, CLICK_INDICATOR_SIZE, 0xffffff, 0.7);
@@ -188,6 +220,9 @@ export function updatePlayerMovement() {
const px = player.x;
const py = player.y + PLAYER_FEET_OFFSET_Y; // Add offset to target the feet
// Update player depth based on actual player position (not feet-adjusted)
updatePlayerDepth(px, player.y);
// Use squared distance for performance
const dx = targetPoint.x - px;
const dy = targetPoint.y - py; // Compare with feet position
@@ -204,15 +239,9 @@ export function updatePlayerMovement() {
return;
}
// Only check room transitions periodically
const movedX = Math.abs(px - lastPlayerPosition.x);
const movedY = Math.abs(py - lastPlayerPosition.y);
if (movedX > ROOM_CHECK_THRESHOLD || movedY > ROOM_CHECK_THRESHOLD) {
// Room checking will be handled in game.js to avoid circular dependencies
lastPlayerPosition.x = px;
lastPlayerPosition.y = py - PLAYER_FEET_OFFSET_Y; // Store actual player position
}
// Update last player position for depth calculations
lastPlayerPosition.x = px;
lastPlayerPosition.y = py - PLAYER_FEET_OFFSET_Y; // Store actual player position
// Normalize movement vector for consistent speed
const distance = Math.sqrt(distanceSq);

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,9 @@ import { initializeDebugSystem } from './systems/debug.js?v=7';
import { initializeUI } from './ui/panels.js?v=9';
import { initializeModals } from './ui/modals.js?v=7';
// Import minigame framework
import './minigames/index.js';
// Global game variables
window.game = null;
window.gameScenario = null;

View File

@@ -9,10 +9,29 @@ export class LockpickingMinigamePhaser extends MinigameScene {
params = params || {};
this.lockable = params.lockable || 'default-lock';
this.lockId = params.lockId || 'default_lock';
this.difficulty = params.difficulty || 'medium';
// Use passed pinCount if provided, otherwise calculate based on difficulty
this.pinCount = params.pinCount || (this.difficulty === 'easy' ? 3 : this.difficulty === 'medium' ? 4 : 5);
// Initialize global lock storage if it doesn't exist
if (!window.lockConfigurations) {
window.lockConfigurations = {};
}
// Also try to load from localStorage for persistence across sessions
if (!window.lockConfigurations[this.lockId]) {
try {
const savedConfigs = localStorage.getItem('lockConfigurations');
if (savedConfigs) {
const parsed = JSON.parse(savedConfigs);
window.lockConfigurations = { ...window.lockConfigurations, ...parsed };
}
} catch (error) {
console.warn('Failed to load lock configurations from localStorage:', error);
}
}
// Threshold sensitivity for pin setting (1-10, higher = more sensitive)
this.thresholdSensitivity = params.thresholdSensitivity || 5;
@@ -34,6 +53,8 @@ export class LockpickingMinigamePhaser extends MinigameScene {
this.keyData = params.keyData || null; // Key data with cuts/ridges
this.keyInsertionProgress = 0; // 0 = not inserted, 1 = fully inserted
this.keyInserting = false;
this.skipStartingKey = params.skipStartingKey || false; // Skip creating initial key if true
this.keySelectionMode = false; // Track if we're in key selection mode
// Sound effects
this.sounds = {};
@@ -64,6 +85,97 @@ export class LockpickingMinigamePhaser extends MinigameScene {
this.scene = null;
}
saveLockConfiguration() {
// Save the current lock configuration to global storage and localStorage
if (this.pins && this.pins.length > 0) {
const pinHeights = this.pins.map(pin => pin.originalHeight);
const config = {
pinHeights: pinHeights,
pinCount: this.pinCount,
timestamp: Date.now()
};
// Save to memory
window.lockConfigurations[this.lockId] = config;
// Save to localStorage for persistence
try {
const savedConfigs = localStorage.getItem('lockConfigurations') || '{}';
const parsed = JSON.parse(savedConfigs);
parsed[this.lockId] = config;
localStorage.setItem('lockConfigurations', JSON.stringify(parsed));
} catch (error) {
console.warn('Failed to save lock configuration to localStorage:', error);
}
console.log(`Saved lock configuration for ${this.lockId}:`, pinHeights);
}
}
loadLockConfiguration() {
// Load lock configuration from global storage
const config = window.lockConfigurations[this.lockId];
if (config && config.pinHeights && config.pinHeights.length === this.pinCount) {
console.log(`Loaded lock configuration for ${this.lockId}:`, config.pinHeights);
return config.pinHeights;
}
return null;
}
clearLockConfiguration() {
// Clear the lock configuration for this lock
if (window.lockConfigurations[this.lockId]) {
delete window.lockConfigurations[this.lockId];
// Also remove from localStorage
try {
const savedConfigs = localStorage.getItem('lockConfigurations') || '{}';
const parsed = JSON.parse(savedConfigs);
delete parsed[this.lockId];
localStorage.setItem('lockConfigurations', JSON.stringify(parsed));
} catch (error) {
console.warn('Failed to clear lock configuration from localStorage:', error);
}
console.log(`Cleared lock configuration for ${this.lockId}`);
}
}
clearAllLockConfigurations() {
// Clear all lock configurations (useful for testing)
window.lockConfigurations = {};
// Also clear from localStorage
try {
localStorage.removeItem('lockConfigurations');
} catch (error) {
console.warn('Failed to clear all lock configurations from localStorage:', error);
}
console.log('Cleared all lock configurations');
}
resetPinsToOriginalPositions() {
// Reset all pins to their original positions (before any key insertion)
this.pins.forEach(pin => {
pin.currentHeight = 0;
pin.isSet = false;
// Clear any highlights
if (pin.shearHighlight) {
pin.shearHighlight.setVisible(false);
}
if (pin.setHighlight) {
pin.setHighlight.setVisible(false);
}
// Update pin visuals
this.updatePinVisuals(pin);
});
console.log('Reset all pins to original positions');
}
init() {
super.init();
@@ -177,11 +289,19 @@ export class LockpickingMinigamePhaser extends MinigameScene {
self.createHookPick();
self.createShearLine();
// Create key if in key mode
if (self.keyMode) {
// Create key if in key mode and not skipping starting key
if (self.keyMode && !self.skipStartingKey) {
self.createKey();
self.hideLockpickingTools();
self.updateFeedback("Click the key to insert it into the lock");
} else if (self.keyMode && self.skipStartingKey) {
// Skip creating initial key, will show key selection instead
// But we still need to initialize keyData for the correct key
if (!self.keyData) {
self.generateKeyDataFromPins();
}
self.hideLockpickingTools();
self.updateFeedback("Select a key to begin");
} else {
self.updateFeedback("Apply tension first, then lift pins in binding order - only the binding pin can be set");
}
@@ -604,6 +724,359 @@ export class LockpickingMinigamePhaser extends MinigameScene {
console.log('Generated key data from pins:', this.keyData);
}
createKeyFromPinSizes(pinSizes) {
// Create a complete key object based on a set of pin sizes
// pinSizes: array of numbers representing the depth of each cut (0-100)
const keyConfig = {
pinCount: pinSizes.length,
cuts: pinSizes,
// Standard key dimensions
circleRadius: 20,
shoulderWidth: 30,
shoulderHeight: 130,
bladeWidth: 420,
bladeHeight: 110,
keywayStartX: 100,
keywayStartY: 170,
keywayWidth: 400,
keywayHeight: 120
};
return keyConfig;
}
generateRandomKey(pinCount = 5) {
// Generate a random key with the specified number of pins
const cuts = [];
for (let i = 0; i < pinCount; i++) {
// Generate random cut depth between 20-80 (avoiding extremes)
cuts.push(Math.floor(Math.random() * 60) + 20);
}
return {
id: `random_key_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
cuts,
name: `Random Key`,
pinCount: pinCount
};
}
createKeysFromInventory(inventoryKeys, correctKeyId) {
// Create key selection from inventory keys
// inventoryKeys: array of key objects from player inventory
// correctKeyId: ID of the key that should work with this lock
// Filter keys to only include those with cuts data
const validKeys = inventoryKeys.filter(key => key.cuts && Array.isArray(key.cuts));
if (validKeys.length === 0) {
// No valid keys in inventory, generate random ones
const key1 = this.generateRandomKey(this.pinCount);
const key2 = this.generateRandomKey(this.pinCount);
const key3 = this.generateRandomKey(this.pinCount);
// Make the first key correct
key1.cuts = this.keyData.cuts;
key1.id = correctKeyId || 'correct_key';
key1.name = 'Correct Key';
// Randomize the order
const keys = [key1, key2, key3];
this.shuffleArray(keys);
return this.createKeySelectionUI(keys, correctKeyId);
}
// Use inventory keys and randomize their order
const shuffledKeys = [...validKeys];
this.shuffleArray(shuffledKeys);
return this.createKeySelectionUI(shuffledKeys, correctKeyId);
}
createKeysForChallenge(correctKeyId = 'challenge_key') {
// Create keys for challenge mode (like locksmith-forge.html)
// Generates 3 keys with one guaranteed correct key
const key1 = this.generateRandomKey(this.pinCount);
const key2 = this.generateRandomKey(this.pinCount);
const key3 = this.generateRandomKey(this.pinCount);
// Make the first key correct by copying the actual key cuts
key1.cuts = this.keyData.cuts;
key1.id = correctKeyId;
key1.name = 'Correct Key';
// Give other keys descriptive names
key2.name = 'Wrong Key 1';
key3.name = 'Wrong Key 2';
// Randomize the order of keys
const keys = [key1, key2, key3];
this.shuffleArray(keys);
// Find the new index of the correct key after shuffling
const correctKeyIndex = keys.findIndex(key => key.id === correctKeyId);
return this.createKeySelectionUI(keys, correctKeyId);
}
startWithKeySelection(inventoryKeys = null, correctKeyId = null) {
// Start the minigame with key selection instead of a default key
// inventoryKeys: array of keys from inventory (optional)
// correctKeyId: ID of the correct key (optional)
this.keySelectionMode = true; // Mark that we're in key selection mode
if (inventoryKeys && inventoryKeys.length > 0) {
// Use provided inventory keys
this.createKeysFromInventory(inventoryKeys, correctKeyId);
} else {
// Generate random keys for challenge
this.createKeysForChallenge(correctKeyId || 'challenge_key');
}
}
// Example usage:
//
// 1. For BreakEscape main game with inventory keys:
// const playerKeys = [
// { id: 'office_key', cuts: [45, 67, 23, 89, 34], name: 'Office Key' },
// { id: 'basement_key', cuts: [12, 78, 56, 23, 90], name: 'Basement Key' },
// { id: 'shed_key', cuts: [67, 34, 89, 12, 45], name: 'Shed Key' }
// ];
// this.startWithKeySelection(playerKeys, 'office_key');
//
// 2. For challenge mode (like locksmith-forge.html):
// this.startWithKeySelection(); // Generates 3 random keys, one correct
//
// 3. Skip starting key and go straight to selection:
// const minigame = new LockpickingMinigamePhaser(container, {
// keyMode: true,
// skipStartingKey: true, // Don't create initial key
// lockId: 'office_door_lock'
// });
// minigame.startWithKeySelection(playerKeys, 'office_key');
createKeySelectionUI(keys, correctKeyId = null) {
// Create a UI for selecting between multiple keys
// keys: array of key objects with id, cuts, and optional name properties
// correctKeyId: ID of the correct key (if null, uses index 0 as fallback)
// Find the correct key index
let correctKeyIndex = 0;
if (correctKeyId) {
correctKeyIndex = keys.findIndex(key => key.id === correctKeyId);
if (correctKeyIndex === -1) {
correctKeyIndex = 0; // Fallback to first key if ID not found
}
}
// Remove any existing key from the scene before showing selection UI
if (this.keyGroup) {
this.keyGroup.destroy();
this.keyGroup = null;
}
// Remove any existing click zone
if (this.keyClickZone) {
this.keyClickZone.destroy();
this.keyClickZone = null;
}
// Reset pins to their original positions before showing key selection
this.resetPinsToOriginalPositions();
// Create container for key selection - positioned in the middle but below pins
const keySelectionContainer = this.scene.add.container(0, 230);
keySelectionContainer.setDepth(1000); // High z-index to appear above everything
// Add background
const background = this.scene.add.graphics();
background.fillStyle(0x000000, 0.8);
background.fillRect(0, 0, 700, 180);
background.lineStyle(2, 0xffffff);
background.strokeRect(0, 0, 600, 170);
keySelectionContainer.add(background);
// Add title
const title = this.scene.add.text(300, 15, 'Select the correct key', {
fontSize: '24px',
fill: '#ffffff',
fontFamily: 'VT323',
});
title.setOrigin(0.5, 0);
keySelectionContainer.add(title);
// Create key options
const keyWidth = 140;
const keyHeight = 80;
const spacing = 20;
const startX = 50;
const startY = 50;
keys.forEach((keyData, index) => {
const keyX = startX + index * (keyWidth + spacing);
const keyY = startY;
// Create key visual representation
const keyVisual = this.createKeyVisual(keyData, keyWidth, keyHeight);
keyVisual.setPosition(keyX, keyY);
keySelectionContainer.add(keyVisual);
// Make key clickable
keyVisual.setInteractive(new Phaser.Geom.Rectangle(0, 0, keyWidth, keyHeight), Phaser.Geom.Rectangle.Contains);
keyVisual.on('pointerdown', () => {
// Close the popup
keySelectionContainer.destroy();
// Trigger key selection and insertion
this.selectKey(index, correctKeyIndex, keyData);
});
// Add key label (use name if available, otherwise use number)
const keyName = keyData.name || `Key ${index + 1}`;
const keyLabel = this.scene.add.text(keyX + keyWidth/2, keyY + keyHeight + 5, keyName, {
fontSize: '16px',
fill: '#ffffff',
fontFamily: 'VT323'
});
keyLabel.setOrigin(0.5, 0);
keySelectionContainer.add(keyLabel);
});
this.keySelectionContainer = keySelectionContainer;
}
createKeyVisual(keyData, width, height) {
// Create a visual representation of a key for the selection UI by building the actual key and scaling it down
const keyContainer = this.scene.add.container(0, 0);
// Temporarily set the key data to create the key
const originalKeyData = this.keyData;
this.keyData = keyData;
// Create the key using the same method as the main key
this.createKey();
// Get the key group and scale it down
const keyGroup = this.keyGroup;
if (keyGroup) {
// Calculate scale to fit within the selection area
const maxWidth = width - 20; // Leave 10px margin on each side
const maxHeight = height - 20;
// Get the key's current dimensions
const keyBounds = keyGroup.getBounds();
const keyWidth = keyBounds.width;
const keyHeight = keyBounds.height;
// Calculate scale
const scaleX = maxWidth / keyWidth;
const scaleY = maxHeight / keyHeight;
const scale = Math.min(scaleX, scaleY) * 0.9; // Use 90% to leave some margin
// Scale the key group
keyGroup.setScale(scale);
// Center the key in the selection area
const scaledWidth = keyWidth * scale;
const scaledHeight = keyHeight * scale;
const offsetX = (width - scaledWidth) / 2;
const offsetY = (height - scaledHeight) / 2;
// Position the key
keyGroup.setPosition(offsetX, offsetY);
// Add the key group to the container
keyContainer.add(keyGroup);
}
// Restore the original key data
this.keyData = originalKeyData;
return keyContainer;
}
selectKey(selectedIndex, correctIndex, keyData) {
// Handle key selection from the UI
console.log(`Key ${selectedIndex + 1} selected (correct: ${correctIndex + 1})`);
// Close the popup immediately
if (this.keySelectionContainer) {
this.keySelectionContainer.destroy();
}
// Remove any existing key from the scene
if (this.keyGroup) {
this.keyGroup.destroy();
this.keyGroup = null;
}
// Remove any existing click zone
if (this.keyClickZone) {
this.keyClickZone.destroy();
this.keyClickZone = null;
}
// Reset pins to their original positions before creating the new key
this.resetPinsToOriginalPositions();
// Store the original correct key data (this determines if the key is correct)
const originalKeyData = this.keyData;
// Store the selected key data for visual purposes
this.selectedKeyData = keyData;
// Create the visual key with the selected key data
this.keyData = keyData;
this.pinCount = keyData.pinCount;
this.createKey();
// Restore the original key data for correctness checking
this.keyData = originalKeyData;
// Update feedback - don't reveal if correct/wrong yet
this.updateFeedback("Key selected! Inserting into lock...");
// Automatically trigger key insertion after a short delay
setTimeout(() => {
this.startKeyInsertion();
}, 300); // Small delay to let the key appear first
// Update feedback if available
if (this.selectKeyCallback) {
this.selectKeyCallback(selectedIndex, correctIndex, keyData);
}
}
showWrongKeyFeedback() {
// Show visual feedback for wrong key selection
const feedback = this.scene.add.graphics();
feedback.fillStyle(0xff0000, 0.3);
feedback.fillRect(0, 0, 800, 600);
feedback.setDepth(9999);
// Remove feedback after a short delay
this.scene.time.delayedCall(500, () => {
feedback.destroy();
});
}
flashLockRed() {
// Flash the entire lock area red to indicate wrong key
const flash = this.scene.add.graphics();
flash.fillStyle(0xff0000, 0.4); // Red with 40% opacity
flash.fillRect(100, 50, 400, 300); // Cover the entire lock area
flash.setDepth(9998); // High z-index but below other UI elements
// Remove flash after a short delay
this.scene.time.delayedCall(800, () => {
flash.destroy();
});
}
createKey() {
if (!this.keyMode) return;
@@ -773,7 +1246,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
// | |_|______________/
// \________/
const cutWidth = 24; // Width of each cut (same as pin width)
@@ -1163,25 +1636,30 @@ export class LockpickingMinigamePhaser extends MinigameScene {
checkKeyCorrectness() {
if (!this.keyData || !this.keyData.cuts) return;
// Check if all pins are green (at the shear line)
let isCorrect = true;
// Check if the selected key matches the correct key
let isCorrect = false;
console.log('Checking key correctness based on pin highlights...');
this.pins.forEach((pin, index) => {
if (index >= this.pinCount) return;
if (this.selectedKeyData && this.selectedKeyData.cuts) {
// Compare the selected key cuts with the original correct key cuts
const selectedCuts = this.selectedKeyData.cuts;
const correctCuts = this.keyData.cuts;
// Check if this pin has a green highlight (meaning it's at the shear line)
const isPinGreen = pin.shearHighlight && pin.shearHighlight.visible;
console.log(`Pin ${index}: isGreen=${isPinGreen}, hasShearHighlight=${!!pin.shearHighlight}, highlightVisible=${pin.shearHighlight ? pin.shearHighlight.visible : false}`);
if (!isPinGreen) {
if (selectedCuts.length === correctCuts.length) {
isCorrect = true;
for (let i = 0; i < selectedCuts.length; i++) {
if (Math.abs(selectedCuts[i] - correctCuts[i]) > 5) { // Allow small tolerance
isCorrect = false;
break;
}
}
}
});
}
console.log('Key correctness result:', isCorrect);
console.log('Key correctness check:', {
selectedKey: this.selectedKeyData ? this.selectedKeyData.cuts : 'none',
correctKey: this.keyData.cuts,
isCorrect: isCorrect
});
if (isCorrect) {
// Key is correct - all pins are aligned at the shear line
@@ -1197,28 +1675,37 @@ export class LockpickingMinigamePhaser extends MinigameScene {
this.complete(true);
}, 3000); // Longer delay to allow rotation animation to complete
} else {
// Key is wrong
this.updateFeedback("Wrong key! Try a different one.");
// Key is wrong - show red flash and then pop up key selection again
this.updateFeedback("Wrong key! The lock won't turn.");
// Play wrong sound
if (this.sounds.wrong) {
this.sounds.wrong.play();
}
// Reset key position
// Flash the entire lock red
this.flashLockRed();
// Reset key position and show key selection again after a delay
setTimeout(() => {
this.updateKeyPosition(0);
}, 1000);
// Show key selection again
if (this.keySelectionMode) {
this.createKeysForChallenge('correct_key');
}
}, 2000); // Longer delay to show the red flash
}
}
snapPinsToExactPositions() {
if (!this.keyData || !this.keyData.cuts) return;
// Use selected key data for visual positioning, but original key data for correctness
const keyDataToUse = this.selectedKeyData || this.keyData;
if (!keyDataToUse || !keyDataToUse.cuts) return;
console.log('Snapping pins to exact positions based on key cuts for shear line alignment');
// Set each pin to the exact final position based on key cut dimensions
this.keyData.cuts.forEach((cutDepth, index) => {
keyDataToUse.cuts.forEach((cutDepth, index) => {
if (index >= this.pinCount) return;
const pin = this.pins[index];
@@ -1625,10 +2112,10 @@ export class LockpickingMinigamePhaser extends MinigameScene {
let nextCutDepth = 0;
if (i < this.pinCount) {
cutDepth = this.keyData.cuts[i] || 0;
cutDepth = (this.selectedKeyData || this.keyData).cuts[i] || 0;
}
if (i < this.pinCount - 1) {
nextCutDepth = this.keyData.cuts[i + 1] || 0;
nextCutDepth = (this.selectedKeyData || this.keyData).cuts[i + 1] || 0;
}
// Calculate pin position
@@ -1644,8 +2131,8 @@ export class LockpickingMinigamePhaser extends MinigameScene {
if (i < this.pinCount) {
// Draw the cut
const cutStartX = cutX - cutWidth/2;
const cutEndX = cutX + cutWidth/2;
const cutStartX = cutX - cutWidth/2;
const cutEndX = cutX + cutWidth/2;
points.push({ x: cutStartX, y: keyBladeBaseY + cutDepth });
points.push({ x: cutEndX, y: keyBladeBaseY + cutDepth });
currentX = cutEndX;
@@ -2283,19 +2770,31 @@ export class LockpickingMinigamePhaser extends MinigameScene {
const pinSpacing = 400 / (this.pinCount + 1);
const margin = pinSpacing * 0.75; // 25% smaller margins
// Try to load saved pin heights for this lock
const savedPinHeights = this.loadLockConfiguration();
for (let i = 0; i < this.pinCount; i++) {
const pinX = 100 + margin + i * pinSpacing;
const pinY = 200;
// Random pin lengths that add up to 75 (total height - 25% increase from 60)
const keyPinLength = 25 + Math.random() * 37.5; // 25-62.5 (25% increase)
const driverPinLength = 75 - keyPinLength; // Remaining to make 75 total
// Use saved pin heights if available, otherwise generate random ones
let keyPinLength, driverPinLength;
if (savedPinHeights && savedPinHeights[i] !== undefined) {
// Use saved configuration
keyPinLength = savedPinHeights[i];
driverPinLength = 75 - keyPinLength; // Total height is 75
} else {
// Generate random pin lengths that add up to 75 (total height - 25% increase from 60)
keyPinLength = 25 + Math.random() * 37.5; // 25-62.5 (25% increase)
driverPinLength = 75 - keyPinLength; // Remaining to make 75 total
}
const pin = {
index: i,
binding: bindingOrder[i],
isSet: false,
currentHeight: 0,
originalHeight: keyPinLength, // Store original height for consistency
keyPinHeight: 0, // Track key pin position separately
driverPinHeight: 0, // Track driver pin position separately
keyPinLength: keyPinLength,
@@ -2522,6 +3021,9 @@ export class LockpickingMinigamePhaser extends MinigameScene {
this.pins.push(pin);
}
// Save the lock configuration after all pins are created
this.saveLockConfiguration();
}
createShearLine() {

View File

@@ -419,21 +419,21 @@ function removeFromInventory(item) {
function handleUnlock(lockable, type) {
console.log('UNLOCK ATTEMPT');
const isLocked = type === 'door' ?
lockable.properties?.locked :
lockable.scenarioData?.locked;
if (!isLocked) {
console.log('OBJECT NOT LOCKED');
return;
}
// Get lock requirements based on type
const lockRequirements = type === 'door'
? getLockRequirementsForDoor(lockable)
: getLockRequirementsForItem(lockable);
if (!lockRequirements) {
console.log('NO LOCK REQUIREMENTS FOUND');
return;
}
// Check if object is locked based on lock requirements
const isLocked = lockRequirements.requires;
if (!isLocked) {
console.log('OBJECT NOT LOCKED');
return;
}
@@ -441,22 +441,16 @@ function handleUnlock(lockable, type) {
case 'key':
const requiredKey = lockRequirements.requires;
console.log('KEY REQUIRED', requiredKey);
const hasKey = window.inventory.items.some(item =>
// Get all keys from player's inventory
const playerKeys = window.inventory.items.filter(item =>
item && item.scenarioData &&
item.scenarioData.key_id === requiredKey
item.scenarioData.type === 'key'
);
if (hasKey) {
const keyItem = window.inventory.items.find(item =>
item && item.scenarioData &&
item.scenarioData.key_id === requiredKey
);
const keyName = keyItem?.scenarioData?.name || 'key';
const keyLocation = keyItem?.scenarioData?.foundIn || 'your inventory';
console.log('KEY UNLOCK SUCCESS');
unlockTarget(lockable, type, lockable.layer);
window.gameAlert(`You used the ${keyName} that you found in ${keyLocation} to unlock the ${type}.`, 'success', 'Unlock Successful', 5000);
if (playerKeys.length > 0) {
// Show key selection interface
startKeySelectionMinigame(lockable, type, playerKeys, requiredKey);
} else {
// Check for lockpick kit
const hasLockpick = window.inventory.items.some(item =>
@@ -481,7 +475,7 @@ function handleUnlock(lockable, type) {
});
}
} else {
console.log('KEY NOT FOUND - FAIL');
console.log('NO KEYS OR LOCKPICK AVAILABLE');
window.gameAlert(`Requires key: ${requiredKey}`, 'error', 'Locked', 4000);
}
}
@@ -636,13 +630,62 @@ function handleUnlock(lockable, type) {
}
}
function getLockRequirementsForDoor(doorTile) {
if (!doorTile.properties) return null;
function getLockRequirementsForDoor(doorSprite) {
const doorWorldX = doorSprite.x;
const doorWorldY = doorSprite.y;
return {
lockType: doorTile.properties.lockType || 'key',
requires: doorTile.properties.requires || ''
};
const overlappingRooms = [];
Object.entries(rooms).forEach(([roomId, otherRoom]) => {
const doorCheckArea = {
x: doorWorldX - DOOR_ALIGN_OVERLAP,
y: doorWorldY - DOOR_ALIGN_OVERLAP,
width: DOOR_ALIGN_OVERLAP * 2,
height: DOOR_ALIGN_OVERLAP * 2
};
const roomBounds = {
x: otherRoom.position.x,
y: otherRoom.position.y,
width: otherRoom.map.widthInPixels,
height: otherRoom.map.heightInPixels
};
if (boundsOverlap(doorCheckArea, roomBounds)) {
const roomCenterX = roomBounds.x + (roomBounds.width / 2);
const roomCenterY = roomBounds.y + (roomBounds.height / 2);
const player = window.player;
const distanceToPlayer = player ? Phaser.Math.Distance.Between(
player.x, player.y,
roomCenterX, roomCenterY
) : 0;
const gameScenario = window.gameScenario;
const roomData = gameScenario?.rooms?.[roomId];
overlappingRooms.push({
id: roomId,
room: otherRoom,
distance: distanceToPlayer,
lockType: roomData?.lockType,
requires: roomData?.requires,
locked: roomData?.locked
});
}
});
const lockedRooms = overlappingRooms
.filter(r => r.locked)
.sort((a, b) => b.distance - a.distance);
if (lockedRooms.length > 0) {
const targetRoom = lockedRooms[0];
return {
lockType: targetRoom.lockType,
requires: targetRoom.requires
};
}
return null;
}
function getLockRequirementsForItem(item) {
@@ -656,11 +699,14 @@ function getLockRequirementsForItem(item) {
function unlockTarget(lockable, type, layer) {
if (type === 'door') {
if (!layer) {
console.error('Missing layer for door unlock');
return;
// After unlocking, open the door
// Find the room that contains this door sprite
const room = Object.values(rooms).find(r =>
r.doorSprites && r.doorSprites.includes(lockable)
);
if (room) {
openDoor(lockable, room);
}
unlockDoor(lockable, layer);
} else {
// Handle item unlocking
if (lockable.scenarioData) {
@@ -681,75 +727,17 @@ function unlockTarget(lockable, type, layer) {
console.log(`${type} unlocked successfully`);
}
// This function is no longer needed - door unlocking is handled by the minigame system
// and door opening is handled by openDoor()
function unlockDoor(doorTile, doorsLayer) {
if (!doorsLayer) {
console.error('Missing doorsLayer in unlockDoor');
return;
}
// Remove lock properties from this door and adjacent door tiles
const doorTiles = [
doorsLayer.getTileAt(doorTile.x, doorTile.y - 1),
doorsLayer.getTileAt(doorTile.x, doorTile.y),
doorsLayer.getTileAt(doorTile.x, doorTile.y + 1),
doorsLayer.getTileAt(doorTile.x - 1, doorTile.y),
doorsLayer.getTileAt(doorTile.x + 1, doorTile.y)
].filter(tile => tile && tile.index !== -1);
doorTiles.forEach(tile => {
if (tile.properties) {
tile.properties.locked = false;
}
});
// Find the room that contains this doors layer
const room = Object.values(rooms).find(r => r.doorsLayer === doorsLayer);
if (!room) {
console.error('Could not find room for doors layer');
return;
}
// Process each door tile's position to remove wall collisions
doorTiles.forEach(tile => {
const worldX = doorsLayer.x + (tile.x * TILE_SIZE);
const worldY = doorsLayer.y + (tile.y * TILE_SIZE);
const doorCheckArea = {
x: worldX - DOOR_ALIGN_OVERLAP,
y: worldY - DOOR_ALIGN_OVERLAP,
width: DOOR_ALIGN_OVERLAP * 2,
height: DOOR_ALIGN_OVERLAP * 2
};
// Remove collision for this door in ALL overlapping rooms' wall layers
Object.entries(rooms).forEach(([otherId, otherRoom]) => {
const otherBounds = {
x: otherRoom.position.x,
y: otherRoom.position.y,
width: otherRoom.map.widthInPixels,
height: otherRoom.map.heightInPixels
};
if (boundsOverlap(doorCheckArea, otherBounds)) {
otherRoom.wallsLayers.forEach(wallLayer => {
const wallX = Math.floor((worldX - wallLayer.x) / TILE_SIZE);
const wallY = Math.floor((worldY - wallLayer.y) / TILE_SIZE);
const wallTile = wallLayer.getTileAt(wallX, wallY);
if (wallTile) {
wallTile.setCollision(false);
}
});
}
});
});
// Update door visuals for all affected tiles
doorTiles.forEach(tile => {
colorDoorTiles(tile, room);
});
console.log('unlockDoor called - this should not happen');
// The unlock process is handled by the minigame system
// When unlock is successful, unlockTarget() calls openDoor()
}
// Store door zones globally so we can manage them
window.doorZones = window.doorZones || new Map();
export function setupDoorOverlapChecks() {
if (!gameRef) {
console.error('Game reference not set in interactions.js');
@@ -758,80 +746,148 @@ export function setupDoorOverlapChecks() {
const DOOR_INTERACTION_RANGE = 2 * TILE_SIZE;
// Clear existing door zones
if (window.doorZones) {
window.doorZones.forEach(zone => {
if (zone && zone.destroy) {
zone.destroy();
}
});
window.doorZones.clear();
}
Object.entries(rooms).forEach(([roomId, room]) => {
if (!room.doorsLayer) return;
if (!room.doorSprites) return;
const doorTiles = room.doorsLayer.getTilesWithin().filter(tile => tile.index !== -1);
const doorSprites = room.doorSprites;
doorTiles.forEach(doorTile => {
const worldX = room.doorsLayer.x + (doorTile.x * TILE_SIZE);
const worldY = room.doorsLayer.y + (doorTile.y * TILE_SIZE);
const zone = gameRef.add.zone(worldX + TILE_SIZE/2, worldY + TILE_SIZE/2, TILE_SIZE, TILE_SIZE);
// Get room data to check if this room should be locked
const gameScenario = window.gameScenario;
const roomData = gameScenario?.rooms?.[roomId];
doorSprites.forEach(doorSprite => {
const zone = gameRef.add.zone(doorSprite.x, doorSprite.y, TILE_SIZE, TILE_SIZE * 2);
zone.setInteractive({ useHandCursor: true });
// Store zone reference for later management
const zoneKey = `${roomId}_${doorSprite.doorProperties.topTile.x}_${doorSprite.doorProperties.topTile.y}`;
window.doorZones.set(zoneKey, zone);
zone.on('pointerdown', () => {
console.log('Door clicked:', { doorTile, room });
console.log('Door clicked:', { doorSprite, room });
console.log('Door properties:', doorSprite.doorProperties);
console.log('Door open state:', doorSprite.doorProperties?.open);
console.log('Door sprite position:', { x: doorSprite.x, y: doorSprite.y });
const player = window.player;
if (!player) return;
const distance = Phaser.Math.Distance.Between(
player.x, player.y,
worldX + TILE_SIZE/2, worldY + TILE_SIZE/2
doorSprite.x, doorSprite.y
);
if (distance <= DOOR_INTERACTION_RANGE) {
if (doorTile.properties?.locked) {
console.log('DOOR LOCKED - ATTEMPTING UNLOCK');
colorDoorTiles(doorTile, room);
handleDoorUnlock(doorTile, room);
} else {
console.log('DOOR NOT LOCKED');
}
handleDoorInteraction(doorSprite, room);
} else {
console.log('DOOR TOO FAR TO INTERACT');
}
});
gameRef.physics.world.enable(zone);
const player = window.player;
if (player) {
gameRef.physics.add.overlap(player, zone, () => {
colorDoorTiles(doorTile, room);
}, null, gameRef);
}
});
});
}
function colorDoorTiles(doorTile, room) {
// Visual feedback for door tiles
const doorTiles = [
room.doorsLayer.getTileAt(doorTile.x, doorTile.y - 1),
room.doorsLayer.getTileAt(doorTile.x, doorTile.y),
room.doorsLayer.getTileAt(doorTile.x, doorTile.y + 1)
];
doorTiles.forEach(tile => {
if (tile) {
// Use red tint for locked doors, clear tint for unlocked doors
// Check each individual tile's lock status, not just the main doorTile
const isLocked = tile.properties?.locked !== false;
if (isLocked) {
tile.tint = 0xff0000; // Red tint for locked doors
tile.tintFill = false;
} else {
// Black tint for unlocked doors - tiles don't have clearTint() method
tile.tint = 0x000000;
tile.tintFill = false;
}
// Function to update door zone visibility based on room visibility
export function updateDoorZoneVisibility() {
if (!window.doorZones || !gameRef) return;
const discoveredRooms = window.discoveredRooms || new Set();
window.doorZones.forEach((zone, zoneKey) => {
const [roomId] = zoneKey.split('_');
// Show zone if this room is discovered
if (discoveredRooms.has(roomId)) {
zone.setVisible(true);
zone.setInteractive({ useHandCursor: true });
} else {
zone.setVisible(false);
zone.setInteractive(false);
}
});
}
function handleDoorUnlock(doorTile, room) {
function colorDoorSprite(doorSprite, isLocked = null) {
// Visual feedback for door sprites
if (doorSprite) {
const isOpen = doorSprite.doorProperties?.open;
if (isOpen) {
doorSprite.setTint(0x000000); // Black tint for open doors
} else if (isLocked) {
doorSprite.setTint(0xff0000); // Red tint for locked doors
} else {
doorSprite.setTint(0xffffff); // White tint for closed but unlocked doors
}
}
}
function handleDoorInteraction(doorSprite, room) {
// Check if door is already open
if (doorSprite.doorProperties.open) {
console.log('DOOR ALREADY OPEN');
return;
}
// Check if door is locked by looking up lock requirements
const lockRequirements = getLockRequirementsForDoor(doorSprite);
const isLocked = lockRequirements && lockRequirements.requires;
if (isLocked) {
console.log('DOOR LOCKED - ATTEMPTING UNLOCK');
colorDoorSprite(doorSprite, true);
handleDoorUnlock(doorSprite, room);
} else {
console.log('DOOR UNLOCKED - OPENING DOOR');
openDoor(doorSprite, room);
}
}
function handleDoorUnlock(doorSprite, room) {
console.log('DOOR UNLOCK ATTEMPT');
doorTile.layer = room.doorsLayer; // Ensure layer reference is set
handleUnlock(doorTile, 'door');
handleUnlock(doorSprite, 'door');
}
function openDoor(doorSprite, room) {
console.log('OPENING DOOR');
console.log('Door sprite before opening:', { x: doorSprite.x, y: doorSprite.y, open: doorSprite.doorProperties?.open });
// Mark door sprite as open
doorSprite.doorProperties.open = true;
// Remove the door sprite (this removes collision and visual)
doorSprite.destroy();
// Remove from room's door sprites array
const spriteIndex = room.doorSprites.indexOf(doorSprite);
if (spriteIndex > -1) {
room.doorSprites.splice(spriteIndex, 1);
}
console.log('Door sprite removed - door is now open');
// Show success message
window.gameAlert('Door opened successfully!', 'success', 'Door Opened', 2000);
console.log('DOOR OPENED SUCCESSFULLY');
}
function startLockpickingMinigame(lockable, scene, difficulty = 'medium', callback) {
@@ -879,6 +935,79 @@ function startLockpickingMinigame(lockable, scene, difficulty = 'medium', callba
});
}
function startKeySelectionMinigame(lockable, type, playerKeys, requiredKeyId) {
console.log('Starting key selection minigame', { playerKeys, requiredKeyId });
// Initialize the minigame framework if not already done
if (!window.MinigameFramework) {
console.error('MinigameFramework not available');
// Fallback to simple key selection
const correctKey = playerKeys.find(key => key.scenarioData.key_id === requiredKeyId);
if (correctKey) {
window.gameAlert(`You used the ${correctKey.scenarioData.name} to unlock the ${type}.`, 'success', 'Unlock Successful', 4000);
unlockTarget(lockable, type, lockable.layer);
} else {
window.gameAlert('None of your keys work with this lock.', 'error', 'Wrong Keys', 4000);
}
return;
}
// Use the advanced minigame framework
if (!window.MinigameFramework.mainGameScene) {
window.MinigameFramework.init(window.game);
}
// Convert inventory keys to the format expected by the minigame
const inventoryKeys = playerKeys.map(key => {
// Generate cuts data if not present
let cuts = key.scenarioData.cuts;
if (!cuts) {
// Generate random cuts based on the key_id for consistency
const seed = key.scenarioData.key_id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
const random = (min, max) => {
const x = Math.sin(seed++) * 10000;
return Math.floor((x - Math.floor(x)) * (max - min + 1)) + min;
};
cuts = [];
for (let i = 0; i < 5; i++) {
cuts.push(random(20, 80)); // Random cuts between 20-80
}
}
return {
id: key.scenarioData.key_id,
name: key.scenarioData.name,
cuts: cuts,
pinCount: key.scenarioData.pinCount || 5
};
});
// Start the key selection minigame
window.MinigameFramework.startMinigame('lockpicking', null, {
keyMode: true,
skipStartingKey: true,
lockable: lockable,
onComplete: (success, result) => {
if (success) {
console.log('KEY SELECTION SUCCESS');
window.gameAlert('Successfully unlocked with the correct key!', 'success', 'Unlock Successful', 4000);
unlockTarget(lockable, type, lockable.layer);
} else {
console.log('KEY SELECTION FAILED');
window.gameAlert('The selected key doesn\'t work with this lock.', 'error', 'Wrong Key', 4000);
}
}
});
// Start with key selection using inventory keys
setTimeout(() => {
if (window.MinigameFramework.currentMinigame && window.MinigameFramework.currentMinigame.startWithKeySelection) {
window.MinigameFramework.currentMinigame.startWithKeySelection(inventoryKeys, requiredKeyId);
}
}, 500);
}
// Fingerprint collection function
function collectFingerprint(item) {
if (!item.scenarioData?.hasFingerprint) {
@@ -1035,4 +1164,6 @@ function generateFingerprintData(item) {
window.checkObjectInteractions = checkObjectInteractions;
window.setupDoorOverlapChecks = setupDoorOverlapChecks;
window.handleObjectInteraction = handleObjectInteraction;
window.processAllDoorCollisions = processAllDoorCollisions;
window.processAllDoorCollisions = processAllDoorCollisions;
window.startKeySelectionMinigame = startKeySelectionMinigame;
window.updateDoorZoneVisibility = updateDoorZoneVisibility;

View File

@@ -228,7 +228,7 @@
<div class="header">
<div class="title">🔑 LOCKPICKING KEY MODE DEMO 🔑</div>
<div class="description">
Test the new key mode functionality! Try different keys to see which ones work with the lock.
Test the new key mode functionality! Key mode now starts with a selection of 3 random keys to choose from.
</div>
</div>
@@ -254,22 +254,25 @@
<div class="key-preset" data-key="key3">Key 3 (Partial)</div>
<div class="key-preset" data-key="key4">Key 4 (Random)</div>
<div class="key-preset" data-key="key5">Key 5 (Deep)</div>
<button class="key-preset" id="keySelectionToggle">🎲 Random Key Selection</button>
</div>
<div class="game-container">
<div id="gameContainer"></div>
<div class="feedback" id="feedback">Select a key and try inserting it into the lock</div>
<div class="feedback" id="feedback">Select a key from the options above to begin</div>
</div>
<div class="demo-container">
<div class="instructions">
<h3>How to Use</h3>
<ul>
<li><strong>Key Mode:</strong> Click and drag the key from left to right to insert it into the lock</li>
<li><strong>Key Mode:</strong> Automatically shows 3 random keys to choose from (one correct)</li>
<li><strong>Key Selection:</strong> Click any key to select it and automatically insert it into the lock</li>
<li><strong>Random Key Selection:</strong> Click "🎲 Random Key Selection" to generate new random keys</li>
<li><strong>Pick Mode:</strong> Use the tension wrench and hook pick to manually pick the lock</li>
<li><strong>Key Cuts:</strong> The numbers show the depth of each cut as a percentage (0-100%)</li>
<li><strong>Success:</strong> When the key cuts match the pin heights, the lock will unlock</li>
<li><strong>Failure:</strong> Wrong keys will be rejected and the key will reset</li>
<li><strong>Failure:</strong> Wrong keys will flash the lock red and show key selection again</li>
</ul>
</div>
</div>
@@ -284,6 +287,7 @@
this.currentGame = null;
this.currentMode = 'key';
this.currentKey = 'key1';
this.keySelectionMode = false;
// Key presets with different cut patterns
this.keyPresets = {
@@ -329,11 +333,16 @@
});
// Key preset buttons
document.querySelectorAll('.key-preset').forEach(button => {
document.querySelectorAll('.key-preset[data-key]').forEach(button => {
button.addEventListener('click', () => {
this.setKey(button.dataset.key);
});
});
// Key selection toggle button
document.getElementById('keySelectionToggle').addEventListener('click', () => {
this.toggleKeySelection();
});
}
setMode(mode) {
@@ -352,9 +361,10 @@
setKey(keyId) {
this.currentKey = keyId;
this.keySelectionMode = false;
// Update button states
document.querySelectorAll('.key-preset').forEach(button => {
document.querySelectorAll('.key-preset[data-key]').forEach(button => {
button.classList.toggle('active', button.dataset.key === keyId);
});
@@ -367,6 +377,39 @@
}
}
toggleKeySelection() {
try {
this.keySelectionMode = !this.keySelectionMode;
if (this.keySelectionMode) {
if (!this.currentGame) {
throw new Error('No game instance available. Please start the game first.');
}
// Generate new random keys for selection
this.currentGame.keySelectionMode = true; // Mark that we're in key selection mode
this.currentGame.createKeysForChallenge('correct_key');
// Update feedback
document.getElementById('feedback').textContent =
"Select the correct key from the 3 new options above";
} else {
// Hide key selection UI if it exists
if (this.currentGame.keySelectionContainer) {
this.currentGame.keySelectionContainer.destroy();
this.currentGame.keySelectionContainer = null;
}
// Update feedback
document.getElementById('feedback').textContent =
"Key selection mode disabled. Use preset keys or enable random selection.";
}
} catch (error) {
console.error('Error toggling key selection:', error);
document.getElementById('feedback').textContent = 'Error: ' + error.message;
}
}
updateKeyDisplay() {
const keyData = this.keyPresets[this.currentKey];
const keyCutsContainer = document.getElementById('keyCuts');
@@ -394,48 +437,61 @@
}
startGame() {
if (this.currentGame) {
this.currentGame.cleanup();
}
const params = {
pinCount: 5,
difficulty: 'medium',
thresholdSensitivity: 5,
highlightBindingOrder: true,
pinAlignmentHighlighting: true,
liftSpeed: 1.0,
lockable: { id: 'key-demo-lock' },
closeButtonText: 'Reset',
closeButtonAction: 'reset'
};
// Add key mode parameters if in key mode
if (this.currentMode === 'key') {
params.keyMode = true;
// For the "correct" key, don't pass keyData so it generates from pins
// For other keys, use the preset cuts
if (this.currentKey === 'key1') {
// Let the game generate the correct key from actual pin heights
console.log('Using auto-generated correct key from pin heights');
} else {
params.keyData = {
cuts: this.keyPresets[this.currentKey].cuts
};
try {
if (this.currentGame) {
this.currentGame.cleanup();
}
const params = {
pinCount: 5,
difficulty: 'medium',
thresholdSensitivity: 5,
highlightBindingOrder: true,
pinAlignmentHighlighting: true,
liftSpeed: 1.0,
lockable: { id: 'key-demo-lock' },
closeButtonText: 'Reset',
closeButtonAction: 'reset'
};
// Add key mode parameters if in key mode
if (this.currentMode === 'key') {
params.keyMode = true;
params.skipStartingKey = true; // Skip creating initial key, go straight to selection
// For the "correct" key, don't pass keyData so it generates from pins
// For other keys, use the preset cuts
if (this.currentKey === 'key1') {
// Let the game generate the correct key from actual pin heights
console.log('Using auto-generated correct key from pin heights');
} else {
params.keyData = {
cuts: this.keyPresets[this.currentKey].cuts
};
}
}
console.log('Starting game with params:', params);
this.currentGame = new LockpickingMinigamePhaser(
document.getElementById('gameContainer'),
params
);
this.currentGame.init();
this.currentGame.start();
// If in key mode, automatically show key selection
if (this.currentMode === 'key') {
setTimeout(() => {
this.currentGame.startWithKeySelection();
}, 500); // Small delay to ensure game is fully initialized
}
} catch (error) {
console.error('Error starting game:', error);
document.getElementById('feedback').textContent = 'Error starting game: ' + error.message;
}
console.log('Starting game with params:', params);
this.currentGame = new LockpickingMinigamePhaser(
document.getElementById('gameContainer'),
params
);
this.currentGame.init();
this.currentGame.start();
// Update key display after game starts (for auto-generated correct key)
if (this.currentMode === 'key' && this.currentKey === 'key1') {
setTimeout(() => {
@@ -449,11 +505,21 @@
console.log('Game completed with success:', success);
if (success) {
document.getElementById('feedback').textContent =
`🎉 Success! ${this.keyPresets[this.currentKey].name} worked!`;
if (this.keySelectionMode) {
document.getElementById('feedback').textContent =
"🎉 Success! You selected the correct key!";
} else {
document.getElementById('feedback').textContent =
`🎉 Success! ${this.keyPresets[this.currentKey].name} worked!`;
}
} else {
document.getElementById('feedback').textContent =
`❌ Failed! ${this.keyPresets[this.currentKey].name} didn't work.`;
if (this.keySelectionMode) {
document.getElementById('feedback').textContent =
"❌ Failed! Wrong key selected. Try again!";
} else {
document.getElementById('feedback').textContent =
`❌ Failed! ${this.keyPresets[this.currentKey].name} didn't work.`;
}
}
// Restart after a delay
@@ -464,10 +530,25 @@
originalComplete(success);
};
// Override the selectKey method to handle key selection feedback
const originalSelectKey = this.currentGame.selectKey.bind(this.currentGame);
this.currentGame.selectKey = (selectedIndex, correctIndex, keyData) => {
// Don't reveal if correct/wrong yet - keep it suspenseful
document.getElementById('feedback').textContent =
"🔑 Key selected! Inserting into lock...";
originalSelectKey(selectedIndex, correctIndex, keyData);
};
// Update feedback message
if (this.currentMode === 'key') {
document.getElementById('feedback').textContent =
`Try inserting ${this.keyPresets[this.currentKey].name} into the lock (drag from left to right)`;
if (this.keySelectionMode) {
document.getElementById('feedback').textContent =
"Select the correct key from the 3 options above";
} else {
document.getElementById('feedback').textContent =
"Select a key from the options above to begin";
}
} else {
document.getElementById('feedback').textContent =
"Use the tension wrench and hook pick to manually pick the lock";

View File

@@ -310,10 +310,11 @@
<div class="level-display">LEVEL <span id="currentLevel">1</span></div>
<div class="stats">
<div class="stat">Pins: <span id="pinCount">3</span></div>
<div class="stat">Sensitivity: <span id="sensitivity">5</span></div>
<div class="stat">Lift Speed: <span id="liftSpeed">1.0</span></div>
<div class="stat">Binding Order: <span id="bindingHints">Enabled</span></div>
<div class="stat">Pin Alignment: <span id="alignmentHints">Enabled</span></div>
<div class="stat" id="sensitivityStat">Sensitivity: <span id="sensitivity">5</span></div>
<div class="stat" id="liftSpeedStat">Lift Speed: <span id="liftSpeed">1.0</span></div>
<div class="stat" id="bindingHintsStat">Binding Order: <span id="bindingHints">Enabled</span></div>
<div class="stat" id="alignmentHintsStat">Pin Alignment: <span id="alignmentHints">Enabled</span></div>
<div class="stat" id="gameModeStat">Mode: <span id="gameMode">Lockpicking</span></div>
</div>
</div>
@@ -438,11 +439,11 @@
let sensitivity = 1;
let liftSpeed = 0.6;
if (positionInBlock <= 5) {
// First 5 levels: increase sensitivity
if (positionInBlock % 2 === 1) {
// odd numbers: increase sensitivity
sensitivity = 1 + Math.floor((positionInBlock - 1) / 2);
} else {
// Last 5 levels: increase speed
// even numbers: increase speed
const speedLevel = positionInBlock - 5;
liftSpeed = 0.6 + (speedLevel * 0.1);
}
@@ -465,6 +466,12 @@
// Hint settings based on position in 10-level block
let highlightBindingOrder = 'enabled';
let pinAlignmentHighlighting = 'enabled';
let keyMode = false; // Default to lockpicking mode
// Level 6 of each 10-level block: Key selection challenge
if (positionInBlock === 6) {
keyMode = true;
}
// Last 3 levels of each 10-level block remove hints progressively
if (positionInBlock === 8) {
@@ -485,7 +492,8 @@
sensitivity,
liftSpeed: parseFloat(liftSpeed.toFixed(2)),
highlightBindingOrder,
pinAlignmentHighlighting
pinAlignmentHighlighting,
keyMode
};
}
@@ -571,6 +579,12 @@
closeButtonAction: 'reset'
};
// Add key mode parameters if this is a key selection level
if (config.keyMode) {
params.keyMode = true;
params.skipStartingKey = true; // Skip creating initial key, go straight to selection
}
this.updateStatus(`Starting Level ${this.currentLevel}...`);
console.log('Creating minigame with params:', params);
@@ -591,6 +605,42 @@
// Start the minigame
this.currentGame.start();
// If this is a key mode level, automatically show key selection
if (config.keyMode) {
setTimeout(() => {
// Override the createKeysForChallenge method to use generic names
const originalCreateKeysForChallenge = this.currentGame.createKeysForChallenge.bind(this.currentGame);
this.currentGame.createKeysForChallenge = (correctKeyId = 'challenge_key') => {
// Create keys for challenge mode (like locksmith-forge.html)
// Generates 3 keys with one guaranteed correct key
const key1 = this.currentGame.generateRandomKey(this.currentGame.pinCount);
const key2 = this.currentGame.generateRandomKey(this.currentGame.pinCount);
const key3 = this.currentGame.generateRandomKey(this.currentGame.pinCount);
// Make the first key correct by copying the actual key cuts
key1.cuts = this.currentGame.keyData.cuts;
key1.id = correctKeyId;
key1.name = `Key ${Math.floor(Math.random() * 1000)}`; // Generic name
// Give other keys generic names too
key2.name = `Key ${Math.floor(Math.random() * 1000)}`;
key3.name = `Key ${Math.floor(Math.random() * 1000)}`;
// Randomize the order of keys
const keys = [key1, key2, key3];
this.currentGame.shuffleArray(keys);
// Find the new index of the correct key after shuffling
const correctKeyIndex = keys.findIndex(key => key.id === correctKeyId);
return this.currentGame.createKeySelectionUI(keys, correctKeyId);
};
this.currentGame.startWithKeySelection();
}, 500); // Small delay to ensure game is fully initialized
}
console.log('Minigame started');
// Listen for completion by overriding the complete method
@@ -641,8 +691,14 @@
this.updateStatus(milestone.status);
this.showAchievement(milestone.achievement);
} else {
this.updateStatus(`Level ${this.currentLevel} completed successfully!`);
this.showAchievement(`Level ${this.currentLevel} Complete!`);
const config = this.levelConfig[this.currentLevel];
if (config && config.keyMode) {
this.updateStatus(`Key selection challenge completed!`);
this.showAchievement(`🔑 Key Master - Level ${this.currentLevel} Complete! 🔑`);
} else {
this.updateStatus(`Level ${this.currentLevel} completed successfully!`);
this.showAchievement(`Level ${this.currentLevel} Complete!`);
}
}
}
@@ -710,6 +766,16 @@
document.getElementById('alignmentHints').textContent = config.pinAlignmentHighlighting === 'enabled' ? 'Visible' : 'Hidden';
document.getElementById('sensitivity').textContent = config.sensitivity;
document.getElementById('liftSpeed').textContent = config.liftSpeed;
document.getElementById('gameMode').textContent = config.keyMode ? 'Key Selection' : 'Lockpicking';
// Show/hide stats based on game mode
const lockpickingStats = ['sensitivityStat', 'liftSpeedStat', 'bindingHintsStat', 'alignmentHintsStat'];
lockpickingStats.forEach(statId => {
const statElement = document.getElementById(statId);
if (statElement) {
statElement.style.display = config.keyMode ? 'none' : 'block';
}
});
// Apply highlighting based on level position
this.highlightBasedOnLevel(config);
@@ -742,13 +808,13 @@
break;
case 'sensitivity':
// Highlight on levels 1-5 (sensitivity focus phase)
shouldHighlight = (positionInBlock <= 5);
// odd numbers: increase sensitivity
shouldHighlight = (positionInBlock % 2 === 1);
break;
case 'liftSpeed':
// Highlight on levels 6-10 (speed focus phase)
shouldHighlight = (positionInBlock >= 6);
// even numbers: increase speed
shouldHighlight = (positionInBlock % 2 === 0);
break;
case 'bindingHints':
@@ -761,6 +827,11 @@
shouldHighlight = (config.pinAlignmentHighlighting === 'disabled');
break;
case 'gameMode':
// Highlight when in key selection mode
shouldHighlight = config.keyMode;
break;
default:
shouldHighlight = false;
}

View File

@@ -1,103 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Phaser Test</title>
<style>
body {
font-family: Arial, sans-serif;
background: #1a1a1a;
color: #ffffff;
margin: 0;
padding: 20px;
}
.test-container {
max-width: 800px;
margin: 0 auto;
background: #2a2a2a;
border-radius: 10px;
padding: 20px;
}
.test-container h1 {
text-align: center;
color: #00ff00;
}
#phaser-container {
width: 600px;
height: 400px;
background: #333;
border-radius: 5px;
margin: 20px auto;
border: 2px solid #444;
}
.status {
text-align: center;
margin: 20px 0;
padding: 10px;
background: #444;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="test-container">
<h1>Simple Phaser Test</h1>
<div class="status" id="status">Loading...</div>
<div id="phaser-container"></div>
</div>
<!-- Load Phaser.js -->
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
<script>
document.getElementById('status').textContent = 'Phaser loaded, creating game...';
// Simple test scene
class TestScene extends Phaser.Scene {
constructor() {
super({ key: 'TestScene' });
}
create() {
document.getElementById('status').textContent = 'Phaser scene created successfully!';
// Create a simple rectangle
const graphics = this.add.graphics();
graphics.fillStyle(0x00ff00);
graphics.fillRect(100, 100, 200, 100);
// Add some text
this.add.text(200, 150, 'Phaser Works!', {
fontSize: '24px',
fill: '#ffffff'
}).setOrigin(0.5);
console.log('Test scene created successfully');
}
}
// Game config
const config = {
type: Phaser.AUTO,
parent: 'phaser-container',
width: 600,
height: 400,
backgroundColor: '#1a1a1a',
scene: TestScene
};
try {
const game = new Phaser.Game(config);
console.log('Phaser game created:', game);
} catch (error) {
console.error('Error creating Phaser game:', error);
document.getElementById('status').textContent = 'Error: ' + error.message;
}
</script>
</body>
</html>