Anonymous

Module:GameData/doc: Difference between revisions

From Melvor Idle
Update for v1.3
(Update to support 2023 Birthday event)
(Update for v1.3)
Line 7: 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]] and [[Module:GameData/data2]] accordingly
# Copy the game data & update [[Module:GameData/data]], [[Module:GameData/data2]], [[Module:GameData/data3]] 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 13: Line 13:
this.debugMode = false;
this.debugMode = false;
this.prettyPrint = false;
this.prettyPrint = false;
this.baseDir = "/assets/data/";
this.baseDir = '/assets/data/';
this.namespaces = {
this.namespaces = {
melvorD: { displayName: "Demo", url: "https://" + location.hostname + this.baseDir + "melvorDemo.json" },
melvorD: { displayName: 'Demo', url: 'https://' + location.hostname + this.baseDir + 'melvorDemo.json' },
melvorF: { displayName: "Full Version", url: "https://" + location.hostname + this.baseDir + "melvorFull.json" },
melvorF: { displayName: 'Full Version', url: 'https://' + location.hostname + this.baseDir + 'melvorFull.json' },
melvorTotH: { displayName: "Throne of the Herald", url: "https://" + location.hostname + this.baseDir + "melvorTotH.json" },
melvorTotH: {
melvorAoD: { displayName: "Atlas of Discovery", url: "https://" + location.hostname + this.baseDir + "melvorExpansion2.json" },
displayName: 'Throne of the Herald',
melvorBirthday2023: { displayName: "Melvor Birthday 2023", url: "https://" + location.hostname + this.baseDir + "melvorBirthday2023.json" }
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',
},
melvorItA: {
displayName: 'Into the Abyss',
url: 'https://' + location.hostname + this.baseDir + 'melvorItA.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 25: Line 38:
const nsTest = game.registeredNamespaces.getNamespace(nsID);
const nsTest = game.registeredNamespaces.getNamespace(nsID);
if (nsTest === undefined) {
if (nsTest === undefined) {
throw new Error(`Namespace ${ nsID } (${ this.namespaces[nsID].displayName }) is not registered - Ensure you are signed in and have the expansion.`);
throw new Error(
`Namespace ${nsID} (${this.namespaces[nsID].displayName}) is not registered - Ensure you are signed in and have the expansion.`
);
}
}
});
});
Line 32: Line 47:
// 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.maxPageBytes = 2 * 1024 ** 2; // 2048KB
this.printPages = [
this.printPages = [
{ includeCategories: '*', destination: 'Module:GameData/data' },
{ includeCategories: '*', destination: 'Module:GameData/data' },
{ includeCategories: ['items', 'itemUpgrades', 'itemSynergies', 'modifierData', 'shopPurchases'], destination: 'Module:GameData/data2' }
{
includeCategories: ['items'],
destination: 'Module:GameData/data2',
},
{
includeCategories: [
'itemUpgrades',
'itemSynergies',
'modifiers',
'shopPurchases',
'realms',
'damageTypes',
'combatTriangleSets',
'randomAbyssalGems',
'randomFragments',
'randomFiremakingOils',
'ancientRelics',
'attackSpells',
'attacks',
'combatPassives',
'monsters',
'bankSortOrder',
'combatEffects',
'combatEffectTemplates',
'combatEffectGroups'
],
destination: 'Module:GameData/data3',
},
];
];


Line 41: Line 83:
this.gameData = {};
this.gameData = {};
this.skillDataInit = {};
this.skillDataInit = {};
};
}
async getWikiData() {
async getWikiData() {
if (!isLoaded) {
if (!isLoaded) {
Line 49: Line 91:
const ns = Object.keys(this.namespaces)[nsIdx];
const ns = Object.keys(this.namespaces)[nsIdx];
const dataURL = this.namespaces[ns].url;
const dataURL = this.namespaces[ns].url;
console.log(`URL: ${ dataURL }`);
console.log(`URL: ${dataURL}`);
const dataPackage = await this.getDataPackage(dataURL);
const dataPackage = await this.getDataPackage(dataURL);
if (dataPackage.namespace === undefined) {
if (dataPackage.namespace === undefined) {
throw new Error(`Data package has no namespace: ${ dataURL }`);
throw new Error(`Data package has no namespace: ${dataURL}`);
}
} else if (dataPackage.data === undefined) {
else if (dataPackage.data === undefined) {
throw new Error(`Data package has no data: ${dataURL}`);
throw new Error(`Data package has no data: ${ dataURL }`);
}
}
console.log(`Obtained data for namespace ${ dataPackage.namespace }, ${ JSON.stringify(dataPackage.data).length.toLocaleString() } bytes`);
console.log(
`Obtained data for namespace ${dataPackage.namespace}, ${JSON.stringify(
dataPackage.data
).length.toLocaleString()} bytes`
);
this.processDataPackage(dataPackage);
this.processDataPackage(dataPackage);
console.log(`After transformation: ${ JSON.stringify(dataPackage.data).length.toLocaleString() } bytes`);
console.log(`After transformation: ${JSON.stringify(dataPackage.data).length.toLocaleString()} bytes`);
}
}
// All data packages should now be within this.gameData
// All data packages should now be within this.gameData
Line 65: Line 110:
getGameVersion() {
getGameVersion() {
const fileDOM = document.querySelector('#sidebar ul.nav-main');
const fileDOM = document.querySelector('#sidebar ul.nav-main');
let fileVer = "Unknown";
let fileVer = 'Unknown';
if (fileDOM !== null && fileDOM.dataset !== undefined) {
if (fileDOM !== null && fileDOM.dataset !== undefined) {
fileVer = fileDOM.dataset.fileVersion;
fileVer = fileDOM.dataset.fileVersion;
Line 72: Line 117:
}
}
getObjectByID(data, objectID, idKey = 'id') {
getObjectByID(data, objectID, idKey = 'id') {
if ((data !== undefined) && (objectID !== undefined)) {
if (data !== undefined && objectID !== undefined) {
return data.find((obj) => obj[idKey] === objectID);
return data.find((obj) => obj[idKey] === objectID);
}
}
Line 79: Line 124:
if (Array.isArray(page.includeCategories)) {
if (Array.isArray(page.includeCategories)) {
return page.includeCategories;
return page.includeCategories;
}
} else if (page.includeCategories === '*') {
else if (page.includeCategories === '*') {
// Special value, include all categories other than those included within
// Special value, include all categories other than those included within
// other pages
// other pages
return Object.keys(this.gameData).filter((cat) => !this.printPages.some((p) => Array.isArray(p.includeCategories) && p.includeCategories.includes(cat)));
return Object.keys(this.gameData).filter(
(cat) => !this.printPages.some((p) => Array.isArray(p.includeCategories) && p.includeCategories.includes(cat))
);
}
}
}
}
escapeQuotes(data) {
escapeQuotes(data) {
var newData = data.replace(/\\/g, '\\\\')
var newData = data.replace(/\\/g, '\\\\');
newData = newData.replace(/'/g, "\\'");
newData = newData.replace(/'/g, "\\'");
newData = newData.replace(/"/g, '\\"');
newData = newData.replace(/"/g, '\\"');
Line 94: Line 140:
formatJSONData(category, data) {
formatJSONData(category, data) {
if (data === undefined) {
if (data === undefined) {
console.warn(`dataFormatter: Data for category ${ category } is undefined`);
console.warn(`dataFormatter: Data for category ${category} is undefined`);
return '';
return '';
}
}
Line 102: Line 148:
if (category === 'skillData') {
if (category === 'skillData') {
return '"' + category + '":[' + data.map((x) => this.escapeQuotes(JSON.stringify(x))).join(",' ..\n'") + ']';
return '"' + category + '":[' + data.map((x) => this.escapeQuotes(JSON.stringify(x))).join(",' ..\n'") + ']';
}
} else {
else {
return '"' + category + '":' + this.escapeQuotes(JSON.stringify(data));
return '"' + category + '":' + this.escapeQuotes(JSON.stringify(data));
}
}
Line 118: Line 163:
const inclCat = this.getCategoriesForPage(page);
const inclCat = this.getCategoriesForPage(page);
inclCat.forEach((cat) => {
inclCat.forEach((cat) => {
dataLengths.push(({
dataLengths.push({
page: page.destination,
page: page.destination,
category: cat,
category: cat,
length: this.formatJSONData(cat, this.gameData[cat]).length
length: this.formatJSONData(cat, this.gameData[cat]).length,
}));
});
});
});
});
});
Line 139: Line 184:
const inclCat = this.getCategoriesForPage(page);
const inclCat = this.getCategoriesForPage(page);
let gameDataFiltered = {};
let gameDataFiltered = {};
inclCat.forEach((cat) => gameDataFiltered[cat] = this.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
Line 145: Line 190:
if (this.prettyPrint) {
if (this.prettyPrint) {
dataText = JSON.stringify(gameDataFiltered, undefined, '\t');
dataText = JSON.stringify(gameDataFiltered, undefined, '\t');
}
} else {
else {
dataText = JSON.stringify(gameDataFiltered);
dataText = JSON.stringify(gameDataFiltered);
}
}


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) {
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.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);
});
});
}
}
async getDataPackage(url) {
async getDataPackage(url) {
Line 163: Line 211:
return await fetch(url, {
return await fetch(url, {
method: 'GET',
method: 'GET',
headers
headers,
}).then(function(response) {
}).then(function (response) {
if (!response.ok) {
if (!response.ok) {
throw new Error(`Couldn't fetch data package from URL: ${ url }`);
throw new Error(`Couldn't fetch data package from URL: ${url}`);
}
}
return response.json();
return response.json();
Line 187: Line 235:


Object.keys(packData).forEach((categoryName) => {
Object.keys(packData).forEach((categoryName) => {
switch(categoryName) {
switch (categoryName) {
case 'pages':
case 'pages':
case 'steamAchievements':
case 'steamAchievements':
Line 213: Line 261:
// Recursive call to ensure all data is transformed, regardless of its depth
// Recursive call to ensure all data is transformed, regardless of its depth
dataNode.forEach((entity, idx) => this.transformDataNode(ns, categoryName, dataNode, idx));
dataNode.forEach((entity, idx) => this.transformDataNode(ns, categoryName, dataNode, idx));
}
} else if (typeof dataNode === 'object' && dataNode !== null) {
else if (typeof dataNode === 'object' && dataNode !== null) {
// Iterate properties of object, checking if each should be deleted or transformed
// Iterate properties of object, checking if each should be deleted or transformed
Object.keys(dataNode).forEach((key) => {
Object.keys(dataNode).forEach((key) => {
Line 220: Line 267:
if (this.isPropertyFiltered(categoryName, dataNode, key)) {
if (this.isPropertyFiltered(categoryName, dataNode, key)) {
delete dataNode[key];
delete dataNode[key];
}
} else if (typeof dataNode[key] === 'object' && dataNode[key] !== null) {
else if (typeof dataNode[key] === "object" && dataNode[key] !== null) {
// If an object (either an array or key/value store) is within the current
// If an object (either an array or key/value store) is within the current
// object then we must traverse this too
// object then we must traverse this too
this.transformDataNode(ns, categoryName, dataNode, key);
this.transformDataNode(ns, categoryName, dataNode, key);
}
} else {
else {
// Transform property, if a transformation is defined below
// Transform property, if a transformation is defined below
switch(key) {
switch (key) {
case 'id':
case 'id':
// Add namespace to ID if it isn't already
// Add namespace to ID if it isn't already
dataNode[key] = this.getNamespacedID(ns, dataNode[key]);
const id = dataNode[key];
if (!Number.isInteger(id)) dataNode[key] = this.getNamespacedID(ns, dataNode[key]);
break;
break;
}
}
Line 244: Line 290:
// Special case for skillData so that certain values initialized when the various Skill
// Special case for skillData so that certain values initialized when the various Skill
// classes are initialized may be added here also
// classes are initialized may be added here also
if ((categoryName === 'skillData') && dataNode.skillID !== undefined && dataNode.data !== undefined) {
if (categoryName === 'skillData' && dataNode.skillID !== undefined && dataNode.data !== undefined) {
// We are currently at the topmost level of a skill object
// We are currently at the topmost level of a skill object
const gameSkill = game.skills.getObjectByID(dataNode.skillID);
const gameSkill = game.skills.getObjectByID(dataNode.skillID);
// For every skill with mastery, add mastery checkpoint descriptions
// For every skill with mastery, add mastery checkpoint descriptions
if (gameSkill instanceof SkillWithMastery && dataNode.data.masteryTokenID !== undefined && dataNode.data.masteryCheckpoints === undefined) {
if (
gameSkill instanceof SkillWithMastery &&
dataNode.data.masteryTokenID !== undefined &&
dataNode.data.masteryCheckpoints === undefined
) {
const localID = this.getLocalID(dataNode.skillID);
const localID = this.getLocalID(dataNode.skillID);
dataNode.data.baseMasteryPoolCap = gameSkill.baseMasteryPoolCap;
dataNode.data.baseMasteryPoolCap = gameSkill.baseMasteryPoolCap;
dataNode.data.masteryCheckpoints = [];
dataNode.data.masteryCheckpoints = [];
masteryCheckpoints.forEach((pct, idx) => {
masteryCheckpoints.forEach((pct, idx) => {
dataNode.data.masteryCheckpoints[idx] = this.getLangString('MASTERY_CHECKPOINT', `${ localID }_${ idx }`);
dataNode.data.masteryCheckpoints[idx] = this.getLangString('MASTERY_CHECKPOINT', `${localID}_${idx}`);
});
});
}
}
Line 260: Line 310:
// Import other attributes varying by skill
// Import other attributes varying by skill
let importKeys = [];
let importKeys = [];
switch(dataNode.skillID) {
switch (dataNode.skillID) {
case 'melvorD:Firemaking':
case 'melvorD:Mining':
importKeys = [
importKeys = ['baseInterval', 'baseRockHP', 'passiveRegenInterval'];
'baseAshChance',
dataNode.data.baseGemChance = 1;
'baseStardustChance',
dataNode.data.rockTypes = loadedLangJson.MINING_TYPE;
'baseCharcoalChance'
];
break;
break;
case 'melvorD:Mining':
case 'melvorItA:Harvesting':
importKeys = [
importKeys = [
'baseInterval',
'baseInterval',
'baseRockHP',
'baseVeinIntensity',
'passiveRegenInterval'
'passiveRegenInterval',
'uniqueProductChance',
'hpCheckpoints',
];
];
dataNode.data.baseGemChance = 1;
dataNode.data.rockTypes = loadedLangJson.MINING_TYPE;
break;
break;
case 'melvorD:Smithing':
case 'melvorD:Smithing':
Line 282: Line 330:
case 'melvorD:Runecrafting':
case 'melvorD:Runecrafting':
case 'melvorD:Herblore':
case 'melvorD:Herblore':
importKeys = [
importKeys = ['baseInterval'];
'baseInterval'
];
break;
break;
case 'melvorD:Thieving':
case 'melvorD:Thieving':
importKeys = [
importKeys = ['baseInterval', 'baseStunInterval', 'itemChance', 'baseAreaUniqueChance'];
'baseInterval',
'baseStunInterval',
'itemChance',
'baseAreaUniqueChance'
];
break;
case 'melvorD:Agility':
importKeys = [
'obstacleUnlockLevels'
];
break;
break;
case 'melvorD:Summoning':
case 'melvorD:Summoning':
importKeys = [
importKeys = ['baseInterval'];
'baseInterval'
const sumKeys = ['recipeGPCost', 'markLevels'];
];
sumKeys.forEach((k) => (dataNode.data[k] = Summoning[k]));
const sumKeys = [
'recipeGPCost',
'markLevels'
];
sumKeys.forEach((k) => dataNode.data[k] = Summoning[k]);
break;
break;
case 'melvorD:Astrology':
case 'melvorD:Astrology':
Line 314: Line 345:
'standardModifierLevels',
'standardModifierLevels',
'uniqueModifierLevels',
'uniqueModifierLevels',
'standardModifierCosts',
'abyssalModifierLevels',
'uniqueModifierCosts',
'baseInterval',
'baseStardustChance',
'baseGoldenStardustChance',
'baseInterval'
];
];
astKeys.forEach((k) => dataNode.data[k] = Astrology[k]);
astKeys.forEach((k) => (dataNode.data[k] = Astrology[k]));
break;
break;
case 'melvorD:Township':
case 'melvorD:Township':
// 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: 'BASE_STORAGE', to: 'baseStorage'},
{ from: 'BASE_STORAGE', to: 'baseStorage' },
{from: 'BASE_TAX_RATE', to: 'baseTaxRate'},
{ from: 'BASE_TAX_RATE', to: 'baseTaxRate' },
{from: 'DECREASED_BUILDING_COST_CAP', to: 'decreasedBuildingCostCap' },
{ from: 'DECREASED_BUILDING_COST_CAP', to: 'decreasedBuildingCostCap' },
{from: 'GP_PER_CITIZEN', to: 'gpPerCitizen'},
{ from: 'GP_PER_CITIZEN', to: 'gpPerCitizen' },
{from: 'MAX_WORSHIP', to: 'maxWorship'},
{ from: 'MAX_WORSHIP', to: 'maxWorship' },
{from: 'MINIMUM_HEALTH', to: 'minimumHealth'},
{ from: 'MINIMUM_HEALTH', to: 'minimumHealth' },
{from: 'populationForTier', to: 'populationForTier'},
{ from: 'populationForTier', to: 'populationForTier' },
{from: 'TICK_LENGTH', to: 'tickLength'},
{ from: 'TICK_LENGTH', to: 'tickLength' },
{from: 'RARE_SEASON_CHANCE', to: 'rareSeasonChance'},
{ from: 'RARE_SEASON_CHANCE', to: 'rareSeasonChance' },
{from: 'WORSHIP_CHANGE_COST', to: 'worshipChangeCost'},
{ from: 'WORSHIP_CHANGE_COST', to: 'worshipChangeCost' },
{from: 'WORSHIP_CHECKPOINTS', to: 'worshipCheckpoints'},
{ from: 'WORSHIP_CHECKPOINTS', to: 'worshipCheckpoints' },
{ from: 'BASE_MAX_HEALTH', to: 'baseMaxHealth' },
{ from: 'abyssalTierRequirements', to: 'abyssalTierRequirements' },
{ from: 'BASE_SOUL_STORAGE', to: 'baseSoulStorage' },
];
];
townKeys.forEach((k) => dataNode.data[k.to] = gameSkill[k.from]);
townKeys.forEach((k) => (dataNode.data[k.to] = gameSkill[k.from]));
// Add task categories & localization of name
// Add task categories & localization of name
const taskCategories = Array.from(new Set(gameSkill.tasks.tasks.allObjects.map((t) => t.category)));
const taskCategories = Array.from(new Set(gameSkill.tasks.tasks.allObjects.map((t) => t.category)));
dataNode.data.taskCategories = taskCategories.map((i) => ({ id: i, name: gameSkill.tasks.getTownshipTaskCategoryName(i)}));
dataNode.data.taskCategories = taskCategories.map((category) => ({
id: category.id,
name: category.name,
}));
break;
break;
}
}
if (importKeys.length > 0) {
if (importKeys.length > 0) {
importKeys.forEach((k) => dataNode.data[k] = gameSkill[k]);
importKeys.forEach((k) => (dataNode.data[k] = gameSkill[k]));
}
}
}
}
Line 358: Line 392:
const packData = this.packData[namespace].data;
const packData = this.packData[namespace].data;
if (packData === undefined) {
if (packData === undefined) {
throw new Error(`Couldn't find data for package ${ namespace }`);
throw new Error(`Couldn't find data for package ${namespace}`);
}
}
// Add data within the game but outside of data packs
// Add data within the game but outside of data packs
Line 366: Line 400:
let categoryData = packData[categoryName];
let categoryData = packData[categoryName];
// Some data is adjusted before combining - do this here
// Some data is adjusted before combining - do this here
if (['combatAreas', 'dungeons', 'slayerAreas'].includes(categoryName)) {
if (['combatAreas', 'dungeons', 'slayerAreas', 'abyssDepths'].includes(categoryName)) {
// Add area type to each area object
// Add area type to each area object
const areaTypes = {
const areaTypes = {
'combatAreas': 'combatArea',
combatAreas: 'combatArea',
'dungeons': 'dungeon',
dungeons: 'dungeon',
'slayerAreas': 'slayerArea'
slayerAreas: 'slayerArea',
}
abyssDepths: 'abyssDepth',
};
const areaType = areaTypes[categoryName];
const areaType = areaTypes[categoryName];
const newData = structuredClone(categoryData);
const newData = structuredClone(categoryData);
newData.forEach((x) => x.type = areaType);
newData.forEach((x) => (x.type = areaType));
categoryData = newData;
categoryData = newData;
}
} /*else if (
else if (['ancientSpells', 'archaicSpells', 'auroraSpells', 'curseSpells', 'standardSpells'].includes(categoryName)) {
['ancientSpells', 'archaicSpells', 'auroraSpells', 'curseSpells', 'standardSpells'].includes(categoryName)
) {
// For spell books, add the spell type to each spell object.
// For spell books, add the spell type to each spell object.
// Alt Magic spells are handled elsewhere, as they are within a skill object
// Alt Magic spells are handled elsewhere, as they are within a skill object
const spellType = categoryName.replace('Spells', '');
const spellType = categoryName.replace('Spells', '');
const newData = structuredClone(categoryData);
const newData = structuredClone(categoryData);
newData.forEach((x) => x.spellBook = spellType);
newData.forEach((x) => (x.spellBook = spellType));
categoryData = newData;
categoryData = newData;
}
}*/ else if (categoryName === 'golbinRaid') {
else if (categoryName === 'golbinRaid') {
 
}
}
// Data must be pushed into the consoldiated data, rules for vary
// Data must be pushed into the consoldiated data, rules for vary
// depending on the category in question
// depending on the category in question
switch(categoryName) {
switch (categoryName) {
case 'realms':
case 'attackSpellbooks':
case 'damageTypes':
case 'equipmentSlots':
case 'combatAreaCategories':
case 'combatEffects':
case 'combatEffectGroups':
case 'combatEffectTables':
case 'combatEffectTemplates':
case 'combatTriangleSets':
case 'masterPoolBonuses':
case 'masteryLevelUnlocks':
case 'masteryLevelBonuses':
case 'masterPoolBonuses':
case 'ancientRelics':
case 'ancientRelics':
case 'ancientSpells':
case 'attackSpells':
case 'archaicSpells':
case 'attackStyles':
case 'attackStyles':
case 'attacks':
case 'attacks':
Line 403: Line 450:
case 'curseSpells':
case 'curseSpells':
case 'dungeons':
case 'dungeons':
case 'strongholds':
case 'abyssDepths':
case 'gamemodes':
case 'gamemodes':
case 'itemEffects':
case 'itemEffects':
Line 410: Line 459:
case 'items':
case 'items':
case 'lore':
case 'lore':
case 'modifiers':
case 'monsters':
case 'monsters':
case 'pages':
case 'pages':
Line 416: Line 466:
case 'randomGems':
case 'randomGems':
case 'randomSuperiorGems':
case 'randomSuperiorGems':
case 'randomAbyssalGems':
case 'randomFragments':
case 'randomFiremakingOils':
case 'shopCategories':
case 'shopCategories':
case 'shopPurchases':
case 'shopPurchases':
case 'shopUpgradeChains':
case 'shopUpgradeChains':
case 'skillLevelCapIncreases':
case 'slayerAreas':
case 'slayerAreas':
case 'stackingEffects':
case 'slayerTaskCategories':
case 'standardSpells':
case 'steamAchievements':
case 'steamAchievements':
case 'tutorialStages':
case 'tutorialStages':
Line 429: Line 482:
// Category doesn't exist yet in consolidated data, so create it
// Category doesn't exist yet in consolidated data, so create it
this.gameData[categoryName] = categoryData;
this.gameData[categoryName] = categoryData;
}
} else {
else {
this.gameData[categoryName].push(...categoryData);
this.gameData[categoryName].push(...categoryData);
}
}
break;
break;
case 'ancientRelicsDisplayOrder':
case 'bankSortOrder':
case 'bankSortOrder':
case 'combatAreaCategoryOrder':
case 'combatAreaDisplayOrder':
case 'combatAreaDisplayOrder':
case 'dungeonDisplayOrder':
case 'dungeonDisplayOrder':
case 'shopCategoryOrder':
case 'shopCategoryOrder':
case 'shopDisplayOrder':
case 'shopDisplayOrder':
case 'skillTreesDisplayOrder':
case 'slayerAreaDisplayOrder':
case 'slayerAreaDisplayOrder':
case 'tutorialStageOrder':
case 'tutorialStageOrder':
Line 450: Line 505:
this.gameData[categoryName] = categoryData;
this.gameData[categoryName] = categoryData;
this.gameData.golbinRaid.possibleModifiers = RaidManager.possibleModifiers;
this.gameData.golbinRaid.possibleModifiers = RaidManager.possibleModifiers;
}
} else {
else {
Object.keys(categoryData).forEach((dataKey) => {
Object.keys(categoryData).forEach((dataKey) => {
if ((this.gameData[categoryName][dataKey] === undefined) || !Array.isArray(this.gameData[categoryName][dataKey])) {
if (
this.gameData[categoryName][dataKey] === undefined ||
!Array.isArray(this.gameData[categoryName][dataKey])
) {
// Property is undefined or isn't an array
// Property is undefined or isn't an array
this.gameData[categoryName][dataKey] = categoryData[dataKey];
this.gameData[categoryName][dataKey] = categoryData[dataKey];
}  
} else {
else {
// Property is an array
// Property is an array
this.gameData[categoryName][dataKey].push(...categoryData[dataKey]);
this.gameData[categoryName][dataKey].push(...categoryData[dataKey]);
Line 482: Line 538:
Object.keys(skillData.data).forEach((dataKey) => {
Object.keys(skillData.data).forEach((dataKey) => {
// Special case for Township item conversions
// Special case for Township item conversions
if ((skillObj[dataKey] !== undefined) && (skillData.skillID === 'melvorD:Township') && (dataKey === 'itemConversions')) {
if (
skillObj[dataKey] !== undefined &&
skillData.skillID === 'melvorD:Township' &&
dataKey === 'itemConversions'
) {
Object.keys(skillData.data[dataKey]).forEach((convKey) => {
Object.keys(skillData.data[dataKey]).forEach((convKey) => {
skillData.data[dataKey][convKey].forEach((resource) => {
skillData.data[dataKey][convKey].forEach((resource) => {
// Find the resource if it already exists within the combined data
// Find the resource if it already exists within the combined data
const resourceIdx = skillObj[dataKey][convKey].findIndex((res) => res.resourceID === resource.resourceID);
const resourceIdx = skillObj[dataKey][convKey].findIndex(
(res) => res.resourceID === resource.resourceID
);
if (resourceIdx === -1) {
if (resourceIdx === -1) {
skillObj[dataKey][convKey].push(resource);
skillObj[dataKey][convKey].push(resource);
}
} else {
else {
skillObj[dataKey][convKey][resourceIdx].items.push(...resource.items);
skillObj[dataKey][convKey][resourceIdx].items.push(...resource.items);
}
}
})
});
});
});
}
} else if (
else if (Array.isArray(skillData.data[dataKey]) && skillData.data[dataKey].length > 0 && skillData.data[dataKey][0].insertAt !== undefined) {
Array.isArray(skillData.data[dataKey]) &&
skillData.data[dataKey].length > 0 &&
skillData.data[dataKey][0].insertAt !== undefined
) {
// Data is ordered, special handling applies
// Data is ordered, special handling applies
skillObj[dataKey] = this.combineOrderedData(skillObj[dataKey], skillData.data[dataKey]);
skillObj[dataKey] = this.combineOrderedData(skillObj[dataKey], skillData.data[dataKey]);
}
} else if (skillObj[dataKey] === undefined || !Array.isArray(skillObj[dataKey])) {
else if ((skillObj[dataKey] === undefined) || !Array.isArray(skillObj[dataKey])) {
// Property is undefined or isn't an array
// Property is undefined or isn't an array
skillObj[dataKey] = skillData.data[dataKey];
skillObj[dataKey] = skillData.data[dataKey];
}
} else {
else {
// Property is an array
// Property is an array
skillObj[dataKey].push(...skillData.data[dataKey]);
skillObj[dataKey].push(...skillData.data[dataKey]);
}
}
});
});
Line 512: Line 574:
break;
break;
default:
default:
console.warn(`Skipping unknown category while registering data package: ${ categoryName }`);
console.warn(`Skipping unknown category while registering data package: ${categoryName}`);
break;
break;
}
}
Line 527: Line 589:
}
}
applyDataModifications(modData) {
applyDataModifications(modData) {
const modDataKeys = Object.keys(modData)
// TODO: Handle modifications for the following:
// equipmentSlots - Currently tweaks passive slot requirements
// pages - Not so important, this is unused data
// damageTypes
// skillData - Adjusts Township season modifiers to include ItA resources
const modDataKeys = Object.keys(modData);
for (const modCatID in modDataKeys) {
for (const modCatID in modDataKeys) {
const modCat = modDataKeys[modCatID];
const modCat = modDataKeys[modCatID];
const catData = modData[modCat];
const catData = modData[modCat];
if ((modCat === 'shopUpgradeChains') || (modCat === 'shopPurchases')) {
if (modCat === 'combatAreaCategories') {
// The 'areas' property of elements within the category data are ordered data
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[modCat], modObjID);
if (modObj === undefined) {
console.warn(
`Could not apply data modification: Object with ID "${modObjID}" not found for category "${modCat}"`
);
} else {
modObj.areas = this.combineOrderedData(modObj.areas, modItem.areas.add);
}
}
});
} else if (modCat === 'shopUpgradeChains' || modCat === 'shopPurchases') {
// Modify the root upgrade ID of shop upgrade chains, and modify attributes of shop purchases
// 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;
if (modObjID === undefined) {
if (modObjID === undefined) {
console.warn(`Could not apply data modification: ID of object to be modified not found, category "${ modCat }"`);
console.warn(
}
`Could not apply data modification: ID of object to be modified not found, category "${modCat}"`
else {
);
} else {
const modObj = this.getObjectByID(this.gameData[modCat], modObjID);
const modObj = this.getObjectByID(this.gameData[modCat], modObjID);
if (modObj === undefined) {
if (modObj === undefined) {
console.warn(`Could not apply data modification: Object with ID "${ modObjID }" not found for category "${ modCat }"`);
console.warn(
}
`Could not apply data modification: Object with ID "${modObjID}" not found for category "${modCat}"`
else {
);
} else {
const overrideKeys = {
const overrideKeys = {
purchaseRequirements: {
purchaseRequirements: {
sourceKey: 'newRequirements', // Key that holds the data in the data package
sourceKey: 'newRequirements', // Key that holds the data in the data package
destKey: 'purchaseRequirementsOverrides', // Key to insert into within this.gameData
destKey: 'purchaseRequirementsOverrides', // Key to insert into within this.gameData
subKey: 'requirements' // Sub-key containing the override data
subKey: 'requirements', // Sub-key containing the override data
},
},
cost: {
cost: {
sourceKey: 'newCosts',
sourceKey: 'newCosts',
destKey: 'costOverrides',
destKey: 'costOverrides',
subKey: 'cost'
subKey: 'cost',
}
},
};
};
Object.keys(modItem).filter((k) => k !== 'id').forEach((k) => {
Object.keys(modItem)
const overrideKey = overrideKeys[k];
.filter((k) => k !== 'id')
if (overrideKey !== undefined) {
.forEach((k) => {
// Is an override specific to a gamemode, do not replace
const overrideKey = overrideKeys[k];
// the key's existing data
if (overrideKey !== undefined) {
const destKey = overrideKey.destKey;
// Is an override specific to a gamemode, do not replace
if (modObj[destKey] === undefined) {
// the key's existing data
modObj[destKey] = [];
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];
}
}
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];
}
});
}
}
}
}
});
});
}
} else if (modCat === 'cookingCategories') {
else if (modCat === 'cookingCategories') {
// Append to the list of shop upgrade IDs for cooking utilities/categories
// Append to the list of shop upgrade IDs for cooking utilities/categories
catData.forEach((modItem) => {
catData.forEach((modItem) => {
Line 586: Line 674:
const cookingSkill = this.getObjectByID(this.gameData.skillData, 'melvorD:Cooking', 'skillID');
const cookingSkill = this.getObjectByID(this.gameData.skillData, 'melvorD:Cooking', 'skillID');
if (modObjID === undefined) {
if (modObjID === undefined) {
console.warn(`Could not apply data modification: ID of object to be modified not found, category "${ modCat }"`);
console.warn(
}
`Could not apply data modification: ID of object to be modified not found, category "${modCat}"`
else if (cookingSkill === undefined) {
);
} else if (cookingSkill === undefined) {
console.warn('Could not apply data modification: Data for skill "melvorD:Cooking" not found');
console.warn('Could not apply data modification: Data for skill "melvorD:Cooking" not found');
}
} else {
else {
const modObj = this.getObjectByID(cookingSkill.data.categories, modObjID);
const modObj = this.getObjectByID(cookingSkill.data.categories, modObjID);
if (modObj === undefined) {
if (modObj === undefined) {
console.warn(`Could not apply data modification: Object with ID "${ modObjID }" not found for category "${ modCat }"`);
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) => {
} else {
if (k === 'shopUpgradeIDs') {
Object.keys(modItem)
if (modObj[k] === undefined) {
.filter((k) => k !== 'id')
modObj[k] = modItem[k];
.forEach((k) => {
if (k === 'shopUpgradeIDs') {
if (modObj[k] === undefined) {
modObj[k] = modItem[k];
} else {
modObj[k].push(...modItem[k]);
}
} else {
console.warn(
`Could not apply data modification: Unhandled key "${k}" for category "${modCat}", object "${mobObjID}"`
);
}
}
else {
});
modObj[k].push(...modItem[k]);
}
}
else {
console.warn(`Could not apply data modification: Unhandled key "${ k }" for category "${ modCat }", object "${ mobObjID }"`);
}
});
}
}
}
}
});
});
}
} else if (modCat === 'fletchingRecipes') {
else if (modCat === 'fletchingRecipes') {
// Append to alternativeCosts property of recipes (e.g. Arrow shafts)
// Append to alternativeCosts property of recipes (e.g. Arrow shafts)
catData.forEach((modItem) => {
catData.forEach((modItem) => {
Line 620: Line 710:
const fletchingSkill = this.getObjectByID(this.gameData.skillData, 'melvorD:Fletching', 'skillID');
const fletchingSkill = this.getObjectByID(this.gameData.skillData, 'melvorD:Fletching', 'skillID');
if (modObjID === undefined) {
if (modObjID === undefined) {
console.warn(`Could not apply data modification: ID of object to be modified not found, category "${ modCat }"`);
console.warn(
}
`Could not apply data modification: ID of object to be modified not found, category "${modCat}"`
else if (fletchingSkill === undefined) {
);
} else if (fletchingSkill === undefined) {
console.warn('Could not apply data modification: Data for skill "melvorD:Fletching" not found');
console.warn('Could not apply data modification: Data for skill "melvorD:Fletching" not found');
}
} else {
else {
const modObj = this.getObjectByID(fletchingSkill.data.recipes, modObjID);
const modObj = this.getObjectByID(fletchingSkill.data.recipes, modObjID);
if (modObj === undefined) {
if (modObj === undefined) {
console.warn(`Could not apply data modification: Object with ID "${ modObjID }" not found for category "${ modCat }"`);
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) => {
} else {
if (k === 'alternativeCosts') {
Object.keys(modItem)
if (modObj[k] === undefined) {
.filter((k) => k !== 'id')
modObj[k] = modItem[k];
.forEach((k) => {
if (k === 'alternativeCosts') {
if (modObj[k] === undefined) {
modObj[k] = modItem[k];
} else {
modObj[k].push(...modItem[k]);
}
} else {
console.warn(
`Could not apply data modification: Unhandled key "${k}" for category "${modCat}", object "${mobObjID}"`
);
}
}
else {
});
modObj[k].push(...modItem[k]);
}
}
else {
console.warn(`Could not apply data modification: Unhandled key "${ k }" for category "${ modCat }", object "${ mobObjID }"`);
}
});
}
}
}
}
});
});
}
} else if (modCat === 'dungeons') {
else if (modCat === 'dungeons') {
catData.forEach((modItem) => {
catData.forEach((modItem) => {
const modObjID = modItem.id;
const modObjID = modItem.id;
if (modObjID === undefined) {
if (modObjID === undefined) {
console.warn(`Could not apply data modification: ID of object to be modified not found, category "${ modCat }"`);
console.warn(
}
`Could not apply data modification: ID of object to be modified not found, category "${modCat}"`
else {
);
} else {
const modObj = this.getObjectByID(this.gameData.dungeons, modObjID);
const modObj = this.getObjectByID(this.gameData.dungeons, modObjID);
if (modObj === undefined) {
if (modObj === undefined) {
console.warn(`Could not apply data modification: Object with ID "${ modObjID }" not found for category "${ modCat }"`);
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}"`
);
}
});
}
}
else {
}
Object.keys(modItem).filter((k) => k !== 'id').forEach((k) => {
});
if (k === 'gamemodeRewardItemIDs') {
} else if (modCat === 'modifiers') {
// Add gamemode specific item rewards to dungeon data
catData.forEach((modItem) => {
const itemRules = modItem[k];
const modObjID = modItem.id;
Object.keys(itemRules).forEach((ruleKey) => {
if (modObjID === undefined) {
if (ruleKey === 'add') {
console.warn(
if (modObj[k] === undefined) {
`Could not apply data modification: ID of object to be modified not found, category "${modCat}"`);
modObj[k] = [];
} else {
// Find modifier definition
const modParentObj = this.getObjectByID(this.gameData.modifiers, modObjID);
if ((modParentObj === undefined) || (modParentObj.allowedScopes === undefined)) {
console.warn(`Could not apply data modification: Modifier with ID ${modObjID} not found or modifier has no scopes`);
} else {
modItem.allowedScopes.forEach((srcScope) => {
// Find scope within modifier modParentObj with matching scopes definition
const srcScopeKeys = Object.keys(srcScope.scopes);
modParentObj.allowedScopes.forEach((destScope) => {
const destScopeKeys = Object.keys(destScope.scopes);
const scopeMatch = (
srcScopeKeys.length === destScopeKeys.length
&& srcScopeKeys.every((k) => destScope.scopes[k] !== undefined && srcScope.scopes[k] == destScope.scopes[k])
);
if (scopeMatch) {
// Scopes match - add aliases to modifier allowedScope definition
const aliasKeys = ['posAliases', 'negAliases'];
aliasKeys.forEach((aliasKey) => {
if (srcScope[aliasKey] !== undefined) {
if (destScope[aliasKey] === undefined) {
destScope[aliasKey] = [];
}
destScope[aliasKey].push(...srcScope[aliasKey]);
}
}
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 }"`);
}
});
});
}
}
}
}
});
});
}
} else {
else {
console.warn(`Could not apply data modification: Unhandled category "${modCat}"`);
console.warn(`Could not apply data modification: Unhandled category "${ modCat }"`);
}
}
}
}
Line 713: Line 846:
game.registeredNamespaces.forEach((ns) => {
game.registeredNamespaces.forEach((ns) => {
if (ns.isModded) {
if (ns.isModded) {
throw new Error(`Modded namespace '${ ns.displayName }' found, all mods must be disabled before game data can be generated`);
throw new Error(
}
`Modded namespace '${ns.displayName}' found, all mods must be disabled before game data can be generated`
else {
);
} else {
nsData.push(ns);
nsData.push(ns);
}
}
Line 721: Line 855:
this.gameData.namespaces = nsData;
this.gameData.namespaces = nsData;
}
}
if (this.gameData.currencies === undefined) {
this.gameData.currencies = game.currencies.allObjects.map((c) => ({
id: c.id,
name: c.name,
type: c.type
}));
}
// Melvor realm exists outside of data packages
if (this.gameData.realms === undefined) {
this.gameData.realms = game.realms
.filter((r) => r.id === 'melvorD:Melvor')
.map((r) => ({
id: r.id,
name: r.name,
unlockRequirements: r.unlockRequirements
}));
}
// Normal damage type exisst outside of data packages
if (this.gameData.damageTypes === undefined) {
this.gameData.damageTypes =  game.damageTypes
.filter((d) => d.id === 'melvorD:Normal')
.map((d) => ({
id: d.id,
name: d.name,
resistanceCap: d._resistanceCap,
resistanceName: d.resistanceName
}));
}
/**
if (this.gameData.combatTriangles === undefined) {
if (this.gameData.combatTriangles === undefined) {
const ctData = [];
const ctData = [];
Line 730: Line 893:
this.gameData.combatTriangles = ctData;
this.gameData.combatTriangles = ctData;
}
}
*/
/**
if (this.gameData.masteryCheckpoints === undefined) {
if (this.gameData.masteryCheckpoints === undefined) {
this.gameData.masteryCheckpoints = masteryCheckpoints;
this.gameData.masteryCheckpoints = masteryCheckpoints;
}
}
*/
if (this.gameData.combatAreaDifficulties === undefined) {
if (this.gameData.combatAreaDifficulties === undefined) {
this.gameData.combatAreaDifficulties = CombatAreaMenuElement.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: this.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) {
this.gameData.attackTypes = AttackTypeID;
this.gameData.attackTypes = AttackTypeID;
}  
}
/**
if (this.gameData.slayerTiers === undefined) {
if (this.gameData.slayerTiers === undefined) {
const newData = structuredClone(SlayerTask.data)
const newData = structuredClone(SlayerTask.data);
newData.forEach((tier) => delete tier.engDisplay);
newData.forEach((tier) => delete tier.engDisplay);
this.gameData.slayerTiers = newData;
this.gameData.slayerTiers = newData;
}
}
*/
/**
if (this.gameData.modifierData === undefined && modifierData !== undefined) {
if (this.gameData.modifierData === undefined && modifierData !== undefined) {
var wikiModData = {};
var wikiModData = {};
Line 762: Line 936:
}
}
wikiModData[modK][k] = funcName;
wikiModData[modK][k] = funcName;
}
} else if (k === 'langDescription') {
else if (k === 'langDescription') {
wikiModData[modK]['description'] = mod[k];
wikiModData[modK]['description'] = mod[k];
}
} else if (k !== 'description') {
else if (k !== 'description') {
wikiModData[modK][k] = mod[k];
wikiModData[modK][k] = mod[k];
}
}
Line 773: Line 945:
this.gameData.modifierData = wikiModData;
this.gameData.modifierData = wikiModData;
}
}
*/
}
}
combineOrderedData(existingData, newData) {
combineOrderedData(existingData, newData) {
Line 780: Line 953:
if (existingData === undefined) {
if (existingData === undefined) {
resultData = [];
resultData = [];
}
} else {
else {
resultData = structuredClone(existingData);
resultData = structuredClone(existingData);
}
}
newData.forEach((orderData) => {
newData.forEach((orderData) => {
switch(orderData.insertAt) {
switch (orderData.insertAt) {
case 'Start':
case 'Start':
resultData.splice(0, 0, ...orderData.ids);
resultData.splice(0, 0, ...orderData.ids);
Line 795: Line 967:
const beforeIdx = resultData.findIndex((item) => item === orderData.beforeID);
const beforeIdx = resultData.findIndex((item) => item === orderData.beforeID);
if (beforeIdx === -1) {
if (beforeIdx === -1) {
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(beforeIdx, 0, ...orderData.ids);
resultData.splice(beforeIdx, 0, ...orderData.ids);
Line 802: Line 974:
const afterIdx = resultData.findIndex((item) => item === orderData.afterID);
const afterIdx = resultData.findIndex((item) => item === orderData.afterID);
if (afterIdx === -1) {
if (afterIdx === -1) {
throw new Error(`Couldn't insert after: Item ${ orderData.afterID } is not in the array.`);
throw new Error(`Couldn't insert after: Item ${orderData.afterID} is not in the array.`);
}
}
resultData.splice(afterIdx + 1, 0, ...orderData.ids);
resultData.splice(afterIdx + 1, 0, ...orderData.ids);
Line 814: Line 986:
// Returns true if the property is to be removed, false if it is to be retained
// Returns true if the property is to be removed, false if it is to be retained
isPropertyFiltered(entityType, entity, propertyName) {
isPropertyFiltered(entityType, entity, propertyName) {
switch(propertyName) {
switch (propertyName) {
case 'media':
case 'media':
case 'altMedia':
case 'altMedia':
Line 841: Line 1,013:
if (entityType === 'items') {
if (entityType === 'items') {
return entity.tier === 'none';
return entity.tier === 'none';
}
} else {
else {
return false;
return false;
}
}
Line 853: Line 1,024:
// Returns undefined if the property has no transformation
// Returns undefined if the property has no transformation
transformProperty(entityType, entity, propertyName, namespace) {
transformProperty(entityType, entity, propertyName, namespace) {
switch(propertyName) {
switch (propertyName) {
case 'langHint':
case 'langHint':
case 'langCustomDescription':
case 'langCustomDescription':
Line 862: Line 1,033:
if (newStats[stat.key] === undefined) {
if (newStats[stat.key] === undefined) {
newStats[stat.key] = stat.value;
newStats[stat.key] = stat.value;
}
} else {
else {
newStats[stat.key] += stat.value;
newStats[stat.key] += stat.value;
}
}
Line 871: Line 1,041:
if (entityType !== 'skillData') {
if (entityType !== 'skillData') {
return undefined;
return undefined;
}
} else {
else {
const newData = structuredClone(entity);
const newData = structuredClone(entity);
newData.forEach((i) => {
newData.forEach((i) => {
Line 884: Line 1,053:
}
}
langApply(parentNode, nodeKey, isSkill) {
langApply(parentNode, nodeKey, isSkill) {
const nodeName = (isSkill ? parentNode[nodeKey].skillID : nodeKey);
const nodeName = isSkill ? parentNode[nodeKey].skillID : nodeKey;
const altMagicDescIDKey = function(data) {
const altMagicDescIDKey = function (data) {
// Accepts an Alt. Magic spell object, returns the ID format for that spell
// Accepts an Alt. Magic spell object, returns the ID format for that spell
// Using a function for this as some spells (e.g. Superheat) have bespoke logic
// Using a function for this as some spells (e.g. Superheat) have bespoke logic
Line 892: Line 1,061:
return 'HOLY_INVOCATION';
return 'HOLY_INVOCATION';
}
}
switch(data.specialCost.type) {
switch (data.specialCost.type) {
case 'BarIngredientsWithCoal':
case 'BarIngredientsWithCoal':
return 'SUPERHEAT';
return 'SUPERHEAT';
Line 911: Line 1,080:
chainName: 'chainNameLang',
chainName: 'chainNameLang',
defaultDescription: 'descriptionLang',
defaultDescription: 'descriptionLang',
defaultName: 'defaultNameLang'
defaultName: 'defaultNameLang',
};
};
const langPropName = propToLang[dataKey];
const langPropName = propToLang[dataKey];
Line 920: Line 1,089:
}
}
}
}
}
};
const itemDesc = (data) => {
const itemDesc = (data) => {
// 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
// from modifiers, so just get the description from in-game objects instead.
let desc;
const item = game.items.getObjectByID(data.id);
const item = game.items.getObjectByID(data.id);
if (item !== undefined) {
if (item !== undefined && item.hasDescription) {
desc = item.description;
return item.description;
if (desc === this.getLangString('BANK_STRING', '38')) {
} else return '';
// Generic "no description" string
};
return undefined;
const shopPurchaseDesc = (data) => {
}
const purchase = game.shop.purchases.getObjectByID(data.id);
// Temporary fix for issue with language data keys for FrostSpark 1H Sword
if (purchase !== undefined) {
else if (desc.includes('UNDEFINED TRANSLATION') && data.id === 'melvorTotH:FrostSpark_1H_Sword') {
return purchase.description;
return this.getLangString('ITEM_DESCRIPTION', 'Frostspark_1H_Sword')
} else return '';
}
};
else {
return desc;
}
}
}
const relicDesc = (data) => {
const relicDesc = (data) => {
const relic = game.ancientRelics.getObjectByID(data.id);
const relic = game.ancientRelics.getObjectByID(data.id);
Line 947: Line 1,107:
return relic.name;
return relic.name;
}
}
}
};
const passiveDesc = (data) => {
const passiveDesc = (data) => {
const passive = game.combatPassives.getObjectByID(data.id);
const passive = game.combatPassives.getObjectByID(data.id);
Line 953: Line 1,113:
return passive.description;
return passive.description;
}
}
}
};
const spAttDesc = (data) => {
const spAttDesc = (data) => {
const spAtt = game.specialAttacks.getObjectByID(data.id);
const spAtt = game.specialAttacks.getObjectByID(data.id);
Line 959: Line 1,119:
return spAtt.description;
return spAtt.description;
}
}
}
};
const tsWorshipName = (data) => {
const tsWorshipName = (data) => {
const worship = game.township.worships.getObjectByID(data.id);
const worship = game.township.worships.getObjectByID(data.id);
Line 965: Line 1,125:
return worship.name;
return worship.name;
}
}
}
};
const tsWorshipStatueName = (data) => {
const tsWorshipStatueName = (data) => {
const worship = game.township.worships.getObjectByID(data.id);
const worship = game.township.worships.getObjectByID(data.id);
Line 971: Line 1,131:
return worship.statueName;
return worship.statueName;
}
}
}
};
const attackSpellbooksName = (data) => {
const book = game.attackSpellbooks.getObjectByID(data.id);
if (book !== undefined) {
return book.name;
}
};
const attackSpellName = (data) => {
const spell = game.attackSpells.getObjectByID(data.id);
if (spell !== undefined) {
return spell.name;
}
};
const hasNoLangData = [
const hasNoLangData = [
// Categories that contain no localized text. Supresses warnings about no lang data
// Categories that contain no localized text. Supresses warnings about no lang data
'ancientRelicsDisplayOrder',
'bankSortOrder',
'bankSortOrder',
'combatAreaDisplayOrder',
'combatAreaDisplayOrder',
'combatAreaCategoryOrder',
'combatEffectTemplates',
'combatEvents',
'combatEvents',
'dungeonDisplayOrder',
'dungeonDisplayOrder',
Line 983: Line 1,158:
'itemUpgrades',
'itemUpgrades',
'itmMonsters',
'itmMonsters',
'modifiers', // TODO Does have lang data, supressing warning for now
'randomAbyssalGems',
'randomFiremakingOils',
'randomFragments',
'randomGems',
'randomGems',
'randomSuperiorGems',
'randomSuperiorGems',
'slayerAreaDisplayOrder',
'slayerAreaDisplayOrder',
'slayerTaskCategories', // TODO Does have lang data, supressing warning for now
'shopCategoryOrder',
'shopCategoryOrder',
'shopDisplayOrder',
'shopDisplayOrder',
'skillLevelCapIncreases',
'skillTreesDisplayOrder',
'spiderLairMonsters',
'spiderLairMonsters',
'stackingEffects'
'stackingEffects',
];
];
const langKeys = {
const langKeys = {
ancientRelics: {
realms: {
name: { stringSpecial: 'relicDesc' }
name: { key: 'REALM', idFormat: 'NAME_{ID}' },
},
damageTypes: {
name: { idFormat: 'DAMAGE_TYPE_{ID}' },
},
combatTriangleSets: {
name: { key: 'COMBAT_TRIANGLE_NAME', idFormat: 'NAME_{ID}' },
},
attackSpellbooks: {
name: { stringSpecial: 'attackSpellbooksName' },
},
},
ancientSpells: {
attackSpells: {
name: { key: 'MAGIC', idFormat: 'ANCIENT_NAME_{ID}' }
name: { stringSpecial: 'attackSpellName' },
},
},
archaicSpells: {
ancientRelics: {
name: { key: 'MAGIC', idFormat: 'ARCHAIC_NAME_{ID}' }
name: { stringSpecial: 'relicDesc' },
},
},
attackStyles: {
attackStyles: {
name: { key: 'COMBAT_MISC', idFormat: 'ATTACK_STYLE_NAME_{ID}' }
name: { key: 'COMBAT_MISC', idFormat: 'ATTACK_STYLE_NAME_{ID}' },
},
},
attacks: {
attacks: {
name: { key: 'SPECIAL_ATTACK_NAME' },
name: { key: 'SPECIAL_ATTACK_NAME' },
description: { stringSpecial: 'spAttDesc' }
description: { stringSpecial: 'spAttDesc' },
},
},
auroraSpells: {
auroraSpells: {
name: { key: 'MAGIC', idFormat: 'AURORA_NAME_{ID}' }
name: { key: 'MAGIC', idFormat: 'AURORA_NAME_{ID}' },
},
combatAreaCategories: {
name: { key: 'COMBAT_AREA_CATEGORY' }
},
},
combatAreas: {
combatAreas: {
name: { key: 'COMBAT_AREA', idFormat: 'NAME_{ID}'}
name: { key: 'COMBAT_AREA', idFormat: 'NAME_{ID}' },
},
combatEffectGroups: {
name: { idKey: 'nameLang' }
},
combatEffects: {
name: { idKey: 'nameLang' }
},
},
combatPassives: {
combatPassives: {
name: { key: 'PASSIVES', idFormat: 'NAME_{ID}' },
name: { key: 'PASSIVES', idFormat: 'NAME_{ID}' },
customDescription: { stringSpecial: 'passiveDesc' }
customDescription: { stringSpecial: 'passiveDesc' },
//customDescription: { key: 'PASSIVES', idFormat: 'DESC_{ID}' }
//customDescription: { key: 'PASSIVES', idFormat: 'DESC_{ID}' }
},
},
curseSpells: {
curseSpells: {
name: { key: 'MAGIC', idFormat: 'CURSE_NAME_{ID}' }
name: { key: 'MAGIC', idFormat: 'CURSE_NAME_{ID}' },
},
},
dungeons: {
dungeons: {
name: { key: 'DUNGEON', idFormat: 'NAME_{ID}' }
name: { key: 'DUNGEON', idFormat: 'NAME_{ID}' },
},
abyssDepths: {
name: { key: 'THE_ABYSS', idFormat: 'NAME_{ID}' },
},
strongholds: {
name: { key: 'STRONGHOLD_NAME', idFormat: 'NAME_{ID}' },
},
},
gamemodes: {
gamemodes: {
Line 1,029: Line 1,235:
description: { key: 'GAMEMODES', idFormat: 'GAMEMODE_DESC_{ID}' },
description: { key: 'GAMEMODES', idFormat: 'GAMEMODE_DESC_{ID}' },
// Gamemodes have an array of rules
// Gamemodes have an array of rules
rules: { key: 'GAMEMODES', idFormat: 'GAMEMODE_RULES_{ID}_{NUM}' }
rules: { key: 'GAMEMODES', idFormat: 'GAMEMODE_RULES_{ID}_{NUM}' },
},
},
items: {
items: {
name: { key: 'ITEM_NAME' },
name: { key: 'ITEM_NAME' },
customDescription: { stringSpecial: 'itemDesc', onlyIfExists: true }
customDescription: { stringSpecial: 'itemDesc', onlyIfExists: false },
},
},
lore: {
lore: {
title: { key: 'LORE', idFormat: 'TITLE_{ID}' }
title: { key: 'LORE', idFormat: 'TITLE_{ID}' },
},
},
monsters: {
monsters: {
name: { key: 'MONSTER_NAME' },
name: { key: 'MONSTER_NAME' },
description: { key: 'MONSTER_DESCRIPTION' }
description: { key: 'MONSTER_DESCRIPTION' },
},
},
pets: {
pets: {
name: { key: 'PET_NAME' }
name: { key: 'PET_NAME' },
hint: { idKey: 'langHint' }
},
},
prayers: {
prayers: {
name: { key: 'PRAYER', idFormat: 'PRAYER_NAME_{ID}' }
name: { key: 'PRAYER', idFormat: 'PRAYER_NAME_{ID}' },
},
},
shopCategories: {
shopCategories: {
name: { key: 'SHOP_CAT' }
name: { key: 'SHOP_CAT' },
},
},
shopPurchases: {
shopPurchases: {
customName: { key: 'SHOP_NAME', onlyIfExists: true },
customName: { key: 'SHOP_NAME', onlyIfExists: true },
customDescription: { key: 'SHOP_DESCRIPTION', onlyIfExists: true }
customDescription: { stringSpecial: 'shopPurchaseDesc', onlyIfExists: false },
},
},
shopUpgradeChains: {
shopUpgradeChains: {
chainName: { keySpecial: 'shopChainKey', idSpecial: 'shopChainID' },
chainName: { keySpecial: 'shopChainKey', idSpecial: 'shopChainID' },
defaultDescription: { keySpecial: 'shopChainKey', idSpecial: 'shopChainID' },
defaultDescription: { keySpecial: 'shopChainKey', idSpecial: 'shopChainID' },
defaultName: { keySpecial: 'shopChainKey', idSpecial: 'shopChainID' }
defaultName: { keySpecial: 'shopChainKey', idSpecial: 'shopChainID' },
 
},
},
slayerAreas: {
slayerAreas: {
name: { key: 'SLAYER_AREA', idFormat: 'NAME_{ID}' },
name: { key: 'SLAYER_AREA', idFormat: 'NAME_{ID}' },
areaEffectDescription: { key: 'SLAYER_AREA', idFormat: 'EFFECT_{ID}' }
areaEffectDescription: { key: 'SLAYER_AREA', idFormat: 'EFFECT_{ID}' },
},
standardSpells: {
name: { key: 'MAGIC', idFormat: 'SPELL_NAME_{ID}' }
},
},
skillData: {
skillData: {
Line 1,075: Line 1,278:
// for all skills
// for all skills
_root: {
_root: {
name: { key: 'SKILL_NAME', idFormat: '{SKILLID}' }
name: { key: 'SKILL_NAME', idFormat: '{SKILLID}' },
},
categories: {
name: { key: 'SKILL_CATEGORY', idFormat: '{SKILLID}_{ID}' }
},
},
customMilestones: {
customMilestones: {
name: { key: 'MILESTONES', idKey: 'milestoneID' }
name: { key: 'MILESTONES', idKey: 'milestoneID' },
},
},
masteryLevelUnlocks: {
masteryLevelUnlocks: {
description: { key: 'MASTERY_BONUS', idKey: 'descriptionID', idFormat: '{SKILLID}_{ID}' }
description: { key: 'MASTERY_BONUS', idKey: 'descriptionID', idFormat: '{SKILLID}_{ID}' },
}
},
},
},
Archaeology: {
Archaeology: {
digSites: {
digSites: {
name: { key: 'POI_NAME_Melvor' }
name: { key: 'POI_NAME_Melvor' },
}
},
// TODO Tool names
// TODO Tool names
},
},
Agility: {
Agility: {
elitePillars: {
elitePillars: {
name: { key: 'AGILITY', idFormat: 'PILLAR_NAME_{ID}' }
name: { key: 'AGILITY', idFormat: 'PILLAR_NAME_{ID}' },
},
},
obstacles: {
obstacles: {
name: { key: 'AGILITY', idFormat: 'OBSTACLE_NAME_{ID}' }
name: { key: 'AGILITY', idFormat: 'OBSTACLE_NAME_{ID}' },
},
},
pillars: {
pillars: {
name: { key: 'AGILITY', idFormat: 'PILLAR_NAME_{ID}' }
name: { key: 'AGILITY', idFormat: 'PILLAR_NAME_{ID}' },
}
},
},
},
Astrology: {
Astrology: {
recipes: {
recipes: {
name: { key: 'ASTROLOGY', idFormat: 'NAME_{ID}' }
name: { key: 'ASTROLOGY', idFormat: 'NAME_{ID}' },
}
},
},
},
Cartography: {
Cartography: {
mapPortals: { _handler: 'mapPortals' },
mapPortals: { _handler: 'mapPortals' },
travelEvents: {
travelEvents: {
description: { key: 'TRAVEL_EVENT' }
description: { key: 'TRAVEL_EVENT' },
},
},
worldMaps: { _handler: 'cartoMaps' }
worldMaps: { _handler: 'cartoMaps' },
//name: { key: 'WORLD_MAP_NAME' },
//name: { key: 'WORLD_MAP_NAME' },
//pointsOfInterest: { _handler: 'mapPOI' }
//pointsOfInterest: { _handler: 'mapPOI' }
//name: { key: 'POI_NAME', idFormat: '{MAPID}_{ID}' },
//name: { key: 'POI_NAME', idFormat: '{MAPID}_{ID}' },
//description: { key: 'POI_DESCRIPTION', idFormat: '{MAPID}_{ID}' }
//description: { key: 'POI_DESCRIPTION', idFormat: '{MAPID}_{ID}' }
},
Cooking: {
categories: {
name: { idFormat: 'SKILL_CATEGORY_{SKILLID}_{ID}'}
}
},
Crafting: {
categories: {
name: { idFormat: 'SKILL_CATEGORY_{SKILLID}_{ID}'}
}
},
},
Farming: {
Farming: {
categories: {
categories: {
description: { key: 'SKILL_CATEGORY', idFormat: '{SKILLID}_{ID}_description' },
description: { key: 'SKILL_CATEGORY', idFormat: '{SKILLID}_{ID}_description' },
name: { idFormat: 'SKILL_CATEGORY_{SKILLID}_{ID}'},
seedNotice: { key: 'SKILL_CATEGORY', idFormat: '{SKILLID}_{ID}_seedNotice' },
seedNotice: { key: 'SKILL_CATEGORY', idFormat: '{SKILLID}_{ID}_seedNotice' },
singularName: { key: 'SKILL_CATEGORY', idFormat: '{SKILLID}_{ID}_singular' }
singularName: { key: 'SKILL_CATEGORY', idFormat: '{SKILLID}_{ID}_singular' },
},
},
Fletching: {
categories: {
name: { idFormat: 'SKILL_CATEGORY_{SKILLID}_{ID}'}
}
}
},
},
Fishing: {
Fishing: {
areas: {
areas: {
name: { key: 'FISHING', idFormat: 'AREA_NAME_{ID}' }
name: { key: 'FISHING', idFormat: 'AREA_NAME_{ID}' },
}
},
},
},
Herblore: {
Herblore: {
categories: {
name: { idFormat: 'SKILL_CATEGORY_{SKILLID}_{ID}'}
},
recipes: {
recipes: {
name: { key: 'POTION_NAME' }
name: { key: 'POTION_NAME' },
}
},
},
},
Magic: {
Magic: {
altSpells: {
altSpells: {
name: { key: 'MAGIC', idFormat: 'ALTMAGIC_NAME_{ID}' },
name: { key: 'MAGIC', idFormat: 'ALTMAGIC_NAME_{ID}' },
description: { key: 'MAGIC', idSpecial: 'altMagicDesc' }
description: { key: 'MAGIC', idSpecial: 'altMagicDesc' },
}
},
},
},
Mining: {
Mining: {
categories: {
name: { idFormat: 'MINING_TYPE_{ID}' }
},
rockData: {
rockData: {
name: { key: 'ORE_NAME' }
name: { key: 'ORE_NAME' },
},
},
Runecrafting: {
categories: {
name: { idFormat: 'SKILL_CATEGORY_{SKILLID}_{ID}'}
}
}
},
},
Summoning: {
Summoning: {
categories: {
name: { idFormat: 'SKILL_CATEGORY_{SKILLID}_{ID}'}
},
synergies: {
synergies: {
customDescription: { key: 'SUMMONING_SYNERGY', idKey: 'summonIDs', idFormat: 'DESC_{ID0}_{ID1}', onlyIfExists: true }
customDescription: {
}
key: 'SUMMONING_SYNERGY',
idKey: 'summonIDs',
idFormat: 'DESC_{ID0}_{ID1}',
onlyIfExists: true,
},
},
},
},
Thieving: {
Thieving: {
areas: {
areas: {
name: { key: 'THIEVING', idFormat: 'AREA_NAME_{ID}' }
name: { key: 'THIEVING', idFormat: 'AREA_NAME_{ID}' },
},
},
npcs: {
npcs: {
name: { key: 'THIEVING', idFormat: 'NPC_NAME_{ID}' }
name: { key: 'THIEVING', idFormat: 'NPC_NAME_{ID}' },
}
},
},
},
Township: {
Township: {
biomes: {
biomes: {
// Can't locate biome description localization, don't think this is exposed in game UI
// Can't locate biome description localization, don't think this is exposed in game UI
name: { key: 'TOWNSHIP', idFormat: 'BIOME_{ID}' }
name: { key: 'TOWNSHIP', idFormat: 'BIOME_{ID}' },
},
},
buildings: {
buildings: {
// Building description has no localization, as it is unused
// Building description has no localization, as it is unused
name: { key: 'TOWNSHIP', idFormat: 'BUILDING_{ID}' }
name: { key: 'TOWNSHIP', idFormat: 'BUILDING_{ID}' },
},
},
jobs: {
jobs: {
name: { key: 'TOWNSHIP', idFormat: 'JOB_{ID}' }
name: { key: 'TOWNSHIP', idFormat: 'JOB_{ID}' },
},
},
resources: {
resources: {
name: { key: 'TOWNSHIP', idFormat: 'RESOURCE_{ID}' }
name: { key: 'TOWNSHIP', idFormat: 'RESOURCE_{ID}' },
},
},
tasks: {
tasks: {
// name is not exposed in game UI, and has no localization
// name is not exposed in game UI, and has no localization
// category is localized in transformDataNode
// category is localized in transformDataNode
description: { key: 'TOWNSHIP_TASKS', idFormat: '{ID}_description' }
description: { key: 'TOWNSHIP_TASKS', idFormat: '{ID}_description' },
},
},
worships: {
worships: {
name: { stringSpecial: 'tsWorshipName' },
name: { stringSpecial: 'tsWorshipName' },
statueName: { stringSpecial: 'tsWorshipStatueName' }
statueName: { stringSpecial: 'tsWorshipStatueName' },
}
},
},
},
Woodcutting: {
Woodcutting: {
trees: {
trees: {
name: { key: 'TREE_NAME' }
name: { key: 'TREE_NAME' },
}
},
}
},
}
},
};
};


Line 1,216: Line 1,451:
}
}
langKeyData = langSkill;
langKeyData = langSkill;
}
} else if (langKeys[nodeKey] !== undefined) {
else if (langKeys[nodeKey] !== undefined) {
langKeyData = { _root: langKeys[nodeKey] };
langKeyData = { _root: langKeys[nodeKey] };
}
} else if (!hasNoLangData.includes(nodeKey)) {
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,230: Line 1,463:
}
}
if (!Array.isArray(dataToTranslate)) {
if (!Array.isArray(dataToTranslate)) {
dataToTranslate = [ dataToTranslate ];
dataToTranslate = [dataToTranslate];
}
}
dataToTranslate.forEach((tData) => {
dataToTranslate.forEach((tData) => {
Object.keys(langKeyData).forEach((langKey) => {
Object.keys(langKeyData).forEach((langKey) => {
const targetData = ((langKey === '_root') ? tData : tData[langKey]);
const targetData = langKey === '_root' ? tData : tData[langKey];
if (targetData !== undefined) {
if (targetData !== undefined) {
const targetArr = (Array.isArray(targetData) ? targetData : [ targetData ]);
const targetArr = Array.isArray(targetData) ? targetData : [targetData];
targetArr.forEach((target) => {
targetArr.forEach((target) => {
const handlerFunc = langKeyData[langKey]['_handler'];
const handlerFunc = langKeyData[langKey]['_handler'];
if (handlerFunc !== undefined) {
if (handlerFunc !== undefined) {
switch(handlerFunc) {
switch (handlerFunc) {
case 'mapPortals':
case 'mapPortals':
Object.keys(target).forEach((portalKey) => {
Object.keys(target).forEach((portalKey) => {
Line 1,261: Line 1,494:
break;
break;
}
}
}
} else {
else {
Object.keys(langKeyData[langKey]).forEach((langPropID) => {
Object.keys(langKeyData[langKey]).forEach((langPropID) => {
const langProp = langKeyData[langKey][langPropID];
const langProp = langKeyData[langKey][langPropID];
Line 1,271: Line 1,503:
// The ID key can sometimes be an array of IDs (e.g. Summoning synergies)
// The ID key can sometimes be an array of IDs (e.g. Summoning synergies)
langIDValue = target[langIDKey].map((id) => this.getLocalID((id ?? '').toString()));
langIDValue = target[langIDKey].map((id) => this.getLocalID((id ?? '').toString()));
}
} else {
else {
langIDValue = this.getLocalID((target[langIDKey] ?? '').toString());
langIDValue = this.getLocalID((target[langIDKey] ?? '').toString());
}
}
Line 1,278: Line 1,509:
if (langProp.idSpecial !== undefined) {
if (langProp.idSpecial !== undefined) {
// Use a special method to determine the ID format
// Use a special method to determine the ID format
switch(langProp.idSpecial) {
switch (langProp.idSpecial) {
case 'altMagicDesc':
case 'altMagicDesc':
langIdent = altMagicDescIDKey(target);
langIdent = altMagicDescIDKey(target);
Line 1,289: Line 1,520:
if (langIdent === undefined) {
if (langIdent === undefined) {
langIdent = langIDValue;
langIdent = langIDValue;
}
} else {
else {
// langIdent is in a specific format
// langIdent is in a specific format
const langTemplate = {}
const langTemplate = {};
if (isSkill) {
if (isSkill) {
langTemplate.SKILLID = this.getLocalID(parentNode[nodeKey].skillID);
langTemplate.SKILLID = this.getLocalID(parentNode[nodeKey].skillID);
Line 1,300: Line 1,530:
langTemplate['ID' + idx] = this.getLocalID(val);
langTemplate['ID' + idx] = this.getLocalID(val);
});
});
}
} else {
else {
langTemplate.ID = langIDValue;
langTemplate.ID = langIDValue;
}
}
Line 1,312: Line 1,541:
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');
Line 1,326: Line 1,555:
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 ' +
else {
langPropID +
console.debug('No translation: property ' + langPropID + ' for ' + langIdentFinal + ' in node ' + nodeName);
'[' +
num.toString() +
'] for ' +
langIdentFinal +
' in node ' +
nodeName +
' to: ' +
langString
);
} else {
console.debug(
'No translation: property ' +
langPropID +
' for ' +
langIdentFinal +
' in node ' +
nodeName
);
}
}
}
}
});
});
}
} else {
else {
let langString;
let langString;
if (langProp.stringSpecial !== undefined) {
if (langProp.stringSpecial !== undefined) {
// Use a custom function to determine the string
// Use a custom function to determine the string
switch(langProp.stringSpecial) {
switch (langProp.stringSpecial) {
case 'itemDesc':
case 'itemDesc':
langString = itemDesc(target);
langString = itemDesc(target);
break;
case 'shopPurchaseDesc':
langString = shopPurchaseDesc(target);
break;
break;
case 'passiveDesc':
case 'passiveDesc':
Line 1,356: Line 1,604:
case 'tsWorshipStatueName':
case 'tsWorshipStatueName':
langString = tsWorshipStatueName(target);
langString = tsWorshipStatueName(target);
break;
case 'attackSpellbooksName':
langString = attackSpellbooksName(target);
break;
case 'attackSpellName':
langString = attackSpellName(target);
break;
break;
}
}
}
} else {
else {
langString = this.getLangString(langCategoryKey, langIdent);
langString = this.getLangString(langCategoryKey, langIdent);
}
}
Line 1,365: Line 1,618:
if (this.debugMode) {
if (this.debugMode) {
if (langString !== undefined) {
if (langString !== undefined) {
console.debug('Set value of property ' + langPropID + ' for ' + langIdent + ' in node ' + nodeName + ' to: ' + langString);
console.debug(
}
'Set value of property ' +
else {
langPropID +
console.debug('No translation: property ' + langPropID + ' for ' + langIdent + ' in node ' + nodeName);
' for ' +
langIdent +
' in node ' +
nodeName +
' to: ' +
langString
);
} else {
console.debug(
'No translation: property ' + langPropID + ' for ' + langIdent + ' in node ' + nodeName
);
}
}
}
}
Line 1,382: Line 1,645:
}
}
getLangString(key, identifier) {
getLangString(key, identifier) {
if (identifier === undefined) {
let lookupVal = '';
return loadedLangJson[key];
if (key !== undefined) {
lookupVal = key;
}
}
else {
if (identifier !== undefined) {
return loadedLangJson[key + '_' + identifier];
lookupVal += (lookupVal.length > 0 ? '_' : '') + identifier;
}
}
return loadedLangJson[lookupVal];
}
}
getNamespacedID(namespace, ID) {
getNamespacedID(namespace, ID) {
if (ID.indexOf(':') > 0) {
if (ID.indexOf(':') > 0) {
return ID;
return ID;
}
} else {
else {
return namespace + ':' + ID;
return namespace + ':' + ID;
}
}
}
}
getLocalID(ID) {
getLocalID(ID) {
if (ID.indexOf(':') > 0) {
if (ID !== undefined && ID.indexOf(':') > 0) {
return ID.split(':').pop();
return ID.split(':').pop();
}
} else {
else {
return ID;
return ID;
}
}
Line 1,407: Line 1,670:
}
}


let wd = new Wiki;
let wd = new Wiki();
wd.printWikiData();</syntaxhighlight>}}
wd.printWikiData();</syntaxhighlight>}}