class WardrobeController < ApplicationController def show # Load saved outfit if an ID is provided (e.g. /outfits/:id/v2) @saved_outfit = Outfit.find(params[:id]) if params[:id].present? # If visiting a saved outfit with no state params, redirect with the # outfit's state as query params. This keeps URL-as-source-of-truth simple: # the rest of the action always reads from params. if @saved_outfit && !outfit_state_params_present? redirect_to wardrobe_v2_outfit_path(@saved_outfit, **@saved_outfit.wardrobe_params) return end # Set the form target path for all wardrobe forms @wardrobe_path = @saved_outfit ? wardrobe_v2_outfit_path(@saved_outfit) : wardrobe_v2_path # Get selected species and color from params, or default to Blue Acara @selected_species = params[:species] ? Species.find_by_id(params[:species]) : Species.find_by_name("Acara") @selected_color = params[:color] ? Color.find_by_id(params[:color]) : Color.find_by_name("Blue") # Load valid colors for the selected species (colors that have existing pet types) @species = Species.alphabetical @colors = @selected_species.compatible_colors # Find the best pet type for this species+color combo # If the exact combo doesn't exist, this will fall back to a simple color @pet_type = PetType.for_species_and_color( species_id: @selected_species.id, color_id: @selected_color.id ) # Use the pet type's actual color as the selected color # (might differ from requested color if we fell back to a simple color) @selected_color = @pet_type&.color # Get the selected pose from params, or default to nil (will use canonical) @selected_pose = params[:pose] # Find the pet state for the selected pose, or use canonical @pet_state = if @pet_type && @selected_pose.present? @pet_type.pet_states.with_pose(@selected_pose).first || @pet_type.canonical_pet_state else @pet_type&.canonical_pet_state end # If we found a pet_state, use its actual pose as the selected pose @selected_pose = @pet_state&.pose # Load all available poses for this pet type (for the pose picker) @available_poses = @pet_type ? available_poses_for(@pet_type) : {} # Preload the layers for all available poses so the thumbnails render efficiently if @pet_type pose_pet_states = @available_poses.values.compact SwfAsset.preload_manifests(pose_pet_states.flat_map(&:swf_assets)) end # Load alt style from params, scoped to the current species @alt_style = if params[:style].present? && @selected_species AltStyle.where(species_id: @selected_species.id).find_by(id: params[:style]) end # Load all available alt styles for this species (for the style picker) @available_alt_styles = @selected_species ? AltStyle.where(species_id: @selected_species.id).by_name_grouped : [] # Load items from the objects[] parameter item_ids = params[:objects] || [] items = Item.where(id: item_ids) # Build the outfit @outfit = Outfit.new( name: @saved_outfit ? @saved_outfit.name : (params[:name].presence || "Untitled outfit"), pet_state: @pet_state, alt_style: @alt_style, worn_items: items, ) # Preload the manifests for all visible layers, so they load efficiently # in parallel rather than sequentially when rendering SwfAsset.preload_manifests(@outfit.visible_layers) # Also preload alt style layer manifests for the style picker thumbnails SwfAsset.preload_manifests(@alt_style.swf_assets.to_a) if @alt_style # Compute saved outfit state for the view if @saved_outfit @has_unsaved_changes = !@outfit.same_wardrobe_state_as?(@saved_outfit) @is_owner = user_signed_in? && current_user.id == @saved_outfit.user_id else @has_unsaved_changes = false end # Handle search mode @search_mode = params[:q].present? if @search_mode search_filters = build_search_filters(params[:q], @outfit) query_params = ActionController::Parameters.new( search_filters.each_with_index.map { |filter, i| [i.to_s, filter] }.to_h ) @query = Item::Search::Query.from_params(query_params, current_user) @search_results = @query.results.paginate(page: params.dig(:q, :page), per_page: 30) end render layout: false end private # Returns a hash of pose => pet_state for all the main poses, # indicating which poses are available for this pet type. # Uses the same logic as the Rainbow Pool to pick the "canonical" pet state # for each pose when multiple states exist. def available_poses_for(pet_type) poses_hash = {} # Group all pet states by pose, then pick the best one for each pose # using emotion_order (same logic as Rainbow Pool) pet_type.pet_states.emotion_order.group_by(&:pose).each do |pose, states| # Only include the main poses (skip UNKNOWN, UNCONVERTED, etc.) if PetState::MAIN_POSES.include?(pose) poses_hash[pose] = states.first end end # Ensure all main poses are in the hash, even if nil PetState::MAIN_POSES.each do |pose| poses_hash[pose] ||= nil end poses_hash end def outfit_state_params_present? params[:species].present? || params[:color].present? || params[:objects].present? end def build_search_filters(query_params, outfit) filters = [] # Add name filter if present if query_params[:name].present? filters << { key: "name", value: query_params[:name] } end # Add item kind filter if present if query_params[:item_kind].present? case query_params[:item_kind] when "nc" filters << { key: "is_nc", value: "true" } when "np" filters << { key: "is_np", value: "true" } when "pb" filters << { key: "is_pb", value: "true" } end end # Add zone filter if present if query_params[:zone].present? filters << { key: "occupied_zone_set_name", value: query_params[:zone] } end # Always add auto-filter for items that fit the current pet pet_type = outfit.pet_type if pet_type fit_filter = { key: "fits", value: { species_id: pet_type.species_id.to_s, color_id: pet_type.color_id.to_s } } # Include alt_style_id if present if outfit.alt_style_id.present? fit_filter[:value][:alt_style_id] = outfit.alt_style_id.to_s end filters << fit_filter end filters end end