add Support mode to PosePicker

Still just read-only stuff, but now you can look at all the different poses we have for a species/color!

Soon I'll make the pose/glitched stuff editable :3

Some sizable refactors here to add the ability to specify appearance ID as well as pose… most of the app still doesn't use it, it's mostly just lil extra logic to make it win if it's available!

(The rationale for making it an override, rather than always tracking appearance ID, is that it gets really inconvenient in practice to //wait// on looking up the appearance ID in order to start loading various queries. Species/color/pose is a more intuitive key, and works better and faster when the canonical appearance is what you want!)
This commit is contained in:
Emi Matchu 2020-08-28 22:58:39 -07:00
parent 7954c11c74
commit 1e30e7c8b0
16 changed files with 552 additions and 302 deletions

View file

@ -174,6 +174,7 @@ function OutfitControls({ outfitState, dispatchToOutfit }) {
speciesId={outfitState.speciesId} speciesId={outfitState.speciesId}
colorId={outfitState.colorId} colorId={outfitState.colorId}
pose={outfitState.pose} pose={outfitState.pose}
appearanceId={outfitState.appearanceId}
dispatchToOutfit={dispatchToOutfit} dispatchToOutfit={dispatchToOutfit}
onLockFocus={onLockFocus} onLockFocus={onLockFocus}
onUnlockFocus={onUnlockFocus} onUnlockFocus={onUnlockFocus}

View file

@ -23,6 +23,7 @@ import {
} from "../components/useOutfitAppearance"; } from "../components/useOutfitAppearance";
import { OutfitLayers } from "../components/OutfitPreview"; import { OutfitLayers } from "../components/OutfitPreview";
import SupportOnly from "./support/SupportOnly"; import SupportOnly from "./support/SupportOnly";
import { useLocalStorage } from "../util";
// From https://twemoji.twitter.com/, thank you! // From https://twemoji.twitter.com/, thank you!
import twemojiSmile from "../../images/twemoji/smile.svg"; import twemojiSmile from "../../images/twemoji/smile.svg";
@ -54,6 +55,7 @@ function PosePicker({
speciesId, speciesId,
colorId, colorId,
pose, pose,
appearanceId,
dispatchToOutfit, dispatchToOutfit,
onLockFocus, onLockFocus,
onUnlockFocus, onUnlockFocus,
@ -61,7 +63,10 @@ function PosePicker({
const theme = useTheme(); const theme = useTheme();
const checkedInputRef = React.useRef(); const checkedInputRef = React.useRef();
const { loading, error, poseInfos } = usePoses(speciesId, colorId, pose); const { loading, error, poseInfos } = usePoses(speciesId, colorId, pose);
const [isInSupportMode, setIsInSupportMode] = React.useState(false); const [isInSupportMode, setIsInSupportMode] = useLocalStorage(
"DTIPosePickerIsInSupportMode",
false
);
if (loading) { if (loading) {
return null; return null;
@ -136,12 +141,14 @@ function PosePicker({
</PopoverTrigger> </PopoverTrigger>
<Portal> <Portal>
<PopoverContent> <PopoverContent>
<Box p="4" position="relative"> <Box p="4" position="relative" minWidth="200px" minHeight="150px">
{isInSupportMode ? ( {isInSupportMode ? (
<PosePickerSupport <PosePickerSupport
speciesId={speciesId} speciesId={speciesId}
colorId={colorId} colorId={colorId}
onChange={onChange} pose={pose}
appearanceId={appearanceId}
dispatchToOutfit={dispatchToOutfit}
/> />
) : ( ) : (
<PosePickerTable <PosePickerTable

View file

@ -87,6 +87,7 @@ function WardrobePage() {
speciesId={outfitState.speciesId} speciesId={outfitState.speciesId}
colorId={outfitState.colorId} colorId={outfitState.colorId}
pose={outfitState.pose} pose={outfitState.pose}
appearanceId={outfitState.appearanceId}
wornItemIds={outfitState.wornItemIds} wornItemIds={outfitState.wornItemIds}
/> />
</Box> </Box>

View file

@ -420,11 +420,12 @@ function ItemSupportPetCompatibilityRuleFields({
*/ */
function ItemSupportAppearanceLayers({ item }) { function ItemSupportAppearanceLayers({ item }) {
const outfitState = React.useContext(OutfitStateContext); const outfitState = React.useContext(OutfitStateContext);
const { speciesId, colorId, pose } = outfitState; const { speciesId, colorId, pose, appearanceId } = outfitState;
const { error, visibleLayers } = useOutfitAppearance({ const { error, visibleLayers } = useOutfitAppearance({
speciesId, speciesId,
colorId, colorId,
pose, pose,
appearanceId,
wornItemIds: [item.id], wornItemIds: [item.id],
}); });

View file

@ -5,7 +5,7 @@ import { Box } from "@chakra-ui/core";
* Metadata is a UI component for showing metadata about something, as labels * Metadata is a UI component for showing metadata about something, as labels
* and their values. * and their values.
*/ */
function Metadata({ children }) { function Metadata({ children, ...otherProps }) {
return ( return (
<Box <Box
as="dl" as="dl"

View file

@ -3,21 +3,75 @@ import gql from "graphql-tag";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Box, Select, Switch } from "@chakra-ui/core"; import { Box, Select, Switch } from "@chakra-ui/core";
import { petAppearanceFragment } from "../../components/useOutfitAppearance";
import HangerSpinner from "../../components/HangerSpinner"; import HangerSpinner from "../../components/HangerSpinner";
import Metadata, { MetadataLabel, MetadataValue } from "./Metadata";
function PosePickerSupport({ speciesId, colorId }) { function PosePickerSupport({
speciesId,
colorId,
pose,
appearanceId,
dispatchToOutfit,
}) {
const { loading, error, data } = useQuery( const { loading, error, data } = useQuery(
gql` gql`
query PosePickerSupport($speciesId: ID!, $colorId: ID!) { query PosePickerSupport($speciesId: ID!, $colorId: ID!) {
petAppearances(speciesId: $speciesId, colorId: $colorId) { petAppearances(speciesId: $speciesId, colorId: $colorId) {
id id
bodyId
pose pose
...PetAppearanceForOutfitPreview isGlitched
layers {
id
zone {
id
label
}
}
}
happyMasc: petAppearance(
speciesId: $speciesId
colorId: $colorId
pose: HAPPY_MASC
) {
id
}
sadMasc: petAppearance(
speciesId: $speciesId
colorId: $colorId
pose: SAD_MASC
) {
id
}
sickMasc: petAppearance(
speciesId: $speciesId
colorId: $colorId
pose: SICK_MASC
) {
id
}
happyFem: petAppearance(
speciesId: $speciesId
colorId: $colorId
pose: HAPPY_FEM
) {
id
}
sadFem: petAppearance(
speciesId: $speciesId
colorId: $colorId
pose: SAD_FEM
) {
id
}
sickFem: petAppearance(
speciesId: $speciesId
colorId: $colorId
pose: SICK_FEM
) {
id
} }
} }
${petAppearanceFragment}
`, `,
{ variables: { speciesId, colorId } } { variables: { speciesId, colorId } }
); );
@ -38,17 +92,102 @@ function PosePickerSupport({ speciesId, colorId }) {
); );
} }
const canonicalAppearanceIdsByPose = {
HAPPY_MASC: data.happyMasc?.id,
SAD_MASC: data.sadMasc?.id,
SICK_MASC: data.sickMasc?.id,
HAPPY_FEM: data.happyFem?.id,
SAD_FEM: data.sadFem?.id,
SICK_FEM: data.sickFem?.id,
};
const canonicalAppearanceIds = Object.values(
canonicalAppearanceIdsByPose
).filter((id) => id);
if (!appearanceId) {
appearanceId = canonicalAppearanceIdsByPose[pose];
}
const currentPetAppearance = data.petAppearances.find(
(pa) => pa.id === appearanceId
);
if (!currentPetAppearance) {
return (
<Box color="red.400" marginTop="8">
Pet appearance with ID {JSON.stringify(appearanceId)} not found
</Box>
);
}
return ( return (
<Box> <Box>
<Box display="flex" justifyContent="flex-end"> <Box display="flex" justifyContent="flex-end" marginBottom="4">
<Select size="sm" width="auto"> <Select
size="sm"
width="auto"
value={appearanceId}
onChange={(e) => {
const id = e.target.value;
const petAppearance = data.petAppearances.find(
(pa) => pa.id === id
);
dispatchToOutfit({
type: "setPose",
pose: petAppearance.pose,
appearanceId: petAppearance.id,
});
}}
>
{data.petAppearances.map((pa) => ( {data.petAppearances.map((pa) => (
<option key={pa.id}> <option key={pa.id} value={pa.id}>
{POSE_NAMES[pa.pose]} ({pa.id}) {POSE_NAMES[pa.pose]} ({pa.id}){" "}
{canonicalAppearanceIds.includes(pa.id) && "⭐️"}
{pa.isGlitched && "👾"}
</option> </option>
))} ))}
</Select> </Select>
</Box> </Box>
<Metadata>
<MetadataLabel>DTI ID:</MetadataLabel>
<MetadataValue>{appearanceId}</MetadataValue>
<MetadataLabel>Pose:</MetadataLabel>
<MetadataValue>
<Box display="flex" flexDirection="row" alignItems="center">
<Select
size="sm"
value={currentPetAppearance.pose}
flex="0 1 200px"
cursor="not-allowed"
isReadOnly
>
{Object.entries(POSE_NAMES).map(([pose, name]) => (
<option key={pose} value={pose}>
{name}
</option>
))}
</Select>
<Select
size="sm"
marginLeft="2"
flex="0 1 150px"
value={currentPetAppearance.isGlitched}
cursor="not-allowed"
isReadOnly
>
<option value="false">Usable</option>
<option value="true">Glitched</option>
</Select>
</Box>
</MetadataValue>
<MetadataLabel>Zones:</MetadataLabel>
<MetadataValue>
{currentPetAppearance.layers
.map((l) => l.zone)
.map((z) => `${z.label} (${z.id})`)
.sort()
.join(", ")}
</MetadataValue>
</Metadata>
</Box> </Box>
); );
} }

View file

@ -17,7 +17,7 @@ function useOutfitState() {
initialState initialState
); );
const { name, speciesId, colorId, pose } = state; const { name, speciesId, colorId, pose, appearanceId } = state;
// It's more convenient to manage these as a Set in state, but most callers // It's more convenient to manage these as a Set in state, but most callers
// will find it more convenient to access them as arrays! e.g. for `.map()` // will find it more convenient to access them as arrays! e.g. for `.map()`
@ -87,6 +87,7 @@ function useOutfitState() {
speciesId, speciesId,
colorId, colorId,
pose, pose,
appearanceId,
url, url,
}; };
@ -108,6 +109,7 @@ const outfitStateReducer = (apolloClient) => (baseState, action) => {
speciesId: action.speciesId, speciesId: action.speciesId,
colorId: action.colorId, colorId: action.colorId,
pose: action.pose, pose: action.pose,
appearanceId: null,
}; };
case "wearItem": case "wearItem":
return produce(baseState, (state) => { return produce(baseState, (state) => {
@ -161,7 +163,15 @@ const outfitStateReducer = (apolloClient) => (baseState, action) => {
closetedItemIds.delete(itemId); closetedItemIds.delete(itemId);
}); });
case "setPose": case "setPose":
return { ...baseState, pose: action.pose }; return {
...baseState,
pose: action.pose,
// Usually only the `pose` is specified, but `PosePickerSupport` can
// also specify a corresponding `appearanceId`, to get even more
// particular about which version of the pose to show if more than one.
appearanceId: action.appearanceId || null,
};
case "reset": case "reset":
return produce(baseState, (state) => { return produce(baseState, (state) => {
const { const {
@ -195,6 +205,7 @@ function parseOutfitUrl() {
speciesId: urlParams.get("species"), speciesId: urlParams.get("species"),
colorId: urlParams.get("color"), colorId: urlParams.get("color"),
pose: urlParams.get("pose") || "HAPPY_FEM", pose: urlParams.get("pose") || "HAPPY_FEM",
appearanceId: urlParams.get("state") || null,
wornItemIds: new Set(urlParams.getAll("objects[]")), wornItemIds: new Set(urlParams.getAll("objects[]")),
closetedItemIds: new Set(urlParams.getAll("closet[]")), closetedItemIds: new Set(urlParams.getAll("closet[]")),
}; };
@ -330,6 +341,7 @@ function buildOutfitUrl(state) {
speciesId, speciesId,
colorId, colorId,
pose, pose,
appearanceId,
wornItemIds, wornItemIds,
closetedItemIds, closetedItemIds,
} = state; } = state;
@ -346,6 +358,11 @@ function buildOutfitUrl(state) {
for (const itemId of closetedItemIds) { for (const itemId of closetedItemIds) {
params.append("closet[]", itemId); params.append("closet[]", itemId);
} }
if (appearanceId != null) {
// `state` is an old name for compatibility with old-style DTI URLs. It
// refers to "PetState", the database table name for pet appearances.
params.append("state", appearanceId);
}
const { origin, pathname } = window.location; const { origin, pathname } = window.location;
const url = origin + pathname + "?" + params.toString(); const url = origin + pathname + "?" + params.toString();

View file

@ -25,6 +25,7 @@ function OutfitPreview({
speciesId, speciesId,
colorId, colorId,
pose, pose,
appearanceId,
wornItemIds, wornItemIds,
placeholder, placeholder,
loadingDelay, loadingDelay,
@ -33,6 +34,7 @@ function OutfitPreview({
speciesId, speciesId,
colorId, colorId,
pose, pose,
appearanceId,
wornItemIds, wornItemIds,
}); });

View file

@ -7,7 +7,7 @@ import { useQuery } from "@apollo/client";
* visibleLayers for rendering. * visibleLayers for rendering.
*/ */
export default function useOutfitAppearance(outfitState) { export default function useOutfitAppearance(outfitState) {
const { wornItemIds, speciesId, colorId, pose } = outfitState; const { wornItemIds, speciesId, colorId, pose, appearanceId } = outfitState;
// We split this query out from the other one, so that we can HTTP cache it. // We split this query out from the other one, so that we can HTTP cache it.
// //
@ -24,9 +24,26 @@ export default function useOutfitAppearance(outfitState) {
// HomePage. At time of writing, Vercel isn't actually edge-caching these, I // HomePage. At time of writing, Vercel isn't actually edge-caching these, I
// assume because our traffic isn't enough - so let's keep an eye on this! // assume because our traffic isn't enough - so let's keep an eye on this!
const { loading: loading1, error: error1, data: data1 } = useQuery( const { loading: loading1, error: error1, data: data1 } = useQuery(
gql` appearanceId == null
query OutfitPetAppearance($speciesId: ID!, $colorId: ID!, $pose: Pose!) { ? gql`
petAppearance(speciesId: $speciesId, colorId: $colorId, pose: $pose) { query OutfitPetAppearance(
$speciesId: ID!
$colorId: ID!
$pose: Pose!
) {
petAppearance(
speciesId: $speciesId
colorId: $colorId
pose: $pose
) {
...PetAppearanceForOutfitPreview
}
}
${petAppearanceFragment}
`
: gql`
query OutfitPetAppearanceById($appearanceId: ID!) {
petAppearance: petAppearanceById(id: $appearanceId) {
...PetAppearanceForOutfitPreview ...PetAppearanceForOutfitPreview
} }
} }
@ -37,6 +54,7 @@ export default function useOutfitAppearance(outfitState) {
speciesId, speciesId,
colorId, colorId,
pose, pose,
appearanceId,
}, },
skip: speciesId == null || colorId == null || pose == null, skip: speciesId == null || colorId == null || pose == null,
} }

View file

@ -159,3 +159,32 @@ export function useFetch(url, { responseType }) {
return { loading, error, data }; return { loading, error, data };
} }
/**
* useLocalStorage is like React.useState, but it persists the value in the
* device's `localStorage`, so it comes back even after reloading the page.
*
* Adapted from https://usehooks.com/useLocalStorage/.
*/
export function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = React.useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
}

View file

@ -101,6 +101,10 @@ const typeDefs = gql`
layers: [AppearanceLayer!]! layers: [AppearanceLayer!]!
petStateId: ID! # Deprecated, an alias for id petStateId: ID! # Deprecated, an alias for id
# Whether this PetAppearance is known to look incorrect. This is a manual
# flag that we set, in the case where this glitchy PetAppearance really did
# appear on Neopets.com, and has since been fixed.
isGlitched: Boolean!
} }
type ItemAppearance { type ItemAppearance {
@ -210,10 +214,17 @@ const typeDefs = gql`
offset: Int offset: Int
limit: Int limit: Int
): ItemSearchResult! ): ItemSearchResult!
petAppearanceById(id: ID!): PetAppearance @cacheControl(maxAge: 10800) # Cache for 3 hours (Support might edit!)
# The canonical pet appearance for the given species, color, and pose.
# Null if we don't have any data for this combination.
petAppearance(speciesId: ID!, colorId: ID!, pose: Pose!): PetAppearance petAppearance(speciesId: ID!, colorId: ID!, pose: Pose!): PetAppearance
@cacheControl(maxAge: 604800) # Cache for 1 week (unlikely to change) @cacheControl(maxAge: 10800) # Cache for 3 hours (we might model more!)
# All pet appearances we've ever seen for the given species and color. Note
# that this might include multiple copies for the same pose, and they might
# even be glitched data. We use this for Support tools.
petAppearances(speciesId: ID!, colorId: ID!): [PetAppearance!]! petAppearances(speciesId: ID!, colorId: ID!): [PetAppearance!]!
@cacheControl(maxAge: 10800) # Cache for 3 hours (we might add more!) @cacheControl(maxAge: 10800) # Cache for 3 hours (we might model more!)
outfit(id: ID!): Outfit outfit(id: ID!): Outfit
petOnNeopetsDotCom(petName: String!): Outfit petOnNeopetsDotCom(petName: String!): Outfit
@ -347,6 +358,10 @@ const resolvers = {
return swfAssets; return swfAssets;
}, },
petStateId: ({ id }) => id, petStateId: ({ id }) => id,
isGlitched: async ({ id }, _, { petStateLoader }) => {
const petState = await petStateLoader.load(id);
return petState.glitched;
},
}, },
AppearanceLayer: { AppearanceLayer: {
bodyId: async ({ id }, _, { swfAssetLoader }) => { bodyId: async ({ id }, _, { swfAssetLoader }) => {
@ -558,6 +573,7 @@ const resolvers = {
}); });
return { query, items }; return { query, items };
}, },
petAppearanceById: (_, { id }) => ({ id }),
petAppearance: async ( petAppearance: async (
_, _,
{ speciesId, colorId, pose }, { speciesId, colorId, pose },
@ -568,9 +584,12 @@ const resolvers = {
colorId, colorId,
}); });
// TODO: We could query for this more directly, instead of loading all
// appearances 🤔
const petStates = await petStatesForPetTypeLoader.load(petType.id); const petStates = await petStatesForPetTypeLoader.load(petType.id);
// TODO: This could be optimized into the query condition 🤔 const petState = petStates.find(
const petState = petStates.find((ps) => getPoseFromPetState(ps) === pose); (ps) => getPoseFromPetState(ps) === pose && !ps.glitched
);
if (!petState) { if (!petState) {
return null; return null;
} }

View file

@ -354,8 +354,8 @@ const buildPetStatesForPetTypeLoader = (db, loaders) =>
const qs = petTypeIds.map((_) => "?").join(","); const qs = petTypeIds.map((_) => "?").join(",");
const [rows, _] = await db.execute( const [rows, _] = await db.execute(
`SELECT * FROM pet_states `SELECT * FROM pet_states
WHERE pet_type_id IN (${qs}) AND glitched = 0 WHERE pet_type_id IN (${qs})
ORDER BY (mood_id = 1) DESC, id`, ORDER BY mood_id ASC, female DESC, id DESC`,
petTypeIds petTypeIds
); );

View file

@ -46,8 +46,8 @@ describe("PetAppearance", () => {
], ],
Array [ Array [
"SELECT * FROM pet_states "SELECT * FROM pet_states
WHERE pet_type_id IN (?) AND glitched = 0 WHERE pet_type_id IN (?)
ORDER BY (mood_id = 1) DESC", ORDER BY mood_id ASC, female DESC, id DESC",
Array [ Array [
"2", "2",
], ],
@ -142,8 +142,8 @@ describe("PetAppearance", () => {
], ],
Array [ Array [
"SELECT * FROM pet_states "SELECT * FROM pet_states
WHERE pet_type_id IN (?) AND glitched = 0 WHERE pet_type_id IN (?)
ORDER BY (mood_id = 1) DESC", ORDER BY mood_id ASC, female DESC, id DESC",
Array [ Array [
"2", "2",
], ],
@ -155,14 +155,14 @@ describe("PetAppearance", () => {
rel.swf_asset_id = sa.id rel.swf_asset_id = sa.id
WHERE rel.parent_id IN (?,?,?,?,?,?,?,?)", WHERE rel.parent_id IN (?,?,?,?,?,?,?,?)",
Array [ Array [
"2",
"436",
"4751", "4751",
"5991", "2",
"10014",
"11089",
"17723", "17723",
"17742", "17742",
"5991",
"436",
"10014",
"11089",
], ],
], ],
Array [ Array [
@ -186,8 +186,8 @@ describe("PetAppearance", () => {
"5", "5",
"37", "37",
"30", "30",
"33",
"34", "34",
"33",
], ],
], ],
] ]

View file

@ -44,7 +44,7 @@ Object {
"id": "34", "id": "34",
"name": "Green", "name": "Green",
}, },
"id": "54-34-UNKNOWN", "id": "3951",
"pose": "UNKNOWN", "pose": "UNKNOWN",
"species": Object { "species": Object {
"id": "54", "id": "54",

View file

@ -8,7 +8,7 @@ Object {
"isStandard": true, "isStandard": true,
"name": "Starry", "name": "Starry",
}, },
"id": "54-75-HAPPY_FEM", "id": "17723",
"layers": Array [ "layers": Array [
Object { Object {
"id": "5995", "id": "5995",
@ -76,130 +76,7 @@ Object {
"id": "75", "id": "75",
"name": "Starry", "name": "Starry",
}, },
"id": "54-75-UNKNOWN", "id": "4751",
"layers": Array [
Object {
"id": "5995",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7941/600x600.png?v2-0",
"zone": Object {
"depth": 18,
},
},
Object {
"id": "5996",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7942/600x600.png?v2-0",
"zone": Object {
"depth": 7,
},
},
Object {
"id": "6000",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7946/600x600.png?v2-0",
"zone": Object {
"depth": 40,
},
},
Object {
"id": "16467",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?v2-0",
"zone": Object {
"depth": 34,
},
},
Object {
"id": "19549",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/028/28548/600x600.png?v2-1345719457000",
"zone": Object {
"depth": 37,
},
},
Object {
"id": "19550",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/028/28549/600x600.png?v2-0",
"zone": Object {
"depth": 38,
},
},
Object {
"id": "163528",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/028/28549/600x600.png?v2-1326455337000",
"zone": Object {
"depth": 38,
},
},
],
"petStateId": "2",
"pose": "UNKNOWN",
"species": Object {
"id": "54",
"name": "Zafara",
},
},
Object {
"bodyId": "180",
"color": Object {
"id": "75",
"name": "Starry",
},
"id": "54-75-SAD_MASC",
"layers": Array [
Object {
"id": "5995",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7941/600x600.png?v2-0",
"zone": Object {
"depth": 18,
},
},
Object {
"id": "5996",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7942/600x600.png?v2-0",
"zone": Object {
"depth": 7,
},
},
Object {
"id": "6000",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7946/600x600.png?v2-0",
"zone": Object {
"depth": 40,
},
},
Object {
"id": "14790",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21057/600x600.png?v2-0",
"zone": Object {
"depth": 38,
},
},
Object {
"id": "14792",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21060/600x600.png?v2-0",
"zone": Object {
"depth": 37,
},
},
Object {
"id": "16467",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?v2-0",
"zone": Object {
"depth": 34,
},
},
],
"petStateId": "436",
"pose": "SAD_MASC",
"species": Object {
"id": "54",
"name": "Zafara",
},
},
Object {
"bodyId": "180",
"color": Object {
"id": "75",
"name": "Starry",
},
"id": "54-75-UNKNOWN",
"layers": Array [ "layers": Array [
Object { Object {
"id": "5995", "id": "5995",
@ -264,7 +141,7 @@ Object {
"id": "75", "id": "75",
"name": "Starry", "name": "Starry",
}, },
"id": "54-75-SAD_FEM", "id": "2",
"layers": Array [ "layers": Array [
Object { Object {
"id": "5995", "id": "5995",
@ -287,20 +164,6 @@ Object {
"depth": 40, "depth": 40,
}, },
}, },
Object {
"id": "14790",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21057/600x600.png?v2-0",
"zone": Object {
"depth": 38,
},
},
Object {
"id": "14793",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21061/600x600.png?v2-0",
"zone": Object {
"depth": 37,
},
},
Object { Object {
"id": "16467", "id": "16467",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?v2-0", "imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?v2-0",
@ -308,9 +171,30 @@ Object {
"depth": 34, "depth": 34,
}, },
}, },
Object {
"id": "19549",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/028/28548/600x600.png?v2-1345719457000",
"zone": Object {
"depth": 37,
},
},
Object {
"id": "19550",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/028/28549/600x600.png?v2-0",
"zone": Object {
"depth": 38,
},
},
Object {
"id": "163528",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/028/28549/600x600.png?v2-1326455337000",
"zone": Object {
"depth": 38,
},
},
], ],
"petStateId": "5991", "petStateId": "2",
"pose": "SAD_FEM", "pose": "UNKNOWN",
"species": Object { "species": Object {
"id": "54", "id": "54",
"name": "Zafara", "name": "Zafara",
@ -322,123 +206,7 @@ Object {
"id": "75", "id": "75",
"name": "Starry", "name": "Starry",
}, },
"id": "54-75-SICK_FEM", "id": "17723",
"layers": Array [
Object {
"id": "5995",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7941/600x600.png?v2-0",
"zone": Object {
"depth": 18,
},
},
Object {
"id": "5996",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7942/600x600.png?v2-0",
"zone": Object {
"depth": 7,
},
},
Object {
"id": "6000",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7946/600x600.png?v2-0",
"zone": Object {
"depth": 40,
},
},
Object {
"id": "14791",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21059/600x600.png?v2-0",
"zone": Object {
"depth": 38,
},
},
Object {
"id": "14795",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21066/600x600.png?v2-0",
"zone": Object {
"depth": 37,
},
},
Object {
"id": "16467",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?v2-0",
"zone": Object {
"depth": 34,
},
},
],
"petStateId": "10014",
"pose": "SICK_FEM",
"species": Object {
"id": "54",
"name": "Zafara",
},
},
Object {
"bodyId": "180",
"color": Object {
"id": "75",
"name": "Starry",
},
"id": "54-75-SICK_MASC",
"layers": Array [
Object {
"id": "5995",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7941/600x600.png?v2-0",
"zone": Object {
"depth": 18,
},
},
Object {
"id": "5996",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7942/600x600.png?v2-0",
"zone": Object {
"depth": 7,
},
},
Object {
"id": "6000",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7946/600x600.png?v2-0",
"zone": Object {
"depth": 40,
},
},
Object {
"id": "14791",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21059/600x600.png?v2-0",
"zone": Object {
"depth": 38,
},
},
Object {
"id": "14794",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21064/600x600.png?v2-0",
"zone": Object {
"depth": 37,
},
},
Object {
"id": "16467",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?v2-0",
"zone": Object {
"depth": 34,
},
},
],
"petStateId": "11089",
"pose": "SICK_MASC",
"species": Object {
"id": "54",
"name": "Zafara",
},
},
Object {
"bodyId": "180",
"color": Object {
"id": "75",
"name": "Starry",
},
"id": "54-75-HAPPY_FEM",
"layers": Array [ "layers": Array [
Object { Object {
"id": "5995", "id": "5995",
@ -496,7 +264,7 @@ Object {
"id": "75", "id": "75",
"name": "Starry", "name": "Starry",
}, },
"id": "54-75-HAPPY_MASC", "id": "17742",
"layers": Array [ "layers": Array [
Object { Object {
"id": "5995", "id": "5995",
@ -548,6 +316,238 @@ Object {
"name": "Zafara", "name": "Zafara",
}, },
}, },
Object {
"bodyId": "180",
"color": Object {
"id": "75",
"name": "Starry",
},
"id": "5991",
"layers": Array [
Object {
"id": "5995",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7941/600x600.png?v2-0",
"zone": Object {
"depth": 18,
},
},
Object {
"id": "5996",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7942/600x600.png?v2-0",
"zone": Object {
"depth": 7,
},
},
Object {
"id": "6000",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7946/600x600.png?v2-0",
"zone": Object {
"depth": 40,
},
},
Object {
"id": "14790",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21057/600x600.png?v2-0",
"zone": Object {
"depth": 38,
},
},
Object {
"id": "14793",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21061/600x600.png?v2-0",
"zone": Object {
"depth": 37,
},
},
Object {
"id": "16467",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?v2-0",
"zone": Object {
"depth": 34,
},
},
],
"petStateId": "5991",
"pose": "SAD_FEM",
"species": Object {
"id": "54",
"name": "Zafara",
},
},
Object {
"bodyId": "180",
"color": Object {
"id": "75",
"name": "Starry",
},
"id": "436",
"layers": Array [
Object {
"id": "5995",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7941/600x600.png?v2-0",
"zone": Object {
"depth": 18,
},
},
Object {
"id": "5996",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7942/600x600.png?v2-0",
"zone": Object {
"depth": 7,
},
},
Object {
"id": "6000",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7946/600x600.png?v2-0",
"zone": Object {
"depth": 40,
},
},
Object {
"id": "14790",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21057/600x600.png?v2-0",
"zone": Object {
"depth": 38,
},
},
Object {
"id": "14792",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21060/600x600.png?v2-0",
"zone": Object {
"depth": 37,
},
},
Object {
"id": "16467",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?v2-0",
"zone": Object {
"depth": 34,
},
},
],
"petStateId": "436",
"pose": "SAD_MASC",
"species": Object {
"id": "54",
"name": "Zafara",
},
},
Object {
"bodyId": "180",
"color": Object {
"id": "75",
"name": "Starry",
},
"id": "10014",
"layers": Array [
Object {
"id": "5995",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7941/600x600.png?v2-0",
"zone": Object {
"depth": 18,
},
},
Object {
"id": "5996",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7942/600x600.png?v2-0",
"zone": Object {
"depth": 7,
},
},
Object {
"id": "6000",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7946/600x600.png?v2-0",
"zone": Object {
"depth": 40,
},
},
Object {
"id": "14791",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21059/600x600.png?v2-0",
"zone": Object {
"depth": 38,
},
},
Object {
"id": "14795",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21066/600x600.png?v2-0",
"zone": Object {
"depth": 37,
},
},
Object {
"id": "16467",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?v2-0",
"zone": Object {
"depth": 34,
},
},
],
"petStateId": "10014",
"pose": "SICK_FEM",
"species": Object {
"id": "54",
"name": "Zafara",
},
},
Object {
"bodyId": "180",
"color": Object {
"id": "75",
"name": "Starry",
},
"id": "11089",
"layers": Array [
Object {
"id": "5995",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7941/600x600.png?v2-0",
"zone": Object {
"depth": 18,
},
},
Object {
"id": "5996",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7942/600x600.png?v2-0",
"zone": Object {
"depth": 7,
},
},
Object {
"id": "6000",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/007/7946/600x600.png?v2-0",
"zone": Object {
"depth": 40,
},
},
Object {
"id": "14791",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21059/600x600.png?v2-0",
"zone": Object {
"depth": 38,
},
},
Object {
"id": "14794",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/021/21064/600x600.png?v2-0",
"zone": Object {
"depth": 37,
},
},
Object {
"id": "16467",
"imageUrl": "https://impress-asset-images.s3.amazonaws.com/biology/000/000/024/24008/600x600.png?v2-0",
"zone": Object {
"depth": 34,
},
},
],
"petStateId": "11089",
"pose": "SICK_MASC",
"species": Object {
"id": "54",
"name": "Zafara",
},
},
], ],
} }
`; `;

View file

@ -9203,6 +9203,14 @@ Object {
"id": "17", "id": "17",
}, },
}, },
Object {
"color": Object {
"id": "105",
},
"species": Object {
"id": "17",
},
},
Object { Object {
"color": Object { "color": Object {
"id": "106", "id": "106",
@ -17539,6 +17547,14 @@ Object {
"id": "33", "id": "33",
}, },
}, },
Object {
"color": Object {
"id": "112",
},
"species": Object {
"id": "33",
},
},
Object { Object {
"color": Object { "color": Object {
"id": "6", "id": "6",