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])
+}