Module:GameData/doc: Difference between revisions

Support all remaining data modifications & dependent data
(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).forEach((categoryName) => {
Object.keys(packData)
switch (categoryName) {
.filter((categoryName) => !this.excludedCategories.includes(categoryName))
case 'pages':
.forEach((categoryName) => {
case 'steamAchievements':
this.transformDataNode(ns, categoryName, packData, categoryName);
case 'tutorialStageOrder':
});
case 'tutorialStages':
// This data serves no purpose for the wiki and only serves to bloat
// the data, so simply delete it
delete packData[categoryName];
break;
default:
this.transformDataNode(ns, categoryName, packData, categoryName);
break;
}
});
}
}
transformDataNode(ns, categoryName, parentNode, nodeKey) {
transformDataNode(ns, categoryName, parentNode, nodeKey) {
Line 418: Line 420:
}
}
}
}
registerDataPackage(namespace) {
registerPackData(packData) {
// Consolidates the data package identified by namespace with existing data within
Object.keys(packData)
// this.gameData
.filter((categoryName) => !this.excludedCategories.includes(categoryName))
const packData = this.packData[namespace].data;
.forEach((categoryName) => {
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
Object.keys(packData).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 (
}
['ancientSpells', 'archaicSpells', 'auroraSpells', 'curseSpells', 'standardSpells'].includes(categoryName)
else if (categoryName === 'golbinRaid') {
) {
// For spell books, add the spell type to each spell object.
// Alt Magic spells are handled elsewhere, as they are within a skill object
const spellType = categoryName.replace('Spells', '');
const newData = structuredClone(categoryData);
newData.forEach((x) => (x.spellBook = spellType));
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);
}
}
const dependentData = this.packData[namespace].dependentData;
// Dependent data is handled later, once all packages have been registered
if (dependentData !== undefined) {
 
console.warn(
if (!this.registeredNamespaces.includes(namespace)) {
`Data package for ${namespace} has dependent data, which is currently unsupported`
this.registeredNamespaces.push(namespace);
);
// TODO Handle dependentData
}
}
}
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, modifierAdjustments) {
applyModifierModifications(objToModify, adjustments) {
Object.keys(modifierAdjustments)
if (objToModify.modifiers === undefined) {
objToModify.modifiers = {};
}
Object.keys(adjustments)
.forEach((adjType) => {
.forEach((adjType) => {
if (adjType === 'add') {
if (adjType === 'add') {
Object.entries(modifierAdjustments[adjType])
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) {
// TODO: Handle modifications for the following:
const modDataKeys = Object.keys(modData).filter((modCatID) => !this.excludedCategories.includes(modCatID));
// 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];
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.id;
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 "${mobObjID}"`
`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') {
if (objToModify[k] === undefined) {
objToModify[k] = [];
}
itemRules[ruleKey].forEach((itemDef) => {
itemRules[ruleKey].forEach((itemDef) => {
let gamemodeRewards = this.getObjectByID(objToModify[k], itemDef.gamemodeID, 'gamemodeID');
const modToApply = {
if (gamemodeRewards === undefined) {
gamemodeID: itemDef.gamemodeID,
objToModify[k].push({
add: itemDef.rewardItemIDs
gamemodeID: itemDef.gamemodeID,
itemIDs: itemDef.rewardItemIDs,
});
} else {
gamemodeRewards.push(...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
if (objToModify[k] === undefined) {
this.applyGamemodeSpecificModifications(objToModify, modItem[k], 'entryRequirements');
objToModify[k] = [];
}
objToModify[k].push(modItem[k]);
} else {
} else {
console.warn(
console.warn(
Line 833: Line 942:
break;
break;
case 'items':
case 'items':
case 'pets':
Object.keys(modItem)
Object.keys(modItem)
.filter((k) => k !== 'id')
.filter((k) => k !== 'id')
Line 881: Line 989:
);
);
break;
break;
// case 'equipmentSlots':
case 'equipmentSlots':
// TODO
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.combatTriangles === undefined) {
const ctData = [];
Object.keys(COMBAT_TRIANGLE_IDS).forEach((id) => {
const newObj = structuredClone(combatTriangle[COMBAT_TRIANGLE_IDS[id]]);
newObj.id = id;
ctData.push(newObj);
});
this.gameData.combatTriangles = ctData;
}
*/
/**
if (this.gameData.masteryCheckpoints === undefined) {
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) {
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),
}));
}
*/
if (this.gameData.attackTypes === undefined) {
if (this.gameData.attackTypes === undefined) {
this.gameData.attackTypes = AttackTypeID;
this.gameData.attackTypes = AttackTypeID;
}
}
/**
if (this.gameData.slayerTiers === undefined) {
const newData = structuredClone(SlayerTask.data);
newData.forEach((tier) => delete tier.engDisplay);
this.gameData.slayerTiers = newData;
}
*/
/**
if (this.gameData.modifierData === undefined && modifierData !== undefined) {
var wikiModData = {};
Object.keys(modifierData).forEach((modK) => {
const mod = modifierData[modK];
wikiModData[modK] = {};
Object.keys(mod).forEach((k) => {
if (k === 'modifyValue') {
// Convert function into function name
// If the function is inline and has no name, then use the function definition instead
var funcName = mod[k].name;
if (funcName === 'modifyValue') {
funcName = mod[k].toString();
}
wikiModData[modK][k] = funcName;
} else if (k === 'langDescription') {
wikiModData[modK]['description'] = mod[k];
} else if (k !== 'description') {
wikiModData[modK][k] = mod[k];
}
});
});
this.gameData.modifierData = wikiModData;
}
*/
}
}
combineOrderedData(existingData, newData) {
combineOrderedData(existingData, newData) {
Line 1,242: Line 1,321:
'combatAreaDisplayOrder',
'combatAreaDisplayOrder',
'combatAreaCategoryOrder',
'combatAreaCategoryOrder',
'combatEffectTables',
'combatEffectTemplates',
'combatEffectTemplates',
'combatEvents',
'combatEvents',