const path = require('path'); const fs = require('fs'); const textMapFolderPath = path.resolve('../Resources/TextMap'); const textMapFiles = fs.readdirSync(textMapFolderPath); const getHashEN = readJsonFile('../Resources/TextMap/TextMapEN.json'); const getHashCN = readJsonFile('../Resources/TextMap/TextMapCHS.json'); // beta stuff // TODO: API IMAGE function runAvatar(lang, getHashLANG) { const saveAvatar = lang + `avatar.json`; const getAvatar = readJsonFile('../Resources/ExcelBinOutput/AvatarExcelConfigData.json'); var dataAvatar = []; for (const data of Object.values(getAvatar)) { if (data && data.nameTextMapHash && data.nameTextMapHash) { const hash = data.nameTextMapHash; const id = data.id; if (id < 10000002 || id >= 11000000) continue; const iconName = data.iconName; const name = getHashLANG[hash] || getHashCN[hash] || `N/A`; var objAvatar = new Object(); objAvatar["id"] = id; objAvatar["name"] = name; objAvatar['img'] = `https://upload-os-bbs.mihoyo.com/game_record/genshin/character_icon/${iconName}.png` // TODO: download all image to localhost or just dump it bruh dataAvatar.push(objAvatar) } else { console.log("skip", data); } } fs.writeFileSync(saveAvatar, JSON.stringify(dataAvatar, null, 2)); dataAvatar = []; // cleanup console.log(`done avatar: ` + saveAvatar) } function runScene(lang, getHashLANG) { const saveScene = lang + `scene.json`; const getScene = readJsonFile('../Resources/ExcelBinOutput/SceneExcelConfigData.json'); var dataScene = []; for (const data of Object.values(getScene)) { if (data && data.id) { const id = data.id; const name = `${data.scriptData}` + (data.levelEntityConfig == "" ? "" : " (" + data.levelEntityConfig + ")") var objScene = new Object(); objScene["id"] = id; objScene["name"] = `${name}`; dataScene.push(objScene) } else { console.log("skip", data); } } console.log(`Done scene: ` + saveScene) fs.writeFileSync(saveScene, JSON.stringify(dataScene, null, 2)); dataScene = []; // cleanup /* const saveStage = lang + `stage.json`; const getStage = readJsonFile('../Resources/ExcelOutput/StageConfig.json'); for (const data of Object.values(getStage)) { if (data && data.StageName && data.StageName.Hash) { const hash = data.StageName.Hash; const id = data.StageID; const name = getHashLANG[hash] || getHashCN[hash] || `N/A`; var objScene = new Object(); objScene["id"] = id; objScene["name"] = `${name} (${data.StageType} #${data.Level})`; dataScene.push(objScene) } else { console.log("skip", data); } } console.log(`Done stage: ` + saveStage) fs.writeFileSync(saveStage, JSON.stringify(dataScene, null, 2)); dataScene = []; // cleanup */ } function runMonster(lang, getHashLANG) { const saveMonster = lang + 'monster.json'; const getMonster = readJsonFile('../Resources/ExcelBinOutput/MonsterExcelConfigData.json'); var dataMonster = []; for (const data of Object.values(getMonster)) { if (data && data.nameTextMapHash) { const hash = data.nameTextMapHash; const id = data.id; const name = getHashLANG[hash] || getHashCN[hash] || data.monsterName || `N/A`; var objMonster = new Object(); objMonster["id"] = id; objMonster["name"] = name; dataMonster.push(objMonster) } else { console.log("skip", data); } } fs.writeFileSync(saveMonster, JSON.stringify(dataMonster, null, 2)); dataMonster = []; // cleanup console.log(`done monster: ` + saveMonster) } function runProp(lang, getHashLANG) { const saveProp = lang + 'prop.json'; const getProp = readJsonFile('../Resources/ExcelBinOutput/GadgetExcelConfigData.json'); var dataProp = []; for (const data of Object.values(getProp)) { if (data && data.nameTextMapHash) { const hash = data.nameTextMapHash; const hash2 = data.interactNameTextMapHash; const id = data.id; const NamePath = data.jsonName; const name = (getHashLANG[hash] || getHashCN[hash] || getHashLANG[hash2] || getHashCN[hash2] || `N/A`) + (NamePath == "" ? "" : " (" + NamePath + ")") var objProp = new Object(); objProp["id"] = id; objProp["name"] = name; dataProp.push(objProp) } else { console.log("skip", data); } } fs.writeFileSync(saveProp, JSON.stringify(dataProp, null, 2)); dataProp = []; // cleanup console.log(`done Prop: ` + saveProp) } function runItem(lang, getHashLANG) { const saveItem = lang + 'item.json'; var dataItem = []; const filePaths = [ '../Resources/ExcelBinOutput/MaterialExcelConfigData.json', // '../Resources/ExcelBinOutput/WeaponExcelConfigData.json', //'../Resources/ExcelBinOutput/ReliquaryExcelConfigData.json', '../Resources/ExcelBinOutput/HomeWorldFurnitureExcelConfigData.json' ]; filePaths.forEach(filePath => { const getItem = readJsonFile(filePath); for (const data of Object.values(getItem)) { if (data && data.nameTextMapHash) { const hash = data.nameTextMapHash; const id = data.id; const name = getHashLANG[hash] || getHashCN[hash] || `N/A`; var iconPath = data.icon; // UI_EquipIcon_${item.type}_${item.id} var image = `https://upload-os-bbs.mihoyo.com/game_record/genshin/equip/${iconPath}.png`; var objItem = new Object(); objItem["id"] = id; objItem["name"] = `${name}`; objItem["type"] = data.itemType; objItem["rank"] = data.rank; objItem["img"] = image dataItem.push(objItem); } else { console.log("skip", data); } } }); fs.writeFileSync(saveItem, JSON.stringify(dataItem, null, 2)); dataItem = []; // cleanup console.log(`done item: ` + saveItem) } function runWeapon(lang, getHashLANG) { const saveItem = lang + 'weapon.json'; var dataItem = []; const filePaths = [ '../Resources/ExcelBinOutput/WeaponExcelConfigData.json' // light cone ? ]; filePaths.forEach(filePath => { const getItem = readJsonFile(filePath); for (const data of Object.values(getItem)) { if (data && data.nameTextMapHash) { const hash = data.nameTextMapHash; const id = data.id; const name = getHashLANG[hash] || getHashCN[hash] || `N/A`; var subType = data.weaponType; var iconPath = data.icon; var image = `https://upload-os-bbs.mihoyo.com/game_record/genshin/equip/${iconPath}.png`; var objItem = new Object(); objItem["id"] = id; objItem["name"] = `${name}`; objItem["type_main"] = data.itemType; objItem["type_sub"] = subType; objItem["rank"] = data.rankLevel; objItem["img"] = image dataItem.push(objItem); } else { console.log("skip", data); } } }); fs.writeFileSync(saveItem, JSON.stringify(dataItem, null, 2)); dataItem = []; // cleanup console.log(`done wp: ` + saveItem) } const getRelicConfig = readJsonFile('../Resources/ExcelBinOutput/ReliquaryExcelConfigData.json'); const getRelicSub = readJsonFile('../Resources/ExcelBinOutput/ReliquaryAffixExcelConfigData.json'); // sub const getAvatarProp = readJsonFile('../Resources/ExcelBinOutput/AvatarCurveExcelConfigData.json'); const getHasIndex1 = readJsonFile(`../Resources/ExcelBinOutput/ManualTextMapConfigData.json`) // Main const getRelicMain = readJsonFile('../Resources/ExcelBinOutput/ReliquaryMainPropExcelConfigData.json'); const levelConfig = readJsonFile(`../Resources/ExcelBinOutput/ReliquaryLevelExcelConfigData.json`) // Simulator // /give 76544 lv1 x1 15001 501064,1 501204,1 501224,1 501234,1 function runGiveEmu(cmd) { const parts = cmd.split(" "); const action = parts[1]; const item_id = parseInt(action); let level = 1; let item_count = 1; let refined_count = 0; let main_prop_id = 0; let propDataCount = 0; var maxStep = false; var maxLevel = 999; if (parts.length > 2) { let currentParamIndex = 2; while (currentParamIndex < parts.length) { const param = parts[currentParamIndex]; //console.log(param) if (param.startsWith("x")) { item_count = parseInt(param.replace("x", "")); } else if (param.startsWith("s")) { //main_prop_id = parseInt(param.replace("s", "")); } else if (param.startsWith("lv")) { level = parseInt(param.replace("lv", "")) + 1; console.log(`found level: ` + level) } else if (param.startsWith("r")) { refined_count = parseInt(param.replace("r", "")) + 1; } else if (param.startsWith("sorted")) { //continue } else if (param.startsWith("-maxsteps")) { maxStep = true; //console.log(`found max`) } else { main_prop_id = parseInt(param) var dataRelic = getRelicConfig.find(entry => entry.id == item_id); console.log(dataRelic) var dataRelicMain = getRelicMain.find(entry => entry.id == main_prop_id); if (dataRelicMain) { //console.log(dataRelicMain) //console.log(`RelicMainAffixConfig ${dataRelic.MainAffixGroup} > ${main_prop_id}`) // Get Name var mainPropHash = getHasIndex1.find(item => item.textMapId === dataRelicMain.propType ).textMapContentTextMapHash; const nameMain = getHashEN[mainPropHash] || getHashCN[mainPropHash] || `N/A`; //console.log(dataRelic.rankLevel) var valueMain = 0; var TesLevel = levelConfig.find(entry => entry.level === level && entry.rank === dataRelic.rankLevel && entry.addProps.some(prop => prop.propType === dataRelicMain.propType) ); if (!TesLevel) { //var tus1 = getRelicSub.find(entry => entry.propType == dataRelicMain.propType); //valueMain = tus1.propValue //console.log(`${dataRelicMain.propType} > ${main_prop_id} > ${valueMain}`, tus1) //continue; console.log('not found data...') return } else { var tus2 = TesLevel.addProps.find(prop => prop.propType === dataRelicMain.propType); //console.log(tus2) valueMain = tus2.value; } var lo = CaC(dataRelicMain.propType, valueMain); console.log(`Main ${nameMain}: ${lo}`) // Add sub prop for (let i = currentParamIndex; i < parts.length; i++) { const propData = parts[i].split(/[,:;.]/); const prop_id = parseInt(propData[0]); let prop_count = parseInt(propData[1]); if (isNaN(prop_count)) { continue } //console.log(`${prop_id} > ${prop_count}`) var SubData = getRelicSub.find(prop => prop.id === prop_id); if (!SubData) continue //console.log(SubData) // limit count base level max art const maxCount = Math.floor(maxLevel / 3) + 1; // or just use 15 ? const count = Math.min(prop_count, maxCount); // fix step count var finalValue = SubData.propValue; if (count >= 2) { finalValue = finalValue * count } // Get Name var subPropHash = getHasIndex1.find(item => item.textMapId === SubData.propType ).textMapContentTextMapHash; const nameSub = getHashEN[subPropHash] || getHashCN[subPropHash] || `N/A`; var lo = CaC(SubData.propType, finalValue); console.log(`Sub ${nameSub}: ${lo}`) propDataCount++; } } else { console.log(`not found`) } break; } currentParamIndex++; } } else { return console.log(`bad cmd`) } //console.log(extra_params) console.log(`${cmd}`) } function CaC(name, finalValue) { if (name.includes('PERCENT') || name.includes('CRITICAL') || name.includes('CHARGE') || name.includes('HURT') || name.includes('HEAL')) { return `${(finalValue * 100).toFixed(2)}%` } else { return `+${Math.floor(finalValue)}` } } function runRelic(lang, getHashLANG) { // lock basic stats var maxLevel = 21; var maxRank = 5; const saveMainRelic = lang + 'relic_main.json'; var dataMainRelic = []; for (const data of Object.values(getRelicMain)) { var id = data.id; var name = data.propType; // Get Name var mainPropHash = getHasIndex1.find(item => item.textMapId === name).textMapContentTextMapHash; const nameMain = getHashLANG[mainPropHash] || getHashCN[mainPropHash] || `N/A`; var valueMain = 0; var TesLevel = levelConfig.find(entry => entry.level === maxLevel && entry.rank === maxRank && entry.addProps.some(prop => prop.propType === name) ); if (!TesLevel) { //valueMain = getRelicSub.find(entry => entry.propType == name).propValue continue; } else { valueMain = TesLevel.addProps.find(prop => prop.propType === name).value; } var bonus = CaC(name, valueMain) var objMainRelic = new Object(); objMainRelic["id"] = id; objMainRelic["grup"] = data.propDepotId objMainRelic["name"] = `${nameMain} (${bonus} > R${maxRank}LV${maxLevel})`; dataMainRelic.push(objMainRelic) } fs.writeFileSync(saveMainRelic, JSON.stringify(dataMainRelic, null, 2)); dataMainRelic = []; // cleanup console.log(`done MainRelic: ` + saveMainRelic) const saveSubRelic = lang + 'relic_sub.json'; var dataSubRelic = []; for (const sub of Object.values(getRelicSub)) { var id = sub.id; var name = sub.propType; var mainPropHash = getHasIndex1.find(item => item.textMapId === name).textMapContentTextMapHash; const nameMain = getHashLANG[mainPropHash] || getHashCN[mainPropHash] || `N/A`; var bonus = CaC(name, sub.propValue) var objSubRelic = new Object(); objSubRelic["id"] = id; objSubRelic["grup"] = sub.depotId objSubRelic["name"] = `${nameMain} (${bonus})`; dataSubRelic.push(objSubRelic) } fs.writeFileSync(saveSubRelic, JSON.stringify(dataSubRelic, null, 2)); dataSubRelic = []; // cleanup console.log(`done SubRelic: ` + saveSubRelic) const saveItemRelic = lang + 'relic_item.json'; const getItemRelic = readJsonFile('../Resources/ExcelBinOutput/ReliquaryExcelConfigData.json'); var dataItemRelic = []; const uniqueMainSub = new Set(); for (const data of Object.values(getItemRelic)) { const hash = data.nameTextMapHash; const id = data.id; //var config = getRelicConfig[id]; const name = getHashLANG[hash] || getHashCN[hash] || `N/A`; // var subType = data.ItemSubType; var iconPath = data.icon; var image = `https://upload-os-bbs.mihoyo.com/game_record/genshin/equip/${iconPath}.png`; var IndexCheck = getHasIndex1.find(item => item.textMapId === data.equipType); if (!IndexCheck) { console.log(data.equipType) continue; } var indexType = IndexCheck.textMapContentTextMapHash const IndexName = getHashLANG[indexType] || getHashCN[indexType] || `N/A`; //var idgu = data.appendPropNum ?? 0; var main = data.mainPropDepotId; var sub = data.appendPropDepotId; var rank = data.rankLevel; var subp = `${name} (${IndexName}) (R${rank})`; if (!uniqueMainSub.has(subp)) { uniqueMainSub.add(subp); var objItem = new Object(); objItem["id"] = id; //objItem["num"] = idgu; objItem["name"] = subp; objItem["rarity"] = data.Rarity; objItem["rank"] = rank; objItem["main"] = main; objItem["sub"] = sub; objItem["type"] = data.equipType; objItem["img"] = image dataItemRelic.push(objItem) } } fs.writeFileSync(saveItemRelic, JSON.stringify(dataItemRelic, null, 2)); dataItemRelic = []; // cleanup console.log(`done Item Relic: ` + saveItemRelic) } async function runMappings(version = 1) { var dataMappings = {}; const saveMappings = '../Tool/data/mappings.json'; for (const textMapFile of textMapFiles) { var lang = textMapFile.replace("TextMap", "").replace(".json", ""); if (version == 1) { const textMapFilePath = path.resolve(textMapFolderPath, textMapFile); const textMapData = await readJsonFileAsync(textMapFilePath); // Initialize the object if it's undefined if (!dataMappings[mapLanguageCode(lang, true)]) { dataMappings[mapLanguageCode(lang, true)] = {}; } const filePaths = [ '../Resources/ExcelBinOutput/AvatarExcelConfigData.json', '../Resources/ExcelBinOutput/WeaponExcelConfigData.json' ]; for (const filePath of filePaths) { try { const getAvatar = await readJsonFileAsync(filePath); for (const data of Object.values(getAvatar)) { if (data && data.nameTextMapHash) { const hash = data.nameTextMapHash; var id = data.id; const rank = getColorByRankLevel(data.qualityType || data.rankLevel.toString()); const name = textMapData[hash] || `N/A`; var nameType = "idk"; if (filePath.includes(`AvatarExcelConfigData`)) { /* if (id >= 11000000) { continue // skip test avatar } */ id = id % 1000 + 1000; nameType = textMapData[`4233146695`] || `N/A`; } else if (filePath.includes(`WeaponExcelConfigData`)) { /* if (id <= 11101 || id >= 20000) { continue // skip non weapon items } */ nameType = textMapData[`4231343903`] || `N/A`; } dataMappings[mapLanguageCode(lang, true)][id] = [`${name} (${nameType})`, rank]; } else { console.log("skip", data); } } } catch (error) { console.error("Error processing file:", error); } } // Type Banner dataMappings[mapLanguageCode(lang, true)][200] = textMapData[`332935371`] || `N/A`; // Standard Wish dataMappings[mapLanguageCode(lang, true)][301] = textMapData[`2272170627`] || `N/A`; // Character Event Wish dataMappings[mapLanguageCode(lang, true)][400] = textMapData[`3352513147`] || `N/A`; // Character Event Wish-2 dataMappings[mapLanguageCode(lang, true)][302] = textMapData[`2864268523`] || `N/A`; // Weapon Event Wish } else { const langDirectory = `../Tool/resources/${mapLanguageCode(lang, false)}/`; if (!fs.existsSync(langDirectory)) { fs.mkdirSync(langDirectory, { recursive: true }); } const getHashLANG = readJsonFile(`../Resources/TextMap/TextMap${lang}.json`); runAvatar(langDirectory, getHashLANG) runScene(langDirectory, getHashLANG) runMonster(langDirectory, getHashLANG) runItem(langDirectory, getHashLANG) runRelic(langDirectory, getHashLANG) runWeapon(langDirectory, getHashLANG) runProp(langDirectory, getHashLANG) } } if (version == 1) { await writeFileAsync(saveMappings, JSON.stringify(dataMappings, null, 2), 'utf-8'); } } //runAvatar(); //runScene(); //runMonster(); //runItem(); //runRelic(); //runWeapon(); //runProp(); //runMappings(2); runMappings(1); //runGiveEmu(`/give 76544 lv19 x1 15001 501064,10 501204,10 501224,10 501234,10`); //runGiveEmu(`/give 63036 lv15 s1 1:99 2:1 3:1 4:1`); //runGiveEmu(`/give 63116 lv15 s4 4:1 7:2 8:1 9:5`); //runGiveEmu(`/give 63115 lv15 s10 5:1 7:1 8:1 9:6`); //runGiveEmu(`/give 61013 lv20 s1 1:10000000 5:100000000 7:100000 10:100000`) // Function to read and parse JSON file function readJsonFile(filePath) { const fileContent = fs.readFileSync(filePath, 'utf-8'); return JSON.parse(fileContent); } async function readJsonFileAsync(filePath) { try { const fileContent = await new Promise((resolve, reject) => { fs.readFile(filePath, 'utf-8', (err, data) => { if (err) reject(err); else resolve(data); }); }); return JSON.parse(fileContent); } catch (error) { console.error(`Error reading JSON file at ${filePath}:`, error); throw error; } } async function writeFileAsync(filePath, content, encoding = 'utf-8') { return new Promise((resolve, reject) => { fs.writeFile(filePath, content, encoding, (err) => { if (err) reject(err); else resolve(); }); }); } function mapLanguageCode(languageCode, useLowerCase = false) { // http://www.lingoes.net/en/translator/langcode.htm const languageMap = { CHS: 'zh_CN', CHT: 'zh_TW', DE: 'de_DE', // TODO EN: 'en_US', ES: 'es_ES', FR: 'fr_FR', ID: 'id_ID', JP: 'ja_JP', KR: 'ko_KR', PT: 'pt_PT', // TODO RU: 'ru_RU', TH: 'th_TH', VI: 'vi_VN', IT: 'it_IT', // TODO TR: `tr_TR` // TODO }; // Check if the language code has a mapping, otherwise use the original code var tus = languageMap[languageCode] || languageCode; if (useLowerCase) { tus = tus.toLowerCase(); } return tus; } function getColorByRankLevel(rankLevel) { switch (rankLevel) { case "3", "QUALITY_BLUE": return "blue"; case "4", "QUALITY_PURPLE": return "purple"; case "5", "QUALITY_ORANGE": return "yellow"; default: return ""; } }