This commit is contained in:
theBowja 2021-04-27 19:56:48 -05:00
parent ebb117f78d
commit 526a903429
8 changed files with 538 additions and 0 deletions

1
myscripts/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
export

View File

@ -0,0 +1,55 @@
// 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'};
function collateArtifact(lang) {
const language = getLanguage(lang);
const xsets = getExcel('ReliquarySetExcelConfigData');
const xrelics = getExcel('ReliquaryExcelConfigData');
const xreliccodex = getExcel('ReliquaryCodexExcelConfigData');
const xrefine = getExcel('EquipAffixExcelConfigData');
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;
}
module.exports = collateArtifact;

View File

@ -0,0 +1,78 @@
// avatar extra info
const xextrainfo = getExcel('FetterInfoExcelConfigData');
// object map that converts player's avatar id to TextMapHash
const playerIdToTextMapHash = { 10000005: 2329553598, 10000007: 3241049361 };
function collateCharacter(lang) {
const language = getLanguage(lang);
const xsubstat = getExcel('AvatarPromoteExcelConfigData');
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 = getExcel('AvatarPromoteExcelConfigData');
const xmanualtext = getExcel('ManualTextMapConfigData');
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;
}
module.exports = collateCharacter;

View File

@ -0,0 +1,42 @@
const xconstellation = getExcel('AvatarTalentExcelConfigData');
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;
}
module.exports = collateConstellation;

98
myscripts/collateFood.js Normal file
View File

@ -0,0 +1,98 @@
const xrecipe = getExcel('CookRecipeExcelConfigData');
const xmaterial = getExcel('MaterialExcelConfigData');
const xsource = getExcel('MaterialSourceDataExcelConfigData');
const xspecialty = getExcel('CookBonusExcelConfigData');
const xavatar = getExcel('AvatarExcelConfigData');
function getSpecialty(id) { return xspecialty.find(ele => ele.RecipeId === id); }
function getMaterial(id) { return xmaterial.find(ele => ele.Id === id); }
function getAvatar(id) { return xavatar.find(ele => ele.Id === id); }
function getManualTextMapHash(id) {
if(id === 'COOK_FOOD_DEFENSE') id = 'COOK_FOOD_DEFENCE';
return xmanualtext.find(ele => ele.TextMapId === id).TextMapContentTextMapHash;
}
const mapQualityToProp = {
'FOOD_QUALITY_STRANGE': 'suspicious',
'FOOD_QUALITY_ORDINARY': 'normal',
'FOOD_QUALITY_DELICIOUS': 'delicious',
}
function collateFood(lang) {
const language = getLanguage(lang);
let myfood = xrecipe.reduce((accum, obj) => {
//if(obj.Id !== 1003) return accum;
let data = {};
data.name = language[obj.NameTextMapHash];
data.rarity = obj.RankLevel;
data.foodtype = 'NORMAL';
data.foodfilter = language[getManualTextMapHash(obj.FoodType)];
data.foodcategory = undefined;
data.effect = obj.EffectDesc.reduce((accum, eff) => {
const tmp = stripHTML(language[eff]);
if(tmp) accum.push(tmp);
return accum;
}, []).join('\n');
data.description = sanitizeDescription(language[obj.DescTextMapHash]);
// check error
for(let i = 2; i <= 3; i++) { const tmp = language[obj.EffectDesc[i]]; if(tmp) console.log(`${obj.Id} ${data.name}: ${tmp}`); }
// get suspicious, normal, delicious
for(let xd of obj.QualityOutputVec) {
xd = getMaterial(xd.Id);
let subdata = {};
if(language[xd.InteractionTitleTextMapHash]) console.log(`food ${obj.Id} has interaction`);
if(language[xd.SpecialDescTextMapHash]) console.log(`food ${obj.Id} has special`);
subdata.effect = language[xd.EffectDescTextMapHash];
subdata.description = sanitizeDescription(language[xd.DescTextMapHash]);
data[mapQualityToProp[xd.FoodQuality]] = subdata;
data.foodcategory = xd.EffectIcon.substring(13);
}
data.ingredients = obj.InputVec.reduce((accum, ing) => {
if(ing.Id === undefined) return accum;
const mat = getMaterial(ing.Id);
accum.push({ name: language[mat.NameTextMapHash], count: ing.Count });
return accum;
}, []);
// data.source =
accum[makeFileName(getLanguage('EN')[obj.NameTextMapHash])] = data;
// check if there is a specialty
let myspec = getSpecialty(obj.Id);
if(myspec === undefined) return accum;
let xd = getMaterial(myspec.ParamVec[0]);
// if(xd === undefined) return accum;
let foodfilter = data.foodfilter;
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);
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]);
data.basedish = basedish;
data.character = language[getAvatar(myspec.AvatarId).NameTextMapHash];
data.ingredients = ingredients;
accum[makeFileName(getLanguage('EN')[obj.NameTextMapHash])] = data;
return accum;
}, {});
// console.log(myfood);
return myfood;
}
module.exports = collateFood;

View File

@ -0,0 +1,61 @@
const xtalent = getExcel('AvatarSkillExcelConfigData'); // combat talents
const xpassive = getExcel('ProudSkillExcelConfigData'); // passive talents
// object map that converts index to the talent type
const talentCombatTypeMap = { '0': 'combat1', '1': 'combat2', '2': 'combatsp', '4': 'combat3' };
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<i>'); // 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;
}
module.exports = collateTalent;

View File

@ -0,0 +1,93 @@
const xweapon = getExcel('WeaponExcelConfigData');
const xrefine = getExcel('EquipAffixExcelConfigData');
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;
});
function collateWeapon(lang) {
const language = getLanguage(lang);
const xsubstat = getExcel('WeaponPromoteExcelConfigData');
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(/<color=#.*?>/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;
}
module.exports = collateWeapon;

110
myscripts/myscript.js Normal file
View File

@ -0,0 +1,110 @@
const fs = require('fs');
global.getExcel = function(file) { return require(`../ExcelBinOutput/${file}.json`); }
global.getTextMap = function(langcode) { return require(`../TextMap/Text${langcode}.json`); }
const xavatar = getExcel('AvatarExcelConfigData'); // array
global.xskilldepot = getExcel('AvatarSkillDepotExcelConfigData');
global.xmanualtext = getExcel('ManualTextMapConfigData');
const langcodes = ['CHS', 'CHT', 'DE', 'EN', 'ES', 'FR', 'ID', 'JA', 'KO', 'PT', 'RU', 'TH', 'VI'];
/* ========================================================================================== */
// const weaponIdToFileName = xweapon.reduce((accum, obj) => {
// accum[obj.Id] =
// }, {})
// UNUSED object map that converts AvatarAssocType into a TextMapHash
const assocTextMapHash = ['ASSOC_TYPE_MONDSTADT', 'ASSOC_TYPE_LIYUE', 'ASSOC_TYPE_FATUI'];
global.isPlayer = function(data) { return data.CandSkillDepotIds.length !== 0; }
global.getPlayerElement = function(SkillDepotId) { let tmp = xskilldepot.find(ele => ele.Id === SkillDepotId); return tmp === undefined ? tmp : tmp.TalentStarName.split('_').pop(); }
global.getLanguage = function(abbriev) { return getTextMap(abbriev.toUpperCase()); }
global.normalizeStr = function(str) { return str.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); }
global.makeFileName = function(str, lang) { return normalizeStr(str).toLowerCase().replace(/[^a-z]/g,''); }
global.convertBold = function(str) { return str.replace(/<color=#FFD780FF>(.*?)<\/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.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
global.elementTextMapHash = ['Fire', 'Water', 'Grass', 'Electric', 'Wind', 'Ice', 'Rock'].reduce((accum, element) => {
accum[element] = xmanualtext.find(ele => ele.TextMapId === element).TextMapContentTextMapHash;
return accum;
}, {});
global.xplayableAvatar = xavatar.filter(obj => obj.AvatarPromoteId !== 2); // 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';
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;
}, {});
// object map that converts a WeaponType into a TextMapHash
global.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;
}, {});
/* =========================================================================================== */
function exportCurve(folder, file) {
const xcurve = getExcel(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, skipwrite) {
langcodes.forEach(lang => {
if(englishonly && lang !== 'EN') return;
let data = collateFunc(lang);
fs.mkdirSync(`./export/${lang}`, { recursive: true });
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);
}
});
console.log("done "+folder);
}
// exportData('characters', require('./collateCharacter.js'));
// exportCurve('characters', 'AvatarCurveExcelConfigData');
// exportData('constellations', require('./collateConstellation'));
// exportData('talents', require('./collateTalent.js'));
// exportData('weapons', require('./collateWeapon.js'));
// exportCurve('weapons', 'WeaponCurveExcelConfigData')
// exportData('artifacts', require('./collateArtifact.js'));
exportData('foods', require('./collateFood'));
//console.log(collateCharacter('EN'))
//console.log(collateConstellation('EN').hutao)
//console.log(collateTalent('EN').mona)
//console.log(collateWeapon('EN'));
// console.log(collateArtifact('EN'));