mirror of
https://github.com/VickScarlet/lifeRestart.git
synced 2026-08-30 02:24:42 +08:00
feature: multiple lock, pick min max
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
"author": "Vick Scarlet <vick@syaro.io>",
|
"author": "Vick Scarlet <vick@syaro.io>",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bunx --bun vite",
|
"dev": "bunx --bun vite",
|
||||||
|
"dev:prod": "bunx --bun vite --mode production",
|
||||||
"build": "bunx --bun vite build",
|
"build": "bunx --bun vite build",
|
||||||
"preview": "bunx --bun vite preview",
|
"preview": "bunx --bun vite preview",
|
||||||
"lint": "bunx --bun eslint",
|
"lint": "bunx --bun eslint",
|
||||||
@@ -14,7 +15,6 @@
|
|||||||
"@remake/data": "workspace:*",
|
"@remake/data": "workspace:*",
|
||||||
"@remake/hooks": "workspace:*",
|
"@remake/hooks": "workspace:*",
|
||||||
"@remake/vitex": "workspace:*",
|
"@remake/vitex": "workspace:*",
|
||||||
"classnames": "^2.5.1",
|
|
||||||
"jotai": "^2.20.2",
|
"jotai": "^2.20.2",
|
||||||
"react": "^19.2.8",
|
"react": "^19.2.8",
|
||||||
"react-dom": "^19.2.8"
|
"react-dom": "^19.2.8"
|
||||||
|
|||||||
+32
-8
@@ -1,20 +1,33 @@
|
|||||||
import type { Config } from '@remake/hooks'
|
import type { Config } from '@remake/hooks'
|
||||||
|
|
||||||
|
export const isDev = import.meta.env.MODE === 'development'
|
||||||
|
// 自动模式自动点击间隔
|
||||||
export const AutoInterval = 500
|
export const AutoInterval = 500
|
||||||
|
// 可锁定的天赋数量
|
||||||
|
export const LockLimit = isDev ? 10 : 1
|
||||||
|
// 名人模式重开次数限制
|
||||||
export const ModeLimit = 10
|
export const ModeLimit = 10
|
||||||
export const PickLimit = 3
|
// 天赋选择数量限制
|
||||||
|
export const PickMax = isDev ? 10 : 3
|
||||||
|
// 天赋选择最少数量限制
|
||||||
|
export const PickMin = 3
|
||||||
|
// 基础属性点
|
||||||
export const BasePoints = 20
|
export const BasePoints = 20
|
||||||
|
// 单项属性点最大限制
|
||||||
export const AllocLimit = 10
|
export const AllocLimit = 10
|
||||||
|
// 默认初始快乐
|
||||||
export const DefaultSpirit = 5
|
export const DefaultSpirit = 5
|
||||||
export const PullCount = 10
|
// 天赋抽取个数
|
||||||
|
export const PullCount = isDev ? 20 : 10
|
||||||
|
// 天赋抽取基础概率
|
||||||
export const PullRateBase: Config['pull']['rate']['base'] = new Map([
|
export const PullRateBase: Config['pull']['rate']['base'] = new Map([
|
||||||
[0, 889],
|
[0, 889],
|
||||||
[1, 100],
|
[1, 100],
|
||||||
[2, 10],
|
[2, 10],
|
||||||
[3, 1],
|
[3, 1],
|
||||||
])
|
])
|
||||||
|
// 属性评级线
|
||||||
export const JudgeMap = {
|
export const JudgeLineMap = {
|
||||||
age: [0, 1, 10, 18, 40, 60, 70, 80, 90, 95, 100, 500],
|
age: [0, 1, 10, 18, 40, 60, 70, 80, 90, 95, 100, 500],
|
||||||
summary: [0, 41, 50, 60, 80, 100, 110, 120],
|
summary: [0, 41, 50, 60, 80, 100, 110, 120],
|
||||||
times: [0, 10, 30, 50, 70, 100],
|
times: [0, 10, 30, 50, 70, 100],
|
||||||
@@ -27,15 +40,17 @@ export const JudgeMap = {
|
|||||||
talent: [0, 0.3, 0.6, 0.9],
|
talent: [0, 0.3, 0.6, 0.9],
|
||||||
event: [0, 0.2, 0.4, 0.6],
|
event: [0, 0.2, 0.4, 0.6],
|
||||||
}
|
}
|
||||||
export type Judges = keyof typeof JudgeMap
|
// 可评级属性
|
||||||
|
export type Judges = keyof typeof JudgeLineMap
|
||||||
|
// 获取属性评级
|
||||||
export function judge(key: Judges, value: number) {
|
export function judge(key: Judges, value: number) {
|
||||||
const arr = JudgeMap[key]
|
const arr = JudgeLineMap[key]
|
||||||
for (let i = arr.length - 1; i >= 0; i--) {
|
for (let i = arr.length - 1; i >= 0; i--) {
|
||||||
if (value >= arr[i]!) return i
|
if (value >= arr[i]!) return i
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
// 属性稀有度等级线
|
||||||
export const JudgeGradeMap = {
|
export const JudgeGradeMap = {
|
||||||
age: [0, 5, 7, 9],
|
age: [0, 5, 7, 9],
|
||||||
summary: [0, 4, 5, 6],
|
summary: [0, 4, 5, 6],
|
||||||
@@ -49,6 +64,7 @@ export const JudgeGradeMap = {
|
|||||||
talent: [0, 1, 2, 3],
|
talent: [0, 1, 2, 3],
|
||||||
event: [0, 1, 2, 3],
|
event: [0, 1, 2, 3],
|
||||||
}
|
}
|
||||||
|
// 获取属性稀有度
|
||||||
export function judgeGrade(key: Judges, level: number) {
|
export function judgeGrade(key: Judges, level: number) {
|
||||||
const arr = JudgeGradeMap[key]
|
const arr = JudgeGradeMap[key]
|
||||||
for (let i = arr.length - 1; i >= 0; i--) {
|
for (let i = arr.length - 1; i >= 0; i--) {
|
||||||
@@ -56,7 +72,13 @@ export function judgeGrade(key: Judges, level: number) {
|
|||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
// 根据属性值获取属性稀有度
|
||||||
|
export function judgeGradeByValue(key: Judges, value: number) {
|
||||||
|
const level = judge(key, value)
|
||||||
|
return judgeGrade(key, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 天赋抽取概率加成
|
||||||
export const PullRateAdditions: Config['pull']['rate']['additions'] = {
|
export const PullRateAdditions: Config['pull']['rate']['additions'] = {
|
||||||
times: value => {
|
times: value => {
|
||||||
const level = judge('times', value)
|
const level = judge('times', value)
|
||||||
@@ -69,8 +91,10 @@ export const PullRateAdditions: Config['pull']['rate']['additions'] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const config: Config = {
|
export const config: Config = {
|
||||||
|
lock: LockLimit,
|
||||||
mode: ModeLimit,
|
mode: ModeLimit,
|
||||||
pick: PickLimit,
|
max: PickMax,
|
||||||
|
min: PickMin,
|
||||||
points: BasePoints,
|
points: BasePoints,
|
||||||
allocate: AllocLimit,
|
allocate: AllocLimit,
|
||||||
spirit: DefaultSpirit,
|
spirit: DefaultSpirit,
|
||||||
|
|||||||
@@ -9,10 +9,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
border: 0.0625rem solid var(--base-text-color);
|
border: 0.0625rem solid var(--grade-color);
|
||||||
|
color: var(--grade-color);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
button {
|
button {
|
||||||
|
--style-color: var(--grade-color);
|
||||||
flex: 1 0;
|
flex: 1 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -25,6 +28,8 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
color: var(--grade-color);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.points-detail {
|
.points-detail {
|
||||||
@@ -73,23 +78,27 @@
|
|||||||
> span {
|
> span {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
border: 0.0625rem solid var(--base-text-color);
|
color: var(--base-background-color);
|
||||||
|
background: var(--grade-color);
|
||||||
|
border: 0.0625rem solid var(--grade-color);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
> li.left {
|
> li.left {
|
||||||
|
--left-color: var(--color-error);
|
||||||
|
&.left-0 { --left-color: var(--color-primary); }
|
||||||
> span {
|
> span {
|
||||||
background: var(--color-primary);
|
background: var(--left-color);
|
||||||
color: var(--base-background-color);
|
border: 0.0625rem solid var(--left-color);
|
||||||
border: 0.0625rem solid var(--color-primary);
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
}
|
||||||
> button {
|
> button {
|
||||||
|
--style-color: var(--left-color);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { usePicked, useReplaced, useStart } from '@remake/hooks'
|
|||||||
import type { AdditionalPoint } from '@remake/hooks'
|
import type { AdditionalPoint } from '@remake/hooks'
|
||||||
import { properties } from '@/display'
|
import { properties } from '@/display'
|
||||||
import { keys } from '@remake/vitex'
|
import { keys } from '@remake/vitex'
|
||||||
import { BasePoints } from '@/config'
|
import { BasePoints, judgeGradeByValue } from '@/config'
|
||||||
import { talents } from '@remake/data'
|
import { talents } from '@remake/data'
|
||||||
import Replaced from '@/components/Replaced'
|
import Replaced from '@/components/Replaced'
|
||||||
import './Alloc.css'
|
import './Alloc.css'
|
||||||
@@ -62,6 +62,10 @@ export function Alloc() {
|
|||||||
const start = useStart()
|
const start = useStart()
|
||||||
const [showDetail, setShowDetail] = useState(false)
|
const [showDetail, setShowDetail] = useState(false)
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
|
if (left) {
|
||||||
|
// TODO: Show a warning that there are still points left
|
||||||
|
return
|
||||||
|
}
|
||||||
const achievements = start()
|
const achievements = start()
|
||||||
// TODO: Show achievements
|
// TODO: Show achievements
|
||||||
console.log('Achievements', achievements)
|
console.log('Achievements', achievements)
|
||||||
@@ -76,10 +80,10 @@ export function Alloc() {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="alloc">
|
<ul className="alloc">
|
||||||
<li className="left">
|
<li className={`left left-${left}`}>
|
||||||
<span>剩余点数</span>
|
<span>剩余点数</span>
|
||||||
<button
|
<button
|
||||||
className="primary font-mono"
|
className="font-mono"
|
||||||
onClick={() => setShowDetail(!showDetail)}
|
onClick={() => setShowDetail(!showDetail)}
|
||||||
>
|
>
|
||||||
{left}
|
{left}
|
||||||
@@ -87,7 +91,10 @@ export function Alloc() {
|
|||||||
{showDetail && <PointsDetail source={source} />}
|
{showDetail && <PointsDetail source={source} />}
|
||||||
</li>
|
</li>
|
||||||
{keys(allocation).map(key => (
|
{keys(allocation).map(key => (
|
||||||
<li key={key}>
|
<li
|
||||||
|
key={key}
|
||||||
|
className={`property grade-${judgeGradeByValue(key, allocation[key])}`}
|
||||||
|
>
|
||||||
<span>{properties[key]}</span>
|
<span>{properties[key]}</span>
|
||||||
<AllocInput
|
<AllocInput
|
||||||
point={allocation[key]}
|
point={allocation[key]}
|
||||||
@@ -100,7 +107,10 @@ export function Alloc() {
|
|||||||
<button className="info" onClick={() => random()}>
|
<button className="info" onClick={() => random()}>
|
||||||
随机分配
|
随机分配
|
||||||
</button>
|
</button>
|
||||||
<button className="primary" onClick={handleNext}>
|
<button
|
||||||
|
className={left ? 'error' : 'primary'}
|
||||||
|
onClick={handleNext}
|
||||||
|
>
|
||||||
开始新人生
|
开始新人生
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,21 +15,21 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
min-width: 6rem;
|
min-width: 6rem;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
color: var(--grade-color);
|
||||||
span {
|
span {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 0.0625rem solid var(--base-text-color);
|
border: 0.0625rem solid var(--grade-color);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.value {
|
.name {
|
||||||
color: var(--color-primary);
|
color: var(--base-background-color);
|
||||||
font-size: 1.25rem;
|
background-color: var(--grade-color);
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
&.charm { order: 1 }
|
&.charm { order: 1 }
|
||||||
&.intelligence { order: 2 }
|
&.intelligence { order: 2 }
|
||||||
@@ -112,3 +112,54 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.screen.play > ul.properties > li {
|
||||||
|
.value {
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--base-background-color);
|
||||||
|
transition: background-color 0.5s ease;
|
||||||
|
}
|
||||||
|
&[class*="trend-up"] .value::after,
|
||||||
|
&[class*="trend-down"] .value::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 0.5rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border-style: solid;
|
||||||
|
animation: triangle-pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||||
|
}
|
||||||
|
&[class*="trend-up"] .value::after {
|
||||||
|
border-width: 0 0.3125rem 0.375rem 0.3125rem;
|
||||||
|
border-color: transparent transparent var(--color-success) transparent;
|
||||||
|
}
|
||||||
|
&[class*="trend-down"] .value::after {
|
||||||
|
border-width: 0.375rem 0.3125rem 0 0.3125rem;
|
||||||
|
border-color: var(--color-error) transparent transparent transparent;
|
||||||
|
}
|
||||||
|
&[class*="trend-up-1"] .value { animation: flash-success-odd 0.5s ease-out forwards; }
|
||||||
|
&[class*="trend-up-0"] .value { animation: flash-success-even 0.5s ease-out forwards; }
|
||||||
|
&[class*="trend-down-1"] .value { animation: flash-error-odd 0.5s ease-out forwards; }
|
||||||
|
&[class*="trend-down-0"] .value { animation: flash-error-even 0.5s ease-out forwards; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes flash-success-odd {
|
||||||
|
0% { background-color: color-mix(in srgb, var(--color-success) 30%, var(--base-background-color)); }
|
||||||
|
100% { background-color: var(--base-background-color); }
|
||||||
|
}
|
||||||
|
@keyframes flash-success-even {
|
||||||
|
0% { background-color: color-mix(in srgb, var(--color-success) 30%, var(--base-background-color)); }
|
||||||
|
100% { background-color: var(--base-background-color); }
|
||||||
|
}
|
||||||
|
@keyframes flash-error-odd {
|
||||||
|
0% { background-color: color-mix(in srgb, var(--color-error) 30%, var(--base-background-color)); }
|
||||||
|
100% { background-color: var(--base-background-color); }
|
||||||
|
}
|
||||||
|
@keyframes flash-error-even {
|
||||||
|
0% { background-color: color-mix(in srgb, var(--color-error) 30%, var(--base-background-color)); }
|
||||||
|
100% { background-color: var(--base-background-color); }
|
||||||
|
}
|
||||||
|
@keyframes triangle-pop {
|
||||||
|
0% { opacity: 0; transform: translateY(-50%) scale(0.5); }
|
||||||
|
100% { opacity: 1; transform: translateY(-50%) scale(1); }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useState, useRef } from 'react'
|
import { useState, useRef } from 'react'
|
||||||
import { useLayoutEffect, useEffect } from 'react'
|
import { useLayoutEffect, useEffect } from 'react'
|
||||||
import { useNext, useGotoSummary } from '@remake/hooks'
|
import { useNext, useGotoSummary } from '@remake/hooks'
|
||||||
|
import { useJudge } from '@/hooks/judge'
|
||||||
import type { Log } from '@remake/hooks'
|
import type { Log } from '@remake/hooks'
|
||||||
import { properties } from '@/display'
|
import { properties } from '@/display'
|
||||||
import { keys } from '@remake/vitex'
|
|
||||||
import { achievements, events, talents } from '@remake/data'
|
import { achievements, events, talents } from '@remake/data'
|
||||||
import { AutoInterval } from '@/config'
|
import { AutoInterval } from '@/config'
|
||||||
import './Play.css'
|
import './Play.css'
|
||||||
@@ -66,10 +66,7 @@ function LogAchievements({ items }: { items: number[] }) {
|
|||||||
return <ul className="log-inner log-achievements">{els}</ul>
|
return <ul className="log-inner log-achievements">{els}</ul>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LogProps {
|
function Log({ log }: { log: Log }) {
|
||||||
log: Log
|
|
||||||
}
|
|
||||||
function Log({ log }: LogProps) {
|
|
||||||
return (
|
return (
|
||||||
<li className="log">
|
<li className="log">
|
||||||
<span className="age font-mono">{log.age}岁</span>
|
<span className="age font-mono">{log.age}岁</span>
|
||||||
@@ -82,8 +79,51 @@ function Log({ log }: LogProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PropProps {
|
||||||
|
prop: keyof typeof properties
|
||||||
|
value: number
|
||||||
|
grade: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function Prop({ prop, value, grade }: PropProps) {
|
||||||
|
const prevRef = useRef<number>(value)
|
||||||
|
const [trend, setTrend] = useState<'up' | 'down' | 'normal'>('normal')
|
||||||
|
const [animCount, setAnimCount] = useState<number>(0)
|
||||||
|
useEffect(() => {
|
||||||
|
const prev = prevRef.current
|
||||||
|
if (value === prev) return
|
||||||
|
prevRef.current = value
|
||||||
|
setTrend(value > prev ? 'up' : 'down')
|
||||||
|
setAnimCount(c => c + 1)
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setTrend('normal')
|
||||||
|
}, 2000)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}, [value])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={`${prop} grade-${grade} trend-${trend}-${animCount % 2}`}
|
||||||
|
>
|
||||||
|
<span className="name">{properties[prop]}</span>
|
||||||
|
<span className="value font-mono">{value}</span>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Properties() {
|
||||||
|
const judges = useJudge()
|
||||||
|
return (
|
||||||
|
<ul className="properties">
|
||||||
|
{judges.map(([key, { value, grade }]) => (
|
||||||
|
<Prop key={key} prop={key} value={value} grade={grade} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function Play() {
|
export function Play() {
|
||||||
const [{ state, logs, ended }, next] = useNext()
|
const [{ logs, ended }, next] = useNext()
|
||||||
const [auto, setAuto] = useState(false)
|
const [auto, setAuto] = useState(false)
|
||||||
const logRef = useRef<HTMLUListElement>(null)
|
const logRef = useRef<HTMLUListElement>(null)
|
||||||
const autoRef = useRef(0)
|
const autoRef = useRef(0)
|
||||||
@@ -113,16 +153,7 @@ export function Play() {
|
|||||||
}, [auto, handleNext])
|
}, [auto, handleNext])
|
||||||
return (
|
return (
|
||||||
<div className="screen play">
|
<div className="screen play">
|
||||||
<ul className="properties">
|
<Properties />
|
||||||
{keys(state.props.current, ['age']).map(key => (
|
|
||||||
<li className={key} key={key}>
|
|
||||||
<span>{properties[key]}</span>
|
|
||||||
<span className="font-mono">
|
|
||||||
{state.props.current[key]}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
<ul
|
<ul
|
||||||
className="logs hide-scrollbar"
|
className="logs hide-scrollbar"
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
@@ -135,7 +166,7 @@ export function Play() {
|
|||||||
<div className="controls">
|
<div className="controls">
|
||||||
{!ended && (
|
{!ended && (
|
||||||
<button className="primary" onClick={() => setAuto(!auto)}>
|
<button className="primary" onClick={() => setAuto(!auto)}>
|
||||||
{auto ? '手动' : '自动'}
|
{auto ? '关闭自动' : '开启自动'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{ended && (
|
{ended && (
|
||||||
|
|||||||
@@ -1,35 +1,65 @@
|
|||||||
import { useState } from 'react'
|
import { useEffect, useState, useRef } from 'react'
|
||||||
import { usePicked, useEnd } from '@remake/hooks'
|
import { usePicked, useEnd, useProfile } from '@remake/hooks'
|
||||||
import { useJudge } from '@/hooks/judge'
|
import { useEndJudge } from '@/hooks/judge'
|
||||||
import type { JudgeKeys, Judge } from '@/hooks/judge'
|
|
||||||
import { properties, judgeDisplay } from '@/display'
|
import { properties, judgeDisplay } from '@/display'
|
||||||
|
import { isDev } from '@/config'
|
||||||
import TalentComponent from '@/components/Talent'
|
import TalentComponent from '@/components/Talent'
|
||||||
import './Summary.css'
|
import './Summary.css'
|
||||||
|
|
||||||
interface JudgeItemProps {
|
function Judges() {
|
||||||
prop: JudgeKeys
|
const judges = useEndJudge()
|
||||||
judge: Judge
|
|
||||||
}
|
|
||||||
function JudgeItem({ prop, judge }: JudgeItemProps) {
|
|
||||||
const { value, grade, level } = judge
|
|
||||||
return (
|
return (
|
||||||
<li className={`${prop} grade-${grade}`}>
|
<ul className="judge-list">
|
||||||
<span className="property">{properties[prop]}</span>
|
{judges.map(([key, { value, grade, level }]) => (
|
||||||
<span className="value font-mono">{value}</span>
|
<li className={`${key} grade-${grade}`} key={key}>
|
||||||
<span className="level">{judgeDisplay(prop, level)}</span>
|
<span className="property">{properties[key]}</span>
|
||||||
</li>
|
<span className="value font-mono">{value}</span>
|
||||||
|
<span className="level">{judgeDisplay(key, level)}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TalentListProps {
|
||||||
|
picked: Set<number>
|
||||||
|
picker: (id: number) => void
|
||||||
|
}
|
||||||
|
function TalentList({ picked, picker }: TalentListProps) {
|
||||||
|
const [profile] = useProfile()
|
||||||
|
const [talents, setTalents] = useState(usePicked())
|
||||||
|
const hasInitialized = useRef(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!profile.locked) return
|
||||||
|
let currentTalents = talents
|
||||||
|
if (isDev) {
|
||||||
|
currentTalents = new Set([...talents, ...profile.locked])
|
||||||
|
setTalents(currentTalents)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasInitialized.current) {
|
||||||
|
for (const id of profile.locked) {
|
||||||
|
if (currentTalents.has(id) && !picked.has(id)) {
|
||||||
|
picker(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasInitialized.current = true
|
||||||
|
}
|
||||||
|
}, [profile, talents, picked, picker])
|
||||||
|
return (
|
||||||
|
<ul className="talent-list">
|
||||||
|
{Array.from(talents, id => (
|
||||||
|
<li key={id} onClick={() => picker(id)}>
|
||||||
|
<TalentComponent id={id} selected={picked.has(id)} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Summary() {
|
export default function Summary() {
|
||||||
const picked = usePicked()
|
const [locked, picker, end] = useEnd()
|
||||||
const judges = useJudge()
|
|
||||||
const end = useEnd()
|
|
||||||
const [locked, setLocked] = useState<number | null>(null)
|
|
||||||
const handleSelect = (id: number) => {
|
|
||||||
if (locked === id) setLocked(null)
|
|
||||||
else setLocked(id)
|
|
||||||
}
|
|
||||||
const handleEnd = () => {
|
const handleEnd = () => {
|
||||||
const achievements = end()
|
const achievements = end()
|
||||||
// TODO: Show achievements
|
// TODO: Show achievements
|
||||||
@@ -37,24 +67,10 @@ export default function Summary() {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="screen summary">
|
<div className="screen summary">
|
||||||
<ul className="judge-list">
|
<Judges />
|
||||||
{judges.map(([key, { value, grade, level }]) => (
|
|
||||||
<JudgeItem
|
|
||||||
key={key}
|
|
||||||
prop={key}
|
|
||||||
judge={{ value, grade, level }}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<div className="title">你可以锁定一个天赋,下辈子还能抽到</div>
|
<div className="title">你可以锁定一个天赋,下辈子还能抽到</div>
|
||||||
<ul className="talent-list">
|
<TalentList picked={locked} picker={picker} />
|
||||||
{Array.from(picked, id => (
|
|
||||||
<li key={id} onClick={() => handleSelect(id)}>
|
|
||||||
<TalentComponent id={id} selected={locked === id} />
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<button className="primary" onClick={handleEnd}>
|
<button className="primary" onClick={handleEnd}>
|
||||||
再次重开
|
再次重开
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
// import { useEffect } from 'react'
|
// import { useEffect } from 'react'
|
||||||
import { useTalentPuller, useTalentPicker } from '@remake/hooks'
|
import { useTalentPuller, useTalentPicker } from '@remake/hooks'
|
||||||
import { useTalentSubmit, useSubmitIsEnable } from '@remake/hooks'
|
import { useTalentSubmit, useSubmitIsEnable } from '@remake/hooks'
|
||||||
|
import { PullCount } from '@/config'
|
||||||
import TalentComponent from '@/components/Talent'
|
import TalentComponent from '@/components/Talent'
|
||||||
import './TalentPick.css'
|
import './TalentPick.css'
|
||||||
|
|
||||||
export default function TalentPick() {
|
export default function TalentPick() {
|
||||||
const [pulled, puller] = useTalentPuller()
|
const [pulled, puller] = useTalentPuller()
|
||||||
const [talents, picker] = useTalentPicker()
|
const [talents, picker] = useTalentPicker()
|
||||||
const [enabled, limit] = useSubmitIsEnable()
|
const { enabled, min, max } = useSubmitIsEnable()
|
||||||
const submit = useTalentSubmit()
|
const submit = useTalentSubmit()
|
||||||
// useEffect(puller, [])
|
// useEffect(puller, [])
|
||||||
if (!pulled)
|
if (!pulled)
|
||||||
return (
|
return (
|
||||||
<div className="screen talent-pick">
|
<div className="screen talent-pick">
|
||||||
<button className="primary" onClick={() => puller()}>
|
<button className="primary font-mono" onClick={() => puller()}>
|
||||||
十连抽!
|
{PullCount}连抽!
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -32,7 +33,9 @@ export default function TalentPick() {
|
|||||||
下一步
|
下一步
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button className="error">请选取 {limit} 个天赋</button>
|
<button className="error">
|
||||||
|
请选取 {min == max ? min : `${min}~${max}`} 个天赋
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
+26
-12
@@ -1,7 +1,7 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useSummary, useGameState } from '@remake/hooks'
|
import { useSummary, useGameState } from '@remake/hooks'
|
||||||
import type { Properties } from '@remake/hooks'
|
|
||||||
import { judge, judgeGrade } from '@/config'
|
import { judge, judgeGrade } from '@/config'
|
||||||
|
import type { Judges } from '@/config'
|
||||||
import { keys } from '@remake/vitex'
|
import { keys } from '@remake/vitex'
|
||||||
|
|
||||||
export interface Judge {
|
export interface Judge {
|
||||||
@@ -9,22 +9,36 @@ export interface Judge {
|
|||||||
level: number
|
level: number
|
||||||
grade: number
|
grade: number
|
||||||
}
|
}
|
||||||
export type JudgePropKeys = keyof Properties
|
function judges<T extends Judges, K extends keyof Record<T, number> = never>(
|
||||||
export type JudgeKeys = 'summary' | JudgePropKeys
|
props: Record<T, number>,
|
||||||
export type Judges = [JudgeKeys, Judge][]
|
filter?: K[],
|
||||||
|
) {
|
||||||
|
const judges: [Exclude<T, K>, Judge][] = []
|
||||||
|
for (const key of keys(props, filter)) {
|
||||||
|
const value = props[key]
|
||||||
|
const level = judge(key, value)
|
||||||
|
const grade = judgeGrade(key, level)
|
||||||
|
judges.push([key, { value, level, grade }])
|
||||||
|
}
|
||||||
|
return judges
|
||||||
|
}
|
||||||
|
|
||||||
export const useJudge = () => {
|
export const useJudge = () => {
|
||||||
|
const state = useGameState()
|
||||||
|
if (!state) throw new Error('Game state is not set')
|
||||||
|
return useMemo(() => {
|
||||||
|
const result = judges(state.props.current, ['age'])
|
||||||
|
return result
|
||||||
|
}, [state.props.current])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useEndJudge = () => {
|
||||||
const summary = useSummary()
|
const summary = useSummary()
|
||||||
const state = useGameState()
|
const state = useGameState()
|
||||||
if (!state) throw new Error('Game state is not set')
|
if (!state) throw new Error('Game state is not set')
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const props = { ...state.props.highest, summary }
|
const props = { ...state.props.highest, summary }
|
||||||
const judges: Judges = []
|
const result = judges(props)
|
||||||
for (const key of keys(props)) {
|
return result
|
||||||
const value = props[key]
|
|
||||||
const level = judge(key, value)
|
|
||||||
const grade = judgeGrade(key, level)
|
|
||||||
judges.push([key, { value, level, grade }])
|
|
||||||
}
|
|
||||||
return judges
|
|
||||||
}, [state.props.highest, summary])
|
}, [state.props.highest, summary])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ if (localStorage.getItem('version') !== '3.0.0') {
|
|||||||
const lockedTalents = JSON.parse(
|
const lockedTalents = JSON.parse(
|
||||||
localStorage.getItem('extendTalent') || 'null',
|
localStorage.getItem('extendTalent') || 'null',
|
||||||
)
|
)
|
||||||
if (lockedTalents) profile.lockedTalent = lockedTalents
|
if (lockedTalents) profile.locked = [lockedTalents]
|
||||||
const unique = JSON.parse(
|
const unique = JSON.parse(
|
||||||
localStorage.getItem('uniqueWaTaShi') || 'null',
|
localStorage.getItem('uniqueWaTaShi') || 'null',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,9 +8,10 @@
|
|||||||
--color-grade-2: light-dark(#b03fe4, #e09eff);
|
--color-grade-2: light-dark(#b03fe4, #e09eff);
|
||||||
--color-grade-3: light-dark(#ffaf2d, #ffe48d);
|
--color-grade-3: light-dark(#ffaf2d, #ffe48d);
|
||||||
|
|
||||||
--color-primary: light-dark(#61b700, #b9ff69);
|
--color-success: light-dark(#61b700, #b9ff69);
|
||||||
--color-info: light-dark(#454ad3, #b5b7ff);
|
--color-info: light-dark(#454ad3, #b5b7ff);
|
||||||
--color-error: light-dark(#e42b2b, #ff6969);
|
--color-error: light-dark(#e42b2b, #ff6969);
|
||||||
|
--color-primary: var(--color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.grade-0 { --grade-color: var(--color-grade-0); }
|
.grade-0 { --grade-color: var(--color-grade-0); }
|
||||||
|
|||||||
@@ -1,8 +1,30 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
const dataSplitRule = {
|
||||||
|
test: /[\\/]packages[\\/]data[\\/]|@remake\/data/,
|
||||||
|
priority: 40,
|
||||||
|
name(id: string) {
|
||||||
|
const name = id.split(/[\/\\]/).pop()!
|
||||||
|
const baseName = name.substring(0, name.lastIndexOf('.'))
|
||||||
|
if (baseName && baseName !== 'index') return `data-${baseName}`
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
resolve: { tsconfigPaths: true },
|
resolve: { tsconfigPaths: true },
|
||||||
|
build: {
|
||||||
|
chunkSizeWarningLimit: 1500,
|
||||||
|
rolldownOptions: {
|
||||||
|
output: {
|
||||||
|
codeSplitting: {
|
||||||
|
minSize: 1024,
|
||||||
|
groups: [dataSplitRule],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
+19
-18
@@ -4,25 +4,26 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"author": "Vick Scarlet <vick@syaro.io>",
|
"author": "Vick Scarlet <vick@syaro.io>",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun --filter @remake/web dev",
|
"dev": "pnpm --filter @remake/web dev",
|
||||||
"build:web": "bun --filter @remake/web build",
|
"dev:prod": "pnpm --filter @remake/web dev:prod",
|
||||||
"build:data": "bun --filter @remake/data build",
|
"build:web": "pnpm --filter @remake/web build",
|
||||||
"preview": "bun --filter @remake/web preview",
|
"build:data": "pnpm --filter @remake/data build",
|
||||||
"start": "bun --filter @remake/console start",
|
"preview": "pnpm --filter @remake/web preview",
|
||||||
|
"start": "pnpm --filter @remake/console start",
|
||||||
"lint": "bunx --bun eslint",
|
"lint": "bunx --bun eslint",
|
||||||
"lint:console": "bun --filter @remake/console lint",
|
"lint:console": "pnpm --filter @remake/console lint",
|
||||||
"lint:web": "bun --filter @remake/web lint",
|
"lint:web": "pnpm --filter @remake/web lint",
|
||||||
"lint:core": "bun --filter @remake/core lint",
|
"lint:core": "pnpm --filter @remake/core lint",
|
||||||
"lint:data": "bun --filter @remake/data lint",
|
"lint:data": "pnpm --filter @remake/data lint",
|
||||||
"lint:condition": "bun --filter @remake/condition lint",
|
"lint:condition": "pnpm --filter @remake/condition lint",
|
||||||
"lint:hooks": "bun --filter @remake/hooks lint",
|
"lint:hooks": "pnpm --filter @remake/hooks lint",
|
||||||
"test": "bun --filter @remake/web test",
|
"test": "pnpm --filter @remake/web test",
|
||||||
"test:console": "bun --filter @remake/console test",
|
"test:console": "pnpm --filter @remake/console test",
|
||||||
"test:web": "bun --filter @remake/web test",
|
"test:web": "pnpm --filter @remake/web test",
|
||||||
"test:core": "bun --filter @remake/core test",
|
"test:core": "pnpm --filter @remake/core test",
|
||||||
"test:data": "bun --filter @remake/data test",
|
"test:data": "pnpm --filter @remake/data test",
|
||||||
"test:condition": "bun --filter @remake/condition test",
|
"test:condition": "pnpm --filter @remake/condition test",
|
||||||
"test:hooks": "bun --filter @remake/hooks test"
|
"test:hooks": "pnpm --filter @remake/hooks test"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "1.3.14",
|
"@types/bun": "1.3.14",
|
||||||
|
|||||||
@@ -102,10 +102,10 @@ export interface EndResult {
|
|||||||
export function end(
|
export function end(
|
||||||
state: GameState,
|
state: GameState,
|
||||||
profile: ProfileState,
|
profile: ProfileState,
|
||||||
lockedTalent?: Talent['id'],
|
locked?: Talent['id'][],
|
||||||
) {
|
) {
|
||||||
const ar = atr(Ao.End, state, profile)
|
const ar = atr(Ao.End, state, profile)
|
||||||
const p = nextProfile(profile, ar.state, lockedTalent)
|
const p = nextProfile(profile, ar.state, locked)
|
||||||
return { profile: p, achievements: ar.triggers }
|
return { profile: p, achievements: ar.triggers }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export type UniqueCharacter = Omit<Character, 'id' | 'name'>
|
|||||||
/** 持久化存储的数据 */
|
/** 持久化存储的数据 */
|
||||||
export interface ProfileState {
|
export interface ProfileState {
|
||||||
times: number // 游戏次数
|
times: number // 游戏次数
|
||||||
lockedTalent?: Talent['id'] // 继承的天赋
|
locked?: Talent['id'][] // 锁定的天赋
|
||||||
talents: Set<Talent['id']> // 拥有过的天赋
|
talents: Set<Talent['id']> // 拥有过的天赋
|
||||||
events: Set<Event['id']> // 触发过的事件
|
events: Set<Event['id']> // 触发过的事件
|
||||||
achievements: Set<Achievement['id']> // 达成的成就
|
achievements: Set<Achievement['id']> // 达成的成就
|
||||||
@@ -195,7 +195,7 @@ export function lowestProperties(a: Properties, b?: Properties): Properties {
|
|||||||
export function nextProfile(
|
export function nextProfile(
|
||||||
profile: ProfileState,
|
profile: ProfileState,
|
||||||
state: GameState,
|
state: GameState,
|
||||||
lockedTalent?: Talent['id'],
|
locked?: Talent['id'][],
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
times: profile.times + 1,
|
times: profile.times + 1,
|
||||||
@@ -204,6 +204,6 @@ export function nextProfile(
|
|||||||
achievements: profile.achievements.union(state.achievements),
|
achievements: profile.achievements.union(state.achievements),
|
||||||
highest: highestProperties(state.props.highest, profile.highest),
|
highest: highestProperties(state.props.highest, profile.highest),
|
||||||
lowest: lowestProperties(state.props.lowest, profile.lowest),
|
lowest: lowestProperties(state.props.lowest, profile.lowest),
|
||||||
lockedTalent: lockedTalent ?? profile.lockedTalent,
|
locked,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,10 +70,13 @@ export function pull(
|
|||||||
)
|
)
|
||||||
const result = []
|
const result = []
|
||||||
const map = new Map(Grades.map(g => [g, new Set(GradeMap.get(g))]))
|
const map = new Map(Grades.map(g => [g, new Set(GradeMap.get(g))]))
|
||||||
if (profile.lockedTalent) {
|
if (profile.locked) {
|
||||||
result.push(profile.lockedTalent)
|
result.push(...profile.locked)
|
||||||
const { grade } = talents.get(profile.lockedTalent)!
|
if (result.length >= options.count) return result
|
||||||
map.get(grade)!.delete(profile.lockedTalent)
|
for (const talent of result) {
|
||||||
|
const { grade } = talents.get(talent)!
|
||||||
|
map.get(grade)!.delete(talent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (let i = options.count - result.length; i > 0; i--) {
|
for (let i = options.count - result.length; i > 0; i--) {
|
||||||
const grade = pickWeight(rate, rng)! ?? 0
|
const grade = pickWeight(rate, rng)! ?? 0
|
||||||
|
|||||||
@@ -3,11 +3,21 @@ import { atom, useSetAtom, useAtomValue } from 'jotai'
|
|||||||
import type { PullOptions } from '@remake/core'
|
import type { PullOptions } from '@remake/core'
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
pick: number
|
/** 游戏锁定天赋数量 */
|
||||||
|
lock: number
|
||||||
|
/** 游戏可选天赋数量 */
|
||||||
|
max: number
|
||||||
|
/** 游戏最少选择天赋数量 */
|
||||||
|
min: number
|
||||||
|
/** 天赋抽取个数 */
|
||||||
pull: PullOptions
|
pull: PullOptions
|
||||||
|
/** 初始属性点 */
|
||||||
points: number
|
points: number
|
||||||
|
/** 单项属性点最大限制 */
|
||||||
allocate: number
|
allocate: number
|
||||||
|
/** 初始快乐 */
|
||||||
spirit: number
|
spirit: number
|
||||||
|
/** 模式选择限制 */
|
||||||
mode: number
|
mode: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-11
@@ -101,7 +101,7 @@ export const useNext = () => {
|
|||||||
}
|
}
|
||||||
return result.achievements
|
return result.achievements
|
||||||
}, [state, profile, setState])
|
}, [state, profile, setState])
|
||||||
return [{ state, logs, ended }, nexter] as const
|
return [{ logs, ended }, nexter] as const
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGotoSummary = () => {
|
export const useGotoSummary = () => {
|
||||||
@@ -120,20 +120,36 @@ export const useGotoSummary = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useEnd = () => {
|
export const useEnd = () => {
|
||||||
|
const { lock } = useConfig()
|
||||||
const [profile, setProfile] = useProfile()
|
const [profile, setProfile] = useProfile()
|
||||||
const [step, setStep] = useAtom(stepAtom)
|
const [step, setStep] = useAtom(stepAtom)
|
||||||
const state = useAtomValue(gameStateAtom)
|
const state = useAtomValue(gameStateAtom)
|
||||||
const reset = useGameReset()
|
const reset = useGameReset()
|
||||||
return useCallback(
|
const [locked, setLocked] = useState<Set<Talent['id']>>(new Set())
|
||||||
(talent?: Talent['id']) => {
|
const picker = useCallback(
|
||||||
if (!state)
|
(talent: Talent['id']) => {
|
||||||
throw new Error('Game state is not available or already ended.')
|
setLocked(prev => {
|
||||||
const result = end(state, profile, talent)
|
const next = new Set(prev)
|
||||||
setStep(Step.Idle)
|
if (next.has(talent)) {
|
||||||
setProfile(result.profile)
|
next.delete(talent)
|
||||||
reset()
|
return next
|
||||||
return result.achievements
|
}
|
||||||
|
if (next.size >= lock) return prev
|
||||||
|
next.add(talent)
|
||||||
|
return next
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[state, step, profile, setStep, setProfile, reset],
|
[setLocked],
|
||||||
)
|
)
|
||||||
|
const ender = useCallback(() => {
|
||||||
|
if (!state)
|
||||||
|
throw new Error('Game state is not available or already ended.')
|
||||||
|
const l = locked.size > 0 ? Array.from(locked) : undefined
|
||||||
|
const result = end(state, profile, l)
|
||||||
|
setStep(Step.Idle)
|
||||||
|
setProfile(result.profile)
|
||||||
|
reset()
|
||||||
|
return result.achievements
|
||||||
|
}, [state, step, profile, locked, setStep, setProfile, reset])
|
||||||
|
return [locked, picker, ender] as const
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,7 @@ export const useTalentPuller = () => {
|
|||||||
const [profile] = useProfile()
|
const [profile] = useProfile()
|
||||||
const [pulled, setPulled] = useState<Talent['id'][] | null>(null)
|
const [pulled, setPulled] = useState<Talent['id'][] | null>(null)
|
||||||
const puller = useCallback(
|
const puller = useCallback(
|
||||||
// (rng?: RNG) => setPulled(pull(p, profile, rng)),
|
(rng?: RNG) => setPulled(pull(p, profile, rng)),
|
||||||
(rng?: RNG) =>
|
|
||||||
setPulled([
|
|
||||||
1142, 1143, 1144, 1145, 1146, 1086, 1122, 1111, 1130, 1048,
|
|
||||||
1033,
|
|
||||||
]),
|
|
||||||
[p, profile, setPulled],
|
[p, profile, setPulled],
|
||||||
)
|
)
|
||||||
return [pulled, puller] as const
|
return [pulled, puller] as const
|
||||||
@@ -61,7 +56,7 @@ export type TalentPickerResult =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useTalentPicker = () => {
|
export const useTalentPicker = () => {
|
||||||
const { pick } = useConfig()
|
const { max } = useConfig()
|
||||||
const [picked, setPicked] = useAtom(pickedAtom)
|
const [picked, setPicked] = useAtom(pickedAtom)
|
||||||
const picker = useCallback(
|
const picker = useCallback(
|
||||||
(talent: Talent['id']): TalentPickerResult => {
|
(talent: Talent['id']): TalentPickerResult => {
|
||||||
@@ -75,29 +70,29 @@ export const useTalentPicker = () => {
|
|||||||
setPicked(next)
|
setPicked(next)
|
||||||
return { type: 'ok' }
|
return { type: 'ok' }
|
||||||
}
|
}
|
||||||
if (picked.size >= pick) return { type: 'not-enough' }
|
if (picked.size >= max) return { type: 'not-enough' }
|
||||||
const e = exclude(talent, picked)
|
const e = exclude(talent, picked)
|
||||||
if (e) return { type: 'exclude', talent: e }
|
if (e) return { type: 'exclude', talent: e }
|
||||||
const next = new Set([...picked, talent])
|
const next = new Set([...picked, talent])
|
||||||
setPicked(next)
|
setPicked(next)
|
||||||
return { type: 'ok' }
|
return { type: 'ok' }
|
||||||
},
|
},
|
||||||
[pick, picked, setPicked],
|
[max, picked, setPicked],
|
||||||
)
|
)
|
||||||
return [picked, picker] as const
|
return [picked, picker] as const
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSubmitIsEnable = () => {
|
export const useSubmitIsEnable = () => {
|
||||||
const { pick } = useConfig()
|
const { max, min } = useConfig()
|
||||||
const picked = useAtomValue(pickedAtom)
|
const picked = useAtomValue(pickedAtom)
|
||||||
const enabled = picked.size >= pick
|
const enabled = picked.size >= min && picked.size <= max
|
||||||
return [enabled, pick] as const
|
return { min, max, enabled } as const
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTalentSubmit = () => {
|
export const useTalentSubmit = () => {
|
||||||
const picked = useAtomValue(pickedAtom)
|
const picked = useAtomValue(pickedAtom)
|
||||||
const setReplaced = useSetAtom(replacedAtom)
|
const setReplaced = useSetAtom(replacedAtom)
|
||||||
const [enabled] = useSubmitIsEnable()
|
const { enabled } = useSubmitIsEnable()
|
||||||
const next = useSetStep()
|
const next = useSetStep()
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(rng?: RNG) => {
|
(rng?: RNG) => {
|
||||||
|
|||||||
Generated
-8
@@ -47,9 +47,6 @@ importers:
|
|||||||
'@remake/vitex':
|
'@remake/vitex':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/vitex
|
version: link:../../packages/vitex
|
||||||
classnames:
|
|
||||||
specifier: ^2.5.1
|
|
||||||
version: 2.5.1
|
|
||||||
jotai:
|
jotai:
|
||||||
specifier: ^2.20.2
|
specifier: ^2.20.2
|
||||||
version: 2.20.2(@types/react@19.2.18)(react@19.2.8)
|
version: 2.20.2(@types/react@19.2.18)(react@19.2.8)
|
||||||
@@ -672,9 +669,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
|
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
classnames@2.5.1:
|
|
||||||
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
@@ -1735,8 +1729,6 @@ snapshots:
|
|||||||
|
|
||||||
chai@6.2.2: {}
|
chai@6.2.2: {}
|
||||||
|
|
||||||
classnames@2.5.1: {}
|
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
|
|||||||
Reference in New Issue
Block a user