mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
BIN
assets/tiles/door_sheet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
BIN
assets/tiles/door_side_sheet.png
Normal file
BIN
assets/tiles/door_side_sheet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
1353
js/core/rooms.js
1353
js/core/rooms.js
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
183
key-demo.html
183
key-demo.html
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user