add UC support!

I'm really into how the PosePicker button came out :3
This commit is contained in:
Emi Matchu 2020-08-31 20:26:15 -07:00
parent 17b00e295d
commit 47f55b1c3e
3 changed files with 161 additions and 115 deletions

View file

@ -30,6 +30,7 @@ import { useLocalStorage } from "../util";
import twemojiSmile from "../../images/twemoji/smile.svg"; import twemojiSmile from "../../images/twemoji/smile.svg";
import twemojiCry from "../../images/twemoji/cry.svg"; import twemojiCry from "../../images/twemoji/cry.svg";
import twemojiSick from "../../images/twemoji/sick.svg"; import twemojiSick from "../../images/twemoji/sick.svg";
import twemojiSunglasses from "../../images/twemoji/sunglasses.svg";
import twemojiQuestion from "../../images/twemoji/question.svg"; import twemojiQuestion from "../../images/twemoji/question.svg";
import twemojiMasc from "../../images/twemoji/masc.svg"; import twemojiMasc from "../../images/twemoji/masc.svg";
import twemojiFem from "../../images/twemoji/fem.svg"; import twemojiFem from "../../images/twemoji/fem.svg";
@ -143,18 +144,7 @@ function PosePicker({
isOpen && "is-open" isOpen && "is-open"
)} )}
> >
{getEmotion(pose) === "HAPPY" && ( <EmojiImage src={getIcon(pose)} alt="Choose a pose" />
<EmojiImage src={twemojiSmile} alt="Choose a pose" />
)}
{getEmotion(pose) === "SAD" && (
<EmojiImage src={twemojiCry} alt="Choose a pose" />
)}
{getEmotion(pose) === "SICK" && (
<EmojiImage src={twemojiSick} alt="Choose a pose" />
)}
{getEmotion(pose) === null && (
<EmojiImage src={twemojiQuestion} alt="Choose a pose" />
)}
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<Portal> <Portal>
@ -213,76 +203,88 @@ function PosePicker({
function PosePickerTable({ poseInfos, onChange, initialFocusRef }) { function PosePickerTable({ poseInfos, onChange, initialFocusRef }) {
return ( return (
<table width="100%"> <Box display="flex" flexDirection="column" alignItems="center">
<thead> <table width="100%">
<tr> <thead>
<th /> <tr>
<Cell as="th"> <th />
<EmojiImage src={twemojiSmile} alt="Happy" /> <Cell as="th">
</Cell> <EmojiImage src={twemojiSmile} alt="Happy" />
<Cell as="th"> </Cell>
<EmojiImage src={twemojiCry} alt="Sad" /> <Cell as="th">
</Cell> <EmojiImage src={twemojiCry} alt="Sad" />
<Cell as="th"> </Cell>
<EmojiImage src={twemojiSick} alt="Sick" /> <Cell as="th">
</Cell> <EmojiImage src={twemojiSick} alt="Sick" />
</tr> </Cell>
</thead> </tr>
<tbody> </thead>
<tr> <tbody>
<Cell as="th"> <tr>
<EmojiImage src={twemojiMasc} alt="Masculine" /> <Cell as="th">
</Cell> <EmojiImage src={twemojiMasc} alt="Masculine" />
<Cell as="td"> </Cell>
<PoseOption <Cell as="td">
poseInfo={poseInfos.happyMasc} <PoseOption
onChange={onChange} poseInfo={poseInfos.happyMasc}
inputRef={poseInfos.happyMasc.isSelected && initialFocusRef} onChange={onChange}
/> inputRef={poseInfos.happyMasc.isSelected && initialFocusRef}
</Cell> />
<Cell as="td"> </Cell>
<PoseOption <Cell as="td">
poseInfo={poseInfos.sadMasc} <PoseOption
onChange={onChange} poseInfo={poseInfos.sadMasc}
inputRef={poseInfos.sadMasc.isSelected && initialFocusRef} onChange={onChange}
/> inputRef={poseInfos.sadMasc.isSelected && initialFocusRef}
</Cell> />
<Cell as="td"> </Cell>
<PoseOption <Cell as="td">
poseInfo={poseInfos.sickMasc} <PoseOption
onChange={onChange} poseInfo={poseInfos.sickMasc}
inputRef={poseInfos.sickMasc.isSelected && initialFocusRef} onChange={onChange}
/> inputRef={poseInfos.sickMasc.isSelected && initialFocusRef}
</Cell> />
</tr> </Cell>
<tr> </tr>
<Cell as="th"> <tr>
<EmojiImage src={twemojiFem} alt="Feminine" /> <Cell as="th">
</Cell> <EmojiImage src={twemojiFem} alt="Feminine" />
<Cell as="td"> </Cell>
<PoseOption <Cell as="td">
poseInfo={poseInfos.happyFem} <PoseOption
onChange={onChange} poseInfo={poseInfos.happyFem}
inputRef={poseInfos.happyFem.isSelected && initialFocusRef} onChange={onChange}
/> inputRef={poseInfos.happyFem.isSelected && initialFocusRef}
</Cell> />
<Cell as="td"> </Cell>
<PoseOption <Cell as="td">
poseInfo={poseInfos.sadFem} <PoseOption
onChange={onChange} poseInfo={poseInfos.sadFem}
inputRef={poseInfos.sadFem.isSelected && initialFocusRef} onChange={onChange}
/> inputRef={poseInfos.sadFem.isSelected && initialFocusRef}
</Cell> />
<Cell as="td"> </Cell>
<PoseOption <Cell as="td">
poseInfo={poseInfos.sickFem} <PoseOption
onChange={onChange} poseInfo={poseInfos.sickFem}
inputRef={poseInfos.sickFem.isSelected && initialFocusRef} onChange={onChange}
/> inputRef={poseInfos.sickFem.isSelected && initialFocusRef}
</Cell> />
</tr> </Cell>
</tbody> </tr>
</table> </tbody>
</table>
{poseInfos.unconverted.isAvailable && (
<PoseOption
poseInfo={poseInfos.unconverted}
onChange={onChange}
inputRef={poseInfos.unconverted.isSelected && initialFocusRef}
size="sm"
label="Unconverted"
marginTop="2"
/>
)}
</Box>
); );
} }
@ -315,14 +317,24 @@ const GENDER_PRESENTATION_STRINGS = {
SICK_FEM: "Feminine", SICK_FEM: "Feminine",
}; };
function PoseOption({ poseInfo, onChange, inputRef }) { function PoseOption({
poseInfo,
onChange,
inputRef,
size = "50px",
label,
...otherProps
}) {
const theme = useTheme(); const theme = useTheme();
const genderPresentationStr = GENDER_PRESENTATION_STRINGS[poseInfo.pose]; const genderPresentationStr = GENDER_PRESENTATION_STRINGS[poseInfo.pose];
const emotionStr = EMOTION_STRINGS[poseInfo.pose]; const emotionStr = EMOTION_STRINGS[poseInfo.pose];
let label = `${emotionStr} and ${genderPresentationStr}`; let poseName =
poseInfo.pose === "UNCONVERTED"
? "Unconverted"
: `${emotionStr} and ${genderPresentationStr}`;
if (!poseInfo.isAvailable) { if (!poseInfo.isAvailable) {
label += ` (not modeled yet)`; poseName += ` (not modeled yet)`;
} }
const borderColor = useColorModeValue( const borderColor = useColorModeValue(
@ -334,16 +346,24 @@ function PoseOption({ poseInfo, onChange, inputRef }) {
<Box <Box
as="label" as="label"
cursor="pointer" cursor="pointer"
display="flex"
alignItems="center"
borderColor={poseInfo.isSelected ? borderColor : "gray.400"}
boxShadow={label ? "md" : "none"}
borderWidth={label ? "1px" : "0"}
borderRadius={label ? "full" : "0"}
paddingRight={label ? "3" : "0"}
onClick={(e) => { onClick={(e) => {
// HACK: We need the timeout to beat the popover's focus stealing! // HACK: We need the timeout to beat the popover's focus stealing!
const input = e.currentTarget.querySelector("input"); const input = e.currentTarget.querySelector("input");
setTimeout(() => input.focus(), 0); setTimeout(() => input.focus(), 0);
}} }}
{...otherProps}
> >
<VisuallyHidden <VisuallyHidden
as="input" as="input"
type="radio" type="radio"
aria-label={label} aria-label={poseName}
name="pose" name="pose"
value={poseInfo.pose} value={poseInfo.pose}
checked={poseInfo.isSelected} checked={poseInfo.isSelected}
@ -356,8 +376,8 @@ function PoseOption({ poseInfo, onChange, inputRef }) {
borderRadius="full" borderRadius="full"
boxShadow="md" boxShadow="md"
overflow="hidden" overflow="hidden"
width="50px" width={size === "sm" ? "30px" : "50px"}
height="50px" height={size === "sm" ? "30px" : "50px"}
title={ title={
poseInfo.isAvailable poseInfo.isAvailable
? // A lil debug output, so that we can quickly identify glitched ? // A lil debug output, so that we can quickly identify glitched
@ -408,34 +428,30 @@ function PoseOption({ poseInfo, onChange, inputRef }) {
)} )}
/> />
{poseInfo.isAvailable ? ( {poseInfo.isAvailable ? (
<Box <Box width="100%" height="100%" transform={getTransform(poseInfo)}>
width="50px"
height="50px"
transform={
transformsByBodyId[poseInfo.bodyId] || transformsByBodyId.default
}
>
<OutfitLayers visibleLayers={getVisibleLayers(poseInfo, [])} /> <OutfitLayers visibleLayers={getVisibleLayers(poseInfo, [])} />
</Box> </Box>
) : ( ) : (
<Flex align="center" justify="center"> <Flex align="center" justify="center" width="100%" height="100%">
<Box <EmojiImage src={twemojiQuestion} boxSize="24px" />
fontFamily="Delicious, sans-serif"
fontSize="3xl"
fontWeight="900"
color="gray.600"
>
?
</Box>
</Flex> </Flex>
)} )}
</Box> </Box>
{label && (
<Box
marginLeft="2"
fontSize="xs"
fontWeight={poseInfo.isSelected ? "bold" : "normal"}
>
{label}
</Box>
)}
</Box> </Box>
); );
} }
function EmojiImage({ src, alt }) { function EmojiImage({ src, alt, boxSize = "16px" }) {
return <img src={src} alt={alt} width="16px" height="16px" />; return <img src={src} alt={alt} width={boxSize} height={boxSize} />;
} }
function usePoses(speciesId, colorId, selectedPose) { function usePoses(speciesId, colorId, selectedPose) {
@ -484,6 +500,13 @@ function usePoses(speciesId, colorId, selectedPose) {
) { ) {
...PetAppearanceForPosePicker ...PetAppearanceForPosePicker
} }
unconverted: petAppearance(
speciesId: $speciesId
colorId: $colorId
pose: UNCONVERTED
) {
...PetAppearanceForPosePicker
}
} }
fragment PetAppearanceForPosePicker on PetAppearance { fragment PetAppearanceForPosePicker on PetAppearance {
@ -500,53 +523,76 @@ function usePoses(speciesId, colorId, selectedPose) {
const poseInfos = { const poseInfos = {
happyMasc: { happyMasc: {
...data?.happyMasc, ...data?.happyMasc,
pose: "HAPPY_MASC",
isAvailable: Boolean(data?.happyMasc), isAvailable: Boolean(data?.happyMasc),
isSelected: selectedPose === "HAPPY_MASC", isSelected: selectedPose === "HAPPY_MASC",
}, },
sadMasc: { sadMasc: {
...data?.sadMasc, ...data?.sadMasc,
pose: "SAD_MASC",
isAvailable: Boolean(data?.sadMasc), isAvailable: Boolean(data?.sadMasc),
isSelected: selectedPose === "SAD_MASC", isSelected: selectedPose === "SAD_MASC",
}, },
sickMasc: { sickMasc: {
...data?.sickMasc, ...data?.sickMasc,
pose: "SICK_MASC",
isAvailable: Boolean(data?.sickMasc), isAvailable: Boolean(data?.sickMasc),
isSelected: selectedPose === "SICK_MASC", isSelected: selectedPose === "SICK_MASC",
}, },
happyFem: { happyFem: {
...data?.happyFem, ...data?.happyFem,
pose: "HAPPY_FEM",
isAvailable: Boolean(data?.happyFem), isAvailable: Boolean(data?.happyFem),
isSelected: selectedPose === "HAPPY_FEM", isSelected: selectedPose === "HAPPY_FEM",
}, },
sadFem: { sadFem: {
...data?.sadFem, ...data?.sadFem,
pose: "SAD_FEM",
isAvailable: Boolean(data?.sadFem), isAvailable: Boolean(data?.sadFem),
isSelected: selectedPose === "SAD_FEM", isSelected: selectedPose === "SAD_FEM",
}, },
sickFem: { sickFem: {
...data?.sickFem, ...data?.sickFem,
pose: "SICK_FEM",
isAvailable: Boolean(data?.sickFem), isAvailable: Boolean(data?.sickFem),
isSelected: selectedPose === "SICK_FEM", isSelected: selectedPose === "SICK_FEM",
}, },
unconverted: {
...data?.unconverted,
pose: "UNCONVERTED",
isAvailable: Boolean(data?.unconverted),
isSelected: selectedPose === "UNCONVERTED",
},
}; };
return { loading, error, poseInfos }; return { loading, error, poseInfos };
} }
function getEmotion(pose) { function getIcon(pose) {
if (["HAPPY_MASC", "HAPPY_FEM"].includes(pose)) { if (["HAPPY_MASC", "HAPPY_FEM"].includes(pose)) {
return "HAPPY"; return twemojiSmile;
} else if (["SAD_MASC", "SAD_FEM"].includes(pose)) { } else if (["SAD_MASC", "SAD_FEM"].includes(pose)) {
return "SAD"; return twemojiCry;
} else if (["SICK_MASC", "SICK_FEM"].includes(pose)) { } else if (["SICK_MASC", "SICK_FEM"].includes(pose)) {
return "SICK"; return twemojiSick;
} else if (["UNCONVERTED", "UNKNOWN"].includes(pose)) { } else if (pose === "UNCONVERTED") {
return null; return twemojiSunglasses;
} else { } else {
throw new Error(`unrecognized pose ${JSON.stringify(pose)}`); return twemojiQuestion;
} }
} }
function getTransform(poseInfo) {
const { pose, bodyId } = poseInfo;
if (pose === "UNCONVERTED") {
return transformsByBodyId.default;
}
if (bodyId in transformsByBodyId) {
return transformsByBodyId[bodyId];
}
return transformsByBodyId.default;
}
const transformsByBodyId = { const transformsByBodyId = {
"93": "translate(-5px, 10px) scale(2.8)", "93": "translate(-5px, 10px) scale(2.8)",
"106": "translate(-8px, 8px) scale(2.9)", "106": "translate(-8px, 8px) scale(2.9)",

View file

@ -243,8 +243,7 @@ function getValidPoses(valids, speciesId, colorId) {
if (pairByte & 0b00001000) validPoses.add("HAPPY_FEM"); if (pairByte & 0b00001000) validPoses.add("HAPPY_FEM");
if (pairByte & 0b00010000) validPoses.add("SAD_FEM"); if (pairByte & 0b00010000) validPoses.add("SAD_FEM");
if (pairByte & 0b00100000) validPoses.add("SICK_FEM"); if (pairByte & 0b00100000) validPoses.add("SICK_FEM");
// TODO: Add unconverted support! if (pairByte & 0b01000000) validPoses.add("UNCONVERTED");
// if (pairByte & 0b01000000) validPoses.add("UNCONVERTED");
if (pairByte & 0b10000000) validPoses.add("UNKNOWN"); if (pairByte & 0b10000000) validPoses.add("UNKNOWN");
return validPoses; return validPoses;

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFCC4D" d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18"/><path fill-rule="evenodd" clip-rule="evenodd" fill="#292F33" d="M1.24 11.018c.24.239 1.438.957 1.677 1.675.24.717.72 4.784 2.158 5.981 1.483 1.232 7.077.774 8.148.24 2.397-1.195 2.691-4.531 3.115-6.221.239-.957 1.677-.957 1.677-.957s1.438 0 1.678.956c.424 1.691.72 5.027 3.115 6.221 1.072.535 6.666.994 8.151-.238 1.436-1.197 1.915-5.264 2.155-5.982.238-.717 1.438-1.435 1.677-1.674.241-.239.241-1.196 0-1.436-.479-.478-6.134-.904-12.223-.239-1.215.133-1.677.478-4.554.478-2.875 0-3.339-.346-4.553-.478-6.085-.666-11.741-.24-12.221.238-.239.239-.239 1.197 0 1.436z"/><path fill="#664500" d="M27.335 23.629c-.178-.161-.444-.171-.635-.029-.039.029-3.922 2.9-8.7 2.9-4.766 0-8.662-2.871-8.7-2.9-.191-.142-.457-.13-.635.029-.177.16-.217.424-.094.628C8.7 24.472 11.788 29.5 18 29.5s9.301-5.028 9.429-5.243c.123-.205.084-.468-.094-.628z"/></svg>

After

Width:  |  Height:  |  Size: 997 B