Files
ox_lib/web/src/features/skillcheck/index.tsx

164 lines
4.7 KiB
TypeScript
Raw Normal View History

import { useRef, useState } from 'react';
2022-10-13 14:00:34 +02:00
import { useNuiEvent } from '../../hooks/useNuiEvent';
import Indicator from './indicator';
import { fetchNui } from '../../utils/fetchNui';
import { useMantineTheme, Box, createStyles } from '@mantine/core';
2022-10-13 14:00:34 +02:00
interface CustomGameDifficulty {
areaSize: number;
speedMultiplier: number;
}
export type GameDifficulty = 'easy' | 'medium' | 'hard' | CustomGameDifficulty;
2022-10-13 14:00:34 +02:00
export interface SkillCheckProps {
angle: number;
difficultyOffset: number;
difficulty: GameDifficulty;
key: string;
2022-10-13 14:00:34 +02:00
}
export const circleCircumference = 2 * 50 * Math.PI;
2022-10-13 14:00:34 +02:00
const getRandomAngle = (min: number, max: number) => Math.floor(Math.random() * (max - min)) + min;
const difficultyOffsets = {
easy: 50,
medium: 40,
hard: 25,
2022-10-13 14:00:34 +02:00
};
const useStyles = createStyles((theme) => ({
svg: {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
},
track: {
fill: 'transparent',
stroke: theme.colors.dark[5],
strokeWidth: 8,
},
skillArea: {
fill: 'transparent',
stroke: theme.fn.primaryColor(),
strokeWidth: 8,
},
indicator: {
stroke: 'red',
strokeWidth: 16,
fill: 'transparent',
},
button: {
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: theme.colors.dark[5],
width: 25,
height: 25,
textAlign: 'center',
borderRadius: 5,
fontSize: 16,
fontWeight: 500,
},
}));
2022-10-13 14:00:34 +02:00
const SkillCheck: React.FC = () => {
const { classes } = useStyles();
const [visible, setVisible] = useState(false);
const dataRef = useRef<{ difficulty: GameDifficulty | GameDifficulty[]; inputs?: string[] } | null>(null);
const dataIndexRef = useRef<number>(0);
2022-10-13 14:00:34 +02:00
const [skillCheck, setSkillCheck] = useState<SkillCheckProps>({
angle: 0,
difficultyOffset: 50,
2022-10-13 14:00:34 +02:00
difficulty: 'easy',
key: 'e',
2022-10-13 14:00:34 +02:00
});
useNuiEvent('startSkillCheck', (data: { difficulty: GameDifficulty | GameDifficulty[]; inputs?: string[] }) => {
dataRef.current = data;
dataIndexRef.current = 0;
const gameData = Array.isArray(data.difficulty) ? data.difficulty[0] : data.difficulty;
const offset = typeof gameData === 'object' ? gameData.areaSize : difficultyOffsets[gameData];
const randomKey = data.inputs ? data.inputs[Math.floor(Math.random() * data.inputs.length)] : 'e';
2022-10-13 14:00:34 +02:00
setSkillCheck({
angle: -90 + getRandomAngle(120, 360 - offset),
difficultyOffset: offset,
difficulty: gameData,
key: randomKey,
2022-10-13 14:00:34 +02:00
});
setVisible(true);
2022-10-13 14:00:34 +02:00
});
const handleComplete = (success: boolean) => {
if (!dataRef.current) return;
if (!success || !Array.isArray(dataRef.current.difficulty)) {
setVisible(false);
return fetchNui('skillCheckOver', success);
}
if (dataIndexRef.current >= dataRef.current.difficulty.length - 1) {
setVisible(false);
return fetchNui('skillCheckOver', success);
}
dataIndexRef.current++;
const data = dataRef.current.difficulty[dataIndexRef.current];
const key = dataRef.current.inputs
? dataRef.current.inputs[Math.floor(Math.random() * dataRef.current.inputs.length)]
: 'e';
const offset = typeof data === 'object' ? data.areaSize : difficultyOffsets[data];
setSkillCheck({
angle: -90 + getRandomAngle(120, 360 - offset),
difficultyOffset: offset,
difficulty: data,
key,
});
};
2022-10-13 14:00:34 +02:00
return (
<>
{visible && (
2022-10-13 14:00:34 +02:00
<>
<svg r={50} width={500} height={500} className={classes.svg}>
2022-10-13 14:00:34 +02:00
{/*Circle track*/}
<circle r={50} cx={250} cy={250} className={classes.track} strokeDasharray={circleCircumference} />
2022-10-13 14:00:34 +02:00
{/*SkillCheck area*/}
<circle
r={50}
cx={250}
cy={250}
strokeDasharray={circleCircumference}
strokeDashoffset={circleCircumference - (Math.PI * 50 * skillCheck.difficultyOffset) / 180}
2022-10-13 14:00:34 +02:00
transform={`rotate(${skillCheck.angle}, 250, 250)`}
className={classes.skillArea}
2022-10-13 14:00:34 +02:00
/>
<Indicator
angle={skillCheck.angle}
offset={skillCheck.difficultyOffset}
multiplier={
skillCheck.difficulty === 'easy'
? 1
: skillCheck.difficulty === 'medium'
? 1.5
: skillCheck.difficulty === 'hard'
? 1.75
: skillCheck.difficulty.speedMultiplier
}
handleComplete={handleComplete}
className={classes.indicator}
skillCheck={skillCheck}
2022-10-13 14:00:34 +02:00
/>
</svg>
<Box className={classes.button}>{skillCheck.key.toUpperCase()}</Box>
2022-10-13 14:00:34 +02:00
</>
)}
</>
2022-10-13 14:00:34 +02:00
);
};
export default SkillCheck;