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