diff --git a/myscript.js b/myscript.js new file mode 100644 index 000000000..363bf7715 --- /dev/null +++ b/myscript.js @@ -0,0 +1,420 @@ +const fs = require('fs'); + +const xavatar = require(".../ExcelBinOutput/AvatarExcelConfigData.json"); // array +// avatar extra info +const xextrainfo = require('.../ExcelBinOutput/FetterInfoExcelConfigData.json'); + +const xskilldepot = require('.../ExcelBinOutput/AvatarSkillDepotExcelConfigData.json'); +const xtalent = require('.../ExcelBinOutput/AvatarSkillExcelConfigData.json'); // combat talents +const xpassive = require('.../ExcelBinOutput/ProudSkillExcelConfigData.json'); // passive talents +const xconstellation = require('.../ExcelBinOutput/AvatarTalentExcelConfigData.json'); + +const xweapon = require('.../ExcelBinOutput/WeaponExcelConfigData.json'); +const xrefine = require('.../ExcelBinOutput/EquipAffixExcelConfigData.json'); + +const xmanualtext = getExcel('ManualTextMapConfigData'); //require('.../ExcelBinOutput/ManualTextMapConfigData.json'); + +const langcodes = ['CHS', 'CHT', 'DE', 'EN', 'ES', 'FR', 'ID', 'JA', 'KO', 'PT', 'RU', 'TH', 'VI']; + +function getExcel(file) { + return require(`.../ExcelBinOutput/${file}.json`); +} + +/* ========================================================================================== */ + +// object map that converts the genshin coded element into a TextMapHash +const elementTextMapHash = ['Fire', 'Water', 'Grass', 'Electric', 'Wind', 'Ice', 'Rock'].reduce((accum, element) => { + accum[element] = xmanualtext.find(ele => ele.TextMapId === element).TextMapContentTextMapHash; + return accum; +}, {}); + +const xplayableWeapon = xweapon.filter(obj => { + if(obj.RankLevel >= 3 && obj.SkillAffix[0] === 0) return false; + if(obj.SkillAffix[1] !== 0) { console.log('danger'); return false }; + if(getLanguage('EN')[obj.NameTextMapHash] === '') return false; + return true; +}) + +const xplayableAvatar = xavatar.filter(obj => obj.AvatarPromoteId !== 2); // array +// object map that converts an avatar Id or traveler SkillDepotId to filename +const avatarIdToFileName = xplayableAvatar.reduce((accum, obj) => { + if(obj.Id === 10000005) accum[obj.Id] = 'aether'; + else if(obj.Id === 10000007) accum[obj.Id] = 'lumine'; + else accum[obj.Id] = makeFileName(getLanguage('EN')[obj.NameTextMapHash]); + if(isPlayer(obj)) { // + obj.CandSkillDepotIds.forEach(skdeId => { + let trelement = elementTextMapHash[getPlayerElement(skdeId)]; + if(trelement === undefined) return; + accum[skdeId] = makeFileName(getLanguage('EN')[obj.NameTextMapHash] + getLanguage('EN')[trelement]); + }) + } + return accum; +}, {}); + +// const weaponIdToFileName = xweapon.reduce((accum, obj) => { +// accum[obj.Id] = + +// }, {}) + +// object map that converts a WeaponType into a TextMapHash +const weaponTextMapHash = ['WEAPON_SWORD_ONE_HAND', 'WEAPON_CATALYST', 'WEAPON_CLAYMORE', 'WEAPON_BOW', 'WEAPON_POLE'].reduce((accum, str) => { + accum[str] = xmanualtext.find(ele => ele.TextMapId === str).TextMapContentTextMapHash; + return accum; +}, {}); + +// UNUSED object map that converts AvatarAssocType into a TextMapHash +const assocTextMapHash = ['ASSOC_TYPE_MONDSTADT', 'ASSOC_TYPE_LIYUE', 'ASSOC_TYPE_FATUI']; + +// object map that converts relic EquipType to a property name +const relicTypeToPropertyName = { 'EQUIP_BRACER': 'flower', 'EQUIP_NECKLACE': 'plume', 'EQUIP_SHOES': 'sands', 'EQUIP_RING': 'goblet', 'EQUIP_DRESS': 'circlet'}; + +// object map that converts index to the talent type +const talentCombatTypeMap = { '0': 'combat1', '1': 'combat2', '2': 'combatsp', '4': 'combat3' }; + +// object map that converts player's avatar id to TextMapHash +const playerIdToTextMapHash = { 10000005: 2329553598, 10000007: 3241049361 }; + +function isPlayer(data) { return data.CandSkillDepotIds.length !== 0; } +function getPlayerElement(SkillDepotId) { let tmp = xskilldepot.find(ele => ele.Id === SkillDepotId); return tmp === undefined ? tmp : tmp.TalentStarName.split('_').pop(); } +function getLanguage(abbriev) { return require("./TextMap/Text"+abbriev.toUpperCase()+".json"); } +function makeFileName(str) { return str.toLowerCase().replace(/[^a-z]/g,''); } +function convertBold(str) { return str.replace(/(.*?)<\/color>/gi, '**$1**'); } +function stripHTML(str) { return str.replace(/(<([^>]+)>)/gi, ''); } +function capitalizeFirst(str) { return str[0].toUpperCase() + str.toLowerCase().slice(1); } +function replaceLayout(str) { return str.replace(/{LAYOUT_MOBILE#Tap}{LAYOUT_PC#Press}{LAYOUT_PS#Press}/gi,'Press').replace('#',''); } +function replaceNewline(str) { return str.replace(/\\n/gi, '\n'); } +function sanitizeDescription(str) { return replaceNewline(replaceLayout(stripHTML(convertBold(str)))); } + +function collateCharacter(lang) { + const language = getLanguage(lang); + const xsubstat = require('.../ExcelBinOutput/AvatarPromoteExcelConfigData.json'); + let myavatar = xplayableAvatar.reduce((accum, obj) => { + let data = {}; + let extra = xextrainfo.find(ele => ele.AvatarId === obj.Id); + + data.name = language[obj.NameTextMapHash]; + if(isPlayer(obj)) data.name = language[playerIdToTextMapHash[obj.Id]]; + //if(data.name === 'Traveler') data.name = capitalizeFirst(avatarIdToFileName[obj.Id]); + data.description = language[obj.DescTextMapHash]; + data.weapontype = language[weaponTextMapHash[obj.WeaponType]]; + data.body = obj.BodyType.slice(obj.BodyType.indexOf('BODY_')+5); + data.rarity = obj.QualityType === 'QUALITY_PURPLE' ? '4' : '5'; + data.birthmonth = extra.InfoBirthMonth; + data.birthday = extra.InfoBirthDay; + data.affiliation = isPlayer(obj) ? '' : language[extra.AvatarNativeTextMapHash]; + data.element = language[extra.AvatarVisionBeforTextMapHash]; + data.constellation = language[extra.AvatarConstellationBeforTextMapHash]; + if(obj.Id === 10000030) data.constellation = language[extra.AvatarConstellationAfterTextMapHash]; // Zhongli exception + data.title = language[extra.AvatarTitleTextMapHash]; + data.association = extra.AvatarAssocType.slice(extra.AvatarAssocType.indexOf('TYPE_')+5); + data.cv = { + english: language[extra.CvEnglishTextMapHash], + chinese: language[extra.CvChineseTextMapHash], + japanese: language[extra.CvJapaneseTextMapHash], + korean: language[extra.CvKoreanTextMapHash] + }; + + const xsubstat = require('.../ExcelBinOutput/AvatarPromoteExcelConfigData.json'); + const xmanualtext = require('.../ExcelBinOutput/ManualTextMapConfigData.json'); + + let substat = xsubstat.find(ele => ele.AvatarPromoteId === obj.AvatarPromoteId).AddProps[3].PropType; + data.substat = language[xmanualtext.find(ele => ele.TextMapId === substat).TextMapContentTextMapHash]; + + data.icon = obj.IconName; + data.sideicon = obj.SideIconName; + + // INFORMATION TO CALCULATE STATS AT EACH LEVEL + let stats = { base: {}, curve: {} }; + stats.base.hp = obj.HpBase; + stats.base.attack = obj.AttackBase; + stats.base.defense = obj.DefenseBase; + stats.base.critrate = obj.Critical; + stats.base.critdmg = obj.CriticalHurt; + + stats.curve.hp = obj.PropGrowCurves.find(ele => ele.Type === 'FIGHT_PROP_BASE_HP').GrowCurve; + stats.curve.attack = obj.PropGrowCurves.find(ele => ele.Type === 'FIGHT_PROP_BASE_ATTACK').GrowCurve; + stats.curve.defense = obj.PropGrowCurves.find(ele => ele.Type === 'FIGHT_PROP_BASE_DEFENSE').GrowCurve; + stats.specialized = substat; + stats.promotion = xsubstat.reduce((accum, ele) => { + if(ele.AvatarPromoteId !== obj.AvatarPromoteId) return accum; + let promotelevel = ele.PromoteLevel || 0; + accum[promotelevel] = { + maxlevel: ele.UnlockMaxLevel, + hp: ele.AddProps.find(ele => ele.PropType === 'FIGHT_PROP_BASE_HP').Value || 0, + attack: ele.AddProps.find(ele => ele.PropType === 'FIGHT_PROP_BASE_ATTACK').Value || 0, + defense: ele.AddProps.find(ele => ele.PropType === 'FIGHT_PROP_BASE_DEFENSE').Value || 0, + specialized: ele.AddProps.find(ele => ele.PropType === substat).Value || 0, + }; + return accum; + }, []); + data.stats = stats; + + accum[avatarIdToFileName[obj.Id]] = data; + return accum; + }, {}) + return myavatar; +} + +function collateConstellation(lang) { + const language = getLanguage(lang); + let myconstellation = xplayableAvatar.reduce((accum, obj) => { + // bad practice to declare functions inside loop but i need to be able to call it multiple times for players + function dowork() { + let data = {}; + let depot = xskilldepot.find(ele => ele.Id === obj.SkillDepotId); + if(depot === undefined || depot.EnergySkill === undefined) return; // not a finished (traveler) character + if(depot.TalentStarName === '') return; // unfinished + + data.name = language[obj.NameTextMapHash]; + if(isPlayer(obj)) data.name += ` (${language[elementTextMapHash[getPlayerElement(obj.SkillDepotId)]]})` + //console.log(depot) + data.images = {}; + let stars = depot.Talents.map(talentId => xconstellation.find(ele => ele.TalentId === talentId)); + for(let i = 1; i <= 6; i++) { + data['c'+i] = { + name: language[stars[i-1].NameTextMapHash], + effect: sanitizeDescription(language[stars[i-1].DescTextMapHash]) + }; + data.images['c'+i] = `https://upload-os-bbs.mihoyo.com/game_record/genshin/constellation_icon/${stars[i-1].Icon}.png`; + } + + accum[avatarIdToFileName[isPlayer(obj) ? obj.SkillDepotId : obj.Id]] = data; + } + + if(isPlayer(obj)) { + obj.CandSkillDepotIds.forEach(ele => { + obj.SkillDepotId = ele; + dowork(); + }); + } else { + dowork(); + } + return accum; + }, {}); + return myconstellation; +} + +function collateTalent(lang) { + const language = getLanguage(lang); + let mytalent = xplayableAvatar.reduce((accum, obj) => { + // bad practice to declare functions inside loop but i need to be able to call it multiple times for players + function dowork() { + let data = {}; + let depot = xskilldepot.find(ele => ele.Id === obj.SkillDepotId); + if(depot === undefined || depot.EnergySkill === undefined) return; // not a finished (traveler) character + if(depot.TalentStarName === '') return; // unfinished + + data.name = language[obj.NameTextMapHash]; // client-facing name + if(isPlayer(obj)) data.name += ` (${language[elementTextMapHash[getPlayerElement(obj.SkillDepotId)]]})` + + let combat = depot.Skills.concat([depot.EnergySkill]) // get array of combat skills IDs + let passive = depot.InherentProudSkillOpens.reduce((accum2, proud) => { // get array of passive skill IDs + if(proud.ProudSkillGroupId) accum2.push(proud.ProudSkillGroupId); + return accum2; + }, []) + + combat.forEach((skId, index) => { + if(skId === 0) return; + let talent = xtalent.find(tal => tal.Id === skId); + let ref = data[talentCombatTypeMap[index]] = {}; + + ref.name = language[talent.NameTextMapHash]; + let desc = language[talent.DescTextMapHash].split('\\n\\n'); // extract out the italicized part + ref.info = sanitizeDescription(desc[0]); + if(desc[1]) ref.description = sanitizeDescription(desc[1]); + }); + + passive.forEach((skId, index) => { + let talent = xpassive.find(pas => pas.ProudSkillGroupId === skId); + let ref = data['passive'+(index+1)] = {}; // store reference in variable to make it easier to access + + ref.name = language[talent.NameTextMapHash]; + ref.info = sanitizeDescription(language[talent.DescTextMapHash]); + }); + + accum[avatarIdToFileName[isPlayer(obj) ? obj.SkillDepotId : obj.Id]] = data; + } + + if(isPlayer(obj)) { + obj.CandSkillDepotIds.forEach(ele => { + obj.SkillDepotId = ele; + dowork(); + }); + } else { + dowork(); + } + return accum; + }, {}); + return mytalent; +} + +function collateWeapon(lang) { + const language = getLanguage(lang); + const xsubstat = require('.../ExcelBinOutput/WeaponPromoteExcelConfigData.json'); + let myweapon = xplayableWeapon.reduce((accum, obj) => { + + let data = {}; + + data.name = language[obj.NameTextMapHash]; + data.description = language[obj.DescTextMapHash]; + data.weapontype = language[weaponTextMapHash[obj.WeaponType]]; + data.rarity = ''+obj.RankLevel; + + if(obj.WeaponProp[0].PropType !== 'FIGHT_PROP_BASE_ATTACK') console.log(obj,'weapon did not find base atk'); + data.baseatk = obj.WeaponProp.find(obj => obj.PropType === 'FIGHT_PROP_BASE_ATTACK').InitValue; + + let substat = obj.WeaponProp[1].PropType; + if(substat !== undefined) { + data.substat = language[xmanualtext.find(ele => ele.TextMapId === substat).TextMapContentTextMapHash]; + let subvalue = obj.WeaponProp[1].InitValue; + data.subvalue = subvalue; + } + + if(obj.SkillAffix[0] !== 0) { + let affixId = obj.SkillAffix[0] * 10; + for(let offset = 0; offset < 5; offset++) { + let ref = xrefine.find(ele => ele.AffixId === affixId+offset); + if(ref === undefined) break; + if(offset === 0) data.effectname = language[ref.NameTextMapHash]; + let effect = language[ref.DescTextMapHash]; + effect = effect.replace(//gi, '{').replace(/<\/color>/gi, '}'); + effect = effect.split(/{|}/); + data['r'+(offset+1)] = []; + data['effect'] = effect.reduce((accum, ele, i) => { + if(i % 2 === 0) { + return accum + ele; + } else { + data['r'+(offset+1)].push(ele); + return accum + `{${(i-1)/2}}`; + } + }, ''); + } + } + + // INFORMATION TO CALCULATE STATS AT EACH LEVEL + let stats = { base: {}, curve: {} }; + stats.base.attack = obj.WeaponProp[0].InitValue; + stats.base.specialized = obj.WeaponProp[1].InitValue || 0; + + stats.curve.attack = obj.WeaponProp[0].Type; + stats.curve.specialized = obj.WeaponProp[1].Type; + stats.specialized = substat; + stats.promotion = xsubstat.reduce((accum, ele) => { + if(ele.WeaponPromoteId !== obj.WeaponPromoteId) return accum; + let promotelevel = ele.PromoteLevel || 0; + accum[promotelevel] = { + maxlevel: ele.UnlockMaxLevel, + attack: ele.AddProps.find(ele => ele.PropType === 'FIGHT_PROP_BASE_ATTACK').Value || 0 + }; + let special = ele.AddProps.find(ele => ele.PropType === substat);//.Value; + if(special) special = special.Value; + if(special !== undefined) { + console.log('WEAPON SPECIAL SUBSTAT FOUND: ' + obj.Id) + accum[promotelevel].specialized = special; + } + return accum; + }, []) + data.stats = stats; + + data.icon = obj.Icon; + data.awakenicon = obj.AwakenIcon; + + let filename = makeFileName(getLanguage('EN')[obj.NameTextMapHash]); + if(accum[filename] !== undefined) console.log(filename+' IS NOT UNIQUE'); + accum[filename] = data; + return accum; + }, {}); + + return myweapon; +} + +function collateArtifact(lang) { + const language = getLanguage(lang); + const xsets = require('.../ExcelBinOutput/ReliquarySetExcelConfigData.json'); + const xrelics = require('.../ExcelBinOutput/ReliquaryExcelConfigData.json'); + const xreliccodex = require('.../ExcelBinOutput/ReliquaryCodexExcelConfigData.json'); + const xrefine = require('.../ExcelBinOutput/EquipAffixExcelConfigData.json'); + + let myartifact = xsets.reduce((accum, obj) => { + if(obj.SetIcon === '') return accum; + let setname; + let filename; + let data = {}; + + // get available rarities + data.rarity = xreliccodex.reduce((accum, relic) => { + if(obj.SetId !== relic.SuitId) return accum; + relic.Level = relic.Level.toString(); + if(accum.indexOf(relic.Level) === -1) accum.push(relic.Level); + return accum; + }, []); + + // set bonus effects + obj.SetNeedNum.forEach((ele, ind) => { + let effect = xrefine.find(e => e.AffixId === obj.EquipAffixId*10 + ind); + data[ele+'pc'] = language[effect.DescTextMapHash]; + if(setname === undefined) { + setname = language[effect.NameTextMapHash]; + filename = makeFileName(getLanguage('EN')[effect.NameTextMapHash]); + } + }); + + data.images = {}; + // relic pieces + obj.ContainsList.forEach(ele => { + let relic = xrelics.find(e => e.Id === ele); + let relicdata = {}; + relicdata.name = language[relic.NameTextMapHash]; + relicdata.relictype = xmanualtext.find(ele => ele.TextMapId === relic.EquipType).TextMapContentTextMapHash; + relicdata.relictype = language[relicdata.relictype]; + relicdata.description = language[relic.DescTextMapHash]; + data[relicTypeToPropertyName[relic.EquipType]] = relicdata; + data.images[relicTypeToPropertyName[relic.EquipType]] = `https://upload-os-bbs.mihoyo.com/game_record/genshin/equip/${relic.Icon}.png`; + }); + + data.name = setname; + accum[filename] = data; + return accum; + }, {}); + return myartifact; +} + +function exportCurve(folder, file) { + const xcurve = require(file); + let output = {}; + xcurve.forEach(ele => { + let curveinfo = {}; + ele.CurveInfos.forEach(ele => { + curveinfo[ele.Type] = ele.Value; + }); + output[ele.Level] = curveinfo; + }); + fs.mkdirSync(`./export/curve`, { recursive: true }); + fs.writeFileSync(`./export/curve/${folder}.json`, JSON.stringify(output, null, '\t')); +} + +function exportData(folder, collateFunc, englishonly) { + langcodes.forEach(lang => { + if(englishonly && lang !== 'EN') return; + let data = collateFunc(lang); + fs.mkdirSync(`./export/${lang}`, { recursive: true }); + fs.writeFileSync(`./export/${lang}/${folder}.json`, JSON.stringify(data, null, '\t')); + if(JSON.stringify(data).search('undefined') !== -1) console.log('undefined found in '+folder); + }); + console.log("done "+folder); +} + +exportData('characters', collateCharacter); +exportCurve('characters', '.../ExcelBinOutput/AvatarCurveExcelConfigData.json'); +exportData('constellations', collateConstellation); +exportData('talents', collateTalent); +exportData('weapons', collateWeapon); +exportCurve('weapons', '.../ExcelBinOutput/WeaponCurveExcelConfigData.json') +exportData('artifacts', collateArtifact); +exportData('foods', collateFood); + +//console.log(collateCharacter('EN')) +//console.log(collateConstellation('EN').hutao) +//console.log(collateTalent('EN').mona) +//console.log(collateWeapon('EN')); +// console.log(collateArtifact('EN')); diff --git a/myscripts/collateCharacter.js b/myscripts/collateCharacter.js index fa480b6f6..748e2249f 100644 --- a/myscripts/collateCharacter.js +++ b/myscripts/collateCharacter.js @@ -1,13 +1,17 @@ // avatar extra info const xextrainfo = getExcel('FetterInfoExcelConfigData'); - // object map that converts player's avatar id to TextMapHash const playerIdToTextMapHash = { 10000005: 2329553598, 10000007: 3241049361 }; +const moraNameTextMapHash = getExcel('MaterialExcelConfigData').find(ele => ele.Id === 202).NameTextMapHash; +const xmat = getExcel('MaterialExcelConfigData'); + function collateCharacter(lang) { const language = getLanguage(lang); const xsubstat = getExcel('AvatarPromoteExcelConfigData'); + // console.log(xplayableAvatar.map(ele => ele.ImageName)); + // console.log(avatarIdToFileName) let myavatar = xplayableAvatar.reduce((accum, obj) => { let data = {}; let extra = xextrainfo.find(ele => ele.AvatarId === obj.Id); @@ -19,8 +23,11 @@ function collateCharacter(lang) { data.weapontype = language[weaponTextMapHash[obj.WeaponType]]; data.body = obj.BodyType.slice(obj.BodyType.indexOf('BODY_')+5); data.rarity = obj.QualityType === 'QUALITY_PURPLE' ? '4' : '5'; - data.birthmonth = extra.InfoBirthMonth; - data.birthday = extra.InfoBirthDay; + if(!isPlayer(obj)) { + data.birthmonth = extra.InfoBirthMonth; + data.birthday = extra.InfoBirthDay; + } + if(isPlayer(obj) && (data.birthmonth || data.birthday)) console.log('warning player has birthday'); data.affiliation = isPlayer(obj) ? '' : language[extra.AvatarNativeTextMapHash]; data.element = language[extra.AvatarVisionBeforTextMapHash]; data.constellation = language[extra.AvatarConstellationBeforTextMapHash]; @@ -43,6 +50,24 @@ function collateCharacter(lang) { data.icon = obj.IconName; data.sideicon = obj.SideIconName; + // get the promotion costs + let costs = {}; + for(let i = 1; i <= 6; i++) { + let apromo = xsubstat.find(ele => ele.AvatarPromoteId === obj.AvatarPromoteId && ele.PromoteLevel === i); + costs['ascend'+i] = [{ + name: language[moraNameTextMapHash], + count: apromo.ScoinCost + }]; + for(let items of apromo.CostItems) { + if(items.Id === undefined) continue; + costs['ascend'+i].push({ + name: language[xmat.find(ele => ele.Id === items.Id).NameTextMapHash], + count: items.Count + }) + } + } + data.costs = costs; + // INFORMATION TO CALCULATE STATS AT EACH LEVEL let stats = { base: {}, curve: {} }; stats.base.hp = obj.HpBase; diff --git a/myscripts/collateFood.js b/myscripts/collateFood.js index f48b234ef..18e69efb7 100644 --- a/myscripts/collateFood.js +++ b/myscripts/collateFood.js @@ -70,24 +70,24 @@ function collateFood(lang) { let basedish = data.name; let ingredients = data.ingredients; - data = {}; - data.name = language[xd.NameTextMapHash]; - data.rarity = xd.RankLevel; - data.foodtype = 'SPECIALTY'; - data.foodfilter = foodfilter; - data.foodcategory = xd.EffectIcon.substring(13); + let spdata = {}; + spdata.name = language[xd.NameTextMapHash]; + spdata.rarity = xd.RankLevel; + spdata.foodtype = 'SPECIALTY'; + spdata.foodfilter = foodfilter; + spdata.foodcategory = xd.EffectIcon.substring(13); if(language[xd.InteractionTitleTextMapHash]) console.log(`specialty ${obj.Id} has interaction`); if(language[xd.SpecialDescTextMapHash]) console.log(`specialty ${obj.Id} has special`); - data.effect = language[xd.EffectDescTextMapHash]; - data.description = sanitizeDescription(language[xd.DescTextMapHash]); + spdata.effect = language[xd.EffectDescTextMapHash]; + spdata.description = sanitizeDescription(language[xd.DescTextMapHash]); - data.basedish = basedish; - data.character = language[getAvatar(myspec.AvatarId).NameTextMapHash]; + spdata.basedish = basedish; + spdata.character = language[getAvatar(myspec.AvatarId).NameTextMapHash]; - data.ingredients = ingredients; + spdata.ingredients = ingredients; - accum[makeFileName(getLanguage('EN')[obj.NameTextMapHash])] = data; + accum[makeFileName(getLanguage('EN')[xd.NameTextMapHash])] = spdata; return accum; }, {}); // console.log(myfood); diff --git a/myscripts/collateMaterial.js b/myscripts/collateMaterial.js new file mode 100644 index 000000000..00269eb43 --- /dev/null +++ b/myscripts/collateMaterial.js @@ -0,0 +1,75 @@ +/* +MATERIAL_AVATAR_MATERIAL is talent level-up material, etc. + +*/ + +const filter = ['MATERIAL_EXCHANGE', 'MATERIAL_WOOD', 'MATERIAL_AVATAR_MATERIAL']; + +// Mora, Apple, Sunsettia +const includeMatId = [202, 100001, 100002]; +// Crafted Items, Primordial Essence, Raw Meat (S), Fowl (S) +const excludeMatId = [110000, 112001, 100086, 100087]; + +function collateMaterial(lang) { + const language = getLanguage(lang); + const xsource = getExcel('MaterialSourceDataExcelConfigData'); + const xmat = getExcel('MaterialExcelConfigData'); + const xarchive = getExcel('MaterialCodexExcelConfigData'); + const xdungeon = getExcel('DungeonExcelConfigData'); + + let mymaterial = xmat.reduce((accum, obj) => { + if(!obj.MaterialType) return accum; + if(excludeMatId.includes(obj.Id)) return accum; + if(!filter.includes(obj.MaterialType) && !includeMatId.includes(obj.Id)) return accum; + + let data = {}; + data.name = language[obj.NameTextMapHash]; + if(data.name === '') return accum; + // data.Id = obj.Id; + data.description = language[obj.DescTextMapHash]; + data.category = obj.MaterialType.slice(9); + data.materialtype = language[obj.TypeDescTextMapHash]; + if(obj.RankLevel) data.rarity = ''+obj.RankLevel; + + let tmp = xsource.find(ele => ele.Id === obj.Id); + let dungeonlist = tmp.DungeonList.filter(ele => ele !== 0); + if(dungeonlist > 0) { + if(dungeonlist.length > 1) console.log(`${data.name} drops from more than one dungeon!`); + data.dropdomain = language[xdungeon.find(ele => ele.Id === dungeonlist[0]).DisplayNameTextMapHash]; + data.daysofweek = getDayWeekList(dungeonlist[0], language); + } + data.source = tmp.TextList.map(ele => language[ele]).filter(ele => ele !== ''); + + let filename = makeFileName(getLanguage('EN')[obj.NameTextMapHash]); + if(filename === '') return accum; + accum[filename] = data; + return accum; + }, {}); + return mymaterial; +} + +// format returned is translated and sorted array ["Monday", "Thursday", "Sunday"] +function getDayWeekList(dungeonId, langmap) { + const xdailyd = getExcel('DailyDungeonConfigData'); + const mapENtoNum = { 'Monday': 1, 'Tuesday': 2, 'Wednesday': 3, 'Thursday': 4, 'Friday': 5, 'Saturday': 6, 'Sunday': 7 }; + let mylist = []; + for(const ele of xdailyd) + for(const [key, value] of Object.entries(mapENtoNum)) + if(ele[key].includes(dungeonId)) mylist.push(value); + mylist = mylist.sort((a, b) => a - b); + return mylist.map(ele => langmap[dayOfWeek(ele)]); +} + +module.exports = collateMaterial; + +// MaterialSourceDataExcelConfigData +// Each object has a duplicate DungeonList property :/ +// Remove it. +function cleanupMaterialSourceFile() { + const fs = require('fs'); + let data = fs.readFileSync('../ExcelBinOutput/MaterialSourceDataExcelConfigData.json', 'utf8'); + data = data.replace(/("DungeonList"[^\]]*?],)\s*"DungeonList".*?],/gs, '$1'); + fs.writeFileSync('../ExcelBinOutput/MaterialSourceDataExcelConfigData.json', data); +} + +cleanupMaterialSourceFile(); \ No newline at end of file diff --git a/myscripts/collateTalent.js b/myscripts/collateTalent.js index 7139e13a7..f5a317d84 100644 --- a/myscripts/collateTalent.js +++ b/myscripts/collateTalent.js @@ -1,11 +1,14 @@ const xtalent = getExcel('AvatarSkillExcelConfigData'); // combat talents -const xpassive = getExcel('ProudSkillExcelConfigData'); // passive talents +const xpassive = getExcel('ProudSkillExcelConfigData'); // passive talents. also talent upgrade costs // object map that converts index to the talent type const talentCombatTypeMap = { '0': 'combat1', '1': 'combat2', '2': 'combatsp', '4': 'combat3' }; +const moraNameTextMapHash = getExcel('MaterialExcelConfigData').find(ele => ele.Id === 202).NameTextMapHash; + function collateTalent(lang) { const language = getLanguage(lang); + const xmat = getExcel('MaterialExcelConfigData'); let mytalent = xplayableAvatar.reduce((accum, obj) => { // bad practice to declare functions inside loop but i need to be able to call it multiple times for players function dowork() { @@ -22,16 +25,54 @@ function collateTalent(lang) { if(proud.ProudSkillGroupId) accum2.push(proud.ProudSkillGroupId); return accum2; }, []) - + let parameters = {}; + let costs = {}; combat.forEach((skId, index) => { if(skId === 0) return; let talent = xtalent.find(tal => tal.Id === skId); - let ref = data[talentCombatTypeMap[index]] = {}; + let combatTypeProp = talentCombatTypeMap[index]; + let ref = data[combatTypeProp] = {}; ref.name = language[talent.NameTextMapHash]; let desc = language[talent.DescTextMapHash].split('\\n\\n'); // extract out the italicized part ref.info = sanitizeDescription(desc[0]); if(desc[1]) ref.description = sanitizeDescription(desc[1]); + + ref.labels = []; + // build the labels + let attTalent = xpassive.find(tal => (tal.ProudSkillGroupId === talent.ProudSkillGroupId && tal.Level === 1)); + for(let labelTextMap of attTalent.ParamDescList) { + if(language[labelTextMap] === "") continue; + ref.labels.push(replaceLayout(language[labelTextMap])); + } + + parameters[combatTypeProp] = {}; + for(let lvl = 1; lvl <= 15; lvl++) { + if(lvl !== 1 && index === 2) continue; // sprint skills don't have level-up + let attTalent = xpassive.find(tal => (tal.ProudSkillGroupId === talent.ProudSkillGroupId && tal.Level === lvl)); + attTalent.ParamList.forEach((value, paramIndex) => { + const name = `param${paramIndex+1}`; + if(value === 0) { // exclude those with values of 0 + if(lvl !== 1 && parameters[combatTypeProp][name] !== undefined) console.log(`talent ${ref.name} value 0`); + return; + } + if(parameters[combatTypeProp][name] === undefined) parameters[combatTypeProp][name] = []; + parameters[combatTypeProp][name].push(value); + }); + if(lvl >= 2 && lvl <= 10) { // get upgrade costs + costs['lvl'+lvl] = [{ + name: language[moraNameTextMapHash], + count: attTalent.CoinCost + }]; + for(let items of attTalent.CostItems) { + if(items.Id === undefined) continue; + costs['lvl'+lvl].push({ + name: language[xmat.find(ele => ele.Id === items.Id).NameTextMapHash], + count: items.Count + }) + } + } + } }); passive.forEach((skId, index) => { @@ -41,6 +82,8 @@ function collateTalent(lang) { ref.name = language[talent.NameTextMapHash]; ref.info = sanitizeDescription(language[talent.DescTextMapHash]); }); + data.costs = costs; + data.parameters = parameters; accum[avatarIdToFileName[isPlayer(obj) ? obj.SkillDepotId : obj.Id]] = data; } diff --git a/myscripts/collateWeapon.js b/myscripts/collateWeapon.js index d5fa14ca0..bf06dd0af 100644 --- a/myscripts/collateWeapon.js +++ b/myscripts/collateWeapon.js @@ -1,6 +1,9 @@ const xweapon = getExcel('WeaponExcelConfigData'); const xrefine = getExcel('EquipAffixExcelConfigData'); +const moraNameTextMapHash = getExcel('MaterialExcelConfigData').find(ele => ele.Id === 202).NameTextMapHash; +const xmat = getExcel('MaterialExcelConfigData'); + const xplayableWeapon = xweapon.filter(obj => { if(obj.RankLevel >= 3 && obj.SkillAffix[0] === 0) return false; if(obj.SkillAffix[1] !== 0) { console.log('danger'); return false }; @@ -8,8 +11,6 @@ const xplayableWeapon = xweapon.filter(obj => { return true; }); - - function collateWeapon(lang) { const language = getLanguage(lang); const xsubstat = getExcel('WeaponPromoteExcelConfigData'); @@ -53,6 +54,26 @@ function collateWeapon(lang) { } } + // get the promotion costs + let costs = {}; + for(let i = 1; i <= (obj.RankLevel <= 2 ? 4 : 6); i++) { + // 1 and 2 star weapons only have 4 ascensions instead of 6 + let apromo = xsubstat.find(ele => ele.WeaponPromoteId === obj.WeaponPromoteId && ele.PromoteLevel === i); + costs['ascend'+i] = [{ + name: language[moraNameTextMapHash], + count: apromo.CoinCost + }]; + + for(let items of apromo.CostItems) { + if(items.Id === undefined) continue; + costs['ascend'+i].push({ + name: language[xmat.find(ele => ele.Id === items.Id).NameTextMapHash], + count: items.Count + }) + } + } + data.costs = costs; + // INFORMATION TO CALCULATE STATS AT EACH LEVEL let stats = { base: {}, curve: {} }; stats.base.attack = obj.WeaponProp[0].InitValue; diff --git a/myscripts/myscript.js b/myscripts/myscript.js index b92237f83..d818ecd4b 100644 --- a/myscripts/myscript.js +++ b/myscripts/myscript.js @@ -1,7 +1,7 @@ const fs = require('fs'); global.getExcel = function(file) { return require(`../ExcelBinOutput/${file}.json`); } -global.getTextMap = function(langcode) { return require(`../TextMap/Text${langcode}.json`); } +global.getTextMap = function(langcode) { return require(`../TextMap/TextMap${langcode}.json`); } const xavatar = getExcel('AvatarExcelConfigData'); // array @@ -9,7 +9,7 @@ global.xskilldepot = getExcel('AvatarSkillDepotExcelConfigData'); global.xmanualtext = getExcel('ManualTextMapConfigData'); -const langcodes = ['CHS', 'CHT', 'DE', 'EN', 'ES', 'FR', 'ID', 'JA', 'KO', 'PT', 'RU', 'TH', 'VI']; +const langcodes = ['CHS', 'CHT', 'DE', 'EN', 'ES', 'FR', 'ID', 'JP', 'KR', 'PT', 'RU', 'TH', 'VI']; /* ========================================================================================== */ @@ -31,10 +31,9 @@ global.makeFileName = function(str, lang) { return normalizeStr(str).toLowerCase global.convertBold = function(str) { return str.replace(/(.*?)<\/color>/gi, '**$1**'); } global.stripHTML = function(str) { return (str || '').replace(/(<([^>]+)>)/gi, ''); } global.capitalizeFirst = function(str) { return str[0].toUpperCase() + str.toLowerCase().slice(1); } -global.replaceLayout = function(str) { return str.replace(/{LAYOUT_MOBILE#Tap}{LAYOUT_PC#Press}{LAYOUT_PS#Press}/gi,'Press').replace('#',''); } +global.replaceLayout = function(str) { return str.replace(/{LAYOUT_MOBILE#.*?}{LAYOUT_PC#(.*?)}{LAYOUT_PS#.*?}/gi,'$1').replace('#',''); } global.replaceNewline = function(str) { return str.replace(/\\n/gi, '\n'); } global.sanitizeDescription = function(str) { return replaceNewline(replaceLayout(stripHTML(convertBold(str)))); } - /* ======================================================================================= */ // object map that converts the genshin coded element into a TextMapHash @@ -43,7 +42,7 @@ global.elementTextMapHash = ['Fire', 'Water', 'Grass', 'Electric', 'Wind', 'Ice' return accum; }, {}); -global.xplayableAvatar = xavatar.filter(obj => obj.AvatarPromoteId !== 2); // array +global.xplayableAvatar = xavatar.filter(obj => obj.AvatarPromoteId !== 2 || obj.Id === 10000002); // array // object map that converts an avatar Id or traveler SkillDepotId to filename global.avatarIdToFileName = xplayableAvatar.reduce((accum, obj) => { if(obj.Id === 10000005) accum[obj.Id] = 'aether'; @@ -65,6 +64,11 @@ global.weaponTextMapHash = ['WEAPON_SWORD_ONE_HAND', 'WEAPON_CATALYST', 'WEAPON_ return accum; }, {}); +// translates day of the week. 1 => Monday, etc. Returns textmaphash +global.dayOfWeek = function(num) { + return xmanualtext.find(ele => ele.TextMapId === 'UI_ABYSSUS_DATE'+num).TextMapContentTextMapHash; +} + /* =========================================================================================== */ function exportCurve(folder, file) { @@ -89,6 +93,7 @@ function exportData(folder, collateFunc, englishonly, skipwrite) { if(!skipwrite) { fs.writeFileSync(`./export/${lang}/${folder}.json`, JSON.stringify(data, null, '\t')); if(JSON.stringify(data).search('undefined') !== -1) console.log('undefined found in '+folder); + if(data[""]) console.log('empty key found in '+folder); } }); console.log("done "+folder); @@ -98,10 +103,11 @@ function exportData(folder, collateFunc, englishonly, skipwrite) { // exportCurve('characters', 'AvatarCurveExcelConfigData'); // exportData('constellations', require('./collateConstellation')); // exportData('talents', require('./collateTalent.js')); -// exportData('weapons', require('./collateWeapon.js')); +exportData('weapons', require('./collateWeapon.js')); // exportCurve('weapons', 'WeaponCurveExcelConfigData') // exportData('artifacts', require('./collateArtifact.js')); -exportData('foods', require('./collateFood')); +// exportData('foods', require('./collateFood')); +// exportData('materials', require('./collateMaterial')); //console.log(collateCharacter('EN')) //console.log(collateConstellation('EN').hutao)