diff --git a/typescript/.eslintrc.json b/typescript/.eslintrc.json index 0cd7753..4ce7a46 100644 --- a/typescript/.eslintrc.json +++ b/typescript/.eslintrc.json @@ -17,6 +17,7 @@ "rules": { "prettier/prettier": "error", "semi": "error", + "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-var-requires": "off", "@typescript-eslint/no-empty-function": "off" } diff --git a/typescript/src/client/index.ts b/typescript/src/client/index.ts index bfe26f6..5bd4a1c 100644 --- a/typescript/src/client/index.ts +++ b/typescript/src/client/index.ts @@ -13,7 +13,7 @@ import { HEAD_OVERLAYS, } from './constants'; -import * as Interface from './modules/interface'; +import * as Customization from './modules/customization'; export async function setPlayerModel(model: string): Promise { if (!model) return; @@ -62,7 +62,7 @@ export function setPedFaceFeatures( ped: number, faceFeatures: PedFaceFeatures = DEFAULT_FACE_FEATURES, ): void { - Object.keys(FACE_FEATURES).forEach((key, index) => { + FACE_FEATURES.forEach((key, index) => { const faceFeature = faceFeatures[key]; SetPedFaceFeature(ped, index, faceFeature); @@ -209,7 +209,7 @@ function setPedAppearance(ped: number, appearance: PedAppearance): void { function init(): void { global.Delay = Delay; - Interface.loadModule(); + Customization.loadModule(); SetNuiFocus(true, true); SetNuiFocusKeepInput(false); diff --git a/typescript/src/client/modules/customization/index.ts b/typescript/src/client/modules/customization/index.ts new file mode 100644 index 0000000..11cf4d3 --- /dev/null +++ b/typescript/src/client/modules/customization/index.ts @@ -0,0 +1,459 @@ +import { DEFAULT_APPEARANCE, DEFAULT_SETTINGS } from '../../constants'; +import { PED_MODELS } from '../../constants'; + +import { arrayToVector3 } from '../../utils/vector'; + +import { registerNuiCallbacks } from './nui'; + +declare function OpenSequenceTask(id: number): [any, number]; + +const CAMERAS = { + default: { + coords: { x: 0, y: 2.2, z: 0.2 }, + point: { x: 0, y: 0, z: -0.05 }, + }, + head: { + coords: { x: 0, y: 0.9, z: 0.65 }, + point: { x: 0, y: 0, z: 0.6 }, + }, + body: { + coords: { x: 0, y: 2.2, z: 0.2 }, + point: { x: 0, y: 0, z: 0 }, + }, + bottom: { + coords: { x: 0, y: 0.98, z: -0.7 }, + point: { x: 0, y: 0, z: -0.9 }, + }, +}; + +const OFFSETS = { + default: { x: 1.5, y: -1 }, + head: { x: 0.7, y: -0.45 }, + body: { x: 2.2, y: -1 }, + bottom: { x: 0.7, y: -0.45 }, +}; + +let callback: (appearance?: PedAppearance) => void; + +let playerAppearance: PedAppearance; + +let playerCoords: Vector3; + +let cameraHandle: number; +let currentCamera: string; +let reverseCamera: boolean; + +let isCameraInterpolating: boolean; + +function getComponent(components: PedComponent[], component_id: number) { + let component = components.find(c => c.component_id === component_id); + + if (!component) { + [component] = components; + } + return component; +} + +function getProp(props: PedProp[], prop_id: number) { + let prop = props.find(p => p.prop_id === prop_id); + + if (!prop) { + [prop] = props; + } + return prop; +} + +function getRgbColors(): { hair: number[][]; makeUp: number[][] } { + const colors = { + hair: [], + makeUp: [], + }; + + for (let i = 0; i <= 63; i++) { + colors.hair.push(GetPedHairRgbColor(i)); + colors.makeUp.push(GetMakeupRgbColor(i)); + } + + return colors; +} + +export function getComponentsSettings(components: PedComponent[]): ComponentSettings[] { + const playerPed = PlayerPedId(); + + const settings = DEFAULT_SETTINGS.components; + + settings.forEach(componentSettings => { + const component = getComponent(components, componentSettings.component_id); + + componentSettings.drawable.max = GetNumberOfPedDrawableVariations( + playerPed, + componentSettings.component_id, + ); + + componentSettings.texture.max = GetNumberOfPedTextureVariations( + playerPed, + component.component_id, + component.drawable, + ); + }); + + return settings; +} + +export function getPropsSettings(props: PedProp[]): PropSettings[] { + const playerPed = PlayerPedId(); + + const settings = DEFAULT_SETTINGS.props; + + settings.forEach(propSettings => { + const prop = getProp(props, propSettings.prop_id); + + propSettings.drawable.max = GetNumberOfPedPropDrawableVariations( + playerPed, + propSettings.prop_id, + ); + + propSettings.texture.max = GetNumberOfPedPropTextureVariations( + playerPed, + propSettings.prop_id, + prop.drawable, + ); + }); + + return settings; +} + +export function getPlayerPedAppearance(model?: string): PedAppearance { + const playerPed = PlayerPedId(); + + const playerPedAppearance = DEFAULT_APPEARANCE; + + if (model) { + playerPedAppearance.model = model; + } + + playerPedAppearance.components.forEach(component => { + component.drawable = GetPedDrawableVariation(playerPed, component.component_id); + component.texture = GetPedTextureVariation(playerPed, component.component_id); + }); + + playerPedAppearance.props.forEach(prop => { + prop.drawable = GetPedPropIndex(playerPed, prop.prop_id); + prop.texture = GetPedPropTextureIndex(playerPed, prop.prop_id); + }); + + playerPedAppearance.hair = { + style: GetPedDrawableVariation(playerPed, 2), + color: GetPedHairColor(playerPed), + highlight: GetPedHairHighlightColor(playerPed), + }; + + return playerPedAppearance; +} + +export function getAppearance(): PedAppearance { + return playerAppearance ? playerAppearance : getPlayerPedAppearance(); +} + +export function getAppearanceSettings(appearanceData: PedAppearance): AppearanceSettings { + const pedSettings = DEFAULT_SETTINGS.ped; + + pedSettings.model.items = PED_MODELS; + + const componentsSettings = getComponentsSettings(appearanceData.components); + + const propsSettings = getPropsSettings(appearanceData.props); + + const headBlendSettings = DEFAULT_SETTINGS.headBlend; + + const faceFeaturesSettings = DEFAULT_SETTINGS.faceFeatures; + + const { hair: hairColors, makeUp: makeUpColors } = getRgbColors(); + + const headOverlaysSettings = DEFAULT_SETTINGS.headOverlays; + + Object.keys(headOverlaysSettings).forEach((key: HeadOverlaysSettingsKey) => { + if (headOverlaysSettings[key].color) { + const colorMap = { + beard: hairColors, + eyebrows: hairColors, + chestHair: hairColors, + makeUp: makeUpColors, + blush: makeUpColors, + lipstick: makeUpColors, + }; + + headOverlaysSettings[key].color.items = colorMap[key]; + } + }); + + const hairSettings = DEFAULT_SETTINGS.hair; + + hairSettings.style.max = GetNumberOfPedDrawableVariations(PlayerPedId(), 2); + + hairSettings.color.items = hairColors; + hairSettings.highlight.items = hairColors; + + const eyeColorSettings = DEFAULT_SETTINGS.eyeColor; + + const appearanceSettings = { + ped: pedSettings, + components: componentsSettings, + props: propsSettings, + headBlend: headBlendSettings, + faceFeatures: faceFeaturesSettings, + headOverlays: headOverlaysSettings, + hair: hairSettings, + eyeColor: eyeColorSettings, + }; + + return appearanceSettings; +} + +export function setCamera(key: string): void { + if (isCameraInterpolating) { + return; + } + + if (key !== 'current') { + currentCamera = key; + } + + const { coords, point } = CAMERAS[currentCamera]; + + const reverseFactor = reverseCamera ? -1 : 1; + + if (cameraHandle) { + const camCoords = arrayToVector3( + GetOffsetFromEntityInWorldCoords( + PlayerPedId(), + coords.x * reverseFactor, + coords.y * reverseFactor, + coords.z, + ), + ); + + const camPoint = arrayToVector3( + GetOffsetFromEntityInWorldCoords(PlayerPedId(), point.x, point.y, point.z), + ); + + const tmpCamera = CreateCameraWithParams( + 'DEFAULT_SCRIPTED_CAMERA', + camCoords.x, + camCoords.y, + camCoords.z, + 0.0, + 0.0, + 0.0, + 50.0, + false, + 0, + ); + PointCamAtCoord(tmpCamera, camPoint.x, camPoint.y, camPoint.z); + SetCamActiveWithInterp(tmpCamera, cameraHandle, 1000, 1, 1); + + isCameraInterpolating = true; + + const updateCameraInterval = setInterval(() => { + if (!IsCamInterpolating(cameraHandle) && IsCamActive(tmpCamera)) { + DestroyCam(cameraHandle, false); + cameraHandle = tmpCamera; + isCameraInterpolating = false; + clearInterval(updateCameraInterval); + } + }, 500); + } else { + const camCoords = arrayToVector3( + GetOffsetFromEntityInWorldCoords(PlayerPedId(), coords.x, coords.y, coords.z), + ); + + const camPoint = arrayToVector3( + GetOffsetFromEntityInWorldCoords(PlayerPedId(), point.x, point.y, point.z), + ); + + cameraHandle = CreateCameraWithParams( + 'DEFAULT_SCRIPTED_CAMERA', + camCoords.x, + camCoords.y, + camCoords.z, + 0.0, + 0.0, + 0.0, + 50.0, + false, + 0, + ); + PointCamAtCoord(cameraHandle, camPoint.x, camPoint.y, camPoint.z); + SetCamActive(cameraHandle, true); + } +} + +export async function rotateCamera(direction: 'left' | 'right'): Promise { + if (isCameraInterpolating) { + return; + } + + const { coords, point } = CAMERAS[currentCamera]; + const offset = OFFSETS[currentCamera]; + + let sideFactor: number; + + const reverseFactor = reverseCamera ? -1 : 1; + + if (direction === 'left') { + sideFactor = 1; + } else if (direction === 'right') { + sideFactor = -1; + } + + const camCoords = arrayToVector3( + GetOffsetFromEntityInWorldCoords( + PlayerPedId(), + (coords.x + offset.x) * sideFactor * reverseFactor, + (coords.y + offset.y) * reverseFactor, + coords.z, + ), + ); + + const camPoint = arrayToVector3( + GetOffsetFromEntityInWorldCoords(PlayerPedId(), point.x, point.y, point.z), + ); + + const tmpCamera = CreateCameraWithParams( + 'DEFAULT_SCRIPTED_CAMERA', + camCoords.x, + camCoords.y, + camCoords.z, + 0.0, + 0.0, + 0.0, + 50.0, + false, + 0, + ); + + PointCamAtCoord(tmpCamera, camPoint.x, camPoint.y, camPoint.z); + + SetCamActiveWithInterp(tmpCamera, cameraHandle, 1000, 1, 1); + + isCameraInterpolating = true; + + const updateCameraInterval = setInterval(() => { + if (!IsCamInterpolating(cameraHandle) && IsCamActive(tmpCamera)) { + DestroyCam(cameraHandle, false); + cameraHandle = tmpCamera; + isCameraInterpolating = false; + clearInterval(updateCameraInterval); + } + }, 500); +} + +export function pedTurnAround(ped: number): void { + reverseCamera = !reverseCamera; + + const [, sequence] = OpenSequenceTask(0); + TaskGoStraightToCoord( + 0, + playerCoords.x, + playerCoords.y, + playerCoords.z, + 8.0, + -1, + GetEntityHeading(ped) - 180.0, + 0.1, + ); + TaskStandStill(0, -1); + CloseSequenceTask(sequence); + + ClearPedTasks(ped); + TaskPerformSequence(ped, sequence); + ClearSequenceTask(sequence); +} + +function startPlayerCustomization(cb: (appearance?: PedAppearance) => void): void; +function startPlayerCustomization( + appearance: PedAppearance, + cb: (appearance?: PedAppearance) => void, +): void; + +function startPlayerCustomization( + paramOne: PedAppearance | (() => void), + paramTwo?: () => void, +): void { + let appearance; + let cb; + + if (paramTwo) { + appearance = paramOne; + cb = paramTwo; + } else { + cb = paramOne; + } + + if (appearance) { + playerAppearance = appearance; + } + + callback = cb; + + playerCoords = arrayToVector3(GetEntityCoords(PlayerPedId(), true)); + reverseCamera = false; + isCameraInterpolating = false; + + setCamera('default'); + + RenderScriptCams(true, false, 0, true, true); + DisplayRadar(false); + + TaskStandStill(PlayerPedId(), -1); + + const nuiMessage = { + type: 'appearance_display', + payload: {}, + }; + + SendNuiMessage(JSON.stringify(nuiMessage)); +} + +export function exitPlayerCustomization(appearance?: PedAppearance): void { + RenderScriptCams(false, false, 0, true, true); + DisplayRadar(false); + SetNuiFocus(false, false); + + ClearPedTasksImmediately(PlayerPedId()); + + const nuiMessage = { + type: 'appearance_hide', + payload: {}, + }; + + SendNuiMessage(JSON.stringify(nuiMessage)); + + if (callback) { + callback(appearance); + } + + callback = null; + + playerAppearance = null; + + playerCoords = null; + + cameraHandle = null; + currentCamera = null; + reverseCamera = null; + + isCameraInterpolating = null; +} + +export function loadModule(): void { + registerNuiCallbacks(); + + startPlayerCustomization(appearance => { + if (appearance) { + console.log(appearance); + } else { + console.log('haha'); + } + }); +} diff --git a/typescript/src/client/modules/interface/events.ts b/typescript/src/client/modules/customization/nui.ts similarity index 55% rename from typescript/src/client/modules/interface/events.ts rename to typescript/src/client/modules/customization/nui.ts index 537696e..19e705e 100644 --- a/typescript/src/client/modules/interface/events.ts +++ b/typescript/src/client/modules/customization/nui.ts @@ -3,6 +3,11 @@ import { getAppearanceSettings, getComponentsSettings, getPropsSettings, + pedTurnAround, + setCamera, + rotateCamera, + getPlayerPedAppearance, + exitPlayerCustomization, } from './index'; import { @@ -18,6 +23,9 @@ import { export function registerNuiCallbacks(): void { RegisterNuiCallbackType('appearance_get_settings_and_data'); + RegisterNuiCallbackType('appearance_set_camera'); + RegisterNuiCallbackType('appearance_turn_around'); + RegisterNuiCallbackType('appearance_rotate_camera'); RegisterNuiCallbackType('appearance_change_model'); RegisterNuiCallbackType('appearance_change_head_blend'); RegisterNuiCallbackType('appearance_change_face_feature'); @@ -27,31 +35,53 @@ export function registerNuiCallbacks(): void { RegisterNuiCallbackType('appearance_change_component'); RegisterNuiCallbackType('appearance_change_prop'); - on('__cfx_nui:appearance_get_settings_and_data', (_: any, cb: (arg: any) => void) => { + RegisterNuiCallbackType('appearance_save'); + RegisterNuiCallbackType('appearance_exit'); + + on('__cfx_nui:appearance_get_settings_and_data', (_: any, cb: (arg: any) => void): void => { const appearanceData = getAppearance(); const appearanceSettings = getAppearanceSettings(appearanceData); - cb({ appearanceData, appearanceSettings }); }); - on('__cfx_nui:appearance_change_model', (appearance: PedAppearance, cb: (arg: any) => void) => { - setPlayerModel(appearance.model); + on('__cfx_nui:appearance_set_camera', (camera: string, cb: (arg: any) => void): void => { + cb({}); + setCamera(camera); + }); + + on('__cfx_nui:appearance_turn_around', (_: any, cb: (arg: any) => void): void => { + cb({}); + + pedTurnAround(PlayerPedId()); + }); + + on( + '__cfx_nui:appearance_rotate_camera', + (direction: 'left' | 'right', cb: (arg: any) => void): void => { + cb({}); + rotateCamera(direction); + }, + ); + + on('__cfx_nui:appearance_change_model', (model: string, cb: (arg: any) => void): void => { + setPlayerModel(model); + + const appearanceData = getPlayerPedAppearance(model); + const appearanceSettings = getAppearanceSettings(appearanceData); - cb(getAppearanceSettings(appearance)); + cb({ appearanceSettings, appearanceData }); }); on( '__cfx_nui:appearance_change_component', - (components: PedComponent[], cb: (arg: any) => void) => { + (components: PedComponent[], cb: (arg: any) => void): void => { setPedComponents(PlayerPedId(), components); - cb(getComponentsSettings(components)); }, ); - on('__cfx_nui:appearance_change_prop', (props: PedProp[], cb: (arg: any) => void) => { + on('__cfx_nui:appearance_change_prop', (props: PedProp[], cb: (arg: any) => void): void => { setPedProps(PlayerPedId(), props); - cb(getPropsSettings(props)); }); @@ -65,29 +95,37 @@ export function registerNuiCallbacks(): void { on( '__cfx_nui:appearance_change_face_feature', - (faceFeatures: PedFaceFeatures, cb: (arg: any) => void) => { + (faceFeatures: PedFaceFeatures, cb: (arg: any) => void): void => { cb({}); - console.log(faceFeatures); setPedFaceFeatures(PlayerPedId(), faceFeatures); }, ); on( '__cfx_nui:appearance_change_head_overlay', - (headOverlays: PedHeadOverlays, cb: (arg: any) => void) => { + (headOverlays: PedHeadOverlays, cb: (arg: any) => void): void => { cb({}); - console.log(headOverlays.beard); setPedHeadOverlays(PlayerPedId(), headOverlays); }, ); - on('__cfx_nui:appearance_change_hair', (hair: PedHair, cb: (arg: any) => void) => { + on('__cfx_nui:appearance_change_hair', (hair: PedHair, cb: (arg: any) => void): void => { cb({}); setPedHair(PlayerPedId(), hair); }); - on('__cfx_nui:appearance_change_eye_color', (eyeColor: number, cb: (arg: any) => void) => { + on('__cfx_nui:appearance_change_eye_color', (eyeColor: number, cb: (arg: any) => void): void => { cb({}); setPedEyeColor(PlayerPedId(), eyeColor); }); + + on('__cfx_nui:appearance_save', (appearance: PedAppearance, cb: (arg: any) => void): void => { + cb({}); + exitPlayerCustomization(appearance); + }); + + on('__cfx_nui:appearance_exit', (_: any, cb: (arg: any) => void): void => { + cb({}); + exitPlayerCustomization(); + }); } diff --git a/typescript/src/client/modules/interface/index.ts b/typescript/src/client/modules/interface/index.ts deleted file mode 100644 index 7321fac..0000000 --- a/typescript/src/client/modules/interface/index.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { DEFAULT_APPEARANCE, DEFAULT_SETTINGS } from '../../constants'; -import { PED_MODELS } from '../../constants'; - -import { registerNuiCallbacks } from './events'; - -let playerAppearance: PedAppearance; - -function getComponent(components: PedComponent[], component_id: number) { - let component = components.find(c => c.component_id === component_id); - - if (!component) { - [component] = components; - } - return component; -} - -function getProp(props: PedProp[], prop_id: number) { - let prop = props.find(p => p.prop_id === prop_id); - - if (!prop) { - [prop] = props; - } - return prop; -} - -function getRgbColors(): { hair: number[][]; makeUp: number[][] } { - const colors = { - hair: [], - makeUp: [], - }; - - for (let i = 0; i <= 63; i++) { - colors.hair.push(GetPedHairRgbColor(i)); - colors.makeUp.push(GetMakeupRgbColor(i)); - } - - return colors; -} - -export function getComponentsSettings(components: PedComponent[]): ComponentSettings[] { - const playerPed = PlayerPedId(); - - const settings = DEFAULT_SETTINGS.components; - - settings.forEach(componentSettings => { - const component = getComponent(components, componentSettings.component_id); - - componentSettings.drawable.max = GetNumberOfPedDrawableVariations( - playerPed, - componentSettings.component_id, - ); - - console.log(componentSettings.component_id, componentSettings.drawable.max); - - componentSettings.texture.max = GetNumberOfPedTextureVariations( - playerPed, - component.component_id, - component.drawable, - ); - }); - - return settings; -} - -export function getPropsSettings(props: PedProp[]): PropSettings[] { - const playerPed = PlayerPedId(); - - const settings = DEFAULT_SETTINGS.props; - - settings.forEach(propSettings => { - const prop = getProp(props, propSettings.prop_id); - - propSettings.drawable.max = GetNumberOfPedPropDrawableVariations( - playerPed, - propSettings.prop_id, - ); - - propSettings.texture.max = GetNumberOfPedPropTextureVariations( - playerPed, - propSettings.prop_id, - prop.drawable, - ); - }); - - return settings; -} - -export function getAppearance(): PedAppearance { - return playerAppearance ? playerAppearance : DEFAULT_APPEARANCE; -} - -export function getAppearanceSettings(appearanceData: PedAppearance): AppearanceSettings { - const pedSettings = DEFAULT_SETTINGS.ped; - - pedSettings.model.items = PED_MODELS; - - const componentsSettings = getComponentsSettings(appearanceData.components); - - const propsSettings = getPropsSettings(appearanceData.props); - - const headBlendSettings = DEFAULT_SETTINGS.headBlend; - - const faceFeaturesSettings = DEFAULT_SETTINGS.faceFeatures; - - const { hair: hairColors, makeUp: makeUpColors } = getRgbColors(); - - const headOverlaysSettings = DEFAULT_SETTINGS.headOverlays; - - Object.keys(headOverlaysSettings).forEach((key: HeadOverlaysSettingsKey) => { - if (headOverlaysSettings[key].color) { - const colorMap = { - beard: hairColors, - eyebrows: hairColors, - chestHair: hairColors, - makeUp: makeUpColors, - blush: makeUpColors, - lipstick: makeUpColors, - }; - - headOverlaysSettings[key].color.items = colorMap[key]; - } - }); - - const hairSettings = DEFAULT_SETTINGS.hair; - - hairSettings.color.items = hairColors; - hairSettings.highlight.items = hairColors; - - const eyeColorSettings = DEFAULT_SETTINGS.eyeColor; - - const appearanceSettings = { - ped: pedSettings, - components: componentsSettings, - props: propsSettings, - headBlend: headBlendSettings, - faceFeatures: faceFeaturesSettings, - headOverlays: headOverlaysSettings, - hair: hairSettings, - eyeColor: eyeColorSettings, - }; - - return appearanceSettings; -} - -export function loadModule(): void { - registerNuiCallbacks(); -} diff --git a/typescript/src/client/utils/vector.ts b/typescript/src/client/utils/vector.ts new file mode 100644 index 0000000..c0fd3d1 --- /dev/null +++ b/typescript/src/client/utils/vector.ts @@ -0,0 +1,47 @@ +export function arrayToVector3(coords: number[]): Vector3 { + return { + x: coords[0], + y: coords[1], + z: coords[2], + }; +} + +export function addVector3(vectorA: Vector3, vectorB: Vector3): Vector3 { + return { + x: vectorA.x + vectorB.x, + y: vectorA.y + vectorB.y, + z: vectorA.z + vectorB.y, + }; +} + +export function subVector3(vectorA: Vector3, vectorB: Vector3): Vector3 { + return { + x: vectorA.x - vectorB.x, + y: vectorA.y - vectorB.y, + z: vectorA.z - vectorB.y, + }; +} + +export function multiplyVector3ByNumber(vectorA: Vector3, value: number): Vector3 { + return { + x: vectorA.x * value, + y: vectorA.y * value, + z: vectorA.z * value, + }; +} + +export function distance(vectorA: Vector3, vectorB: Vector3): number { + return Math.sqrt( + Math.pow(vectorA.x - vectorB.x, 2) + + Math.pow(vectorA.y - vectorB.y, 2) + + Math.pow(vectorA.z - vectorB.z, 2), + ); +} + +export function distance2d(vectorA: Vector3, vectorB: Vector3): number { + if (vectorA === undefined || vectorB === undefined) { + throw new Error('distance2d: vectorA or vectorB is undefined'); + } + + return Math.sqrt(Math.pow(vectorA.x - vectorB.x, 2) + Math.pow(vectorA.y - vectorB.y, 2)); +} diff --git a/ui/src/components/Appearance/HeadOverlays.tsx b/ui/src/components/Appearance/HeadOverlays.tsx index dc8e094..8181ee9 100644 --- a/ui/src/components/Appearance/HeadOverlays.tsx +++ b/ui/src/components/Appearance/HeadOverlays.tsx @@ -42,7 +42,7 @@ const HeadOverlays: React.FC = ({ handleHeadOverlayChange, handleEyeColorChange, }) => ( -
+
void; + handleSetCamera: (key: keyof CameraState) => void; + handleTurnAround: () => void; handleRotateLeft: () => void; handleRotateRight: () => void; handleSave: () => void; @@ -39,7 +40,7 @@ const Container = styled.div` align-items: flex-start; justify-content: flex-start; - padding: 20px 0; + padding: 40px 0; > * { & + * { @@ -204,10 +205,11 @@ const ExtendedOption: React.FC = ({ children, icon }) => { ); }; -const Options: React.FC = ({ +const Options: React.FC = ({ camera, rotate, - handleCameraChange, + handleSetCamera, + handleTurnAround, handleRotateLeft, handleRotateRight, handleExit, @@ -216,17 +218,17 @@ const Options: React.FC = ({ return ( }> - handleCameraChange('head')}> + handleSetCamera('head')}> - handleCameraChange('body')}> + handleSetCamera('body')}> - handleCameraChange('bottom')}> + handleSetCamera('bottom')}> - diff --git a/ui/src/components/Appearance/components/Section.tsx b/ui/src/components/Appearance/components/Section.tsx index 3e559f5..b365827 100644 --- a/ui/src/components/Appearance/components/Section.tsx +++ b/ui/src/components/Appearance/components/Section.tsx @@ -1,10 +1,11 @@ import { useState, useEffect, useRef } from 'react'; import styled from 'styled-components'; import { FiChevronDown, FiChevronUp } from 'react-icons/fi'; -import { useTransition, animated } from 'react-spring'; +import { useSpring, animated } from 'react-spring'; interface SectionProps { title: string; + deps?: any[]; } interface HeaderProps { @@ -57,42 +58,43 @@ const Header = styled.div` const Items = styled.div` padding: 0 2px 5px 2px; + + overflow: hidden; `; -const Section: React.FC = ({ children, title }) => { - const [active, setActive] = useState(true); +const Section: React.FC = ({ children, title, deps = [] }) => { + const [active, setActive] = useState(false); const [height, setHeight] = useState(0); const ref = useRef(null); - const itemsTransition = useTransition(active, null, { - from: { transform: 'translateY(-50px)', opacity: 0, height: 0 }, - enter: { transform: 'translateY(0)', opacity: 1, height }, - leave: { transform: 'translateY(-50px)', opacity: 0, height: 0 }, + const props = useSpring({ + height: active ? height : 0, + opacity: active ? 1 : 0, }); useEffect(() => { if (ref.current) { setHeight(ref.current.offsetHeight); - setActive(false); } }, [ref, setHeight]); + useEffect(() => { + if (ref.current) { + setHeight(ref.current.offsetHeight); + } + }, [deps]); + return (
setActive(state => !state)}> {title} {active ? : }
- {children && - itemsTransition.map( - ({ item, key, props }) => - item && ( - - {children} - - ), - )} + + + {children} +
); }; diff --git a/ui/src/components/Appearance/index.tsx b/ui/src/components/Appearance/index.tsx index d03c6b3..f9a2565 100644 --- a/ui/src/components/Appearance/index.tsx +++ b/ui/src/components/Appearance/index.tsx @@ -36,7 +36,21 @@ import { Wrapper, Container } from './styles'; if (process.env.REACT_APP_ENV !== 'production') { mock('appearance_get_settings_and_data', () => ({ appearanceData: { ...APPEARANCE_INITIAL_STATE, model: 'mp_f_freemode_01' }, - appearanceSettings: { ...SETTINGS_INITIAL_STATE, eyeColor: { min: 0, max: 24 } }, + appearanceSettings: { + ...SETTINGS_INITIAL_STATE, + eyeColor: { min: 0, max: 24 }, + hair: { + ...SETTINGS_INITIAL_STATE.hair, + color: { + items: [ + [255, 0, 0], + [0, 255, 0], + [0, 0, 255], + [0, 0, 255], + ], + }, + }, + }, })); mock('appearance_change_model', () => SETTINGS_INITIAL_STATE); @@ -142,28 +156,43 @@ const Appearance: React.FC = () => { return prop; }; - const handleCameraChange = useCallback( - (key: keyof CameraState) => { - setCamera(state => { - const currentCameraState = state[key]; - - const cameraState = { ...state }; + const handleTurnAround = useCallback(() => { + Nui.post('appearance_turn_around'); + }, []); - Object.assign(cameraState, CAMERA_INITIAL_STATE); + const handleSetCamera = useCallback( + (key: keyof CameraState) => { + setCamera({ ...CAMERA_INITIAL_STATE, [key]: !camera[key] }); + setRotate(ROTATE_INITIAL_STATE); - return { ...cameraState, [key]: !currentCameraState }; - }); + if (!camera[key]) { + Nui.post('appearance_set_camera', key); + } else { + Nui.post('appearance_set_camera', 'default'); + } }, - [setCamera], + [camera, setCamera, setRotate], ); const handleRotateLeft = useCallback(() => { - setRotate(state => ({ left: !state.left, right: false })); - }, [setRotate]); + setRotate({ left: !rotate.left, right: false }); + + if (!rotate.left) { + Nui.post('appearance_rotate_camera', 'left'); + } else { + Nui.post('appearance_set_camera', 'current'); + } + }, [setRotate, rotate]); const handleRotateRight = useCallback(() => { - setRotate(state => ({ left: false, right: !state.right })); - }, [setRotate]); + setRotate({ left: false, right: !rotate.right }); + + if (!rotate.right) { + Nui.post('appearance_rotate_camera', 'right'); + } else { + Nui.post('appearance_set_camera', 'current'); + } + }, [setRotate, rotate]); const handleSaveModal = useCallback(() => { setSaveModal(true); @@ -199,12 +228,10 @@ const Appearance: React.FC = () => { const handleModelChange = useCallback( async (value: string) => { - const updatedData = { ...APPEARANCE_INITIAL_STATE, model: value }; - setData(updatedData); + const { appearanceSettings, appearanceData } = await Nui.post('appearance_change_model', value); - const updatedSettings = await Nui.post('appearance_change_model', updatedData); - - setSettings(updatedSettings); + setSettings(appearanceSettings); + setData(appearanceData); }, [setData, setSettings], ); @@ -377,6 +404,10 @@ const Appearance: React.FC = () => { Nui.onEvent('appearance_display', () => { setDisplay({ appearance: true }); }); + + Nui.onEvent('appearance_hide', () => { + setDisplay({ appearance: false }); + }); }, [setDisplay]); useEffect(() => { @@ -408,7 +439,7 @@ const Appearance: React.FC = () => { data={model} handleModelChange={handleModelChange} /> - {isPedFreemodeModel && ( + {isPedFreemodeModel && settings && ( <> {