From 927401fc9253d074b2cb75c9a467b5022e3d06a9 Mon Sep 17 00:00:00 2001 From: Matchu Date: Sat, 13 Mar 2021 07:21:30 -0800 Subject: [PATCH] The NEW UC rule is that non-body-specific is okay! --- src/app/WardrobePage/OutfitControls.js | 48 ++++++++++------ src/app/components/useOutfitAppearance.js | 69 +++++++++++++++++------ 2 files changed, 82 insertions(+), 35 deletions(-) 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;