genshin-db
Version:
Genshin Impact v6.2 JSON data. Search and get results in all in-game languages! Sources from the fandom wiki and GenshinData repo.
385 lines (348 loc) • 14.6 kB
JavaScript
let all = require('./min/data.min.json');
const pako = require('pako');
if(all instanceof ArrayBuffer) {
all = JSON.parse(pako.ungzip(all, { to: 'string' }));
}
/**
{
data: {
[Languages]: {
[folders]: ObjectMap
}
},
index: {
[Languages]: {
[folders]: ObjectMap
}
},
image: {
[folders]: ObjectMap
},
stats: {
[folders]: ObjectMap
},
curve: {
[folders]: ObjectMap
},
url: {
[folders]: ObjectMap
}
}
*/
if(!all.data) all.data = {};
if(!all.index) all.index = {};
if(!all.image) all.image = {};
if(!all.url) all.url = {};
if(!all.stats) all.stats = {};
if(!all.curve) all.curve = {};
if(!all.version) all.version = {};
const alldata = all.data;
const allindex = all.index;
const allimage = all.image;
const allurl = all.url;
const allstats = all.stats;
const allcurve = all.curve;
const allversion = all.version
const design = require('./design.json');
const v4prop = require('./v4prop.js');
const calcStatsMap = {
'characters': calcStatsCharacter,
'weapons': calcStatsWeapon,
'enemies': calcStatsEnemy
};
function getData(lang, folder, filename, v4Prop, v4PropOnly) {
let tmp;
try {
tmp = alldata[lang][folder][filename];
if(tmp.images === undefined && design.hasImage.includes(folder)) {
tmp.images = getImage(folder, filename);
}
if(tmp.url === undefined && design.hasUrl.includes(folder)) {
tmp.url = getURL(folder, filename);
}
if(tmp.stats === undefined && design.hasStat.includes(folder) && calcStatsMap[folder]) {
tmp.stats = calcStatsMap[folder](filename);
}
if(folder === 'talents' && tmp.combat1.parameters === undefined) {
setAttributesTalent(tmp, filename);
}
if(tmp.version === undefined) {
tmp.version = getVersion(folder, filename);
}
} catch(e) { return undefined; }
if (v4PropOnly) return v4prop.getv4(tmp, folder);
if (v4Prop) return v4prop.addv4(tmp, folder);
return tmp;
}
function getIndex(lang, folder) {
try {
return allindex[lang][folder];
} catch(e) { return undefined; }
}
function getImage(folder, filename) {
try {
return allimage[folder][filename];
} catch(e) { return undefined; }
}
function getURL(folder, filename) {
try {
return allurl[folder][filename];
} catch(e) { return undefined; }
}
function getVersion(folder, filename) {
try {
return allversion[folder][filename];
} catch(e) { return ''; }
}
function getStats(folder, filename) {
try {
return allstats[folder][filename];
} catch(e) { return undefined; }
}
function getCurve(folder) {
try {
return allcurve[folder];
} catch(e) { return undefined; }
}
function calcStatsCharacter(filename) {
const mystats = getStats('characters', filename);
const mycurve = getCurve('characters');
if(mystats === undefined || mycurve === undefined) return undefined;
/**
* Calculates the stats of a character at a specific level and ascension phase.
* @param level: number - level. number between 1-100. returns undefined if not within range.
* @param ascension: undefined | number | string - the ascension number, or '-'/'+'. defaults to '-'.
* only decides which stats to return at level boundaries (20, 40, 50, 60, 70, 80).
*/
return function(level, ascension) {
level = parseInt(level, 10);
if(isNaN(level)) return undefined;
if(level > 100 || level < 1) return undefined;
const [phase, promotion] = getPromotionBonus(mystats.promotion, level, ascension);
let output = {
level: level,
ascension: phase,
hp: mystats.base.hp * mycurve[level][mystats.curve.hp] + promotion.hp,
attack: mystats.base.attack * mycurve[level][mystats.curve.attack] + promotion.attack,
defense: mystats.base.defense * mycurve[level][mystats.curve.defense] + promotion.defense,
specialized: promotion.specialized
};
if(mystats.specialized === 'FIGHT_PROP_CRITICAL')
output.specialized += mystats.base.critrate;
else if(mystats.specialized === 'FIGHT_PROP_CRITICAL_HURT')
output.specialized += mystats.base.critdmg;
return output;
}
}
function calcStatsWeapon(filename) {
const mystats = getStats('weapons', filename);
const mycurve = getCurve('weapons');
if(mystats === undefined || mycurve === undefined) return undefined;
const maxlevel = mystats.promotion[mystats.promotion.length-1].maxlevel;
/**
* Calculates the stats of a weapon at a specific level and ascension phase.
*/
return function(level, ascension) {
level = parseInt(level, 10);
if(isNaN(level)) return undefined;
if(level > maxlevel || level < 1) return undefined;
const [phase, promotion] = getPromotionBonus(mystats.promotion, level, ascension);
let output = {
level: level,
ascension: phase,
attack: mystats.base.attack * mycurve[level][mystats.curve.attack] + promotion.attack,
specialized: mystats.base.specialized * mycurve[level][mystats.curve.specialized]
};
return output;
}
}
/**
* Generates a function for calculating the hp/atk/defense of an enemy.
*/
function calcStatsEnemy(filename) {
const mystats = getStats('enemies', filename);
const mycurve = getCurve('enemies');
if(mystats === undefined || mycurve === undefined) return undefined;
/**
* Calculates the stats of a enemy at a specific level. Does not take into account location.
*/
return function(level) {
level = parseInt(level, 10);
if(isNaN(level)) return undefined;
if(level > 200 || level < 1) return undefined;
let output = {
level: level,
hp: mystats.base.hp * mycurve[level][mystats.curve.hp],
attack: mystats.base.attack * mycurve[level][mystats.curve.attack],
defense: level*5+500//mystats.base.defense * mycurve[level][mystats.curve.defense]
};
return output;
}
}
/**
* @param level: integer
* @param ascension: undefined | number | string - the ascension number, or '-'/'+'. defaults to '-'.
* only decides which stats to return at level boundaries.
*/
function getPromotionBonus(promotions, level, ascension) {
for(let index = promotions.length - 2; index >= 0; index--) {
if(level > promotions[index].maxlevel) {
return [index+1, promotions[index+1]];
} else if(level === promotions[index].maxlevel) {
if(Number.isFinite(ascension) && ascension > index || ascension === '+')
return [index+1, promotions[index+1]];
else
return [index, promotions[index]];
}
}
return [0, promotions[0]];
}
function setAttributesTalent(data, filename) {
const myparams = getStats('talents', filename);
for(const prop of ['combat1', 'combat2', 'combatsp', 'combatju', 'combat3']) {
if(myparams[prop] === undefined) continue;
data[prop].attributes.parameters = myparams[prop];
}
}
/* ======================================================================================= */
function isObject(obj) {
return !!obj && typeof obj === 'object';
}
function dataExists(data, language, folder, filename) {
try {
if(language)
return data[language][folder][filename]
else
return data[folder][filename];
} catch {
return undefined;
}
}
// Initializes the nested property with an empty object {} if it doesn't exist. Returns reference to that empty object.
function initPropIfNotExist(obj, keyPath) {
for(const key of keyPath) {
if(!obj[key])
obj[key] = {}
obj = obj[key];
}
return obj;
}
function assignOverride(obj, keyPath, lastKey, value) {
obj = initPropIfNotExist(obj, keyPath);
obj[lastKey] = value;
}
function assignArray(obj, keyPath, lastKey, array) {
array = array.filter(val => typeof val === 'string');
if(array.length === 0) return;
obj = initPropIfNotExist(obj, keyPath);
if(!obj[lastKey]) return obj[lastKey] = array;
for(const value of array) {
if(typeof value !== 'string') continue;
if(!obj[lastKey].includes(value))
obj[lastKey].push(value);
}
}
const languagesArr = require('./language.js').languages;
const foldersArr = require('./folder.js').folders;
function addData(newdata, override = true) {
if(newdata instanceof ArrayBuffer || (Buffer && newdata instanceof Buffer)) {
newdata = JSON.parse(pako.ungzip(newdata, { to: 'string' }));
}
if(!isObject(newdata)) return;
if(isObject(newdata.data)) {
for(const language of languagesArr) {
if(!isObject(newdata.data[language])) continue;
for(const folder of Object.keys(newdata.data[language])) {
// for(const folder of foldersArr) { // commented to allow addition of new folders
if(!isObject(newdata.data[language][folder])) continue;
for(const filename in newdata.data[language][folder]) {
if(!isObject(newdata.data[language][folder][filename])) continue;
if(!override && dataExists(alldata, language, folder, filename)) continue;
assignOverride(alldata, [language, folder], filename, newdata.data[language][folder][filename]);
}
}
}
}
if(isObject(newdata.index)) {
for(const language of languagesArr) {
if(!isObject(newdata.index[language])) continue;
for(const folder of Object.keys(newdata.index[language])) { // allows new folders
if(!isObject(newdata.index[language][folder])) continue;
for(const indexprop of ['namemap', 'names', 'aliases', 'categories', 'properties']) {
initPropIfNotExist(allindex, ['language', 'folder', indexprop]);
if(!isObject(newdata.index[language][folder][indexprop])) continue;
for(const filename in newdata.index[language][folder][indexprop]) {
switch(indexprop) { // check if valid type (either string or array)
case 'namemap':
case 'names':
case 'aliases':
if(typeof newdata.index[language][folder][indexprop][filename] !== 'string') continue;
assignOverride(allindex, [language, folder, indexprop], filename, newdata.index[language][folder][indexprop][filename]);
break;
case 'categories':
case 'properties':
if(!Array.isArray(newdata.index[language][folder][indexprop][filename])) continue;
assignArray(allindex, [language, folder, indexprop], filename, newdata.index[language][folder][indexprop][filename]);
break;
}
}
}
}
}
}
if(isObject(newdata.image)) {
for(const folder of Object.keys(newdata.image)) {
// for(const folder of design.hasImage) { // commented to allow addition of new folders
if(!isObject(newdata.image[folder])) continue;
for(const filename in newdata.image[folder]) {
if(!isObject(newdata.image[folder][filename])) continue;
if(!override && dataExists(allimage, folder, filename)) continue;
assignOverride(allimage, [folder], filename, newdata.image[folder][filename]);
if(!design.hasImage.includes(folder)) design.hasImage.push(folder);
}
}
}
if(isObject(newdata.stats)) {
for(const folder of Object.keys(newdata.stats)) {
// for(const folder of design.hasStat) { // commented to allow addition of new folders
if(!isObject(newdata.stats[folder])) continue;
for(const filename in newdata.stats[folder]) {
if(!isObject(newdata.stats[folder][filename])) continue;
if(!override && dataExists(allimage, folder, filename)) continue;
assignOverride(allstats, [folder], filename, newdata.stats[folder][filename]);
if(!design.hasStat.includes(folder)) design.hasStat.push(folder);
}
}
}
if(isObject(newdata.curve)) {
for(const folder of Object.keys(newdata.curve)) {
// for(const folder of design.hasCurve) { // commented to allow addition of new folders
if(!isObject(newdata.curve[folder])) continue;
for(const filename in newdata.curve[folder]) {
if(!isObject(newdata.curve[folder][filename])) continue;
if(!override && dataExists(allimage, folder, filename)) continue;
assignOverride(allcurve, [folder], filename, newdata.curve[folder][filename]);
if(!design.hasCurve.includes(folder)) design.hasCurve.push(folder);
}
}
}
if(isObject(newdata.url)) {
for(const folder of Object.keys(newdata.url)) {
// for(const folder of design.hasUrl) { // commented to allow addition of new folders
if(!isObject(newdata.url[folder])) continue;
for(const filename in newdata.url[folder]) {
if(!isObject(newdata.url[folder][filename])) continue;
if(!override && dataExists(allimage, folder, filename)) continue;
assignOverride(allurl, [folder], filename, newdata.url[folder][filename]);
if(!design.hasUrl.includes(folder)) design.hasUrl.push(folder);
}
}
}
}
module.exports = {
addData: addData,
getData: getData,
getIndex: getIndex,
getImage: getImage,
getStats: getStats
}