From 9cacbbbad4e2ac216e3a6f264890091ae95ec591 Mon Sep 17 00:00:00 2001 From: CPTN Cosmo Date: Fri, 23 Jan 2026 20:15:19 +0100 Subject: [PATCH 1/9] minor bugfix for close button mainly --- module.json | 4 ++-- scripts/app.js | 2 +- scripts/importer.js | 10 +--------- styles/importer.css | 6 +++--- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/module.json b/module.json index a68e00d..35cba3e 100644 --- a/module.json +++ b/module.json @@ -1,7 +1,7 @@ { "id": "dh-importer", "title": "Daggerheart Importer", - "version": "1.0.0", + "version": "1.0.1", "compatibility": { "minimum": "13", "verified": "13" @@ -34,5 +34,5 @@ "description": "Imports Adversaries and Environments from text blocks into the Daggerheart system.", "url": "https://github.com/cptn-cosmo/dh-importer", "manifest": "https://git.geeks.gay/cosmo/dh-importer/raw/branch/main/module.json", - "download": "https://git.geeks.gay/cosmo/dh-importer/releases/download/1.0.0/dh-importer.zip" + "download": "https://git.geeks.gay/cosmo/dh-importer/releases/download/1.0.1/dh-importer.zip" } \ No newline at end of file diff --git a/scripts/app.js b/scripts/app.js index e8b2543..0899031 100644 --- a/scripts/app.js +++ b/scripts/app.js @@ -86,7 +86,7 @@ export class DHImporterApp extends HandlebarsApplicationMixin(ApplicationV2) { static async onImport(event, target) { try { - const formData = new FormDataExtended(this.element).object; + const formData = new foundry.applications.ux.FormDataExtended(this.element).object; for (const actor of this.parsedData) { actor.useFeatures = {}; diff --git a/scripts/importer.js b/scripts/importer.js index 651158e..6ad5614 100644 --- a/scripts/importer.js +++ b/scripts/importer.js @@ -165,18 +165,12 @@ export class DHImporter { } else if (prevLine.match(/STRESS:/i)) { data.system.resources.stress.max = val; } else if (prevLine.includes("minor") || prevPrevLine.includes("minor")) { - data.system.damageThresholds.minor = val; // System doesn't have minor usually? - // Wait, Daggerheart system model: major, severe. Minor is usually just 1 HP. - // But let's check input: "minor \n 1 HP \n 9". 9 is likely the threshold. - // However, data.system.damageThresholds usually only stores Major/Severe in Foundry system? - // Let's assume we ignore minor or map it if valid. + data.system.damageThresholds.minor = val; } else if (prevLine.includes("major") || prevPrevLine.includes("major")) { data.system.damageThresholds.major = val; } else if (prevLine.includes("severe") || prevPrevLine.includes("severe")) { data.system.damageThresholds.severe = val; } else if (prevLine.includes("HP")) { - // "1 HP" / "2 HP" lines often precede thresholds - // If we see "9" after "1 HP", check if "minor" was before that. if (prevPrevLine.includes("minor")) { /* ignore or store */ } if (prevPrevLine.includes("major")) data.system.damageThresholds.major = val; if (prevPrevLine.includes("severe")) data.system.damageThresholds.severe = val; @@ -225,8 +219,6 @@ export class DHImporter { if (firstColon > -1) { const name = line.substring(0, firstColon).trim(); const rest = line.substring(firstColon + 1).trim(); - // "Melee 1d8+3 phy" -> split by first space? or known ranges? - // Heuristic: Range is usually one word (Melee, Close, Far) const rangeMatch = rest.match(/^(\w+)\s+(.*)$/); if (rangeMatch) { parts = [name + ":" + rangeMatch[1], rangeMatch[2]]; diff --git a/styles/importer.css b/styles/importer.css index 325ef85..aad3c84 100644 --- a/styles/importer.css +++ b/styles/importer.css @@ -77,7 +77,7 @@ font-size: 1.1em; } -.dh-importer-app button { +.dh-importer-app .window-content button { background: linear-gradient(180deg, #5b1c1c 0%, #3a0e0e 100%); border: 1px solid #7a6a4a; color: #ffd700; @@ -90,14 +90,14 @@ transition: all 0.2s; } -.dh-importer-app button:hover { +.dh-importer-app .window-content button:hover { background: linear-gradient(180deg, #7a2828 0%, #561414 100%); box-shadow: 0 0 8px #cbb484; text-shadow: 0 0 5px #ffd700; border-color: #ffd700; } -.dh-importer-app button i { +.dh-importer-app .window-content button i { margin-right: 5px; } From b06d89c20e57630ffa910fb708f8f235ddc66ade Mon Sep 17 00:00:00 2001 From: CPTN Cosmo Date: Fri, 23 Jan 2026 20:23:35 +0100 Subject: [PATCH 2/9] updated title to macht foundryvtt.com module listing --- module.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module.json b/module.json index 35cba3e..2dba168 100644 --- a/module.json +++ b/module.json @@ -1,7 +1,7 @@ { "id": "dh-importer", - "title": "Daggerheart Importer", - "version": "1.0.1", + "title": "Daggerheart Statblock Importer", + "version": "1.0.2", "compatibility": { "minimum": "13", "verified": "13" @@ -34,5 +34,5 @@ "description": "Imports Adversaries and Environments from text blocks into the Daggerheart system.", "url": "https://github.com/cptn-cosmo/dh-importer", "manifest": "https://git.geeks.gay/cosmo/dh-importer/raw/branch/main/module.json", - "download": "https://git.geeks.gay/cosmo/dh-importer/releases/download/1.0.1/dh-importer.zip" + "download": "https://git.geeks.gay/cosmo/dh-importer/releases/download/1.0.2/dh-importer.zip" } \ No newline at end of file From bbf4ecbc873b86fbe28b2dfb29b4cba37b985fb3 Mon Sep 17 00:00:00 2001 From: CPTN Cosmo Date: Sat, 24 Jan 2026 10:52:49 +0100 Subject: [PATCH 3/9] fix default icons --- scripts/importer.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/scripts/importer.js b/scripts/importer.js index 6ad5614..66397d0 100644 --- a/scripts/importer.js +++ b/scripts/importer.js @@ -16,6 +16,8 @@ export class DHImporter { const data = { name: "Unknown Adversary", type: "adversary", + img: "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", + prototypeToken: { texture: { src: "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg" } }, system: { tier: 1, type: "standard", @@ -282,13 +284,32 @@ export class DHImporter { // sometimes with specific actions embedded. // We will put the type in the description. - return { + const isAction = buffer.typeHint?.toLowerCase() === "action"; + + const item = { name: buffer.name, type: "feature", + img: "icons/svg/item-bag.svg", system: { - description: `

${buffer.typeHint}: ${buffer.description}

` + description: `

${buffer.typeHint}: ${buffer.description}

`, + actions: {} } }; + + if (isAction) { + const actionId = foundry.utils.randomID(); + item.system.actions[actionId] = { + _id: actionId, + type: "attack", + name: buffer.name, + actionType: "action", + img: "icons/svg/item-bag.svg", + systemPath: "actions", + chatDisplay: true + }; + } + + return item; } /** @@ -308,6 +329,7 @@ export class DHImporter { const data = { name: lines[0], type: "environment", + img: "systems/daggerheart/assets/icons/documents/actors/forest.svg", system: { tier: 1, type: "exploration", From 74f123ecf2a3c5abe05076d9f3abb434db701cc0 Mon Sep 17 00:00:00 2001 From: CPTN Cosmo Date: Sat, 24 Jan 2026 11:21:05 +0100 Subject: [PATCH 4/9] improved importer to detect attack features and actions --- scripts/app.js | 49 ++++++++++ scripts/importer.js | 207 ++++++++++++++++++++++++++++++------------ styles/importer.css | 67 ++++++++++++++ templates/preview.hbs | 42 ++++++++- 4 files changed, 304 insertions(+), 61 deletions(-) diff --git a/scripts/app.js b/scripts/app.js index 0899031..dd72686 100644 --- a/scripts/app.js +++ b/scripts/app.js @@ -49,6 +49,27 @@ export class DHImporterApp extends HandlebarsApplicationMixin(ApplicationV2) { }; } + _onRender(context, options) { + super._onRender(context, options); + + // Bind File Pickers + this.element.querySelectorAll("img[data-edit]").forEach(img => { + img.addEventListener("click", ev => { + const field = img.dataset.edit; + const fp = new FilePicker({ + type: "image", + current: img.getAttribute("src"), + callback: path => { + img.src = path; + const input = this.element.querySelector(`input[name='${field}']`); + if (input) input.value = path; + } + }); + fp.browse(); + }); + }); + } + static async onParse(event, target) { const form = this.element; const text = form.querySelector("textarea[name='text']").value; @@ -89,12 +110,40 @@ export class DHImporterApp extends HandlebarsApplicationMixin(ApplicationV2) { const formData = new foundry.applications.ux.FormDataExtended(this.element).object; for (const actor of this.parsedData) { + // Update Actor Image + if (formData[`img.${actor.name}`]) { + actor.img = formData[`img.${actor.name}`]; + actor.prototypeToken.texture.src = formData[`img.${actor.name}`]; + } + + // Update Attack Image + if (formData[`attackImg.${actor.name}`]) { + actor.system.attack.img = formData[`attackImg.${actor.name}`]; + } + + // Open Sheet Flag + if (formData[`openSheet.${actor.name}`]) { + actor.openSheet = true; + } + actor.useFeatures = {}; for (const item of actor.items) { const key = `useFeatures.${actor.name}.${item.name}`; if (formData[key]) { actor.useFeatures[item.name] = formData[key]; } + + // Update Item Image + const itemImgKey = `itemImg.${actor.name}.${item.name}`; + if (formData[itemImgKey]) { + item.img = formData[itemImgKey]; + // Also update embedded action image if present + if (item.system.actions) { + for (const actionId in item.system.actions) { + item.system.actions[actionId].img = formData[itemImgKey]; + } + } + } } } diff --git a/scripts/importer.js b/scripts/importer.js index 66397d0..cbc5cd4 100644 --- a/scripts/importer.js +++ b/scripts/importer.js @@ -83,43 +83,53 @@ export class DHImporter { data.system.type = match[2].toLowerCase(); } else if (line.match(/^Difficulty:/i)) { - // Can be "Difficulty: 14" or "Difficulty: 11 | Thresholds: ..." - const valueMatch = line.match(/Difficulty:\s*(\d+)/i); - if (valueMatch) data.system.difficulty = parseInt(valueMatch[1]); + // Can be "Difficulty: 14" or "Difficulty: 11 | Thresholds: ..." or "Difficulty: 12Thresholds: 5/9HP: 4Stress: 3" - // Check for inline stuff (Old Format) + // Robust Regex Extraction + const diffMatch = line.match(/Difficulty:\s*(\d+)/i); + if (diffMatch) data.system.difficulty = parseInt(diffMatch[1]); + + const thMatch = line.match(/Thresholds:\s*(\d+)\/(\d+)/i); + if (thMatch) { + data.system.damageThresholds.major = parseInt(thMatch[1]); + data.system.damageThresholds.severe = parseInt(thMatch[2]); + } + + const hpMatch = line.match(/HP:\s*(\d+)/i); + if (hpMatch) data.system.resources.hitPoints.max = parseInt(hpMatch[1]); + + const stressMatch = line.match(/Stress:\s*(\d+)/i); + if (stressMatch) data.system.resources.stress.max = parseInt(stressMatch[1]); + + // Check for inline stuff (Old Format with Pipes) if (line.includes("|")) { const parts = line.split("|").map(p => p.trim()); for (const part of parts) { - if (part.startsWith("HP:")) { - data.system.resources.hitPoints.max = parseInt(part.split(":")[1]); - } else if (part.startsWith("Stress:")) { - data.system.resources.stress.max = parseInt(part.split(":")[1]); - } else if (part.startsWith("Thresholds:")) { - const th = part.split(":")[1].split("/").map(t => parseInt(t.trim())); - if (th[0]) data.system.damageThresholds.major = th[0]; - if (th[1]) data.system.damageThresholds.severe = th[1]; - } + // Fallback for pipes if regex somehow missed or overwrote (unlikely but safe) + // Actually the regex above is better. } } } else if (line.match(/^(Attack:|ATK:)/i)) { - // "Attack: +1" or "ATK: +2 | Claws: Melee | ..." + // "Attack: +1" or "ATK: +2 | Claws: Melee | ..." or "ATK: +2Claws: Melee1d6+2 phy" const bonusMatch = line.match(/(?:Attack|ATK):\s*([+-]?\d+)/i); if (bonusMatch) data.system.attack.roll.bonus = parseInt(bonusMatch[1]); + // Clean the line to remove "ATK: +2" part + let restOfLine = line.replace(/(?:Attack|ATK):\s*[+-]?\d+/, "").trim(); + // If split format e.g. "Attack: +1" and next line is "Claws: Melee..." - if (!line.includes("|") && lineIndex + 1 < lines.length) { + if ((!restOfLine || restOfLine.length === 0) && lineIndex + 1 < lines.length) { const nextLine = lines[lineIndex + 1]; if (nextLine.includes(":") && !nextLine.match(/^(Experience|Features|Motives|Difficulty)/)) { // Likely the weapon details: "Claws: Melee | 1d8+3 phy" DHImporter._parseAttackDetails(nextLine, data); lineIndex++; // Consume next line } - } else if (line.includes("|")) { - // Inline formatting - const parts = line.split("|").slice(1).join("|"); // remove ATK part - DHImporter._parseAttackDetails(parts, data); + } else if (restOfLine.length > 0) { + // Inline formatting (smashed or piped) + if (restOfLine.startsWith("|")) restOfLine = restOfLine.substring(1).trim(); + DHImporter._parseAttackDetails(restOfLine, data); } } else if (line.match(/^Experience:/i)) { @@ -182,8 +192,9 @@ export class DHImporter { // FEATURES else if (currentSection === "features") { - const featureMatch = line.match(/^(.+?)\s+[–-]\s+(\w+)(?::\s*(.*))?$/); // "Name - Type" (Description on next line) OR "Name - Type: Description" + // Supports hyphen (-), en-dash (–), em-dash (—) and optional surrounding spaces + const featureMatch = line.match(/^(.+?)\s*[–—-]\s*(\w+)(?::\s*(.*))?$/); if (featureMatch) { if (featureBuffer) data.items.push(DHImporter._createFeatureItem(featureBuffer)); @@ -212,51 +223,53 @@ export class DHImporter { } static _parseAttackDetails(line, data) { - // Claws: Melee | 1d8+3 phy OR Claws: Melee 1d8+3 phy - // Simple heuristic splitting - let parts = line.split("|").map(p => p.trim()); - if (parts.length === 1) { - // Try splitting by : for name/range - const firstColon = line.indexOf(":"); - if (firstColon > -1) { - const name = line.substring(0, firstColon).trim(); - const rest = line.substring(firstColon + 1).trim(); - const rangeMatch = rest.match(/^(\w+)\s+(.*)$/); - if (rangeMatch) { - parts = [name + ":" + rangeMatch[1], rangeMatch[2]]; - } else { - parts = [line]; - } - } + // Claws: Melee | 1d8+3 phy OR Claws: Melee 1d8+3 phy OR Claws: Melee1d6+2 phy + + // 1. Extract Name + let name = "Attack"; + let rest = line; + + if (line.includes(":")) { + const parts = line.split(":"); + name = parts[0].trim(); + rest = parts.slice(1).join(":").trim(); } - // Now parse parts roughly matching old logic - // Part 0: Name: Range - if (parts.length > 0) { - const nameRange = parts[0].split(":"); - data.system.attack.name = nameRange[0].trim(); - if (nameRange[1]) data.system.attack.range = nameRange[1].trim().toLowerCase(); + data.system.attack.name = name; + + // 2. Extract Range + // Ranges: Melee, Very Close, Close, Far, Very Far + const rangeMatch = rest.match(/^(Melee|Very Close|Close|Far|Very Far)/i); + if (rangeMatch) { + data.system.attack.range = rangeMatch[1].toLowerCase(); + rest = rest.substring(rangeMatch[0].length).trim(); } - // Part 1: Damage - if (parts.length > 1) { - const dmgStr = parts[1]; - const dmgMatch = dmgStr.match(/^(.+?)\s+(\w+)$/); - let formula = dmgStr; + + // 3. Extract Damage + // Formula + Type + if (rest) { + // Remove optional pipes + rest = rest.replace(/^\|\s*/, ""); + + // "1d6+2 phy" + const dmgMatch = rest.match(/^(.+?)\s+(\w+)$/); + let formula = rest; let type = "physical"; + if (dmgMatch) { formula = dmgMatch[1]; type = dmgMatch[2].toLowerCase(); - - // Map short codes if (type === "phy") type = "physical"; else if (type === "mag") type = "magic"; } + data.system.attack.damage.parts.push({ type: [type], applyTo: "hitPoints", value: { custom: { enabled: true, formula: formula }, - multiplier: "flat", dice: "d6" + multiplier: "flat", dice: "d6", + flatMultiplier: 1 } }); } @@ -292,21 +305,92 @@ export class DHImporter { img: "icons/svg/item-bag.svg", system: { description: `

${buffer.typeHint}: ${buffer.description}

`, - actions: {} + actions: {}, + featureForm: isAction ? "action" : "passive" } }; if (isAction) { const actionId = foundry.utils.randomID(); - item.system.actions[actionId] = { + const action = { _id: actionId, type: "attack", name: buffer.name, actionType: "action", img: "icons/svg/item-bag.svg", systemPath: "actions", - chatDisplay: true + chatDisplay: true, + cost: [], + damage: { parts: [], includeBase: false, direct: false }, + range: "", + roll: { + type: "action", // Default to action, switch to attack if detected + diceRolling: { multiplier: "flat", dice: "d6" } + } }; + + // Parse Fear Cost + if (buffer.description.match(/^spen[md]\s+(?:a|1)\s+fear/i)) { + action.cost.push({ + consumeOnSuccess: false, + scalable: false, + key: "fear", + value: 1, + itemId: null, + step: null + }); + } + + // Parse Stress Cost + if (buffer.description.match(/mark\s+(?:a|1)\s+stress/i)) { + action.cost.push({ + consumeOnSuccess: false, + scalable: false, + key: "stress", + value: 1, + itemId: null, + step: null + }); + } + + // Parse Attack Trigger + if (buffer.description.match(/make.*?attack/i)) { + action.roll.type = "attack"; + } + + // Parse Range + const rangeMatch = buffer.description.match(/(Melee|Very Close|Close|Far|Very Far)/i); + if (rangeMatch) { + action.range = rangeMatch[1].toLowerCase(); + } + + // Parse Damage: "deal 3d4+10 direct physical damage" + // Matches: context? + formula + optional direct + type + "damage" + const damageMatch = buffer.description.match(/(?:deal|inflict|take)\s+(\d+(?:d\d+)?(?:[\s]*[\+\-][\s]*\d+)?)\s+(direct\s+)?(\w+)\s+damage/i); + + if (damageMatch) { + // remove spaces in formula for cleaner data + let formula = damageMatch[1].replace(/\s/g, ""); + const isDirect = !!damageMatch[2]; // Captured "direct " + let type = damageMatch[3].toLowerCase(); + + // Map short codes + if (type === "phy") type = "physical"; + else if (type === "mag") type = "magic"; + + action.damage.direct = isDirect; + action.damage.parts.push({ + value: { + custom: { enabled: true, formula: formula }, + multiplier: "flat", dice: "d6", + flatMultiplier: 1 // Default + }, + applyTo: "hitPoints", + type: [type] + }); + } + + item.system.actions[actionId] = action; } return item; @@ -379,7 +463,7 @@ export class DHImporter { currentSection = "features"; } else if (currentSection === "features") { // Parsing Features: "Name - Type: Description" - const featureMatch = line.match(/^(.+?)\s+[–-]\s+(\w+):\s*(.*)/); + const featureMatch = line.match(/^(.+?)\s*[–—-]\s*(\w+):\s*(.*)/); if (featureMatch) { if (featureBuffer) { data.items.push(DHImporter._createFeatureItem(featureBuffer)); @@ -472,12 +556,23 @@ export class DHImporter { data.items = finalItems; // Clean up temporary flags + const shouldOpen = data.openSheet; delete data.foundFeatures; delete data.useFeatures; + delete data.openSheet; - actorsToCreate.push(data); + actorsToCreate.push({ data, shouldOpen }); } - await Actor.createDocuments(actorsToCreate); + const createdActors = await Actor.createDocuments(actorsToCreate.map(a => a.data)); + + // Open sheets + if (createdActors && createdActors.length > 0) { + for (let i = 0; i < createdActors.length; i++) { + if (actorsToCreate[i].shouldOpen) { + createdActors[i].sheet.render(true); + } + } + } } } diff --git a/styles/importer.css b/styles/importer.css index aad3c84..18c6afc 100644 --- a/styles/importer.css +++ b/styles/importer.css @@ -254,4 +254,71 @@ gap: 10px; padding-top: 10px; border-top: 1px solid #4a4a4a; +} + +/* --- New Styles for Image Pickers --- */ + +.dh-importer-preview .header-row { + display: flex; + align-items: center; + gap: 15px; + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 10px; +} + +.dh-importer-preview .header-row h3 { + margin: 0; + border: none; + padding: 0; + flex: 1; +} + +.dh-importer-preview .image-picker { + position: relative; + cursor: pointer; + border: 1px solid #7a6a4a; + border-radius: 4px; + overflow: hidden; + transition: box-shadow 0.2s; + flex-shrink: 0; +} + +.dh-importer-preview .image-picker:hover { + box-shadow: 0 0 5px #ffd700; +} + +.dh-importer-preview .image-picker img { + display: block; + object-fit: cover; + background: #000; +} + +.dh-importer-preview .attack-pill { + display: inline-flex; + align-items: center; + background: #2a2a2a; + padding: 4px 16px 4px 8px; + /* Adjusted padding for icon */ + border-radius: 4px; + border: 1px solid #444; + color: #ccc; + font-size: 1.1em; + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.5); + gap: 5px; +} + +.dh-importer-preview .attack-pill .image-picker { + border: none; + box-shadow: none; + background: transparent; +} + +.dh-importer-preview .feature-header { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; + margin-bottom: 5px; } \ No newline at end of file diff --git a/templates/preview.hbs b/templates/preview.hbs index 6bebe31..cc25c33 100644 --- a/templates/preview.hbs +++ b/templates/preview.hbs @@ -2,16 +2,34 @@
{{#each parsedData as |actor idx|}}
-

- {{actor.name}} - Tier {{actor.system.tier}} {{actor.system.type}} -

+
+
+ + +
+

+ {{actor.name}} + Tier {{actor.system.tier}} {{actor.system.type}} +

+
{{#if actor.system.resources.hitPoints.max}}HP: {{actor.system.resources.hitPoints.max}}{{/if}} {{#if actor.system.resources.stress.max}}Stress: {{actor.system.resources.stress.max}}{{/if}} Difficulty: {{actor.system.difficulty}} + {{#if actor.system.attack.name}} + {{#if actor.system.attack.name}} + +
+ + +
+ Attack: {{actor.system.attack.name}} +
+ {{/if}} + {{/if}}
@@ -20,7 +38,15 @@ {{#each actor.items as |item itemIdx|}} {{#if (eq item.type "feature")}}
  • - {{item.name}} +
    +
    + + +
    + {{item.name}} +
    {{#if (lookup ../foundFeatures item.name)}}
    @@ -40,6 +66,12 @@ {{/each}}
    + +
    + +
  • {{/each}}
    From fd0e718a1c8fba4f402419aae747ead04c0e7b4e Mon Sep 17 00:00:00 2001 From: CPTN Cosmo Date: Sat, 24 Jan 2026 11:21:34 +0100 Subject: [PATCH 5/9] version update --- module.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module.json b/module.json index 2dba168..8f8ee2e 100644 --- a/module.json +++ b/module.json @@ -1,7 +1,7 @@ { "id": "dh-importer", "title": "Daggerheart Statblock Importer", - "version": "1.0.2", + "version": "1.1.0", "compatibility": { "minimum": "13", "verified": "13" @@ -34,5 +34,5 @@ "description": "Imports Adversaries and Environments from text blocks into the Daggerheart system.", "url": "https://github.com/cptn-cosmo/dh-importer", "manifest": "https://git.geeks.gay/cosmo/dh-importer/raw/branch/main/module.json", - "download": "https://git.geeks.gay/cosmo/dh-importer/releases/download/1.0.2/dh-importer.zip" + "download": "https://git.geeks.gay/cosmo/dh-importer/releases/download/1.1.0/dh-importer.zip" } \ No newline at end of file From b6a1af99263064bcb543119740cd3fd90a5b5a84 Mon Sep 17 00:00:00 2001 From: CPTN Cosmo Date: Sat, 24 Jan 2026 11:39:37 +0100 Subject: [PATCH 6/9] fixed range detection for actions --- module.json | 4 ++-- scripts/importer.js | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/module.json b/module.json index 8f8ee2e..7c240de 100644 --- a/module.json +++ b/module.json @@ -1,7 +1,7 @@ { "id": "dh-importer", "title": "Daggerheart Statblock Importer", - "version": "1.1.0", + "version": "1.1.1", "compatibility": { "minimum": "13", "verified": "13" @@ -34,5 +34,5 @@ "description": "Imports Adversaries and Environments from text blocks into the Daggerheart system.", "url": "https://github.com/cptn-cosmo/dh-importer", "manifest": "https://git.geeks.gay/cosmo/dh-importer/raw/branch/main/module.json", - "download": "https://git.geeks.gay/cosmo/dh-importer/releases/download/1.1.0/dh-importer.zip" + "download": "https://git.geeks.gay/cosmo/dh-importer/releases/download/1.1.1/dh-importer.zip" } \ No newline at end of file diff --git a/scripts/importer.js b/scripts/importer.js index cbc5cd4..2ad4f3b 100644 --- a/scripts/importer.js +++ b/scripts/importer.js @@ -241,7 +241,10 @@ export class DHImporter { // Ranges: Melee, Very Close, Close, Far, Very Far const rangeMatch = rest.match(/^(Melee|Very Close|Close|Far|Very Far)/i); if (rangeMatch) { - data.system.attack.range = rangeMatch[1].toLowerCase(); + let rangeVal = rangeMatch[1].toLowerCase(); + if (rangeVal === "very close") rangeVal = "veryClose"; + if (rangeVal === "very far") rangeVal = "veryFar"; + data.system.attack.range = rangeVal; rest = rest.substring(rangeMatch[0].length).trim(); } @@ -260,7 +263,7 @@ export class DHImporter { formula = dmgMatch[1]; type = dmgMatch[2].toLowerCase(); if (type === "phy") type = "physical"; - else if (type === "mag") type = "magic"; + else if (type === "mag" || type === "magic") type = "magical"; } data.system.attack.damage.parts.push({ @@ -324,7 +327,7 @@ export class DHImporter { damage: { parts: [], includeBase: false, direct: false }, range: "", roll: { - type: "action", // Default to action, switch to attack if detected + type: "attack", // Default to attack diceRolling: { multiplier: "flat", dice: "d6" } } }; @@ -361,7 +364,11 @@ export class DHImporter { // Parse Range const rangeMatch = buffer.description.match(/(Melee|Very Close|Close|Far|Very Far)/i); if (rangeMatch) { - action.range = rangeMatch[1].toLowerCase(); + let rangeVal = rangeMatch[1].toLowerCase(); + if (rangeVal === "very close") rangeVal = "veryClose"; + if (rangeVal === "very far") rangeVal = "veryFar"; + + action.range = rangeVal; } // Parse Damage: "deal 3d4+10 direct physical damage" @@ -376,7 +383,7 @@ export class DHImporter { // Map short codes if (type === "phy") type = "physical"; - else if (type === "mag") type = "magic"; + else if (type === "mag" || type === "magic") type = "magical"; action.damage.direct = isDirect; action.damage.parts.push({ From 3e9bc42ca443f8fb86b9cea4f54421fa3c8d9292 Mon Sep 17 00:00:00 2001 From: CPTN Cosmo Date: Sat, 24 Jan 2026 12:02:46 +0100 Subject: [PATCH 7/9] Going back to the input from the preview no longer empties the textfield, allowing for easier edits before importing --- module.json | 4 ++-- scripts/app.js | 7 ++++++- scripts/importer.js | 12 ++++++++++-- templates/importer.hbs | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/module.json b/module.json index 7c240de..90458b6 100644 --- a/module.json +++ b/module.json @@ -1,7 +1,7 @@ { "id": "dh-importer", "title": "Daggerheart Statblock Importer", - "version": "1.1.1", + "version": "1.2.0", "compatibility": { "minimum": "13", "verified": "13" @@ -34,5 +34,5 @@ "description": "Imports Adversaries and Environments from text blocks into the Daggerheart system.", "url": "https://github.com/cptn-cosmo/dh-importer", "manifest": "https://git.geeks.gay/cosmo/dh-importer/raw/branch/main/module.json", - "download": "https://git.geeks.gay/cosmo/dh-importer/releases/download/1.1.1/dh-importer.zip" + "download": "https://git.geeks.gay/cosmo/dh-importer/releases/download/1.2.0/dh-importer.zip" } \ No newline at end of file diff --git a/scripts/app.js b/scripts/app.js index dd72686..2d30b66 100644 --- a/scripts/app.js +++ b/scripts/app.js @@ -8,6 +8,7 @@ export class DHImporterApp extends HandlebarsApplicationMixin(ApplicationV2) { this.step = "input"; this.parsedData = null; this.parsedDataType = null; + this.inputText = ""; } static DEFAULT_OPTIONS = { @@ -45,7 +46,8 @@ export class DHImporterApp extends HandlebarsApplicationMixin(ApplicationV2) { types: { adversary: "Adversary", environment: "Environment" - } + }, + inputText: this.inputText }; } @@ -75,6 +77,9 @@ export class DHImporterApp extends HandlebarsApplicationMixin(ApplicationV2) { const text = form.querySelector("textarea[name='text']").value; const type = form.querySelector("select[name='type']").value; + // Store input text + this.inputText = text; + if (!text) { ui.notifications.warn("Please enter text to import."); return; diff --git a/scripts/importer.js b/scripts/importer.js index 2ad4f3b..1500f3c 100644 --- a/scripts/importer.js +++ b/scripts/importer.js @@ -314,10 +314,18 @@ export class DHImporter { }; if (isAction) { + // Determine if this is an attack or a generic ability + const isAttack = buffer.description.match(/make.*?attack/i) || + buffer.description.match(/(?:deal|inflict|take)\s+(\d+(?:d\d+)?(?:[\s]*[\+\-][\s]*\d+)?)\s+(direct\s+)?(\w+)\s+damage/i); + + const actionType = isAttack ? "attack" : "ability"; + const rollType = isAttack ? "attack" : "ability"; + const actionId = foundry.utils.randomID(); + const action = { _id: actionId, - type: "attack", + type: actionType, name: buffer.name, actionType: "action", img: "icons/svg/item-bag.svg", @@ -327,7 +335,7 @@ export class DHImporter { damage: { parts: [], includeBase: false, direct: false }, range: "", roll: { - type: "attack", // Default to attack + type: rollType, diceRolling: { multiplier: "flat", dice: "d6" } } }; diff --git a/templates/importer.hbs b/templates/importer.hbs index 833799c..4ad3828 100644 --- a/templates/importer.hbs +++ b/templates/importer.hbs @@ -13,7 +13,7 @@
    - +
    +
    +
    + + +
    +
    + +
    +
    +