// ---------------- Constants ----------------- //
const DATA_UNKNOWN = "Data unknown";
const SYMBOL_FALSE = ""; // "❌ ";
const SYMBOL_TRUE = ""; // "✅ ";
// ---------------- Variables ----------------- //
var isCompleted = "";
var isNotCompleted = "";
var currentData = DATA_UNKNOWN;
var completionSymbol = SYMBOL_FALSE;
var divStart = [
"
"
].join("\n");
var divEnd = [
"
"
].join("\n");
var strongStart = "";
var strongEnd = "";
// ---------------- Hollow Knight Data Constant Objects ----------------- //
const DIV_ID = {
intro: "hk-intro",
bosses: "hk-bosses",
charms: "hk-charms",
colosseum: "hk-colosseum",
dreamers: "hk-dreamers",
dreamNail: "hk-dreamnail",
equipment: "hk-equipment",
maskShards: "hk-maskshards",
nailArts: "hk-nailarts",
nailUpgrades: "hk-nailupgrades",
spells: "hk-spells",
vesselFragments: "hk-vesselfragments",
warriorDreams: "hk-warriordreams",
grimmTroupe: "hk-grimmtroupe",
lifeblood: "hk-lifeblood",
godmaster: "hk-godmaster"
};
const HK_BOSSES = {
killedMawlek: "Brooding Mawlek",
killedBurstingBouncer: "Gruz Mother",
collectorDefeated: "The Collector",
defeatedMegaJelly: "Uumuu",
killedMimicSpider: "Nosk",
killedTraitorLord: "Traitor Lord",
killedInfectedKnight: "Broken Vessel",
killedMageKnight: "Watcher Knights",
defeatedDungDefender: "Dung Defender",
mageLordDefeated: "Soul Master",
defeatedMantisLords: "Mantis Lords",
hornet1Defeated: "Hornet Protector",
hornetOutskirtsDefeated: "Hornet Sentinel",
falseKnightDefeated: "False Knight"
};
// reference: https://radiance.host/apidocs/Charms.html
const HK_CHARMS = {
gotCharm_1: "Gathering Swarm", // 1
gotCharm_2: "Wayward Compass", // 1
gotCharm_3: "Grubsong", // 1
gotCharm_4: "Stalwart Shell", // 2
gotCharm_5: "Baldur Shell", // 2
gotCharm_6: "Fury of the Fallen", // 2
gotCharm_7: "Quick Focus", // 3
gotCharm_8: "Lifeblood Heart", // 2
gotCharm_9: "Lifeblood Core", // 3
gotCharm_10: "Defender's Crest", // 1
gotCharm_11: "Flukenest", // 3
gotCharm_12: "Thorns of Agony", // 1
gotCharm_13: "Mark of Pride", // 3
gotCharm_14: "Steady Body", // 1
gotCharm_15: "Heavy Blow", // 2
gotCharm_16: "Sharp Shadow", // 2
gotCharm_17: "Spore Shroom", // 1
gotCharm_18: "Longnail", // 2
gotCharm_19: "Shaman Stone", // 3
gotCharm_20: "Soul Catcher", // 2
gotCharm_21: "Soul Eater", // 4
gotCharm_22: "Glowing Womb", // 2
gotCharm_23: "Fragile Heart / Unbreakable Heart", // 2
gotCharm_24: "Fragile Greed / Unbreakable Greed", // 2
gotCharm_25: "Fragile Strength / Unbreakable Strength", // 3
gotCharm_26: "Nailmaster’s Glory", // 1
gotCharm_27: "Joni’s Blessing", // 4
gotCharm_28: "Shape of Unn", // 2
gotCharm_29: "Hiveblood", // 4
gotCharm_30: "Dream Wielder", // 1
gotCharm_31: "Dashmaster", // 2
gotCharm_32: "Quick Slash", // 3
gotCharm_33: "Spell Twister", // 2
gotCharm_34: "Deep Focus", // 4
gotCharm_35: "Grubberfly’s Elegy", // 3
gotCharm_36: "Kingsoul / Void Heart", // 5
};
const HK_COLOSSEUM = {
colosseumBronzeCompleted: "Trial of the Warrior",
colosseumSilverCompleted: "Trial of the Conqueror",
colosseumGoldCompleted: "Trial of the Fool"
};
const HK_DREAMERS = {
lurienDefeated: "Lurien the Watcher",
monomonDefeated: "Monomon the Teacher",
hegemolDefeated: "Herrah the Beast"
};
const HK_DREAMNAIL = {
hasDreamNail: "Dream Nail acquired",
dreamNailUpgraded: "Awoken Dream Nail (Seer - 1800 Essence)",
mothDeparted: "Hear the Seer's final words (Seer - 2400 Essence)"
};
const HK_EQUIPMENT = {
hasKingsBrand: "King's Brand",
hasSuperDash: "Crystal Heart",
hasWalljump: "Mantis Claw",
hasDoubleJump: "Monarch Wings",
hasAcidArmour: "Isma's Tear",
hasDash: "Mothwing Cloak",
hasShadowDash: "Shade Cloak"
};
const HK_MASKSHARDS = {
slyShellFrag1: "Mask Shard (Sly - 150 Geo)",
slyShellFrag2: "Mask Shard (Sly - 500 Geo)",
slyShellFrag3: "Mask Shard (Sly - 800 Geo)",
slyShellFrag4: "Mask Shard (Sly - 1500 Geo)",
dreamReward7: "Mask Shard (Seer - 1500 Essence)"
};
// "Heart Piece" sceneData.persistentBoolItems[n].id
const HK_MASKSHARDS_WORLD = {
Crossroads_13: "Mask Shard (Forgotten Crossroads - below Hot Springs)",
Fungus2_01: "Mask Shard (Queen's Station)",
Crossroads_38: "Mask Shard (Grubfather - rescue 5 Grubs)",
Waterways_04b: "Mask Shard (Royal Waterways)",
Fungus1_36: "Mask Shard (Greenpath - Stone Sanctuary)",
Crossroads_09: "Mask Shard (Forgotten Crossroads - Brooding Mawlek)",
Mines_32: "Mask Shard (Crystal Peak - Enraged Guardian)",
Fungus2_25: "Mask Shard (Deepnest - entrance from Fungal Wastes)",
Room_Bretta: "Mask Shard (Dirtmouth - Bretta's Room)",
Hive_04: "Mask Shard (The Hive)",
Room_Mansion: "Mask Shard (Resting Grounds - Delicate Flower)"
};
const HK_NAILARTS = {
hasAllNailArts: "All Nail Arts",
hasUpwardSlash: "Great Slash",
hasDashSlash: "Dash Slash",
hasCyclone: "Cyclone Slash"
};
const HK_SPELLS = {
fireballLevel: {
1: "Vengeful Spirit",
2: "Shade Soul"
},
quakeLevel: {
1: "Desolate Dive",
2: "Descending Dark"
},
screamLevel: {
1: "Howling Wraiths",
2: "Abyss Shriek"
}
};
const HK_VESSELFRAGMENTS = {
slyVesselFrag1: "Vessel Fragment (Sly - 550 Geo)",
slyVesselFrag2: "Vessel Fragment (Sly - 900 Geo)",
dreamReward5: "Vessel Fragment (Seer - 700 Essence)",
vesselFragStagNest: "Vessel Fragment (Stag Nest)"
};
// "Vessel Fragment" sceneData.persistentBoolItems[n].id
const HK_VESSELFRAGMENTS_WORLD = {
Fungus1_13: "Vessel Fragment (Greenpath - near Queen's Gardens exit)",
Crossroads_37: "Vessel Fragment (Forgotten Crossroads - unlock lift in City of Tears)",
Ruins2_09: "Vessel Fragment (Above King's Station)",
Deepnest_38: "Vessel Fragment (Deepnest - Goam platforming challenge)",
Abyss_04: "Vessel Fragment (Ancient Basin Fountain - 3000 Geo)"
};
const HK_WARRIORDREAMS = {
galienDefeated: "Galien",
mumCaterpillarDefeated: "Marmu",
markothDefeated: "Markoth",
xeroDefeated: "Xero",
noEyesDefeated: "No Eyes",
elderHuDefeated: "Elder Hu",
aladarSlugDefeated: "Gorb"
};
const HK_GRIMMTROUPE = {
gotCharm_40: "Grimmchild / Carefree Melody",
gotCharm_38: "Dreamshield",
gotCharm_37: "Sprintmaster",
gotCharm_39: "Weaversong",
gotGrimmNotch: "Troupe Leader Grimm"
};
const HK_LIFEBLOOD = {
killedHiveKnight: "Hive Knight"
};
const HK_GODMASTER = {
hasGodfinder: "Godtuner"
};
function HKCheckCompletion(jsonObject) {
// start benchmark
let countBegin = new Date();
// Pre-Cleaning all divs for safety
CleanHTML(DIV_ID);
// Shallow Clone const objects (used for destructive functions)
let HK_BOSSES_temp = Object.assign({}, HK_BOSSES);
let HK_CHARMS_temp = Object.assign({}, HK_CHARMS);
let HK_COLOSSEUM_temp = Object.assign({}, HK_COLOSSEUM);
let HK_DREAMERS_temp = Object.assign({}, HK_DREAMERS);
let HK_DREAMNAIL_temp = Object.assign({}, HK_DREAMNAIL);
let HK_EQUIPMENT_temp = Object.assign({}, HK_EQUIPMENT);
let HK_MASKSHARDS_temp = Object.assign({}, HK_MASKSHARDS);
let HK_MASKSHARDS_WORLD_temp = Object.assign({}, HK_MASKSHARDS_WORLD);
let HK_NAILARTS_temp = Object.assign({}, HK_NAILARTS);
let HK_VESSELFRAGMENTS_temp = Object.assign({}, HK_VESSELFRAGMENTS);
let HK_VESSELFRAGMENTS_WORLD_temp = Object.assign({}, HK_VESSELFRAGMENTS_WORLD);
let HK_WARRIORDREAMS_temp = Object.assign({}, HK_WARRIORDREAMS);
let HK_GRIMMTROUPE_temp = Object.assign({}, HK_GRIMMTROUPE);
let HK_LIFEBLOOD_temp = Object.assign({}, HK_LIFEBLOOD);
let HK_GODMASTER_temp = Object.assign({}, HK_GODMASTER);
// Deep Clone const spells multi-layer object (used for destructive functions)
let HK_SPELLS_temp = JSON.parse(JSON.stringify(HK_SPELLS));
let HKPlayerData = jsonObject.playerData;
let HKWorldItems = jsonObject.sceneData.persistentBoolItems;
let bossDoor = ["Pantheon of the Master", "Pantheon of the Artist", "Pantheon of the Sage", "Pantheon of the Knight"];
let nailName = ["Old Nail", "Sharpened Nail", "Channeled Nail", "Coiled Nail", "Pure Nail"];
for (let i in HKPlayerData) {
completionSymbol = SYMBOL_FALSE;
currentData = DATA_UNKNOWN;
// ---------------- Game Completion Status ----------------- //
if (i === "completionPercentage") {
if (HKPlayerData.completionPercentage >= 112) CurrentDataTrue(HKPlayerData.completionPercentage);
else CurrentDataFalse(HKPlayerData.completionPercentage);
FillHTML(DIV_ID.intro, "Hollow Knight Completion: ", " %");
}
// ---------------- Time Played ----------------- //
if (i === "playTime") {
let seconds = Math.floor(HKPlayerData.playTime);
let minutes = Math.floor((seconds / 60) % 60);
let hours = Math.floor(seconds / 3600);
let sec = Math.floor(seconds % 60);
if (sec <= 10) sec = "0" + sec;
if (minutes <= 10) minutes = "0" + minutes;
CurrentDataTrue();
FillHTML(DIV_ID.intro, "Time Played: " + hours + " h " + minutes + " min " + sec + " sec");
}
// ---------------- Bosses (Base Game) --------------------- //
if (HK_BOSSES_temp) CheckIfDataTrue(DIV_ID.bosses, HK_BOSSES_temp, HKPlayerData);
// ---------------- Charms --------------------- //
if (HK_CHARMS_temp) CheckIfDataTrue(DIV_ID.charms, HK_CHARMS_temp, HKPlayerData);
// ---------------- Colosseum of Fools --------------------- //
if (HK_COLOSSEUM_temp) CheckIfDataTrue(DIV_ID.colosseum, HK_COLOSSEUM_temp, HKPlayerData);
// ---------------- Dreamers --------------------- //
if (HK_DREAMERS_temp) CheckIfDataTrue(DIV_ID.dreamers, HK_DREAMERS_temp, HKPlayerData);
// ---------------- Dream Nail and Essence --------------------- //
if (HK_DREAMNAIL_temp) CheckIfDataTrue(DIV_ID.dreamNail, HK_DREAMNAIL_temp, HKPlayerData);
// ---------------- Equipment --------------------- //
if (HK_EQUIPMENT_temp) CheckIfDataTrue(DIV_ID.equipment, HK_EQUIPMENT_temp, HKPlayerData);
// ---------------- Mask Shards --------------------- //
if (HK_MASKSHARDS_temp) CheckIfDataTrue(DIV_ID.maskShards, HK_MASKSHARDS_temp, HKPlayerData);
// ---------------- Nail Arts --------------------- //
if (HK_NAILARTS_temp) CheckIfDataTrue(DIV_ID.nailArts, HK_NAILARTS_temp, HKPlayerData);
// ---------------- Nail Upgrades --------------------- //
if (i === "nailSmithUpgrades") {
for (let j = 0; j < nailName.length; j++) {
CurrentDataFalse();
if (HKPlayerData.nailSmithUpgrades >= j) CurrentDataTrue();
FillHTML(DIV_ID.nailUpgrades, nailName[j]);
}
}
// ---------------- Spells --------------------- //
if (HK_SPELLS_temp) CheckSpellLevel(DIV_ID.spells, HK_SPELLS_temp, HKPlayerData);
// ---------------- Vessel Fragments --------------------- //
if (HK_VESSELFRAGMENTS_temp) CheckIfDataTrue(DIV_ID.vesselFragments, HK_VESSELFRAGMENTS_temp, HKPlayerData);
// ---------------- Warrior Dreams --------------------- //
if (HK_WARRIORDREAMS_temp) CheckWarriorDreams(DIV_ID.warriorDreams, HK_WARRIORDREAMS_temp, HKPlayerData);
// ---------------- Grimm Troupe Content Pack --------------------- //
if (HK_GRIMMTROUPE_temp) CheckIfDataTrue(DIV_ID.grimmTroupe, HK_GRIMMTROUPE_temp, HKPlayerData);
if (i === "grimmChildLevel") {
if (HKPlayerData.grimmChildLevel >= 4) CurrentDataTrue();
else CurrentDataFalse();
FillHTML(DIV_ID.grimmTroupe, "Nightmare King Grimm / Banishment");
}
// ---------------- Lifeblood Content Pack --------------------- //
if (HK_LIFEBLOOD_temp) CheckIfDataTrue(DIV_ID.lifeblood, HK_LIFEBLOOD_temp, HKPlayerData);
// ---------------- Godmaster Content Pack --------------------- //
if (HK_GODMASTER_temp) CheckIfDataTrue(DIV_ID.godmaster, HK_GODMASTER_temp, HKPlayerData);
if (bossDoor.length) {
for (let j = 1; j <= 4; j++) {
CurrentDataFalse();
if (i === ("bossDoorStateTier" + j)) {
if (HKPlayerData["bossDoorStateTier" + j].completed === true) CurrentDataTrue();
FillHTML(DIV_ID.godmaster, bossDoor[0]);
bossDoor.shift();
}
}
}
}
// ---------------- Mask Shards (World Map) --------------------- //
CheckWorldDataTrue(DIV_ID.maskShards, "Heart Piece", HK_MASKSHARDS_WORLD_temp, HKWorldItems);
// ---------------- Vessel Fragments (World Map) --------------------- //
CheckWorldDataTrue(DIV_ID.vesselFragments, "Vessel Fragment", HK_VESSELFRAGMENTS_WORLD_temp, HKWorldItems);
// finish and show benchmark
let countEnd = new Date();
console.info("HKCheckCompletion() time (ms) =", countEnd - countBegin);
}
function CleanHTML(jsObj) {
for (let i in jsObj) {
document.getElementById(jsObj[i]).innerHTML = "";
}
}
function FillHTML(divId = "", textPrefix = "Unknown Completion Element: ", textSuffix = "") {
document.getElementById(divId).innerHTML += divStart + completionSymbol + strongStart + textPrefix + "" + strongEnd + currentData + textSuffix + divEnd;
}
function CurrentDataTrue(textData = isCompleted) {
completionSymbol = SYMBOL_TRUE;
currentData = textData;
}
function CurrentDataFalse(textData = isNotCompleted) {
completionSymbol = SYMBOL_FALSE;
currentData = textData;
}
function CheckIfDataTrue(divId, dataObject, playerData) {
for (let i in dataObject) {
if (playerData[i] === true) CurrentDataTrue();
else CurrentDataFalse();
FillHTML(divId, dataObject[i]);
delete dataObject[i];
}
}
function CheckSpellLevel(divId, dataObject, playerData) {
for (let i in dataObject) {
for (let j = 1; j <= 2; j++) {
CurrentDataFalse();
if (playerData[i] >= j) CurrentDataTrue();
FillHTML(divId, dataObject[i][j]);
}
delete dataObject[i];
}
}
function CheckWarriorDreams(divId, dataObject, playerData) {
for (let i in dataObject) {
if (playerData[i] > 0) CurrentDataTrue();
else CurrentDataFalse();
FillHTML(divId, dataObject[i]);
delete dataObject[i];
}
}
function CheckWorldDataTrue(divId, idText, dataObject, worldData) {
let foundData = 0;
let size = ObjectLength(dataObject);
for (let i = 0; i < worldData.length; i++) {
for (let j in dataObject) {
if (worldData[i].id === idText && worldData[i].sceneName === j && worldData[i].activated === true) {
CurrentDataTrue();
foundData++;
FillHTML(divId, dataObject[j]);
delete dataObject[j];
}
}
}
if (foundData < size) {
CurrentDataFalse();
for (let j in dataObject) {
FillHTML(divId, dataObject[j]);
delete dataObject[j];
}
}
}
function HKReadTextArea() {
CleanHTML(DIV_ID);
let textAreaId = "save-area";
let contents = document.getElementById(textAreaId).value;
if (contents.length) {
let jsonObject = JSON.parse(contents);
HKCheckCompletion(jsonObject);
// console.log(jsonObject);
}
}
function ObjectLength(object) {
var length = 0;
for (var key in object) {
if (object.hasOwnProperty(key)) {
++length;
}
}
return length;
}