From 0aaf1adb29c53b361b629d673624fee1ba6778ad Mon Sep 17 00:00:00 2001 From: Matchu Date: Fri, 12 Mar 2021 04:01:35 -0800 Subject: [PATCH] Add Support tool for OFFICIAL_SVG_IS_INCORRECT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inspired by the "Flying in an Airplane" bug (item 82287), where the official SVG (and I think SWF) were visually glitched and included both zones in the image, but the official PNG was correct. This flag lets us use the PNG, like the official player does—but only for this item, while still keeping SVGs for everyone else! --- .../support/ItemLayerSupportModal.js | 47 +++++- src/app/components/useOutfitAppearance.js | 1 + src/server/types/AppearanceLayer.js | 32 +++++ src/server/types/MutationsForSupport.js | 134 ++++++++++++++++-- 4 files changed, 205 insertions(+), 9 deletions(-) diff --git a/src/app/WardrobePage/support/ItemLayerSupportModal.js b/src/app/WardrobePage/support/ItemLayerSupportModal.js index 40f0f0d..6f84431 100644 --- a/src/app/WardrobePage/support/ItemLayerSupportModal.js +++ b/src/app/WardrobePage/support/ItemLayerSupportModal.js @@ -21,6 +21,9 @@ import { Spinner, useDisclosure, useToast, + CheckboxGroup, + VStack, + Checkbox, } from "@chakra-ui/react"; import { ChevronRightIcon, ExternalLinkIcon } from "@chakra-ui/icons"; @@ -45,6 +48,10 @@ function ItemLayerSupportModal({ onClose, }) { const [selectedBodyId, setSelectedBodyId] = React.useState(itemLayer.bodyId); + const [selectedKnownGlitches, setSelectedKnownGlitches] = React.useState( + itemLayer.knownGlitches + ); + const [previewBiology, setPreviewBiology] = React.useState({ speciesId: outfitState.speciesId, colorId: outfitState.colorId, @@ -63,6 +70,7 @@ function ItemLayerSupportModal({ mutation ItemSupportSetLayerBodyId( $layerId: ID! $bodyId: ID! + $knownGlitches: [AppearanceLayerKnownGlitch!]! $supportSecret: String! $outfitSpeciesId: ID! $outfitColorId: ID! @@ -98,6 +106,16 @@ function ItemLayerSupportModal({ } } } + + setLayerKnownGlitches( + layerId: $layerId + knownGlitches: $knownGlitches + supportSecret: $supportSecret + ) { + id + knownGlitches + svgUrl # Affected by OFFICIAL_SVG_IS_INCORRECT + } } ${itemAppearanceFragment} `, @@ -105,6 +123,7 @@ function ItemLayerSupportModal({ variables: { layerId: itemLayer.id, bodyId: selectedBodyId, + knownGlitches: selectedKnownGlitches, supportSecret, outfitSpeciesId: outfitState.speciesId, outfitColorId: outfitState.colorId, @@ -251,6 +270,11 @@ function ItemLayerSupportModal({ onChangeBodyId={setSelectedBodyId} onChangePreviewBiology={setPreviewBiology} /> + + - Pet compatibility + Pet compatibility + Known glitches + + + + Official SVG is incorrect{" "} + + (Will use the PNG instead) + + + + + + ); +} + function ItemLayerSupportModalRemoveButton({ item, itemLayer, diff --git a/src/app/components/useOutfitAppearance.js b/src/app/components/useOutfitAppearance.js index 7d3086f..600d11f 100644 --- a/src/app/components/useOutfitAppearance.js +++ b/src/app/components/useOutfitAppearance.js @@ -169,6 +169,7 @@ export const itemAppearanceFragment = gql` canvasMovieLibraryUrl imageUrl(size: SIZE_600) swfUrl # HACK: This is for Support tools, but other views don't need it + knownGlitches # HACK: This is for Support tools, but other views don't need it bodyId zone { label @client # HACK: This is for Support tools, but other views don't need it diff --git a/src/server/types/AppearanceLayer.js b/src/server/types/AppearanceLayer.js index ef36ec3..3629667 100644 --- a/src/server/types/AppearanceLayer.js +++ b/src/server/types/AppearanceLayer.js @@ -64,6 +64,13 @@ const typeDefs = gql` """ item: Item + """ + Glitches that we know to affect this appearance layer. This can be useful + for changing our behavior to match official behavior, or to alert the user + that our behavior _doesn't_ match official behavior. + """ + knownGlitches: [AppearanceLayerKnownGlitch!]! + """ The zones that this layer restricts, if any. Note that, for item layers, this is generally empty and the restriction is on the ItemAppearance, not @@ -75,6 +82,15 @@ const typeDefs = gql` restrictedZones: [Zone!]! } + enum AppearanceLayerKnownGlitch { + # This glitch means that, while the official manifest declares an SVG + # version of this layer, it is incorrect and does not visually match the + # PNG version that the official pet editor users. + # + # For affected layers, svgUrl will be null, regardless of the manifest. + OFFICIAL_SVG_IS_INCORRECT + } + extend type Query { # Return the number of layers that have been converted to HTML5, optionally # filtered by type. Cache for 30 minutes (we re-sync with Neopets every @@ -138,6 +154,13 @@ const resolvers = { }, svgUrl: async ({ id }, _, { db, swfAssetLoader }) => { const layer = await swfAssetLoader.load(id); + + if ( + layer.knownGlitches.split(",").includes("OFFICIAL_SVG_IS_INCORRECT") + ) { + return null; + } + let manifest = layer.manifest && JSON.parse(layer.manifest); // When the manifest is specifically null, that means we don't know if @@ -254,6 +277,15 @@ const resolvers = { return { id: String(rows[0].parent_id) }; }, + knownGlitches: async ({ id }, _, { swfAssetLoader }) => { + const layer = await swfAssetLoader.load(id); + + if (!layer.knownGlitches) { + return []; + } + + return layer.knownGlitches.split(","); + }, }, Query: { diff --git a/src/server/types/MutationsForSupport.js b/src/server/types/MutationsForSupport.js index 03be98d..06368de 100644 --- a/src/server/types/MutationsForSupport.js +++ b/src/server/types/MutationsForSupport.js @@ -1,13 +1,6 @@ import { gql } from "apollo-server"; import { ManagementClient } from "auth0"; -const auth0 = new ManagementClient({ - domain: "openneo.us.auth0.com", - clientId: process.env.AUTH0_SUPPORT_CLIENT_ID, - clientSecret: process.env.AUTH0_SUPPORT_CLIENT_SECRET, - scope: "read:users update:users", -}); - import { capitalize, getPoseFromPetState, @@ -18,6 +11,13 @@ import { normalizeRow, } from "../util"; +const auth0 = new ManagementClient({ + domain: "openneo.us.auth0.com", + clientId: process.env.AUTH0_SUPPORT_CLIENT_ID, + clientSecret: process.env.AUTH0_SUPPORT_CLIENT_SECRET, + scope: "read:users update:users", +}); + const typeDefs = gql` type RemoveLayerFromItemMutationResult { layer: AppearanceLayer! @@ -47,7 +47,13 @@ const typeDefs = gql` layerId: ID! bodyId: ID! supportSecret: String! - ): AppearanceLayer! + ): AppearanceLayer + + setLayerKnownGlitches( + layerId: ID! + knownGlitches: [AppearanceLayerKnownGlitch!]! + supportSecret: String! + ): AppearanceLayer removeLayerFromItem( layerId: ID! @@ -289,6 +295,21 @@ const resolvers = { assertSupportSecretOrThrow(supportSecret); const oldSwfAsset = await swfAssetLoader.load(layerId); + if (!oldSwfAsset) { + console.warn( + `Skipping setLayerBodyId for unknown layer ID: ${layerId}` + ); + return null; + } + + // Skip the update, and the logging, if there's no change. + if (oldSwfAsset.bodyId === bodyId) { + console.info( + `Skipping setLayerBodyId for ${layerId}: no change. ` + + `(bodyId=${oldSwfAsset.bodyId})` + ); + return { id: layerId }; + } const [ result, @@ -361,6 +382,103 @@ const resolvers = { return { id: layerId }; }, + setLayerKnownGlitches: async ( + _, + { layerId, knownGlitches, supportSecret }, + { + itemLoader, + itemTranslationLoader, + swfAssetLoader, + zoneTranslationLoader, + db, + } + ) => { + assertSupportSecretOrThrow(supportSecret); + + const oldSwfAsset = await swfAssetLoader.load(layerId); + if (!oldSwfAsset) { + console.warn( + `Skipping setLayerKnownGlitches for unknown layer ID: ${layerId}` + ); + return null; + } + + const newKnownGlitchesString = knownGlitches.join(","); + + // Skip the update, and the logging, if there's no change. + if (oldSwfAsset.knownGlitches === newKnownGlitchesString) { + console.info( + `Skipping setLayerKnownGlitches for ${layerId}: no change. ` + + `(knownGlitches=${oldSwfAsset.knownGlitches})` + ); + return { id: layerId }; + } + + const [ + result, + ] = await db.execute( + `UPDATE swf_assets SET known_glitches = ? WHERE id = ? LIMIT 1`, + [newKnownGlitchesString, layerId] + ); + + if (result.affectedRows !== 1) { + throw new Error( + `Expected to affect 1 layer, but affected ${result.affectedRows}` + ); + } + + swfAssetLoader.clear(layerId); // we changed it, so clear it from cache + + if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) { + try { + const itemId = await db + .execute( + `SELECT parent_id FROM parents_swf_assets + WHERE swf_asset_id = ? AND parent_type = "Item" LIMIT 1;`, + [layerId] + ) + .then(([rows]) => normalizeRow(rows[0]).parentId); + + const [item, itemTranslation, zoneTranslation] = await Promise.all([ + itemLoader.load(itemId), + itemTranslationLoader.load(itemId), + zoneTranslationLoader.load(oldSwfAsset.zoneId), + ]); + + await logToDiscord({ + embeds: [ + { + title: `🛠 ${itemTranslation.name}`, + thumbnail: { + url: item.thumbnailUrl, + height: 80, + width: 80, + }, + fields: [ + { + name: + `Layer ${layerId} (${zoneTranslation.label}): ` + + `Known glitches`, + value: `${oldSwfAsset.knownGlitches || ""} → **${ + newKnownGlitchesString || "" + }**`, + }, + ], + timestamp: new Date().toISOString(), + url: `https://impress.openneo.net/items/${itemId}`, + }, + ], + }); + } catch (e) { + console.error("Error sending Discord support log", e); + } + } else { + console.warn("No Discord support webhook provided, skipping"); + } + + return { id: layerId }; + }, + removeLayerFromItem: async ( _, { layerId, itemId, supportSecret },