Add elf pages

This commit is contained in:
Sakura Knoll
2023-07-14 09:48:09 +09:00 Unverified
parent afafab1956
commit 679ffe48b9
14 changed files with 560 additions and 18 deletions
+20
View File
@@ -0,0 +1,20 @@
import { assetsBucketBaseUrl } from '../../lib/consts'
import ResizedImage from './ResizedImage'
interface ElfIconProps {
icon: string
}
const ElfIcon = ({ icon }: ElfIconProps) => {
return (
<ResizedImage
src={`${assetsBucketBaseUrl}/raw/elfcardicons/${icon}.png`}
originalHeight={120}
originalWidth={140}
ratio={0.8}
alignItems="end"
/>
)
}
export default ElfIcon
+29
View File
@@ -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 (
<Flex sx={{ marginTop: -starSize / 2, justifyContent: 'center', width: '100%' }}>
{repeat(rarity, index => (
<Box
key={index}
sx={{
width: starSize,
height: starSize,
zIndex: 1,
background: `no-repeat 50% / 100% url(${assetsBucketBaseUrl}/raw-misc/StarBig.png)`
}}
/>
))}
</Flex>
)
}
export default RarityBar
+50
View File
@@ -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 (
<Box sx={{ width: targetWidth, height: targetHeight, position: 'relative' }}>
<Box
sx={{
position: 'absolute',
left: (targetWidth - originalWidth) / 2,
top: (targetHeight - originalHeight) / 2
}}
>
{src.map((aSrc, index) => {
return (
<Flex
key={index}
sx={{
position: 'absolute',
width: originalWidth,
height: originalHeight,
alignItems: 'center',
justifyContent: 'center',
zIndex: 1,
transform: `scale(${ratio})`
}}
>
<Image src={aSrc} />
</Flex>
)
})}
</Box>
</Box>
)
}
export default ResizedImage
+3 -18
View File
@@ -1,14 +1,12 @@
import { Box, Flex } from 'theme-ui' import { Box } from 'theme-ui'
import { assetsBucketBaseUrl } from '../../lib/consts' import { assetsBucketBaseUrl } from '../../lib/consts'
import { repeat } from '../../lib/utils' import RarityBar from './RarityBar'
interface WeaponIconProps { interface WeaponIconProps {
icon: string icon: string
rarity?: number rarity?: number
} }
const starSize = 16
const WeaponIcon = ({ icon: fileName, rarity }: WeaponIconProps) => { const WeaponIcon = ({ icon: fileName, rarity }: WeaponIconProps) => {
return ( return (
<Box sx={{ width: 112 }}> <Box sx={{ width: 112 }}>
@@ -22,20 +20,7 @@ const WeaponIcon = ({ icon: fileName, rarity }: WeaponIconProps) => {
)}` )}`
}} }}
/> />
{rarity != null && ( {rarity != null && <RarityBar rarity={rarity} />}
<Flex sx={{ marginTop: -starSize / 2, justifyContent: 'center', width: '100%' }}>
{repeat(rarity, index => (
<Box
key={index}
sx={{
width: starSize,
height: starSize,
background: `no-repeat 50% / 100% url(${assetsBucketBaseUrl}/raw-misc/StarBig.png)`
}}
/>
))}
</Flex>
)}
</Box> </Box>
) )
} }
+32
View File
@@ -5,3 +5,35 @@ export function repeat<N>(count: number, rendererFn: (index: number) => N) {
} }
return result return result
} }
export class ListMap<K, V> {
map: Map<K, V[]>
constructor(mapOrEntries?: Map<K, V[]> | [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()
}
}
+50
View File
@@ -307,3 +307,53 @@ export interface ErSigil {
unlockHint: string unlockHint: string
type: number 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
}
@@ -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<ListMap<string, ElfSkill>>(
(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<Elf>(([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
}
+14
View File
@@ -3,6 +3,8 @@ import path from 'path'
import YAML from 'yaml' import YAML from 'yaml'
import { import {
BattlesuitCatalogItem, BattlesuitCatalogItem,
Elf,
ElfCatalogItem,
ErBattlesuit, ErBattlesuit,
ErBattlesuitCatalogItem, ErBattlesuitCatalogItem,
ErSigil, ErSigil,
@@ -35,6 +37,9 @@ export const erSignetsDir = path.join(dataDir, 'er-signets')
export const erSupportsPath = path.join(dataDir, 'er-supports.yaml') export const erSupportsPath = path.join(dataDir, 'er-supports.yaml')
export const erSigilsPath = path.join(dataDir, 'er-sigils.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[] { export function loadBattlesuitCatalog(): BattlesuitCatalogItem[] {
return readYamlFile(battlesuitCatalogPath) return readYamlFile(battlesuitCatalogPath)
} }
@@ -93,6 +98,15 @@ export function loadErSigils(): ErSigil[] {
return readYamlFile(erSigilsPath) 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) { function readYamlFile(pathname: string) {
return YAML.parse(fs.readFileSync(pathname).toString()) return YAML.parse(fs.readFileSync(pathname).toString())
} }
+57
View File
@@ -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[]
}
+32
View File
@@ -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
}
+117
View File
@@ -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<ListMap<string, ElfSkill>>((map, skill) => {
map.set(skill.row.toString(), skill)
return map
}, new ListMap())
return [...rowSkillListMap.values()]
}, [elf.skills])
return (
<Box>
<Heading as="h1">{elf.fullName}</Heading>
<SquareImage
src={`${assetsBucketBaseUrl}/raw/elfcardfigures/${elf.figureImage}.png`}
originalSize={720}
size={480}
/>
<Card mb={3}>
<Box sx={{ p: 1, borderBottom: 'default' }}>
<Flex>
{elf.tags.map(tag => {
return (
<Box key={tag.type} mr={1}>
<TagIcon type={tag.type} />
</Box>
)
})}
</Flex>
</Box>
<Box p={1}>
Ult CD: {elf.ultSkillCd} / Ult SP Cost: {elf.ultSkillCost}
</Box>
</Card>
<Heading as="h2">Skills</Heading>
{skillRows.map((row, index) => {
return (
<Card key={index} mb={2}>
{row.map(skill => {
return (
<Box
key={skill.id}
sx={{
borderBottom: 'default',
'&:last-child': {
borderBottom: 'none'
}
}}
>
<Box sx={{ borderBottom: 'default', p: 1 }}>
<Flex sx={{ alignItems: 'center' }}>
<Box sx={{ flexShrink: 0 }}>
<SquareImage
src={`${assetsBucketBaseUrl}/raw/elf_skill_icon/${skill.icon}.png`}
originalSize={40}
/>
</Box>
<Heading as={'h3'} sx={{ my: 0, ml: 1 }}>
<FormattedText>{skill.name}</FormattedText>
</Heading>
{/* {skill.id} */}
<Box sx={{ color: 'textMuted', ml: 1 }}>({skill.skillType})</Box>
</Flex>
</Box>
<Box sx={{ p: 1 }}>
<FormattedText>{formatSubSkillInfo(skill)}</FormattedText>
</Box>
</Box>
)
})}
</Card>
)
})}
</Box>
)
}
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
}
}
+61
View File
@@ -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 (
<Box>
<h1>Elfs</h1>
<Flex sx={{ flexWrap: 'wrap' }}>
{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 (
<Box key={elf.id} m={2}>
<Link href={`/v2-pre/elfs/${elf.id}`}>
<Card p={1}>
<Box>
<ElfIcon icon={elf.cardIcon} />
<RarityBar rarity={elf.rarity} />
</Box>
<Box
sx={{ textAlign: 'center', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
>
{elf.fullName}
</Box>
</Card>
</Link>
</Box>
)
})}
</Flex>
</Box>
)
}
export default ElfListPage
export async function getStaticProps({ locale }: NextPageContext) {
const elfs = loadElfCatalog()
return {
props: { elfs }
}
}
+28
View File
@@ -7,6 +7,8 @@ import {
battlesuitCatalogPath, battlesuitCatalogPath,
battlesuitsDir, battlesuitsDir,
dataDir, dataDir,
elfCatalogPath,
elfsDir,
erBattlesuitCatalogPath, erBattlesuitCatalogPath,
erBattlesuitsDir, erBattlesuitsDir,
erSigilsPath, erSigilsPath,
@@ -21,6 +23,7 @@ import {
} from '../lib/v2-pre/server/loadData' } from '../lib/v2-pre/server/loadData'
import { import {
BattlesuitCatalogItem, BattlesuitCatalogItem,
ElfCatalogItem,
StigmataCatalogItem, StigmataCatalogItem,
StigmataSetCatalogItem, StigmataSetCatalogItem,
WeaponCatalogItem WeaponCatalogItem
@@ -29,6 +32,7 @@ import { compileBattlesuitData } from '../lib/v2-pre/server/compileData/compileB
import { compileWeaponData } from '../lib/v2-pre/server/compileData/compileWeaponData' import { compileWeaponData } from '../lib/v2-pre/server/compileData/compileWeaponData'
import { compileStigmataData } from '../lib/v2-pre/server/compileData/compileStigmataData' import { compileStigmataData } from '../lib/v2-pre/server/compileData/compileStigmataData'
import { compileGodWarData } from '../lib/v2-pre/server/compileData/compileGodWarData' import { compileGodWarData } from '../lib/v2-pre/server/compileData/compileGodWarData'
import { compileElfData } from '../lib/v2-pre/server/compileData/compileElfData'
runScript(async () => { runScript(async () => {
createDirIfNotExist(dataDir) createDirIfNotExist(dataDir)
@@ -36,6 +40,7 @@ runScript(async () => {
writeWeaponData() writeWeaponData()
writeStigmataData() writeStigmataData()
writeErData() writeErData()
writeElfData()
}) })
function writeBattlesuitData() { function writeBattlesuitData() {
@@ -158,3 +163,26 @@ function writeErData() {
fs.writeFileSync(erSupportsPath, YAML.stringify(supportAvatarList)) fs.writeFileSync(erSupportsPath, YAML.stringify(supportAvatarList))
fs.writeFileSync(erSigilsPath, YAML.stringify(sigilList)) fs.writeFileSync(erSigilsPath, YAML.stringify(sigilList))
} }
function writeElfData() {
const elfs = compileElfData()
const elfCatalogList = elfs.map<ElfCatalogItem>(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))
}
}
+1
View File
@@ -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/weaponicons \
dump/sprite2-dump-67/assets/asbres/spriteoutput/weapontypeicons \ dump/sprite2-dump-67/assets/asbres/spriteoutput/weapontypeicons \
dump/sprite2-dump-67/assets/asbres/spriteoutput/materialfigures \ dump/sprite2-dump-67/assets/asbres/spriteoutput/materialfigures \
dump/sprite2-dump-67/assets/asbres/spriteoutput/elf_skill_icon \
assets/raw assets/raw