transition to closest valid pose for species/color
This commit is contained in:
parent
75a0fe2e8c
commit
bcdd9af806
5 changed files with 146 additions and 10 deletions
|
@ -48,9 +48,15 @@ function HomePage() {
|
||||||
function StartOutfitForm() {
|
function StartOutfitForm() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
const idealPose = React.useMemo(
|
||||||
|
() => (Math.random() > 0.5 ? "HAPPY_FEM" : "HAPPY_MASC"),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const [speciesId, setSpeciesId] = React.useState("1");
|
const [speciesId, setSpeciesId] = React.useState("1");
|
||||||
const [colorId, setColorId] = React.useState("8");
|
const [colorId, setColorId] = React.useState("8");
|
||||||
const [isValid, setIsValid] = React.useState(true);
|
const [isValid, setIsValid] = React.useState(true);
|
||||||
|
const [closestPose, setClosestPose] = React.useState(idealPose);
|
||||||
|
|
||||||
const onSubmit = (e) => {
|
const onSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -62,6 +68,7 @@ function StartOutfitForm() {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
species: speciesId,
|
species: speciesId,
|
||||||
color: colorId,
|
color: colorId,
|
||||||
|
pose: closestPose,
|
||||||
});
|
});
|
||||||
|
|
||||||
history.push(`/outfits/new?${params}`);
|
history.push(`/outfits/new?${params}`);
|
||||||
|
@ -73,11 +80,13 @@ function StartOutfitForm() {
|
||||||
<SpeciesColorPicker
|
<SpeciesColorPicker
|
||||||
speciesId={speciesId}
|
speciesId={speciesId}
|
||||||
colorId={colorId}
|
colorId={colorId}
|
||||||
|
idealPose={idealPose}
|
||||||
showPlaceholders
|
showPlaceholders
|
||||||
onChange={(species, color, isValid) => {
|
onChange={(species, color, isValid, closestPose) => {
|
||||||
setSpeciesId(species.id);
|
setSpeciesId(species.id);
|
||||||
setColorId(color.id);
|
setColorId(color.id);
|
||||||
setIsValid(isValid);
|
setIsValid(isValid);
|
||||||
|
setClosestPose(closestPose);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Box width="4" />
|
<Box width="4" />
|
||||||
|
|
|
@ -87,13 +87,15 @@ function OutfitControls({ outfitState, dispatchToOutfit }) {
|
||||||
<SpeciesColorPicker
|
<SpeciesColorPicker
|
||||||
speciesId={outfitState.speciesId}
|
speciesId={outfitState.speciesId}
|
||||||
colorId={outfitState.colorId}
|
colorId={outfitState.colorId}
|
||||||
|
idealPose={outfitState.pose}
|
||||||
dark
|
dark
|
||||||
onChange={(species, color, isValid) => {
|
onChange={(species, color, isValid, closestPose) => {
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
dispatchToOutfit({
|
dispatchToOutfit({
|
||||||
type: "setSpeciesAndColor",
|
type: "setSpeciesAndColor",
|
||||||
speciesId: species.id,
|
speciesId: species.id,
|
||||||
colorId: color.id,
|
colorId: color.id,
|
||||||
|
pose: closestPose,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { Delay, useFetch } from "./util";
|
||||||
function SpeciesColorPicker({
|
function SpeciesColorPicker({
|
||||||
speciesId,
|
speciesId,
|
||||||
colorId,
|
colorId,
|
||||||
|
idealPose,
|
||||||
showPlaceholders,
|
showPlaceholders,
|
||||||
dark = false,
|
dark = false,
|
||||||
onChange,
|
onChange,
|
||||||
|
@ -99,7 +100,11 @@ function SpeciesColorPicker({
|
||||||
|
|
||||||
const species = allSpecies.find((s) => s.id === speciesId);
|
const species = allSpecies.find((s) => s.id === speciesId);
|
||||||
const newColor = allColors.find((c) => c.id === newColorId);
|
const newColor = allColors.find((c) => c.id === newColorId);
|
||||||
onChange(species, newColor, pairIsValid(valids, speciesId, newColorId));
|
const validPoses = getValidPoses(valids, speciesId, newColorId);
|
||||||
|
const isValid = validPoses.size > 0;
|
||||||
|
const closestPose = getClosestPose(validPoses, idealPose);
|
||||||
|
console.log(idealPose, closestPose, validPoses);
|
||||||
|
onChange(species, newColor, isValid, closestPose);
|
||||||
};
|
};
|
||||||
|
|
||||||
// When the species changes, check if the new pair is valid, and update the
|
// When the species changes, check if the new pair is valid, and update the
|
||||||
|
@ -109,7 +114,11 @@ function SpeciesColorPicker({
|
||||||
|
|
||||||
const newSpecies = allSpecies.find((s) => s.id === newSpeciesId);
|
const newSpecies = allSpecies.find((s) => s.id === newSpeciesId);
|
||||||
const color = allColors.find((c) => c.id === colorId);
|
const color = allColors.find((c) => c.id === colorId);
|
||||||
onChange(newSpecies, color, pairIsValid(valids, newSpeciesId, colorId));
|
const validPoses = getValidPoses(valids, newSpeciesId, colorId);
|
||||||
|
const isValid = validPoses.size > 0;
|
||||||
|
const closestPose = getClosestPose(validPoses, idealPose);
|
||||||
|
console.log(idealPose, closestPose, validPoses);
|
||||||
|
onChange(newSpecies, color, isValid, closestPose);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -157,14 +166,131 @@ function SpeciesColorPicker({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pairIsValid(valids, speciesId, colorId) {
|
function getPairByte(valids, speciesId, colorId) {
|
||||||
// Reading a bit table, owo!
|
// Reading a bit table, owo!
|
||||||
const speciesIndex = speciesId - 1;
|
const speciesIndex = speciesId - 1;
|
||||||
const colorIndex = colorId - 1;
|
const colorIndex = colorId - 1;
|
||||||
const numColors = valids.getUint8(1);
|
const numColors = valids.getUint8(1);
|
||||||
const pairByteIndex = speciesIndex * numColors + colorIndex + 2;
|
const pairByteIndex = speciesIndex * numColors + colorIndex + 2;
|
||||||
const pairByte = valids.getUint8(pairByteIndex);
|
return valids.getUint8(pairByteIndex);
|
||||||
return pairByte !== 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pairIsValid(valids, speciesId, colorId) {
|
||||||
|
return getPairByte(valids, speciesId, colorId) !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValidPoses(valids, speciesId, colorId) {
|
||||||
|
const pairByte = getPairByte(valids, speciesId, colorId);
|
||||||
|
console.log("pair byte", pairByte.toString(2).padStart(8, "0"));
|
||||||
|
|
||||||
|
const validPoses = new Set();
|
||||||
|
if (pairByte & 0b00000001) validPoses.add("HAPPY_MASC");
|
||||||
|
if (pairByte & 0b00000010) validPoses.add("SAD_MASC");
|
||||||
|
if (pairByte & 0b00000100) validPoses.add("SICK_MASC");
|
||||||
|
if (pairByte & 0b00001000) validPoses.add("HAPPY_FEM");
|
||||||
|
if (pairByte & 0b00010000) validPoses.add("SAD_FEM");
|
||||||
|
if (pairByte & 0b00100000) validPoses.add("SICK_FEM");
|
||||||
|
// TODO: Add unconverted support!
|
||||||
|
// if (pairByte & 0b01000000) validPoses.add("UNCONVERTED");
|
||||||
|
if (pairByte & 0b10000000) validPoses.add("UNKNOWN");
|
||||||
|
|
||||||
|
return validPoses;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClosestPose(validPoses, idealPose) {
|
||||||
|
return closestPosesInOrder[idealPose].find((p) => validPoses.has(p)) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each pose, in what order do we prefer to match other poses?
|
||||||
|
//
|
||||||
|
// The principles of this ordering are:
|
||||||
|
// - Happy/sad matters more than gender presentation.
|
||||||
|
// - "Sick" is an unpopular emotion, and it's better to change gender
|
||||||
|
// presentation and stay happy/sad than to become sick.
|
||||||
|
// - Sad is a better fallback for sick than happy.
|
||||||
|
// - Unconverted vs converted is the biggest possible difference.
|
||||||
|
// - Unknown is the pose of last resort - even coming from another unknown.
|
||||||
|
const closestPosesInOrder = {
|
||||||
|
HAPPY_MASC: [
|
||||||
|
"HAPPY_MASC",
|
||||||
|
"HAPPY_FEM",
|
||||||
|
"SAD_MASC",
|
||||||
|
"SAD_FEM",
|
||||||
|
"SICK_MASC",
|
||||||
|
"SICK_FEM",
|
||||||
|
"UNCONVERTED",
|
||||||
|
"UNKNOWN",
|
||||||
|
],
|
||||||
|
HAPPY_FEM: [
|
||||||
|
"HAPPY_FEM",
|
||||||
|
"HAPPY_MASC",
|
||||||
|
"SAD_FEM",
|
||||||
|
"SAD_MASC",
|
||||||
|
"SICK_FEM",
|
||||||
|
"SICK_MASC",
|
||||||
|
"UNCONVERTED",
|
||||||
|
"UNKNOWN",
|
||||||
|
],
|
||||||
|
SAD_MASC: [
|
||||||
|
"SAD_MASC",
|
||||||
|
"SAD_FEM",
|
||||||
|
"HAPPY_MASC",
|
||||||
|
"HAPPY_FEM",
|
||||||
|
"SICK_MASC",
|
||||||
|
"SICK_FEM",
|
||||||
|
"UNCONVERTED",
|
||||||
|
"UNKNOWN",
|
||||||
|
],
|
||||||
|
SAD_FEM: [
|
||||||
|
"SAD_FEM",
|
||||||
|
"SAD_MASC",
|
||||||
|
"HAPPY_FEM",
|
||||||
|
"HAPPY_MASC",
|
||||||
|
"SICK_FEM",
|
||||||
|
"SICK_MASC",
|
||||||
|
"UNCONVERTED",
|
||||||
|
"UNKNOWN",
|
||||||
|
],
|
||||||
|
SICK_MASC: [
|
||||||
|
"SICK_MASC",
|
||||||
|
"SICK_FEM",
|
||||||
|
"SAD_MASC",
|
||||||
|
"SAD_FEM",
|
||||||
|
"HAPPY_MASC",
|
||||||
|
"HAPPY_FEM",
|
||||||
|
"UNCONVERTED",
|
||||||
|
"UNKNOWN",
|
||||||
|
],
|
||||||
|
SICK_FEM: [
|
||||||
|
"SICK_FEM",
|
||||||
|
"SICK_MASC",
|
||||||
|
"SAD_FEM",
|
||||||
|
"SAD_MASC",
|
||||||
|
"HAPPY_FEM",
|
||||||
|
"HAPPY_MASC",
|
||||||
|
"UNCONVERTED",
|
||||||
|
"UNKNOWN",
|
||||||
|
],
|
||||||
|
UNCONVERTED: [
|
||||||
|
"UNCONVERTED",
|
||||||
|
"HAPPY_FEM",
|
||||||
|
"HAPPY_MASC",
|
||||||
|
"SAD_FEM",
|
||||||
|
"SAD_MASC",
|
||||||
|
"SICK_FEM",
|
||||||
|
"SICK_MASC",
|
||||||
|
"UNKNOWN",
|
||||||
|
],
|
||||||
|
UNKNOWN: [
|
||||||
|
"HAPPY_FEM",
|
||||||
|
"HAPPY_MASC",
|
||||||
|
"SAD_FEM",
|
||||||
|
"SAD_MASC",
|
||||||
|
"SICK_FEM",
|
||||||
|
"SICK_MASC",
|
||||||
|
"UNCONVERTED",
|
||||||
|
"UNKNOWN",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export default SpeciesColorPicker;
|
export default SpeciesColorPicker;
|
||||||
|
|
|
@ -105,6 +105,7 @@ const outfitStateReducer = (apolloClient) => (baseState, action) => {
|
||||||
...baseState,
|
...baseState,
|
||||||
speciesId: action.speciesId,
|
speciesId: action.speciesId,
|
||||||
colorId: action.colorId,
|
colorId: action.colorId,
|
||||||
|
pose: action.pose,
|
||||||
};
|
};
|
||||||
case "wearItem":
|
case "wearItem":
|
||||||
return produce(baseState, (state) => {
|
return produce(baseState, (state) => {
|
||||||
|
|
|
@ -38,9 +38,7 @@ export default async function getValidPetPoses() {
|
||||||
for (let colorId = 1; colorId <= numColors; colorId++) {
|
for (let colorId = 1; colorId <= numColors; colorId++) {
|
||||||
const colorIndex = colorId - 1;
|
const colorIndex = colorId - 1;
|
||||||
|
|
||||||
// We fill in the high bits first. If we add more things later, write
|
// We fill in the high bits first, and shift left as we go!
|
||||||
// them first, so that they fill in the currently-empty high bits and
|
|
||||||
// everything else stays in the same position as before!
|
|
||||||
let byte = 0;
|
let byte = 0;
|
||||||
byte += hasPose(speciesId, colorId, "UNKNOWN") ? 1 : 0;
|
byte += hasPose(speciesId, colorId, "UNKNOWN") ? 1 : 0;
|
||||||
byte <<= 1;
|
byte <<= 1;
|
||||||
|
|
Loading…
Reference in a new issue