12,790
edits
(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 + " | melvorD: { displayName: "Demo", url: "https://" + location.hostname + this.baseDir + "melvorDemo.json" }, | ||
melvorF: { displayName: "Full Version", url: "https://" + location.hostname + " | melvorF: { displayName: "Full Version", url: "https://" + location.hostname + this.baseDir + "melvorFull.json" }, | ||
melvorTotH: { displayName: "Throne of the Herald", url: "https://" + location.hostname + "/ | 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 ( | 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(); | ||
} | } | ||
console.log('Printing data for game version ' + this.getGameVersion()); | |||
this.printPages.forEach((page) => { | this.printPages.forEach((page) => { | ||
const inclCat = this.getCategoriesForPage(page); | |||
let gameDataFiltered = {}; | let gameDataFiltered = {}; | ||
inclCat.forEach((cat) => gameDataFiltered[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 | ||
let dataText; | |||
if (this.prettyPrint) { | if (this.prettyPrint) { | ||
dataText = JSON.stringify(gameDataFiltered, undefined, '\t'); | |||
} | } | ||
else { | else { | ||
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) { | |||
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: 'BASE_STORAGE', to: 'baseStorage'}, | ||
{ | {from: 'BASE_TAX_RATE', to: 'baseTaxRate'}, | ||
{ | {from: 'DECREASED_BUILDING_COST_CAP', to: 'decreasedBuildingCostCap' }, | ||
{ | {from: 'GP_PER_CITIZEN', to: 'gpPerCitizen'}, | ||
{ | {from: 'MAX_WORSHIP', to: 'maxWorship'}, | ||
{ | {from: 'MINIMUM_HEALTH', to: 'minimumHealth'}, | ||
{ | {from: 'populationForTier', to: 'populationForTier'}, | ||
{ | {from: 'TICK_LENGTH', to: 'tickLength'}, | ||
{from: 'RARE_SEASON_CHANCE', to: 'rareSeasonChance'}, | |||
{from: 'WORSHIP_CHANGE_COST', to: 'worshipChangeCost'}, | |||
{from: 'WORSHIP_CHECKPOINTS', to: 'worshipCheckpoints'}, | |||
{ | |||
{ | |||
{ | |||
]; | ]; | ||
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 = | 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( | 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 = | 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 = | 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 = | 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 = | 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: { | name: { stringSpecial: 'tsWorshipName' }, | ||
statueName: { | 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']; | ||
if (handlerFunc !== undefined) { | |||
switch(handlerFunc) { | |||
case 'mapPortals': | |||
Object.keys(target).forEach((portalKey) => { | |||
let portalData = target[portalKey]; | |||
const langID = this.getLocalID(portalData.originWorldMap) + '_' + this.getLocalID(portalData.id); | |||
portalData.name = this.getLangString('POI_NAME', langID); | |||
portalData.description = this.getLangString('POI_DESCRIPTION', langID); | |||
}); | |||
break; | |||
case 'cartoMaps': | |||
// Target represents a world map | |||
const mapID = this.getLocalID(target.id); | |||
target.name = this.getLangString('WORLD_MAP_NAME', mapID); | |||
// Process POIs | |||
target.pointsOfInterest.forEach((poi) => { | |||
const langID = mapID + '_' + this.getLocalID(poi.id); | |||
poi.name = this.getLangString('POI_NAME', langID); | |||
poi.description = this.getLangString('POI_DESCRIPTION', langID); | |||
}); | |||
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 (langProp.idSpecial !== undefined) { | |||
// Use a special method to determine the ID format | |||
switch(langProp.idSpecial) { | |||
case 'altMagicDesc': | |||
langIdent = altMagicDescIDKey(target); | |||
break; | |||
case 'shopChainID': | |||
langIdent = this.getLocalID(shopChainPropKey(target, langPropID, 'id')); | |||
break; | |||
} | |||
} | } | ||
if ( | if (langIdent === undefined) { | ||
langIDValue | langIdent = langIDValue; | ||
} | } | ||
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]); | |||
}); | |||
} | } | ||
let langCategoryKey = langProp.key; | |||
if (langProp.keySpecial !== undefined) { | |||
// Use a special method to determine the category key | |||
switch(langProp.keySpecial) { | |||
case 'shopChainKey': | |||
langCategoryKey = shopChainPropKey(target, langPropID, 'category'); | |||
break; | |||
} | |||
} | } | ||
if (Array.isArray(target[langPropID])) { | |||
target[langPropID].forEach((targetElem, num) => { | |||
const langIdentFinal = langIdent.replaceAll('{NUM}', num.toString()); | |||
const langString = this.getLangString(langCategoryKey, langIdentFinal); | |||
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 + ' | console.debug('Set value of property ' + langPropID + ' for ' + langIdent + ' in node ' + nodeName + ' to: ' + langString); | ||
} | } | ||
else { | else { | ||
console.debug('No translation: property ' + langPropID + ' for ' + | console.debug('No translation: property ' + langPropID + ' for ' + langIdent + ' in node ' + nodeName); | ||
} | } | ||
} | } | ||
} | } | ||
} | } | ||
} | }); | ||
} | } | ||
}); | }); | ||
} | } | ||
Line 1,149: | Line 1,382: | ||
} | } | ||
getLangString(key, identifier) { | getLangString(key, identifier) { | ||
if (identifier === undefined) { | |||
return loadedLangJson[key]; | |||
return | } | ||
else { | |||
return loadedLangJson[key + '_' + identifier]; | |||
} | } | ||
} | } |