" + icon + textFill + maskImages + divEnd;
}
/**
* Fills HTML with appriopriate number of soul orbs images
* @param {object} divId ID of the HTML element for data appending
* @param {number} totalSoul Number of max Soul reserve from the save. 99 = full Soul Orb
*/
function CheckSoulOrbs(divId, totalSoul) {
let icon = SYMBOL_EMPTY;
let textFill = "Soul:";
let soulImages = "";
let soulNormal = "
";
let soulImg = soulNormal;
for (let i = 0, total = totalSoul / 33; i < total; i++) {
soulImages += soulImg;
}
document.getElementById(divId.id).innerHTML += "
" + icon + textFill + soulImages + divEnd;
}
/**
* Fills HTML with the Geo value of the save file
* @param {object} divId ID of the HTML element for data appending
* @param {number} geoValue Number of total Geo value
*/
function CheckGeo(divId, geoValue) {
let icon = SYMBOL_EMPTY;
let textFill = "Geo:
" + geoValue + "";
document.getElementById(divId.id).innerHTML += "
" + icon + textFill + divEnd;
}
/**
* Verifies if the data in a specific object is true or false, and appends HTML accordingly.
* @param {object} divId ID of the HTML element for data appending
* @param {object} dataObject Object containing data to be verified (use copy - data inside this object will be deleted)
* @param {object} playerData Reference/pointer to specific data where to search
*/
function CheckIfDataTrue(divId, dataObject, playerData) {
for (let i in dataObject) {
(playerData[i] === true) ? CurrentDataTrue(divId): CurrentDataFalse();
FillHTML(divId, dataObject[i][0], dataObject[i][1]);
}
}
/**
* Verifies if the data in a specific object is 0 or > 0, and appends HTML accordingly.
* @param {object} divId ID of the HTML element for data appending
* @param {object} dataObject Object containing data to be verified (use copy - data inside this object will be deleted)
* @param {object} playerData Reference/pointer to specific data where to search
*/
function CheckSpellLevel(divId, dataObject, playerData) {
for (let i in dataObject) {
for (let j = 1; j <= 2; j++) {
(playerData[i] >= j) ? CurrentDataTrue(divId): CurrentDataFalse();
FillHTML(divId, dataObject[i][j][0], dataObject[i][j][1]);
}
}
}
/**
* Verifies if the data in a specific object is 0 or > 0, and appends HTML accordingly.
* @param {object} divId ID of the HTML element for data appending
* @param {object} dataObject Object containing data to be verified (use copy - data inside this object will be deleted)
* @param {object} playerData Reference/pointer to specific data where to search
*/
function CheckWarriorDreams(divId, dataObject, playerData) {
for (let i in dataObject) {
(playerData[i] > 0) ? CurrentDataTrue(divId): CurrentDataFalse();
FillHTML(divId, dataObject[i][0], dataObject[i][1]);
}
}
/**
* Verifies if the data in a specific object are true or false, and appends HTML accordingly. Creates a copy of dataObject.
* @param {object} divId ID of the HTML element for data appending
* @param {string} idText Text string inside save data to search for
* @param {object} dataObject Object containing data to be verified
* @param {object} worldData Reference/pointer to specific data where to search
*/
function CheckWorldDataTrue(divId, idText, dataObject, worldData) {
let orderedArray = [];
let size = ObjectLength(dataObject);
// Order the items before displaying them (creates a copy of dataObject)
for (let i in dataObject) {
orderedArray.push([i, dataObject[i][0], dataObject[i][1], false]);
}
// Search for completed items and mark them for display
for (let i = 0, length = worldData.length; i < length; i++) {
for (let j = 0; j < size; j++) {
if (worldData[i].id === idText && worldData[i].sceneName === orderedArray[j][0] && worldData[i].activated === true) {
orderedArray[j][3] = true;
}
}
}
// Display the items as they were initially ordered
for (let i = 0; i < size; i++) {
CurrentDataFalse();
if (orderedArray[i][3] === true) CurrentDataTrue(divId);
FillHTML(divId, orderedArray[i][1], orderedArray[i][2]);
}
}
/**
* Verifies if the data in a specific object are true or false, or checks what values they have, and appends HTML accordingly.
* @param {object} divId ID of the HTML element for data appending
* @param {object} dataObject Object containing data to be verified
* @param {object} playerData Reference/pointer to specific data where to search
* @param {object} worldData Reference/pointer to specific data where to search
*/
function CheckEssential(divId, dataObject, playerData, worldData) {
let textPrefix = "";
let textSuffix = "";
/**
* Searches for a given item in the in-game area and returns true when found and collected.
* @param {string} itemArea Code of the in-game area on the map
* @param {string} itemId Name of the item
* @returns {boolean}
*/
function FindWorldItem(itemArea = "", itemId = "Shiny Item") {
for (let i = 0, length = worldData.length; i < length; i++) {
if (worldData[i].id === itemId) {
if (worldData[i].sceneName === itemArea) {
if (worldData[i].activated === true) return true;
}
}
}
return false;
}
for (let i in dataObject) {
textPrefix = dataObject[i][0];
textSuffix = dataObject[i][1];
switch (i) {
case "grubsCollected":
case "dreamOrbs":
case "fountainGeo":
textPrefix += ": " + playerData[i];
(playerData[i] >= dataObject[i][2]) ? CurrentDataTrue(): CurrentDataInfo();
break;
case "shopkeeperKey":
(playerData.hasSlykey === true || playerData.gaveSlykey === true) ? CurrentDataTrue(): CurrentDataFalse();
break;
case "elegantKey":
(playerData.hasWhiteKey === true || playerData.usedWhiteKey === true) ? CurrentDataTrue(): CurrentDataFalse();
break;
case "loveKey":
(playerData.hasLoveKey === true || playerData.openedLoveDoor === true) ? CurrentDataTrue(): CurrentDataFalse();
break;
case "simpleKeyCityOfTears": // #2
(FindWorldItem("Ruins1_17", "Shiny Item")) ? CurrentDataTrue(): CurrentDataFalse();
break;
case "simpleKeyAncientBasin": // #3
(FindWorldItem("Abyss_20", "Shiny Item Stand")) ? CurrentDataTrue(): CurrentDataFalse();
break;
case "paleOreAncientBasin": // #1
(FindWorldItem("Abyss_17", "Battle Scene Ore")) ? CurrentDataTrue(): CurrentDataFalse();
break;
case "paleOreSeer": // #2
(playerData.dreamReward2 === true) ? CurrentDataTrue(): CurrentDataFalse();
break;
case "paleOreCrystalPeak": // #3
(FindWorldItem("Mines_34", "Shiny Item Stand")) ? CurrentDataTrue(): CurrentDataFalse();
break;
case "paleOreDeepnest": // #4
(FindWorldItem("Deepnest_32", "Shiny Item Stand")) ? CurrentDataTrue(): CurrentDataFalse();
break;
case "paleOreGrubfather": // #5
(FindWorldItem("Crossroads_38", "Shiny Item Ore")) ? CurrentDataTrue(): CurrentDataFalse();
break;
case "paleOreColosseum": // #6
(FindWorldItem("Room_Colosseum_Silver", "Shiny Item")) ? CurrentDataTrue(): CurrentDataFalse();
break;
default:
(playerData[i] === true) ? CurrentDataTrue(): CurrentDataFalse();
}
FillHTML(divId, textPrefix, textSuffix);
}
}
/**
* Checks, validates and shows hints to the player depending on their save progression, in chronological order. Shows only hint for the last uncompleted event. If Hollow Knight is defeated, shows a dummy text.
* @param {object} divId object containing div hints ID and h2 title
* @param {object} dataObject object containing all hints data
* @param {object} playerData object containing HK Player Data to look in
* @param {object} worldData object containing HK World Data to look in
* @returns {bool} true when defeated Hollow Knight, false if not
*/
function CheckHintsTrue(divId, dataObject, playerData, worldData) {
if (playerData.killedHollowKnight === true) {
// a text to show when player already finished their first playthrough (killed Hollow Knight first time)
FillHTML(divId, "", "...a successful Knight who doesn't need hints anymore");
return true;
}
for (let i in dataObject) {
if (playerData[i] === true) {
continue;
} else if (i === "fireballLevel") {
if (playerData[i] >= 1) {
continue;
} else {
FillHTML(divId, dataObject[i][0], dataObject[i][1]);
return false;
}
} else if (i === "Crossroads_04") {
let GruzMotherDefeated = false;
for (let k = 0, length = worldData.length; k < length; k++) {
if (worldData[k].id === "Battle Scene" && worldData[k].sceneName === "Crossroads_04" && worldData[k].activated === true) {
GruzMotherDefeated = true;
break;
}
}
if (GruzMotherDefeated) {
continue; // next dataObject (i)
} else {
FillHTML(divId, dataObject[i][0], dataObject[i][1]);
return false;
}
} else if (i === "dungDefenderOrHornet2") {
if (playerData.defeatedDungDefender === true) {
continue;
} else if (playerData.hornetOutskirtsDefeated === true) {
continue;
} else { // if no Dung Defender or Hornet 2
FillHTML(divId, dataObject[i][0], dataObject[i][1]);
return false;
}
} else if (i === "ismaTearOrShadeCloak") {
if (playerData.hasAcidArmour === true) {
continue;
} else if (playerData.hasKingsBrand === true) {
if (playerData.hasShadowDash === true) {
continue;
} else { // if Kings Brand but no Shade Cloak
FillHTML(divId, dataObject[i][0], dataObject[i][1]);
return false;
}
} else { // if no Isma's Tear or Kings Brand
FillHTML(divId, dataObject[i][0], dataObject[i][1]);
return false;
}
} else { // if anything from the hints list is not done
FillHTML(divId, dataObject[i][0], dataObject[i][1]);
return false;
}
} // end: for (let i in dataObject)
} // function CheckHintsTrue()
/**
* Pre-Cleans HTML. Reads contents inside text area and parses it to a JavaScript object. If not empty, runs HKCheckCompletion() to check data.
*/
function HKReadTextArea() {
InitialHTMLPopulate(DIV_ID);
let textAreaId = "save-area";
let contents = document.getElementById(textAreaId).value;
if (contents.length) {
let jsonObject = JSON.parse(contents);
// console.log(jsonObject);
if (jsonObject.hasOwnProperty("playerData")) HKCheckCompletion(jsonObject);
// console.log(jsonObject);
}
}
/**
* Checks the length of a JavaScript Object like Array.length
* @param {object} object JavaScript Object
* @return {number} length of the Object
*/
function ObjectLength(object) {
let length = 0;
for (let key in object) {
if (object.hasOwnProperty(key)) {
++length;
}
}
return length;
}
/**
* Populate all HTML with given ID and their initial data set as false (used at DOM load)
* @param {object} divIdObj JavaScript Object containing all HTML IDs to populate
*/
function InitialHTMLPopulate(divIdObj) {
PrefillHTML(divIdObj);
CurrentDataFalse();
// Play Time
CheckPlayTime(divIdObj.intro, 0)
// Game Completion
CheckCompletionPercent(divIdObj.intro, 0);
// Health Masks
CheckHealthMasks(divIdObj.intro, 5, 0);
// Soul Orbs
CheckSoulOrbs(divIdObj.intro, 99);
// Geo
CheckGeo(divIdObj.intro, 0);
// First Hint Only
FillHTML(divIdObj.hints, HK_HINTS.fireballLevel[0], HK_HINTS.fireballLevel[1]);
// Temp arrays storing references (addresses) to objects for looping through them (duplicates important)
let hkObjArray = [HK_BOSSES, HK_BOSSES_WORLD, HK_CHARMS, HK_EQUIPMENT, HK_NAILARTS, HK_MASKSHARDS, HK_MASKSHARDS_WORLD, HK_VESSELFRAGMENTS, HK_VESSELFRAGMENTS_WORLD, HK_DREAMERS, HK_COLOSSEUM, HK_DREAMNAIL, HK_WARRIORDREAMS, HK_GRIMMTROUPE, HK_LIFEBLOOD, HK_GODMASTER, HK_ESSENTIAL];
// duplicates and order important - must be the same as in hkObjArray[]
let divObjArray = [divIdObj.bosses, divIdObj.bosses, divIdObj.charms, divIdObj.equipment, divIdObj.nailArts, divIdObj.maskShards, divIdObj.maskShards, divIdObj.vesselFragments, divIdObj.vesselFragments, divIdObj.dreamers, divIdObj.colosseum, divIdObj.dreamNail, divIdObj.warriorDreams, divIdObj.grimmTroupe, divIdObj.lifeblood, divIdObj.godmaster, divIdObj.essential];
// Looped filling to reduce redundancy
do {
for (let parameter in hkObjArray[0]) {
FillHTML(divObjArray[0], hkObjArray[0][parameter][0], hkObjArray[0][parameter][1]);
}
divObjArray.shift();
} while (hkObjArray.shift());
// Nail Upgrades Misc
for (let i = 0; i < HK_NAILUPGRADES.length; i++) {
FillHTML(divIdObj.nailUpgrades, HK_NAILUPGRADES[i][0], HK_NAILUPGRADES[i][1]);
}
// Spells Misc
for (let i in HK_SPELLS) {
for (let j = 1; j <= 2; j++) {
FillHTML(divIdObj.spells, HK_SPELLS[i][j][0], HK_SPELLS[i][j][1]);
}
}
// Nightmare King Grimm / Banishment Misc
FillHTML(divIdObj.grimmTroupe, "Nightmare King Grimm / Banishment", "Dirtmouth or Howling Cliffs");
// Godmaster Doors Misc
for (let i = 0; i < ObjectLength(HK_GODMASTER_DOORS); i++) {
FillHTML(divIdObj.godmaster, HK_GODMASTER_DOORS[i][0], HK_GODMASTER_DOORS[i][1]);
}
}
/**
* Toggles display of "hk-hints". On click with no parameters or on demand when called with a parameter
* @param {string} param "hide", "show" or none (optional)
*/
function CheckboxHintsToggle(param) {
let checkboxId = document.getElementById("checkbox-hints");
switch (param) {
case "hide":
document.getElementById("hk-hints").classList.add("hidden");
checkboxId.value = "hints-off";
checkboxId.checked = false;
break;
case "show":
document.getElementById("hk-hints").classList.remove("hidden");
checkboxId.value = "hints-on";
checkboxId.checked = true;
break;
default:
if (checkboxId.checked === false) {
document.getElementById("hk-hints").classList.add("hidden");
checkboxId.value = "hints-off";
} else {
document.getElementById("hk-hints").classList.remove("hidden");
checkboxId.value = "hints-on";
}
}
}
/**
* Toggles display of ".location" class. On click with no parameters or on demand when called with a parameter
* @param {string} param "hide", "show" or none (optional)
*/
function CheckboxLocationsToggle(param) {
let checkboxId = document.getElementById("checkbox-locations");
let allClassElements = document.querySelectorAll(".location");
let length = allClassElements.length;
switch (param) {
case "hide":
for (let i = 0; i < length; i++) {
allClassElements[i].classList.add("hidden");
}
checkboxId.value = "locations-off";
checkboxId.checked = false;
break;
case "show":
for (let i = 0; i < length; i++) {
allClassElements[i].classList.remove("hidden");
}
checkboxId.value = "locations-on";
checkboxId.checked = true;
break;
default:
if (checkboxId.checked === false) {
for (let i = 0; i < length; i++) {
allClassElements[i].classList.add("hidden");
}
checkboxId.value = "locations-off";
} else {
for (let i = 0; i < length; i++) {
allClassElements[i].classList.remove("hidden");
}
checkboxId.value = "locations-on";
}
}
}
/**
* Zero-fill all "percent" properties in the JSON Object (reset to default)
* @param {object} jsObj object containing the "percent" properties to be reset to 0
*/
function ResetCompletion(jsObj) {
for (let i in jsObj) {
if (jsObj[i].hasOwnProperty("percent")) jsObj[i].percent = 0;
}
}