UPDATE: Update readme and Next 16.07 (CVE-2025-66478)
This commit is contained in:
100
src/helper/calcData.ts
Normal file
100
src/helper/calcData.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { mappingStats, ratioStats } from "@/constant/constant"
|
||||
import { AffixDetail } from "@/types"
|
||||
|
||||
export function calcPromotion(level: number) {
|
||||
if (level < 20) {
|
||||
return 0
|
||||
}
|
||||
if (level < 30) {
|
||||
return 1
|
||||
}
|
||||
if (level < 40) {
|
||||
return 2
|
||||
}
|
||||
if (level < 50) {
|
||||
return 3
|
||||
}
|
||||
if (level < 60) {
|
||||
return 4
|
||||
}
|
||||
if (level < 70) {
|
||||
return 5
|
||||
}
|
||||
return 6
|
||||
}
|
||||
|
||||
|
||||
export function calcRarity(rarity: string) {
|
||||
if (rarity.includes("5")) {
|
||||
return 5
|
||||
}
|
||||
if (rarity.includes("4")) {
|
||||
return 4
|
||||
}
|
||||
if (rarity.includes("3")) {
|
||||
return 3
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
export function calcMainAffixBonus(affix?: AffixDetail, level?: number) {
|
||||
if (!affix || typeof level !== "number") return "0"
|
||||
const value = affix.base + affix.step * level;
|
||||
|
||||
if (mappingStats?.[affix.property].unit === "%") {
|
||||
return (value * 100).toFixed(1);
|
||||
}
|
||||
if (mappingStats?.[affix.property].name === "SPD") {
|
||||
return value.toFixed(1);
|
||||
}
|
||||
|
||||
return value.toFixed(0);
|
||||
}
|
||||
|
||||
export const calcAffixBonus = (affix?: AffixDetail, stepCount?: number, rollCount?: number) => {
|
||||
if (!affix || typeof stepCount !== "number" || typeof rollCount !== "number") return "0"
|
||||
if (mappingStats?.[affix.property].unit === "%") {
|
||||
return ((affix.base * rollCount + affix.step * stepCount) * 100).toFixed(1);
|
||||
}
|
||||
if (mappingStats?.[affix.property].name === "SPD") {
|
||||
return (affix.base * rollCount + affix.step * stepCount).toFixed(1);
|
||||
}
|
||||
return (affix.base * rollCount + affix.step * stepCount).toFixed(0);
|
||||
}
|
||||
|
||||
export const calcBaseStat = (baseStat: number, stepStat: number, roundFixed: number, level: number) => {
|
||||
const promotionStat = baseStat + stepStat * (level-1);
|
||||
return promotionStat.toFixed(roundFixed);
|
||||
}
|
||||
|
||||
export const calcBaseStatRaw = (baseStat?: number, stepStat?: number, level?: number) => {
|
||||
if (typeof baseStat !== "number" || typeof stepStat !== "number" || typeof level !== "number") return 0
|
||||
return baseStat + stepStat * (level-1);
|
||||
}
|
||||
|
||||
export const calcSubAffixBonusRaw = (affix?: AffixDetail, stepCount?: number, rollCount?: number, baseStat?: number) => {
|
||||
if (!affix || typeof stepCount !== "number" || typeof rollCount !== "number" || typeof baseStat !== "number") return 0
|
||||
if (ratioStats.includes(affix.property)) {
|
||||
return (affix.base * rollCount + affix.step * stepCount) * baseStat;
|
||||
}
|
||||
return affix.base * rollCount + affix.step * stepCount;
|
||||
}
|
||||
|
||||
export const calcMainAffixBonusRaw = (affix?: AffixDetail, level?: number, baseStat?: number) => {
|
||||
if (!affix || typeof level !== "number" || typeof baseStat !== "number") return 0
|
||||
const value = affix.base + affix.step * level;
|
||||
|
||||
if (ratioStats.includes(affix.property)) {
|
||||
return baseStat * value
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
export const calcBonusStatRaw = (affix?: string, baseStat?: number, bonusValue?: number) => {
|
||||
if (!affix || typeof baseStat !== "number" || typeof bonusValue !== "number") return 0
|
||||
if (ratioStats.includes(affix)) {
|
||||
return baseStat * bonusValue
|
||||
}
|
||||
return bonusValue
|
||||
}
|
||||
100
src/helper/connect.ts
Normal file
100
src/helper/connect.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
"use client"
|
||||
import { SendDataThroughProxy, SendDataToServer } from "@/lib/api/api"
|
||||
import useConnectStore from "@/stores/connectStore"
|
||||
import useUserDataStore from "@/stores/userDataStore"
|
||||
import { converterToFreeSRJson } from "./converterToFreeSRJson"
|
||||
import { psResponseSchema } from "@/zod"
|
||||
import useGlobalStore from "@/stores/globalStore"
|
||||
|
||||
export const connectToPS = async (): Promise<{ success: boolean, message: string }> => {
|
||||
const {
|
||||
connectionType,
|
||||
privateType,
|
||||
serverUrl,
|
||||
username,
|
||||
password
|
||||
} = useConnectStore.getState()
|
||||
const {setExtraData, setIsConnectPS} = useGlobalStore.getState()
|
||||
|
||||
let urlQuery = serverUrl
|
||||
if (!urlQuery.startsWith("http://") && !urlQuery.startsWith("https://")) {
|
||||
urlQuery = `http://${urlQuery}`
|
||||
}
|
||||
if (connectionType === "FireflyGo") {
|
||||
urlQuery = "http://localhost:21000/sync"
|
||||
} else if (connectionType === "Other" && privateType === "Server") {
|
||||
const response = await SendDataThroughProxy({data: {username, password, serverUrl, data: null, method: "POST"}})
|
||||
if (response instanceof Error) {
|
||||
return { success: false, message: response.message }
|
||||
} else if (response.error) {
|
||||
return { success: false, message: response.error }
|
||||
} else {
|
||||
const parsed = psResponseSchema.safeParse(response.data)
|
||||
if (!parsed.success) {
|
||||
return { success: false, message: "Invalid response schema" }
|
||||
}
|
||||
return { success: true, message: "" }
|
||||
}
|
||||
}
|
||||
const response = await SendDataToServer(username, password, urlQuery, null)
|
||||
if (typeof response === "string") {
|
||||
setIsConnectPS(false)
|
||||
return { success: false, message: response }
|
||||
} else if (response.status != 200) {
|
||||
setIsConnectPS(false)
|
||||
return { success: false, message: response.message }
|
||||
} else {
|
||||
setIsConnectPS(true)
|
||||
setExtraData(response?.extra_data)
|
||||
return { success: true, message: "" }
|
||||
}
|
||||
}
|
||||
|
||||
export const syncDataToPS = async (): Promise<{ success: boolean, message: string }> => {
|
||||
const {
|
||||
connectionType,
|
||||
privateType,
|
||||
serverUrl,
|
||||
username,
|
||||
password
|
||||
} = useConnectStore.getState()
|
||||
|
||||
const {extraData, setIsConnectPS, setExtraData} = useGlobalStore.getState()
|
||||
|
||||
|
||||
const {avatars, battle_type, moc_config, pf_config, as_config, ce_config, peak_config} = useUserDataStore.getState()
|
||||
const data = converterToFreeSRJson(avatars, battle_type, moc_config, pf_config, as_config, ce_config, peak_config)
|
||||
|
||||
let urlQuery = serverUrl
|
||||
if (!urlQuery.startsWith("http://") && !urlQuery.startsWith("https://")) {
|
||||
urlQuery = `http://${urlQuery}`
|
||||
}
|
||||
if (connectionType === "FireflyGo") {
|
||||
urlQuery = "http://localhost:21000/sync"
|
||||
} else if (connectionType === "Other" && privateType === "Server") {
|
||||
const response = await SendDataThroughProxy({data: {username, password, serverUrl, data, method: "POST"}})
|
||||
if (response instanceof Error) {
|
||||
return { success: false, message: response.message }
|
||||
} else if (response.error) {
|
||||
return { success: false, message: response.error }
|
||||
} else {
|
||||
const parsed = psResponseSchema.safeParse(response.data)
|
||||
if (!parsed.success) {
|
||||
return { success: false, message: "Invalid response schema" }
|
||||
}
|
||||
return { success: true, message: "" }
|
||||
}
|
||||
}
|
||||
const response = await SendDataToServer(username, password, urlQuery, data, extraData)
|
||||
if (typeof response === "string") {
|
||||
setIsConnectPS(false)
|
||||
return { success: false, message: response }
|
||||
} else if (response.status != 200) {
|
||||
setIsConnectPS(false)
|
||||
return { success: false, message: response.message }
|
||||
} else {
|
||||
setIsConnectPS(true)
|
||||
setExtraData(response?.extra_data)
|
||||
return { success: true, message: "" }
|
||||
}
|
||||
}
|
||||
142
src/helper/convertData.ts
Normal file
142
src/helper/convertData.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { CharacterBasic, CharacterBasicRaw, EventBasic, EventBasicRaw, LightConeBasic, LightConeBasicRaw, MonsterBasic, MonsterBasicRaw, RelicBasic, RelicBasicEffect, RelicBasicRaw } from "@/types";
|
||||
|
||||
export function convertRelicSet(id: string, item: RelicBasicRaw): RelicBasic {
|
||||
const lang = new Map<string, string>([
|
||||
['en', item.en],
|
||||
['kr', item.kr],
|
||||
['cn', item.cn],
|
||||
['jp', item.jp]
|
||||
]);
|
||||
|
||||
const setRelic = new Map<string, RelicBasicEffect>();
|
||||
|
||||
Object.entries(item.set).forEach(([key, value]) => {
|
||||
setRelic.set(key, {
|
||||
ParamList: value.ParamList,
|
||||
lang: new Map<string, string>([
|
||||
['en', value.en],
|
||||
['kr', value.kr],
|
||||
['cn', value.cn],
|
||||
['jp', value.jp]
|
||||
])
|
||||
});
|
||||
});
|
||||
|
||||
const result: RelicBasic = {
|
||||
icon: item.icon,
|
||||
lang: lang,
|
||||
id: id,
|
||||
set: setRelic
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function convertLightcone(id: string, item: LightConeBasicRaw): LightConeBasic {
|
||||
const lang = new Map<string, string>([
|
||||
['en', item.en],
|
||||
['kr', item.kr],
|
||||
['cn', item.cn],
|
||||
['jp', item.jp]
|
||||
]);
|
||||
const result: LightConeBasic = {
|
||||
rank: item.rank,
|
||||
baseType: item.baseType,
|
||||
desc: item.desc,
|
||||
lang: lang,
|
||||
id: id
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export function convertAvatar(id: string, item: CharacterBasicRaw): CharacterBasic {
|
||||
const lang = new Map<string, string>([
|
||||
['en', item.en],
|
||||
['kr', item.kr],
|
||||
['cn', item.cn],
|
||||
['jp', item.jp]
|
||||
]);
|
||||
let text = ""
|
||||
if (Number(id) % 2 === 0 && Number(id) > 8000) {
|
||||
text = `Female ${item.damageType} MC`
|
||||
} else if (Number(id) > 8000) {
|
||||
text = `Male ${item.damageType} MC`
|
||||
}
|
||||
if (text !== "") {
|
||||
lang.set("en", text)
|
||||
lang.set("kr", text)
|
||||
lang.set("cn", text)
|
||||
lang.set("jp", text)
|
||||
}
|
||||
const result: CharacterBasic = {
|
||||
release: item.release,
|
||||
icon: item.icon,
|
||||
rank: item.rank,
|
||||
baseType: item.baseType,
|
||||
damageType: item.damageType,
|
||||
desc: item.desc,
|
||||
lang: lang,
|
||||
id: id
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function convertEvent(id: string, item: EventBasicRaw): EventBasic {
|
||||
const lang = new Map<string, string>([
|
||||
['en', item.en],
|
||||
['kr', item.kr],
|
||||
['cn', item.cn],
|
||||
['jp', item.jp]
|
||||
]);
|
||||
const result: EventBasic = {
|
||||
lang: lang,
|
||||
id: id,
|
||||
begin: item.begin,
|
||||
end: item.end,
|
||||
live_begin: item.live_begin,
|
||||
live_end: item.live_end,
|
||||
param: item.param,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function convertMonster(id: string, item: MonsterBasicRaw): MonsterBasic {
|
||||
const lang = new Map<string, string>([
|
||||
['en', item.en],
|
||||
['kr', item.kr],
|
||||
['cn', item.cn],
|
||||
['jp', item.jp]
|
||||
]);
|
||||
const result: MonsterBasic = {
|
||||
id: id,
|
||||
rank: item.rank,
|
||||
camp: item.camp,
|
||||
icon: item.icon,
|
||||
child: item.child,
|
||||
weak: item.weak,
|
||||
desc: item.desc,
|
||||
lang: lang
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function convertToRoman(num: number): string {
|
||||
const roman: [number, string][] = [
|
||||
[1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'],
|
||||
[100, 'C'], [90, 'XC'], [50, 'L'], [40, 'XL'],
|
||||
[10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I']
|
||||
];
|
||||
let result = '';
|
||||
for (const [val, sym] of roman) {
|
||||
while (num >= val) {
|
||||
result += sym;
|
||||
num -= val;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
101
src/helper/converterToAvatarStore.ts
Normal file
101
src/helper/converterToAvatarStore.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { AvatarEnkaDetail, AvatarProfileStore, AvatarStore, CharacterDetail, FreeSRJson, RelicStore } from "@/types";
|
||||
|
||||
function safeNumber(val: string | number | null, fallback = 0): number {
|
||||
if (!val) return fallback;
|
||||
const num = Number(val);
|
||||
return Number.isFinite(num) && num !== 0 ? num : fallback;
|
||||
}
|
||||
|
||||
export function converterToAvatarStore(data: Record<string, CharacterDetail>): { [key: string]: AvatarStore } {
|
||||
return Object.fromEntries(
|
||||
Object.entries(data).map(([key, value]) => [
|
||||
key,
|
||||
{
|
||||
owner_uid: 0,
|
||||
avatar_id: Number(key),
|
||||
data: {
|
||||
rank: 0,
|
||||
skills: Object.values(value.SkillTrees).reduce((acc, dataPointEntry) => {
|
||||
const firstEntry = Object.values(dataPointEntry)[0];
|
||||
if (firstEntry) {
|
||||
acc[firstEntry.PointID] = firstEntry.MaxLevel;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, number>)
|
||||
},
|
||||
level: 80,
|
||||
promotion: 6,
|
||||
techniques: [],
|
||||
sp_max: safeNumber(value.SPNeed, 100),
|
||||
can_change_sp: safeNumber(value.SPNeed) !== 0,
|
||||
sp_value: Math.ceil(safeNumber(value.SPNeed) / 2),
|
||||
profileSelect: 0,
|
||||
enhanced: "",
|
||||
profileList: [{
|
||||
profile_name: "Default",
|
||||
lightcone: null,
|
||||
relics: {} as Record<string, RelicStore>
|
||||
} as AvatarProfileStore]
|
||||
}
|
||||
])
|
||||
) as { [key: string]: AvatarStore }
|
||||
}
|
||||
|
||||
export function converterOneEnkaDataToAvatarStore(data: AvatarEnkaDetail, count: number): AvatarProfileStore | null {
|
||||
if (!data.equipment && (!data.relicList || data.relicList.length === 0)) return null
|
||||
const profile: AvatarProfileStore = {
|
||||
profile_name: `Enka Profile ${count}`,
|
||||
lightcone: (data.equipment && data.equipment.tid) ? {
|
||||
level: data.equipment?.level ?? 0,
|
||||
item_id: data.equipment?.tid ?? 0,
|
||||
rank: data.equipment?.rank ?? 0,
|
||||
promotion: data.equipment?.promotion ?? 0,
|
||||
} : null,
|
||||
relics: Object.fromEntries(data.relicList.map((relic) => [relic.tid.toString()[relic.tid.toString().length - 1], {
|
||||
level: relic.level ?? 0,
|
||||
relic_id: relic.tid,
|
||||
relic_set_id: parseInt(relic.tid.toString().slice(1, -1), 10),
|
||||
main_affix_id: relic.mainAffixId,
|
||||
sub_affixes: relic.subAffixList.map((subAffix) => ({
|
||||
sub_affix_id: subAffix.affixId,
|
||||
count: subAffix.cnt,
|
||||
step: subAffix.step ?? 0
|
||||
}))
|
||||
}]))
|
||||
}
|
||||
return profile
|
||||
}
|
||||
|
||||
|
||||
export function converterOneFreeSRDataToAvatarStore(data: FreeSRJson, count: number , avatar_id: number): AvatarProfileStore | null {
|
||||
const lightcone = data.lightcones.find((lightcone) => lightcone.equip_avatar === avatar_id)
|
||||
const relics = data.relics.filter((relic) => relic.equip_avatar === avatar_id)
|
||||
if (!lightcone && (!relics || relics.length === 0)) return null
|
||||
const relicsMap = {} as Record<string, RelicStore>
|
||||
|
||||
relics.forEach((relic) => {
|
||||
relicsMap[relic.relic_id.toString()[relic.relic_id.toString().length - 1]] = {
|
||||
level: relic.level,
|
||||
relic_id: relic.relic_id,
|
||||
relic_set_id: relic.relic_set_id,
|
||||
main_affix_id: relic.main_affix_id,
|
||||
sub_affixes: relic.sub_affixes.map((subAffix) => ({
|
||||
sub_affix_id: subAffix.sub_affix_id,
|
||||
count: subAffix.count,
|
||||
step: subAffix.step ?? 0
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
const profile: AvatarProfileStore = {
|
||||
profile_name: `FreeSR Profile ${count}`,
|
||||
lightcone: (lightcone && lightcone.item_id) ? {
|
||||
level: lightcone?.level ?? 0,
|
||||
item_id: lightcone?.item_id ?? 0,
|
||||
rank: lightcone?.rank ?? 0,
|
||||
promotion: lightcone?.promotion ?? 0,
|
||||
} : null,
|
||||
relics: relicsMap
|
||||
}
|
||||
return profile
|
||||
}
|
||||
133
src/helper/converterToFreeSRJson.ts
Normal file
133
src/helper/converterToFreeSRJson.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { ASConfigStore, AvatarJson, AvatarStore, BattleConfigJson, CEConfigStore, FreeSRJson, LightconeJson, MOCConfigStore, PEAKConfigStore, PFConfigStore, RelicJson } from "@/types";
|
||||
|
||||
|
||||
export function converterToFreeSRJson(
|
||||
avatars: Record<string, AvatarStore>,
|
||||
battle_type: string,
|
||||
moc_config: MOCConfigStore,
|
||||
pf_config: PFConfigStore,
|
||||
as_config: ASConfigStore,
|
||||
ce_config: CEConfigStore,
|
||||
peak_config: PEAKConfigStore,
|
||||
): FreeSRJson {
|
||||
const lightcones: LightconeJson[] = []
|
||||
const relics: RelicJson[] = []
|
||||
let battleJson: BattleConfigJson
|
||||
if (battle_type === "MOC") {
|
||||
battleJson = {
|
||||
battle_type: battle_type,
|
||||
blessings: moc_config.blessings,
|
||||
custom_stats: [],
|
||||
cycle_count: moc_config.cycle_count,
|
||||
stage_id: moc_config.stage_id,
|
||||
path_resonance_id: 0,
|
||||
monsters: moc_config.monsters,
|
||||
}
|
||||
} else if (battle_type === "PF") {
|
||||
battleJson = {
|
||||
battle_type: battle_type,
|
||||
blessings: pf_config.blessings,
|
||||
custom_stats: [],
|
||||
cycle_count: pf_config.cycle_count,
|
||||
stage_id: pf_config.stage_id,
|
||||
path_resonance_id: 0,
|
||||
monsters: pf_config.monsters,
|
||||
}
|
||||
} else if (battle_type === "AS") {
|
||||
battleJson = {
|
||||
battle_type: battle_type,
|
||||
blessings: as_config.blessings,
|
||||
custom_stats: [],
|
||||
cycle_count: as_config.cycle_count,
|
||||
stage_id: as_config.stage_id,
|
||||
path_resonance_id: 0,
|
||||
monsters: as_config.monsters,
|
||||
}
|
||||
} else if (battle_type === "CE") {
|
||||
battleJson = {
|
||||
battle_type: battle_type,
|
||||
blessings: ce_config.blessings,
|
||||
custom_stats: [],
|
||||
cycle_count: ce_config.cycle_count,
|
||||
stage_id: ce_config.stage_id,
|
||||
path_resonance_id: 0,
|
||||
monsters: ce_config.monsters,
|
||||
}
|
||||
} else if (battle_type === "PEAK") {
|
||||
battleJson = {
|
||||
battle_type: battle_type,
|
||||
blessings: peak_config.blessings,
|
||||
custom_stats: [],
|
||||
cycle_count: peak_config.cycle_count,
|
||||
stage_id: peak_config.stage_id,
|
||||
path_resonance_id: 0,
|
||||
monsters: peak_config.monsters,
|
||||
}
|
||||
} else {
|
||||
battleJson = {
|
||||
battle_type: battle_type,
|
||||
blessings: [],
|
||||
custom_stats: [],
|
||||
cycle_count: 0,
|
||||
stage_id: 0,
|
||||
path_resonance_id: 0,
|
||||
monsters: [],
|
||||
}
|
||||
}
|
||||
|
||||
const avatarsJson: { [key: string]: AvatarJson } = {}
|
||||
let internalUidLightcone = 0
|
||||
let internalUidRelic = 0
|
||||
Object.entries(avatars).forEach(([avatarId, avatar]) => {
|
||||
avatarsJson[avatarId] = {
|
||||
owner_uid: Number(avatar.owner_uid || 0),
|
||||
avatar_id: Number(avatar.avatar_id || 0),
|
||||
data: avatar.data,
|
||||
level: Number(avatar.level || 0),
|
||||
promotion: Number(avatar.promotion || 0),
|
||||
techniques: avatar.techniques,
|
||||
sp_value: Number(avatar.sp_value || 0),
|
||||
sp_max: Number(avatar.sp_max || 0),
|
||||
}
|
||||
const currentProfile = avatar.profileList[avatar.profileSelect]
|
||||
if (currentProfile.lightcone && currentProfile.lightcone.item_id !== 0) {
|
||||
const newLightcone: LightconeJson = {
|
||||
level: Number(currentProfile.lightcone.level || 0),
|
||||
item_id: Number(currentProfile.lightcone.item_id || 0),
|
||||
rank: Number(currentProfile.lightcone.rank || 0),
|
||||
promotion: Number(currentProfile.lightcone.promotion || 0),
|
||||
internal_uid: internalUidLightcone,
|
||||
equip_avatar: Number(avatar.avatar_id || 0),
|
||||
}
|
||||
internalUidLightcone++
|
||||
lightcones.push(newLightcone)
|
||||
}
|
||||
|
||||
if (currentProfile.relics) {
|
||||
["1", "2", "3", "4", "5", "6"].forEach(slot => {
|
||||
const relic = currentProfile.relics[slot]
|
||||
if (relic && relic.relic_id !== 0) {
|
||||
const newRelic: RelicJson = {
|
||||
level: Number(relic.level || 0),
|
||||
relic_id: Number(relic.relic_id || 0),
|
||||
relic_set_id: Number(relic.relic_set_id || 0),
|
||||
main_affix_id: Number(relic.main_affix_id || 0),
|
||||
sub_affixes: relic.sub_affixes,
|
||||
internal_uid: internalUidRelic,
|
||||
equip_avatar: Number(avatar.avatar_id || 0),
|
||||
}
|
||||
internalUidRelic++
|
||||
relics.push(newRelic)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
return {
|
||||
lightcones,
|
||||
relics,
|
||||
avatars: avatarsJson,
|
||||
battle_config: battleJson,
|
||||
}
|
||||
}
|
||||
45
src/helper/getName.ts
Normal file
45
src/helper/getName.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { listCurrentLanguage } from "@/constant/constant";
|
||||
import { CharacterBasic, EventBasic, LightConeBasic, MonsterBasic } from "@/types";
|
||||
|
||||
|
||||
export function getNameChar(locale: string, data: CharacterBasic | undefined): string {
|
||||
if (!data) {
|
||||
return ""
|
||||
}
|
||||
if (!listCurrentLanguage.hasOwnProperty(locale)) {
|
||||
return ""
|
||||
}
|
||||
|
||||
let text = data.lang.get(listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase()) ?? "";
|
||||
if (!text) {
|
||||
text = data.lang.get("en") ?? "";
|
||||
}
|
||||
if (Number(data.id) % 2 === 0 && Number(data.id) > 8000) {
|
||||
text = `Female ${data.damageType} MC`
|
||||
} else if (Number(data.id) > 8000) {
|
||||
text = `Male ${data.damageType} MC`
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
export function getLocaleName(locale: string, data: LightConeBasic | EventBasic | MonsterBasic | undefined): string {
|
||||
if (!data) {
|
||||
return ""
|
||||
}
|
||||
if (!listCurrentLanguage.hasOwnProperty(locale)) {
|
||||
return ""
|
||||
}
|
||||
|
||||
let text = data.lang.get(listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase()) ?? "";
|
||||
if (!text) {
|
||||
text = data.lang.get("en") ?? "";
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
export function parseRuby(text: string): string {
|
||||
const rubyRegex = /\{RUBY_B#(.*?)\}(.*?)\{RUBY_E#\}/gs;
|
||||
return text.replace(rubyRegex, (_match, furigana, kanji) => {
|
||||
return `<ruby>${kanji}<rt>${furigana}</rt></ruby>`;
|
||||
});
|
||||
}
|
||||
23
src/helper/getSkillTree.ts
Normal file
23
src/helper/getSkillTree.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import useAvatarStore from "@/stores/avatarStore";
|
||||
|
||||
export function getSkillTree(enhanced: string) {
|
||||
const { avatarSelected, mapAvatarInfo } = useAvatarStore.getState()
|
||||
|
||||
if (!avatarSelected) return null;
|
||||
if (enhanced != "") return Object.values(mapAvatarInfo[avatarSelected.id || ""]?.Enhanced[enhanced].SkillTrees || {}).reduce((acc, dataPointEntry) => {
|
||||
const firstEntry = Object.values(dataPointEntry)[0];
|
||||
if (firstEntry) {
|
||||
acc[firstEntry.PointID] = firstEntry.MaxLevel;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, number>)
|
||||
|
||||
return Object.values(mapAvatarInfo[avatarSelected.id || ""]?.SkillTrees).reduce((acc, dataPointEntry) => {
|
||||
const firstEntry = Object.values(dataPointEntry)[0];
|
||||
if (firstEntry) {
|
||||
acc[firstEntry.PointID] = firstEntry.MaxLevel;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
}
|
||||
|
||||
8
src/helper/index.ts
Normal file
8
src/helper/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export * from "./getName"
|
||||
export * from "./replaceByParam"
|
||||
export * from "./converterToAvatarStore"
|
||||
export * from "./calcData"
|
||||
export * from "./random"
|
||||
export * from "./json"
|
||||
export * from "./convertData"
|
||||
export * from "./connect"
|
||||
15
src/helper/json.ts
Normal file
15
src/helper/json.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function downloadJson(fileName: string, data: any) {
|
||||
const json = JSON.stringify(data, null, 2)
|
||||
const blob = new Blob([json], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `${fileName}.json`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
27
src/helper/random.ts
Normal file
27
src/helper/random.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export function randomPartition(sum: number, parts: number): number[] {
|
||||
const raw = Array.from({ length: parts }, () => Math.random());
|
||||
const total = raw.reduce((a, b) => a + b, 0);
|
||||
const result = raw.map(r => Math.floor((r / total) * (sum - parts)) + 1);
|
||||
let diff = sum - result.reduce((a, b) => a + b, 0);
|
||||
while (diff !== 0) {
|
||||
for (let i = 0; i < result.length && diff !== 0; i++) {
|
||||
if (diff > 0) {
|
||||
result[i]++;
|
||||
diff--;
|
||||
} else if (result[i] > 1) {
|
||||
result[i]--;
|
||||
diff++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function randomStep(x: number): number {
|
||||
let total = 0;
|
||||
for (let i = 0; i < x; i++) {
|
||||
total += Math.floor(Math.random() * 3);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
52
src/helper/replaceByParam.ts
Normal file
52
src/helper/replaceByParam.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
export function replaceByParam(desc: string, params: number[]): string {
|
||||
function formatParam(
|
||||
indexStr: string,
|
||||
format: string,
|
||||
floatDigits: string | undefined,
|
||||
percent: string | undefined
|
||||
): string {
|
||||
const i: number = parseInt(indexStr, 10) - 1;
|
||||
const value: number | undefined = params[i];
|
||||
if (value === undefined) return "";
|
||||
|
||||
if (format.startsWith("f")) {
|
||||
const digits: number = parseInt(floatDigits || "1", 10);
|
||||
const num: number = percent ? value * 100 : value;
|
||||
return `${num.toFixed(digits)}${percent ? "%" : ""}`;
|
||||
}
|
||||
|
||||
if (format === "i") {
|
||||
return percent ? `${(value * 100).toFixed(0)}%` : `${Math.round(value)}`;
|
||||
}
|
||||
|
||||
return `${value}`;
|
||||
}
|
||||
|
||||
const desc1 = desc.replace(/<color=#[0-9a-fA-F]{8}>(.*?)<\/color>/g, (match: string, inner: string): string => {
|
||||
const colorCode: string = match.match(/#[0-9a-fA-F]{8}/)?.[0] ?? "#ffffff";
|
||||
const processed: string = inner
|
||||
.replace(/#(\d+)\[(f(\d+)|i)\](%)?/g, (
|
||||
_: string,
|
||||
index: string,
|
||||
format: string,
|
||||
floatDigits: string | undefined,
|
||||
percent: string | undefined
|
||||
): string => formatParam(index, format, floatDigits, percent))
|
||||
.replace(/<unbreak>(.*?)<\/unbreak>/g, "$1");
|
||||
|
||||
return `<span style="color:${colorCode}">${processed}</span>`;
|
||||
});
|
||||
|
||||
const desc2 = desc1.replace(/<unbreak>#(\d+)\[(f(\d+)|i)\](%)?<\/unbreak>/g, (
|
||||
_: string,
|
||||
index: string,
|
||||
format: string,
|
||||
floatDigits: string | undefined,
|
||||
percent: string | undefined
|
||||
): string => formatParam(index, format, floatDigits, percent));
|
||||
|
||||
const desc3 = desc2.replace(/<unbreak>(\d+)<\/unbreak>/g, (_: string, number: string): string => number);
|
||||
|
||||
const desc4 = desc3.replaceAll("\\n", "<br></br>");
|
||||
return desc4;
|
||||
}
|
||||
Reference in New Issue
Block a user