diff --git a/src/app/WardrobePage/OutfitKnownGlitchesBadge.js b/src/app/WardrobePage/OutfitKnownGlitchesBadge.js index acd7e2f..d48c98b 100644 --- a/src/app/WardrobePage/OutfitKnownGlitchesBadge.js +++ b/src/app/WardrobePage/OutfitKnownGlitchesBadge.js @@ -3,7 +3,7 @@ import { Box, VStack } from "@chakra-ui/react"; import { WarningTwoIcon } from "@chakra-ui/icons"; import { FaBug } from "react-icons/fa"; import { GlitchBadgeLayout, layerUsesHTML5 } from "../components/HTML5Badge"; -import { getVisibleLayers } from "../components/useOutfitAppearance"; +import getVisibleLayers from "../components/getVisibleLayers"; function OutfitKnownGlitchesBadge({ appearance }) { const glitchMessages = []; diff --git a/src/app/WardrobePage/PosePicker.js b/src/app/WardrobePage/PosePicker.js index 94d54e5..b2d96f5 100644 --- a/src/app/WardrobePage/PosePicker.js +++ b/src/app/WardrobePage/PosePicker.js @@ -18,10 +18,8 @@ import { } from "@chakra-ui/react"; import { loadable } from "../util"; -import { - getVisibleLayers, - petAppearanceFragment, -} from "../components/useOutfitAppearance"; +import { petAppearanceFragment } from "../components/useOutfitAppearance"; +import getVisibleLayers from "../components/getVisibleLayers"; import { OutfitLayers } from "../components/OutfitPreview"; import SupportOnly from "./support/SupportOnly"; import useSupport from "./support/useSupport"; diff --git a/src/app/components/OutfitThumbnail.js b/src/app/components/OutfitThumbnail.js index e0138ef..fa01e4d 100644 --- a/src/app/components/OutfitThumbnail.js +++ b/src/app/components/OutfitThumbnail.js @@ -2,11 +2,10 @@ import React from "react"; import { Box } from "@chakra-ui/react"; import gql from "graphql-tag"; -import { - getVisibleLayers, +import getVisibleLayers, { petAppearanceFragmentForGetVisibleLayers, itemAppearanceFragmentForGetVisibleLayers, -} from "./useOutfitAppearance"; +} from "./getVisibleLayers"; function OutfitThumbnail({ petAppearance, itemAppearances, ...props }) { return ( diff --git a/src/app/components/getVisibleLayers.js b/src/app/components/getVisibleLayers.js new file mode 100644 index 0000000..35886bd --- /dev/null +++ b/src/app/components/getVisibleLayers.js @@ -0,0 +1,128 @@ +import gql from "graphql-tag"; + +function getVisibleLayers(petAppearance, itemAppearances) { + if (!petAppearance) { + return []; + } + + const validItemAppearances = itemAppearances.filter((a) => a); + + const petLayers = petAppearance.layers.map((l) => ({ ...l, source: "pet" })); + + const itemLayers = validItemAppearances + .map((a) => a.layers) + .flat() + .map((l) => ({ ...l, source: "item" })); + + let allLayers = [...petLayers, ...itemLayers]; + + const itemRestrictedZoneIds = new Set( + validItemAppearances + .map((a) => a.restrictedZones) + .flat() + .map((z) => z.id) + ); + const petOccupiedZoneIds = new Set(petLayers.map((l) => l.zone.id)); + const petRestrictedZoneIds = new Set( + petAppearance.restrictedZones.map((z) => z.id) + ); + const petOccupiedOrRestrictedZoneIds = new Set([ + ...petOccupiedZoneIds, + ...petRestrictedZoneIds, + ]); + + const visibleLayers = allLayers.filter((layer) => { + // When an item restricts a zone, it hides pet layers of the same zone. + // We use this to e.g. make a hat hide a hair ruff. + // + // NOTE: Items' restricted layers also affect what items you can wear at + // the same time. We don't enforce anything about that here, and + // instead assume that the input by this point is valid! + if (layer.source === "pet" && itemRestrictedZoneIds.has(layer.zone.id)) { + return false; + } + + // When a pet appearance restricts or occupies a zone, or when the pet is + // Unconverted, it makes body-specific items incompatible. We use this to + // disallow UCs from wearing certain body-specific Biology Effects, + // Statics, etc, while still allowing non-body-specific items in those + // zones! (I think this happens for some Invisible pet stuff, too?) + // + // TODO: We shouldn't be *hiding* these zones, like we do with items; we + // should be doing this way earlier, to prevent the item from even + // showing up even in search results! + // + // NOTE: This can result in both pet layers and items occupying the same + // zone, like Static, so long as the item isn't body-specific! That's + // correct, and the item layer should be on top! (Here, we implement + // it by placing item layers second in the list, and rely on JS sort + // stability, and *then* rely on the UI to respect that ordering when + // rendering them by depth. Not great! 😅) + // + // NOTE: UCs used to implement their restrictions by listing specific + // zones, but it seems that the logic has changed to just be about + // UC-ness and body-specific-ness, and not necessarily involve the + // set of restricted zones at all. (This matters because e.g. UCs + // shouldn't show _any_ part of the Rainy Day Umbrella, but most UCs + // don't restrict Right-Hand Item (Zone 49).) Still, I'm keeping the + // zone restriction case running too, because I don't think it + // _hurts_ anything, and I'm not confident enough in this conclusion. + // + // TODO: Do Invisibles follow this new rule like UCs, too? Or do they still + // use zone restrictions? + if ( + layer.source === "item" && + layer.bodyId !== "0" && + (petAppearance.pose === "UNCONVERTED" || + petOccupiedOrRestrictedZoneIds.has(layer.zone.id)) + ) { + return false; + } + + // A pet appearance can also restrict its own zones. The Wraith Uni is an + // interesting example: it has a horn, but its zone restrictions hide it! + if (layer.source === "pet" && petRestrictedZoneIds.has(layer.zone.id)) { + return false; + } + + return true; + }); + visibleLayers.sort((a, b) => a.zone.depth - b.zone.depth); + + return visibleLayers; +} + +export const itemAppearanceFragmentForGetVisibleLayers = gql` + fragment ItemAppearanceForGetVisibleLayers on ItemAppearance { + id + layers { + id + zone { + id + depth @client + } + } + restrictedZones { + id + } + } +`; + +export const petAppearanceFragmentForGetVisibleLayers = gql` + fragment PetAppearanceForGetVisibleLayers on PetAppearance { + id + pose + layers { + id + zone { + id + depth @client + } + } + restrictedZones { + id + } + } +`; + +export default getVisibleLayers; diff --git a/src/app/components/useOutfitAppearance.js b/src/app/components/useOutfitAppearance.js index 42e7388..03adb81 100644 --- a/src/app/components/useOutfitAppearance.js +++ b/src/app/components/useOutfitAppearance.js @@ -1,6 +1,10 @@ import React from "react"; import gql from "graphql-tag"; import { useQuery } from "@apollo/client"; +import getVisibleLayers, { + itemAppearanceFragmentForGetVisibleLayers, + petAppearanceFragmentForGetVisibleLayers, +} from "./getVisibleLayers"; /** * useOutfitAppearance downloads the outfit's appearance data, and returns @@ -114,114 +118,6 @@ export default function useOutfitAppearance(outfitState) { }; } -export function getVisibleLayers(petAppearance, itemAppearances) { - if (!petAppearance) { - return []; - } - - const validItemAppearances = itemAppearances.filter((a) => a); - - const petLayers = petAppearance.layers.map((l) => ({ ...l, source: "pet" })); - - const itemLayers = validItemAppearances - .map((a) => a.layers) - .flat() - .map((l) => ({ ...l, source: "item" })); - - let allLayers = [...petLayers, ...itemLayers]; - - const itemRestrictedZoneIds = new Set( - validItemAppearances - .map((a) => a.restrictedZones) - .flat() - .map((z) => z.id) - ); - const petOccupiedZoneIds = new Set(petLayers.map((l) => l.zone.id)); - const petRestrictedZoneIds = new Set( - petAppearance.restrictedZones.map((z) => z.id) - ); - const petOccupiedOrRestrictedZoneIds = new Set([ - ...petOccupiedZoneIds, - ...petRestrictedZoneIds, - ]); - - const visibleLayers = allLayers.filter((layer) => { - // When an item restricts a zone, it hides pet layers of the same zone. - // We use this to e.g. make a hat hide a hair ruff. - // - // NOTE: Items' restricted layers also affect what items you can wear at - // the same time. We don't enforce anything about that here, and - // instead assume that the input by this point is valid! - if (layer.source === "pet" && itemRestrictedZoneIds.has(layer.zone.id)) { - return false; - } - - // When a pet appearance restricts or occupies a zone, or when the pet is - // Unconverted, it makes body-specific items incompatible. We use this to - // disallow UCs from wearing certain body-specific Biology Effects, - // Statics, etc, while still allowing non-body-specific items in those - // zones! (I think this happens for some Invisible pet stuff, too?) - // - // TODO: We shouldn't be *hiding* these zones, like we do with items; we - // should be doing this way earlier, to prevent the item from even - // showing up even in search results! - // - // NOTE: This can result in both pet layers and items occupying the same - // zone, like Static, so long as the item isn't body-specific! That's - // correct, and the item layer should be on top! (Here, we implement - // it by placing item layers second in the list, and rely on JS sort - // stability, and *then* rely on the UI to respect that ordering when - // rendering them by depth. Not great! 😅) - // - // NOTE: UCs used to implement their restrictions by listing specific - // zones, but it seems that the logic has changed to just be about - // UC-ness and body-specific-ness, and not necessarily involve the - // set of restricted zones at all. (This matters because e.g. UCs - // shouldn't show _any_ part of the Rainy Day Umbrella, but most UCs - // don't restrict Right-Hand Item (Zone 49).) Still, I'm keeping the - // zone restriction case running too, because I don't think it - // _hurts_ anything, and I'm not confident enough in this conclusion. - // - // TODO: Do Invisibles follow this new rule like UCs, too? Or do they still - // use zone restrictions? - if ( - layer.source === "item" && - layer.bodyId !== "0" && - (petAppearance.pose === "UNCONVERTED" || - petOccupiedOrRestrictedZoneIds.has(layer.zone.id)) - ) { - return false; - } - - // A pet appearance can also restrict its own zones. The Wraith Uni is an - // interesting example: it has a horn, but its zone restrictions hide it! - if (layer.source === "pet" && petRestrictedZoneIds.has(layer.zone.id)) { - return false; - } - - return true; - }); - visibleLayers.sort((a, b) => a.zone.depth - b.zone.depth); - - return visibleLayers; -} - -export const itemAppearanceFragmentForGetVisibleLayers = gql` - fragment ItemAppearanceForGetVisibleLayers on ItemAppearance { - id - layers { - id - zone { - id - depth @client - } - } - restrictedZones { - id - } - } -`; - export const appearanceLayerFragment = gql` fragment AppearanceLayerForOutfitPreview on AppearanceLayer { id @@ -266,23 +162,6 @@ export const itemAppearanceFragment = gql` ${itemAppearanceFragmentForGetVisibleLayers} `; -export const petAppearanceFragmentForGetVisibleLayers = gql` - fragment PetAppearanceForGetVisibleLayers on PetAppearance { - id - pose - layers { - id - zone { - id - depth @client - } - } - restrictedZones { - id - } - } -`; - export const petAppearanceFragment = gql` fragment PetAppearanceForOutfitPreview on PetAppearance { id