From 577e2c74a1ce5d0466c99884e0cf200ea918e8d8 Mon Sep 17 00:00:00 2001 From: Diogo Trindade Date: Sat, 11 May 2024 01:42:21 +0100 Subject: [PATCH] add dna string im/exporting --- src/Chf/Dna.ts | 89 +++++++------- src/Components/CharacterEditor.tsx | 14 +-- src/Components/DnaPanel.tsx | 191 +++++++++++++++++++---------- 3 files changed, 175 insertions(+), 119 deletions(-) diff --git a/src/Chf/Dna.ts b/src/Chf/Dna.ts index ec0bfcb..452db95 100644 --- a/src/Chf/Dna.ts +++ b/src/Chf/Dna.ts @@ -35,7 +35,6 @@ export type DnaFacePart = 'crown' export interface DnaBlend { - part: DnaFacePart headId: number percent: number } @@ -61,17 +60,55 @@ export interface Dna { blends: DnaBlends } -export function readDna(parentReader: BufferReader, bodyType: BodyType): Dna { - const isMale = bodyType === 'male' +export function readDna(parentReader: BufferReader): Dna { parentReader.expectUint64(dnaSize) const bytes = parentReader.readBytes(dnaSize) const dnaString = toHexStr(bytes) - const reader = new BufferReader(bytes.buffer) + return dnaFromString(dnaString) +} + +export function writeDna(writer: BufferWriter, dna: Dna, _bodyType: BodyType) { + const bytes = fromHexStr(dna.dnaString) + if (bytes.length !== dnaSize) + throw new Error(`DNA string must be ${dnaSize} bytes long`) + + writer.writeUint64(dnaSize) + writer.writeBytes(bytes) + + // TODO: this is probably broken. Replace the above with this when fixed + // const isMale = bodyType === 'male' + // writer.writeUint32(0xFCD09394) + // writer.writeUint32(isMale ? 0xDD6C67F6 : 0x9EF4EB54) + // writer.writeUint32(isMale ? 0x65E740D3 : 0x65D75204) + // writer.writeUint32(0) + // writer.writeByte(0x0C) + // writer.writeByte(0x0) + // writer.writeByte(0x04) + // writer.writeByte(0x0) + // writer.writeByte(0x4) + // writer.writeByte(0x0) + // writer.writeByte(dna.childCount) + // writer.writeByte(0x0) + + // for (let i = 0; i < partCount; i++) { + // const part = i % 12 + // const blends = dna.blends.get(part) || [] + // const blend = blends[i % blends.length] + + // const percentShort = blend.percent / 100 * 0xFFFF + // writer.writeUint16(percentShort) + // writer.writeByte(blend.headId) + // writer.writeByte(0) + // } +} + +export function dnaFromString(dnaString: string): Dna { + const reader = new BufferReader(fromHexStr(dnaString).buffer) reader.expectUint32(0xFCD09394) - reader.expectUint32(isMale ? 0xDD6C67F6 : 0x9EF4EB54) - reader.expectUint32(isMale ? 0x65E740D3 : 0x65D75204) + reader.readUint32()// skip keys. bad idea? + reader.readUint32() reader.expectUint32(0) reader.expectByte(0x0C) reader.expectByte(0x0) @@ -80,7 +117,7 @@ export function readDna(parentReader: BufferReader, bodyType: BodyType): Dna { reader.expectByte(0x4) reader.expectByte(0x0) const childCount = reader.readByte() - reader.expectByte(0x0) + reader.readByte()// might be 0 or ff for some reason? const map: DnaBlends = { eyebrowLeft: [], @@ -104,9 +141,8 @@ export function readDna(parentReader: BufferReader, bodyType: BodyType): Dna { reader.expectByte(0) const percent = percentShort / 0xFFFF * 100 - const blend = { headId, percent, part: idxPartRecord[part] } - map[idxPartRecord[part]].push(blend) + map[idxPartRecord[part]].push({ headId, percent }) } return { @@ -115,38 +151,3 @@ export function readDna(parentReader: BufferReader, bodyType: BodyType): Dna { blends: map, } } - -export function writeDna(writer: BufferWriter, dna: Dna, _bodyType: BodyType) { - const bytes = fromHexStr(dna.dnaString) - if (bytes.length !== dnaSize) - throw new Error(`DNA string must be ${dnaSize} bytes long`) - - writer.writeUint64(dnaSize) - writer.writeBytes(bytes) - - // TODO: this is probably broken. Replace the above with this when fixed - // const isMale = bodyType === 'male' - // writer.writeUint32(0xFCD09394) - // writer.writeUint32(isMale ? 0xDD6C67F6 : 0x9EF4EB54) - // writer.writeUint32(isMale ? 0x65E740D3 : 0x65D75204) - // writer.writeUint32(0) - // writer.writeByte(0x0C) - // writer.writeByte(0x0) - // writer.writeByte(0x04) - // writer.writeByte(0x0) - // writer.writeByte(0x4) - // writer.writeByte(0x0) - // writer.writeByte(dna.childCount) - // writer.writeByte(0x0) - - // for (let i = 0; i < partCount; i++) { - // const part = i % 12 - // const blends = dna.blends.get(part) || [] - // const blend = blends[i % blends.length] - - // const percentShort = blend.percent / 100 * 0xFFFF - // writer.writeUint16(percentShort) - // writer.writeByte(blend.headId) - // writer.writeByte(0) - // } -} diff --git a/src/Components/CharacterEditor.tsx b/src/Components/CharacterEditor.tsx index e7abe91..87f42e5 100644 --- a/src/Components/CharacterEditor.tsx +++ b/src/Components/CharacterEditor.tsx @@ -1,6 +1,7 @@ import { Affix, Button, Center, Group, Stack } from '@mantine/core' import { IconDownload } from '@tabler/icons-react' import { useCallback } from 'react' +import { useLocalStorage } from '@mantine/hooks' import { useCharacter } from '../Context/CharacterContext' import { createChf } from '../Chf/ChfFile' import SkinColorPicker from './SkinColorPicker' @@ -8,7 +9,10 @@ import { CharacterJsonDisplay } from './CharacterJsonDisplay' import { DnaPanel } from './DnaPanel' function CharacterEditor() { - const isDev = import.meta.env.DEV + const [isDev] = useLocalStorage({ + key: 'isDev', + defaultValue: false, + }) const [character] = useCharacter() const exportCharacter = useCallback(() => { const buffer = createChf(character) @@ -25,13 +29,9 @@ function CharacterEditor() { - - - - {isDev && ( - - )} + {isDev && } + {isDev && }
diff --git a/src/Components/DnaPanel.tsx b/src/Components/DnaPanel.tsx index 733dea0..d3f7550 100644 --- a/src/Components/DnaPanel.tsx +++ b/src/Components/DnaPanel.tsx @@ -1,45 +1,115 @@ -import { Fieldset, Group, NumberInput, Slider, Stack } from '@mantine/core' -import { useCallback } from 'react' +import { Button, Center, Fieldset, Group, Input, Modal, NumberInput, Slider, Stack, Text } from '@mantine/core' +import { useCallback, useState } from 'react' import type { NumberFormatValues } from 'react-number-format' +import { useDisclosure } from '@mantine/hooks' +import { notifications } from '@mantine/notifications' import { useCharacter } from '../Context/CharacterContext' -import type { DnaFacePart } from '../Chf/Dna' +import { type DnaFacePart, dnaFromString } from '../Chf/Dna' export function DnaPanel() { + const [opened, { toggle, close }] = useDisclosure(false) + const [dnaString, setDnaString] = useState('') + const [character, updateCharacter] = useCharacter() + + const importDna = useCallback(() => { + updateCharacter((d) => { + d.dna = dnaFromString(dnaString) + }) + close() + }, [updateCharacter, close, dnaString]) + + const dnaStringOpen = useCallback(() => { + setDnaString('') + toggle() + }, [toggle, setDnaString]) + + const dnaStringClipboard = useCallback(() => { + navigator.clipboard.writeText(character.dna.dnaString) + notifications.show({ + title: 'DNA String copied to clipboard', + message: 'You can now share it with others or import it into another character.', + autoClose: 2000, + }) + }, [character]) + return ( -
- - - - - - - - - - - - - - - - - - - - -
+ <> +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Enter the DNA string of the character you want to import. These can be shared between players or imported from NPCs from the game files. + + setDnaString(event.currentTarget.value)} + size="xl" + w="100%" + placeholder="9493D0FC...." + /> +
+ +
+
+
+ ) } function DnaPart({ label, part }: { label: string, part: DnaFacePart }) { - const [character] = useCharacter() + const [character, updateCharacter] = useCharacter() const blend = character.dna.blends[part] // make this work properly. We want the sum of all the blend percentages to be 100 // and the others to be updated accordingly when one is changed - // const updateBlend = useCallback((sliderIndex: number, newValue: number) => { - - // }, [blend, character, part, updateCharacter]) + const updateBlend = useCallback((sliderIndex: number, newValue: number) => { + console.log('updateBlend', sliderIndex, newValue) + }, [blend, character, part, updateCharacter]) const isValidHeadId = useCallback((headId: NumberFormatValues) => { const id = headId.floatValue ?? 0 @@ -47,51 +117,36 @@ function DnaPart({ label, part }: { label: string, part: DnaFacePart }) { return id >= 0 && id <= 26 && Number.isInteger(id) }, []) - function HeadPicker({ index }: { index: number }) { - return ( - updateCharacter((d) => { d.dna.blends[part][index].headId = values.floatValue ?? 0 })} - /> - ) - } - - function MySlider({ index }: { index: number }) { + function DnaBlend({ index }: { index: number }) { return ( - updateBlend(index, value)} - /> + + updateCharacter((d) => { d.dna.blends[part][index].headId = values.floatValue ?? 0 })} + /> + updateBlend(index, value)} + /> + ) } return (
- - - - - - - - - - - - - - - - + + + +
) }