From 9dc8a3e8bb7da67ca8af5aa710786696907892eb Mon Sep 17 00:00:00 2001 From: Vick Scarlet Date: Sat, 15 Aug 2026 22:11:37 +0800 Subject: [PATCH] update: achievement --- apps/web/src/Game.css | 2 +- apps/web/src/Game.tsx | 2 + apps/web/src/config.ts | 4 +- apps/web/src/containers/Achv.css | 120 ++++++++++++++++++++++++++++++ apps/web/src/containers/Achv.tsx | 82 ++++++++++++++++++++ apps/web/src/containers/Home.css | 11 +++ apps/web/src/containers/Home.tsx | 21 +++++- packages/hooks/src/achievement.ts | 34 +++++++++ packages/hooks/src/alloc.ts | 9 +-- packages/hooks/src/character.ts | 5 +- packages/hooks/src/index.ts | 1 + packages/hooks/src/play.ts | 10 ++- 12 files changed, 284 insertions(+), 17 deletions(-) create mode 100644 apps/web/src/containers/Achv.css create mode 100644 apps/web/src/containers/Achv.tsx create mode 100644 packages/hooks/src/achievement.ts diff --git a/apps/web/src/Game.css b/apps/web/src/Game.css index 549d215..ed2b637 100644 --- a/apps/web/src/Game.css +++ b/apps/web/src/Game.css @@ -17,7 +17,7 @@ width: 100%; padding: 0; box-sizing: border-box; - .title { + > .title { display: flex; flex-direction: row; justify-content: center; diff --git a/apps/web/src/Game.tsx b/apps/web/src/Game.tsx index 39b8c76..839efaf 100644 --- a/apps/web/src/Game.tsx +++ b/apps/web/src/Game.tsx @@ -8,6 +8,7 @@ import Pick from '@/containers/TalentPick' import Alloc from '@/containers/Alloc' import Play from '@/containers/Play' import Summary from '@/containers/Summary' +import Achv from '@/containers/Achv' import './Game.css' export function Container() { @@ -20,6 +21,7 @@ export function Container() { case Step.Alloc: return case Step.Play: return case Step.Summary: return + case Step.Achv: return default: return null } } diff --git a/apps/web/src/config.ts b/apps/web/src/config.ts index 8be5239..f24e072 100644 --- a/apps/web/src/config.ts +++ b/apps/web/src/config.ts @@ -52,8 +52,8 @@ export const JudgeLineMap = { spirit: [0, 1, 2, 4, 7, 9, 11], intelligence: [0, 1, 2, 4, 7, 9, 11, 21, 131, 501], strength: [0, 1, 2, 4, 7, 9, 11, 21, 101, 401, 1001, 2001], - talent: [0, 0.3, 0.6, 0.9], - event: [0, 0.2, 0.4, 0.6], + talent: [0, 30, 60, 90], + event: [0, 20, 40, 60], } // 可评级属性 export type Judges = keyof typeof JudgeLineMap diff --git a/apps/web/src/containers/Achv.css b/apps/web/src/containers/Achv.css new file mode 100644 index 0000000..3b4a573 --- /dev/null +++ b/apps/web/src/containers/Achv.css @@ -0,0 +1,120 @@ +.screen.achv { + height: calc(100dvh - 2rem); + width: min(calc(100dvw - 2rem), 48rem); + gap: 1rem; + > .stats { + > .details { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.5rem; + > div { + display: flex; + flex-direction: column; + align-items: center; + border: 0.0625rem solid var(--grade-color); + color: var(--grade-color); + box-sizing: border-box; + position: relative; + transform-style: preserve-3d; + user-select: none; + > span { + display: flex; + align-items: center; + justify-content: center; + } + > .title { + font-weight: bold; + font-size: 1.5rem; + height: 2.5rem; + } + > .value { + height: 2rem; + } + } + + > .prg { + padding-bottom: 0.1875rem; + &::before, + &::after { + content: ''; + display: block; + position: absolute; + height: 0.1875rem; + left: 0; + bottom: 0; + transform: translateZ(-1px); + } + &::after { + width: var(--progress, 0%); + background: var(--grade-color); + transition: width 0.2s ease-in-out; + } + &::before { + width: 100%; + border-top: 0.0625rem solid var(--grade-color); + box-sizing: border-box; + } + } + } + } + > .achievement-list { + flex: 1 0; + width: 100%; + > ul { + flex: 1 0; + display: grid; + flex-direction: column; + grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); + gap: 0.5rem; + box-sizing: border-box; + > li.achievement { + margin-right: 0.0625rem; + display: flex; + flex-direction: column; + align-items: center; + color: var(--grade-color); + border: 0.0625rem solid var(--grade-color); + padding: 0 0.0625rem; + box-sizing: border-box; + transform-style: preserve-3d; + position: relative; + user-select: none; + >.name { + font-weight: bold; + font-size: 1.2rem; + height: 2rem; + display: flex; + align-items: center; + justify-content: center; + } + >.description { + height: 1.5rem; + display: flex; + align-items: center; + justify-content: center; + } + &::before{ + content: ''; + display: block; + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + padding: 0.0625rem; + margin: -0.0625rem; + transform: translateZ(1px); + background: var(--base-background-color); + opacity: 0.8; + } + &.colled { + &::before{ + transform: translateZ(-1px); + background: var(--grade-color); + opacity: 0.15; + } + } + } + } + } +} \ No newline at end of file diff --git a/apps/web/src/containers/Achv.tsx b/apps/web/src/containers/Achv.tsx new file mode 100644 index 0000000..8568c66 --- /dev/null +++ b/apps/web/src/containers/Achv.tsx @@ -0,0 +1,82 @@ +import { useAchv, useGoHome } from '@remake/hooks' +import { judge, judgeGrade, judgeGradeByValue } from '@/config' +import { rates } from '@/display' +import { achievements, type Achievement } from '@remake/data' +import './Achv.css' + +interface AchievementProps { + id: Achievement['id'] + colled: boolean +} +export function AchievementItem({ id, colled }: AchievementProps) { + const { name, grade, description, hide } = achievements.get(id)! + return ( +
  • + {hide && !colled ? '???' : name} + + {hide && !colled ? '???' : description} + +
  • + ) +} + +export function Achv() { + const goHome = useGoHome() + const [stats, sorted] = useAchv() + const jtimes = judge('times', stats.times) + const gtimes = judgeGrade('times', jtimes) + const jachv = judge('achievements', stats.achv) + const gachv = judgeGrade('achievements', jachv) + const gevent = judgeGradeByValue('event', stats.event) + const revent = stats.event.toFixed(2) + '%' + const gtalent = judgeGradeByValue('talent', stats.talent) + const rtalent = stats.talent.toFixed(2) + '%' + return ( +
    +
    + + +
    +
    +
    统计
    +
    +
    + 已重开{stats.times}次 + {rates.times[jtimes]} +
    +
    + 已收集成就{stats.achv}个 + + {rates.achievement[jachv]} + +
    +
    + 事件收集率 + {revent} +
    +
    + 天赋收集率 + {rtalent} +
    +
    +
    +
    +
    成就
    +
      + {sorted.map(({ id, colled }) => ( + + ))} +
    +
    +
    + ) +} +export default Achv diff --git a/apps/web/src/containers/Home.css b/apps/web/src/containers/Home.css index dd73cff..0fbb8d7 100644 --- a/apps/web/src/containers/Home.css +++ b/apps/web/src/containers/Home.css @@ -21,4 +21,15 @@ -webkit-tap-highlight-color: transparent; transition: background-color 0.3s; } + >.controls { + flex-direction: column; + > div { + flex: 1 0; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + > button { flex: 1 0; } + } + } } \ No newline at end of file diff --git a/apps/web/src/containers/Home.tsx b/apps/web/src/containers/Home.tsx index 2cb1d70..9d850f4 100644 --- a/apps/web/src/containers/Home.tsx +++ b/apps/web/src/containers/Home.tsx @@ -1,19 +1,32 @@ -import { useRemake } from '@remake/hooks' +import { useRemake, useGoAchv } from '@remake/hooks' import { TextSvg } from '@/components/TextSvg' import { ThemeToggle } from '@/components/ThemeToggle' import './Home.css' export default function Home() { const remake = useRemake() + const goAchv = useGoAchv() return (
    - +
    +
    + +
    +
    + + +
    +
    ) diff --git a/packages/hooks/src/achievement.ts b/packages/hooks/src/achievement.ts new file mode 100644 index 0000000..346b293 --- /dev/null +++ b/packages/hooks/src/achievement.ts @@ -0,0 +1,34 @@ +import { useCallback } from 'react' +import { events, talents, achievements } from '@remake/data' +import { useProfile, useSetStep, Step } from '.' + +const TotalEvents = events.size +const TotalTalents = talents.size + +export const useAchv = () => { + const [profile] = useProfile() + const o = new Map(Array.from(profile.achievements, (id, i) => [id, i])) + const sorted = Array.from(achievements.keys()) + .sort((a, b) => { + if (o.has(a) && o.has(b)) return o.get(b)! - o.get(a)! + if (o.has(a)) return -1 + if (o.has(b)) return 1 + const { hide: ha, grade: ga } = achievements.get(a)! + const { hide: hb, grade: gb } = achievements.get(b)! + if (ha == hb) return gb - ga + return ha ? 1 : -1 + }) + .map(id => ({ id, colled: o.has(id) })) + const stats = { + times: profile.times, + achv: profile.achievements.size, + event: (profile.events.size / TotalEvents) * 100, + talent: (profile.talents.size / TotalTalents) * 100, + } + return [stats, sorted] as const +} + +export const useGoAchv = () => { + const setStep = useSetStep() + return useCallback(() => setStep(Step.Achv), [setStep]) +} diff --git a/packages/hooks/src/alloc.ts b/packages/hooks/src/alloc.ts index 48ee635..70ebaf8 100644 --- a/packages/hooks/src/alloc.ts +++ b/packages/hooks/src/alloc.ts @@ -42,12 +42,11 @@ export const usePoints = () => { const isClassic = useIsClassic() const { additionalPoints } = useReplaced() const additional = additionalPoints.points - if (!isClassic) { - const base = useAtomValue(baseAllocAtom) - return { base: alloced(base), total: additional, additional } - } + const ba = useAtomValue(baseAllocAtom) const { points } = useConfig() - return { base: points, total: points + additional, additional } + const base = isClassic ? points : alloced(ba) + const total = isClassic ? points + additional : additional + return { base, total, additional } } export const useLeftPoints = () => { diff --git a/packages/hooks/src/character.ts b/packages/hooks/src/character.ts index 01af8c9..2e125f5 100644 --- a/packages/hooks/src/character.ts +++ b/packages/hooks/src/character.ts @@ -1,7 +1,7 @@ import { useCallback, useState } from 'react' import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' import { pickedAtom, replacedAtom, baseAllocAtom } from '.' -import { useConfig, useProfile, useSetStep, Step } from '.' +import { useConfig, useSetStep, Step } from '.' import { uniqueGenerate, startUnique, startChara } from '@remake/core' import { pullChara, pick } from '@remake/core' import type { BaseChara, PullCharaTms } from '@remake/core' @@ -72,7 +72,6 @@ export const useCharaPicker = () => { } export const useCharaSubmit = () => { - const [profile] = useProfile() const unique = useAtomValue(uniqueAtom) const setStep = useSetStep() const setPicked = useSetAtom(pickedAtom) @@ -93,7 +92,7 @@ export const useCharaSubmit = () => { setBaseAlloc(result.allocation) setStep(Step.Alloc) }, - [profile, unique, setPicked, setReplaced, setStep], + [unique, setPicked, setReplaced, setBaseAlloc, setStep], ) } diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 3626b85..824efa5 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -4,4 +4,5 @@ export * from './talent' export * from './play' export * from './alloc' export * from './character' +export * from './achievement' export type * from '@remake/core' diff --git a/packages/hooks/src/play.ts b/packages/hooks/src/play.ts index 55645b7..72e3671 100644 --- a/packages/hooks/src/play.ts +++ b/packages/hooks/src/play.ts @@ -14,6 +14,7 @@ export enum Step { Alloc = 'alloc', Play = 'play', Summary = 'summary', + Achv = 'achv', } export enum Mode { @@ -168,7 +169,7 @@ export const useEnd = () => { return next }) }, - [lock, setLocked], + [lock, isClassic, setLocked], ) const ender = useCallback(() => { if (!state) @@ -183,6 +184,11 @@ export const useEnd = () => { setProfile(result.profile) reset() return result.achievements - }, [state, profile, locked, setStep, setProfile, reset]) + }, [state, profile, locked, isClassic, setStep, setProfile, reset]) return [locked, picker, ender] as const } + +export const useGoHome = () => { + const setStep = useSetAtom(stepAtom) + return useCallback(() => setStep(Step.Idle), [setStep]) +}