aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintignore5
-rw-r--r--assets/minecraft_font.ttfbin0 -> 15700 bytes
-rw-r--r--src/commands/moulberry-bush/neuRepo.ts153
-rw-r--r--src/lib/common/util/Minecraft.ts234
-rw-r--r--src/lib/common/util/Minecraft_Test.ts86
-rw-r--r--test.js365
-rw-r--r--test.pngbin0 -> 41015 bytes
-rw-r--r--tooltips.nnb118
-rw-r--r--tsconfig.eslint.json15
-rw-r--r--tsconfig.json13
10 files changed, 865 insertions, 124 deletions
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..c531fde
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,5 @@
+dist
+.yarn
+node_modules
+*.nnb
+tooltips* \ No newline at end of file
diff --git a/assets/minecraft_font.ttf b/assets/minecraft_font.ttf
new file mode 100644
index 0000000..61b4610
--- /dev/null
+++ b/assets/minecraft_font.ttf
Binary files differ
diff --git a/src/commands/moulberry-bush/neuRepo.ts b/src/commands/moulberry-bush/neuRepo.ts
index 24f83ad..9d76810 100644
--- a/src/commands/moulberry-bush/neuRepo.ts
+++ b/src/commands/moulberry-bush/neuRepo.ts
@@ -1,5 +1,16 @@
import { BushCommand, clientSendAndPermCheck, type ArgType, type CommandMessage, type SlashMessage } from '#lib';
-import { ApplicationCommandOptionType, AutocompleteInteraction, CacheType, PermissionFlagsBits } from 'discord.js';
+import canvas from 'canvas';
+import {
+ ApplicationCommandOptionType,
+ AttachmentBuilder,
+ AutocompleteInteraction,
+ CacheType,
+ PermissionFlagsBits
+} from 'discord.js';
+import { dirname, join } from 'path';
+import tinycolor from 'tinycolor2';
+import { fileURLToPath } from 'url';
+import { formattingInfo, RawNeuItem } from '../../lib/common/util/Minecraft.js';
export default class NeuRepoCommand extends BushCommand {
public static items: { name: string; id: string }[] = [];
@@ -34,14 +45,148 @@ export default class NeuRepoCommand extends BushCommand {
],
slash: true,
clientPermissions: (m) => clientSendAndPermCheck(m, [PermissionFlagsBits.EmbedLinks], true),
- userPermissions: []
+ userPermissions: [],
+ ownerOnly: true
});
}
public override async exec(
message: CommandMessage | SlashMessage,
args: { item: ArgType<'string'> /* dangerous: ArgType<'flag'> */ }
- ) {}
+ ) {
+ const itemPath = join(import.meta.url, '..', '..', '..', '..', '..', 'neu-item-repo-dangerous', 'items', `${args.item}.json`);
+ const item = (await import(itemPath, { assert: { type: 'json' } })).default as RawNeuItem;
- public override async autocomplete(interaction: AutocompleteInteraction<CacheType>) {}
+ const toolTip = this.toolTip(item);
+
+ return message.util.reply({
+ files: [new AttachmentBuilder(toolTip, { name: `${item.internalname}.png`, description: item.displayname })]
+ });
+ }
+
+ public toolTip(item: RawNeuItem): Buffer {
+ canvas.registerFont(join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..', 'assets', 'Faithful.ttf'), {
+ family: 'Faithful'
+ });
+
+ const background = '#100010';
+
+ const width = 250;
+ const height = 250;
+ const scale = 10;
+
+ const itemRender = canvas.createCanvas(width, height),
+ ctx = itemRender.getContext('2d');
+
+ ctx.globalAlpha = 0.94;
+ ctx.fillStyle = background;
+
+ // top outside
+ ctx.fillRect(scale, 0, width - 2 * scale, scale);
+
+ // bottom outside
+ ctx.fillRect(scale, height - scale, width - 2 * scale, scale);
+
+ // left outside
+ ctx.fillRect(0, scale, scale, height - 2 * scale);
+
+ // right outside
+ ctx.fillRect(width - scale, scale, scale, height - 2 * scale);
+
+ // middle
+ ctx.fillRect(2 * scale, 2 * scale, width - 4 * scale, height - 4 * scale);
+
+ ctx.globalAlpha = 0.78;
+
+ const borderColorStart = parseInt(new tinycolor(this.getPrimaryColour(item.displayname)).toHex(), 16);
+ const borderColorEnd = ((borderColorStart & 0xfefefe) >> 1) | (borderColorStart & 0xff000000);
+
+ const borderColorStartStr = `#${borderColorStart.toString(16)}`;
+ const borderColorEndStr = `#${borderColorEnd.toString(16)}`;
+
+ ctx.fillStyle = borderColorStartStr;
+
+ // top highlight
+ ctx.fillRect(scale, scale, width - 2 * scale, scale);
+
+ // left highlight
+ ctx.fillRect(scale, 2 * scale, scale, height - 3 * scale);
+
+ // bottom highlight
+ {
+ const x = 2 * scale,
+ y = height - 2 * scale,
+ w = width - 3 * scale,
+ h = scale;
+ const gradient = ctx.createLinearGradient(x, y, x + w, y + h);
+ gradient.addColorStop(0, borderColorStartStr);
+ gradient.addColorStop(1, borderColorEndStr);
+ ctx.fillStyle = gradient;
+
+ ctx.fillRect(x, y, w, h);
+ }
+
+ // right highlight
+ {
+ const x = width - 2 * scale,
+ y = 2 * scale,
+ w = scale,
+ h = height - 4 * scale;
+ const gradient = ctx.createLinearGradient(x, y, x + w, y + h);
+ gradient.addColorStop(0, borderColorStartStr);
+ gradient.addColorStop(1, borderColorEndStr);
+ ctx.fillStyle = gradient;
+
+ ctx.fillRect(x, y, w, h);
+ }
+
+ item.displayname.split('');
+
+ return itemRender.toBuffer();
+ }
+
+ // stolen from NEU and modified
+ public getPrimaryColourCode(displayname: string): code {
+ let lastColourCode = -99;
+ let currentColour = 0;
+ const mostCommon = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ for (let i = 0; i < displayname.length; i++) {
+ const c = displayname.charAt(i);
+ if (c === '\u00A7') {
+ lastColourCode = i;
+ } else if (lastColourCode === i - 1) {
+ const colIndex = '0123456789abcdef'.indexOf(c);
+ if (colIndex >= 0) {
+ currentColour = colIndex;
+ } else {
+ currentColour = 0;
+ }
+ } else if ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.indexOf(c) >= 0) {
+ if (currentColour > 0) {
+ mostCommon[currentColour] = mostCommon[currentColour]++;
+ }
+ }
+ }
+ let mostCommonCount = 0;
+ for (let index = 0; index < mostCommon.length; index++) {
+ if (mostCommon[index] > mostCommonCount) {
+ mostCommonCount = mostCommon[index];
+ currentColour = index;
+ }
+ }
+
+ return <code>'0123456789abcdef'.charAt(currentColour);
+ }
+
+ // stolen from NEU and modified
+ public getPrimaryColour(displayname: string) {
+ const code = this.getPrimaryColourCode(displayname);
+ return formattingInfo[`§${code}`].foregroundDarker;
+ }
+
+ public override async autocomplete(interaction: AutocompleteInteraction<CacheType>) {
+ return interaction.respond([{ name: 'Blazetekk™ Ham Radio', value: 'BLAZETEKK_HAM_RADIO' }]);
+ }
}
+
+type code = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f';
diff --git a/src/lib/common/util/Minecraft.ts b/src/lib/common/util/Minecraft.ts
index b4b013f..a12ebf2 100644
--- a/src/lib/common/util/Minecraft.ts
+++ b/src/lib/common/util/Minecraft.ts
@@ -1,6 +1,5 @@
import { Byte, Int, parse } from '@ironm00n/nbt-ts';
import { BitField } from 'discord.js';
-import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
@@ -34,22 +33,118 @@ export enum FormattingCodes {
// https://minecraft.fandom.com/wiki/Formatting_codes
export const formattingInfo = {
- [FormattingCodes.Black]: { foreground: '#000000', background: '#000000', ansi: '\u001b[0;30m' },
- [FormattingCodes.DarkBlue]: { foreground: '#0000AA', background: '#00002A', ansi: '\u001b[0;34m' },
- [FormattingCodes.DarkGreen]: { foreground: '#00AA00', background: '#002A00', ansi: '\u001b[0;32m' },
- [FormattingCodes.DarkAqua]: { foreground: '#00AAAA', background: '#002A2A', ansi: '\u001b[0;36m' },
- [FormattingCodes.DarkRed]: { foreground: '#AA0000', background: '#2A0000', ansi: '\u001b[0;31m' },
- [FormattingCodes.DarkPurple]: { foreground: '#AA00AA', background: '#2A002A', ansi: '\u001b[0;35m' },
- [FormattingCodes.Gold]: { foreground: '#FFAA00', background: '#2A2A00', ansi: '\u001b[0;33m' },
- [FormattingCodes.Gray]: { foreground: '#AAAAAA', background: '#2A2A2A', ansi: '\u001b[0;37m' },
- [FormattingCodes.DarkGray]: { foreground: '#555555', background: '#151515', ansi: '\u001b[0;90m' },
- [FormattingCodes.Blue]: { foreground: '#5555FF', background: '#15153F', ansi: '\u001b[0;94m' },
- [FormattingCodes.Green]: { foreground: '#55FF55', background: '#153F15', ansi: '\u001b[0;92m' },
- [FormattingCodes.Aqua]: { foreground: '#55FFFF', background: '#153F3F', ansi: '\u001b[0;96m' },
- [FormattingCodes.Red]: { foreground: '#FF5555', background: '#3F1515', ansi: '\u001b[0;91m' },
- [FormattingCodes.LightPurple]: { foreground: '#FF55FF', background: '#3F153F', ansi: '\u001b[0;95m' },
- [FormattingCodes.Yellow]: { foreground: '#FFFF55', background: '#3F3F15', ansi: '\u001b[0;93m' },
- [FormattingCodes.White]: { foreground: '#FFFFFF', background: '#3F3F3F', ansi: '\u001b[0;97m' },
+ [FormattingCodes.Black]: {
+ foreground: 'rgb(0, 0, 0)',
+ foregroundDarker: 'rgb(0, 0, 0)',
+ background: 'rgb(0, 0, 0)',
+ backgroundDarker: 'rgb(0, 0, 0)',
+ ansi: '\u001b[0;30m'
+ },
+ [FormattingCodes.DarkBlue]: {
+ foreground: 'rgb(0, 0, 170)',
+ foregroundDarker: 'rgb(0, 0, 118)',
+ background: 'rgb(0, 0, 42)',
+ backgroundDarker: 'rgb(0, 0, 29)',
+ ansi: '\u001b[0;34m'
+ },
+ [FormattingCodes.DarkGreen]: {
+ foreground: 'rgb(0, 170, 0)',
+ foregroundDarker: 'rgb(0, 118, 0)',
+ background: 'rgb(0, 42, 0)',
+ backgroundDarker: 'rgb(0, 29, 0)',
+ ansi: '\u001b[0;32m'
+ },
+ [FormattingCodes.DarkAqua]: {
+ foreground: 'rgb(0, 170, 170)',
+ foregroundDarker: 'rgb(0, 118, 118)',
+ background: 'rgb(0, 42, 42)',
+ backgroundDarker: 'rgb(0, 29, 29)',
+ ansi: '\u001b[0;36m'
+ },
+ [FormattingCodes.DarkRed]: {
+ foreground: 'rgb(170, 0, 0)',
+ foregroundDarker: 'rgb(118, 0, 0)',
+ background: 'rgb(42, 0, 0)',
+ backgroundDarker: 'rgb(29, 0, 0)',
+ ansi: '\u001b[0;31m'
+ },
+ [FormattingCodes.DarkPurple]: {
+ foreground: 'rgb(170, 0, 170)',
+ foregroundDarker: 'rgb(118, 0, 118)',
+ background: 'rgb(42, 0, 42)',
+ backgroundDarker: 'rgb(29, 0, 29)',
+ ansi: '\u001b[0;35m'
+ },
+ [FormattingCodes.Gold]: {
+ foreground: 'rgb(255, 170, 0)',
+ foregroundDarker: 'rgb(178, 118, 0)',
+ background: 'rgb(42, 42, 0)',
+ backgroundDarker: 'rgb(29, 29, 0)',
+ ansi: '\u001b[0;33m'
+ },
+ [FormattingCodes.Gray]: {
+ foreground: 'rgb(170, 170, 170)',
+ foregroundDarker: 'rgb(118, 118, 118)',
+ background: 'rgb(42, 42, 42)',
+ backgroundDarker: 'rgb(29, 29, 29)',
+ ansi: '\u001b[0;37m'
+ },
+ [FormattingCodes.DarkGray]: {
+ foreground: 'rgb(85, 85, 85)',
+ foregroundDarker: 'rgb(59, 59, 59)',
+ background: 'rgb(21, 21, 21)',
+ backgroundDarker: 'rgb(14, 14, 14)',
+ ansi: '\u001b[0;90m'
+ },
+ [FormattingCodes.Blue]: {
+ foreground: 'rgb(85, 85, 255)',
+ foregroundDarker: 'rgb(59, 59, 178)',
+ background: 'rgb(21, 21, 63)',
+ backgroundDarker: 'rgb(14, 14, 44)',
+ ansi: '\u001b[0;94m'
+ },
+ [FormattingCodes.Green]: {
+ foreground: 'rgb(85, 255, 85)',
+ foregroundDarker: 'rgb(59, 178, 59)',
+ background: 'rgb(21, 63, 21)',
+ backgroundDarker: 'rgb(14, 44, 14)',
+ ansi: '\u001b[0;92m'
+ },
+ [FormattingCodes.Aqua]: {
+ foreground: 'rgb(85, 255, 255)',
+ foregroundDarker: 'rgb(59, 178, 178)',
+ background: 'rgb(21, 63, 63)',
+ backgroundDarker: 'rgb(14, 44, 44)',
+ ansi: '\u001b[0;96m'
+ },
+ [FormattingCodes.Red]: {
+ foreground: 'rgb(255, 85, 85)',
+ foregroundDarker: 'rgb(178, 59, 59)',
+ background: 'rgb(63, 21, 21)',
+ backgroundDarker: 'rgb(44, 14, 14)',
+ ansi: '\u001b[0;91m'
+ },
+ [FormattingCodes.LightPurple]: {
+ foreground: 'rgb(255, 85, 255)',
+ foregroundDarker: 'rgb(178, 59, 178)',
+ background: 'rgb(63, 21, 63)',
+ backgroundDarker: 'rgb(44, 14, 44)',
+ ansi: '\u001b[0;95m'
+ },
+ [FormattingCodes.Yellow]: {
+ foreground: 'rgb(255, 255, 85)',
+ foregroundDarker: 'rgb(178, 178, 59)',
+ background: 'rgb(63, 63, 21)',
+ backgroundDarker: 'rgb(44, 44, 14)',
+ ansi: '\u001b[0;93m'
+ },
+ [FormattingCodes.White]: {
+ foreground: 'rgb(255, 255, 255)',
+ foregroundDarker: 'rgb(178, 178, 178)',
+ background: 'rgb(63, 63, 63)',
+ backgroundDarker: 'rgb(44, 44, 44)',
+ ansi: '\u001b[0;97m'
+ },
[FormattingCodes.Obfuscated]: { ansi: '\u001b[8m' },
[FormattingCodes.Bold]: { ansi: '\u001b[1m' },
@@ -68,7 +163,7 @@ export type SbRecipe = {
};
export type InfoType = 'WIKI_URL' | '';
-type Slayer = `${'WOLF' | 'BLAZE' | 'EMAN'}_${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
+export type Slayer = `${'WOLF' | 'BLAZE' | 'EMAN'}_${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;
export interface RawNeuItem {
itemid: McItemId;
@@ -128,87 +223,8 @@ export function removeMCFormatting(str: string) {
}
const repo = path.join(__dirname, '..', '..', '..', '..', '..', 'neu-item-repo-dangerous');
-const itemPath = path.join(repo, 'items');
-const items = await fs.readdir(itemPath);
-
-// for (let i = 0; i < 5; i++) {
-for (const path_ of items) {
- // const randomItem = items[Math.floor(Math.random() * items.length)];
- // console.log(randomItem);
- const item = (await import(path.join(itemPath, /* randomItem */ path_), { assert: { type: 'json' } })).default as RawNeuItem;
- if (/.*?((_MONSTER)|(_NPC)|(_ANIMAL)|(_MINIBOSS)|(_BOSS)|(_SC))$/.test(item.internalname)) continue;
- if (!/.*?;[0-5]$/.test(item.internalname)) continue;
- /* console.log(path_);
- console.dir(item, { depth: Infinity }); */
-
- /* console.log('==========='); */
- const nbt = parse(item.nbttag) as NbtTag;
-
- if (nbt?.SkullOwner?.Properties?.textures?.[0]?.Value) {
- nbt.SkullOwner.Properties.textures[0].Value = parse(
- Buffer.from(nbt.SkullOwner.Properties.textures[0].Value, 'base64').toString('utf-8')
- ) as string;
- }
-
- if (nbt.ExtraAttributes?.petInfo) {
- nbt.ExtraAttributes.petInfo = JSON.parse(nbt.ExtraAttributes.petInfo as any as string);
- }
- // delete nbt.display?.Lore;
-
- console.dir(nbt, { depth: Infinity });
- console.log('===========');
-
- /* if (nbt?.display && nbt.display.Name !== item.displayname)
- console.log(`${path_} display name mismatch: ${mcToAnsi(nbt.display.Name)} != ${mcToAnsi(item.displayname)}`);
-
- if (nbt?.ExtraAttributes && nbt?.ExtraAttributes.id !== item.internalname)
- console.log(`${path_} internal name mismatch: ${mcToAnsi(nbt?.ExtraAttributes.id)} != ${mcToAnsi(item.internalname)}`); */
-
- /* console.log('===========');
-
- console.log(mcToAnsi(item.displayname));
- console.log(item.lore.map((l) => mcToAnsi(l)).join('\n')); */
-
- /* const keys = [
- 'itemid',
- 'displayname',
- 'nbttag',
- 'damage',
- 'lore',
- 'recipe',
- 'internalname',
- 'modver',
- 'infoType',
- 'info',
- 'crafttext',
- 'vanilla',
- 'useneucraft',
- 'slayer_req',
- 'clickcommand',
- 'x',
- 'y',
- 'z',
- 'island',
- 'recipes',
- 'parent',
- 'noseal'
- ];
-
- Object.keys(item).forEach((k) => {
- if (!keys.includes(k)) throw new Error(`Unknown key: ${k}`);
- });
-
- if (
- 'slayer_req' in item &&
- !new Array(10).flatMap((_, i) => ['WOLF', 'BLAZE', 'EMAN'].map((e) => e + (i + 1)).includes(item.slayer_req!))
- )
- throw new Error(`Unknown slayer req: ${item.slayer_req!}`); */
-
- /* console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-'); */
-}
-
-interface NbtTag {
+export interface NbtTag {
overrideMeta?: Byte;
Unbreakable?: Int;
ench?: string[];
@@ -218,22 +234,22 @@ interface NbtTag {
ExtraAttributes?: ExtraAttributes;
}
-interface SkullOwner {
+export interface SkullOwner {
Id?: string;
Properties?: {
textures?: { Value?: string }[];
};
}
-interface NbtTagDisplay {
+export interface NbtTagDisplay {
Lore?: string[];
color?: Int;
Name?: string;
}
-type RuneId = string;
+export type RuneId = string;
-interface ExtraAttributes {
+export interface ExtraAttributes {
originTag?: Origin;
id?: string;
generator_tier?: Int;
@@ -244,7 +260,7 @@ interface ExtraAttributes {
petInfo?: PetInfo;
}
-interface PetInfo {
+export interface PetInfo {
type: 'ZOMBIE';
active: boolean;
exp: number;
@@ -253,7 +269,7 @@ interface PetInfo {
candyUsed: number;
}
-type Origin = 'SHOP_PURCHASE';
+export type Origin = 'SHOP_PURCHASE';
const neuConstantsPath = path.join(repo, 'constants');
const neuPetsPath = path.join(neuConstantsPath, 'pets.json');
@@ -261,14 +277,14 @@ const neuPets = (await import(neuPetsPath, { assert: { type: 'json' } })) as Pet
const neuPetNumsPath = path.join(neuConstantsPath, 'petnums.json');
const neuPetNums = (await import(neuPetNumsPath, { assert: { type: 'json' } })) as PetNums;
-interface PetsConstants {
+export interface PetsConstants {
pet_rarity_offset: Record<string, number>;
pet_levels: number[];
custom_pet_leveling: Record<string, { type: number; pet_levels: number[]; max_level: number }>;
pet_types: Record<string, string>;
}
-interface PetNums {
+export interface PetNums {
[key: string]: {
[key: string]: {
'1': {
@@ -284,7 +300,7 @@ interface PetNums {
};
}
-class NeuItem {
+export class NeuItem {
public itemId: McItemId;
public displayName: string;
public nbtTag: NbtTag;
@@ -315,6 +331,8 @@ class NeuItem {
const curve = petInfoTier?.stats_levelling_curve?.split(';');
+ // todo: finish copying from neu
+
const minStatsLevel = parseInt(curve?.[0] ?? '0');
const maxStatsLevel = parseInt(curve?.[0] ?? '100');
@@ -323,7 +341,7 @@ class NeuItem {
}
}
-function mcToAnsi(str: string) {
+export function mcToAnsi(str: string) {
for (const format in formattingInfo) {
str = str.replaceAll(format, formattingInfo[format as keyof typeof formattingInfo].ansi);
}
diff --git a/src/lib/common/util/Minecraft_Test.ts b/src/lib/common/util/Minecraft_Test.ts
new file mode 100644
index 0000000..26ca648
--- /dev/null
+++ b/src/lib/common/util/Minecraft_Test.ts
@@ -0,0 +1,86 @@
+import fs from 'fs/promises';
+import path from 'path';
+import { fileURLToPath } from 'url';
+import { mcToAnsi, RawNeuItem } from './Minecraft.js';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const repo = path.join(__dirname, '..', '..', '..', '..', '..', 'neu-item-repo-dangerous');
+const itemPath = path.join(repo, 'items');
+const items = await fs.readdir(itemPath);
+
+// for (let i = 0; i < 5; i++) {
+for (const path_ of items) {
+ // const randomItem = items[Math.floor(Math.random() * items.length)];
+ // console.log(randomItem);
+ const item = (await import(path.join(itemPath, /* randomItem */ path_), { assert: { type: 'json' } })).default as RawNeuItem;
+ if (/.*?((_MONSTER)|(_NPC)|(_ANIMAL)|(_MINIBOSS)|(_BOSS)|(_SC))$/.test(item.internalname)) continue;
+ if (!/.*?;[0-5]$/.test(item.internalname)) continue;
+ /* console.log(path_);
+ console.dir(item, { depth: Infinity }); */
+
+ /* console.log('==========='); */
+ // const nbt = parse(item.nbttag) as NbtTag;
+
+ // if (nbt?.SkullOwner?.Properties?.textures?.[0]?.Value) {
+ // nbt.SkullOwner.Properties.textures[0].Value = parse(
+ // Buffer.from(nbt.SkullOwner.Properties.textures[0].Value, 'base64').toString('utf-8')
+ // ) as string;
+ // }
+
+ // if (nbt.ExtraAttributes?.petInfo) {
+ // nbt.ExtraAttributes.petInfo = JSON.parse(nbt.ExtraAttributes.petInfo as any as string);
+ // }
+
+ // delete nbt.display?.Lore;
+
+ // console.dir(nbt, { depth: Infinity });
+ // console.log('===========');
+
+ /* if (nbt?.display && nbt.display.Name !== item.displayname)
+ console.log(`${path_} display name mismatch: ${mcToAnsi(nbt.display.Name)} != ${mcToAnsi(item.displayname)}`);
+
+ if (nbt?.ExtraAttributes && nbt?.ExtraAttributes.id !== item.internalname)
+ console.log(`${path_} internal name mismatch: ${mcToAnsi(nbt?.ExtraAttributes.id)} != ${mcToAnsi(item.internalname)}`); */
+
+ // console.log('===========');
+
+ console.log(mcToAnsi(item.displayname));
+ console.log(item.lore.map((l) => mcToAnsi(l)).join('\n'));
+
+ /* const keys = [
+ 'itemid',
+ 'displayname',
+ 'nbttag',
+ 'damage',
+ 'lore',
+ 'recipe',
+ 'internalname',
+ 'modver',
+ 'infoType',
+ 'info',
+ 'crafttext',
+ 'vanilla',
+ 'useneucraft',
+ 'slayer_req',
+ 'clickcommand',
+ 'x',
+ 'y',
+ 'z',
+ 'island',
+ 'recipes',
+ 'parent',
+ 'noseal'
+ ];
+
+ Object.keys(item).forEach((k) => {
+ if (!keys.includes(k)) throw new Error(`Unknown key: ${k}`);
+ });
+
+ if (
+ 'slayer_req' in item &&
+ !new Array(10).flatMap((_, i) => ['WOLF', 'BLAZE', 'EMAN'].map((e) => e + (i + 1)).includes(item.slayer_req!))
+ )
+ throw new Error(`Unknown slayer req: ${item.slayer_req!}`); */
+
+ /* console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-'); */
+}
diff --git a/test.js b/test.js
new file mode 100644
index 0000000..d0840ec
--- /dev/null
+++ b/test.js
@@ -0,0 +1,365 @@
+/* eslint-disable */
+// @ts-check
+
+import { createCanvas, registerFont } from 'canvas';
+import fs from 'fs/promises';
+import path, { dirname, join } from 'path';
+import tinycolor from 'tinycolor2';
+import { fileURLToPath } from 'url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+registerFont(join(__dirname, 'assets', 'Faithful.ttf'), { family: 'ComplianceSans' });
+registerFont(join(dirname(fileURLToPath(import.meta.url)), 'assets', 'Roboto-Regular.ttf'), { family: 'Roboto' });
+
+/** @typedef {string} McItemId */
+/** @typedef {string} SbItemId */
+/** @typedef {string} MojangJson */
+/** @typedef {`${SbItemId}:${number}` | ''} SbRecipeItem */
+/** @typedef {{[Location in `${'A' | 'B' | 'C'}${1 | 2 | 3}`]: SbRecipeItem;}} SbRecipe */
+/** @typedef {'WIKI_URL' | ''} InfoType */
+/** @typedef {`${'WOLF' | 'BLAZE' | 'EMAN'}_${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`} Slayer */
+/** @typedef {'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'a'|'b'|'c'|'d'|'e'|'f'} code */
+/**
+ * @typedef RawNeuItem
+ * @property {McItemId} itemid
+ * @property {string} displayname
+ * @property {MojangJson} nbttag
+ * @property {number} damage
+ * @property {string[]} lore
+ * @property {SbRecipe} [recipe]
+ * @property {SbItemId} internalname
+ * @property {InfoType} infoType
+ * @property {string[]} [info]
+ * @property {string} crafttext
+ * @property {boolean} [vanilla]
+ * @property {boolean} [useneucraft]
+ * @property {Slayer} [slayer_req]
+ * @property {string} [clickcommand]
+ * @property {number} [x]
+ * @property {number} [y]
+ * @property {number} [z]
+ * @property {string} [island]
+ * @property {{ type: string; cost: any[]; result: SbItemId }[]} [recipes]
+ * @property {SbItemId} [parent]
+ * @property {boolean} [noseal]
+ */
+
+const FormattingCodes = {
+ Black: '§0',
+ DarkBlue: '§1',
+ DarkGreen: '§2',
+ DarkAqua: '§3',
+ DarkRed: '§4',
+ DarkPurple: '§5',
+ Gold: '§6',
+ Gray: '§7',
+ DarkGray: '§8',
+ Blue: '§9',
+ Green: '§a',
+ Aqua: '§b',
+ Red: '§c',
+ LightPurple: '§d',
+ Yellow: '§e',
+ White: '§f',
+
+ Obfuscated: '§k',
+ Bold: '§l',
+ Strikethrough: '§m',
+ Underline: '§n',
+ Italic: '§o',
+ Reset: '§r'
+};
+
+const formattingInfo = {
+ [FormattingCodes.Black]: {
+ foreground: 'rgb(0, 0, 0)',
+ foregroundDarker: 'rgb(0, 0, 0)',
+ background: 'rgb(0, 0, 0)',
+ backgroundDarker: 'rgb(0, 0, 0)',
+ ansi: '\u001b[0;30m'
+ },
+ [FormattingCodes.DarkBlue]: {
+ foreground: 'rgb(0, 0, 170)',
+ foregroundDarker: 'rgb(0, 0, 118)',
+ background: 'rgb(0, 0, 42)',
+ backgroundDarker: 'rgb(0, 0, 29)',
+ ansi: '\u001b[0;34m'
+ },
+ [FormattingCodes.DarkGreen]: {
+ foreground: 'rgb(0, 170, 0)',
+ foregroundDarker: 'rgb(0, 118, 0)',
+ background: 'rgb(0, 42, 0)',
+ backgroundDarker: 'rgb(0, 29, 0)',
+ ansi: '\u001b[0;32m'
+ },
+ [FormattingCodes.DarkAqua]: {
+ foreground: 'rgb(0, 170, 170)',
+ foregroundDarker: 'rgb(0, 118, 118)',
+ background: 'rgb(0, 42, 42)',
+ backgroundDarker: 'rgb(0, 29, 29)',
+ ansi: '\u001b[0;36m'
+ },
+ [FormattingCodes.DarkRed]: {
+ foreground: 'rgb(170, 0, 0)',
+ foregroundDarker: 'rgb(118, 0, 0)',
+ background: 'rgb(42, 0, 0)',
+ backgroundDarker: 'rgb(29, 0, 0)',
+ ansi: '\u001b[0;31m'
+ },
+ [FormattingCodes.DarkPurple]: {
+ foreground: 'rgb(170, 0, 170)',
+ foregroundDarker: 'rgb(118, 0, 118)',
+ background: 'rgb(42, 0, 42)',
+ backgroundDarker: 'rgb(29, 0, 29)',
+ ansi: '\u001b[0;35m'
+ },
+ [FormattingCodes.Gold]: {
+ foreground: 'rgb(255, 170, 0)',
+ foregroundDarker: 'rgb(178, 118, 0)',
+ background: 'rgb(42, 42, 0)',
+ backgroundDarker: 'rgb(29, 29, 0)',
+ ansi: '\u001b[0;33m'
+ },
+ [FormattingCodes.Gray]: {
+ foreground: 'rgb(170, 170, 170)',
+ foregroundDarker: 'rgb(118, 118, 118)',
+ background: 'rgb(42, 42, 42)',
+ backgroundDarker: 'rgb(29, 29, 29)',
+ ansi: '\u001b[0;37m'
+ },
+ [FormattingCodes.DarkGray]: {
+ foreground: 'rgb(85, 85, 85)',
+ foregroundDarker: 'rgb(59, 59, 59)',
+ background: 'rgb(21, 21, 21)',
+ backgroundDarker: 'rgb(14, 14, 14)',
+ ansi: '\u001b[0;90m'
+ },
+ [FormattingCodes.Blue]: {
+ foreground: 'rgb(85, 85, 255)',
+ foregroundDarker: 'rgb(59, 59, 178)',
+ background: 'rgb(21, 21, 63)',
+ backgroundDarker: 'rgb(14, 14, 44)',
+ ansi: '\u001b[0;94m'
+ },
+ [FormattingCodes.Green]: {
+ foreground: 'rgb(85, 255, 85)',
+ foregroundDarker: 'rgb(59, 178, 59)',
+ background: 'rgb(21, 63, 21)',
+ backgroundDarker: 'rgb(14, 44, 14)',
+ ansi: '\u001b[0;92m'
+ },
+ [FormattingCodes.Aqua]: {
+ foreground: 'rgb(85, 255, 255)',
+ foregroundDarker: 'rgb(59, 178, 178)',
+ background: 'rgb(21, 63, 63)',
+ backgroundDarker: 'rgb(14, 44, 44)',
+ ansi: '\u001b[0;96m'
+ },
+ [FormattingCodes.Red]: {
+ foreground: 'rgb(255, 85, 85)',
+ foregroundDarker: 'rgb(178, 59, 59)',
+ background: 'rgb(63, 21, 21)',
+ backgroundDarker: 'rgb(44, 14, 14)',
+ ansi: '\u001b[0;91m'
+ },
+ [FormattingCodes.LightPurple]: {
+ foreground: 'rgb(255, 85, 255)',
+ foregroundDarker: 'rgb(178, 59, 178)',
+ background: 'rgb(63, 21, 63)',
+ backgroundDarker: 'rgb(44, 14, 44)',
+ ansi: '\u001b[0;95m'
+ },
+ [FormattingCodes.Yellow]: {
+ foreground: 'rgb(255, 255, 85)',
+ foregroundDarker: 'rgb(178, 178, 59)',
+ background: 'rgb(63, 63, 21)',
+ backgroundDarker: 'rgb(44, 44, 14)',
+ ansi: '\u001b[0;93m'
+ },
+ [FormattingCodes.White]: {
+ foreground: 'rgb(255, 255, 255)',
+ foregroundDarker: 'rgb(178, 178, 178)',
+ background: 'rgb(63, 63, 63)',
+ backgroundDarker: 'rgb(44, 44, 44)',
+ ansi: '\u001b[0;97m'
+ },
+
+ [FormattingCodes.Obfuscated]: { ansi: '\u001b[8m' },
+ [FormattingCodes.Bold]: { ansi: '\u001b[1m' },
+ [FormattingCodes.Strikethrough]: { ansi: '\u001b[9m' },
+ [FormattingCodes.Underline]: { ansi: '\u001b[4m' },
+ [FormattingCodes.Italic]: { ansi: '\u001b[3m' },
+ [FormattingCodes.Reset]: { ansi: '\u001b[0m' }
+};
+
+/**
+ * stolen from NEU
+ * @param {string} displayname
+ * @returns {code}
+ */
+function getPrimaryColourCode(displayname) {
+ let lastColourCode = -99;
+ let currentColour = 0;
+ const mostCommon = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ for (let i = 0; i < displayname.length; i++) {
+ const c = displayname.charAt(i);
+ if (c === '\u00A7') {
+ lastColourCode = i;
+ } else if (lastColourCode === i - 1) {
+ const colIndex = '0123456789abcdef'.indexOf(c);
+ if (colIndex >= 0) {
+ currentColour = colIndex;
+ } else {
+ currentColour = 0;
+ }
+ } else if ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.indexOf(c) >= 0) {
+ if (currentColour > 0) {
+ mostCommon[currentColour] = mostCommon[currentColour]++;
+ }
+ }
+ }
+ let mostCommonCount = 0;
+ for (let index = 0; index < mostCommon.length; index++) {
+ if (mostCommon[index] > mostCommonCount) {
+ mostCommonCount = mostCommon[index];
+ currentColour = index;
+ }
+ }
+
+ /** @type {code} */
+ // @ts-ignore
+ const code = '0123456789abcdef'.charAt(currentColour);
+ return code;
+}
+
+/**
+ * @param {number} decimal
+ */
+function decimalToHex(decimal) {
+ return decimal.toString(16).padStart(6, '0');
+}
+
+/**
+ * @param {RawNeuItem} item
+ * @returns {Buffer}
+ */
+function tooltip(item) {
+ const background = '#100010';
+
+ const width = 1920;
+ const height = 1080;
+ const scale = 10;
+
+ const itemRender = createCanvas(width, height),
+ ctx = itemRender.getContext('2d');
+
+ // ctx.fillStyle = '#000';
+ // ctx.fillRect(0, 0, width, height);
+
+ // ctx.globalAlpha = 0.94;
+ ctx.fillStyle = background;
+
+ // top outside
+ ctx.fillRect(scale, 0, width - 2 * scale, scale);
+
+ // bottom outside
+ ctx.fillRect(scale, height - scale, width - 2 * scale, scale);
+
+ // left outside
+ ctx.fillRect(0, scale, scale, height - 2 * scale);
+
+ // right outside
+ ctx.fillRect(width - scale, scale, scale, height - 2 * scale);
+
+ // middle
+ ctx.fillRect(2 * scale, 2 * scale, width - 4 * scale, height - 4 * scale);
+
+ // ctx.globalAlpha = 0.78;
+
+ const borderColorStart = parseInt(new tinycolor(getPrimaryColour(item.displayname)).toHex(), 16);
+ const borderColorEnd = ((borderColorStart & 0xfefefe) >> 1) | (borderColorStart & 0xff000000);
+
+ const borderColorStartStr = `#${decimalToHex(borderColorStart)}`;
+ const borderColorEndStr = `#${decimalToHex(borderColorEnd)}`;
+
+ console.log(borderColorStartStr, borderColorEndStr);
+
+ ctx.fillStyle = borderColorStartStr;
+
+ // top highlight
+ ctx.fillRect(scale, scale, width - 2 * scale, scale);
+
+ // left highlight
+ ctx.fillRect(scale, 2 * scale, scale, height - 3 * scale);
+
+ // bottom highlight
+ {
+ const x = 2 * scale,
+ y = height - 2 * scale,
+ w = width - 3 * scale,
+ h = scale;
+ const gradient = ctx.createLinearGradient(x, y, x + w, y + h);
+ gradient.addColorStop(0, borderColorStartStr);
+ gradient.addColorStop(1, borderColorEndStr);
+ ctx.fillStyle = gradient;
+
+ ctx.fillRect(x, y, w, h);
+ }
+
+ // right highlight
+ {
+ const x = width - 2 * scale,
+ y = 2 * scale,
+ w = scale,
+ h = height - 4 * scale;
+ const gradient = ctx.createLinearGradient(x, y, x + w, y + h);
+ gradient.addColorStop(0, borderColorStartStr);
+ gradient.addColorStop(1, borderColorEndStr);
+ ctx.fillStyle = gradient;
+
+ ctx.fillRect(x, y, w, h);
+ }
+
+ ctx.font = `50px ComplianceSans`;
+ ctx.fillText(stripCodes(item.displayname), scale * 4, scale * 7);
+
+ for (let i = 0; i < item.lore.length; i++) {
+ const line = item.lore[i];
+
+ ctx.fillStyle = `#${decimalToHex(parseInt(new tinycolor(getPrimaryColour(line)).toHex(), 16))}`;
+ ctx.fillText(stripCodes(line), scale * 4, scale * (7 + (i + 1) * 5));
+ }
+
+ return itemRender.toBuffer('image/png');
+}
+
+/**
+ * @param {string} displayname
+ */
+function getPrimaryColour(displayname) {
+ const code = getPrimaryColourCode(displayname);
+ return formattingInfo[`§${code}`].foregroundDarker;
+}
+
+/**
+ * @param {string} str
+ * @returns {string}
+ */
+function stripCodes(str) {
+ for (const format in formattingInfo) {
+ // @ts-ignore
+ str = str.replaceAll(new RegExp(format, 'ig'), '');
+ }
+ return str;
+}
+
+const repo = path.join(__dirname, 'neu-item-repo-dangerous');
+const itemPath = path.join(repo, 'items');
+const items = await fs.readdir(itemPath);
+
+const randomItem = items[Math.floor(Math.random() * items.length)];
+/** @type {RawNeuItem} */
+const item = (await import(path.join(itemPath, randomItem), { assert: { type: 'json' } })).default;
+
+console.log(randomItem);
+fs.writeFile('./test.png', tooltip(item));
diff --git a/test.png b/test.png
new file mode 100644
index 0000000..942a85e
--- /dev/null
+++ b/test.png
Binary files differ
diff --git a/tooltips.nnb b/tooltips.nnb
new file mode 100644
index 0000000..6e36999
--- /dev/null
+++ b/tooltips.nnb
@@ -0,0 +1,118 @@
+{
+ "cells": [
+ {
+ "language": "markdown",
+ "source": [
+ "# Thingy"
+ ],
+ "outputs": []
+ },
+ {
+ "language": "typescript",
+ "source": [
+ "function drawGradientRect(\n\tzLevel: number,\n\tleft: number,\n\ttop: number,\n\tright: number,\n\tbottom: number,\n\tstartColor: number,\n\tendColor: number\n) {\n\tconst startAlpha = ((startColor >> 24) & 255) / 255.0;\n\tconst startRed = ((startColor >> 16) & 255) / 255.0;\n\tconst startGreen = ((startColor >> 8) & 255) / 255.0;\n\tconst startBlue = (startColor & 255) / 255.0;\n\tconst endAlpha = ((endColor >> 24) & 255) / 255.0;\n\tconst endRed = ((endColor >> 16) & 255) / 255.0;\n\tconst endGreen = ((endColor >> 8) & 255) / 255.0;\n\tconst endBlue = (endColor & 255) / 255.0;\n\n\tconsole.dir({ startAlpha, startRed, startGreen, startBlue, endAlpha, endRed, endGreen, endBlue });\n\tconsole.dir({\n\t\tstartAlpha: color(startAlpha),\n\t\tstartRed: color(startRed),\n\t\tstartGreen: color(startGreen),\n\t\tstartBlue: color(startBlue),\n\t\tendAlpha: color(endAlpha),\n\t\tendRed: color(endRed),\n\t\tendGreen: color(endGreen),\n\t\tendBlue: color(endBlue)\n\t});\n}\n\nfunction color(num: number) {\n\treturn Math.floor(num * 255);\n}\n\nconst zLevel = 300;\nconst backgroundColor = 0xF0100010;\ndrawGradientRect(\n zLevel,\n 0,\n 0,\n 0,\n 0,\n backgroundColor,\n backgroundColor\n);"
+ ],
+ "outputs": [
+ {
+ "items": [
+ {
+ "mime": "application/vnd.code.notebook.stdout",
+ "value": [
+ "{",
+ " startAlpha: 0.9411764705882353,",
+ " startRed: 0.06274509803921569,",
+ " startGreen: 0,",
+ " startBlue: 0.06274509803921569,",
+ " endAlpha: 0.9411764705882353,",
+ " endRed: 0.06274509803921569,",
+ " endGreen: 0,",
+ " endBlue: 0.06274509803921569",
+ "}",
+ "{",
+ " startAlpha: 240,",
+ " startRed: 16,",
+ " startGreen: 0,",
+ " startBlue: 16,",
+ " endAlpha: 240,",
+ " endRed: 16,",
+ " endGreen: 0,",
+ " endBlue: 16",
+ "}",
+ ""
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "language": "markdown",
+ "source": [
+ "# Thingy 2"
+ ],
+ "outputs": []
+ },
+ {
+ "language": "typescript",
+ "source": [
+ "enum FormattingCodes {\n\tBlack = '§0',\n\tDarkBlue = '§1',\n\tDarkGreen = '§2',\n\tDarkAqua = '§3',\n\tDarkRed = '§4',\n\tDarkPurple = '§5',\n\tGold = '§6',\n\tGray = '§7',\n\tDarkGray = '§8',\n\tBlue = '§9',\n\tGreen = '§a',\n\tAqua = '§b',\n\tRed = '§c',\n\tLightPurple = '§d',\n\tYellow = '§e',\n\tWhite = '§f',\n\n\tObfuscated = '§k',\n\tBold = '§l',\n\tStrikethrough = '§m',\n\tUnderline = '§n',\n\tItalic = '§o',\n\tReset = '§r'\n}\n\nconst formattingInfo = {\n\t[FormattingCodes.Black]: {\n\t\tforeground: 'rgb(0, 0, 0)',\n\t\tforegroundDarker: 'rgb(0, 0, 0)',\n\t\tbackground: 'rgb(0, 0, 0)',\n\t\tbackgroundDarker: 'rgb(0, 0, 0)',\n\t\tansi: '\\u001b[0;30m'\n\t},\n\t[FormattingCodes.DarkBlue]: {\n\t\tforeground: 'rgb(0, 0, 170)',\n\t\tforegroundDarker: 'rgb(0, 0, 118)',\n\t\tbackground: 'rgb(0, 0, 42)',\n\t\tbackgroundDarker: 'rgb(0, 0, 29)',\n\t\tansi: '\\u001b[0;34m'\n\t},\n\t[FormattingCodes.DarkGreen]: {\n\t\tforeground: 'rgb(0, 170, 0)',\n\t\tforegroundDarker: 'rgb(0, 118, 0)',\n\t\tbackground: 'rgb(0, 42, 0)',\n\t\tbackgroundDarker: 'rgb(0, 29, 0)',\n\t\tansi: '\\u001b[0;32m'\n\t},\n\t[FormattingCodes.DarkAqua]: {\n\t\tforeground: 'rgb(0, 170, 170)',\n\t\tforegroundDarker: 'rgb(0, 118, 118)',\n\t\tbackground: 'rgb(0, 42, 42)',\n\t\tbackgroundDarker: 'rgb(0, 29, 29)',\n\t\tansi: '\\u001b[0;36m'\n\t},\n\t[FormattingCodes.DarkRed]: {\n\t\tforeground: 'rgb(170, 0, 0)',\n\t\tforegroundDarker: 'rgb(118, 0, 0)',\n\t\tbackground: 'rgb(42, 0, 0)',\n\t\tbackgroundDarker: 'rgb(29, 0, 0)',\n\t\tansi: '\\u001b[0;31m'\n\t},\n\t[FormattingCodes.DarkPurple]: {\n\t\tforeground: 'rgb(170, 0, 170)',\n\t\tforegroundDarker: 'rgb(118, 0, 118)',\n\t\tbackground: 'rgb(42, 0, 42)',\n\t\tbackgroundDarker: 'rgb(29, 0, 29)',\n\t\tansi: '\\u001b[0;35m'\n\t},\n\t[FormattingCodes.Gold]: {\n\t\tforeground: 'rgb(255, 170, 0)',\n\t\tforegroundDarker: 'rgb(178, 118, 0)',\n\t\tbackground: 'rgb(42, 42, 0)',\n\t\tbackgroundDarker: 'rgb(29, 29, 0)',\n\t\tansi: '\\u001b[0;33m'\n\t},\n\t[FormattingCodes.Gray]: {\n\t\tforeground: 'rgb(170, 170, 170)',\n\t\tforegroundDarker: 'rgb(118, 118, 118)',\n\t\tbackground: 'rgb(42, 42, 42)',\n\t\tbackgroundDarker: 'rgb(29, 29, 29)',\n\t\tansi: '\\u001b[0;37m'\n\t},\n\t[FormattingCodes.DarkGray]: {\n\t\tforeground: 'rgb(85, 85, 85)',\n\t\tforegroundDarker: 'rgb(59, 59, 59)',\n\t\tbackground: 'rgb(21, 21, 21)',\n\t\tbackgroundDarker: 'rgb(14, 14, 14)',\n\t\tansi: '\\u001b[0;90m'\n\t},\n\t[FormattingCodes.Blue]: {\n\t\tforeground: 'rgb(85, 85, 255)',\n\t\tforegroundDarker: 'rgb(59, 59, 178)',\n\t\tbackground: 'rgb(21, 21, 63)',\n\t\tbackgroundDarker: 'rgb(14, 14, 44)',\n\t\tansi: '\\u001b[0;94m'\n\t},\n\t[FormattingCodes.Green]: {\n\t\tforeground: 'rgb(85, 255, 85)',\n\t\tforegroundDarker: 'rgb(59, 178, 59)',\n\t\tbackground: 'rgb(21, 63, 21)',\n\t\tbackgroundDarker: 'rgb(14, 44, 14)',\n\t\tansi: '\\u001b[0;92m'\n\t},\n\t[FormattingCodes.Aqua]: {\n\t\tforeground: 'rgb(85, 255, 255)',\n\t\tforegroundDarker: 'rgb(59, 178, 178)',\n\t\tbackground: 'rgb(21, 63, 63)',\n\t\tbackgroundDarker: 'rgb(14, 44, 44)',\n\t\tansi: '\\u001b[0;96m'\n\t},\n\t[FormattingCodes.Red]: {\n\t\tforeground: 'rgb(255, 85, 85)',\n\t\tforegroundDarker: 'rgb(178, 59, 59)',\n\t\tbackground: 'rgb(63, 21, 21)',\n\t\tbackgroundDarker: 'rgb(44, 14, 14)',\n\t\tansi: '\\u001b[0;91m'\n\t},\n\t[FormattingCodes.LightPurple]: {\n\t\tforeground: 'rgb(255, 85, 255)',\n\t\tforegroundDarker: 'rgb(178, 59, 178)',\n\t\tbackground: 'rgb(63, 21, 63)',\n\t\tbackgroundDarker: 'rgb(44, 14, 44)',\n\t\tansi: '\\u001b[0;95m'\n\t},\n\t[FormattingCodes.Yellow]: {\n\t\tforeground: 'rgb(255, 255, 85)',\n\t\tforegroundDarker: 'rgb(178, 178, 59)',\n\t\tbackground: 'rgb(63, 63, 21)',\n\t\tbackgroundDarker: 'rgb(44, 44, 14)',\n\t\tansi: '\\u001b[0;93m'\n\t},\n\t[FormattingCodes.White]: {\n\t\tforeground: 'rgb(255, 255, 255)',\n\t\tforegroundDarker: 'rgb(178, 178, 178)',\n\t\tbackground: 'rgb(63, 63, 63)',\n\t\tbackgroundDarker: 'rgb(44, 44, 44)',\n\t\tansi: '\\u001b[0;97m'\n\t},\n\n\t[FormattingCodes.Obfuscated]: { ansi: '\\u001b[8m' },\n\t[FormattingCodes.Bold]: { ansi: '\\u001b[1m' },\n\t[FormattingCodes.Strikethrough]: { ansi: '\\u001b[9m' },\n\t[FormattingCodes.Underline]: { ansi: '\\u001b[4m' },\n\t[FormattingCodes.Italic]: { ansi: '\\u001b[3m' },\n\t[FormattingCodes.Reset]: { ansi: '\\u001b[0m' }\n} as const;"
+ ],
+ "outputs": []
+ },
+ {
+ "language": "markdown",
+ "source": [
+ "# Thingy 3"
+ ],
+ "outputs": []
+ },
+ {
+ "language": "typescript",
+ "source": [
+ "type code = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f';\n\n// stolen from NEU\nfunction getPrimaryColourCode(displayname: string): code {\n\tlet lastColourCode = -99;\n\tlet currentColour = 0;\n\tconst mostCommon = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n\tfor (let i = 0; i < displayname.length; i++) {\n\t\tconst c = displayname.charAt(i);\n\t\tif (c === '\\u00A7') {\n\t\t\tlastColourCode = i;\n\t\t} else if (lastColourCode === i - 1) {\n\t\t\tconst colIndex = '0123456789abcdef'.indexOf(c);\n\t\t\tif (colIndex >= 0) {\n\t\t\t\tcurrentColour = colIndex;\n\t\t\t} else {\n\t\t\t\tcurrentColour = 0;\n\t\t\t}\n\t\t} else if ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.indexOf(c) >= 0) {\n\t\t\tif (currentColour > 0) {\n\t\t\t\tmostCommon[currentColour] = mostCommon[currentColour]++;\n\t\t\t}\n\t\t}\n\t}\n\tlet mostCommonCount = 0;\n\tfor (let index = 0; index < mostCommon.length; index++) {\n\t\tif (mostCommon[index] > mostCommonCount) {\n\t\t\tmostCommonCount = mostCommon[index];\n\t\t\tcurrentColour = index;\n\t\t}\n\t}\n\n\treturn <code>'0123456789abcdef'.charAt(currentColour);\n}\n\nfunction getPrimaryColour(displayname: string) {\n\tconst code = getPrimaryColourCode(displayname);\n\treturn formattingInfo[`§${code}`].foregroundDarker;\n}\n\nfunction stripCodes(str: string) {\n\tfor (const format in formattingInfo) {\n\t\tstr = str.replaceAll(format, '');\n\t}\n\treturn str;\n}\n"
+ ],
+ "outputs": []
+ },
+ {
+ "language": "markdown",
+ "source": [
+ "# Thingy 4"
+ ],
+ "outputs": []
+ },
+ {
+ "language": "typescript",
+ "source": [
+ "import tinycolor from 'tinycolor2';\nimport canvas from 'canvas';\nimport path from 'path';\n\ntype McItemId = Lowercase<string>;\ntype SbItemId = Uppercase<string>;\ntype MojangJson = string;\ntype SbRecipeItem = `${SbItemId}:${number}` | '';\ntype SbRecipe = {\n\t[Location in `${'A' | 'B' | 'C'}${1 | 2 | 3}`]: SbRecipeItem;\n};\ntype InfoType = 'WIKI_URL' | '';\ntype Slayer = `${'WOLF' | 'BLAZE' | 'EMAN'}_${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`;\ninterface RawNeuItem {\n\titemid: McItemId;\n\tdisplayname: string;\n\tnbttag: MojangJson;\n\tdamage: number;\n\tlore: string[];\n\trecipe?: SbRecipe;\n\tinternalname: SbItemId;\n\tmodver: string;\n\tinfoType: InfoType;\n\tinfo?: string[];\n\tcrafttext: string;\n\tvanilla?: boolean;\n\tuseneucraft?: boolean;\n\tslayer_req?: Slayer;\n\tclickcommand?: string;\n\tx?: number;\n\ty?: number;\n\tz?: number;\n\tisland?: string;\n\trecipes?: { type: string; cost: any[]; result: SbItemId }[];\n\t/** @deprecated */\n\tparent?: SbItemId;\n\tnoseal?: boolean;\n}\n\nfunction tooltip(item: RawNeuItem) {\n\tconst background = '#100010';\n\n\tconst width = 1000;\n\tconst height = 250;\n\tconst scale = 10;\n\n\tcanvas.registerFont(path.join(__dirname, 'assets', 'Faithful.ttf'), { family: 'Compliance Sans' });\n\tcanvas.registerFont(path.join(__dirname, 'assets', 'Roboto-Regular.ttf'), { family: 'Roboto' });\n\n\tconst itemRender = canvas.createCanvas(width, height),\n\t\tctx = itemRender.getContext('2d');\n\n\tctx.globalAlpha = 0.94;\n\tctx.fillStyle = background;\n\n\t// top outside\n\tctx.fillRect(scale, 0, width - 2 * scale, scale);\n\n\t// bottom outside\n\tctx.fillRect(scale, height - scale, width - 2 * scale, scale);\n\n\t// left outside\n\tctx.fillRect(0, scale, scale, height - 2 * scale);\n\n\t// right outside\n\tctx.fillRect(width - scale, scale, scale, height - 2 * scale);\n\n\t// middle\n\tctx.fillRect(2 * scale, 2 * scale, width - 4 * scale, height - 4 * scale);\n\n\tctx.globalAlpha = 0.78;\n\n\tconst borderColorStart = parseInt(new tinycolor(getPrimaryColour(item.displayname)).toHex(), 16);\n\tconst borderColorEnd = ((borderColorStart & 0xfefefe) >> 1) | (borderColorStart & 0xff000000);\n\n\tconst borderColorStartStr = `#${borderColorStart.toString(16)}`;\n\tconst borderColorEndStr = `#${borderColorEnd.toString(16)}`;\n\n\tctx.fillStyle = borderColorStartStr;\n\n\t// top highlight\n\tctx.fillRect(scale, scale, width - 2 * scale, scale);\n\n\t// left highlight\n\tctx.fillRect(scale, 2 * scale, scale, height - 3 * scale);\n\n\t// bottom highlight\n\t{\n\t\tconst x = 2 * scale,\n\t\t\ty = height - 2 * scale,\n\t\t\tw = width - 3 * scale,\n\t\t\th = scale;\n\t\tconst gradient = ctx.createLinearGradient(x, y, x + w, y + h);\n\t\tgradient.addColorStop(0, borderColorStartStr);\n\t\tgradient.addColorStop(1, borderColorEndStr);\n\t\tctx.fillStyle = gradient;\n\n\t\tctx.fillRect(x, y, w, h);\n\t}\n\n\t// right highlight\n\t{\n\t\tconst x = width - 2 * scale,\n\t\t\ty = 2 * scale,\n\t\t\tw = scale,\n\t\t\th = height - 4 * scale;\n\t\tconst gradient = ctx.createLinearGradient(x, y, x + w, y + h);\n\t\tgradient.addColorStop(0, borderColorStartStr);\n\t\tgradient.addColorStop(1, borderColorEndStr);\n\t\tctx.fillStyle = gradient;\n\n\t\tctx.fillRect(x, y, w, h);\n\t}\n\n\tctx.font = `48px Roboto`;\n\tctx.fillText(stripCodes(item.displayname), scale * 4, scale * 7);\n\n\tconst buf = itemRender.toBuffer();\n\treturn buf;\n}\n"
+ ],
+ "outputs": []
+ },
+ {
+ "language": "typescript",
+ "source": [
+ "import fs from 'fs/promises';\nimport path from 'path';\n\nconst repo = path.join(__dirname, 'neu-item-repo-dangerous');\nconst itemPath = path.join(repo, 'items');\nconst items = await fs.readdir(itemPath);\n\nconst randomItem = items[Math.floor(Math.random() * items.length)];\nconst item = (await import(path.join(itemPath, randomItem), { assert: { type: 'json' } })).default as RawNeuItem;\n\nconsole.log(stripCodes(item.displayname));\ntooltip(item);\n"
+ ],
+ "outputs": [
+ {
+ "items": [
+ {
+ "mime": "application/vnd.code.notebook.stdout",
+ "value": [
+ "Enderman Minion IV",
+ ""
+ ]
+ }
+ ]
+ },
+ {
+ "items": [
+ {
+ "mime": "image/png",
+ "value": "data:image/png;base64,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"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json
new file mode 100644
index 0000000..59f6ecc
--- /dev/null
+++ b/tsconfig.eslint.json
@@ -0,0 +1,15 @@
+{
+ "extends": "./tsconfig.json",
+ "include": [
+ "src/**/*.ts",
+ "src/**/*d.ts",
+ "lib/**/*.ts",
+ "ecosystem.config.cjs",
+ ".eslintrc.cjs",
+ "config/**/*.ts",
+ "tests/**/*.ts",
+ "vite.config.ts",
+ "tooltips*",
+ "test*"
+ ]
+}
diff --git a/tsconfig.json b/tsconfig.json
index 565346b..6d24566 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -27,17 +27,6 @@
"#tags": ["./src/lib/common/tags.js"]
}
},
- "include": [
- "src/**/*.ts",
- "src/**/*d.ts",
- "lib/**/*.ts",
- "ecosystem.config.cjs",
- ".eslintrc.cjs",
- "config/**/*.ts",
- "tests/**/*.ts",
- "vite.config.ts",
- "src/bot.ts",
- "src/lib/common/util/Minecraft.ts"
- ],
+ "include": ["src/**/*.ts", "src/**/*d.ts", "lib/**/*.ts", "test.js"],
"exclude": ["dist", "node_modules"]
}