17,481
edits
(Add data version) |
(Support all remaining data modifications & dependent data) |
||
(6 intermediate revisions by the same user not shown) | |||
Line 15: | Line 15: | ||
this.debugMode = false; | this.debugMode = false; | ||
this.prettyPrint = false; | this.prettyPrint = false; | ||
this. | this.customLocalizations = { | ||
// Contains custom localization strings, to override any game provided localizations. | |||
// To be used sparingly, for instances where 2+ objects of the same type | |||
// (e.g. monsters) have the same name, as this isn't convenient to deal with in Lua | |||
// TotH curse also named 'Madness' | |||
MAGIC_ABYSSAL_NAME_Madness: 'Madness (ItA)', | |||
// Stronghold boss monsters, where names overlap with normal monster variants | |||
MONSTER_NAME_FierceDevilBoss: 'Fierce Devil (Stronghold)', | |||
MONSTER_NAME_ElementalistBoss: 'Elementalist (Stronghold)', | |||
MONSTER_NAME_PratTheGuardianOfSecretsBoss: 'Prat, the Guardian of Secrets (Stronghold)', | |||
MONSTER_NAME_MysteriousFigurePhase1Stronghold: 'Mysterious Figure - Phase 1 (Stronghold)', | |||
MONSTER_NAME_MysteriousFigurePhase2Stronghold: 'Mysterious Figure - Phase 2 (Stronghold)', | |||
MONSTER_NAME_AhreniaStronghold: 'Ahrenia (Stronghold)' | |||
}; | |||
this.namespaces = { | this.namespaces = { | ||
melvorD: { displayName: 'Demo', | melvorD: { | ||
melvorF: { displayName: 'Full Version', | displayName: 'Demo', | ||
packFile: 'melvorDemo.json', | |||
}, | |||
melvorF: { | |||
displayName: 'Full Version', | |||
packFile: 'melvorFull.json', | |||
}, | |||
melvorTotH: { | melvorTotH: { | ||
displayName: 'Throne of the Herald', | displayName: 'Throne of the Herald', | ||
packFile: 'melvorTotH.json', | |||
}, | }, | ||
melvorAoD: { | melvorAoD: { | ||
displayName: 'Atlas of Discovery', | displayName: 'Atlas of Discovery', | ||
packFile: 'melvorExpansion2.json', | |||
}, | }, | ||
melvorBirthday2023: { | melvorBirthday2023: { | ||
displayName: 'Melvor Birthday 2023', | displayName: 'Melvor Birthday 2023', | ||
packFile: 'melvorBirthday2023.json', | |||
}, | }, | ||
melvorItA: { | melvorItA: { | ||
displayName: 'Into the Abyss', | displayName: 'Into the Abyss', | ||
packFile: 'melvorItA.json', | |||
}, | }, | ||
}; | }; | ||
this.registeredNamespaces = []; | |||
// List of categories to be excluded from the generated game data. | |||
// These serve no purpose for the wiki and so would otherwise bloat the data | |||
this.excludedCategories = [ | |||
'pages', | |||
'steamAchievements', | |||
'tutorialStageOrder', | |||
'tutorialStages' | |||
]; | |||
// 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 | ||
Object.keys(this.namespaces).forEach((nsID) => { | Object.keys(this.namespaces).forEach((nsID) => { | ||
Line 85: | Line 115: | ||
this.gameData = {}; | this.gameData = {}; | ||
this.skillDataInit = {}; | this.skillDataInit = {}; | ||
} | |||
getDataPackURL(nsID) { | |||
return 'https://' + location.hostname + '/assets/data/' + this.namespaces[nsID].packFile + '?' + DATA_VERSION.toString(); | |||
} | } | ||
async getWikiData() { | async getWikiData() { | ||
Line 92: | Line 125: | ||
for (const nsIdx in Object.keys(this.namespaces)) { | for (const nsIdx in Object.keys(this.namespaces)) { | ||
const ns = Object.keys(this.namespaces)[nsIdx]; | const ns = Object.keys(this.namespaces)[nsIdx]; | ||
const dataURL = this. | const dataURL = this.getDataPackURL(ns); | ||
console.log(`URL: ${dataURL}`); | console.log(`URL: ${dataURL}`); | ||
const dataPackage = await this.getDataPackage(dataURL); | const dataPackage = await this.getDataPackage(dataURL); | ||
Line 108: | Line 141: | ||
console.log(`After transformation: ${JSON.stringify(dataPackage.data).length.toLocaleString()} bytes`); | console.log(`After transformation: ${JSON.stringify(dataPackage.data).length.toLocaleString()} bytes`); | ||
} | } | ||
// Process dependent data after all packages processed | |||
console.log('Processing dependent data for all packages...'); | |||
this.processDependentData(); | |||
// All data packages should now be within this.gameData | // All data packages should now be within this.gameData | ||
} | } | ||
Line 241: | Line 277: | ||
const packData = dataPackage.data; | const packData = dataPackage.data; | ||
Object.keys(packData). | Object.keys(packData) | ||
.filter((categoryName) => !this.excludedCategories.includes(categoryName)) | |||
.forEach((categoryName) => { | |||
this.transformDataNode(ns, categoryName, packData, categoryName); | |||
}); | |||
} | } | ||
transformDataNode(ns, categoryName, parentNode, nodeKey) { | transformDataNode(ns, categoryName, parentNode, nodeKey) { | ||
Line 344: | Line 370: | ||
case 'melvorD:Summoning': | case 'melvorD:Summoning': | ||
importKeys = ['baseInterval']; | importKeys = ['baseInterval']; | ||
const sumKeys = ['recipeGPCost', 'markLevels']; | const sumKeys = ['recipeAPCost', 'recipeGPCost', 'markLevels']; | ||
sumKeys.forEach((k) => (dataNode.data[k] = Summoning[k])); | sumKeys.forEach((k) => (dataNode.data[k] = Summoning[k])); | ||
break; | break; | ||
Line 394: | Line 420: | ||
} | } | ||
} | } | ||
registerPackData(packData) { | |||
Object.keys(packData) | |||
.filter((categoryName) => !this.excludedCategories.includes(categoryName)) | |||
.forEach((categoryName) => { | |||
let categoryData = packData[categoryName]; | |||
// Some data is adjusted before combining - do this here | |||
if (['combatAreas', 'dungeons', 'slayerAreas', 'abyssDepths', 'strongholds'].includes(categoryName)) { | |||
// Add area type to each area object | |||
const areaTypes = { | |||
let categoryData = packData[categoryName]; | |||
// Some data is adjusted before combining - do this here | |||
if (['combatAreas', 'dungeons', 'slayerAreas', 'abyssDepths'].includes(categoryName)) { | |||
// Add area type to each area object | |||
const areaTypes = { | |||
combatAreas: 'combatArea', | combatAreas: 'combatArea', | ||
dungeons: 'dungeon', | dungeons: 'dungeon', | ||
slayerAreas: 'slayerArea', | slayerAreas: 'slayerArea', | ||
strongholds: 'stronghold', | |||
abyssDepths: 'abyssDepth', | abyssDepths: 'abyssDepth', | ||
}; | }; | ||
Line 419: | Line 439: | ||
newData.forEach((x) => (x.type = areaType)); | newData.forEach((x) => (x.type = areaType)); | ||
categoryData = newData; | categoryData = newData; | ||
} | } | ||
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 | ||
Line 585: | Line 597: | ||
} | } | ||
}); | }); | ||
} | |||
registerDataPackage(namespace) { | |||
// Consolidates the data package identified by namespace with existing data within | |||
// this.gameData | |||
const packData = this.packData[namespace].data; | |||
if (packData === undefined) { | |||
throw new Error(`Couldn't find data for package ${namespace}`); | |||
} | |||
// Add data within the game but outside of data packs | |||
this.registerNonPackData(); | |||
// Consolidate data | |||
this.registerPackData(packData); | |||
// If the data package contains modifications, apply these also | // If the data package contains modifications, apply these also | ||
const modificationData = this.packData[namespace].modifications; | const modificationData = this.packData[namespace].modifications; | ||
Line 590: | Line 614: | ||
this.applyDataModifications(modificationData); | this.applyDataModifications(modificationData); | ||
} | } | ||
// Dependent data is handled later, once all packages have been registered | |||
if ( | |||
if (!this.registeredNamespaces.includes(namespace)) { | |||
this.registeredNamespaces.push(namespace); | |||
} | } | ||
} | } | ||
processDependentData() { | |||
Object.entries(this.packData) | |||
.forEach(([namespace, packData]) => { | |||
if (packData.dependentData !== undefined) { | |||
packData.dependentData.forEach((depDataForNS) => { | |||
const depNS = depDataForNS.namespace; | |||
if (!this.registeredNamespaces.includes(depNS)) { | |||
console.warn( | console.warn( | ||
`Could not apply data | `Could not apply dependent data from package ${namespace}: Data depends on namespace ${depNS}, which has not been registered` | ||
); | ); | ||
} | } | ||
else { | |||
console.log(`Attempting to apply dependent data for ${depNS} from package ${namespace}`); | |||
if (depDataForNS.data !== undefined) { | |||
this.registerPackData(depDataForNS.data) | |||
} | |||
if (depDataForNS.modifications !== undefined) { | |||
this.applyDataModifications(depDataForNS.modifications); | |||
} | |||
console. | |||
. | |||
} | } | ||
} | }); | ||
}); | } | ||
}); | |||
} | |||
getDataToModify(modCat) { | |||
switch (modCat) { | |||
case 'combatAreaCategories': | |||
case 'damageTypes': | |||
case 'dungeons': | |||
case 'equipmentSlots': | |||
case 'gamemodes': | |||
case 'items': | |||
case 'modifiers': | |||
case 'pets': | |||
case 'shopUpgradeChains': | |||
case 'shopPurchases': | |||
case 'skillData': | |||
case 'slayerAreas': | |||
return this.gameData[modCat]; | |||
case 'cookingCategories': | |||
const cookingSkill = this.getObjectByID(this.gameData.skillData, 'melvorD:Cooking', 'skillID'); | |||
return cookingSkill.data.categories; | |||
case 'fletchingRecipes': | |||
const fletchingSkill = this.getObjectByID(this.gameData.skillData, 'melvorD:Fletching', 'skillID'); | |||
return fletchingSkill.data.recipes; | |||
} | |||
return undefined; | |||
} | |||
applyModifierModifications(objToModify, adjustments) { | |||
if (objToModify.modifiers === undefined) { | |||
objToModify.modifiers = {}; | |||
} | |||
Object.keys(adjustments) | |||
.forEach((adjType) => { | |||
if (adjType === 'add') { | |||
Object.entries(adjustments[adjType]) | |||
.forEach(([chgKey, chgVal]) => { | |||
if (objToModify.modifiers[chgKey] === undefined) { | |||
objToModify.modifiers[chgKey] = chgVal; | |||
} | |||
else if (Array.isArray(chgVal)) { | |||
objToModify.modifiers[chgKey].push(...chgVal); | |||
} | |||
else { | |||
objToModify.modifiers[chgKey] += chgVal; | |||
} | |||
}); | |||
} | |||
else { | |||
console.warn( | |||
`Could not apply data modification: Unhandled modifier adjustment "${adjType}"` | |||
); | |||
} | |||
} | |||
); | |||
} | |||
applyAddRemoveModifications(objToModify, adjustments, modifyKey) { | |||
if (adjustments.remove !== undefined && Array.isArray(objToModify[modifyKey])) { | |||
// adjustments.remove is an array of requirement types to be removed | |||
let i = 0; | |||
while (i < objToModify[modifyKey].length) { | |||
if (adjustments.remove.includes(objToModify[modifyKey][i].type)) { | |||
objToModify[modifyKey].splice(i, 1); | |||
} | |||
else { | |||
i++; | |||
} | |||
} | |||
} | |||
if (adjustments.add !== undefined) { | |||
if (objToModify[modifyKey] === undefined) { | |||
objToModify[modifyKey] = adjustments.add; | |||
} | |||
else { | |||
objToModify[modifyKey].push(...adjustments.add); | |||
} | |||
} | |||
} | |||
applyGamemodeSpecificModifications(objToModify, adjustments, newProperty) { | |||
const gamemodeID = adjustments.gamemodeID; | |||
if (objToModify.gamemodeOverrides === undefined) { | |||
objToModify.gamemodeOverrides = []; | |||
} | |||
let gamemodeEntryToModify = this.getObjectByID(objToModify.gamemodeOverrides, gamemodeID, 'gamemodeID'); | |||
if (gamemodeEntryToModify === undefined) { | |||
// Initialize gamemode overrides | |||
objToModify.gamemodeOverrides.push({ | |||
gamemodeID: gamemodeID | |||
}); | |||
gamemodeEntryToModify = this.getObjectByID(objToModify.gamemodeOverrides, gamemodeID, 'gamemodeID'); | |||
} | |||
if (gamemodeEntryToModify[newProperty] === undefined) { | |||
gamemodeEntryToModify[newProperty] = structuredClone(objToModify[newProperty]) ?? {}; | |||
} | |||
this.applyAddRemoveModifications(gamemodeEntryToModify, adjustments, newProperty); | |||
} | |||
applyDataModifications(modData) { | |||
const modDataKeys = Object.keys(modData).filter((modCatID) => !this.excludedCategories.includes(modCatID)); | |||
for (const modCatID in modDataKeys) { | |||
const modCat = modDataKeys[modCatID]; | |||
const catData = modData[modCat]; | |||
const dataToModify = this.getDataToModify(modCat); | |||
const modObjIDKey = (modCat === 'skillData' ? 'skillID' : 'id'); | |||
if (dataToModify === undefined) { | |||
console.warn( | |||
`Could not apply data modification for category "${modCat}": Unable to retrieve category data to be modified` | |||
); | |||
} | |||
else { | |||
catData.forEach((modItem) => { | catData.forEach((modItem) => { | ||
const modObjID = modItem | const modObjID = modItem[modObjIDKey]; | ||
if (modObjID === undefined) { | if (modObjID === undefined) { | ||
console.warn( | console.warn( | ||
`Could not apply data modification: ID of object to be modified not found | `Could not apply data modification for category "${modCat}": ID of object to be modified not found` | ||
); | ); | ||
} else { | } else { | ||
const | const objToModify = this.getObjectByID(dataToModify, modObjID, modObjIDKey); | ||
if ( | if (objToModify === undefined) { | ||
console.warn( | console.warn( | ||
`Could not apply data modification: Object with ID "${modObjID}" not found for | `Could not apply data modification: Object with ID "${modObjID}" not found for ctaegory "${modCat}"` | ||
); | ); | ||
} else { | } | ||
Object. | else { | ||
switch (modCat) { | |||
case 'combatAreaCategories': | |||
// The 'areas' property of elements within the category data are ordered data | |||
objToModify.areas = this.combineOrderedData(objToModify.areas, modItem.areas.add); | |||
break; | |||
case 'damageTypes': | |||
if ( | Object.entries(modItem) | ||
.filter(([k, v]) => k !== 'id') | |||
.forEach(([k, v]) => { | |||
if (typeof v === 'object' && (v.add !== undefined || v.remove !== undefined)) { | |||
this.applyAddRemoveModifications(objToModify, v, k); | |||
} | |||
else { | |||
console.warn( | console.warn( | ||
`Could not apply data modification: | `Could not apply data modification: Unhandled key "${k}" for category "${modCat}", object "${modObjID}"` | ||
); | ); | ||
} | } | ||
}); | }); | ||
break; | |||
case 'gamemodes': | |||
Object.entries(modItem) | |||
.filter(([k, v]) => k !== 'id') | |||
.forEach(([k, v]) => { | |||
if (typeof v === 'object' && (v.add !== undefined || v.remove !== undefined)) { | |||
this.applyAddRemoveModifications(objToModify, v, k); | |||
} | |||
else if (['abyssalLevelCapCost', 'post99RollConversion'].includes(k)) { | |||
objToModify[k] = v; | |||
} | |||
else { | |||
console.warn( | |||
`Could not apply data modification: Unhandled key "${k}" for category "${modCat}", object "${modObjID}"` | |||
); | |||
} | |||
}); | |||
break; | |||
case 'shopPurchases': | |||
case 'shopUpgradeChains': | |||
// Modify the root upgrade ID of shop upgrade chains, and modify attributes of shop purchases | |||
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) => { | |||
const overrideKey = overrideKeys[k]; | |||
if (overrideKey !== undefined) { | |||
// Is an override specific to a gamemode, do not replace | |||
// the key's existing data | |||
if ( | const destKey = overrideKey.destKey; | ||
if ( | if (objToModify[destKey] === undefined) { | ||
objToModify[destKey] = []; | |||
} | } | ||
modItem[k].forEach((gamemodeOverride) => { | |||
var newData = {}; | |||
newData.gamemodeID = gamemodeOverride.gamemodeID; | |||
newData[overrideKey.subKey] = gamemodeOverride[overrideKey.sourceKey]; | |||
objToModify[destKey].push(newData); | |||
}); | |||
} else { | |||
objToModify[k] = modItem[k]; | |||
} | } | ||
}); | }); | ||
break; | |||
case 'cookingCategories': | |||
// Append to the list of shop upgrade IDs for cooking utilities/categories | |||
case 'fletchingRecipes': | |||
// Append to alternativeCosts property of recipes (e.g. Arrow shafts) | |||
Object.keys(modItem) | |||
.filter((k) => k !== 'id') | |||
.forEach((k) => { | |||
if ((k === 'shopUpgradeIDs') || (k === 'alternativeCosts')) { | |||
if (objToModify[k] === undefined) { | |||
objToModify[k] = modItem[k]; | |||
} else { | |||
objToModify[k].push(...modItem[k]); | |||
} | |||
} else { | |||
console.warn( | |||
`Could not apply data modification: Unhandled key "${k}" for category "${modCat}", object "${modObjID}"` | |||
); | |||
} | |||
}); | |||
break; | |||
case 'skillData': | |||
Object.entries(modItem.data) | |||
.forEach(([skillProp, propModData]) => { | |||
propModData.forEach((subModItem) => { | |||
const subObjToModify = this.getObjectByID(objToModify.data[skillProp], subModItem.id); | |||
if (subObjToModify === undefined) { | |||
console.warn(`Couldn't find skill object with ID ${subModItem.id} to modify. Property ${skillProp} in skill ID ${objToModify.skillID}`); | |||
} | |||
else { | |||
Object.entries(subModItem) | |||
.forEach(([subProp, subData]) => { | |||
if (subProp === 'modifiers') { | |||
this.applyModifierModifications(subObjToModify, subData); | |||
} | |||
else if (subProp !== 'id') { | |||
this.applyAddRemoveModifications(subObjToModify, subData, subProp); | |||
} | |||
}); | |||
} | |||
}); | |||
}); | |||
break; | |||
case 'dungeons': | |||
// Add gamemode specific data to dungeons | |||
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') { | |||
itemRules[ruleKey].forEach((itemDef) => { | |||
const modToApply = { | |||
gamemodeID: itemDef.gamemodeID, | |||
add: itemDef.rewardItemIDs | |||
} | |||
this.applyGamemodeSpecificModifications(objToModify, modToApply, '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 | |||
this.applyGamemodeSpecificModifications(objToModify, modItem[k], 'entryRequirements'); | |||
} else { | |||
console.warn( | |||
`Could not apply data modification: Unhandled key "${k}" for category "${modCat}", object "${modObjID}"` | |||
); | |||
} | |||
}); | |||
break; | |||
case 'modifiers': | |||
// Add modifier aliases to existing mod scopes | |||
if (objToModify.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 objToModify with matching scopes definition | |||
const srcScopeKeys = Object.keys(srcScope.scopes); | |||
objToModify.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]); | |||
} | |||
}); | |||
} | |||
}); | |||
}); | |||
} | |||
break; | |||
case 'items': | |||
Object.keys(modItem) | |||
.filter((k) => k !== 'id') | |||
.forEach((k) => { | |||
if (k === 'modifiers') { | |||
this.applyModifierModifications(objToModify, modItem[k]); | |||
} | |||
else if (k === 'consumesOn') { | |||
Object.keys(modItem[k]) | |||
.forEach((adjType) => { | |||
if (adjType === 'add') { | |||
if (objToModify[k] === undefined) { | |||
objToModify[k] = modItem[k][adjType]; | |||
} | |||
else { | |||
objToModify[k].push(...modItem[k][adjType]); | |||
} | |||
} | |||
else { | |||
console.warn( | |||
`Could not apply data modification: Unhandled adjustment type "${adjType}" for category "${modCat}", object "${modObjID}, property ${k}"` | |||
); | |||
} | |||
}); | |||
} | |||
else { | |||
console.warn( | |||
`Could not apply data modification: Unhandled key "${k}" for category "${modCat}", object "${modObjID}"` | |||
); | |||
} | |||
} | |||
); | |||
break; | |||
case 'pets': | |||
Object.keys(modItem) | |||
.filter((k) => k !== 'id') | |||
.forEach((k) => { | |||
if (k === 'modifiers') { | |||
this.applyModifierModifications(objToModify, modItem[k]); | |||
} | |||
else { | |||
console.warn( | |||
`Could not apply data modification: Unhandled key "${k}" for category "${modCat}", object "${modObjID}"` | |||
); | |||
} | |||
} | |||
); | |||
break; | |||
case 'equipmentSlots': | |||
Object.keys(modItem) | |||
.filter((k) => k !== 'id') | |||
.forEach((k) => { | |||
if (k === 'requirements') { | |||
this.applyAddRemoveModifications(objToModify, modItem[k], 'requirements'); | |||
} | |||
else { | |||
console.warn( | |||
`Could not apply data modification: Unhandled key "${k}" for category "${modCat}", object "${modObjID}"` | |||
); | |||
} | |||
} | |||
); | |||
break; | |||
case 'slayerAreas': | |||
Object.keys(modItem) | |||
.filter((k) => k !== 'id') | |||
.forEach((k) => { | |||
if (k === 'gamemodeEntryRequirements') { | |||
this.applyGamemodeSpecificModifications(objToModify, modItem[k], 'entryRequirements'); | |||
} | |||
else { | |||
console.warn( | |||
`Could not apply data modification: Unhandled key "${k}" for category "${modCat}", object "${modObjID}"` | |||
); | |||
} | |||
} | |||
); | |||
break; | |||
default: | |||
console.warn( | |||
`Could not apply data modification: Unhandled category "${modCat}"` | |||
); | |||
} | |||
} | |||
} | |||
}); | |||
} | |||
} | } | ||
// | } | ||
if (this.gameData. | registerNonPackData() { | ||
this.gameData. | // Some data resides outside of packages. Add any such data to this.gameData within this function | ||
. | // Metadata for data/file version | ||
if (this.gameData._dataVersion === undefined) { | |||
this.gameData._dataVersion = ({ | |||
gameVersion: this.getGameVersion().substring(1), | |||
fileVersion: this.getGameFileVersion().substring(1) | |||
}); | }); | ||
} | } | ||
// Namespaces | |||
if (this.gameData.namespaces === undefined) { | |||
if (this.gameData. | const nsData = []; | ||
game.registeredNamespaces.forEach((ns) => { | |||
if (ns.isModded) { | |||
throw new Error( | |||
`Modded namespace '${ns.displayName}' found, all mods must be disabled before game data can be generated` | |||
); | |||
} else { | |||
nsData.push(ns); | |||
} | |||
}); | |||
this.gameData. | this.gameData.namespaces = nsData; | ||
} | } | ||
if (this.gameData.currencies === undefined) { | |||
if (this.gameData. | this.gameData.currencies = game.currencies.allObjects.map((c) => ({ | ||
this.gameData. | id: c.id, | ||
name: c.name, | |||
type: c.type | |||
})); | |||
} | } | ||
/ | // Melvor realm exists outside of data packages | ||
if (this.gameData. | 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 exists outside of data packages | |||
if (this.gameData.damageTypes === undefined) { | |||
if (this.gameData. | 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.combatAreaDifficulties === undefined) { | |||
this.gameData.combatAreaDifficulties = CombatAreaMenuElement.difficulty.map((i) => i.name); | |||
} | |||
if (this.gameData.attackTypes === undefined) { | |||
this.gameData.attackTypes = AttackTypeID; | |||
this.gameData. | |||
} | } | ||
} | } | ||
combineOrderedData(existingData, newData) { | combineOrderedData(existingData, newData) { | ||
Line 1,046: | Line 1,172: | ||
const newStats = {}; | const newStats = {}; | ||
entity.forEach((stat) => { | entity.forEach((stat) => { | ||
if (newStats[ | let statKey = stat.key; | ||
newStats[ | if (stat.damageType !== undefined) { | ||
statKey += this.getLocalID(stat.damageType); | |||
} | |||
if (newStats[statKey] === undefined) { | |||
newStats[statKey] = stat.value; | |||
} else { | } else { | ||
newStats[ | newStats[statKey] += stat.value; | ||
} | } | ||
}); | }); | ||
Line 1,114: | Line 1,244: | ||
const purchase = game.shop.purchases.getObjectByID(data.id); | const purchase = game.shop.purchases.getObjectByID(data.id); | ||
if (purchase !== undefined) { | if (purchase !== undefined) { | ||
return purchase.description; | // Logic taken from description method of ShopPurchase class & slightly modified | ||
// to avoid retrieving an item's modified description, which can include HTML | |||
let desc = ''; | |||
if (purchase._customDescription !== undefined) { | |||
if (purchase.isModded) { | |||
return purchase._customDescription; | |||
} | |||
else { | |||
return getLangString(`SHOP_DESCRIPTION_${ purchase.localID }`); | |||
} | |||
} | |||
if (purchase.contains.itemCharges !== undefined) { | |||
return purchase.contains.itemCharges.item.description; | |||
} | |||
if (purchase.contains.items.length === 1) { | |||
return purchase.contains.items[0].item.description; // Was modifiedDescription | |||
} | |||
if (purchase.contains.pet !== undefined) { | |||
return purchase.contains.pet.description; | |||
} | |||
if (purchase.contains.stats !== undefined) { | |||
desc = purchase.contains.stats.describePlain(); | |||
} | |||
if (purchase.hasDisabledModifier) { | |||
desc += getLangString('MENU_TEXT_CONTAINS_DISABLED_MODIFIER'); | |||
} | |||
return desc; | |||
} else return ''; | } else return ''; | ||
}; | }; | ||
Line 1,156: | Line 1,312: | ||
const spell = game.attackSpells.getObjectByID(data.id); | const spell = game.attackSpells.getObjectByID(data.id); | ||
if (spell !== undefined) { | if (spell !== undefined) { | ||
return spell. | return this.getLangString(`${ spell.spellbook.spellNameLangPrefix }${ spell.localID }`); | ||
} | } | ||
}; | }; | ||
Line 1,165: | Line 1,321: | ||
'combatAreaDisplayOrder', | 'combatAreaDisplayOrder', | ||
'combatAreaCategoryOrder', | 'combatAreaCategoryOrder', | ||
'combatEffectTables', | |||
'combatEffectTemplates', | 'combatEffectTemplates', | ||
'combatEvents', | 'combatEvents', | ||
Line 1,217: | Line 1,374: | ||
name: { key: 'MAGIC', idFormat: 'AURORA_NAME_{ID}' }, | name: { key: 'MAGIC', idFormat: 'AURORA_NAME_{ID}' }, | ||
}, | }, | ||
combatAreaCategories: { | combatAreaCategories: { | ||
name: { key: 'COMBAT_AREA_CATEGORY' } | name: { key: 'COMBAT_AREA_CATEGORY' } | ||
}, | }, | ||
combatAreas: { | combatAreas: { | ||
name: { key: 'COMBAT_AREA', idFormat: 'NAME_{ID}' }, | name: { key: 'COMBAT_AREA', idFormat: 'NAME_{ID}' }, | ||
}, | }, | ||
combatEffectGroups: { | combatEffectGroups: { | ||
name: { idKey: 'nameLang' } | name: { idKey: 'nameLang' } | ||
}, | }, | ||
combatEffects: { | combatEffects: { | ||
name: { idKey: 'nameLang' } | 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: { | |||
name: { key: 'MAGIC', idFormat: 'CURSE_NAME_{ID}' }, | |||
}, | |||
dungeons: { | |||
name: { key: 'DUNGEON', idFormat: 'NAME_{ID}' }, | |||
}, | |||
abyssDepths: { | |||
name: { key: 'THE_ABYSS', idFormat: 'NAME_{ID}' }, | |||
}, | |||
strongholds: { | |||
name: { idFormat: 'STRONGHOLD_NAME_{ID}' }, | |||
}, | }, | ||
equipmentSlots: { | |||
emptyName: { idFormat: 'EQUIP_SLOT_{ID}' } | |||
}, | }, | ||
gamemodes: { | gamemodes: { | ||
Line 1,667: | Line 1,827: | ||
lookupVal += (lookupVal.length > 0 ? '_' : '') + identifier; | lookupVal += (lookupVal.length > 0 ? '_' : '') + identifier; | ||
} | } | ||
return loadedLangJson[lookupVal]; | return this.customLocalizations[lookupVal] ?? loadedLangJson[lookupVal]; | ||
} | } | ||
getNamespacedID(namespace, ID) { | getNamespacedID(namespace, ID) { |