Add Support tool for OFFICIAL_SVG_IS_INCORRECT
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!
This commit is contained in:
parent
15d4a27657
commit
0aaf1adb29
4 changed files with 205 additions and 9 deletions
|
@ -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}
|
||||
/>
|
||||
<Box height="8" />
|
||||
<ItemLayerSupportKnownGlitchesFields
|
||||
selectedKnownGlitches={selectedKnownGlitches}
|
||||
onChange={setSelectedKnownGlitches}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<ItemLayerSupportModalRemoveButton
|
||||
|
@ -325,7 +349,7 @@ function ItemLayerSupportPetCompatibilityFields({
|
|||
|
||||
return (
|
||||
<FormControl isInvalid={error || !selectedBiology.isValid ? true : false}>
|
||||
<FormLabel>Pet compatibility</FormLabel>
|
||||
<FormLabel fontWeight="bold">Pet compatibility</FormLabel>
|
||||
<RadioGroup
|
||||
colorScheme="green"
|
||||
value={selectedBodyId}
|
||||
|
@ -397,6 +421,27 @@ function ItemLayerSupportPetCompatibilityFields({
|
|||
);
|
||||
}
|
||||
|
||||
function ItemLayerSupportKnownGlitchesFields({
|
||||
selectedKnownGlitches,
|
||||
onChange,
|
||||
}) {
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel fontWeight="bold">Known glitches</FormLabel>
|
||||
<CheckboxGroup value={selectedKnownGlitches} onChange={onChange}>
|
||||
<VStack spacing="2" align="flex-start">
|
||||
<Checkbox value="OFFICIAL_SVG_IS_INCORRECT">
|
||||
Official SVG is incorrect{" "}
|
||||
<Box display="inline" color="gray.400" fontSize="sm">
|
||||
(Will use the PNG instead)
|
||||
</Box>
|
||||
</Checkbox>
|
||||
</VStack>
|
||||
</CheckboxGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
function ItemLayerSupportModalRemoveButton({
|
||||
item,
|
||||
itemLayer,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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 || "<none>"} → **${
|
||||
newKnownGlitchesString || "<none>"
|
||||
}**`,
|
||||
},
|
||||
],
|
||||
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 },
|
||||
|
|
Loading…
Reference in a new issue