Files
BreakEscape/app/controllers/break_escape/games_controller.rb

1198 lines
44 KiB
Ruby
Raw Normal View History

require 'open3'
module BreakEscape
class GamesController < ApplicationController
2025-11-28 15:36:10 +00:00
before_action :set_game, only: [:show, :scenario, :scenario_map, :ink, :room, :container, :sync_state, :unlock, :inventory, :objectives, :complete_task, :update_task_progress, :submit_flag]
# GET /games/new?mission_id=:id
# Show VM set selection page for VM-required missions
def new
@mission = Mission.find(params[:mission_id])
authorize @mission, :create_game? if defined?(Pundit)
if @mission.requires_vms?
@available_vm_sets = @mission.valid_vm_sets_for_user(current_player)
2025-11-28 15:36:10 +00:00
@existing_games = Game.where(player: current_player, mission: @mission)
end
end
# POST /games
# Create a new game instance for a mission
def create
@mission = Mission.find(params[:mission_id])
authorize @mission, :create_game? if defined?(Pundit)
# Build initial player_state with VM/flag context
initial_player_state = {}
# Hacktivity mode with VM set
if params[:vm_set_id].present? && defined?(::VmSet)
vm_set = ::VmSet.find_by(id: params[:vm_set_id])
return render json: { error: 'VM set not found' }, status: :not_found unless vm_set
# Validate VM set belongs to user and matches mission
if BreakEscape::Mission.hacktivity_mode?
unless @mission.valid_vm_sets_for_user(current_user).include?(vm_set)
return render json: { error: 'Invalid VM set for this mission' }, status: :forbidden
end
initial_player_state['vm_set_id'] = vm_set.id
else
# Standalone mode - vm_set_id shouldn't be used
Rails.logger.warn "[BreakEscape] vm_set_id provided but not in Hacktivity mode, ignoring"
end
end
# Standalone mode with VM IPs JSON
if params[:vm_ips_json].present?
begin
vm_ips = JSON.parse(params[:vm_ips_json])
initial_player_state['vm_ips'] = vm_ips if vm_ips.is_a?(Hash)
rescue JSON::ParserError => e
Rails.logger.warn "[BreakEscape] Invalid vm_ips_json: #{e.message}"
end
end
# Standalone mode with XML flag hints
if params[:flag_hints_xml].present?
flags_by_vm = Mission.parse_flag_hints_xml(params[:flag_hints_xml])
initial_player_state['flags_by_vm'] = flags_by_vm
# Also store flat list for backward compatibility
initial_player_state['standalone_flags'] = flags_by_vm.values.flatten.uniq
# Legacy: comma-separated flags (backward compatibility)
elsif params[:standalone_flags].present?
2025-11-28 15:36:10 +00:00
flags = if params[:standalone_flags].is_a?(Array)
params[:standalone_flags]
else
2025-11-28 15:36:10 +00:00
params[:standalone_flags].split(',').map(&:strip).reject(&:blank?)
end
2025-11-28 15:36:10 +00:00
initial_player_state['standalone_flags'] = flags
end
# CRITICAL: Set player_state BEFORE save! so callbacks can read vm_set_id
# Callback order is:
# 1. before_create :generate_scenario_data_with_context (reads player_state['vm_set_id'])
# 2. before_create :initialize_player_state (adds default fields)
@game = Game.new(
player: current_player,
mission: @mission
)
@game.player_state = initial_player_state
@game.save!
redirect_to game_path(@game)
end
def show
authorize @game if defined?(Pundit)
@mission = @game.mission
end
# GET /games/:id/scenario
# Returns filtered scenario JSON for this game instance
# Uses filtered_scenario_for_bootstrap for lazy-loading support
def scenario
authorize @game if defined?(Pundit)
begin
# Use filtered bootstrap scenario and remove 'requires' fields for security
filtered = @game.filtered_scenario_for_bootstrap
# Remove 'requires' fields recursively for security
filter_requires_recursive(filtered)
# Include objectives state for page reload recovery
# This allows the client to restore completed/progress state
if @game.player_state['objectivesState'].present?
filtered['objectivesState'] = @game.player_state['objectivesState']
end
2025-11-28 15:36:10 +00:00
# Include submitted flags for flag station minigame
if @game.player_state['submitted_flags'].present?
filtered['submittedFlags'] = @game.player_state['submitted_flags']
end
# Include current inventory from player_state for page reload recovery
# This allows the client to restore inventory state on reload
if @game.player_state['inventory'].present? && @game.player_state['inventory'].is_a?(Array)
filtered['playerInventory'] = @game.player_state['inventory']
# Remove startItemsInInventory from scenario to prevent duplicates
# Since we're sending the actual inventory, we don't need the starting items
filtered.delete('startItemsInInventory')
end
render json: filtered
rescue => e
Rails.logger.error "[BreakEscape] scenario error: #{e.message}\n#{e.backtrace.first(5).join("\n")}"
render_error("Failed to generate scenario: #{e.message}", :internal_server_error)
end
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# GET /games/:id/scenario_map
# Returns minimal scenario metadata for navigation (no room contents)
def scenario_map
authorize @game if defined?(Pundit)
begin
# Check if scenario_data exists
unless @game.scenario_data.present?
Rails.logger.error "[BreakEscape] scenario_map: Game #{@game.id} has no scenario_data"
return render_error('Scenario data not available', :internal_server_error)
end
# Return minimal room/connection metadata without contents
layout = {}
rooms = @game.scenario_data['rooms'] || {}
Rails.logger.debug "[BreakEscape] scenario_map: Processing #{rooms.keys.length rescue 0} rooms"
rooms.each do |room_id, room_data|
next unless room_data.is_a?(Hash)
begin
layout[room_id] = {
type: room_data['type'],
connections: room_data['connections'] || {},
locked: room_data['locked'] || false,
lockType: room_data['lockType'],
hasNPCs: (room_data['npcs']&.length || 0) > 0,
accessible: @game.room_unlocked?(room_id)
}
rescue => e
Rails.logger.error "[BreakEscape] Error processing room #{room_id}: #{e.message}"
# Skip this room and continue
next
end
end
render json: {
startRoom: @game.scenario_data['startRoom'],
currentRoom: @game.player_state['currentRoom'],
rooms: layout
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
}
rescue => e
Rails.logger.error "[BreakEscape] scenario_map error: #{e.message}\n#{e.backtrace.first(5).join("\n")}"
render_error("Failed to generate scenario map: #{e.message}", :internal_server_error)
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
end
end
# GET /games/:id/room/:room_id
# Returns room data for a specific room (lazy-loading support)
def room
authorize @game if defined?(Pundit)
begin
room_id = params[:room_id]
return render_error('Missing room_id parameter', :bad_request) unless room_id.present?
# Check if scenario_data exists
unless @game.scenario_data.present?
Rails.logger.error "[BreakEscape] room: Game #{@game.id} has no scenario_data"
return render_error('Scenario data not available', :internal_server_error)
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# Check if room exists in scenario FIRST (before accessibility check)
unless @game.scenario_data['rooms']&.key?(room_id)
return render_error("Room not found: #{room_id}", :not_found)
end
# Check if room is accessible (starting room OR in unlockedRooms)
is_start_room = @game.scenario_data['startRoom'] == room_id
is_unlocked = @game.player_state['unlockedRooms']&.include?(room_id)
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
unless is_start_room || is_unlocked
return render_error("Room not accessible: #{room_id}", :forbidden)
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# Auto-add room to unlockedRooms when accessed
# This ensures items in the room can be collected
if !is_unlocked
@game.player_state['unlockedRooms'] ||= []
@game.player_state['unlockedRooms'] << room_id unless @game.player_state['unlockedRooms'].include?(room_id)
@game.save!
Rails.logger.info "[BreakEscape] Auto-unlocked room #{room_id} on access"
end
# Get and filter room data
room_data = @game.filtered_room_data(room_id)
return render_error("Room not found: #{room_id}", :not_found) unless room_data
# Track NPC encounters BEFORE sending response
npc_count = room_data['npcs']&.length || 0
Rails.logger.info "[BreakEscape] 📦 Loading room: #{room_id} (NPCs: #{npc_count})"
track_npc_encounters(room_id, room_data)
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
Rails.logger.debug "[BreakEscape] Serving room data for: #{room_id}"
render json: { room_id: room_id, room: room_data }
rescue => e
Rails.logger.error "[BreakEscape] room error: #{e.message}\n#{e.backtrace.first(10).join("\n")}"
render_error("Failed to load room: #{e.message}", :internal_server_error)
end
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# GET /games/:id/container/:container_id
# Returns container contents after unlock (lazy-loaded)
def container
authorize @game if defined?(Pundit)
# Reload game to get latest player_state (in case inventory was updated)
@game.reload
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
container_id = params[:container_id]
return render_error('Missing container_id parameter', :bad_request) unless container_id.present?
# Find container in scenario data
container_data = find_container_in_scenario(container_id)
return render_error("Container not found: #{container_id}", :not_found) unless container_data
# Check if container is unlocked (check multiple possible identifiers)
is_unlocked = check_container_unlocked(container_id, container_data)
unless is_unlocked
return render_error("Container not unlocked: #{container_id}", :forbidden)
end
# Return filtered contents
contents = filter_container_contents(container_data)
Rails.logger.info "[BreakEscape] Serving container contents for: #{container_id} - returning #{contents.length} items"
Rails.logger.debug "[BreakEscape] Container contents: #{contents.map { |c| "#{c['type']}/#{c['id']}/#{c['name']}" }.join(', ')}"
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
render json: {
container_id: container_id,
contents: contents
}
end
# GET /games/:id/ink?npc=helper1
# Returns NPC script (JIT compiled if needed)
def ink
authorize @game if defined?(Pundit)
npc_id = params[:npc]
return render_error('Missing npc parameter', :bad_request) unless npc_id.present?
Rails.logger.debug "[BreakEscape] Loading ink for NPC: #{npc_id}"
# Find NPC in scenario data
npc = find_npc_in_scenario(npc_id)
return render_error("NPC not found in scenario: #{npc_id}", :not_found) unless npc
Rails.logger.debug "[BreakEscape] Found NPC: #{npc['id']} with storyPath: #{npc['storyPath']}"
# Check if storyPath is set
unless npc['storyPath'].present?
Rails.logger.warn "[BreakEscape] NPC #{npc['id']} has no storyPath defined"
return render_error("NPC #{npc['id']} has no storyPath defined", :bad_request)
end
# Resolve ink file path and compile if needed
ink_json_path = resolve_and_compile_ink(npc['storyPath'])
unless ink_json_path && File.exist?(ink_json_path)
Rails.logger.error "[BreakEscape] Ink file not found for #{npc['storyPath']} (resolved to #{ink_json_path})"
return render_error("Ink script not found for #{npc['storyPath']}", :not_found)
end
Rails.logger.debug "[BreakEscape] Serving ink from: #{ink_json_path}"
# Serve compiled JSON
render json: JSON.parse(File.read(ink_json_path))
rescue JSON::ParserError => e
render_error("Invalid JSON in compiled ink: #{e.message}", :internal_server_error)
end
# PUT /games/:id/sync_state
# Periodic state sync from client
def sync_state
authorize @game if defined?(Pundit)
# Update allowed fields
if params[:currentRoom]
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# Verify room is accessible
if @game.player_state['unlockedRooms'].include?(params[:currentRoom]) ||
@game.scenario_data['startRoom'] == params[:currentRoom]
@game.player_state['currentRoom'] = params[:currentRoom]
else
return render json: {
success: false,
message: "Cannot enter locked room: #{params[:currentRoom]}"
}, status: :forbidden
end
end
if params[:globalVariables]
@game.update_global_variables!(params[:globalVariables].to_unsafe_h)
end
@game.save!
render json: { success: true }
end
# POST /games/:id/unlock
# Validate unlock attempt
def unlock
authorize @game if defined?(Pundit)
target_type = params[:targetType]
target_id = params[:targetId]
attempt = params[:attempt]
method = params[:method]
is_valid = @game.validate_unlock(target_type, target_id, attempt, method)
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
unless is_valid
return render json: {
success: false,
message: 'Invalid attempt'
}, status: :unprocessable_entity
end
# Use transaction to ensure atomic update
ActiveRecord::Base.transaction do
if target_type == 'door'
@game.unlock_room!(target_id)
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
room_data = @game.filtered_room_data(target_id)
# Track NPC encounters when unlocking a door (room data is cached by client)
# This ensures NPCs are tracked even if loadRoom() uses cached data
track_npc_encounters(target_id, room_data)
render json: {
success: true,
type: 'door',
roomData: room_data
}
else
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# Object/container unlock
@game.unlock_object!(target_id)
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# Find the unlocked object and return its contents if it's a container
object_data = find_object_in_scenario(target_id)
response = {
success: true,
type: 'object'
}
# If object has contents, include them in response
if object_data && object_data['contents'].present?
response[:hasContents] = true
response[:contents] = object_data['contents']
end
render json: response
end
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
rescue ActiveRecord::RecordInvalid => e
render json: {
success: false,
message: "Failed to save unlock: #{e.message}"
}, status: :unprocessable_entity
end
# POST /games/:id/inventory
# Update inventory
def inventory
authorize @game if defined?(Pundit)
action_type = params[:action_type] || params[:actionType]
item = params[:item]
Rails.logger.info "[BreakEscape] inventory endpoint: action=#{action_type}, item=#{item.inspect}"
case action_type
when 'add'
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# Validate item exists and is collectible
validation_error = validate_item_collectible(item)
if validation_error
Rails.logger.warn "[BreakEscape] inventory validation failed: #{validation_error}"
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
return render json: { success: false, message: validation_error },
status: :unprocessable_entity
end
Rails.logger.info "[BreakEscape] Adding item to inventory: #{item['type']} / #{item['name']}"
@game.add_inventory_item!(item.to_unsafe_h)
Rails.logger.info "[BreakEscape] Item added successfully. Current inventory: #{@game.player_state['inventory'].inspect}"
render json: { success: true, inventory: @game.player_state['inventory'] }
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
when 'remove'
@game.remove_inventory_item!(item['id'])
render json: { success: true, inventory: @game.player_state['inventory'] }
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
else
render json: { success: false, message: 'Invalid action' }, status: :bad_request
end
end
# ==========================================
# Objectives System
# ==========================================
# GET /games/:id/objectives
# Returns current objectives and their state
def objectives
authorize @game if defined?(Pundit)
render json: @game.objectives_state
end
# POST /games/:id/objectives/tasks/:task_id
# Complete a specific task
def complete_task
authorize @game if defined?(Pundit)
task_id = params[:task_id]
unless task_id.present?
return render json: { success: false, error: 'Missing task_id' }, status: :bad_request
end
# For submit_flags tasks, accept submittedFlags from request body for validation
validation_data = params[:validation_data] || {}
if params[:submittedFlags].present?
validation_data[:submittedFlags] = params[:submittedFlags]
end
result = @game.complete_task!(task_id, validation_data)
if result[:success]
Rails.logger.info "[BreakEscape] Task completed: #{task_id}"
render json: result
else
Rails.logger.warn "[BreakEscape] Task completion failed: #{task_id} - #{result[:error]}"
render json: result, status: :unprocessable_entity
end
end
# PUT /games/:id/objectives/tasks/:task_id
# Update task progress (for collect_items and submit_flags tasks)
def update_task_progress
authorize @game if defined?(Pundit)
task_id = params[:task_id]
progress = params[:progress].to_i
submitted_flags = params[:submittedFlags]
unless task_id.present?
return render json: { success: false, error: 'Missing task_id' }, status: :bad_request
end
result = @game.update_task_progress!(task_id, progress, submitted_flags)
Rails.logger.debug "[BreakEscape] Task progress updated: #{task_id} = #{progress}, submittedFlags: #{submitted_flags&.length || 0}"
render json: result
end
2025-11-28 15:36:10 +00:00
# ==========================================
# VM/Flag Integration
# ==========================================
# POST /games/:id/flags
# Submit a CTF flag for validation
def submit_flag
authorize @game if defined?(Pundit)
flag_key = params[:flag]
unless flag_key.present?
return render json: { success: false, message: 'No flag provided' }, status: :bad_request
end
result = @game.submit_flag(flag_key)
if result[:success]
# Find flag-station and generate flag identifier
flag_station = find_flag_station_for_flag(flag_key)
flag_id = generate_flag_identifier(flag_key, flag_station)
vm_id = flag_station&.dig('acceptsVms', 0)
2025-11-28 15:36:10 +00:00
# Find rewards for this flag in scenario
rewards = find_flag_rewards(flag_key)
# Process rewards
reward_results = process_flag_rewards(flag_key, rewards)
Rails.logger.info "[BreakEscape] Flag submitted: #{flag_key}, flagId: #{flag_id}, rewards: #{reward_results.length}"
2025-11-28 15:36:10 +00:00
render json: {
success: true,
message: result[:message],
flag: flag_key,
flagId: flag_id,
vmId: vm_id,
2025-11-28 15:36:10 +00:00
rewards: reward_results
}
else
render json: result, status: :unprocessable_entity
end
end
private
def set_game
@game = Game.find(params[:id])
end
def filter_requires_recursive(obj)
case obj
when Hash
# Remove 'requires' for exploitable lock types (key/pin/password)
# Keep it for biometric/bluetooth/rfid since they reference collectible items, not answers
# - biometric: requires fingerprint owner name (e.g., "Mrs Moo")
# - bluetooth: requires device MAC/name (e.g., "00:11:22:33:44:55")
# - rfid: requires card IDs (e.g., ["master_keycard"])
lock_type = obj['lockType']
if lock_type && !%w[biometric bluetooth rfid].include?(lock_type)
obj.delete('requires')
end
# Recursively filter nested structures
obj.each_value { |value| filter_requires_recursive(value) }
when Array
obj.each { |item| filter_requires_recursive(item) }
end
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
def track_npc_encounters(room_id, room_data)
return unless room_data['npcs'].present?
begin
npc_ids = room_data['npcs'].map { |npc| npc['id'] }
# Ensure player_state is a hash
unless @game.player_state.is_a?(Hash)
Rails.logger.error "[BreakEscape] player_state is not a Hash: #{@game.player_state.class}"
@game.player_state = {}
end
# Ensure encounteredNPCs is an array
@game.player_state['encounteredNPCs'] ||= []
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# Handle case where encounteredNPCs might not be an array (legacy data)
unless @game.player_state['encounteredNPCs'].is_a?(Array)
Rails.logger.warn "[BreakEscape] encounteredNPCs is not an Array: #{@game.player_state['encounteredNPCs'].class}, resetting"
@game.player_state['encounteredNPCs'] = []
end
new_npcs = npc_ids - @game.player_state['encounteredNPCs']
return if new_npcs.empty?
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# Log detailed information about each new NPC encountered
new_npcs.each do |npc_id|
npc_data = room_data['npcs'].find { |npc| npc['id'] == npc_id }
if npc_data
display_name = npc_data['displayName'] || npc_id
npc_type = npc_data['npcType'] || 'unknown'
Rails.logger.info "[BreakEscape] 🎭 NPC ENCOUNTERED: #{display_name} (#{npc_id}) - Type: #{npc_type} - Room: #{room_id}"
else
Rails.logger.info "[BreakEscape] 🎭 NPC ENCOUNTERED: #{npc_id} - Room: #{room_id}"
end
end
@game.player_state['encounteredNPCs'] = (@game.player_state['encounteredNPCs'] + new_npcs).uniq
@game.save!
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
total_encountered = @game.player_state['encounteredNPCs'].length
Rails.logger.info "[BreakEscape] ✅ Tracked #{new_npcs.length} new NPC encounter(s) in room #{room_id}. Total NPCs encountered: #{total_encountered}"
rescue => e
Rails.logger.error "[BreakEscape] Error tracking NPC encounters: #{e.message}\n#{e.backtrace.first(5).join("\n")}"
# Continue without tracking to avoid breaking room loading
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
end
def find_container_in_scenario(container_id)
@game.scenario_data['rooms'].each do |room_id, room_data|
# Search objects for container
container = find_container_recursive(room_data['objects'], container_id)
return container if container
# Search nested contents
room_data['objects']&.each do |obj|
container = search_nested_contents(obj['contents'], container_id)
return container if container
end
end
nil
end
def find_container_recursive(objects, container_id)
return nil unless objects
objects.each do |obj|
# Check if this object matches
if obj['id'] == container_id || (obj['name'] && obj['name'] == container_id)
return obj if obj['contents'].present?
end
# Recursively search nested contents
nested = find_container_recursive(obj['contents'], container_id)
return nested if nested
end
nil
end
def find_object_in_scenario(object_id)
# Search all rooms for the object
@game.scenario_data['rooms'].each do |_room_id, room_data|
object = room_data['objects']&.find { |obj|
obj['id'] == object_id || obj['name'] == object_id
}
return object if object
end
nil
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
def search_nested_contents(contents, container_id)
return nil unless contents
contents.each do |item|
return item if (item['id'] == container_id || item['name'] == container_id) && item['contents'].present?
nested = search_nested_contents(item['contents'], container_id)
return nested if nested
end
nil
end
def check_container_unlocked(container_id, container_data)
unlocked_list = @game.player_state['unlockedObjects'] || []
# Check multiple possible identifiers
unlocked_list.include?(container_id) ||
unlocked_list.include?(container_data['id']) ||
unlocked_list.include?(container_data['name']) ||
unlocked_list.include?(container_data['type'])
end
def filter_container_contents(container_data)
contents = container_data['contents']&.map do |item|
item_copy = item.deep_dup
@game.send(:filter_requires_and_contents_recursive, item_copy)
item_copy
end || []
# Filter out items that are already in the player's inventory
inventory = @game.player_state['inventory'] || []
Rails.logger.debug "[BreakEscape] Filtering container contents. Inventory has #{inventory.length} items"
Rails.logger.debug "[BreakEscape] Container has #{contents.length} items before filtering"
filtered_contents = contents.reject do |item|
in_inventory = item_in_inventory?(item, inventory)
if in_inventory
Rails.logger.debug "[BreakEscape] Filtering out item: #{item['type']} / #{item['id']} / #{item['name']} (already in inventory)"
end
in_inventory
end
Rails.logger.debug "[BreakEscape] Container has #{filtered_contents.length} items after filtering"
filtered_contents
end
# Check if an item is already in the player's inventory
# Matches by type, id, or name (similar to validation logic)
def item_in_inventory?(item, inventory)
return false if inventory.blank? || item.blank?
# Normalize item data (handle both string and symbol keys)
item_type = item['type'] || item[:type]
item_id = item['key_id'] || item[:key_id] || item['id'] || item[:id]
item_name = item['name'] || item[:name]
Rails.logger.debug "[BreakEscape] Checking if item in inventory: type=#{item_type}, id=#{item_id}, name=#{item_name}"
inventory.any? do |inv_item|
# Inventory items are stored as flat objects (not nested in scenarioData)
# Handle both string and symbol keys
inv_type = inv_item['type'] || inv_item[:type]
inv_id = inv_item['key_id'] || inv_item[:key_id] || inv_item['id'] || inv_item[:id]
inv_name = inv_item['name'] || inv_item[:name]
Rails.logger.debug "[BreakEscape] Comparing with inventory item: type=#{inv_type}, id=#{inv_id}, name=#{inv_name}"
# Must match type
next false unless inv_type == item_type
# If both have IDs, match by ID (most specific)
if item_id.present? && inv_id.present?
match = inv_id.to_s == item_id.to_s
Rails.logger.debug "[BreakEscape] ID match: #{match} (#{item_id} == #{inv_id})"
return true if match
end
# If both have names, match by name (fallback if no ID match)
if item_name.present? && inv_name.present?
match = inv_name.to_s == item_name.to_s
Rails.logger.debug "[BreakEscape] Name match: #{match} (#{item_name} == #{inv_name})"
return true if match
end
# If item has no ID or name, match by type only (less specific, but works for generic items)
if item_id.blank? && item_name.blank?
Rails.logger.debug "[BreakEscape] Type-only match (no ID/name)"
return true
end
false
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
end
# Items that are always allowed in inventory (core game mechanics)
ALWAYS_ALLOWED_ITEMS = %w[notepad].freeze
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
def validate_item_collectible(item)
item_type = item['type']
# Use key_id for keys (more unique), fall back to id for other items
item_id = item['key_id'] || item['id']
item_name = item['name']
Rails.logger.info "[BreakEscape] validate_item_collectible: type=#{item_type}, id=#{item_id}, name=#{item_name}"
# Always allow core game items like notepad
if ALWAYS_ALLOWED_ITEMS.include?(item_type)
Rails.logger.info "[BreakEscape] Item is always allowed: #{item_type}"
return nil
end
# Check if this is a starting item first (if so, skip all other checks)
is_starting_item = @game.scenario_data['startItemsInInventory']&.any? do |start_item|
start_item['type'] == item_type && (start_item['id'] == item_id || start_item['name'] == item_name)
end
if is_starting_item
Rails.logger.info "[BreakEscape] Item is a starting item, skipping room/container checks"
return nil # Starting items are always valid
end
# Search for item, prioritizing accessible locations (not locked containers/rooms)
found_item_info = find_accessible_item(item_type, item_id, item_name)
unless found_item_info
error_msg = "Item not found in scenario: #{item_type}"
Rails.logger.warn "[BreakEscape] #{error_msg}"
return error_msg
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
found_item = found_item_info[:item]
location = found_item_info[:location]
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# Check if item is takeable
unless found_item['takeable']
error_msg = "Item is not takeable: #{found_item['name']}"
Rails.logger.warn "[BreakEscape] #{error_msg}"
return error_msg
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
end
# Check access based on location type
if location[:type] == 'container'
container_id = location[:container_id]
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
unless @game.player_state['unlockedObjects'].include?(container_id)
error_msg = "Container not unlocked: #{container_id}"
Rails.logger.warn "[BreakEscape] #{error_msg}"
return error_msg
end
elsif location[:type] == 'room'
room_id = location[:room_id]
room_info = @game.scenario_data['rooms'][room_id]
if room_info && room_info['locked'] && !@game.player_state['unlockedRooms'].include?(room_id)
error_msg = "Room not unlocked: #{room_id}"
Rails.logger.warn "[BreakEscape] #{error_msg}"
return error_msg
end
elsif location[:type] == 'npc'
npc_id = location[:npc_id]
unless @game.player_state['encounteredNPCs'].include?(npc_id)
error_msg = "NPC not encountered: #{npc_id}"
Rails.logger.warn "[BreakEscape] #{error_msg}"
return error_msg
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
end
elsif location[:type] == 'flag_station'
# Flag station items are valid if they're in the player's inventory (already awarded server-side)
# or if the corresponding flag has been submitted
flag_station_id = location[:flag_station_id]
Rails.logger.info "[BreakEscape] Item from flag station #{flag_station_id}, allowing (flag reward)"
# Flag rewards are always valid - the server already validated and added them
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
end
Rails.logger.info "[BreakEscape] Item collection valid: #{item_type}"
nil # No error
end
def find_accessible_item(item_type, item_id, item_name)
# Priority 1: Items in unlocked rooms (most accessible)
@game.scenario_data['rooms'].each do |room_id, room_data|
if room_data['locked'] == false || @game.player_state['unlockedRooms'].include?(room_id)
room_data['objects']&.each do |obj|
if obj['type'] == item_type && (obj['key_id'] == item_id || obj['id'] == item_id || obj['name'] == item_name || obj['name'] == item_id)
return { item: obj, location: { type: 'room', room_id: room_id } }
end
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
end
end
# Priority 2: Items in any room (including locked ones - will validate in main method)
@game.scenario_data['rooms'].each do |room_id, room_data|
room_data['objects']&.each do |obj|
if obj['type'] == item_type && (obj['key_id'] == item_id || obj['id'] == item_id || obj['name'] == item_name || obj['name'] == item_id)
return { item: obj, location: { type: 'room', room_id: room_id } }
end
# Search nested contents in room objects
obj['contents']&.each do |content|
if content['type'] == item_type && (content['key_id'] == item_id || content['id'] == item_id || content['name'] == item_name || content['name'] == item_id)
return { item: content, location: { type: 'container', container_id: obj['id'] || obj['name'] } }
end
end
# Search flag-station itemsHeld (flag reward items)
if obj['type'] == 'flag-station' && obj['itemsHeld'].present?
obj['itemsHeld'].each do |held_item|
if held_item['type'] == item_type && (held_item['key_id'] == item_id || held_item['keyId'] == item_id || held_item['id'] == item_id || held_item['name'] == item_name || held_item['name'] == item_id)
return { item: held_item, location: { type: 'flag_station', flag_station_id: obj['id'] || obj['name'], room_id: room_id } }
end
end
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
end
# Priority 3: Items held by NPCs in this room
room_data['npcs']&.each do |npc|
next unless npc['itemsHeld'].present?
npc['itemsHeld'].each do |held_item|
if held_item['type'] == item_type && (held_item['key_id'] == item_id || held_item['id'] == item_id || held_item['name'] == item_name || held_item['name'] == item_id)
return { item: held_item, location: { type: 'npc', npc_id: npc['id'], room_id: room_id } }
end
end
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
end
nil
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
end
def find_item_in_scenario(item_type, item_id, item_name = nil)
# First check startItemsInInventory (items the player begins with)
@game.scenario_data['startItemsInInventory']&.each do |item|
if item['type'] == item_type && (item['key_id'] == item_id || item['id'] == item_id || item['name'] == item_name || item['name'] == item_id)
return item
end
end
# Then search room objects
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
@game.scenario_data['rooms'].each do |room_id, room_data|
# Search room objects
room_data['objects']&.each do |obj|
if obj['type'] == item_type && (obj['key_id'] == item_id || obj['id'] == item_id || obj['name'] == item_name || obj['name'] == item_id)
return obj
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
# Search nested contents
obj['contents']&.each do |content|
if content['type'] == item_type && (content['key_id'] == item_id || content['id'] == item_id || content['name'] == item_name || content['name'] == item_id)
return content
end
Implement comprehensive server-side validation and data filtering for client actions Server-side changes: - Game model: Initialize starting items in player inventory from scenario - Game model: Add filter_requires_and_contents_recursive to hide solutions and locked contents - Game model: Fix filtered_room_data to preserve lockType while removing requires - GamesController: Add scenario_map endpoint for minimal layout metadata - GamesController: Update room endpoint with access control and NPC encounter tracking - GamesController: Add container endpoint for lazy-loading locked container contents - GamesController: Update inventory endpoint with comprehensive validation - Validates item exists in scenario - Checks item is takeable - Verifies container is unlocked if item is in container - Verifies room is unlocked if room is locked - Checks NPC is encountered if item held by NPC - GamesController: Update unlock endpoint with transaction safety - GamesController: Update sync_state to verify room accessibility - Routes: Add scenario_map and container routes Client-side changes: - inventory.js: Make addToInventory async and add server validation before local updates - container-minigame.js: Add lazy-loading of container contents from server - game.js: Update to use scenario_map endpoint for reduced initial payload - api-client.js: Add getScenarioMap method alongside getScenario Security improvements: - Prevents client-side cheating by validating all actions server-side - Hides solution fields (requires) from client responses - Hides locked container contents until unlocked - Enforces room and container access controls - Tracks NPC encounters automatically - All validation failures return clear error messages Implements plans from: - planning_notes/validate_client_actions/GOALS_AND_DECISIONS.md - planning_notes/validate_client_actions/IMPLEMENTATION_PLAN.md
2025-11-22 00:46:55 +00:00
end
end
end
nil
end
def find_item_container(item_type, item_id)
@game.scenario_data['rooms'].each do |room_id, room_data|
room_data['objects']&.each do |obj|
obj['contents']&.each do |content|
if content['type'] == item_type && (content['id'] == item_id || content['name'] == item_id)
return { id: obj['id'] || obj['name'], locked: obj['locked'] }
end
end
end
end
nil
end
def find_item_room(item_type, item_id)
@game.scenario_data['rooms'].each do |room_id, room_data|
room_data['objects']&.each do |obj|
if obj['type'] == item_type && (obj['id'] == item_id || obj['name'] == item_id)
return { id: room_id, locked: room_data['locked'] }
end
end
end
nil
end
def find_npc_holding_item(item_type, item_id)
@game.scenario_data['rooms'].each do |room_id, room_data|
room_data['npcs']&.each do |npc|
next unless npc['itemsHeld'].present?
# itemsHeld is array of full item objects (same structure as room objects)
npc['itemsHeld'].each do |held_item|
# Match by type (required) and optionally by id/name
if held_item['type'] == item_type
# If item_id provided, verify it matches
if item_id.present?
item_matches = (held_item['id'] == item_id) ||
(held_item['name'] == item_id) ||
(item_id == item_type) # Fallback if no id field
next unless item_matches
end
return {
id: npc['id'],
npc: npc,
item: held_item,
type: 'npc'
}
end
end
end
end
nil
end
def find_npc_in_scenario(npc_id)
available_npcs = []
@game.scenario_data['rooms']&.each do |room_id, room_data|
room_data['npcs']&.each do |npc|
available_npcs << "#{npc['id']} (#{room_id})"
return npc if npc['id'] == npc_id
end
end
# Log available NPCs for debugging
if available_npcs.any?
Rails.logger.debug "[BreakEscape] Available NPCs: #{available_npcs.join(', ')}"
else
Rails.logger.warn "[BreakEscape] No NPCs found in scenario data"
end
nil
end
# Resolve ink path and compile if necessary
def resolve_and_compile_ink(story_path)
# Use Engine root for Rails Engine context
engine_root = BreakEscape::Engine.root
base_path = engine_root.join(story_path)
json_path = find_compiled_json(base_path)
ink_path = find_ink_source(base_path)
if ink_path && needs_compilation?(ink_path, json_path)
Rails.logger.info "[BreakEscape] Compiling #{File.basename(ink_path)}..."
json_path = compile_ink(ink_path)
end
json_path
end
def find_compiled_json(base_path)
return base_path if File.exist?(base_path)
ink_json_path = base_path.to_s.gsub(/\.json$/, '.ink.json')
return Pathname.new(ink_json_path) if File.exist?(ink_json_path)
json_path = base_path.to_s.gsub(/\.ink\.json$/, '.json')
return Pathname.new(json_path) if File.exist?(json_path)
nil
end
def find_ink_source(base_path)
ink_path = base_path.to_s.gsub(/\.(ink\.)?json$/, '.ink')
File.exist?(ink_path) ? Pathname.new(ink_path) : nil
end
def needs_compilation?(ink_path, json_path)
return true unless json_path && File.exist?(json_path)
File.mtime(ink_path) > File.mtime(json_path)
end
def compile_ink(ink_path)
output_path = ink_path.to_s.gsub(/\.ink$/, '.json')
inklecate_path = BreakEscape::Engine.root.join('bin', 'inklecate')
stdout, stderr, status = Open3.capture3(
inklecate_path.to_s,
'-o', output_path,
ink_path.to_s
)
unless status.success?
Rails.logger.error "[BreakEscape] Ink compilation failed: #{stderr}"
raise "Ink compilation failed for #{File.basename(ink_path)}: #{stderr}"
end
if stderr.present?
Rails.logger.warn "[BreakEscape] Ink compilation warnings: #{stderr}"
end
Rails.logger.info "[BreakEscape] Compiled #{File.basename(ink_path)} (#{(File.size(output_path) / 1024.0).round(2)} KB)"
Pathname.new(output_path)
end
def render_error(message, status)
render json: { error: message }, status: status
end
2025-11-28 15:36:10 +00:00
# ==========================================
# Flag Reward Helpers
# ==========================================
def find_flag_rewards(flag_key)
rewards = []
# Search scenario for flag-station with this flag
@game.scenario_data['rooms']&.each do |room_id, room|
room['objects']&.each do |obj|
next unless obj['type'] == 'flag-station'
next unless obj['flags']&.any? { |f| f.downcase == flag_key.downcase }
flag_station_id = obj['id'] || obj['name']
# Support both hash structure (preferred) and array structure (legacy)
if obj['flagRewards'].is_a?(Hash)
# Hash structure: { "flag{key}": { "type": "unlock_door", ... } }
# Case-insensitive lookup
reward_key = obj['flagRewards'].keys.find { |k| k.downcase == flag_key.downcase }
reward = obj['flagRewards'][reward_key] if reward_key
if reward
rewards << reward.merge(
'flag_station_id' => flag_station_id,
'room_id' => room_id
)
end
elsif obj['flagRewards'].is_a?(Array)
# Array structure (legacy): rewards[i] corresponds to flags[i]
flag_index = obj['flags'].find_index { |f| f.downcase == flag_key.downcase }
if flag_index && obj['flagRewards'][flag_index]
rewards << obj['flagRewards'][flag_index].merge(
'flag_station_id' => flag_station_id,
'room_id' => room_id
)
end
end
end
end
rewards
end
def process_flag_rewards(flag_key, rewards)
results = []
rewards.each do |reward|
# Skip if already claimed
if @game.player_state['flag_rewards_claimed']&.include?(flag_key)
results << { type: 'skipped', reason: 'Already claimed' }
next
end
# Process each reward type
case reward['type']
when 'give_item'
results << process_item_reward(reward, flag_key)
when 'unlock_door'
results << process_door_unlock_reward(reward, flag_key)
when 'emit_event'
results << process_event_reward(reward, flag_key)
else
results << { type: 'unknown', data: reward }
end
end
# Mark rewards as claimed
@game.player_state['flag_rewards_claimed'] ||= []
@game.player_state['flag_rewards_claimed'] << flag_key
@game.save!
results
end
def process_item_reward(reward, flag_key)
# Find the flag-station object to pull item from its itemsHeld
flag_station = find_flag_station_by_id(reward['flag_station_id'])
return { type: 'error', message: 'Flag station not found' } unless flag_station
# Get item from itemsHeld (similar to NPC item giving)
item = flag_station['itemsHeld']&.find { |i| i['type'] == reward['item_type'] || i['name'] == reward['item_name'] }
return { type: 'error', message: 'Item not found in flag station' } unless item
# Add to player inventory
@game.add_inventory_item!(item)
{ type: 'give_item', item: item, success: true }
end
def process_door_unlock_reward(reward, flag_key)
room_id = reward['room_id'] || reward['target_room']
return { type: 'error', message: 'No room_id specified' } unless room_id
# Unlock the door (same as NPC door unlock)
@game.unlock_room!(room_id)
{ type: 'unlock_door', room_id: room_id, success: true }
end
def process_event_reward(reward, flag_key)
# Emit event (NPC can listen and trigger conversations)
event_name = reward['event_name'] || "flag_submitted:#{flag_key}"
# Store event in player_state for client to emit
@game.player_state['pending_events'] ||= []
@game.player_state['pending_events'] << {
'name' => event_name,
'data' => { 'flag' => flag_key, 'timestamp' => Time.current.to_i }
}
@game.save!
{ type: 'emit_event', event_name: event_name, success: true }
end
def find_flag_station_by_id(flag_station_id)
@game.scenario_data['rooms']&.each do |_room_id, room|
room['objects']&.each do |obj|
return obj if (obj['id'] || obj['name']) == flag_station_id && obj['type'] == 'flag-station'
end
end
nil
end
# Find the flag-station that contains the submitted flag
def find_flag_station_for_flag(flag_key)
@game.scenario_data['rooms']&.each do |_room_id, room|
room['objects']&.each do |obj|
next unless obj['type'] == 'flag-station'
next unless obj['flags']&.any? { |f| f.downcase == flag_key.downcase }
return obj
end
end
nil
end
# Generate a flag identifier in the format: {vmId}-flag{index}
# Example: "desktop-flag1", "kali-flag2"
def generate_flag_identifier(flag_key, flag_station)
return nil unless flag_station
# Find flag index in flags array (0-based)
flag_index = flag_station['flags']&.find_index { |f| f.downcase == flag_key.downcase }
return nil unless flag_index
# Get VM ID (use first VM if multiple)
vm_id = flag_station['acceptsVms']&.first
return nil unless vm_id
# Generate identifier: "desktop-flag1" (1-indexed for display)
"#{vm_id}-flag#{flag_index + 1}"
end
end
end