mirror of
https://github.com/VickScarlet/lifeRestart.git
synced 2026-08-28 01:06:49 +08:00
update: character allocation
This commit is contained in:
+60
-22
@@ -1,8 +1,9 @@
|
||||
import { useCallback } from 'react'
|
||||
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useConfig, useReplaced } from '.'
|
||||
import { atom, useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useConfig, useReplaced, useIsClassic } from '.'
|
||||
import type { Allocation } from '@remake/core'
|
||||
import { keys, shuffle, random as frandom, type RNG } from '@remake/vitex'
|
||||
import { keys, shuffle, zoneFit, random } from '@remake/vitex'
|
||||
import type { RNG } from '@remake/vitex'
|
||||
|
||||
export type UserAllocation = Omit<Allocation, 'spirit'>
|
||||
const init: UserAllocation = {
|
||||
@@ -11,50 +12,86 @@ const init: UserAllocation = {
|
||||
strength: 0,
|
||||
money: 0,
|
||||
}
|
||||
export const baseAllocAtom = atom<UserAllocation>({ ...init })
|
||||
export const allocAtom = atom<UserAllocation>({ ...init })
|
||||
|
||||
const alloced = (alloc: UserAllocation) =>
|
||||
Object.values(alloc).reduce((a, b) => a + b, 0)
|
||||
|
||||
export const useAllocReset = () => {
|
||||
const setBaseAlloc = useSetAtom(baseAllocAtom)
|
||||
const setAlloc = useSetAtom(allocAtom)
|
||||
return useCallback(() => setAlloc({ ...init }), [setAlloc])
|
||||
return useCallback(() => {
|
||||
setBaseAlloc({ ...init })
|
||||
setAlloc({ ...init })
|
||||
}, [setBaseAlloc, setAlloc])
|
||||
}
|
||||
export const useBaseAlloc = () => useAtomValue(baseAllocAtom)
|
||||
export const useAlloc = () => {
|
||||
const base = useAtomValue(baseAllocAtom)
|
||||
const alloc = useAtomValue(allocAtom)
|
||||
const final = {
|
||||
charm: base.charm + alloc.charm,
|
||||
intelligence: base.intelligence + alloc.intelligence,
|
||||
strength: base.strength + alloc.strength,
|
||||
money: base.money + alloc.money,
|
||||
}
|
||||
return { base, alloc, final }
|
||||
}
|
||||
export const useAlloc = () => useAtomValue(allocAtom)
|
||||
export const usePoints = () => {
|
||||
const { points } = useConfig()
|
||||
const isClassic = useIsClassic()
|
||||
const { additionalPoints } = useReplaced()
|
||||
return Math.max(points + additionalPoints.points, 0)
|
||||
const additional = additionalPoints.points
|
||||
if (!isClassic) {
|
||||
const base = useAtomValue(baseAllocAtom)
|
||||
return { base: alloced(base), total: additional, additional }
|
||||
}
|
||||
const { points } = useConfig()
|
||||
return { base: points, total: points + additional, additional }
|
||||
}
|
||||
|
||||
export const useLeftPoints = () => {
|
||||
const points = usePoints()
|
||||
const alloc = useAtomValue(allocAtom)
|
||||
return points - alloced(alloc)
|
||||
const ca = alloced(alloc)
|
||||
const left = points.total - ca
|
||||
return { ...points, left, alloced: ca }
|
||||
}
|
||||
|
||||
export const useAllocBefore = () => {
|
||||
const { allocate } = useConfig()
|
||||
const { total } = usePoints()
|
||||
const isClassic = useIsClassic()
|
||||
const isPositive = total >= 0
|
||||
const limit = isClassic
|
||||
? allocate
|
||||
: Math.min(Math.ceil(Math.abs(total) / 2), allocate) *
|
||||
(isPositive ? 1 : -1)
|
||||
return { total, limit, isPositive, isClassic }
|
||||
}
|
||||
|
||||
export const useAllocator = () => {
|
||||
const { allocate } = useConfig()
|
||||
const total = usePoints()
|
||||
const [alloc, setAlloc] = useAtom(allocAtom)
|
||||
const { total, limit } = useAllocBefore()
|
||||
const alloc = useAlloc()
|
||||
const setAlloc = useSetAtom(allocAtom)
|
||||
const allocator = useCallback(
|
||||
(key: keyof UserAllocation, value: number) => {
|
||||
setAlloc(prev => {
|
||||
const left = total - alloced(prev) + prev[key]
|
||||
const max = Math.min(left, allocate)
|
||||
const final = Math.max(Math.min(max, value), 0)
|
||||
if (prev[key] === final) return prev
|
||||
const last = prev[key]
|
||||
const left = total - alloced(prev) + last
|
||||
const max = zoneFit(left, [0, limit])
|
||||
const final = zoneFit(value, [0, max])
|
||||
if (last === final) return prev
|
||||
return { ...prev, [key]: final }
|
||||
})
|
||||
},
|
||||
[allocate, total, setAlloc],
|
||||
[limit, total, setAlloc],
|
||||
)
|
||||
return [alloc, allocator] as const
|
||||
}
|
||||
|
||||
export const usePointRandomizer = () => {
|
||||
const { allocate } = useConfig()
|
||||
const total = usePoints()
|
||||
const { total, limit } = useAllocBefore()
|
||||
const setAlloc = useSetAtom(allocAtom)
|
||||
return useCallback(
|
||||
(rng?: RNG) => {
|
||||
@@ -63,15 +100,16 @@ export const usePointRandomizer = () => {
|
||||
const alloc = { ...init }
|
||||
while (suffled.length) {
|
||||
const key = suffled.pop()!
|
||||
const max = Math.min(allocate, left)
|
||||
const min = Math.max(0, left - suffled.length * allocate)
|
||||
const value = frandom(max, min, rng)
|
||||
const n = suffled.length
|
||||
const max = zoneFit(limit, [0, left])
|
||||
const min = zoneFit(left - n * limit, [0, limit])
|
||||
const value = random(max, min, rng)
|
||||
alloc[key] = value
|
||||
left -= value
|
||||
}
|
||||
setAlloc(alloc)
|
||||
},
|
||||
[allocate, total, setAlloc],
|
||||
[total, limit, setAlloc],
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useConfig, useProfile, useSetStep, useSetGameState, Step } from '.'
|
||||
import { pickedAtom, replacedAtom, baseAllocAtom } from '.'
|
||||
import { useConfig, useProfile, useSetStep, Step } from '.'
|
||||
import { uniqueGenerate, startUnique, startChara } from '@remake/core'
|
||||
import { pullChara, start, pick } from '@remake/core'
|
||||
import { pullChara, pick } from '@remake/core'
|
||||
import type { BaseChara, PullCharaTms } from '@remake/core'
|
||||
import type { Character } from '@remake/data'
|
||||
import type { RNG } from '@remake/vitex'
|
||||
@@ -70,34 +71,29 @@ export const useCharaPicker = () => {
|
||||
return [picked, { chara, unique: uni }] as const
|
||||
}
|
||||
|
||||
export const useCharaStart = () => {
|
||||
const { spirit } = useConfig()
|
||||
export const useCharaSubmit = () => {
|
||||
const [profile] = useProfile()
|
||||
const unique = useAtomValue(uniqueAtom)
|
||||
const setStep = useSetStep()
|
||||
const setState = useSetGameState()
|
||||
const setPicked = useSetAtom(pickedAtom)
|
||||
const setReplaced = useSetAtom(replacedAtom)
|
||||
const setBaseAlloc = useSetAtom(baseAllocAtom)
|
||||
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)
|
||||
result = startUnique(unique)
|
||||
} else {
|
||||
result = startChara(picked.id, spirit)
|
||||
result = startChara(picked.id)
|
||||
}
|
||||
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
|
||||
setPicked(new Set(result.talents))
|
||||
setReplaced(pick(result.talents, rng))
|
||||
setBaseAlloc(result.allocation)
|
||||
setStep(Step.Alloc)
|
||||
},
|
||||
[spirit, profile, unique, setStep, setState],
|
||||
[profile, unique, setPicked, setReplaced, setStep],
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@ export const useGameReset = () => {
|
||||
}, [resetTalent, resetAlloc, resetState, resetLogs])
|
||||
}
|
||||
|
||||
export const useMode = () => useAtomValue(modeAtom)
|
||||
export const useIsClassic = () => useAtomValue(modeAtom) === Mode.Classic
|
||||
export const useStep = () => useAtomValue(stepAtom)
|
||||
export const useSetStep = () => useSetAtom(stepAtom)
|
||||
export const useGameState = () => useAtomValue(gameStateAtom)
|
||||
@@ -82,7 +84,7 @@ export const useModeChoose = () => {
|
||||
case Mode.Celebrity: return setStep(Step.Chara)
|
||||
}
|
||||
},
|
||||
[setStep],
|
||||
[setMode, setStep],
|
||||
)
|
||||
return [Mode, choose] as const
|
||||
}
|
||||
@@ -92,7 +94,7 @@ export const useStart = () => {
|
||||
const [profile] = useProfile()
|
||||
const setStep = useSetAtom(stepAtom)
|
||||
const setState = useSetAtom(gameStateAtom)
|
||||
const allocate = useAlloc()
|
||||
const { final: allocate } = useAlloc()
|
||||
const { talents: tr } = useReplaced()
|
||||
return useCallback(() => {
|
||||
const alloc = { ...allocate, spirit }
|
||||
@@ -148,10 +150,12 @@ export const useEnd = () => {
|
||||
const setStep = useSetAtom(stepAtom)
|
||||
const state = useAtomValue(gameStateAtom)
|
||||
const reset = useGameReset()
|
||||
const isClassic = useIsClassic()
|
||||
const [locked, setLocked] = useState<Set<Talent['id']>>(new Set())
|
||||
const picker = useCallback(
|
||||
(talent: Talent['id']) => {
|
||||
setLocked(prev => {
|
||||
if (!isClassic) return prev
|
||||
if (prev.has(talent)) {
|
||||
const next = new Set(prev)
|
||||
next.delete(talent)
|
||||
@@ -169,7 +173,11 @@ export const useEnd = () => {
|
||||
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 l = isClassic
|
||||
? locked.size > 0
|
||||
? Array.from(locked)
|
||||
: undefined
|
||||
: profile.locked
|
||||
const result = end(state, profile, l)
|
||||
setStep(Step.Idle)
|
||||
setProfile(result.profile)
|
||||
|
||||
Reference in New Issue
Block a user