Files
BreakEscape/app/models/break_escape/player_preference.rb
Z. Cliffe Schreuders 61afc0a666 Refactor character assets and player preferences
- Deleted unused character images: woman_in_science_lab_coat.png and woman_with_black_long_hair_bow_in_hair_long_sleeve_(1).png.
- Added new padlock icon asset for UI.
- Introduced player_preferences.css for styling the player preferences configuration screen.
- Updated game.js to load new character atlases with simplified filenames.
- Enhanced player.js to create custom idle animations for characters.
- Implemented sprite-grid.js for sprite selection UI, including a preview feature.
- Updated database schema to include break_escape_player_preferences table for storing player settings.
- Modified convert_pixellab_to_spritesheet.py to map character names to simplified filenames and extract headshots from character images.
2026-02-12 14:35:14 +00:00

102 lines
3.0 KiB
Ruby

module BreakEscape
class PlayerPreference < ApplicationRecord
self.table_name = 'break_escape_player_preferences'
# Associations
belongs_to :player, polymorphic: true
# Constants - Available sprite sheets (must match game.js preload and assets on disk)
AVAILABLE_SPRITES = %w[
female_hacker_hood
female_hacker_hood_down
female_office_worker
female_security_guard
female_telecom
female_spy
female_scientist
woman_bow
male_hacker_hood
male_hacker_hood_down
male_office_worker
male_security_guard
male_telecom
male_spy
male_scientist
male_nerd
].freeze
# Mapping from UI key to game texture key (game.js loads these atlas keys)
# woman_bow -> woman_blowse (filename typo in assets); others are identity
SPRITE_FILE_MAPPING = {
'woman_bow' => 'woman_blowse'
}.freeze
# Get the texture key for game injection (must match game.js preload keys)
def self.sprite_filename(sprite_name)
SPRITE_FILE_MAPPING[sprite_name] || sprite_name
end
# Validations
validates :player, presence: true
validates :selected_sprite, inclusion: { in: AVAILABLE_SPRITES }, allow_nil: true
validates :in_game_name, presence: true, length: { in: 1..20 }, format: {
with: /\A[a-zA-Z0-9_ ]+\z/,
message: 'only allows letters, numbers, spaces, and underscores'
}
# Callbacks
before_validation :set_defaults, on: :create
# Check if selected sprite is valid for a given scenario
def sprite_valid_for_scenario?(scenario_data)
# If no sprite selected, invalid (player must choose)
return false if selected_sprite.blank?
# If scenario has no restrictions, any sprite is valid
return true unless scenario_data['validSprites'].present?
valid_sprites = Array(scenario_data['validSprites'])
# Check if sprite matches any pattern
valid_sprites.any? do |pattern|
sprite_matches_pattern?(selected_sprite, pattern)
end
end
# Check if player has selected a sprite
def sprite_selected?
selected_sprite.present?
end
private
def set_defaults
# Seed in_game_name from player.handle if available
if in_game_name.blank? && player.respond_to?(:handle) && player.handle.present?
self.in_game_name = player.handle
end
# Fallback to 'Zero' if still blank
self.in_game_name = 'Zero' if in_game_name.blank?
# NOTE: selected_sprite left NULL - player MUST choose before first game
end
# Pattern matching for sprite validation
# Supports:
# - Exact match: "female_hacker"
# - Wildcard: "female_*" (all female sprites)
# - Wildcard: "*_hacker" (all hacker sprites)
# - Wildcard: "*" (all sprites)
def sprite_matches_pattern?(sprite, pattern)
return true if pattern == '*'
# Convert wildcard pattern to regex
regex_pattern = Regexp.escape(pattern).gsub('\*', '.*')
regex = /\A#{regex_pattern}\z/
sprite.match?(regex)
end
end
end