17,376
edits
(Support further data modifications) |
(Support all remaining data modifications & dependent data) |
||
Line 57: | Line 57: | ||
}, | }, | ||
}; | }; | ||
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 132: | 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 265: | 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 418: | Line 420: | ||
} | } | ||
} | } | ||
registerPackData(packData) { | |||
Object.keys(packData) | |||
.filter((categoryName) => !this.excludedCategories.includes(categoryName)) | |||
.forEach((categoryName) => { | |||
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 | ||
Line 444: | 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 610: | 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 615: | 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( | |||
`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); | |||
} | |||
} | |||
}); | |||
} | |||
}); | |||
} | } | ||
getDataToModify(modCat) { | getDataToModify(modCat) { | ||
switch (modCat) { | switch (modCat) { | ||
case 'combatAreaCategories': | case 'combatAreaCategories': | ||
case 'damageTypes': | |||
case 'dungeons': | case 'dungeons': | ||
case 'equipmentSlots': | case 'equipmentSlots': | ||
case 'gamemodes': | |||
case 'items': | case 'items': | ||
case 'modifiers': | case 'modifiers': | ||
case 'pets': | |||
case 'shopUpgradeChains': | case 'shopUpgradeChains': | ||
case 'shopPurchases': | case 'shopPurchases': | ||
case 'skillData': | |||
case 'slayerAreas': | |||
return this.gameData[modCat]; | return this.gameData[modCat]; | ||
case 'cookingCategories': | case 'cookingCategories': | ||
Line 642: | Line 668: | ||
return undefined; | return undefined; | ||
} | } | ||
applyModifierModifications(objToModify, | applyModifierModifications(objToModify, adjustments) { | ||
Object.keys( | if (objToModify.modifiers === undefined) { | ||
objToModify.modifiers = {}; | |||
} | |||
Object.keys(adjustments) | |||
.forEach((adjType) => { | .forEach((adjType) => { | ||
if (adjType === 'add') { | if (adjType === 'add') { | ||
Object.entries( | Object.entries(adjustments[adjType]) | ||
.forEach(([chgKey, chgVal]) => { | .forEach(([chgKey, chgVal]) => { | ||
if (objToModify.modifiers[chgKey] === undefined) { | if (objToModify.modifiers[chgKey] === undefined) { | ||
Line 664: | Line 693: | ||
); | ); | ||
} | } | ||
} | |||
); | |||
} | |||
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) { | applyDataModifications(modData) { | ||
const modDataKeys = Object.keys(modData).filter((modCatID) => !this.excludedCategories.includes(modCatID)); | |||
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]; | ||
const dataToModify = this.getDataToModify(modCat); | const dataToModify = this.getDataToModify(modCat); | ||
const modObjIDKey = (modCat === 'skillData' ? 'skillID' : 'id'); | |||
if (dataToModify === undefined) { | if (dataToModify === undefined) { | ||
console.warn( | console.warn( | ||
Line 684: | Line 750: | ||
else { | else { | ||
catData.forEach((modItem) => { | catData.forEach((modItem) => { | ||
const modObjID = modItem | const modObjID = modItem[modObjIDKey]; | ||
if (modObjID === undefined) { | if (modObjID === undefined) { | ||
console.warn( | console.warn( | ||
Line 690: | Line 756: | ||
); | ); | ||
} else { | } else { | ||
const objToModify = this.getObjectByID(dataToModify, modObjID); | const objToModify = this.getObjectByID(dataToModify, modObjID, modObjIDKey); | ||
if (objToModify === undefined) { | if (objToModify === undefined) { | ||
console.warn( | console.warn( | ||
Line 701: | Line 767: | ||
// The 'areas' property of elements within the category data are ordered data | // The 'areas' property of elements within the category data are ordered data | ||
objToModify.areas = this.combineOrderedData(objToModify.areas, modItem.areas.add); | objToModify.areas = this.combineOrderedData(objToModify.areas, modItem.areas.add); | ||
break; | |||
case 'damageTypes': | |||
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( | |||
`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; | break; | ||
case 'shopPurchases': | case 'shopPurchases': | ||
Line 754: | Line 851: | ||
} else { | } else { | ||
console.warn( | console.warn( | ||
`Could not apply data modification: Unhandled key "${k}" for category "${modCat}", object "${ | `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; | break; | ||
Line 769: | Line 888: | ||
Object.keys(itemRules).forEach((ruleKey) => { | Object.keys(itemRules).forEach((ruleKey) => { | ||
if (ruleKey === 'add') { | if (ruleKey === 'add') { | ||
itemRules[ruleKey].forEach((itemDef) => { | itemRules[ruleKey].forEach((itemDef) => { | ||
const modToApply = { | |||
gamemodeID: itemDef.gamemodeID, | |||
add: itemDef.rewardItemIDs | |||
} | } | ||
this.applyGamemodeSpecificModifications(objToModify, modToApply, 'rewardItemIDs'); | |||
}); | }); | ||
} else { | } else { | ||
Line 791: | Line 903: | ||
} else if (k === 'gamemodeEntryRequirements') { | } else if (k === 'gamemodeEntryRequirements') { | ||
// Add or remove gamemode specific entry requirements to dungeon data | // Add or remove gamemode specific entry requirements to dungeon data | ||
this.applyGamemodeSpecificModifications(objToModify, modItem[k], 'entryRequirements'); | |||
} else { | } else { | ||
console.warn( | console.warn( | ||
Line 833: | Line 942: | ||
break; | break; | ||
case 'items': | case 'items': | ||
Object.keys(modItem) | Object.keys(modItem) | ||
.filter((k) => k !== 'id') | .filter((k) => k !== 'id') | ||
Line 881: | Line 989: | ||
); | ); | ||
break; | 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: | default: | ||
console.warn( | console.warn( | ||
Line 945: | Line 1,081: | ||
})); | })); | ||
} | } | ||
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.attackTypes === undefined) { | if (this.gameData.attackTypes === undefined) { | ||
this.gameData.attackTypes = AttackTypeID; | this.gameData.attackTypes = AttackTypeID; | ||
} | } | ||
} | } | ||
combineOrderedData(existingData, newData) { | combineOrderedData(existingData, newData) { | ||
Line 1,242: | Line 1,321: | ||
'combatAreaDisplayOrder', | 'combatAreaDisplayOrder', | ||
'combatAreaCategoryOrder', | 'combatAreaCategoryOrder', | ||
'combatEffectTables', | |||
'combatEffectTemplates', | 'combatEffectTemplates', | ||
'combatEvents', | 'combatEvents', |