Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/scripts/parsers/1_21_5/attributes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const Attributes = {
attributeOverlaysFile.push({
type: "attribute",
id: item.nbt.ExtraAttributes.id,
...getOverlay(item),
...overlayProps,
});
}
},
Expand Down
84 changes: 84 additions & 0 deletions .github/scripts/parsers/1_21_5/id_overlays.mjs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,9 +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 slayers = ["zombie", "spider", "wolf", "enderman", "blaze", "vampire"];

const parseLevel = (levelStr) => !isNaN(levelStr) ? parseInt(levelStr, 10) : romanToInt(levelStr);

const getRequirements = (item) => {
const out = [];

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: "slayer",
name: lowerName.charAt(0).toUpperCase() + lowerName.slice(1),
level: parseLevel(levelStr)
});
}
}

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: getRequirements(item),
wiki: getWiki(item),
});

Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/parsers/1_21_5/items.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export const Items = {
itemOverlaysFile.push({
type: "item",
id: itemStack.components["minecraft:custom_data"].id,
...getOverlay(item),
...overlayProps,
});
}
},
Expand Down
106 changes: 57 additions & 49 deletions .github/scripts/parsers/parse.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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(() => {
Recipes.parse(data)
})
if (specialItems.items.includes(data.internalname)) continue;
if (specialItems.items.includes(attributes?.id)) continue;

if (isEntity(file)) {
post.push(() => {
Mobs.parseMob(data);
Recipes.parse(data)
})
} else {
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");

if (isEntity(file)) {
post.push(async () => {
Mobs.parseMob(data);
})
} else {
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) {
Expand Down Expand Up @@ -108,13 +121,6 @@ function isPotion(data) {
return false
}

post.forEach((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";
Expand Down Expand Up @@ -146,3 +152,5 @@ function buildItemOverlayIndex() {

return result;
}

await run();
42 changes: 42 additions & 0 deletions .github/scripts/utils/collection.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const url = "https://api.hypixel.net/v2/resources/skyblock/collections";

let collectionCache = null;

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);
}
}
}
}
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());
};

export const getCollectionId = (name) => {
if (!collectionCache) return null;
return collectionCache.get(name) || collectionCache.get(name.toLowerCase());
};
16 changes: 16 additions & 0 deletions .github/scripts/utils/roman_numeral.mjs
Original file line number Diff line number Diff line change
@@ -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;
};