From caf116a5392d0353e549ec06d97e4cfc03e0463e Mon Sep 17 00:00:00 2001 From: J10a1n15 <45315647+j10a1n15@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:11:53 +0200 Subject: [PATCH 1/3] feat: id overlays item requirements --- .github/scripts/parsers/1_21_5/attributes.mjs | 6 +- .../scripts/parsers/1_21_5/enchantments.mjs | 4 +- .../scripts/parsers/1_21_5/id_overlays.mjs | 82 ++++++++++++++++++- .github/scripts/parsers/1_21_5/items.mjs | 6 +- .github/scripts/parsers/1_21_5/mobs.mjs | 4 +- .github/scripts/parsers/1_21_5/pets.mjs | 4 +- .github/scripts/parsers/1_21_5/potions.mjs | 4 +- .github/scripts/parsers/1_21_5/runes.mjs | 4 +- .github/scripts/parsers/parse.mjs | 22 ++--- .github/scripts/utils/collection.mjs | 44 ++++++++++ .github/scripts/utils/roman_numeral.mjs | 16 ++++ 11 files changed, 168 insertions(+), 28 deletions(-) create mode 100644 .github/scripts/utils/collection.mjs create mode 100644 .github/scripts/utils/roman_numeral.mjs diff --git a/.github/scripts/parsers/1_21_5/attributes.mjs b/.github/scripts/parsers/1_21_5/attributes.mjs index f9626d6..aeb07fe 100644 --- a/.github/scripts/parsers/1_21_5/attributes.mjs +++ b/.github/scripts/parsers/1_21_5/attributes.mjs @@ -9,7 +9,7 @@ export const attributeIds = [] export const Attributes = { /** @param item {Item} */ - parseAttribute: (item) => { + parseAttribute: async (item) => { const originalItem = item item = structuredClone(item) @@ -61,12 +61,12 @@ export const Attributes = { attributesFile.push(attribute) - const overlayProps = getOverlay(item); + const overlayProps = await getOverlay(item); if (overlayProps) { attributeOverlaysFile.push({ type: "attribute", id: item.nbt.ExtraAttributes.id, - ...getOverlay(item), + ...overlayProps(item), }); } }, diff --git a/.github/scripts/parsers/1_21_5/enchantments.mjs b/.github/scripts/parsers/1_21_5/enchantments.mjs index f81af80..a2fe4eb 100644 --- a/.github/scripts/parsers/1_21_5/enchantments.mjs +++ b/.github/scripts/parsers/1_21_5/enchantments.mjs @@ -7,7 +7,7 @@ export const enchantmentIds = [] export const Enchantments = { /** @param item {Item} */ - parseEnchantments: (item) => { + parseEnchantments: async (item) => { const originalItem = item item = structuredClone(item) @@ -53,7 +53,7 @@ export const Enchantments = { enchantmentFile[enchantId] = enchant - const overlayProps = getOverlay(item); + const overlayProps = await getOverlay(item); if (overlayProps) { enchantmentOverlaysFile.push({ type: "enchantment", diff --git a/.github/scripts/parsers/1_21_5/id_overlays.mjs b/.github/scripts/parsers/1_21_5/id_overlays.mjs index 489b466..3a8a06b 100644 --- a/.github/scripts/parsers/1_21_5/id_overlays.mjs +++ b/.github/scripts/parsers/1_21_5/id_overlays.mjs @@ -1,4 +1,6 @@ import {cleanObject} from "./items.mjs"; +import {romanToInt} from "../../utils/roman_numeral.mjs"; +import {isCollectionItem,getCollectionId} from "../../utils/collection.mjs"; const getWiki = (item) => { if (item.infoType !== "WIKI_URL" || !item.info || item.info.length === 0) return undefined; @@ -10,9 +12,87 @@ const getWiki = (item) => { }); }; -export const getOverlay = (item) => { +const hotm = ["hotm", "heart of the mountain", "heart of the mountain tier"] +const hotf = ["hotf", "heart of the forest", "heart of the forest tier"] +const bossCollection = ["bonzo", "scarf", "the professor", "thorn", "livid", "sadan", "necron"] +const skills = ["combat", "farming", "fishing", "mining", "foraging", "enchanting", "alchemy", "carpentry", "taming", "hunting", "duneoneering"] + +const getRequirements = async (item) => { + if (!item.crafttext.startsWith("Requires")) { + console.warn("Non Requirement crafttext found: " + item.crafttext); + return undefined + } + const stringReqs = item.crafttext.replace(/^(Requires:?)/, "").trim().split(" & "); + + const out = []; + + for (let req of stringReqs) { + const match = req.match(/^(.*?)\s+([0-9]+|[IVXLCDM]+)$/); + + if (match) { + const name = match[1]; + const levelStr = match[2]; + + const levelNum = !isNaN(levelStr) ? parseInt(levelStr, 10) : romanToInt(levelStr); + + if (name.toLowerCase().endsWith("slayer")) { + out.push({ + type: "slayer", + name: name.substring(0, name.length - 6).trim(), + level: levelNum + }) + } else if (hotm.includes(name.toLowerCase())) { + out.push({ + type: "hotm", + level: levelNum + }); + } else if (hotf.includes(name.toLowerCase())) { + out.push({ + type: "hotf", + level: levelNum + }); + } else if (bossCollection.includes(name.toLowerCase())) { + out.push({ + type: "bossCollection", + name: name, + level: levelNum + }); + } else if (skills.includes(name.toLowerCase())) { + out.push({ + type: "skill", + name: name, + level: levelNum + }); + } else if (await isCollectionItem(name)) { + out.push({ + type: "collection", + name: name, + id: getCollectionId(name), + level: levelNum + }) + } else { + out.push({ + type: "unknown", + name: name, + level: levelNum + }); + } + } else { + out.push({ + type: "unknown", + name: req, + level: null + }); + } + } + + return out +} + +export const getOverlay = async (item) => { const overlay = cleanObject({ vanilla: item.vanilla ? true : undefined, + requirements: item.crafttext && item.crafttext !== "" ? await getRequirements(item) : undefined, // Add await here wiki: getWiki(item), }); diff --git a/.github/scripts/parsers/1_21_5/items.mjs b/.github/scripts/parsers/1_21_5/items.mjs index b2adba9..df97c6b 100644 --- a/.github/scripts/parsers/1_21_5/items.mjs +++ b/.github/scripts/parsers/1_21_5/items.mjs @@ -34,7 +34,7 @@ export const getItemId = (id, damage) => { export const Items = { /** @param item {Item} */ - parseItem: (item) => { + parseItem: async (item) => { if (specialItems.items.includes(item.internalname)) return; const isUnbreakable = item.nbt?.Unbreakable === 1; @@ -85,12 +85,12 @@ export const Items = { } }); - const overlayProps = getOverlay(item); + const overlayProps = await getOverlay(item); if (overlayProps) { itemOverlaysFile.push({ type: "item", id: item.nbt.ExtraAttributes.id, - ...getOverlay(item), + ...overlayProps, }); } }, diff --git a/.github/scripts/parsers/1_21_5/mobs.mjs b/.github/scripts/parsers/1_21_5/mobs.mjs index bdbef0a..922b3ba 100644 --- a/.github/scripts/parsers/1_21_5/mobs.mjs +++ b/.github/scripts/parsers/1_21_5/mobs.mjs @@ -58,7 +58,7 @@ const parseDropAmountAndChance = (chanceStr, extraLines) => { export const Mobs = { /** @param item {Item} */ - parseMob: (item) => { + parseMob: async (item) => { const realId = item.internalname.replace("MAYOR_MONSTER", "MAYOR"); const [, realName, type] = item.displayname.match(/^§.(.*?)(?: \(([^)]+)\))?$/) || []; @@ -134,7 +134,7 @@ export const Mobs = { lootTables: lootTables.length === 0 ? undefined : lootTables, }; - const overlayProps = getOverlay(item); + const overlayProps = await getOverlay(item); if (overlayProps) { mobOverlaysFile.push({ type: "mob", diff --git a/.github/scripts/parsers/1_21_5/pets.mjs b/.github/scripts/parsers/1_21_5/pets.mjs index 936ac95..55e191d 100644 --- a/.github/scripts/parsers/1_21_5/pets.mjs +++ b/.github/scripts/parsers/1_21_5/pets.mjs @@ -34,7 +34,7 @@ const getPetVariables = (pet, tier) => { export const Pets = { /** @param item {Item} */ - parsePet: (item) => { + parsePet: async (item) => { if (item.itemid !== "minecraft:skull") throw new Error(`Unknown pet: ${item.itemid}:${item.damage}`) const petId = item.pet.type @@ -97,7 +97,7 @@ export const Pets = { petsFile[petId] = data - const overlayProps = getOverlay(item); + const overlayProps = await getOverlay(item); if (overlayProps) { petOverlaysFile.push({ type: "pet", diff --git a/.github/scripts/parsers/1_21_5/potions.mjs b/.github/scripts/parsers/1_21_5/potions.mjs index cd9f41a..a6e4a9f 100644 --- a/.github/scripts/parsers/1_21_5/potions.mjs +++ b/.github/scripts/parsers/1_21_5/potions.mjs @@ -129,7 +129,7 @@ const parseLiteralLevel = (name) => stripFormatting(name).match(potionLevelPatte export const Potions = { /** @param item {Item} */ - parsePotions: (item) => { + parsePotions: async (item) => { const originalItem = item item = structuredClone(item) @@ -166,7 +166,7 @@ export const Potions = { potionFile[potionId] = potion - const overlayProps = getOverlay(item); + const overlayProps = await getOverlay(item); if (overlayProps) { potionOverlaysFile.push({ type: "potion", diff --git a/.github/scripts/parsers/1_21_5/runes.mjs b/.github/scripts/parsers/1_21_5/runes.mjs index 764bfa6..1ccbf3b 100644 --- a/.github/scripts/parsers/1_21_5/runes.mjs +++ b/.github/scripts/parsers/1_21_5/runes.mjs @@ -7,7 +7,7 @@ export const runeIds = [] export const Runes = { /** @param item {Item} */ - parseRune: (item) => { + parseRune: async (item) => { if (item.itemid !== "minecraft:skull") throw new Error(`Unknown rune: ${item.itemid}:${item.damage}`) const runes = item.nbt.ExtraAttributes.runes; @@ -26,7 +26,7 @@ export const Runes = { }) runesFile[rune] = runeInfo; - const overlayProps = getOverlay(item); + const overlayProps = await getOverlay(item); if (overlayProps) { runeOverlaysFile.push({ type: "rune", diff --git a/.github/scripts/parsers/parse.mjs b/.github/scripts/parsers/parse.mjs index 51a4ffa..2bfe6a6 100644 --- a/.github/scripts/parsers/parse.mjs +++ b/.github/scripts/parsers/parse.mjs @@ -38,30 +38,30 @@ for (let file of fs.readdirSync("neu/items")) { if (specialItems.items.includes(data.internalname)) continue; if (specialItems.items.includes(attributes?.id)) continue; - post.push(() => { - Recipes.parse(data) + post.push(async () => { + await Recipes.parse(data) }) if (isEntity(file)) { - post.push(() => { - Mobs.parseMob(data); + post.push(async () => { + await Mobs.parseMob(data); }) } else { if (attributes.hasOwnProperty("attributes") && data.internalname.startsWith("ATTRIBUTE_SHARD_")) { - Attributes.parseAttribute(data) + await Attributes.parseAttribute(data) } else if (attributes.hasOwnProperty("runes")) { - Runes.parseRune(data); + await Runes.parseRune(data); } else if (attributes.hasOwnProperty("petInfo")) { data.pet = JSON.parse(attributes.petInfo.replaceAll("\\\"", "\"")); - Pets.parsePet(data); + await Pets.parsePet(data); } else if (data.displayname.match(/§.Enchanted Book/) && data.itemid === "minecraft:enchanted_book" && attributes.enchantments) { - Enchantments.parseEnchantments(data); + await Enchantments.parseEnchantments(data); } else if (isPotion(data)) { - Potions.parsePotions(data); + await Potions.parsePotions(data); } else if (data.internalname.includes(";")) { //console.log(file + " is a variant"); } else { - Mc1215.items.parseItem(data); + await Mc1215.items.parseItem(data); } } } @@ -95,7 +95,7 @@ function isPotion(data) { return false } -post.forEach((recipe) => recipe()) +await Promise.all(post.map(recipe => recipe())); fs.writeFileSync("cloudflare/shas.json", JSON.stringify({ "1_21_5": Mc1215.shas(), diff --git a/.github/scripts/utils/collection.mjs b/.github/scripts/utils/collection.mjs new file mode 100644 index 0000000..f618412 --- /dev/null +++ b/.github/scripts/utils/collection.mjs @@ -0,0 +1,44 @@ +const url = "https://api.hypixel.net/v2/resources/skyblock/collections"; + +let collectionCache = null; +let fetchPromise = null; + +export const isCollectionItem = async (name) => { + if (!collectionCache) { + if (!fetchPromise) { + fetchPromise = fetch(url) + .then((res) => { + if (!res.ok) throw new Error(`Error: ${res.status}`); + return res.json(); + }) + .then((data) => { + const cache = new Map(); + if (data.success && data.collections) { + for (const category of Object.values(data.collections)) { + if (!category.items) continue; + for (const [id, item] of Object.entries(category.items)) { + cache.set(id, id); + if (item.name) { + cache.set(item.name.toLowerCase(), id); + } + } + } + } + collectionCache = cache; + }) + .catch((err) => { + console.error("Failed to fetch Hypixel collections:", err); + fetchPromise = null; + }); + } + await fetchPromise; + } + + if (!collectionCache) return false; + return collectionCache.has(name) || collectionCache.has(name.toLowerCase()); +}; + +export const getCollectionId = (name) => { + if (!collectionCache) return null; + return collectionCache.get(name) || collectionCache.get(name.toLowerCase()); +}; \ No newline at end of file diff --git a/.github/scripts/utils/roman_numeral.mjs b/.github/scripts/utils/roman_numeral.mjs new file mode 100644 index 0000000..d4cb239 --- /dev/null +++ b/.github/scripts/utils/roman_numeral.mjs @@ -0,0 +1,16 @@ +export const romanToInt = (roman) => { + const romanMap = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 }; + let total = 0; + let prevValue = 0; + + for (let i = roman.length - 1; i >= 0; i--) { + const currentValue = romanMap[roman[i]]; + if (currentValue < prevValue) { + total -= currentValue; + } else { + total += currentValue; + } + prevValue = currentValue; + } + return total; +}; \ No newline at end of file From 2f89dbf796a3939cc6c6c089ae06613b2d905c6d Mon Sep 17 00:00:00 2001 From: J10a1n15 <45315647+j10a1n15@users.noreply.github.com> Date: Fri, 28 Aug 2026 15:35:12 +0200 Subject: [PATCH 2/3] refactor: way better async shit --- .github/scripts/parsers/1_21_5/attributes.mjs | 6 +- .../scripts/parsers/1_21_5/enchantments.mjs | 4 +- .../scripts/parsers/1_21_5/id_overlays.mjs | 8 +- .github/scripts/parsers/1_21_5/items.mjs | 4 +- .github/scripts/parsers/1_21_5/mobs.mjs | 4 +- .github/scripts/parsers/1_21_5/pets.mjs | 4 +- .github/scripts/parsers/1_21_5/potions.mjs | 4 +- .github/scripts/parsers/1_21_5/runes.mjs | 4 +- .github/scripts/parsers/parse.mjs | 108 ++++++++++-------- .github/scripts/utils/collection.mjs | 56 +++++---- 10 files changed, 104 insertions(+), 98 deletions(-) diff --git a/.github/scripts/parsers/1_21_5/attributes.mjs b/.github/scripts/parsers/1_21_5/attributes.mjs index c784a45..8ae8ad8 100644 --- a/.github/scripts/parsers/1_21_5/attributes.mjs +++ b/.github/scripts/parsers/1_21_5/attributes.mjs @@ -9,7 +9,7 @@ export const attributeIds = [] export const Attributes = { /** @param item {Item} */ - parseAttribute: async (item) => { + parseAttribute: (item) => { const originalItem = item item = structuredClone(item) @@ -62,12 +62,12 @@ export const Attributes = { attributesFile.push(attribute) - const overlayProps = await getOverlay(item); + const overlayProps = getOverlay(item); if (overlayProps) { attributeOverlaysFile.push({ type: "attribute", id: item.nbt.ExtraAttributes.id, - ...overlayProps(item), + ...overlayProps, }); } }, diff --git a/.github/scripts/parsers/1_21_5/enchantments.mjs b/.github/scripts/parsers/1_21_5/enchantments.mjs index e3e79cc..c18220b 100644 --- a/.github/scripts/parsers/1_21_5/enchantments.mjs +++ b/.github/scripts/parsers/1_21_5/enchantments.mjs @@ -8,7 +8,7 @@ export const enchantmentIds = [] export const Enchantments = { /** @param item {Item} */ - parseEnchantments: async (item) => { + parseEnchantments: (item) => { const originalItem = item item = structuredClone(item) @@ -55,7 +55,7 @@ export const Enchantments = { enchantmentFile[enchantId] = enchant - const overlayProps = await getOverlay(item); + const overlayProps = getOverlay(item); if (overlayProps) { enchantmentOverlaysFile.push({ type: "enchantment", diff --git a/.github/scripts/parsers/1_21_5/id_overlays.mjs b/.github/scripts/parsers/1_21_5/id_overlays.mjs index e88bd78..1ade572 100644 --- a/.github/scripts/parsers/1_21_5/id_overlays.mjs +++ b/.github/scripts/parsers/1_21_5/id_overlays.mjs @@ -16,7 +16,7 @@ const hotf = ["hotf", "heart of the forest", "heart of the forest tier"] const bossCollection = ["bonzo", "scarf", "the professor", "thorn", "livid", "sadan", "necron"] const skills = ["combat", "farming", "fishing", "mining", "foraging", "enchanting", "alchemy", "carpentry", "taming", "hunting", "duneoneering"] -const getRequirements = async (item) => { +const getRequirements = (item) => { if (!item.crafttext.startsWith("Requires")) { console.warn("Non Requirement crafttext found: " + item.crafttext); return undefined @@ -62,7 +62,7 @@ const getRequirements = async (item) => { name: name, level: levelNum }); - } else if (await isCollectionItem(name)) { + } else if (isCollectionItem(name)) { out.push({ type: "collection", name: name, @@ -88,10 +88,10 @@ const getRequirements = async (item) => { return out } -export const getOverlay = async (item) => { +export const getOverlay = (item) => { const overlay = cleanObject({ vanilla: item.vanilla ? true : undefined, - requirements: item.crafttext && item.crafttext !== "" ? await getRequirements(item) : undefined, // Add await here + requirements: item.crafttext && item.crafttext !== "" ? getRequirements(item) : undefined, wiki: getWiki(item), }); diff --git a/.github/scripts/parsers/1_21_5/items.mjs b/.github/scripts/parsers/1_21_5/items.mjs index d70d9f4..b83354d 100644 --- a/.github/scripts/parsers/1_21_5/items.mjs +++ b/.github/scripts/parsers/1_21_5/items.mjs @@ -133,13 +133,13 @@ export const buildItemStack = (item) => { export const Items = { /** @param item {Item} */ - parseItem: async (item) => { + parseItem: (item) => { const itemStack = buildItemStack(item); if (!itemStack) return; itemsFile.push(itemStack); - const overlayProps = await getOverlay(item); + const overlayProps = getOverlay(item); if (overlayProps) { itemOverlaysFile.push({ type: "item", diff --git a/.github/scripts/parsers/1_21_5/mobs.mjs b/.github/scripts/parsers/1_21_5/mobs.mjs index 44ce6d3..a155dbb 100644 --- a/.github/scripts/parsers/1_21_5/mobs.mjs +++ b/.github/scripts/parsers/1_21_5/mobs.mjs @@ -58,7 +58,7 @@ const parseDropAmountAndChance = (chanceStr, extraLines) => { export const Mobs = { /** @param item {Item} */ - parseMob: async (item) => { + parseMob: (item) => { const realId = item.internalname.replace("MAYOR_MONSTER", "MAYOR"); const [, realName, type] = item.displayname.match(/^§.(.*?)(?: \(([^)]+)\))?$/) || []; @@ -135,7 +135,7 @@ export const Mobs = { lootTables: lootTables.length === 0 ? undefined : lootTables, }; - const overlayProps = await getOverlay(item); + const overlayProps = getOverlay(item); if (overlayProps) { mobOverlaysFile.push({ type: "mob", diff --git a/.github/scripts/parsers/1_21_5/pets.mjs b/.github/scripts/parsers/1_21_5/pets.mjs index dc929b9..5b210fc 100644 --- a/.github/scripts/parsers/1_21_5/pets.mjs +++ b/.github/scripts/parsers/1_21_5/pets.mjs @@ -35,7 +35,7 @@ const getPetVariables = (pet, tier) => { export const Pets = { /** @param item {Item} */ - parsePet: async (item) => { + parsePet: (item) => { if (item.itemid !== "minecraft:skull") throw new Error(`Unknown pet: ${item.itemid}:${item.damage}`) const petId = item.pet.type @@ -99,7 +99,7 @@ export const Pets = { petsFile[petId] = data - const overlayProps = await getOverlay(item); + const overlayProps = getOverlay(item); if (overlayProps) { petOverlaysFile.push({ type: "pet", diff --git a/.github/scripts/parsers/1_21_5/potions.mjs b/.github/scripts/parsers/1_21_5/potions.mjs index 4f23746..665a21c 100644 --- a/.github/scripts/parsers/1_21_5/potions.mjs +++ b/.github/scripts/parsers/1_21_5/potions.mjs @@ -130,7 +130,7 @@ const parseLiteralLevel = (name) => stripFormatting(name).match(potionLevelPatte export const Potions = { /** @param item {Item} */ - parsePotions: async (item) => { + parsePotions: (item) => { const originalItem = item item = structuredClone(item) @@ -168,7 +168,7 @@ export const Potions = { potionFile[potionId] = potion - const overlayProps = await getOverlay(item); + const overlayProps = getOverlay(item); if (overlayProps) { potionOverlaysFile.push({ type: "potion", diff --git a/.github/scripts/parsers/1_21_5/runes.mjs b/.github/scripts/parsers/1_21_5/runes.mjs index 2a4cd79..e5d3c25 100644 --- a/.github/scripts/parsers/1_21_5/runes.mjs +++ b/.github/scripts/parsers/1_21_5/runes.mjs @@ -8,7 +8,7 @@ export const runeIds = [] export const Runes = { /** @param item {Item} */ - parseRune: async (item) => { + parseRune: (item) => { if (item.itemid !== "minecraft:skull") throw new Error(`Unknown rune: ${item.itemid}:${item.damage}`) const runes = item.nbt.ExtraAttributes.runes; @@ -28,7 +28,7 @@ export const Runes = { }) runesFile[rune] = runeInfo; - const overlayProps = await getOverlay(item); + const overlayProps = getOverlay(item); if (overlayProps) { runeOverlaysFile.push({ type: "rune", diff --git a/.github/scripts/parsers/parse.mjs b/.github/scripts/parsers/parse.mjs index 39bd51a..5386f93 100644 --- a/.github/scripts/parsers/parse.mjs +++ b/.github/scripts/parsers/parse.mjs @@ -10,6 +10,7 @@ import {Runes} from "./1_21_5/runes.mjs"; import {Enchantments} from "./1_21_5/enchantments.mjs"; import {Potions} from "./1_21_5/potions.mjs"; import {Attributes} from "./1_21_5/attributes.mjs"; +import {fetchCollections} from "../utils/collection.mjs"; const specialItems = JSON.parse(fs.readFileSync(".github/scripts/data/special_items.json", "utf-8")); const itemOverlayIndex = buildItemOverlayIndex(); @@ -26,57 +27,69 @@ const isEntity = (file) => { return false; } -const post = [] -for (let file of fs.readdirSync("neu/items")) { - if (!file.endsWith(".json")) { - console.error("[WARN] (Parse) Skipping non-json file found in items directory: " + file); - continue; - } - const itemId = file.slice(0, -".json".length); - const data = JSON.parse(fs.readFileSync(`./neu/items/${file}`, "utf-8")); - data.nbt = decodeLegacy(data.nbttag); - const itemOverlay = itemOverlayIndex.get(itemId); - if (itemOverlay) { - try { - data.itemOverlay = decodeModern(fs.readFileSync(itemOverlay.path, "utf-8")); - } catch (error) { - throw new Error(`Failed to parse item overlay ${itemOverlay.path}: ${error.message}`, {cause: error}); - } - } else { - console.warn(`[WARN] (Parse) Missing item SNBT overlay: ${itemId}`); - } +async function run() { + await fetchCollections() + + const post = [] - const attributes = data.nbt.ExtraAttributes; + for (let file of fs.readdirSync("neu/items")) { + if (!file.endsWith(".json")) { + console.error("[WARN] (Parse) Skipping non-json file found in items directory: " + file); + continue; + } + const itemId = file.slice(0, -".json".length); + const data = JSON.parse(fs.readFileSync(`./neu/items/${file}`, "utf-8")); + data.nbt = decodeLegacy(data.nbttag); + const itemOverlay = itemOverlayIndex.get(itemId); + if (itemOverlay) { + try { + data.itemOverlay = decodeModern(fs.readFileSync(itemOverlay.path, "utf-8")); + } catch (error) { + throw new Error(`Failed to parse item overlay ${itemOverlay.path}: ${error.message}`, {cause: error}); + } + } else { + console.warn(`[WARN] (Parse) Missing item SNBT overlay: ${itemId}`); + } - if (specialItems.items.includes(data.internalname)) continue; - if (specialItems.items.includes(attributes?.id)) continue; + const attributes = data.nbt.ExtraAttributes; - post.push(async () => { - await Recipes.parse(data) - }) + if (specialItems.items.includes(data.internalname)) continue; + if (specialItems.items.includes(attributes?.id)) continue; - if (isEntity(file)) { - post.push(async () => { - await Mobs.parseMob(data); + post.push(() => { + Recipes.parse(data) }) - } else { - if (attributes.hasOwnProperty("attributes") && data.internalname.startsWith("ATTRIBUTE_SHARD_")) { - await Attributes.parseAttribute(data) - } else if (attributes.hasOwnProperty("runes")) { - await Runes.parseRune(data); - } else if (attributes.hasOwnProperty("petInfo")) { - data.pet = JSON.parse(attributes.petInfo); - await Pets.parsePet(data); - } else if (data.displayname.match(/§.Enchanted Book/) && data.itemid === "minecraft:enchanted_book" && attributes.enchantments) { - await Enchantments.parseEnchantments(data); - } else if (isPotion(data)) { - await Potions.parsePotions(data); - } else if (data.internalname.includes(";")) { - //console.log(file + " is a variant"); + + if (isEntity(file)) { + post.push(async () => { + Mobs.parseMob(data); + }) } else { - await Mc1215.items.parseItem(data); + if (attributes.hasOwnProperty("attributes") && data.internalname.startsWith("ATTRIBUTE_SHARD_")) { + Attributes.parseAttribute(data) + } else if (attributes.hasOwnProperty("runes")) { + Runes.parseRune(data); + } else if (attributes.hasOwnProperty("petInfo")) { + data.pet = JSON.parse(attributes.petInfo); + Pets.parsePet(data); + } else if (data.displayname.match(/§.Enchanted Book/) && data.itemid === "minecraft:enchanted_book" && attributes.enchantments) { + Enchantments.parseEnchantments(data); + } else if (isPotion(data)) { + Potions.parsePotions(data); + } else if (data.internalname.includes(";")) { + //console.log(file + " is a variant"); + } else { + Mc1215.items.parseItem(data); + } } } + + post.forEach((recipe) => recipe()) + + fs.writeFileSync("cloudflare/shas.json", JSON.stringify({ + "1_21_5": Mc1215.shas(), + ...clone(), + }, null, 4)); } function isPotion(data) { @@ -108,13 +121,6 @@ function isPotion(data) { return false } -await Promise.all(post.map(recipe => recipe())); - -fs.writeFileSync("cloudflare/shas.json", JSON.stringify({ - "1_21_5": Mc1215.shas(), - ...clone(), -}, null, 4)); - function buildItemOverlayIndex() { const result = new Map(); const overlayRoot = "neu/itemsOverlay"; @@ -146,3 +152,5 @@ function buildItemOverlayIndex() { return result; } + +await run(); diff --git a/.github/scripts/utils/collection.mjs b/.github/scripts/utils/collection.mjs index f618412..951b67c 100644 --- a/.github/scripts/utils/collection.mjs +++ b/.github/scripts/utils/collection.mjs @@ -1,39 +1,37 @@ const url = "https://api.hypixel.net/v2/resources/skyblock/collections"; let collectionCache = null; -let fetchPromise = null; -export const isCollectionItem = async (name) => { - if (!collectionCache) { - if (!fetchPromise) { - fetchPromise = fetch(url) - .then((res) => { - if (!res.ok) throw new Error(`Error: ${res.status}`); - return res.json(); - }) - .then((data) => { - const cache = new Map(); - if (data.success && data.collections) { - for (const category of Object.values(data.collections)) { - if (!category.items) continue; - for (const [id, item] of Object.entries(category.items)) { - cache.set(id, id); - if (item.name) { - cache.set(item.name.toLowerCase(), id); - } - } +export async function fetchCollections() { + if (collectionCache) return; + + await fetch(url) + .then((res) => { + if (!res.ok) throw new Error(`Error: ${res.status}`); + return res.json(); + }) + .then((data) => { + const cache = new Map(); + if (data.success && data.collections) { + for (const category of Object.values(data.collections)) { + if (!category.items) continue; + for (const [id, item] of Object.entries(category.items)) { + cache.set(id, id); + if (item.name) { + cache.set(item.name.toLowerCase(), id); } } - collectionCache = cache; - }) - .catch((err) => { - console.error("Failed to fetch Hypixel collections:", err); - fetchPromise = null; - }); - } - await fetchPromise; - } + } + } + console.info(`Loaded ${cache.size} collection items.`); + collectionCache = cache; + }) + .catch((err) => { + console.error("Failed to fetch Hypixel collections:", err); + }); +} +export const isCollectionItem = (name) => { if (!collectionCache) return false; return collectionCache.has(name) || collectionCache.has(name.toLowerCase()); }; From a1412bdcd2fb5d139e25f36f9ba6f63e7e931c99 Mon Sep 17 00:00:00 2001 From: J10a1n15 <45315647+j10a1n15@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:54:05 +0200 Subject: [PATCH 3/3] feat: parse slayer_req --- .../scripts/parsers/1_21_5/id_overlays.mjs | 134 +++++++++--------- 1 file changed, 69 insertions(+), 65 deletions(-) diff --git a/.github/scripts/parsers/1_21_5/id_overlays.mjs b/.github/scripts/parsers/1_21_5/id_overlays.mjs index 1ade572..896ef58 100644 --- a/.github/scripts/parsers/1_21_5/id_overlays.mjs +++ b/.github/scripts/parsers/1_21_5/id_overlays.mjs @@ -11,87 +11,91 @@ const getWiki = (item) => { }); }; -const hotm = ["hotm", "heart of the mountain", "heart of the mountain tier"] -const hotf = ["hotf", "heart of the forest", "heart of the forest tier"] -const bossCollection = ["bonzo", "scarf", "the professor", "thorn", "livid", "sadan", "necron"] -const skills = ["combat", "farming", "fishing", "mining", "foraging", "enchanting", "alchemy", "carpentry", "taming", "hunting", "duneoneering"] +const hotm = ["hotm", "heart of the mountain", "heart of the mountain tier"]; +const hotf = ["hotf", "heart of the forest", "heart of the forest tier"]; +const bossCollection = ["bonzo", "scarf", "the professor", "thorn", "livid", "sadan", "necron"]; +const skills = ["combat", "farming", "fishing", "mining", "foraging", "enchanting", "alchemy", "carpentry", "taming", "hunting", "duneoneering"]; +const slayers = ["zombie", "spider", "wolf", "enderman", "blaze", "vampire"]; -const getRequirements = (item) => { - if (!item.crafttext.startsWith("Requires")) { - console.warn("Non Requirement crafttext found: " + item.crafttext); - return undefined - } - const stringReqs = item.crafttext.replace(/^(Requires:?)/, "").trim().split(" & "); +const parseLevel = (levelStr) => !isNaN(levelStr) ? parseInt(levelStr, 10) : romanToInt(levelStr); +const getRequirements = (item) => { const out = []; - for (let req of stringReqs) { - const match = req.match(/^(.*?)\s+([0-9]+|[IVXLCDM]+)$/); - - if (match) { - const name = match[1]; - const levelStr = match[2]; - - const levelNum = !isNaN(levelStr) ? parseInt(levelStr, 10) : romanToInt(levelStr); - - if (name.toLowerCase().endsWith("slayer")) { - out.push({ - type: "slayer", - name: name.substring(0, name.length - 6).trim(), - level: levelNum - }) - } else if (hotm.includes(name.toLowerCase())) { - out.push({ - type: "hotm", - level: levelNum - }); - } else if (hotf.includes(name.toLowerCase())) { - out.push({ - type: "hotf", - level: levelNum - }); - } else if (bossCollection.includes(name.toLowerCase())) { - out.push({ - type: "bossCollection", - name: name, - level: levelNum - }); - } else if (skills.includes(name.toLowerCase())) { - out.push({ - type: "skill", - name: name, - level: levelNum - }); - } else if (isCollectionItem(name)) { - out.push({ - type: "collection", - name: name, - id: getCollectionId(name), - level: levelNum - }) - } else { - out.push({ - type: "unknown", - name: name, - level: levelNum - }); + if (item.crafttext) { + if (item.crafttext.startsWith("Requires")) { + const stringReqs = item.crafttext.replace(/^(Requires:?)/, "").trim().split(" & "); + + for (const req of stringReqs) { + const match = req.match(/^(.*?)\s+([0-9]+|[IVXLCDM]+)$/); + + if (match) { + const [, name, levelStr] = match; + const lowerName = name.toLowerCase(); + + const reqData = { level: parseLevel(levelStr) }; + + if (lowerName.endsWith("slayer")) { + reqData.type = "slayer"; + reqData.name = name.substring(0, name.length - 6).trim(); + } else if (hotm.includes(lowerName)) { + reqData.type = "hotm"; + } else if (hotf.includes(lowerName)) { + reqData.type = "hotf"; + } else if (bossCollection.includes(lowerName) || skills.includes(lowerName)) { + reqData.type = bossCollection.includes(lowerName) ? "bossCollection" : "skill"; + reqData.name = name; + } else if (isCollectionItem(name)) { + reqData.type = "collection"; + reqData.name = name; + reqData.id = getCollectionId(name); + } else { + reqData.type = "unknown"; + reqData.name = name; + } + + out.push(reqData); + } else { + out.push({ type: "unknown", name: req, level: null }); + } } } else { + console.warn("Non Requirement crafttext found: " + item.crafttext); + } + } + + if (item.slayer_req) { + const [nameStr, levelStr] = item.slayer_req.split("_"); + const lowerName = nameStr?.toLowerCase(); + + if (slayers.includes(lowerName)) { out.push({ - type: "unknown", - name: req, - level: null + type: "slayer", + name: lowerName.charAt(0).toUpperCase() + lowerName.slice(1), + level: parseLevel(levelStr) }); } } - return out + if (out.length === 0) return undefined; + + const uniqueReqs = new Map(); + for (const req of out) { + const key = `${req.type}_${req.name || ''}`; + const existing = uniqueReqs.get(key); + + if (!existing || (req.level !== null && existing.level !== null && req.level > existing.level)) { + uniqueReqs.set(key, req); + } + } + + return Array.from(uniqueReqs.values()); } export const getOverlay = (item) => { const overlay = cleanObject({ vanilla: item.vanilla ? true : undefined, - requirements: item.crafttext && item.crafttext !== "" ? getRequirements(item) : undefined, + requirements: getRequirements(item), wiki: getWiki(item), });