Fix unlock detection to work with server-side data filtering

- Update unlock-system.js to check 'locked' field instead of 'requires' for lock detection
- Pass null for key/pin/password required values (server validates)
- Preserve 'requires' field for biometric/bluetooth locks (contains item identifiers, not answers)
- Update both Game model and controller filtering methods

Fixes issue where locked objects didn't prompt for unlock after server-side filtering was implemented.
This commit is contained in:
Z. Cliffe Schreuders
2025-11-22 00:46:55 +00:00
parent cb2d342802
commit b27cde13d0
3 changed files with 29 additions and 16 deletions

View File

@@ -304,8 +304,12 @@ module BreakEscape
def filter_requires_recursive(obj)
case obj
when Hash
# Remove 'requires' (the answer/solution) from all objects
obj.delete('requires')
# Remove 'requires' for exploitable lock types (key/pin/password/rfid)
# Keep it for biometric/bluetooth since they reference collectible items, not answers
lock_type = obj['lockType']
if lock_type && !%w[biometric bluetooth].include?(lock_type)
obj.delete('requires')
end
# Recursively filter nested structures
obj.each_value { |value| filter_requires_recursive(value) }

View File

@@ -178,8 +178,12 @@ module BreakEscape
def filter_requires_and_contents_recursive(obj)
case obj
when Hash
# Remove 'requires' (the answer/solution)
obj.delete('requires')
# Remove 'requires' for exploitable lock types (key/pin/password/rfid)
# Keep it for biometric/bluetooth since they reference collectible items, not answers
lock_type = obj['lockType']
if lock_type && !%w[biometric bluetooth].include?(lock_type)
obj.delete('requires')
end
# Remove 'contents' if locked (lazy-loaded via separate endpoint)
obj.delete('contents') if obj['locked']

View File

@@ -41,10 +41,11 @@ export function handleUnlock(lockable, type) {
console.log('NO LOCK REQUIREMENTS FOUND');
return;
}
// Check if object is locked based on lock requirements
const isLocked = lockRequirements.requires;
// Use 'locked' field instead of 'requires' (which is filtered server-side for security)
const isLocked = lockRequirements.locked !== false;
if (!isLocked) {
console.log('OBJECT NOT LOCKED');
return;
@@ -63,9 +64,11 @@ export function handleUnlock(lockable, type) {
switch(lockRequirements.lockType) {
case 'key':
const requiredKey = lockRequirements.requires;
console.log('KEY REQUIRED', requiredKey);
// Note: requiredKey no longer available from server (security filtered)
// Server will validate on unlock attempt
const requiredKey = null; // Will be validated server-side
console.log('KEY REQUIRED (server-side validation)');
// Get all keys from player's inventory (including key ring)
let playerKeys = [];
@@ -171,8 +174,9 @@ export function handleUnlock(lockable, type) {
break;
case 'pin':
console.log('PIN CODE REQUESTED');
startPinMinigame(lockable, type, lockRequirements.requires, (success) => {
console.log('PIN CODE REQUESTED (server-side validation)');
// Pass null for required code - will be validated server-side
startPinMinigame(lockable, type, null, (success) => {
if (success) {
unlockTarget(lockable, type, lockable.layer);
}
@@ -180,8 +184,8 @@ export function handleUnlock(lockable, type) {
break;
case 'password':
console.log('PASSWORD REQUESTED');
console.log('PASSWORD REQUESTED (server-side validation)');
// Get password options from the lockable object
const passwordOptions = {
passwordHint: lockable.passwordHint || lockable.scenarioData?.passwordHint || '',
@@ -191,8 +195,9 @@ export function handleUnlock(lockable, type) {
postitNote: lockable.postitNote || lockable.scenarioData?.postitNote || '',
showPostit: lockable.showPostit || lockable.scenarioData?.showPostit || false
};
startPasswordMinigame(lockable, type, lockRequirements.requires, (success) => {
// Pass null for required password - will be validated server-side
startPasswordMinigame(lockable, type, null, (success) => {
if (success) {
unlockTarget(lockable, type, lockable.layer);
}