Oops, fix bug with saving outfits of pets loaded from Neopets.com

Okay right, the wardrobe-2020 app treats `state` as a bit of an
override thing, and `pose` is the main canonical field for how a pet
looks. We were missing a few pieces here:

1. After loading a pet, we weren't including the `pose` field in the
   initial query string for the wardrobe URL, but we _were_ including
   the `state` field, so the outfit would get set up with a conflicting
   pet state ID vs pose.
2. When saving an outfit, we weren't taking the `state` field into
   account at all. This could cause the saved outfit to not quite match
   how it actually looked in-app, because the default pet state for
   that species/color/pose trio could be different; and regardless, the
   outfit state would come back with `appearanceId` set to `null`,
   which wouldn't match the local outfit state, which would trigger an
   infinite loop.

Here, we complete the round-trip of the `state` field, from pet loading
to outfit saving to the outfit data that comes back after saving!
This commit is contained in:
Emi Matchu 2024-02-08 09:51:31 -08:00
parent 566ac241a1
commit dc44b4dbb3
4 changed files with 21 additions and 13 deletions

View file

@ -116,7 +116,7 @@ class OutfitsController < ApplicationController
def outfit_params def outfit_params
params.require(:outfit).permit( params.require(:outfit).permit(
:name, :starred, :alt_style_id, item_ids: {worn: [], closeted: []}, :name, :starred, :alt_style_id, item_ids: {worn: [], closeted: []},
biology: [:species_id, :color_id, :pose]) biology: [:species_id, :color_id, :pose, :pet_state_id])
end end
def find_authorized_outfit def find_authorized_outfit

View file

@ -58,6 +58,7 @@ async function saveOutfit({
speciesId, speciesId,
colorId, colorId,
pose, pose,
appearanceId,
altStyleId, altStyleId,
wornItemIds, wornItemIds,
closetedItemIds, closetedItemIds,
@ -69,6 +70,7 @@ async function saveOutfit({
species_id: speciesId, species_id: speciesId,
color_id: colorId, color_id: colorId,
pose: pose, pose: pose,
pet_state_id: appearanceId,
}, },
alt_style_id: altStyleId, alt_style_id: altStyleId,
item_ids: { worn: wornItemIds, closeted: closetedItemIds }, item_ids: { worn: wornItemIds, closeted: closetedItemIds },
@ -127,6 +129,7 @@ function normalizeOutfit(outfit) {
speciesId: String(outfit.species_id), speciesId: String(outfit.species_id),
colorId: String(outfit.color_id), colorId: String(outfit.color_id),
pose: outfit.pose, pose: outfit.pose,
appearanceId: outfit.pet_state_id,
altStyleId: outfit.alt_style_id ? String(outfit.alt_style_id) : null, altStyleId: outfit.alt_style_id ? String(outfit.alt_style_id) : null,
wornItemIds: (outfit.item_ids?.worn || []).map((id) => String(id)), wornItemIds: (outfit.item_ids?.worn || []).map((id) => String(id)),
closetedItemIds: (outfit.item_ids?.closeted || []).map((id) => closetedItemIds: (outfit.item_ids?.closeted || []).map((id) =>

View file

@ -102,15 +102,19 @@ class Outfit < ApplicationRecord
end end
def biology=(biology) def biology=(biology)
@biology = biology.slice(:species_id, :color_id, :pose) @biology = biology.slice(:species_id, :color_id, :pose, :pet_state_id)
begin begin
if @biology[:pet_state_id]
self.pet_state = PetState.find(@biology[:pet_state_id])
else
pet_type = PetType.where( pet_type = PetType.where(
species_id: @biology[:species_id], species_id: @biology[:species_id],
color_id: @biology[:color_id], color_id: @biology[:color_id],
).first! ).first!
self.pet_state = pet_type.pet_states.with_pose(@biology[:pose]). self.pet_state = pet_type.pet_states.with_pose(@biology[:pose]).
emotion_order.first! emotion_order.first!
end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
# If there's no such pet state (which shouldn't happen normally in-app), # If there's no such pet state (which shouldn't happen normally in-app),
# we don't set `pet_state` but we keep `@biology` for validation. # we don't set `pet_state` but we keep `@biology` for validation.

View file

@ -85,11 +85,12 @@ class Pet < ApplicationRecord
def wardrobe_query def wardrobe_query
{ {
:name => self.name, name: self.name,
:color => self.pet_type.color.id, color: self.pet_type.color.id,
:species => self.pet_type.species.id, species: self.pet_type.species.id,
:state => self.pet_state.id, pose: self.pet_state.pose,
:objects => self.items.map(&:id) state: self.pet_state.id,
objects: self.items.map(&:id),
}.to_query }.to_query
end end