diff --git a/src/app/WardrobePage/OutfitControls.js b/src/app/WardrobePage/OutfitControls.js
index 432a06e..2221e81 100644
--- a/src/app/WardrobePage/OutfitControls.js
+++ b/src/app/WardrobePage/OutfitControls.js
@@ -283,29 +283,41 @@ function OutfitHTML5Badge({ appearance }) {
function OutfitKnownGlitchesBadge({ appearance }) {
const glitchMessages = [];
- // Look for conflicts between Static pet zones (UCs), and Static items.
- const petHasStaticZone = appearance.petAppearance?.layers.some(
- (l) => l.zone.id === "46"
+ const { petAppearance, items } = appearance;
+
+ // Look for UC/Invisible/etc incompatibilities that we hid, that we should
+ // just mark Incompatible someday instead.
+ //
+ // HACK: Most of this logic is copy-pasted from `useOutfitAppearance`.
+ const petOccupiedZoneIds = new Set(
+ petAppearance?.layers.map((l) => l.zone.id)
);
- if (petHasStaticZone) {
- for (const item of appearance.items) {
- const itemHasStaticZone = item.appearance.layers.some(
- (l) => l.zone.id === "46"
+ const petRestrictedZoneIds = new Set(
+ petAppearance?.restrictedZones.map((z) => z.id)
+ );
+ const petOccupiedOrRestrictedZoneIds = new Set([
+ ...petOccupiedZoneIds,
+ ...petRestrictedZoneIds,
+ ]);
+ for (const item of items) {
+ const itemHasZoneRestrictedByPet = item.appearance.layers.some(
+ (layer) =>
+ layer.bodyId !== "0" &&
+ petOccupiedOrRestrictedZoneIds.has(layer.zone.id)
+ );
+ if (itemHasZoneRestrictedByPet) {
+ glitchMessages.push(
+
+ {item.name} isn't actually compatible with this special pet.
+ We're still showing the old behavior, which is to hide the item.
+ Fixing this is in our todo list, sorry for the confusing UI!
+
);
- if (itemHasStaticZone) {
- glitchMessages.push(
-
- When you apply a Static-zone item like {item.name} to an
- Unconverted pet, it hides the pet. This is a known bug on
- Neopets.com, so we reproduce it here, too.
-
- );
- }
}
}
// Look for items with the OFFICIAL_SVG_IS_INCORRECT glitch.
- for (const item of appearance.items) {
+ for (const item of items) {
const itemHasOfficialSvgIsIncorrect = item.appearance.layers.some((l) =>
(l.knownGlitches || []).includes("OFFICIAL_SVG_IS_INCORRECT")
);
@@ -321,7 +333,7 @@ function OutfitKnownGlitchesBadge({ appearance }) {
}
// Look for Dyeworks items that aren't converted yet.
- for (const item of appearance.items) {
+ for (const item of items) {
const itemIsDyeworks = item.name.includes("Dyeworks");
const itemIsConverted = item.appearance.layers.every(layerUsesHTML5);
diff --git a/src/app/components/useOutfitAppearance.js b/src/app/components/useOutfitAppearance.js
index edf1866..9af4096 100644
--- a/src/app/components/useOutfitAppearance.js
+++ b/src/app/components/useOutfitAppearance.js
@@ -118,33 +118,68 @@ export function getVisibleLayers(petAppearance, itemAppearances) {
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" }));
- const itemOccupiedZoneIds = new Set(itemLayers.map((l) => l.zone.id));
-
- const petLayers = petAppearance.layers
- .map((l) => ({ ...l, source: "pet" }))
- // Copying weird Neopets.com behavior: if an item occupies a zone that the
- // pet also occupies, the pet's corresponding zone is hidden.
- .filter((l) => !itemOccupiedZoneIds.has(l.zone.id));
let allLayers = [...petLayers, ...itemLayers];
- const itemRestrictedZoneIds = validItemAppearances
- .map((a) => a.restrictedZones)
- .flat()
- .map((z) => z.id);
- const petRestrictedZoneIds = petAppearance.restrictedZones.map((z) => z.id);
- const allRestrictedZoneIds = new Set([
- ...itemRestrictedZoneIds,
+ 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(
- (l) => !allRestrictedZoneIds.has(l.zone.id)
- );
+ 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, it makes items
+ // that occupy the zone incompatible, but *only* if the item is
+ // body-specific. 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?)
+ //
+ // NOTE: This can result in both pet layers and items occupying the same
+ // zone, like Static! That's correct, and the item layer should be
+ // on top! (Here, we implement it by placing item layers second in
+ // the list, and depending on JS sort stability, and *then* depending
+ // on the UI to respect that ordering when rendering them by depth.
+ // Not great! 😅)
+ //
+ // TODO: Hiding the layer is the *old* behavior. Move this way deeper in
+ // the code to prevent these items from showing up in the first
+ // place!
+ if (
+ layer.source === "item" &&
+ layer.bodyId !== "0" &&
+ petOccupiedOrRestrictedZoneIds.has(layer.zone.id)
+ ) {
+ return false;
+ }
+
+ return true;
+ });
visibleLayers.sort((a, b) => a.zone.depth - b.zone.depth);
return visibleLayers;