diff --git a/package.json b/package.json index 7cbdbb9..43fea56 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,14 @@ "author": "", "license": "ISC", "devDependencies": { + "@types/color": "^3.0.6", "@types/node": "^20.14.2", "typescript": "^5.4.5" }, "dependencies": { "axios": "^1.7.2", "canvas": "^2.11.2", + "color": "^4.2.3", "isolated-vm": "^5.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3aa17cd..dcfc1d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,11 +11,17 @@ dependencies: canvas: specifier: ^2.11.2 version: 2.11.2 + color: + specifier: ^4.2.3 + version: 4.2.3 isolated-vm: specifier: ^5.0.0 version: 5.0.0 devDependencies: + '@types/color': + specifier: ^3.0.6 + version: 3.0.6 '@types/node': specifier: ^20.14.2 version: 20.14.2 @@ -43,6 +49,22 @@ packages: - supports-color dev: false + /@types/color-convert@2.0.3: + resolution: {integrity: sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==} + dependencies: + '@types/color-name': 1.1.4 + dev: true + + /@types/color-name@1.1.4: + resolution: {integrity: sha512-hulKeREDdLFesGQjl96+4aoJSHY5b2GRjagzzcqCfIrWhe5vkCqIvrLbqzBaI1q94Vg8DNJZZqTR5ocdWmWclg==} + dev: true + + /@types/color@3.0.6: + resolution: {integrity: sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==} + dependencies: + '@types/color-convert': 2.0.3 + dev: true + /@types/node@20.14.2: resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} dependencies: @@ -146,11 +168,37 @@ packages: engines: {node: '>=10'} dev: false + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: false + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: false + + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + /color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true dev: false + /color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + dev: false + /combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -326,6 +374,10 @@ packages: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: false + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: false + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -573,6 +625,12 @@ packages: simple-concat: 1.0.1 dev: false + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: false + /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} diff --git a/src/index.ts b/src/index.ts index 7a0764b..8ac9702 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { createCanvas, loadImage } from "canvas"; import { fetchScript, extractDataFromScript, getAsset, dirname } from "./util.js"; import { writeFile } from "fs/promises"; import path from "path"; +import Color from "color"; const MAKER_ID = "1904634"; @@ -12,7 +13,11 @@ type UrlsList = { colorId: string, url: string }; type LayerList = { layerId: string, urls: UrlsList[] }; type ItemList = { itemId: string, layers: LayerList[] }; type LayerColorList = { layerIndex: number, cpId: string, colors: string[] }; -type CategoryList = { cpId: string, pId: string, items: ItemList[], index: number }; +type CategoryList = { cpId: string, pId: string, items: ItemList[], index: number, isRmv: boolean }; + +const exclusions: number[][] = Object.values(config.ruleList) + .filter((rule) => rule.pId == 0) // Not sure what pId is but just to be sure that this won't break something else + .map((rule) => rule.list); const items: ItemList[] = []; const categories: CategoryList[] = []; @@ -50,15 +55,35 @@ for (const c in config.pList) { } items.push(...localItems); - categories.push({ cpId: category.cpId, pId: category.pId.toString(), items: localItems, index: partsId2Index[category.pId.toString()] }); + categories.push({ + cpId: category.cpId, + pId: category.pId.toString(), + items: localItems, + index: partsId2Index[category.pId.toString()], + isRmv: !!category.isRmv, + }); } -const canvas = createCanvas(config.w, config.h); -const ctx = canvas.getContext('2d'); +const images: { layer: number, url: string, categoryId: number }[] = []; -const images: { layer: number, url: string }[] = []; +categoryLoop: for (const category of categories.sort((a, b) => b.index - a.index)) { + const canHide = category.isRmv; + const inRuleset = exclusions.find((arr) => arr.includes(Number(category.pId))); + + if (canHide) { + // If there's other things that would be blocked by this, + // it should be more likely for this one to be hidden so + // the other item appears more frequently. + if (Math.random() < (inRuleset ? 0.5 : 0.2)) continue; + } + + // Check if a conflicting object already exists, and if so, skip this one + for (const rule of exclusions) { + if (rule.includes(Number(category.pId))) { + if (images.find((i) => rule.includes(i.categoryId))) continue categoryLoop; + } + } -for (const category of categories.sort((a, b) => b.index - a.index)) { const colors = layerColors.find((c) => c.cpId == category.cpId)!.colors; const colorId = colors[Math.floor(Math.random() * colors.length)]; const item = category.items[Math.floor(Math.random() * category.items.length)]; @@ -67,11 +92,19 @@ for (const category of categories.sort((a, b) => b.index - a.index)) { const url = (layer.urls.find((i) => i.colorId == colorId) ?? layer.urls?.[0])?.url; if (url) { - images.push({ layer: Number(layer.layerId), url }); + images.push({ layer: Number(layer.layerId), url, categoryId: Number(category.pId) }); } } } +const canvas = createCanvas(config.w, config.h); +const ctx = canvas.getContext('2d'); + +// So we don't accidentally end up with a transparent background +const bgCol = Color.hsv(Math.floor(Math.random() * 360), 20, 100).hex(); +ctx.fillStyle = bgCol; +ctx.fillRect(0, 0, config.w, config.h); + // Retains the correct order const imgPaths = await Promise.all( images diff --git a/src/types.ts b/src/types.ts index a40ebdd..b412298 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,6 +9,7 @@ export type Plist = { cpId: string, // Used to index into cpList lyrs: number[], defItmId: number, + isRmv: number, // 0 if not removable items: Item[], }[]; @@ -23,10 +24,18 @@ export type Lyrlist = { [key: number]: number; } +export type RuleList = { + [key: number]: { + list: number[], + pId: number, + } +} + export type Config = { pList: Plist, cpList: Cplist, lyrList: Lyrlist, + ruleList: RuleList, w: number, h: number,