Module:GameData/doc: Difference between revisions

Update to support 2023 Birthday event
(Splitting output data over multiple pages, as it is now too large for a single page (exceeds $wgMaxArticleSize))
(Update to support 2023 Birthday event)
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
The '''GameData''' module is the source of all game data which many other Lua modules rely upon. This module deals with the initial loading of the game data structure, and then enables other modules to access this both via a library of functions (preferred) and in its raw format.
To generate game data, do the following:
To generate game data, do the following:
# Navigate to https://melvoridle.com within your preferred web browser
# Navigate to https://melvoridle.com within your preferred web browser
Line 5: Line 7:
# Open the browser console/developer mode (usually by hitting the F12 key for most browsers)
# Open the browser console/developer mode (usually by hitting the F12 key for most browsers)
# Within the browser console, enter the following code then hit enter. If successful, the game data should appear within the console
# Within the browser console, enter the following code then hit enter. If successful, the game data should appear within the console
# Copy the game data & update [[Module:GameData/data]] accordingly
# Copy the game data & update [[Module:GameData/data]] and [[Module:GameData/data2]] accordingly


{{SpoilerBox|color=default|title=Code|text=<syntaxhighlight lang="javascript" line>class Wiki {
{{SpoilerBox|color=default|title=Code|text=<syntaxhighlight lang="javascript" line>class Wiki {
Line 11: Line 13:
this.debugMode = false;
this.debugMode = false;
this.prettyPrint = false;
this.prettyPrint = false;
this.baseDir = "/assets/data/";
this.namespaces = {
this.namespaces = {
melvorD: { displayName: "Demo", url: "https://" + location.hostname + "/assets/data/melvorDemo.json" },
melvorD: { displayName: "Demo", url: "https://" + location.hostname + this.baseDir + "melvorDemo.json" },
melvorF: { displayName: "Full Version", url: "https://" + location.hostname + "/assets/data/melvorFull.json" },
melvorF: { displayName: "Full Version", url: "https://" + location.hostname + this.baseDir + "melvorFull.json" },
melvorTotH: { displayName: "Throne of the Herald", url: "https://" + location.hostname + "/assets/data/melvorTotH.json" }
melvorTotH: { displayName: "Throne of the Herald", url: "https://" + location.hostname + this.baseDir + "melvorTotH.json" },
melvorAoD: { displayName: "Atlas of Discovery", url: "https://" + location.hostname + this.baseDir + "melvorExpansion2.json" },
melvorBirthday2023: { displayName: "Melvor Birthday 2023", url: "https://" + location.hostname + this.baseDir + "melvorBirthday2023.json" }
};
};
// Check all required namespaces are registered, as there are still some bits of data extracted from in-game rather than the data packages
// Check all required namespaces are registered, as there are still some bits of data extracted from in-game rather than the data packages
Line 27: Line 32:
// pages (Module:GameData then combines the data into a single structure upon
// pages (Module:GameData then combines the data into a single structure upon
// initialization).
// initialization).
this.maxPageBytes = 2*1024**2; // 2048KB
this.printPages = [
this.printPages = [
{ includeCategories: '*', destination: 'Module:GameData/data' },
{ includeCategories: '*', destination: 'Module:GameData/data' },
{ includeCategories: ['items'], destination: 'Module:GameData/data2' }
{ includeCategories: ['items', 'itemUpgrades', 'itemSynergies', 'modifierData', 'shopPurchases'], destination: 'Module:GameData/data2' }
];
];


Line 69: Line 75:
return data.find((obj) => obj[idKey] === objectID);
return data.find((obj) => obj[idKey] === objectID);
}
}
}
getCategoriesForPage(page) {
if (Array.isArray(page.includeCategories)) {
return page.includeCategories;
}
else if (page.includeCategories === '*') {
// Special value, include all categories other than those included within
// other pages
return Object.keys(this.gameData).filter((cat) => !this.printPages.some((p) => Array.isArray(p.includeCategories) && p.includeCategories.includes(cat)));
}
}
escapeQuotes(data) {
var newData = data.replace(/\\/g, '\\\\')
newData = newData.replace(/'/g, "\\'");
newData = newData.replace(/"/g, '\\"');
return newData;
}
formatJSONData(category, data) {
if (data === undefined) {
console.warn(`dataFormatter: Data for category ${ category } is undefined`);
return '';
}
if (this.debugMode) {
console.debug('Formatting category data: ' + category);
}
if (category === 'skillData') {
return '"' + category + '":[' + data.map((x) => this.escapeQuotes(JSON.stringify(x))).join(",' ..\n'") + ']';
}
else {
return '"' + category + '":' + this.escapeQuotes(JSON.stringify(data));
}
}
dataFullyLoaded() {
return Object.keys(this.packData).length >= Object.keys(this.namespaces).length;
}
printCategoryDataLength() {
if (!this.dataFullyLoaded()) {
throw new Error('Game data not loaded, use printWikiData first');
}
let dataLengths = [];
this.printPages.forEach((page) => {
const inclCat = this.getCategoriesForPage(page);
inclCat.forEach((cat) => {
dataLengths.push(({
page: page.destination,
category: cat,
length: this.formatJSONData(cat, this.gameData[cat]).length
}));
});
});
console.table(dataLengths);
}
}
async printWikiData() {
async printWikiData() {
Line 74: Line 131:
throw new Error('Game must be loaded into a character first');
throw new Error('Game must be loaded into a character first');
}
}
if (Object.keys(this.packData).length < Object.keys(this.namespaces).length) {
if (!this.dataFullyLoaded()) {
// Need to retrieve game data first
// Need to retrieve game data first
const result = await this.getWikiData();
const result = await this.getWikiData();
}
}
let dataObjText;
console.log('Printing data for game version ' + this.getGameVersion());
this.printPages.forEach((page) => {
this.printPages.forEach((page) => {
let inclCat = [];
const inclCat = this.getCategoriesForPage(page);
if (Array.isArray(page.includeCategories)) {
inclCat = page.includeCategories;
}
else if (page.includeCategories === '*') {
// Special value, include all categories other than those included within
// other pages
inclCat = Object.keys(this.gameData).filter((cat) => !this.printPages.some((p) => Array.isArray(p.includeCategories) && p.includeCategories.includes(cat)));
}
let gameDataFiltered = {};
let gameDataFiltered = {};
inclCat.forEach((cat) => gameDataFiltered[cat] = wd.gameData[cat]);
inclCat.forEach((cat) => gameDataFiltered[cat] = this.gameData[cat]);


// Convert game data into a JSON string for export
// Convert game data into a JSON string for export
dataObjText = undefined;
let dataText;
if (this.prettyPrint) {
if (this.prettyPrint) {
dataObjText = JSON.stringify(gameDataFiltered, undefined, '\t');
dataText = JSON.stringify(gameDataFiltered, undefined, '\t');
}
}
else {
else {
dataObjText = JSON.stringify(gameDataFiltered);
dataText = JSON.stringify(gameDataFiltered);
}
}
dataObjText = dataObjText.replace(/\'/g, "\\\'");
 
dataObjText = dataObjText.replace(/\\\"/g, "\\\\\"");
let dataText = '-- Version: ' + this.getGameVersion();
dataText += "\r\n\r\nlocal gameData = mw.text.jsonDecode('";
dataText += dataObjText;
dataText += "')\r\n\r\nreturn gameData";
console.log(`For page "${ page.destination }" (${ dataText.length.toLocaleString() } bytes):`);
console.log(`For page "${ page.destination }" (${ dataText.length.toLocaleString() } bytes):`);
if (dataText.length > this.maxPageBytes) {
console.warn(`Page "${ page.destination }" exceeds max page size of ${ (this.maxPageBytes / 1024).toLocaleString() }KB by ${ (dataText.length - this.maxPageBytes).toLocaleString() } bytes. Consider amending the printPages configuration to move some data categories from this page onto other pages.`)
}
console.log(dataText);
console.log(dataText);
  });
  });
Line 207: Line 253:
dataNode.data.masteryCheckpoints = [];
dataNode.data.masteryCheckpoints = [];
masteryCheckpoints.forEach((pct, idx) => {
masteryCheckpoints.forEach((pct, idx) => {
dataNode.data.masteryCheckpoints[idx] = getLangString('MASTERY_CHECKPOINT', `${ localID }_${ idx }`);
dataNode.data.masteryCheckpoints[idx] = this.getLangString('MASTERY_CHECKPOINT', `${ localID }_${ idx }`);
});
});
}
}
Line 279: Line 325:
// Remap a number of keys from their in-game names
// Remap a number of keys from their in-game names
const townKeys = [
const townKeys = [
{'from': 'TICK_LENGTH', 'to': 'tickLength'},
{from: 'BASE_STORAGE', to: 'baseStorage'},
{'from': 'MAX_TOWN_SIZE', 'to': 'maxTownSize'},
{from: 'BASE_TAX_RATE', to: 'baseTaxRate'},
{'from': 'SECTION_SIZE', 'to': 'sectionSize'},
{from: 'DECREASED_BUILDING_COST_CAP', to: 'decreasedBuildingCostCap' },
{'from': 'INITIAL_CITIZEN_COUNT', 'to': 'initialCitizenCount'},
{from: 'GP_PER_CITIZEN', to: 'gpPerCitizen'},
{'from': 'MIN_WORKER_AGE', 'to': 'minWorkerAge'},
{from: 'MAX_WORSHIP', to: 'maxWorship'},
{'from': 'MAX_WORKER_AGE', 'to': 'maxWorkerAge'},
{from: 'MINIMUM_HEALTH', to: 'minimumHealth'},
{'from': 'AGE_OF_DEATH', 'to': 'ageOfDeath'},
{from: 'populationForTier', to: 'populationForTier'},
{'from': 'MIN_MIGRATION_AGE', 'to': 'minMigrationAge'},
{from: 'TICK_LENGTH', to: 'tickLength'},
{'from': 'MAX_MIGRATION_AGE', 'to': 'maxMigrationAge'},
{from: 'RARE_SEASON_CHANCE', to: 'rareSeasonChance'},
{'from': 'BASE_TAX_RATE', 'to': 'baseTaxRate'},
{from: 'WORSHIP_CHANGE_COST', to: 'worshipChangeCost'},
{'from': 'EDUCATION_PER_CITIZEN', 'to': 'educationPerCitizen'},
{from: 'WORSHIP_CHECKPOINTS', to: 'worshipCheckpoints'},
{'from': 'HAPPINESS_PER_CITIZEN', 'to': 'happinessPerCitizen'},
{'from': 'CITIZEN_FOOD_USAGE', 'to': 'citizenFoodUsage'},
{'from': 'POPULATION_REQUIRED_FOR_BIRTH', 'to': 'populationRequiredForBirth'},
{'from': 'BASE_STORAGE', 'to': 'baseStorage'},
{'from': 'WORSHIP_CHECKPOINTS', 'to': 'worshipCheckpoints'},
{'from': 'MAX_WORSHIP', 'to': 'maxWorship'},
{'from': 'populationForTier', 'to': 'populationForTier'},
{'from': 'MAX_TRADER_STOCK_INCREASE', 'to': 'maxTraderStockIncrease'},
];
];
townKeys.forEach((k) => dataNode.data[k.to] = gameSkill[k.from]);
townKeys.forEach((k) => dataNode.data[k.to] = gameSkill[k.from]);
Line 354: Line 392:
// depending on the category in question
// depending on the category in question
switch(categoryName) {
switch(categoryName) {
case 'ancientRelics':
case 'ancientSpells':
case 'ancientSpells':
case 'archaicSpells':
case 'archaicSpells':
Line 442: Line 481:
const skillObj = this.gameData[categoryName][skillIdx].data;
const skillObj = this.gameData[categoryName][skillIdx].data;
Object.keys(skillData.data).forEach((dataKey) => {
Object.keys(skillData.data).forEach((dataKey) => {
if (Array.isArray(skillData.data[dataKey]) && skillData.data[dataKey].length > 0 && skillData.data[dataKey][0].insertAt !== undefined) {
// Special case for Township item conversions
//Data is ordered, special handling applies
if ((skillObj[dataKey] !== undefined) && (skillData.skillID === 'melvorD:Township') && (dataKey === 'itemConversions')) {
Object.keys(skillData.data[dataKey]).forEach((convKey) => {
skillData.data[dataKey][convKey].forEach((resource) => {
// Find the resource if it already exists within the combined data
const resourceIdx = skillObj[dataKey][convKey].findIndex((res) => res.resourceID === resource.resourceID);
if (resourceIdx === -1) {
skillObj[dataKey][convKey].push(resource);
}
else {
skillObj[dataKey][convKey][resourceIdx].items.push(...resource.items);
}
})
});
}
else if (Array.isArray(skillData.data[dataKey]) && skillData.data[dataKey].length > 0 && skillData.data[dataKey][0].insertAt !== undefined) {
// Data is ordered, special handling applies
skillObj[dataKey] = this.combineOrderedData(skillObj[dataKey], skillData.data[dataKey]);
skillObj[dataKey] = this.combineOrderedData(skillObj[dataKey], skillData.data[dataKey]);
}
}
Line 466: Line 520:
if (modificationData !== undefined) {
if (modificationData !== undefined) {
this.applyDataModifications(modificationData);
this.applyDataModifications(modificationData);
}
const dependentData = this.packData[namespace].dependentData;
if (dependentData !== undefined) {
// TODO Handle dependentData
}
}
}
}
Line 473: Line 531:
const modCat = modDataKeys[modCatID];
const modCat = modDataKeys[modCatID];
const catData = modData[modCat];
const catData = modData[modCat];
if (modCat === 'shopUpgradeChains') {
if ((modCat === 'shopUpgradeChains') || (modCat === 'shopPurchases')) {
// Modify the root upgrade ID of shop upgrade chains
// Modify the root upgrade ID of shop upgrade chains, and modify attributes of shop purchases
catData.forEach((modItem) => {
catData.forEach((modItem) => {
const modObjID = modItem.id;
const modObjID = modItem.id;
Line 486: Line 544:
}
}
else {
else {
const overrideKeys = {
purchaseRequirements: {
sourceKey: 'newRequirements', // Key that holds the data in the data package
destKey: 'purchaseRequirementsOverrides', // Key to insert into within this.gameData
subKey: 'requirements' // Sub-key containing the override data
},
cost: {
sourceKey: 'newCosts',
destKey: 'costOverrides',
subKey: 'cost'
}
};
Object.keys(modItem).filter((k) => k !== 'id').forEach((k) => {
Object.keys(modItem).filter((k) => k !== 'id').forEach((k) => {
modObj[k] = modItem[k];
const overrideKey = overrideKeys[k];
if (overrideKey !== undefined) {
// Is an override specific to a gamemode, do not replace
// the key's existing data
const destKey = overrideKey.destKey;
if (modObj[destKey] === undefined) {
modObj[destKey] = [];
}
modItem[k].forEach((gamemodeOverride) => {
var newData = {};
newData.gamemodeID = gamemodeOverride.gamemodeID;
newData[overrideKey.subKey] = gamemodeOverride[overrideKey.sourceKey];
modObj[destKey].push(newData);
});
}
else {
modObj[k] = modItem[k];
}
});
});
}
}
Line 555: Line 642:
else {
else {
console.warn(`Could not apply data modification: Unhandled key "${ k }" for category "${ modCat }", object "${ mobObjID }"`);
console.warn(`Could not apply data modification: Unhandled key "${ k }" for category "${ modCat }", object "${ mobObjID }"`);
}
});
}
}
});
}
else if (modCat === 'dungeons') {
catData.forEach((modItem) => {
const modObjID = modItem.id;
if (modObjID === undefined) {
console.warn(`Could not apply data modification: ID of object to be modified not found, category "${ modCat }"`);
}
else {
const modObj = this.getObjectByID(this.gameData.dungeons, modObjID);
if (modObj === undefined) {
console.warn(`Could not apply data modification: Object with ID "${ modObjID }" not found for category "${ modCat }"`);
}
else {
Object.keys(modItem).filter((k) => k !== 'id').forEach((k) => {
if (k === 'gamemodeRewardItemIDs') {
// Add gamemode specific item rewards to dungeon data
const itemRules = modItem[k];
Object.keys(itemRules).forEach((ruleKey) => {
if (ruleKey === 'add') {
if (modObj[k] === undefined) {
modObj[k] = [];
}
itemRules[ruleKey].forEach((itemDef) => {
let gamemodeRewards = this.getObjectByID(modObj[k], itemDef.gamemodeID, 'gamemodeID');
if (gamemodeRewards === undefined) {
modObj[k].push(({
gamemodeID: itemDef.gamemodeID,
itemIDs: itemDef.rewardItemIDs
}));
}
else {
gamemodeRewards.push(...itemDef.rewardItemIDs);
}
});
}
else {
console.warn(`Could not apply data modification: Unknown rule for gamemode item rewards: "${ ruleKey }", object "${ modObjID }"`);
}
});
}
else if (k === 'gamemodeEntryRequirements') {
// Add or remove gamemode specific entry requirements to dungeon data
if (modObj[k] === undefined) {
modObj[k] = [];
}
modObj[k].push(modItem[k]);
}
else {
console.warn(`Could not apply data modification: Unhandled key "${ k }" for category "${ modCat }", object "${ modObjID }"`);
}
}
});
});
Line 593: Line 734:
}
}
if (this.gameData.combatAreaDifficulties === undefined) {
if (this.gameData.combatAreaDifficulties === undefined) {
this.gameData.combatAreaDifficulties = CombatAreaMenu.difficulty.map((i) => i.name);
this.gameData.combatAreaDifficulties = CombatAreaMenuElement.difficulty.map((i) => i.name);
}
}
if (this.gameData.equipmentSlots === undefined) {
if (this.gameData.equipmentSlots === undefined) {
const slotIDs = Object.keys(EquipmentSlots).filter((slotID) => !isNaN(parseInt(slotID)));
const slotIDs = Object.keys(EquipmentSlots).filter((slotID) => !isNaN(parseInt(slotID)));
this.gameData.equipmentSlots = slotIDs.map((slotID) => ({id: EquipmentSlots[slotID], name: getLangString('EQUIP_SLOT', slotID)}));
this.gameData.equipmentSlots = slotIDs.map((slotID) => ({id: EquipmentSlots[slotID], name: this.getLangString('EQUIP_SLOT', slotID)}));
}
}
if (this.gameData.attackTypes === undefined) {
if (this.gameData.attackTypes === undefined) {
Line 656: Line 797:
throw new Error(`Couldn't insert before: Item ${ orderData.beforeID } is not in the array.`);
throw new Error(`Couldn't insert before: Item ${ orderData.beforeID } is not in the array.`);
}
}
resultData.splice(beforeIndex, 0, ...orderData.ids);
resultData.splice(beforeIdx, 0, ...orderData.ids);
break;
break;
case 'After':
case 'After':
Line 715: Line 856:
case 'langHint':
case 'langHint':
case 'langCustomDescription':
case 'langCustomDescription':
return getLangString(entity.category, this.getLocalID(entity.id));
return this.getLangString(entity.category, this.getLocalID(entity.id));
case 'equipmentStats':
case 'equipmentStats':
const newStats = {};
const newStats = {};
Line 765: Line 906:
return 'ALTMAGIC_DESC_{ID}';
return 'ALTMAGIC_DESC_{ID}';
};
};
const shopChainPropKey = function(data, dataKey, propName) {
const shopChainPropKey = (data, dataKey, propName) => {
// Accepts an upgrade chain data object & key of the property being localized
// Accepts an upgrade chain data object & key of the property being localized
const propToLang = {
const propToLang = {
Line 780: Line 921:
}
}
}
}
const itemDesc = function(data) {
const itemDesc = (data) => {
// Items have varying logic based on the type of item, and the lang data contains
// Items have varying logic based on the type of item, and the lang data contains
// some incorrect stuff for items whose descriptions are generated entirely
// some incorrect stuff for items whose descriptions are generated entirely
Line 788: Line 929:
if (item !== undefined) {
if (item !== undefined) {
desc = item.description;
desc = item.description;
if (desc === getLangString('BANK_STRING', '38')) {
if (desc === this.getLangString('BANK_STRING', '38')) {
// Generic "no description" string
// Generic "no description" string
return undefined;
return undefined;
Line 794: Line 935:
// Temporary fix for issue with language data keys for FrostSpark 1H Sword
// Temporary fix for issue with language data keys for FrostSpark 1H Sword
else if (desc.includes('UNDEFINED TRANSLATION') && data.id === 'melvorTotH:FrostSpark_1H_Sword') {
else if (desc.includes('UNDEFINED TRANSLATION') && data.id === 'melvorTotH:FrostSpark_1H_Sword') {
return getLangString('ITEM_DESCRIPTION', 'Frostspark_1H_Sword')
return this.getLangString('ITEM_DESCRIPTION', 'Frostspark_1H_Sword')
}
}
else {
else {
Line 801: Line 942:
}
}
}
}
const passiveDesc = function(data) {
const relicDesc = (data) => {
const relic = game.ancientRelics.getObjectByID(data.id);
if (relic !== undefined) {
return relic.name;
}
}
const passiveDesc = (data) => {
const passive = game.combatPassives.getObjectByID(data.id);
const passive = game.combatPassives.getObjectByID(data.id);
if (passive !== undefined) {
if (passive !== undefined) {
Line 807: Line 954:
}
}
}
}
const spAttDesc = function(data) {
const spAttDesc = (data) => {
const spAtt = game.specialAttacks.getObjectByID(data.id);
const spAtt = game.specialAttacks.getObjectByID(data.id);
if (spAtt !== undefined) {
if (spAtt !== undefined) {
Line 813: Line 960:
}
}
}
}
const tsWorshipName = (data) => {
const worship = game.township.worships.getObjectByID(data.id);
if (worship !== undefined) {
return worship.name;
}
}
const tsWorshipStatueName = (data) => {
const worship = game.township.worships.getObjectByID(data.id);
if (worship !== undefined) {
return worship.statueName;
}
}
const hasNoLangData = [
// Categories that contain no localized text. Supresses warnings about no lang data
'bankSortOrder',
'combatAreaDisplayOrder',
'combatEvents',
'dungeonDisplayOrder',
'golbinRaid',
'itemEffects',
'itemSynergies',
'itemUpgrades',
'itmMonsters',
'randomGems',
'randomSuperiorGems',
'slayerAreaDisplayOrder',
'shopCategoryOrder',
'shopDisplayOrder',
'spiderLairMonsters',
'stackingEffects'
];
const langKeys = {
const langKeys = {
ancientRelics: {
name: { stringSpecial: 'relicDesc' }
},
ancientSpells: {
ancientSpells: {
name: { key: 'MAGIC', idFormat: 'ANCIENT_NAME_{ID}' }
name: { key: 'MAGIC', idFormat: 'ANCIENT_NAME_{ID}' }
Line 905: Line 1,086:
description: { key: 'MASTERY_BONUS', idKey: 'descriptionID', idFormat: '{SKILLID}_{ID}' }
description: { key: 'MASTERY_BONUS', idKey: 'descriptionID', idFormat: '{SKILLID}_{ID}' }
}
}
},
Archaeology: {
digSites: {
name: { key: 'POI_NAME_Melvor' }
}
// TODO Tool names
},
},
Agility: {
Agility: {
Line 921: Line 1,108:
name: { key: 'ASTROLOGY', idFormat: 'NAME_{ID}' }
name: { key: 'ASTROLOGY', idFormat: 'NAME_{ID}' }
}
}
},
Cartography: {
mapPortals: { _handler: 'mapPortals' },
travelEvents: {
description: { key: 'TRAVEL_EVENT' }
},
worldMaps: { _handler: 'cartoMaps' }
//name: { key: 'WORLD_MAP_NAME' },
//pointsOfInterest: { _handler: 'mapPOI' }
//name: { key: 'POI_NAME', idFormat: '{MAPID}_{ID}' },
//description: { key: 'POI_DESCRIPTION', idFormat: '{MAPID}_{ID}' }
},
},
Farming: {
Farming: {
Line 984: Line 1,182:
},
},
worships: {
worships: {
name: { key: 'MONSTER_NAME' },
name: { stringSpecial: 'tsWorshipName' },
statueName: { key: 'TOWNSHIP', idFormat: 'Statue_of_{ID}' }
statueName: { stringSpecial: 'tsWorshipStatueName' }
}
}
},
},
Line 1,022: Line 1,220:
langKeyData = { _root: langKeys[nodeKey] };
langKeyData = { _root: langKeys[nodeKey] };
}
}
else {
else if (!hasNoLangData.includes(nodeKey)) {
console.warn('No lang key data found for ' + nodeKey);
console.warn('No lang key data found for ' + nodeKey);
}
}
Line 1,040: Line 1,238:
const targetArr = (Array.isArray(targetData) ? targetData : [ targetData ]);
const targetArr = (Array.isArray(targetData) ? targetData : [ targetData ]);
targetArr.forEach((target) => {
targetArr.forEach((target) => {
Object.keys(langKeyData[langKey]).forEach((langPropID) => {
const handlerFunc = langKeyData[langKey]['_handler'];
const langProp = langKeyData[langKey][langPropID];
if (handlerFunc !== undefined) {
if (!langProp.onlyIfExists || target[langPropID] !== undefined) {
switch(handlerFunc) {
const langIDKey = langProp.idKey ?? 'id';
case 'mapPortals':
var langIDValue;
Object.keys(target).forEach((portalKey) => {
if (Array.isArray(target[langIDKey])) {
let portalData = target[portalKey];
// The ID key can sometimes be an array of IDs (e.g. Summoning synergies)
const langID = this.getLocalID(portalData.originWorldMap) + '_' + this.getLocalID(portalData.id);
langIDValue = target[langIDKey].map((id) => this.getLocalID((id ?? '').toString()));
portalData.name = this.getLangString('POI_NAME', langID);
}
portalData.description = this.getLangString('POI_DESCRIPTION', langID);
else {
});
langIDValue = this.getLocalID((target[langIDKey] ?? '').toString());
break;
}
case 'cartoMaps':
let langIdent = langProp.idFormat;
// Target represents a world map
if (langProp.idSpecial !== undefined) {
const mapID = this.getLocalID(target.id);
// Use a special method to determine the ID format
target.name = this.getLangString('WORLD_MAP_NAME', mapID);
switch(langProp.idSpecial) {
// Process POIs
case 'altMagicDesc':
target.pointsOfInterest.forEach((poi) => {
langIdent = altMagicDescIDKey(target);
const langID = mapID + '_' + this.getLocalID(poi.id);
break;
poi.name = this.getLangString('POI_NAME', langID);
case 'shopChainID':
poi.description = this.getLangString('POI_DESCRIPTION', langID);
langIdent = this.getLocalID(shopChainPropKey(target, langPropID, 'id'));
});
break;
break;
}
}
else {
Object.keys(langKeyData[langKey]).forEach((langPropID) => {
const langProp = langKeyData[langKey][langPropID];
if (!langProp.onlyIfExists || target[langPropID] !== undefined) {
const langIDKey = langProp.idKey ?? 'id';
var langIDValue;
if (Array.isArray(target[langIDKey])) {
// The ID key can sometimes be an array of IDs (e.g. Summoning synergies)
langIDValue = target[langIDKey].map((id) => this.getLocalID((id ?? '').toString()));
}
else {
langIDValue = this.getLocalID((target[langIDKey] ?? '').toString());
}
}
}
let langIdent = langProp.idFormat;
if (langIdent === undefined) {
if (langProp.idSpecial !== undefined) {
langIdent = langIDValue;
// Use a special method to determine the ID format
}
switch(langProp.idSpecial) {
else {
case 'altMagicDesc':
// langIdent is in a specific format
langIdent = altMagicDescIDKey(target);
const langTemplate = {}
break;
if (isSkill) {
case 'shopChainID':
langTemplate.SKILLID = this.getLocalID(parentNode[nodeKey].skillID);
langIdent = this.getLocalID(shopChainPropKey(target, langPropID, 'id'));
break;
}
}
}
if (Array.isArray(langIDValue)) {
if (langIdent === undefined) {
langIDValue.forEach((val, idx) => {
langIdent = langIDValue;
langTemplate['ID' + idx] = this.getLocalID(val);
});
}
}
else {
else {
langTemplate.ID = langIDValue;
// langIdent is in a specific format
const langTemplate = {}
if (isSkill) {
langTemplate.SKILLID = this.getLocalID(parentNode[nodeKey].skillID);
}
if (Array.isArray(langIDValue)) {
langIDValue.forEach((val, idx) => {
langTemplate['ID' + idx] = this.getLocalID(val);
});
}
else {
langTemplate.ID = langIDValue;
}
Object.keys(langTemplate).forEach((k) => {
langIdent = langIdent.replaceAll('{' + k + '}', langTemplate[k]);
});
}
}
Object.keys(langTemplate).forEach((k) => {
langIdent = langIdent.replaceAll('{' + k + '}', langTemplate[k]);
});
}


let langCategoryKey = langProp.key;
let langCategoryKey = langProp.key;
if (langProp.keySpecial !== undefined) {
if (langProp.keySpecial !== undefined) {
// Use a special method to determine the category key
// Use a special method to determine the category key
switch(langProp.keySpecial) {
switch(langProp.keySpecial) {
case 'shopChainKey':
case 'shopChainKey':
langCategoryKey = shopChainPropKey(target, langPropID, 'category');
langCategoryKey = shopChainPropKey(target, langPropID, 'category');
break;
break;
}
}
}
}


if (Array.isArray(target[langPropID])) {
if (Array.isArray(target[langPropID])) {
target[langPropID].forEach((targetElem, num) => {
target[langPropID].forEach((targetElem, num) => {
const langIdentFinal = langIdent.replaceAll('{NUM}', num.toString());
const langIdentFinal = langIdent.replaceAll('{NUM}', num.toString());
const langString = this.getLangString(langCategoryKey, langIdentFinal);
const langString = this.getLangString(langCategoryKey, langIdentFinal);
target[langPropID][num] = langString;
target[langPropID][num] = langString;
if (this.debugMode) {
if (langString !== undefined) {
console.debug('Set value of property ' + langPropID + '[' + num.toString() + '] for ' + langIdentFinal + ' in node ' + nodeName + ' to: ' + langString);
}
else {
console.debug('No translation: property ' + langPropID + ' for ' + langIdentFinal + ' in node ' + nodeName);
}
}
});
}
else {
let langString;
if (langProp.stringSpecial !== undefined) {
// Use a custom function to determine the string
switch(langProp.stringSpecial) {
case 'itemDesc':
langString = itemDesc(target);
break;
case 'passiveDesc':
langString = passiveDesc(target);
break;
case 'relicDesc':
langString = relicDesc(target);
break;
case 'spAttDesc':
langString = spAttDesc(target);
break;
case 'tsWorshipName':
langString = tsWorshipName(target);
break;
case 'tsWorshipStatueName':
langString = tsWorshipStatueName(target);
break;
}
}
else {
langString = this.getLangString(langCategoryKey, langIdent);
}
target[langPropID] = langString;
if (this.debugMode) {
if (this.debugMode) {
if (langString !== undefined) {
if (langString !== undefined) {
console.debug('Set value of property ' + langPropID + '[' + num.toString() + '] for ' + langIdentFinal + ' in node ' + nodeName + ' to: ' + langString);
console.debug('Set value of property ' + langPropID + ' for ' + langIdent + ' in node ' + nodeName + ' to: ' + langString);
}
}
else {
else {
console.debug('No translation: property ' + langPropID + ' for ' + langIdentFinal + ' in node ' + nodeName);
console.debug('No translation: property ' + langPropID + ' for ' + langIdent + ' in node ' + nodeName);
}
}
}
});
}
else {
let langString;
if (langProp.stringSpecial !== undefined) {
// Use a custom function to determine the string
switch(langProp.stringSpecial) {
case 'itemDesc':
langString = itemDesc(target);
break;
case 'passiveDesc':
langString = passiveDesc(target);
break;
case 'spAttDesc':
langString = spAttDesc(target);
break;
}
}
else {
langString = this.getLangString(langCategoryKey, langIdent);
}
target[langPropID] = langString;
if (this.debugMode) {
if (langString !== undefined) {
console.debug('Set value of property ' + langPropID + ' for ' + langIdent + ' in node ' + nodeName + ' to: ' + langString);
}
else {
console.debug('No translation: property ' + langPropID + ' for ' + langIdent + ' in node ' + nodeName);
}
}
}
}
}
}
}
});
});
}
});
});
}
}
Line 1,149: Line 1,382:
}
}
getLangString(key, identifier) {
getLangString(key, identifier) {
const langCat = loadedLangJson[key];
if (identifier === undefined) {
if (langCat !== undefined) {
return loadedLangJson[key];
return langCat[identifier];
}
else {
return loadedLangJson[key + '_' + identifier];
}
}
}
}