From 6ced96796cdd049267b6b33ba573a15d4591d02a Mon Sep 17 00:00:00 2001 From: Blipz Date: Wed, 25 Mar 2026 14:30:47 +0100 Subject: [PATCH] Adapt generator --- package-lock.json | 20 +- package.json | 4 +- src/modules/emblem/generator.ts | 510 +++----------------------------- 3 files changed, 53 insertions(+), 481 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a54e12d..7837e53e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "armoria": "file:../armoria/dist", "d3": "^7.9.0", "delaunator": "^5.0.1", - "polylabel": "^2.0.1" + "polylabel": "^2.0.1", + "symlink-dir": "^9.0.0" }, "devDependencies": { "@biomejs/biome": "2.3.13", @@ -26,7 +27,6 @@ "@vitest/browser": "^4.0.18", "@vitest/browser-playwright": "^4.0.18", "playwright": "^1.57.0", - "symlink-dir": "^9.0.0", "typescript": "^5.9.3", "vite": "^7.3.1", "vitest": "^4.0.18" @@ -51,14 +51,11 @@ "puppeteer-core": "24.11.2" }, "devDependencies": { - "@sveltejs/adapter-auto": "^7.0.1", - "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/adapter-vercel": "^6.3.3", "@sveltejs/kit": "^2.22.5", "@sveltejs/package": "^2.5.7", "@sveltejs/vite-plugin-svelte": "^3.0.0", "@types/eslint": "^9.6.1", - "armoria-core": "file:../armoria-core", "eslint": "^9.31.0", "eslint-config-prettier": "^10.1.5", "eslint-plugin-svelte": "^3.10.1", @@ -75,6 +72,10 @@ "typescript-eslint": "^8.36.0", "vite": "^5.0.3", "workbox-precaching": "^7.3.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.22.5", + "svelte": "^4.2.7" } }, "node_modules/@biomejs/biome": { @@ -1571,7 +1572,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@zkochan/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-GBf4ua7ogWTr7fATnzk/JLowZDBnBJMm8RkMaC/KcvxZ9gxbMWix0/jImd815LmqKyIHZ7h7lADRddGMdGBuCA==", - "dev": true, "license": "MIT", "engines": { "node": ">=18.12" @@ -1601,7 +1601,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", - "dev": true, "license": "MIT", "dependencies": { "is-windows": "^1.0.0" @@ -2130,7 +2129,6 @@ "version": "11.3.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -2160,7 +2158,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/iconv-lite": { @@ -2188,7 +2185,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2198,7 +2194,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -2396,7 +2391,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/rename-overwrite/-/rename-overwrite-6.0.3.tgz", "integrity": "sha512-Daqe51STnrCUq/t4dbzCtfNBLElrqVpCtuWK0MuPrzUi6K/13E98y3E8/kzuMZt6IEmghMnF41J0AidrFqjZUA==", - "dev": true, "license": "MIT", "dependencies": { "@zkochan/rimraf": "^3.0.2", @@ -2519,7 +2513,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/symlink-dir/-/symlink-dir-9.0.0.tgz", "integrity": "sha512-3i39G70eXo9POjx9RnYAgymfUO3AFrezABaJq0Sh+muMCR3Lx7B9KQR2uWMY+yc1QCwhEelVCGS6N+sR3aKCZg==", - "dev": true, "license": "MIT", "dependencies": { "better-path-resolve": "^1.0.0", @@ -2617,7 +2610,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" diff --git a/package.json b/package.json index 35d1d132..f9071536 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "@vitest/browser": "^4.0.18", "@vitest/browser-playwright": "^4.0.18", "playwright": "^1.57.0", - "symlink-dir": "^9.0.0", "typescript": "^5.9.3", "vite": "^7.3.1", "vitest": "^4.0.18" @@ -44,7 +43,8 @@ "armoria": "file:../armoria/dist", "d3": "^7.9.0", "delaunator": "^5.0.1", - "polylabel": "^2.0.1" + "polylabel": "^2.0.1", + "symlink-dir": "^9.0.0" }, "engines": { "node": ">=24.0.0" diff --git a/src/modules/emblem/generator.ts b/src/modules/emblem/generator.ts index 4c1aec24..8216dff9 100644 --- a/src/modules/emblem/generator.ts +++ b/src/modules/emblem/generator.ts @@ -1,12 +1,4 @@ -import { - charges, - divisions, - lines, - ordinaries, - positions, - shields, - tinctures, -} from "armoria"; +import { generateCOA, getTincture, shields, tinctures } from "armoria"; import { P, rw } from "../../utils"; import { typeMapping } from "./typeMapping"; @@ -61,365 +53,60 @@ class EmblemGeneratorModule { kinship = 0; dominion = 0; } - - let usedPattern: string | null = null; - const usedTinctures: string[] = []; - - const t1 = P(kinship as number) - ? parent!.t1 - : this.getTincture("field", usedTinctures, null); - if (t1.includes("-")) usedPattern = t1; - const coa: Emblem = { t1 }; - - const addCharge = P(usedPattern ? 0.5 : 0.93); // 80% for charge - const linedOrdinary = - (addCharge && P(0.3)) || P(0.5) - ? parent?.ordinaries && P(kinship as number) - ? parent.ordinaries[0].ordinary - : rw(ordinaries.lined) - : null; - - const ordinary = - (!addCharge && P(0.65)) || P(0.3) - ? linedOrdinary - ? linedOrdinary - : rw(ordinaries.straight) - : null; // 36% for ordinary - - const rareDivided = [ - "chief", - "terrace", - "chevron", - "quarter", - "flaunches", - ].includes(ordinary!); - - const divisioned = (() => { - if (rareDivided) return P(0.03); - if (addCharge && ordinary) return P(0.03); - if (addCharge) return P(0.3); - if (ordinary) return P(0.7); - return P(0.995); - })(); - - const division = (() => { - if (divisioned) { - if (parent?.division && P((kinship as number) - 0.1)) - return parent.division.division; - return rw(divisions.variants); - } - return null; - })(); - - if (division) { - const t = this.getTincture( - "division", - usedTinctures, - P(0.98) ? coa.t1 : null, - ); - coa.division = { division, t }; - if (divisions[division]) - coa.division.line = - usedPattern || (ordinary && P(0.7)) - ? "straight" - : rw(divisions.data[division].line); - } - - if (ordinary) { - coa.ordinaries = [ - { ordinary, t: this.getTincture("charge", usedTinctures, coa.t1) }, - ]; - if (linedOrdinary) - coa.ordinaries[0].line = - usedPattern || (division && P(0.7)) ? "straight" : rw(lines.variants); - if ( - division && - !addCharge && - !usedPattern && - P(0.5) && - ordinary !== "bordure" && - ordinary !== "orle" - ) { - if (P(0.8)) coa.ordinaries[0].divided = "counter"; - // 40% - else if (P(0.6)) coa.ordinaries[0].divided = "field"; - // 6% - else coa.ordinaries[0].divided = "division"; // 4% - } - } - - if (addCharge) { - const charge = (() => { + return generateCOA(null, { + charge: () => { if (parent?.charges && P((kinship as number) - 0.1)) return parent.charges[0].charge; if (type && type !== "Generic" && P(0.3)) return rw(typeMapping[type]); - return this.selectCharge( - ordinary || divisioned ? charges.types : charges.single, - ); - })(); - const chargeDataEntry = charges.data[charge] || {}; + }, + division: () => { + if (parent?.division && P((kinship as number) - 0.1)) + return parent.division.division; + }, + ordinary: () => { + if (parent?.ordinaries && P(kinship as number)) + return parent.ordinaries[0].ordinary; + }, + tincture: () => { + if (P(kinship as number)) return parent!.t1; + }, + finalize: (coa: Emblem, config: Record) => { + // dominions have canton with parent coa + if (P(dominion as number) && parent?.charges) { + const invert = this.isSameType(parent.t1, coa.t1); + const t = invert + ? getTincture(config, "division", config.usedTinctures, coa.t1) + : parent.t1; + const canton: EmblemOrdinary = { ordinary: "canton", t }; - let p: string; - let t: string; - - const ordinaryData = ordinaries.data[ordinary!]; - const tOrdinary = coa.ordinaries ? coa.ordinaries[0].t : null; - - if (ordinaryData?.positionsOn && P(0.8)) { - // place charge over ordinary (use tincture of field type) - p = rw(ordinaryData.positionsOn); - t = - !usedPattern && P(0.3) - ? coa.t1 - : this.getTincture("charge", [], tOrdinary); - } else if (ordinaryData?.positionsOff && P(0.95)) { - // place charge out of ordinary (use tincture of ordinary type) - p = rw(ordinaryData.positionsOff); - t = - !usedPattern && P(0.3) - ? tOrdinary! - : this.getTincture("charge", usedTinctures, coa.t1); - } else if (divisions.data[division]?.positions) { - // place charge in fields made by division - p = rw(divisions.data[division].positions); - t = this.getTincture( - "charge", - tOrdinary ? usedTinctures.concat(tOrdinary) : usedTinctures, - coa.t1, - ); - } else if (chargeDataEntry.positions) { - // place charge-suitable position - p = rw(chargeDataEntry.positions); - t = this.getTincture("charge", usedTinctures, coa.t1); - } else { - // place in standard position (use new tincture) - p = usedPattern - ? "e" - : charges.conventional[charge] - ? rw(positions.conventional) - : rw(positions.complex); - t = this.getTincture( - "charge", - usedTinctures.concat(tOrdinary!), - coa.t1, - ); - } - - if ( - chargeDataEntry.natural && - chargeDataEntry.natural !== t && - chargeDataEntry.natural !== tOrdinary - ) - t = chargeDataEntry.natural; - - const item: EmblemCharge = { charge: charge, t, p }; - const colors = chargeDataEntry.colors || 1; - if (colors > 1) - item.t2 = P(0.25) - ? this.getTincture("charge", usedTinctures, coa.t1) - : t; - if (colors > 2 && item.t2) - item.t3 = P(0.5) - ? this.getTincture("charge", usedTinctures, coa.t1) - : t; - coa.charges = [item]; - - if (p === "ABCDEFGHIJKL" && P(0.95)) { - // add central charge if charge is in bordure - coa.charges[0].charge = rw(charges.conventional); - const chargeNew = this.selectCharge(charges.single); - const tNew = this.getTincture("charge", usedTinctures, coa.t1); - coa.charges.push({ charge: chargeNew, t: tNew, p: "e" }); - } else if (P(0.8) && charge === "inescutcheon") { - // add charge to inescutcheon - const chargeNew = this.selectCharge(charges.types); - const t2 = this.getTincture("charge", [], t); - coa.charges.push({ charge: chargeNew, t: t2, p, size: 0.5 }); - } else if (division && !ordinary) { - const allowCounter = - !usedPattern && - (!coa.division?.line || coa.division.line === "straight"); - - // dimidiation: second charge at division basic positions - if ( - P(0.3) && - ["perPale", "perFess"].includes(division) && - coa.division?.line === "straight" - ) { - coa.charges[0].divided = "field"; - if (P(0.95)) { - const p2 = - p === "e" || P(0.5) - ? "e" - : rw(divisions.data[division].positions); - const chargeNew = this.selectCharge(charges.single); - const tNew = this.getTincture( - "charge", - usedTinctures, - coa.division!.t, - ); - coa.charges.push({ - charge: chargeNew, - t: tNew, - p: p2, - divided: "division", - }); + if (coa.charges) { + for (let i = coa.charges.length - 1; i >= 0; i--) { + const charge = coa.charges[i]; + if (charge.size === 1.5) charge.size = 1.4; + charge.p = charge.p.replaceAll(/[ajy]/g, ""); + if (!charge.p) coa.charges.splice(i, 1); + } } - } else if (allowCounter && P(0.4)) coa.charges[0].divided = "counter"; - // counterchanged, 40% - else if ( - ["perPale", "perFess", "perBend", "perBendSinister"].includes( - division, - ) && - P(0.8) - ) { - // place 2 charges in division standard positions - const [p1, p2] = - division === "perPale" - ? ["p", "q"] - : division === "perFess" - ? ["k", "n"] - : division === "perBend" - ? ["l", "m"] - : ["j", "o"]; // perBendSinister - coa.charges[0].p = p1; - const chargeNew = this.selectCharge(charges.single); - const tNew = this.getTincture( - "charge", - usedTinctures, - coa.division!.t, - ); - coa.charges.push({ charge: chargeNew, t: tNew, p: p2 }); - } else if (["perCross", "perSaltire"].includes(division) && P(0.5)) { - // place 4 charges in division standard positions - const [p1, p2, p3, p4] = - division === "perCross" - ? ["j", "l", "m", "o"] - : ["b", "d", "f", "h"]; - coa.charges[0].p = p1; + let charge = parent.charges[0].charge; + if (charge === "inescutcheon" && parent.charges[1]) + charge = parent.charges[1].charge; - const c2 = this.selectCharge(charges.single); - const t2 = this.getTincture("charge", [], coa.division!.t); + let t2 = invert ? parent.t1 : parent.charges[0].t; + if (this.isSameType(t, t2)) + t2 = getTincture(config, "charge", config.usedTinctures, t); - const c3 = this.selectCharge(charges.single); - const t3 = this.getTincture("charge", [], coa.division!.t); + if (!coa.charges) coa.charges = []; + coa.charges.push({ charge, t: t2, p: "y", size: 0.5 }); - const c4 = this.selectCharge(charges.single); - const t4 = this.getTincture("charge", [], coa.t1); - coa.charges.push( - { charge: c2, t: t2, p: p2 }, - { charge: c3, t: t3, p: p3 }, - { charge: c4, t: t4, p: p4 }, - ); - } else if (allowCounter && p.length > 1) - coa.charges[0].divided = "counter"; // counterchanged, 40% - } - - for (const c of coa.charges) { - this.defineChargeAttributes(ordinary, division, c); - } - } - - // dominions have canton with parent coa - if (P(dominion as number) && parent?.charges) { - const invert = this.isSameType(parent.t1, coa.t1); - const t = invert - ? this.getTincture("division", usedTinctures, coa.t1) - : parent.t1; - const canton: EmblemOrdinary = { ordinary: "canton", t }; - - if (coa.charges) { - for (let i = coa.charges.length - 1; i >= 0; i--) { - const charge = coa.charges[i]; - if (charge.size === 1.5) charge.size = 1.4; - charge.p = charge.p.replaceAll(/[ajy]/g, ""); - if (!charge.p) coa.charges.splice(i, 1); + if (coa.ordinaries) { + coa.ordinaries.push(canton); + } else { + coa.ordinaries = [canton]; + } } - } - - let charge = parent.charges[0].charge; - if (charge === "inescutcheon" && parent.charges[1]) - charge = parent.charges[1].charge; - - let t2 = invert ? parent.t1 : parent.charges[0].t; - if (this.isSameType(t, t2)) - t2 = this.getTincture("charge", usedTinctures, t); - - if (!coa.charges) coa.charges = []; - coa.charges.push({ charge, t: t2, p: "y", size: 0.5 }); - - if (coa.ordinaries) { - coa.ordinaries.push(canton); - } else { - coa.ordinaries = [canton]; - } - } - - return coa; - } - - private selectCharge(set?: Record): string { - const type = set ? rw(set) : rw(charges.types); - return type === "inescutcheon" - ? "inescutcheon" - : rw(charges[type as keyof typeof charges] as Record); - } - - // Select tincture: element type (field, division, charge), used field tinctures, field type to follow RoT - private getTincture( - element: "field" | "division" | "charge", - fields: string[] = [], - RoT: string | null, - ): string { - const base = RoT ? (RoT.includes("-") ? RoT.split("-")[1] : RoT) : null; - - let type = rw(tinctures[element]); // metals, colours, stains, patterns - if (RoT && type !== "patterns") - type = this.getType(base!) === "metals" ? "colours" : "metals"; // follow RoT - if (type === "metals" && fields.includes("or") && fields.includes("argent")) - type = "colours"; // exclude metals overuse - let tincture = rw( - tinctures[type as keyof typeof tinctures] as Record, - ); - - while (tincture === base || fields.includes(tincture)) { - tincture = rw( - tinctures[type as keyof typeof tinctures] as Record, - ); - } // follow RoT - - if (type !== "patterns" && element !== "charge") fields.push(tincture); // add field tincture - - if (type === "patterns") { - tincture = this.definePattern(tincture, element, fields); - } - - return tincture; - } - - private defineChargeAttributes( - ordinary: string | null, - division: string | null, - c: EmblemCharge, - ): void { - // define size - c.size = (c.size || 1) * this.getSize(c.p, ordinary, division); - - // clean-up position - c.p = [...new Set(c.p)].join(""); - - // define orientation - if (P(0.02) && charges.data[c.charge]?.sinister) c.sinister = 1; - if (P(0.02) && charges.data[c.charge]?.reversed) c.reversed = 1; - } - - private getType(t: string): string | undefined { - const tinc = t.includes("-") ? t.split("-")[1] : t; - if (Object.keys(tinctures.metals).includes(tinc)) return "metals"; - if (Object.keys(tinctures.colours).includes(tinc)) return "colours"; - if (Object.keys(tinctures.stains).includes(tinc)) return "stains"; - return undefined; + }, + }); } private isSameType(t1: string, t2: string): boolean { @@ -433,113 +120,6 @@ class EmblemGeneratorModule { return "pattern"; } - private definePattern( - pattern: string, - element: "field" | "division" | "charge", - usedTinctures: string[], - ): string { - let t1: string | null = null; - let t2: string | null = null; - let size = ""; - - // Size selection - must use sequential P() calls to match original behavior - if (P(0.1)) size = "-small"; - // biome-ignore lint/suspicious/noDuplicateElseIf: false positive - else if (P(0.1)) size = "-smaller"; - else if (P(0.01)) size = "-big"; - else if (P(0.005)) size = "-smallest"; - - // apply standard tinctures - if (P(0.5) && ["vair", "vairInPale", "vairEnPointe"].includes(pattern)) { - t1 = "azure"; - t2 = "argent"; - } else if (P(0.8) && pattern === "ermine") { - t1 = "argent"; - t2 = "sable"; - } else if (pattern === "pappellony") { - if (P(0.2)) { - t1 = "gules"; - t2 = "or"; - // biome-ignore lint/suspicious/noDuplicateElseIf: false positive - } else if (P(0.2)) { - t1 = "argent"; - t2 = "sable"; - // biome-ignore lint/suspicious/noDuplicateElseIf: false positive - } else if (P(0.2)) { - t1 = "azure"; - t2 = "argent"; - } - } else if (pattern === "masoned") { - if (P(0.3)) { - t1 = "gules"; - t2 = "argent"; - // biome-ignore lint/suspicious/noDuplicateElseIf: false positive - } else if (P(0.3)) { - t1 = "argent"; - t2 = "sable"; - } else if (P(0.1)) { - t1 = "or"; - t2 = "sable"; - } - } else if (pattern === "fretty") { - if (t2 === "sable" || P(0.35)) { - t1 = "argent"; - t2 = "gules"; - } else if (P(0.25)) { - t1 = "sable"; - t2 = "or"; - } else if (P(0.15)) { - t1 = "gules"; - t2 = "argent"; - } - } else if (pattern === "semy") - pattern = `${pattern}_of_${this.selectCharge(charges.semy)}`; - - if (!t1 || !t2) { - const startWithMetal = P(0.7); - t1 = startWithMetal ? rw(tinctures.metals) : rw(tinctures.colours); - t2 = startWithMetal ? rw(tinctures.colours) : rw(tinctures.metals); - } - - // division should not be the same tincture as base field - if (element === "division") { - if (usedTinctures.includes(t1)) t1 = this.replaceTincture(t1); - if (usedTinctures.includes(t2)) t2 = this.replaceTincture(t2); - } - - usedTinctures.push(t1, t2); - return `${pattern}-${t1}-${t2}${size}`; - } - - private replaceTincture(t: string): string { - const type = this.getType(t); - let n: string | null = null; - while (!n || n === t) { - n = rw(tinctures[type] as Record); - } - return n; - } - - private getSize( - p: string, - o: string | null = null, - d: string | null = null, - ): number { - if (p === "e" && (o === "bordure" || o === "orle")) return 1.1; - if (p === "e") return 1.5; - if (p === "jln" || p === "jlh") return 0.7; - if (p === "abcpqh" || p === "ez" || p === "be") return 0.5; - if (["a", "b", "c", "d", "f", "g", "h", "i", "bh", "df"].includes(p)) - return 0.5; - if (["j", "l", "m", "o", "jlmo"].includes(p) && d === "perCross") - return 0.6; - if (p.length > 10) return 0.18; // >10 (bordure) - if (p.length > 7) return 0.3; // 8, 9, 10 - if (p.length > 4) return 0.4; // 5, 6, 7 - if (p.length > 2) return 0.5; // 3, 4 - return 0.7; // 1, 2 - } - getShield(culture: number, state?: number): string { const emblemShape = document.getElementById( "emblemShape",