mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
- Create break_escape_missions table (metadata only) - Create break_escape_games table (state + scenario snapshot) - Add Mission model with ERB scenario generation - Add Game model with state management methods - Use JSONB for flexible state storage - Polymorphic player association (User/DemoUser)
186 lines
5.1 KiB
Ruby
186 lines
5.1 KiB
Ruby
module BreakEscape
|
|
class Game < ApplicationRecord
|
|
self.table_name = 'break_escape_games'
|
|
|
|
# Associations
|
|
belongs_to :player, polymorphic: true
|
|
belongs_to :mission, class_name: 'BreakEscape::Mission'
|
|
|
|
# Validations
|
|
validates :player, presence: true
|
|
validates :mission, presence: true
|
|
validates :status, inclusion: { in: %w[in_progress completed abandoned] }
|
|
|
|
# Scopes
|
|
scope :active, -> { where(status: 'in_progress') }
|
|
scope :completed, -> { where(status: 'completed') }
|
|
|
|
# Callbacks
|
|
before_create :generate_scenario_data
|
|
before_create :initialize_player_state
|
|
before_create :set_started_at
|
|
|
|
# Room management
|
|
def unlock_room!(room_id)
|
|
player_state['unlockedRooms'] ||= []
|
|
player_state['unlockedRooms'] << room_id unless player_state['unlockedRooms'].include?(room_id)
|
|
save!
|
|
end
|
|
|
|
def room_unlocked?(room_id)
|
|
player_state['unlockedRooms']&.include?(room_id) || start_room?(room_id)
|
|
end
|
|
|
|
def start_room?(room_id)
|
|
scenario_data['startRoom'] == room_id
|
|
end
|
|
|
|
# Object management
|
|
def unlock_object!(object_id)
|
|
player_state['unlockedObjects'] ||= []
|
|
player_state['unlockedObjects'] << object_id unless player_state['unlockedObjects'].include?(object_id)
|
|
save!
|
|
end
|
|
|
|
def object_unlocked?(object_id)
|
|
player_state['unlockedObjects']&.include?(object_id)
|
|
end
|
|
|
|
# Inventory management
|
|
def add_inventory_item!(item)
|
|
player_state['inventory'] ||= []
|
|
player_state['inventory'] << item
|
|
save!
|
|
end
|
|
|
|
def remove_inventory_item!(item_id)
|
|
player_state['inventory']&.reject! { |item| item['id'] == item_id }
|
|
save!
|
|
end
|
|
|
|
# NPC tracking
|
|
def encounter_npc!(npc_id)
|
|
player_state['encounteredNPCs'] ||= []
|
|
player_state['encounteredNPCs'] << npc_id unless player_state['encounteredNPCs'].include?(npc_id)
|
|
save!
|
|
end
|
|
|
|
# Global variables (synced with client)
|
|
def update_global_variables!(variables)
|
|
player_state['globalVariables'] ||= {}
|
|
player_state['globalVariables'].merge!(variables)
|
|
save!
|
|
end
|
|
|
|
# Minigame state
|
|
def add_biometric_sample!(sample)
|
|
player_state['biometricSamples'] ||= []
|
|
player_state['biometricSamples'] << sample
|
|
save!
|
|
end
|
|
|
|
def add_bluetooth_device!(device)
|
|
player_state['bluetoothDevices'] ||= []
|
|
unless player_state['bluetoothDevices'].any? { |d| d['mac'] == device['mac'] }
|
|
player_state['bluetoothDevices'] << device
|
|
end
|
|
save!
|
|
end
|
|
|
|
def add_note!(note)
|
|
player_state['notes'] ||= []
|
|
player_state['notes'] << note
|
|
save!
|
|
end
|
|
|
|
# Health management
|
|
def update_health!(value)
|
|
player_state['health'] = value.clamp(0, 100)
|
|
save!
|
|
end
|
|
|
|
# Scenario data access
|
|
def room_data(room_id)
|
|
scenario_data.dig('rooms', room_id)
|
|
end
|
|
|
|
def filtered_room_data(room_id)
|
|
room = room_data(room_id)&.deep_dup
|
|
return nil unless room
|
|
|
|
# Remove solutions
|
|
room.delete('requires')
|
|
room.delete('lockType') if room['locked']
|
|
|
|
# Remove solutions from objects
|
|
room['objects']&.each do |obj|
|
|
obj.delete('requires')
|
|
obj.delete('lockType') if obj['locked']
|
|
obj.delete('contents') if obj['locked']
|
|
end
|
|
|
|
room
|
|
end
|
|
|
|
# Unlock validation
|
|
def validate_unlock(target_type, target_id, attempt, method)
|
|
if target_type == 'door'
|
|
room = room_data(target_id)
|
|
return false unless room && room['locked']
|
|
|
|
case method
|
|
when 'key'
|
|
room['requires'] == attempt
|
|
when 'pin', 'password'
|
|
room['requires'].to_s == attempt.to_s
|
|
when 'lockpick'
|
|
true # Client minigame succeeded
|
|
else
|
|
false
|
|
end
|
|
else
|
|
# Find object in all rooms
|
|
scenario_data['rooms'].each do |_room_id, room_data|
|
|
object = room_data['objects']&.find { |obj| obj['id'] == target_id }
|
|
next unless object && object['locked']
|
|
|
|
case method
|
|
when 'key'
|
|
return object['requires'] == attempt
|
|
when 'pin', 'password'
|
|
return object['requires'].to_s == attempt.to_s
|
|
when 'lockpick'
|
|
return true
|
|
end
|
|
end
|
|
false
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def generate_scenario_data
|
|
self.scenario_data = mission.generate_scenario_data
|
|
end
|
|
|
|
def initialize_player_state
|
|
self.player_state ||= {}
|
|
self.player_state['currentRoom'] ||= scenario_data['startRoom']
|
|
self.player_state['unlockedRooms'] ||= [scenario_data['startRoom']]
|
|
self.player_state['unlockedObjects'] ||= []
|
|
self.player_state['inventory'] ||= []
|
|
self.player_state['encounteredNPCs'] ||= []
|
|
self.player_state['globalVariables'] ||= {}
|
|
self.player_state['biometricSamples'] ||= []
|
|
self.player_state['biometricUnlocks'] ||= []
|
|
self.player_state['bluetoothDevices'] ||= []
|
|
self.player_state['notes'] ||= []
|
|
self.player_state['health'] ||= 100
|
|
end
|
|
|
|
def set_started_at
|
|
self.started_at ||= Time.current
|
|
end
|
|
end
|
|
end
|