diff --git a/src/components/v2-pre/ElfIcon.tsx b/src/components/v2-pre/ElfIcon.tsx
new file mode 100644
index 00000000..261a0256
--- /dev/null
+++ b/src/components/v2-pre/ElfIcon.tsx
@@ -0,0 +1,20 @@
+import { assetsBucketBaseUrl } from '../../lib/consts'
+import ResizedImage from './ResizedImage'
+
+interface ElfIconProps {
+ icon: string
+}
+
+const ElfIcon = ({ icon }: ElfIconProps) => {
+ return (
+
+ )
+}
+
+export default ElfIcon
diff --git a/src/components/v2-pre/RarityBar.tsx b/src/components/v2-pre/RarityBar.tsx
new file mode 100644
index 00000000..d938b61c
--- /dev/null
+++ b/src/components/v2-pre/RarityBar.tsx
@@ -0,0 +1,29 @@
+import { Box, Flex } from 'theme-ui'
+import { assetsBucketBaseUrl } from '../../lib/consts'
+import { repeat } from '../../lib/utils'
+
+interface RarityBarProps {
+ rarity: number
+}
+
+const starSize = 16
+
+const RarityBar = ({ rarity }: RarityBarProps) => {
+ return (
+
+ {repeat(rarity, index => (
+
+ ))}
+
+ )
+}
+
+export default RarityBar
diff --git a/src/components/v2-pre/ResizedImage.tsx b/src/components/v2-pre/ResizedImage.tsx
new file mode 100644
index 00000000..16d82d3b
--- /dev/null
+++ b/src/components/v2-pre/ResizedImage.tsx
@@ -0,0 +1,50 @@
+/* eslint-disable jsx-a11y/alt-text */
+import { Box, Flex, Image } from 'theme-ui'
+
+interface ResizedImageProps {
+ src: string | string[]
+ originalWidth: number
+ originalHeight: number
+ ratio?: number
+ alignItems?: string
+}
+
+const ResizedImage = ({ src, originalWidth, originalHeight, ratio = 1, alignItems = 'center' }: ResizedImageProps) => {
+ if (typeof src === 'string') {
+ src = [src]
+ }
+ const targetWidth = originalWidth * ratio
+ const targetHeight = originalHeight * ratio
+ return (
+
+
+ {src.map((aSrc, index) => {
+ return (
+
+
+
+ )
+ })}
+
+
+ )
+}
+
+export default ResizedImage
diff --git a/src/components/v2-pre/WeaponIcon.tsx b/src/components/v2-pre/WeaponIcon.tsx
index ab88191d..a4f8cee6 100644
--- a/src/components/v2-pre/WeaponIcon.tsx
+++ b/src/components/v2-pre/WeaponIcon.tsx
@@ -1,14 +1,12 @@
-import { Box, Flex } from 'theme-ui'
+import { Box } from 'theme-ui'
import { assetsBucketBaseUrl } from '../../lib/consts'
-import { repeat } from '../../lib/utils'
+import RarityBar from './RarityBar'
interface WeaponIconProps {
icon: string
rarity?: number
}
-const starSize = 16
-
const WeaponIcon = ({ icon: fileName, rarity }: WeaponIconProps) => {
return (
@@ -22,20 +20,7 @@ const WeaponIcon = ({ icon: fileName, rarity }: WeaponIconProps) => {
)}`
}}
/>
- {rarity != null && (
-
- {repeat(rarity, index => (
-
- ))}
-
- )}
+ {rarity != null && }
)
}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 5a9c139c..dfb3f612 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -5,3 +5,35 @@ export function repeat(count: number, rendererFn: (index: number) => N) {
}
return result
}
+
+export class ListMap {
+ map: Map
+ constructor(mapOrEntries?: Map | [K, V[]][]) {
+ if (mapOrEntries == null) {
+ this.map = new Map()
+ } else {
+ this.map = new Map(mapOrEntries)
+ }
+ }
+
+ get(key: K) {
+ return this.map.get(key)
+ }
+
+ set(key: K, value: V) {
+ let list = this.map.get(key)
+ if (list == null) {
+ list = []
+ this.map.set(key, list)
+ }
+ list.push(value)
+ }
+
+ entries() {
+ return this.map.entries()
+ }
+
+ values() {
+ return this.map.values()
+ }
+}
diff --git a/src/lib/v2-pre/data/types.ts b/src/lib/v2-pre/data/types.ts
index 9a92b286..163b58dd 100644
--- a/src/lib/v2-pre/data/types.ts
+++ b/src/lib/v2-pre/data/types.ts
@@ -307,3 +307,53 @@ export interface ErSigil {
unlockHint: string
type: number
}
+
+export interface Elf {
+ cardIcon: string
+ id: string
+ fullName: string
+ icon: string
+ chibiIcon: string
+ figureImage: string
+ rarity: number
+ cardId: number
+ fragmentId: number
+ storyDesc: string
+ ultSkillCd: number
+ ultSkillCost: number
+ skillBg: string
+ tags: {
+ type: TagType
+ comment: string
+ }[]
+ captainSkillIds: string[]
+ skills: ElfSkill[]
+}
+
+export interface ElfSkill {
+ id: string
+ elfId: string
+ name: string
+ info: string
+ skillType: string
+ skillTypeId: string
+ icon: string
+ row: number
+ col: number
+ unlockStar: number
+ maxLv: number
+ paramBase1: number
+ paramBase2: number
+ paramBase3: number
+ paramAdd1: number
+ paramAdd2: number
+ paramAdd3: number
+}
+
+export interface ElfCatalogItem {
+ id: string
+ fullName: string
+ cardIcon: string
+ chibiIcon: string
+ rarity: number
+}
diff --git a/src/lib/v2-pre/server/compileData/compileElfData.ts b/src/lib/v2-pre/server/compileData/compileElfData.ts
new file mode 100644
index 00000000..dc4fdad5
--- /dev/null
+++ b/src/lib/v2-pre/server/compileData/compileElfData.ts
@@ -0,0 +1,66 @@
+import { ListMap } from '../../../utils'
+import { Elf, ElfSkill } from '../../data/types'
+import { getRawElfDataMap } from '../raw/elfData'
+import { getRawElfSkillDataMap } from '../raw/elfSkillData'
+import { convertTagType, getText } from './utils'
+
+export function compileElfData() {
+ const rawElfDataMap = getRawElfDataMap()
+ const rawElfSkillDataMap = getRawElfSkillDataMap()
+
+ const rawElfSkillMap = Object.entries(rawElfSkillDataMap).reduce>(
+ (map, [id, rawSkillData]) => {
+ const elfId = rawSkillData.ElfID.toString()
+ map.set(elfId, {
+ id,
+ elfId,
+ name: getText(rawSkillData.Name),
+ info: getText(rawSkillData.Info),
+ skillType: getText(rawSkillData.SkillTypeTag),
+ skillTypeId: rawSkillData.SkillType.toString(),
+ icon: rawSkillData.IconPath.split('/').pop()!,
+ row: rawSkillData.UIPointRow,
+ col: rawSkillData.UIPointColumn,
+ unlockStar: rawSkillData.UnlockStar,
+ maxLv: rawSkillData.MaxLv,
+ paramBase1: rawSkillData.AbilityParamBase_1,
+ paramBase2: rawSkillData.AbilityParamBase_2,
+ paramBase3: rawSkillData.AbilityParamBase_3,
+ paramAdd1: rawSkillData.AbilityParamAdd_1,
+ paramAdd2: rawSkillData.AbilityParamAdd_2,
+ paramAdd3: rawSkillData.AbilityParamAdd_3
+ })
+ return map
+ },
+ new ListMap()
+ )
+
+ const elfs = Object.entries(rawElfDataMap).map(([id, rawData]) => {
+ const skills = rawElfSkillMap.get(id) || []
+ return {
+ id,
+ fullName: getText(rawData.FullName),
+ icon: rawData.IconPath.split('/').pop()!,
+ cardIcon: rawData.StoryBGImg.split('/').pop()!,
+ chibiIcon: rawData.ElfChibiIcon.split('/').pop()!,
+ figureImage: rawData.StoryBGImg.split('/').pop()!,
+ rarity: rawData.Rarity,
+ cardId: rawData.ElfCardID,
+ fragmentId: rawData.ElfFragmentID,
+ storyDesc: getText(rawData.StoryDesc),
+ ultSkillCd: rawData.UltraSkillCD,
+ ultSkillCost: rawData.UltraSkillSPCost,
+ skillBg: rawData.SkillTabBGImg.split('/').pop()!,
+ tags: rawData.TagList.map(rawTag => {
+ return {
+ type: convertTagType(rawTag.TagID),
+ comment: getText(rawTag.TagComment)
+ }
+ }),
+ captainSkillIds: rawData.CaptainSkillIDs.map(rawId => rawId.toString()),
+ skills
+ }
+ })
+
+ return elfs
+}
diff --git a/src/lib/v2-pre/server/loadData.ts b/src/lib/v2-pre/server/loadData.ts
index 207f64a4..2fc00c5a 100644
--- a/src/lib/v2-pre/server/loadData.ts
+++ b/src/lib/v2-pre/server/loadData.ts
@@ -3,6 +3,8 @@ import path from 'path'
import YAML from 'yaml'
import {
BattlesuitCatalogItem,
+ Elf,
+ ElfCatalogItem,
ErBattlesuit,
ErBattlesuitCatalogItem,
ErSigil,
@@ -35,6 +37,9 @@ export const erSignetsDir = path.join(dataDir, 'er-signets')
export const erSupportsPath = path.join(dataDir, 'er-supports.yaml')
export const erSigilsPath = path.join(dataDir, 'er-sigils.yaml')
+export const elfsDir = path.join(dataDir, 'elfs')
+export const elfCatalogPath = path.join(dataDir, 'elf-catalog.yaml')
+
export function loadBattlesuitCatalog(): BattlesuitCatalogItem[] {
return readYamlFile(battlesuitCatalogPath)
}
@@ -93,6 +98,15 @@ export function loadErSigils(): ErSigil[] {
return readYamlFile(erSigilsPath)
}
+export function loadElfCatalog(): ElfCatalogItem[] {
+ return readYamlFile(elfCatalogPath)
+}
+
+export function loadElfData(id: string): Elf {
+ const elfDataPath = path.join(elfsDir, `${id}.yaml`)
+ return readYamlFile(elfDataPath)
+}
+
function readYamlFile(pathname: string) {
return YAML.parse(fs.readFileSync(pathname).toString())
}
diff --git a/src/lib/v2-pre/server/raw/elfData.ts b/src/lib/v2-pre/server/raw/elfData.ts
new file mode 100644
index 00000000..1c8705d4
--- /dev/null
+++ b/src/lib/v2-pre/server/raw/elfData.ts
@@ -0,0 +1,57 @@
+import { loadRawData } from './loadRawData'
+
+export function getRawElfDataMap(): RawElfDataMap {
+ return loadRawData('ElfData.json')
+}
+
+export type RawElfDataMap = {
+ [key: string]: RawElfData
+}
+
+export interface RawElfData {
+ Index: number
+ FullName: number
+ ElfRegistryKey: string
+ ElfUIModelPath: string
+ ElfUIModelPosition: {
+ X: 0.135
+ Y: -0.32
+ Z: 3.74
+ }
+ ElfUIModelRotation: {
+ X: 0
+ Y: -7.2
+ Z: 0
+ }
+ IconPath: string // 'SpriteOutput/ElfIcon/Elf_01'
+ ElfChibiIcon: string // 'SpriteOutput/AvatarChibiIcons/Elf_10001'
+ PortraitCommonIcon: string // 'SpriteOutput/ElfCardIcons/Elf_10001'
+ StoryBGImg: string // 'SpriteOutput/ElfCardFigures/Elf_10001'
+ PresentBGImg: string // 'SpriteOutput/ElfCardFigures/Elf_10001'
+ AIName: string // 'Behavior_Elf_01'
+ IsFlight: number
+ UnlockStar: number
+ Rarity: number
+ ElfCardID: number
+ ElfFragmentID: number
+ CarryPlayerLevel: number
+ UISoundbankName: string
+ StoryDesc: number
+ RGBCGID: number
+ AlphaCGID: number
+ UltraSkillCD: number
+ UltraSkillSPCost: number
+ AttackSkillCD: number
+ SkillTabBGImg: string // 'SpriteOutput/ElfTalentBg/TalentBg1'
+ TalentTabBGImg: string // 'SpriteOutput/ElfTalentBg/TalentBg1'
+ SelectAudio: string // 'VO_Elf_01_BloodEmbrace_SwitchIn'
+ LevelUpAudio: string // 'VO_Elf_01_BloodEmbrace_LevelUp'
+ CombatPointRarity: number
+ TagList: {
+ TagID: number
+ TagComment: number
+ }[]
+ FeatureBrief: number
+ TrialStageActivityList: number[]
+ CaptainSkillIDs: number[]
+}
diff --git a/src/lib/v2-pre/server/raw/elfSkillData.ts b/src/lib/v2-pre/server/raw/elfSkillData.ts
new file mode 100644
index 00000000..c315a8c0
--- /dev/null
+++ b/src/lib/v2-pre/server/raw/elfSkillData.ts
@@ -0,0 +1,32 @@
+import { loadRawData } from './loadRawData'
+
+export function getRawElfSkillDataMap(): RawElfSkillDataMap {
+ return loadRawData('ElfSkillData.json')
+}
+
+export type RawElfSkillDataMap = {
+ [key: string]: RawElfSkillData
+}
+
+export interface RawElfSkillData {
+ ElfID: number
+ Name: number
+ Info: number
+ SkillTypeTag: number
+ MaxLv: number
+ SkillType: number
+ IconPath: string //'SpriteOutput/Elf_Skill_Icon/Skill_Elf_Attack_101'
+ IconType: number
+ UnlockStar: number
+ UIPointRow: number
+ UIPointColumn: number
+ TagList: { TagID: number; TagComment: number }[]
+ IconSpecial: number
+ AbilityParamBase_1: number
+ AbilityParamAdd_1: number
+ AbilityParamBase_2: number
+ AbilityParamAdd_2: number
+ AbilityParamBase_3: number
+ AbilityParamAdd_3: number
+ HasNoRestrictionAbility: number
+}
diff --git a/src/pages/v2-pre/elfs/[elfId].tsx b/src/pages/v2-pre/elfs/[elfId].tsx
new file mode 100644
index 00000000..664e40b2
--- /dev/null
+++ b/src/pages/v2-pre/elfs/[elfId].tsx
@@ -0,0 +1,117 @@
+import { NextPageContext } from 'next'
+import { Box, Card, Flex, Heading } from 'theme-ui'
+import { useMemo } from 'react'
+import { Elf, ElfSkill } from '../../../lib/v2-pre/data/types'
+import { loadElfCatalog, loadElfData } from '../../../lib/v2-pre/server/loadData'
+import SquareImage from '../../../components/v2-pre/SquareImage'
+import TagIcon from '../../../components/v2-pre/TagIcon'
+import { assetsBucketBaseUrl } from '../../../lib/consts'
+import { ListMap } from '../../../lib/utils'
+import FormattedText from '../../../components/v2-pre/FormattedText'
+import { formatSubSkillInfo } from '../../../lib/v2-pre/data/formatText'
+
+interface ElfShowPageProps {
+ elf: Elf
+}
+
+const ElfShowPage = ({ elf }: ElfShowPageProps) => {
+ const skillRows = useMemo(() => {
+ const rowSkillListMap = elf.skills.reduce>((map, skill) => {
+ map.set(skill.row.toString(), skill)
+ return map
+ }, new ListMap())
+
+ return [...rowSkillListMap.values()]
+ }, [elf.skills])
+ return (
+
+ {elf.fullName}
+
+
+
+
+
+ {elf.tags.map(tag => {
+ return (
+
+
+
+ )
+ })}
+
+
+
+ Ult CD: {elf.ultSkillCd} / Ult SP Cost: {elf.ultSkillCost}
+
+
+
+ Skills
+
+ {skillRows.map((row, index) => {
+ return (
+
+ {row.map(skill => {
+ return (
+
+
+
+
+
+
+
+ {skill.name}
+
+ {/* {skill.id} */}
+ ({skill.skillType})
+
+
+
+ {formatSubSkillInfo(skill)}
+
+
+ )
+ })}
+
+ )
+ })}
+
+ )
+}
+
+export default ElfShowPage
+
+export async function getStaticProps({ locale, params }: NextPageContext & { params: { elfId: string } }) {
+ const elf = loadElfData(params.elfId)
+
+ return {
+ props: { elf }
+ }
+}
+
+export async function getStaticPaths() {
+ const elfCatalog = loadElfCatalog()
+
+ return {
+ paths: elfCatalog.map(catalogItem => {
+ return {
+ params: { elfId: catalogItem.id }
+ }
+ }),
+ fallback: false
+ }
+}
diff --git a/src/pages/v2-pre/elfs/index.tsx b/src/pages/v2-pre/elfs/index.tsx
new file mode 100644
index 00000000..6ed429f7
--- /dev/null
+++ b/src/pages/v2-pre/elfs/index.tsx
@@ -0,0 +1,61 @@
+/** @jsxImportSource theme-ui */
+import { Box, Card } from '@theme-ui/components'
+import { NextPageContext } from 'next'
+import { Flex, Link } from 'theme-ui'
+import { ElfCatalogItem } from '../../../lib/v2-pre/data/types'
+import { loadElfCatalog } from '../../../lib/v2-pre/server/loadData'
+import RarityBar from '../../../components/v2-pre/RarityBar'
+import ElfIcon from '../../../components/v2-pre/ElfIcon'
+
+interface ElfListPageProps {
+ elfs: ElfCatalogItem[]
+}
+
+const ElfListPage = ({ elfs }: ElfListPageProps) => {
+ return (
+
+ Elfs
+
+ {elfs
+ .sort((a, b) => {
+ const aId = getId(a.id)
+ const bId = getId(b.id)
+ return bId - aId
+
+ function getId(id: string): number {
+ return parseInt(id)
+ }
+ })
+ .map(elf => {
+ return (
+
+
+
+
+
+
+
+
+ {elf.fullName}
+
+
+
+
+ )
+ })}
+
+
+ )
+}
+
+export default ElfListPage
+
+export async function getStaticProps({ locale }: NextPageContext) {
+ const elfs = loadElfCatalog()
+
+ return {
+ props: { elfs }
+ }
+}
diff --git a/src/scripts/compileRawData.ts b/src/scripts/compileRawData.ts
index a0d9eeac..7bd55c83 100644
--- a/src/scripts/compileRawData.ts
+++ b/src/scripts/compileRawData.ts
@@ -7,6 +7,8 @@ import {
battlesuitCatalogPath,
battlesuitsDir,
dataDir,
+ elfCatalogPath,
+ elfsDir,
erBattlesuitCatalogPath,
erBattlesuitsDir,
erSigilsPath,
@@ -21,6 +23,7 @@ import {
} from '../lib/v2-pre/server/loadData'
import {
BattlesuitCatalogItem,
+ ElfCatalogItem,
StigmataCatalogItem,
StigmataSetCatalogItem,
WeaponCatalogItem
@@ -29,6 +32,7 @@ import { compileBattlesuitData } from '../lib/v2-pre/server/compileData/compileB
import { compileWeaponData } from '../lib/v2-pre/server/compileData/compileWeaponData'
import { compileStigmataData } from '../lib/v2-pre/server/compileData/compileStigmataData'
import { compileGodWarData } from '../lib/v2-pre/server/compileData/compileGodWarData'
+import { compileElfData } from '../lib/v2-pre/server/compileData/compileElfData'
runScript(async () => {
createDirIfNotExist(dataDir)
@@ -36,6 +40,7 @@ runScript(async () => {
writeWeaponData()
writeStigmataData()
writeErData()
+ writeElfData()
})
function writeBattlesuitData() {
@@ -158,3 +163,26 @@ function writeErData() {
fs.writeFileSync(erSupportsPath, YAML.stringify(supportAvatarList))
fs.writeFileSync(erSigilsPath, YAML.stringify(sigilList))
}
+
+function writeElfData() {
+ const elfs = compileElfData()
+
+ const elfCatalogList = elfs.map(elf => {
+ return {
+ id: elf.id,
+ fullName: elf.fullName,
+ cardIcon: elf.cardIcon,
+ icon: elf.icon,
+ chibiIcon: elf.chibiIcon,
+ rarity: elf.rarity
+ }
+ })
+
+ fs.writeFileSync(elfCatalogPath, YAML.stringify(elfCatalogList))
+
+ createDirIfNotExist(elfsDir)
+ for (const elf of elfs) {
+ const elfDataPath = path.join(elfsDir, `${elf.id}.yaml`)
+ fs.writeFileSync(elfDataPath, YAML.stringify(elf))
+ }
+}
diff --git a/src/scripts/copyRawAssets.sh b/src/scripts/copyRawAssets.sh
index 16bad6a9..fd54d236 100644
--- a/src/scripts/copyRawAssets.sh
+++ b/src/scripts/copyRawAssets.sh
@@ -26,4 +26,5 @@ dump/sprite2-dump-67/assets/asbres/spriteoutput/rogue/godcloseup \
dump/sprite2-dump-67/assets/asbres/spriteoutput/weaponicons \
dump/sprite2-dump-67/assets/asbres/spriteoutput/weapontypeicons \
dump/sprite2-dump-67/assets/asbres/spriteoutput/materialfigures \
+dump/sprite2-dump-67/assets/asbres/spriteoutput/elf_skill_icon \
assets/raw