mirror of
https://github.com/VickScarlet/lifeRestart.git
synced 2026-08-30 02:24:42 +08:00
update: character
This commit is contained in:
@@ -32,4 +32,24 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
> .controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
> button { flex: 1 0; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.saving {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
user-select: none;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
&.active {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.1s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+28
-31
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useInit, useSaver } from '@/hooks/storage'
|
import { useInit, useWatcher } from '@/hooks/storage'
|
||||||
import { useStep } from '@remake/hooks'
|
import { useStep, Step } from '@remake/hooks'
|
||||||
import Home from '@/containers/Home'
|
import Home from '@/containers/Home'
|
||||||
import Mode from '@/containers/Mode'
|
import Mode from '@/containers/Mode'
|
||||||
import Chara from '@/containers/Chara'
|
import Chara from '@/containers/Chara'
|
||||||
@@ -8,37 +8,34 @@ import Pick from '@/containers/TalentPick'
|
|||||||
import Alloc from '@/containers/Alloc'
|
import Alloc from '@/containers/Alloc'
|
||||||
import Play from '@/containers/Play'
|
import Play from '@/containers/Play'
|
||||||
import Summary from '@/containers/Summary'
|
import Summary from '@/containers/Summary'
|
||||||
import { Loading } from './containers/Loading'
|
|
||||||
import './Game.css'
|
import './Game.css'
|
||||||
|
|
||||||
export function Game() {
|
export function Container() {
|
||||||
const [inited, init] = useInit()
|
/* prettier-ignore */
|
||||||
const saver = useSaver()
|
switch (useStep()) {
|
||||||
const [step, Step] = useStep()
|
case Step.Idle: return <Home />
|
||||||
useEffect(() => {
|
case Step.Mode: return <Mode />
|
||||||
if (!inited) init()
|
case Step.Chara: return <Chara />
|
||||||
}, [inited])
|
case Step.Pick: return <Pick />
|
||||||
useEffect(() => {
|
case Step.Alloc: return <Alloc />
|
||||||
saver()
|
case Step.Play: return <Play />
|
||||||
}, [saver])
|
case Step.Summary: return <Summary />
|
||||||
if (!inited) return <Loading />
|
default: return null
|
||||||
switch (step) {
|
|
||||||
case Step.Idle:
|
|
||||||
return <Home />
|
|
||||||
case Step.Mode:
|
|
||||||
return <Mode />
|
|
||||||
case Step.Chara:
|
|
||||||
return <Chara />
|
|
||||||
case Step.Pick:
|
|
||||||
return <Pick />
|
|
||||||
case Step.Alloc:
|
|
||||||
return <Alloc />
|
|
||||||
case Step.Play:
|
|
||||||
return <Play />
|
|
||||||
case Step.Summary:
|
|
||||||
return <Summary />
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* prettier-ignore */
|
||||||
|
const Loading = () => <div className="screen loading"><h2>载入中...</h2></div>
|
||||||
|
const Saving = ({ active }: { active: boolean }) => (
|
||||||
|
<div className={`saving ${active ? 'active' : ''}`}>保存中...</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
/* prettier-ignore */
|
||||||
|
export function Game() {
|
||||||
|
const [inited, init] = useInit()
|
||||||
|
const saving = useWatcher()
|
||||||
|
useEffect(() => { if (!inited) init() }, [inited, init])
|
||||||
|
if (!inited) return <Loading />
|
||||||
|
return <><Container /><Saving active={saving} /></>
|
||||||
|
}
|
||||||
export default Game
|
export default Game
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import './Talent.css'
|
|||||||
|
|
||||||
export interface TalentProps {
|
export interface TalentProps {
|
||||||
id: Talent['id']
|
id: Talent['id']
|
||||||
selected: boolean
|
selected?: boolean
|
||||||
}
|
}
|
||||||
export function TalentComponent({ id, selected }: TalentProps) {
|
export function TalentComponent({ id, selected }: TalentProps) {
|
||||||
const talent = talents.get(id)
|
const talent = talents.get(id)
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ export const PullCount = dev.pull ?? 10
|
|||||||
export const CharacterPullCount = dev.chara ?? 3
|
export const CharacterPullCount = dev.chara ?? 3
|
||||||
// 名人模式抽取权重切线
|
// 名人模式抽取权重切线
|
||||||
export const CharacterWeightKnife = dev.knife ?? 10
|
export const CharacterWeightKnife = dev.knife ?? 10
|
||||||
|
// 唯一角色生成抽卡次数限制
|
||||||
|
export const UniqueLimit = dev.unique ?? 10
|
||||||
|
|
||||||
// 天赋抽取基础概率
|
// 天赋抽取基础概率
|
||||||
export const PullRateBase: Config['pull']['rate']['base'] = new Map([
|
export const PullRateBase: Config['pull']['rate']['base'] = new Map([
|
||||||
@@ -103,6 +105,19 @@ export const PullRateAdditions: Config['pull']['rate']['additions'] = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 权重生成器
|
||||||
|
function wg(s: number, e: number) {
|
||||||
|
const length = e - s + 1
|
||||||
|
return Array.from(
|
||||||
|
{ length },
|
||||||
|
(_, i) => [s + i, Math.min(i + 1, length - i)] as const,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 唯一角色属性分配权重
|
||||||
|
export const UniquePropWeight = wg(0, 10)
|
||||||
|
// 唯一角色天赋个数权重
|
||||||
|
export const UniqueTalentWeight = wg(1, 5)
|
||||||
|
|
||||||
export const config: Config = {
|
export const config: Config = {
|
||||||
lock: LockLimit,
|
lock: LockLimit,
|
||||||
mode: ModeLimit,
|
mode: ModeLimit,
|
||||||
@@ -122,6 +137,13 @@ export const config: Config = {
|
|||||||
count: CharacterPullCount,
|
count: CharacterPullCount,
|
||||||
knife: CharacterWeightKnife,
|
knife: CharacterWeightKnife,
|
||||||
},
|
},
|
||||||
|
unique: {
|
||||||
|
limit: UniqueLimit,
|
||||||
|
config: {
|
||||||
|
prop: UniquePropWeight,
|
||||||
|
talent: UniqueTalentWeight,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
|
|||||||
@@ -109,14 +109,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
gap: 0.5rem;
|
|
||||||
button { flex: 1 0; }
|
|
||||||
}
|
|
||||||
> ul.talent-list {
|
> ul.talent-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export function Alloc() {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<div>
|
<div className="controls">
|
||||||
<button className="info" onClick={() => random()}>
|
<button className="info" onClick={() => random()}>
|
||||||
随机分配
|
随机分配
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1 +1,90 @@
|
|||||||
.screen.chara {}
|
.screen.chara {
|
||||||
|
gap: 1rem;
|
||||||
|
> .chara-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
> .character {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: 0.0625rem solid var(--base-text-color);
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
> .generator > ul > li { text-align: center; }
|
||||||
|
> .name {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
&.selected {
|
||||||
|
border: 0.0625rem solid var(--color-primary);
|
||||||
|
> .name {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: var(--base-background-color);
|
||||||
|
}
|
||||||
|
> .chara-details {
|
||||||
|
height: auto;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.chara-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 0.0625rem solid var(--base-text-color);
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 0;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
overflow-y: hidden;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
interpolate-size: allow-keywords;
|
||||||
|
> ul.talent-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
> ul.properties {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
font-weight: bold;
|
||||||
|
> li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1 0;
|
||||||
|
justify-content: space-between;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 6rem;
|
||||||
|
user-select: none;
|
||||||
|
color: var(--grade-color);
|
||||||
|
span {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: 0.0625rem solid var(--grade-color);
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 2rem;
|
||||||
|
&:first-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
color: var(--base-background-color);
|
||||||
|
background-color: var(--grade-color);
|
||||||
|
}
|
||||||
|
&.charm { order: 1 }
|
||||||
|
&.intelligence { order: 2 }
|
||||||
|
&.strength { order: 3 }
|
||||||
|
&.money { order: 4 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,126 @@
|
|||||||
|
import { useCharaPuller, useCharaPicker, convertProps } from '@remake/hooks'
|
||||||
|
import { useUnique, useUniqueGenerator, useCharaStart } from '@remake/hooks'
|
||||||
|
import type { BaseChara } from '@remake/hooks'
|
||||||
|
import { TalentComponent } from '@/components/Talent'
|
||||||
|
import { judgeGradeByValue } from '@/config'
|
||||||
|
import { characters } from '@remake/data'
|
||||||
|
import { properties } from '@/display'
|
||||||
|
import { keys } from '@remake/vitex'
|
||||||
import './Chara.css'
|
import './Chara.css'
|
||||||
|
|
||||||
|
function Details({ detail }: { detail: BaseChara }) {
|
||||||
|
const props = convertProps(detail.property)
|
||||||
|
return (
|
||||||
|
<div className="chara-details">
|
||||||
|
<ul className="properties">
|
||||||
|
{keys(props).map(key => (
|
||||||
|
<li
|
||||||
|
key={key}
|
||||||
|
className={`property ${key} grade-${judgeGradeByValue(key, props[key])}`}
|
||||||
|
>
|
||||||
|
<span>{properties[key]}</span>
|
||||||
|
<span className="font-mono">{props[key]}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<ul className="talent-list">
|
||||||
|
{detail.talent.map(id => (
|
||||||
|
<li key={id}>
|
||||||
|
<TalentComponent id={id} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UniqueProps {
|
||||||
|
selected?: boolean
|
||||||
|
picker?: () => void
|
||||||
|
}
|
||||||
|
function Unique({ selected, picker }: UniqueProps) {
|
||||||
|
const [unique, generator] = useUniqueGenerator()
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={`character ${selected ? 'selected' : ''}`}
|
||||||
|
onClick={picker}
|
||||||
|
>
|
||||||
|
<span className="name">独一无二的我</span>
|
||||||
|
{unique && <Details detail={unique} />}
|
||||||
|
{!unique && (
|
||||||
|
<div className="chara-details generator">
|
||||||
|
<ul>
|
||||||
|
<li>6000万玩家中独一无二的角色卡</li>
|
||||||
|
<li>所有属性 所有天赋 随机生成</li>
|
||||||
|
<li>每人只能生成一次</li>
|
||||||
|
</ul>
|
||||||
|
<button
|
||||||
|
className="primary focus"
|
||||||
|
onClick={() => generator()}
|
||||||
|
>
|
||||||
|
生成唯一角色
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CharacterProps {
|
||||||
|
id: number
|
||||||
|
selected?: boolean
|
||||||
|
picker?: (id: number) => void
|
||||||
|
}
|
||||||
|
function Character({ id, selected, picker }: CharacterProps) {
|
||||||
|
const character = characters.get(id)!
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={`character ${selected ? 'selected' : ''}`}
|
||||||
|
onClick={() => picker?.(id)}
|
||||||
|
>
|
||||||
|
<span className="name">{character.name}</span>
|
||||||
|
<Details detail={character} />
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function Chara() {
|
export function Chara() {
|
||||||
return <div className="screen chara"></div>
|
const u = useUnique()
|
||||||
|
const [{ unique, characters }, puller] = useCharaPuller()
|
||||||
|
const [picked, picker] = useCharaPicker()
|
||||||
|
const start = useCharaStart()
|
||||||
|
const ready = picked && (picked.type === 'unique' ? !!u : true)
|
||||||
|
return (
|
||||||
|
<div className="screen chara">
|
||||||
|
<ul className="chara-list">
|
||||||
|
{unique && (
|
||||||
|
<Unique
|
||||||
|
selected={picked?.type === 'unique'}
|
||||||
|
picker={() => picker.unique()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{characters.map(id => (
|
||||||
|
<Character
|
||||||
|
key={id}
|
||||||
|
id={id}
|
||||||
|
selected={picked?.id === id}
|
||||||
|
picker={id => picker.chara(id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<div className="controls">
|
||||||
|
<button className="info" onClick={puller}>
|
||||||
|
都不是
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={ready ? 'primary' : 'error'}
|
||||||
|
onClick={ready ? () => start(picked!) : undefined}
|
||||||
|
>
|
||||||
|
开始新人生
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Chara
|
export default Chara
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
export function Loading() {
|
|
||||||
return (
|
|
||||||
<div className="screen loading">
|
|
||||||
<h2>载入中...</h2>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Loading
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef } from 'react'
|
import { useState, useRef, useCallback } from 'react'
|
||||||
import { useLayoutEffect, useEffect } from 'react'
|
import { useLayoutEffect, useEffect } from 'react'
|
||||||
import { useNext, useGotoSummary, type Log } from '@remake/hooks'
|
import { useNext, useGotoSummary, type Log } from '@remake/hooks'
|
||||||
import { useJudge } from '@/hooks/judge'
|
import { useJudge } from '@/hooks/judge'
|
||||||
@@ -100,12 +100,12 @@ function Prop({ prop, value, grade }: PropProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const prev = prevRef.current
|
const prev = prevRef.current
|
||||||
if (value === prev) return
|
if (value === prev) return
|
||||||
|
const startValue = prevRef.current
|
||||||
prevRef.current = value
|
prevRef.current = value
|
||||||
setTrend(value > prev ? 'up' : 'down')
|
setTrend(value > prev ? 'up' : 'down')
|
||||||
setFlip(f => (f + 1) % 2)
|
setFlip(f => (f + 1) % 2)
|
||||||
let startTimestamp: number | null = null
|
let startTimestamp: number | null = null
|
||||||
const duration = 400
|
const duration = 400
|
||||||
const startValue = displayValue
|
|
||||||
let end = false
|
let end = false
|
||||||
const step = (timestamp: number) => {
|
const step = (timestamp: number) => {
|
||||||
if (!startTimestamp) startTimestamp = timestamp
|
if (!startTimestamp) startTimestamp = timestamp
|
||||||
@@ -150,18 +150,18 @@ export function Play() {
|
|||||||
const logRef = useRef<HTMLUListElement>(null)
|
const logRef = useRef<HTMLUListElement>(null)
|
||||||
const autoRef = useRef(0)
|
const autoRef = useRef(0)
|
||||||
const gotoSummary = useGotoSummary()
|
const gotoSummary = useGotoSummary()
|
||||||
const handleNext = () => {
|
const handleNext = useCallback(() => {
|
||||||
if (ended) return
|
if (ended) return
|
||||||
const achievements = next()
|
const achievements = next()
|
||||||
// TODO: Show achievements
|
// TODO: Show achievements
|
||||||
console.debug('Achievements:', achievements)
|
console.debug('Achievements:', achievements)
|
||||||
}
|
}, [ended, next])
|
||||||
const handleGotoSummary = () => {
|
const handleGotoSummary = useCallback(() => {
|
||||||
if (!ended) return
|
if (!ended) return
|
||||||
const achievements = gotoSummary()
|
const achievements = gotoSummary()
|
||||||
// TODO: Show achievements
|
// TODO: Show achievements
|
||||||
console.debug('Achievements:', achievements)
|
console.debug('Achievements:', achievements)
|
||||||
}
|
}, [ended, gotoSummary])
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!logRef.current) return
|
if (!logRef.current) return
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function TalentList({ picked, picker }: TalentListProps) {
|
|||||||
}
|
}
|
||||||
hasInitialized.current = true
|
hasInitialized.current = true
|
||||||
}
|
}
|
||||||
}, [picked, picker])
|
}, [profile.locked, talents, picked, picker])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="talent-list">
|
<ul className="talent-list">
|
||||||
|
|||||||
@@ -26,19 +26,21 @@ function judges<T extends Judges, K extends keyof Record<T, number> = never>(
|
|||||||
export const useJudge = () => {
|
export const useJudge = () => {
|
||||||
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')
|
||||||
|
const current = state.props.current
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const result = judges(state.props.current, ['age'])
|
const result = judges(current, ['age'])
|
||||||
return result
|
return result
|
||||||
}, [state.props.current])
|
}, [current])
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useEndJudge = () => {
|
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')
|
||||||
|
const highest = state.props.highest
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const props = { ...state.props.highest, summary }
|
const props = { ...highest, summary }
|
||||||
const result = judges(props)
|
const result = judges(props)
|
||||||
return result
|
return result
|
||||||
}, [state.props.highest, summary])
|
}, [highest, summary])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback } from 'react'
|
import { useEffect, useCallback, useTransition } from 'react'
|
||||||
import { atom, useAtom } from 'jotai'
|
import { atom, useAtom } from 'jotai'
|
||||||
import { useConfigInject, useRawProfile, useProfileInject } from '@remake/hooks'
|
import { useConfigInject, useProfileInject, useRawProfile } from '@remake/hooks'
|
||||||
|
import { useUniqueInject, useUnique } from '@remake/hooks'
|
||||||
import { get, set } from '@/storage'
|
import { get, set } from '@/storage'
|
||||||
import { config } from '@/config'
|
import { config } from '@/config'
|
||||||
|
|
||||||
@@ -9,11 +10,12 @@ const initedAtom = atom(false)
|
|||||||
export const useInit = () => {
|
export const useInit = () => {
|
||||||
const configInject = useConfigInject()
|
const configInject = useConfigInject()
|
||||||
const profileInject = useProfileInject()
|
const profileInject = useProfileInject()
|
||||||
|
const uniqueInject = useUniqueInject()
|
||||||
const [inited, setInited] = useAtom(initedAtom)
|
const [inited, setInited] = useAtom(initedAtom)
|
||||||
const loader = useCallback(async () => {
|
const loader = useCallback(async () => {
|
||||||
if (inited) return
|
if (inited) return
|
||||||
configInject(config)
|
configInject(config)
|
||||||
const { profile } = await get(['profile'])
|
const { profile, unique } = await get(['profile', 'unique'])
|
||||||
const parsed = profile ? JSON.parse(profile) || {} : {}
|
const parsed = profile ? JSON.parse(profile) || {} : {}
|
||||||
profileInject({
|
profileInject({
|
||||||
...parsed,
|
...parsed,
|
||||||
@@ -22,15 +24,19 @@ export const useInit = () => {
|
|||||||
events: new Set(parsed.events || []),
|
events: new Set(parsed.events || []),
|
||||||
talents: new Set(parsed.talents || []),
|
talents: new Set(parsed.talents || []),
|
||||||
})
|
})
|
||||||
|
if (unique) uniqueInject(JSON.parse(unique))
|
||||||
setInited(true)
|
setInited(true)
|
||||||
}, [inited, configInject, profileInject, setInited])
|
}, [inited, configInject, profileInject, uniqueInject, setInited])
|
||||||
return [inited, loader] as const
|
return [inited, loader] as const
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSaver = () => {
|
export const useWatcher = () => {
|
||||||
const [profile] = useRawProfile()
|
const [profile] = useRawProfile()
|
||||||
|
const unique = useUnique()
|
||||||
const [inited] = useAtom(initedAtom)
|
const [inited] = useAtom(initedAtom)
|
||||||
return useCallback(async () => {
|
const [p, saveProfile] = useTransition()
|
||||||
|
const [u, saveUnique] = useTransition()
|
||||||
|
useEffect(() => {
|
||||||
if (!inited || !profile) return
|
if (!inited || !profile) return
|
||||||
const str = JSON.stringify({
|
const str = JSON.stringify({
|
||||||
...profile,
|
...profile,
|
||||||
@@ -39,6 +45,17 @@ export const useSaver = () => {
|
|||||||
events: Array.from(profile.events),
|
events: Array.from(profile.events),
|
||||||
talents: Array.from(profile.talents),
|
talents: Array.from(profile.talents),
|
||||||
})
|
})
|
||||||
return await set({ profile: str })
|
saveProfile(async () => {
|
||||||
}, [inited, profile])
|
await set({ profile: str })
|
||||||
|
})
|
||||||
|
}, [inited, profile, saveProfile])
|
||||||
|
useEffect(() => {
|
||||||
|
if (!inited || !unique) return
|
||||||
|
const str = JSON.stringify(unique)
|
||||||
|
saveUnique(async () => {
|
||||||
|
await set({ unique: str })
|
||||||
|
})
|
||||||
|
}, [inited, unique, saveUnique])
|
||||||
|
|
||||||
|
return p || u
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test, describe } from 'bun:test'
|
import { expect, test, describe } from 'bun:test'
|
||||||
import { pullCharacter, uniqueGenerate } from './character'
|
import { pullChara, uniqueGenerate } from './character'
|
||||||
import { enableMapSet } from 'immer'
|
import { enableMapSet } from 'immer'
|
||||||
enableMapSet()
|
enableMapSet()
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ describe('Character', () => {
|
|||||||
const sum = (map: Map<any, number>) =>
|
const sum = (map: Map<any, number>) =>
|
||||||
Array.from(map.values()).reduce((acc, val) => acc + val, 0)
|
Array.from(map.values()).reduce((acc, val) => acc + val, 0)
|
||||||
test('pull', () => {
|
test('pull', () => {
|
||||||
let result = pullCharacter(
|
let result = pullChara(
|
||||||
{ count: 10, knife: 10 },
|
{ count: 10, knife: 10 },
|
||||||
{ times: 0, drawns: new Map() },
|
{ times: 0, drawns: new Map() },
|
||||||
)
|
)
|
||||||
@@ -15,17 +15,17 @@ describe('Character', () => {
|
|||||||
expect(result.characters.length).toBe(10)
|
expect(result.characters.length).toBe(10)
|
||||||
expect(result.times.times).toBe(10)
|
expect(result.times.times).toBe(10)
|
||||||
expect(result.times.drawns.size).toBe(10)
|
expect(result.times.drawns.size).toBe(10)
|
||||||
result = pullCharacter({ count: 3, knife: 10 }, result.times)
|
result = pullChara({ count: 3, knife: 10 }, result.times)
|
||||||
expect(result.characters).not.toContainValue(null)
|
expect(result.characters).not.toContainValue(null)
|
||||||
expect(result.characters.length).toBe(3)
|
expect(result.characters.length).toBe(3)
|
||||||
expect(result.times.times).toBe(13)
|
expect(result.times.times).toBe(13)
|
||||||
expect(sum(result.times.drawns)).toBe(13)
|
expect(sum(result.times.drawns)).toBe(13)
|
||||||
result = pullCharacter({ count: 3, knife: 10 }, result.times)
|
result = pullChara({ count: 3, knife: 10 }, result.times)
|
||||||
expect(result.characters).not.toContainValue(null)
|
expect(result.characters).not.toContainValue(null)
|
||||||
expect(result.characters.length).toBe(3)
|
expect(result.characters.length).toBe(3)
|
||||||
expect(result.times.times).toBe(16)
|
expect(result.times.times).toBe(16)
|
||||||
expect(sum(result.times.drawns)).toBe(16)
|
expect(sum(result.times.drawns)).toBe(16)
|
||||||
result = pullCharacter({ count: 20, knife: 1 }, result.times)
|
result = pullChara({ count: 20, knife: 1 }, result.times)
|
||||||
expect(result.characters).not.toContainValue(null)
|
expect(result.characters).not.toContainValue(null)
|
||||||
expect(result.characters.length).toBe(20)
|
expect(result.characters.length).toBe(20)
|
||||||
expect(result.times.times).toBe(36)
|
expect(result.times.times).toBe(36)
|
||||||
@@ -56,6 +56,5 @@ describe('Character', () => {
|
|||||||
expect(unique.property.STR).toBeLessThanOrEqual(10)
|
expect(unique.property.STR).toBeLessThanOrEqual(10)
|
||||||
expect(unique.property.MNY).toBeLessThanOrEqual(10)
|
expect(unique.property.MNY).toBeLessThanOrEqual(10)
|
||||||
expect(unique.talent.length).toBeLessThanOrEqual(5)
|
expect(unique.talent.length).toBeLessThanOrEqual(5)
|
||||||
console.debug(unique)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { type Character, type Talent, characters, talents } from '@remake/data'
|
import type { Character, CharacterProperty, Talent } from '@remake/data'
|
||||||
|
import { characters, talents } from '@remake/data'
|
||||||
import { type RNG, type WeightItem, pick, pickWeight } from '@remake/vitex'
|
import { type RNG, type WeightItem, pick, pickWeight } from '@remake/vitex'
|
||||||
|
import type { Allocation } from './state'
|
||||||
|
|
||||||
export type UniqueChara = Omit<Character, 'id' | 'name'>
|
export type BaseChara = Omit<Character, 'id' | 'name'>
|
||||||
|
export type BaseAlloc = Omit<Allocation, 'spirit'>
|
||||||
export interface UniqueGenCfg {
|
export interface UniqueGenCfg {
|
||||||
prop: WeightItem<number>[]
|
prop: WeightItem<number>[]
|
||||||
talent: WeightItem<number>[]
|
talent: WeightItem<number>[]
|
||||||
@@ -22,7 +25,7 @@ export function uniqueGenerate(config: UniqueGenCfg, rng?: RNG) {
|
|||||||
picked.add(t)
|
picked.add(t)
|
||||||
count--
|
count--
|
||||||
}
|
}
|
||||||
return { property, talent: Array.from(picked) } satisfies UniqueChara
|
return { property, talent: Array.from(picked) } satisfies BaseChara
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PullCharaOpt {
|
export interface PullCharaOpt {
|
||||||
@@ -37,9 +40,9 @@ export interface PullCharaRet {
|
|||||||
characters: Character['id'][]
|
characters: Character['id'][]
|
||||||
times: PullCharaTms
|
times: PullCharaTms
|
||||||
}
|
}
|
||||||
export function pullCharacter(
|
export function pullChara(
|
||||||
opt: PullCharaOpt,
|
opt: PullCharaOpt,
|
||||||
tms: PullCharaTms,
|
tms: PullCharaTms = { times: 0, drawns: new Map() },
|
||||||
rng?: RNG,
|
rng?: RNG,
|
||||||
): PullCharaRet {
|
): PullCharaRet {
|
||||||
const { count, knife } = opt
|
const { count, knife } = opt
|
||||||
@@ -69,3 +72,34 @@ function deriveWeightMap(
|
|||||||
Array.from(characters.keys(), id => [id, base - (drawns.get(id) ?? 0)]),
|
Array.from(characters.keys(), id => [id, base - (drawns.get(id) ?? 0)]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function charaPropToBaseAlloc(props: CharacterProperty): BaseAlloc {
|
||||||
|
return {
|
||||||
|
charm: props.CHR,
|
||||||
|
intelligence: props.INT,
|
||||||
|
strength: props.STR,
|
||||||
|
money: props.MNY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function charaPropToAlloc(
|
||||||
|
props: CharacterProperty,
|
||||||
|
spirit: number,
|
||||||
|
): Allocation {
|
||||||
|
return { ...charaPropToBaseAlloc(props), spirit }
|
||||||
|
}
|
||||||
|
|
||||||
|
function convert(chara: BaseChara, spirit: number) {
|
||||||
|
return {
|
||||||
|
allocation: charaPropToAlloc(chara.property, spirit),
|
||||||
|
talents: chara.talent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startChara(id: Character['id'], spirit: number) {
|
||||||
|
return convert(characters.get(id)!, spirit)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startUnique(chara: BaseChara, spirit: number) {
|
||||||
|
return convert(chara, spirit)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
export type { RNG } from '@remake/vitex'
|
|
||||||
export type { GameState, ProfileState, Properties, Allocation } from './state'
|
export type { GameState, ProfileState, Properties, Allocation } from './state'
|
||||||
export type { PullOptions, ReplacementResult } from './talent'
|
export type { PullOptions, ReplacementResult } from './talent'
|
||||||
export type { AdditionalPoint, AdditionalPoints } from './talent'
|
export type { AdditionalPoint, AdditionalPoints } from './talent'
|
||||||
export type { PullCharaOpt, PullCharaTms, PullCharaRet } from './character'
|
export type { PullCharaOpt, PullCharaTms, PullCharaRet } from './character'
|
||||||
export type { UniqueChara, UniqueGenCfg } from './character'
|
export type { BaseChara, UniqueGenCfg } from './character'
|
||||||
export type { PickResult, StartResult } from './game'
|
export type { PickResult, StartResult } from './game'
|
||||||
export type { NextResult, SummaryResult, EndResult } from './game'
|
export type { NextResult, SummaryResult, EndResult } from './game'
|
||||||
export { pull, exclude } from './talent'
|
export { pull, exclude } from './talent'
|
||||||
|
export { uniqueGenerate, pullChara, charaPropToBaseAlloc } from './character'
|
||||||
|
export { startUnique, startChara } from './character'
|
||||||
export { pick, start, next, summary, end } from './game'
|
export { pick, start, next, summary, end } from './game'
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const useAllocator = () => {
|
|||||||
return { ...prev, [key]: final }
|
return { ...prev, [key]: final }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[allocate, total],
|
[allocate, total, setAlloc],
|
||||||
)
|
)
|
||||||
return [alloc, allocator] as const
|
return [alloc, allocator] as const
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ export const usePointRandomizer = () => {
|
|||||||
}
|
}
|
||||||
setAlloc(alloc)
|
setAlloc(alloc)
|
||||||
},
|
},
|
||||||
[allocate, total],
|
[allocate, total, setAlloc],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
import { useConfig, useProfile, useSetStep, useSetGameState, Step } from '.'
|
||||||
|
import { uniqueGenerate, startUnique, startChara } from '@remake/core'
|
||||||
|
import { pullChara, start, pick } from '@remake/core'
|
||||||
|
import type { BaseChara, PullCharaTms } from '@remake/core'
|
||||||
|
import type { Character } from '@remake/data'
|
||||||
|
import type { RNG } from '@remake/vitex'
|
||||||
|
|
||||||
|
export const uniqueAtom = atom<BaseChara | null>(null)
|
||||||
|
export const timesAtom = atom<PullCharaTms | undefined>(undefined)
|
||||||
|
|
||||||
|
export const useUnique = () => useAtomValue(uniqueAtom)
|
||||||
|
export const useUniqueInject = () => {
|
||||||
|
const setUnique = useSetAtom(uniqueAtom)
|
||||||
|
return useCallback((unique: BaseChara) => setUnique(unique), [setUnique])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUniqueGenerator = () => {
|
||||||
|
const { config } = useConfig().unique
|
||||||
|
const [unique, setUnique] = useAtom(uniqueAtom)
|
||||||
|
const generator = useCallback(
|
||||||
|
(rng?: RNG) => {
|
||||||
|
if (unique) return unique
|
||||||
|
const generated = uniqueGenerate(config, rng)
|
||||||
|
setUnique(generated)
|
||||||
|
return generated
|
||||||
|
},
|
||||||
|
[config, unique, setUnique],
|
||||||
|
)
|
||||||
|
return [unique, generator] as const
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PullCharaResult {
|
||||||
|
characters: Character['id'][]
|
||||||
|
unique: boolean
|
||||||
|
}
|
||||||
|
export const useCharaPuller = (rng?: RNG) => {
|
||||||
|
const { unique: u, chara } = useConfig()
|
||||||
|
const [unique] = useAtom(uniqueAtom)
|
||||||
|
const [times, setTimes] = useAtom(timesAtom)
|
||||||
|
const [pulled, setPulled] = useState<PullCharaResult>({
|
||||||
|
characters: pullChara(chara, times, rng).characters,
|
||||||
|
unique: !!unique,
|
||||||
|
})
|
||||||
|
const puller = useCallback(() => {
|
||||||
|
const result = pullChara(chara, times, rng)
|
||||||
|
setTimes(result.times)
|
||||||
|
setPulled({
|
||||||
|
characters: result.characters,
|
||||||
|
unique: !!unique || result.times?.times >= u.limit * chara.count,
|
||||||
|
})
|
||||||
|
}, [u, chara, unique, times, setTimes, rng])
|
||||||
|
return [pulled, puller] as const
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PickUnique = { type: 'unique'; id?: never }
|
||||||
|
export type PickCharacter = {
|
||||||
|
type: 'character'
|
||||||
|
id: Character['id']
|
||||||
|
}
|
||||||
|
export type CharaPick = PickUnique | PickCharacter
|
||||||
|
export const useCharaPicker = () => {
|
||||||
|
const [picked, setPicked] = useState<CharaPick | null>(null)
|
||||||
|
const chara = useCallback(
|
||||||
|
(id: Character['id']) => setPicked({ type: 'character', id }),
|
||||||
|
[setPicked],
|
||||||
|
)
|
||||||
|
const uni = useCallback(() => setPicked({ type: 'unique' }), [setPicked])
|
||||||
|
return [picked, { chara, unique: uni }] as const
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCharaStart = () => {
|
||||||
|
const { spirit } = useConfig()
|
||||||
|
const [profile] = useProfile()
|
||||||
|
const unique = useAtomValue(uniqueAtom)
|
||||||
|
const setStep = useSetStep()
|
||||||
|
const setState = useSetGameState()
|
||||||
|
return useCallback(
|
||||||
|
(picked: CharaPick, rng?: RNG) => {
|
||||||
|
let result
|
||||||
|
if (picked.type === 'unique') {
|
||||||
|
if (!unique)
|
||||||
|
throw new Error('Unique character not generated yet')
|
||||||
|
result = startUnique(unique, spirit)
|
||||||
|
} else {
|
||||||
|
result = startChara(picked.id, spirit)
|
||||||
|
}
|
||||||
|
const { talents, additionalPoints } = pick(result.talents, rng)
|
||||||
|
// TODO: additionalPoints
|
||||||
|
const { state, achievements } = start(
|
||||||
|
profile,
|
||||||
|
result.allocation,
|
||||||
|
talents.talents,
|
||||||
|
)
|
||||||
|
setState(state)
|
||||||
|
setStep(Step.Play)
|
||||||
|
return achievements
|
||||||
|
},
|
||||||
|
[spirit, profile, unique, setStep, setState],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { charaPropToBaseAlloc as convertProps } from '@remake/core'
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { atom, useSetAtom, useAtomValue } from 'jotai'
|
import { atom, useSetAtom, useAtomValue } from 'jotai'
|
||||||
import type { PullOptions, PullCharaOpt } from '@remake/core'
|
import type { PullOptions, PullCharaOpt, UniqueGenCfg } from '@remake/core'
|
||||||
export interface Config {
|
export interface Config {
|
||||||
/** 游戏锁定天赋数量 */
|
/** 游戏锁定天赋数量 */
|
||||||
lock: number
|
lock: number
|
||||||
@@ -20,6 +20,11 @@ export interface Config {
|
|||||||
pull: PullOptions
|
pull: PullOptions
|
||||||
/** 名人模式配置 */
|
/** 名人模式配置 */
|
||||||
chara: PullCharaOpt
|
chara: PullCharaOpt
|
||||||
|
/** 唯一角色限制 */
|
||||||
|
unique: {
|
||||||
|
limit: number
|
||||||
|
config: UniqueGenCfg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const configAtom = atom<Config | null>(null)
|
export const configAtom = atom<Config | null>(null)
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ export * from './profile'
|
|||||||
export * from './talent'
|
export * from './talent'
|
||||||
export * from './play'
|
export * from './play'
|
||||||
export * from './alloc'
|
export * from './alloc'
|
||||||
|
export * from './character'
|
||||||
export type * from '@remake/core'
|
export type * from '@remake/core'
|
||||||
|
|||||||
+13
-12
@@ -25,6 +25,7 @@ export type Log = Omit<NextResult, 'state'> & {
|
|||||||
props: Omit<Properties, 'age'>
|
props: Omit<Properties, 'age'>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const modeAtom = atom<Mode>(Mode.Classic)
|
||||||
export const stepAtom = atom<Step>(Step.Idle)
|
export const stepAtom = atom<Step>(Step.Idle)
|
||||||
export const gameStateAtom = atom<GameState | null>(null)
|
export const gameStateAtom = atom<GameState | null>(null)
|
||||||
export const logsAtom = atom<Log[]>([])
|
export const logsAtom = atom<Log[]>([])
|
||||||
@@ -51,9 +52,10 @@ export const useGameReset = () => {
|
|||||||
}, [resetTalent, resetAlloc, resetState, resetLogs])
|
}, [resetTalent, resetAlloc, resetState, resetLogs])
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useStep = () => [useAtomValue(stepAtom), Step] as const
|
export const useStep = () => useAtomValue(stepAtom)
|
||||||
export const useSetStep = () => [useSetAtom(stepAtom), Step] as const
|
export const useSetStep = () => useSetAtom(stepAtom)
|
||||||
export const useGameState = () => useAtomValue(gameStateAtom)
|
export const useGameState = () => useAtomValue(gameStateAtom)
|
||||||
|
export const useSetGameState = () => useSetAtom(gameStateAtom)
|
||||||
export const useSummary = () => useAtomValue(summaryAtom)
|
export const useSummary = () => useAtomValue(summaryAtom)
|
||||||
export const useLogs = () => useAtomValue(logsAtom)
|
export const useLogs = () => useAtomValue(logsAtom)
|
||||||
|
|
||||||
@@ -69,16 +71,15 @@ export const useRemake = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useModeChoose = () => {
|
export const useModeChoose = () => {
|
||||||
|
const setMode = useSetAtom(modeAtom)
|
||||||
const setStep = useSetAtom(stepAtom)
|
const setStep = useSetAtom(stepAtom)
|
||||||
const choose = useCallback(
|
const choose = useCallback(
|
||||||
(mode: Mode) => {
|
(mode: Mode) => {
|
||||||
|
setMode(mode)
|
||||||
|
/* prettier-ignore */
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case Mode.Classic:
|
case Mode.Classic: return setStep(Step.Pick)
|
||||||
setStep(Step.Pick)
|
case Mode.Celebrity: return setStep(Step.Chara)
|
||||||
break
|
|
||||||
case Mode.Celebrity:
|
|
||||||
setStep(Step.Chara)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setStep],
|
[setStep],
|
||||||
@@ -122,7 +123,7 @@ export const useNext = () => {
|
|||||||
setEnded(true)
|
setEnded(true)
|
||||||
}
|
}
|
||||||
return result.achievements
|
return result.achievements
|
||||||
}, [state, profile, setState])
|
}, [state, profile, setState, setLogs])
|
||||||
return [{ logs, ended }, nexter] as const
|
return [{ logs, ended }, nexter] as const
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +145,7 @@ export const useGotoSummary = () => {
|
|||||||
export const useEnd = () => {
|
export const useEnd = () => {
|
||||||
const { lock } = useConfig()
|
const { lock } = useConfig()
|
||||||
const [profile, setProfile] = useProfile()
|
const [profile, setProfile] = useProfile()
|
||||||
const [step, setStep] = useAtom(stepAtom)
|
const setStep = useSetAtom(stepAtom)
|
||||||
const state = useAtomValue(gameStateAtom)
|
const state = useAtomValue(gameStateAtom)
|
||||||
const reset = useGameReset()
|
const reset = useGameReset()
|
||||||
const [locked, setLocked] = useState<Set<Talent['id']>>(new Set())
|
const [locked, setLocked] = useState<Set<Talent['id']>>(new Set())
|
||||||
@@ -163,7 +164,7 @@ export const useEnd = () => {
|
|||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[setLocked],
|
[lock, setLocked],
|
||||||
)
|
)
|
||||||
const ender = useCallback(() => {
|
const ender = useCallback(() => {
|
||||||
if (!state)
|
if (!state)
|
||||||
@@ -174,6 +175,6 @@ export const useEnd = () => {
|
|||||||
setProfile(result.profile)
|
setProfile(result.profile)
|
||||||
reset()
|
reset()
|
||||||
return result.achievements
|
return result.achievements
|
||||||
}, [state, step, profile, locked, setStep, setProfile, reset])
|
}, [state, profile, locked, setStep, setProfile, reset])
|
||||||
return [locked, picker, ender] as const
|
return [locked, picker, ender] as const
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useCallback } from 'react'
|
import { useState, useCallback } from 'react'
|
||||||
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
|
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||||
import { useConfig, useProfile, useSetStep } from '.'
|
import { useConfig, useProfile, useSetStep, Step } from '.'
|
||||||
import { pull, exclude, pick, type PickResult } from '@remake/core'
|
import { pull, exclude, pick, type PickResult } from '@remake/core'
|
||||||
import type { Talent } from '@remake/data'
|
import type { Talent } from '@remake/data'
|
||||||
import type { RNG } from '@remake/vitex'
|
import type { RNG } from '@remake/vitex'
|
||||||
@@ -82,7 +82,7 @@ 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 [setStep, Step] = useSetStep()
|
const setStep = useSetStep()
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(rng?: RNG) => {
|
(rng?: RNG) => {
|
||||||
if (!enabled) throw new Error('Not enough talents picked')
|
if (!enabled) throw new Error('Not enough talents picked')
|
||||||
|
|||||||
Reference in New Issue
Block a user