impress/app/models/pet.rb
Emi Matchu 52ca41dbff Extract biology processing from AltStyle into Pet
I'm trying to pull more of the modeling code out of the individual
classes, and into this encapsulated preprocessing, so it's a lot more
in-one-place!
2024-11-03 11:46:29 -08:00

156 lines
4.5 KiB
Ruby

class Pet < ApplicationRecord
belongs_to :pet_type
attr_reader :items, :pet_state, :alt_style
def load!(timeout: nil)
viewer_data_hash = Neopets::CustomPets.fetch_viewer_data(name, timeout:)
use_viewer_data(ViewerData.new(viewer_data_hash))
end
def use_viewer_data(viewer_data)
self.pet_type = viewer_data.pet_type
@pet_state = viewer_data.pet_state
@alt_style = viewer_data.alt_style
@items = viewer_data.items
end
def wardrobe_query
{
name: self.name,
color: self.pet_type.color_id,
species: self.pet_type.species_id,
pose: self.pet_state.pose,
state: self.pet_state.id,
objects: self.items.map(&:id),
style: self.alt_style ? self.alt_style.id : nil,
}.to_query
end
def contributables
contributables = [pet_type, @pet_state, @alt_style].filter(&:present?)
items.each do |item|
contributables << item
contributables += item.pending_swf_assets
end
contributables
end
before_validation do
pet_type.save!
@pet_state.save! if @pet_state
if @items
@items.each do |item|
item.save! if item.changed?
item.handle_assets!
end
end
if @alt_style
@alt_style.save!
end
end
def self.load(name, **options)
pet = Pet.find_or_initialize_by(name: name)
pet.load!(**options)
pet
end
class UnexpectedDataFormat < RuntimeError;end
# A representation of a Neopets::CustomPets viewer data response, translated
# to DTI's database models!
class ViewerData
def initialize(viewer_data_hash)
@custom_pet = viewer_data_hash[:custom_pet]
@object_info_registry = viewer_data_hash[:object_info_registry]
@object_asset_registry = viewer_data_hash[:object_asset_registry]
end
def pet_type
@pet_type ||= begin
raise UnexpectedDataFormat unless @custom_pet[:species_id]
raise UnexpectedDataFormat unless @custom_pet[:color_id]
raise UnexpectedDataFormat unless @custom_pet[:body_id]
@custom_pet => {species_id:, color_id:}
PetType.find_or_initialize_by(species_id:, color_id:).tap do |pet_type|
# Apply the pet's body ID to the pet type, unless it's wearing an alt
# style, in which case ignore it, because it's the *alt style*'s body ID.
# (This can theoretically cause a problem saving a new pet type when
# there's an alt style too!)
pet_type.body_id = @custom_pet[:body_id] unless @custom_pet[:alt_style]
if pet_type.body_id.nil?
raise UnexpectedDataFormat,
"can't process alt style on first occurrence of pet type"
end
# Try using this pet for the pet type's thumbnail, but don't worry
# if it fails.
begin
pet_type.consider_pet_image(@custom_pet[:name])
rescue => error
Rails.logger.warn "Failed to load pet image: #{error.full_message}"
end
end
end
end
def pet_state
@pet_state ||= begin
swf_asset_ids = biology_assets.map(&:remote_id).sort.join(",")
pet_type.pet_states.find_or_initialize_by(swf_asset_ids:).tap do |pet_state|
pet_state.swf_assets = biology_assets
end
end
end
def alt_style
@alt_style ||= begin
return nil unless @custom_pet[:alt_style]
raise UnexpectedDataFormat unless @custom_pet[:alt_color]
id = @custom_pet[:alt_style].to_i
AltStyle.find_or_initialize_by(id:).tap do |alt_style|
alt_style.assign_attributes(
color_id: @custom_pet[:alt_color].to_i,
species_id: @custom_pet[:species_id].to_i,
body_id: @custom_pet[:body_id].to_i,
swf_assets: alt_style_assets,
)
end
end
end
def items
@items ||= Item.collection_from_pet_type_and_registries(
pet_type, @object_info_registry, @object_asset_registry
)
end
private
def biology_assets
@biology_assets ||= begin
biology = @custom_pet[:alt_style].present? ?
@custom_pet[:original_biology] :
@custom_pet[:biology_by_zone]
assets_from_biology(biology)
end
end
def alt_style_assets
raise UnexpectedDataFormat if @custom_pet[:biology_by_zone].empty?
assets_from_biology(@custom_pet[:biology_by_zone])
end
def assets_from_biology(biology)
raise UnexpectedDataFormat if biology.empty?
body_id = @custom_pet[:body_id].to_i
biology.values.map { |b| SwfAsset.from_biology_data(body_id, b) }
end
end
end