From 8ff1fe64948aa2c6cd938a969caea4289f84036e Mon Sep 17 00:00:00 2001 From: Zen-Raven Date: Tue, 4 Nov 2025 09:48:43 -0800 Subject: [PATCH 1/6] Added Macro Titles - Added more macro titles to inputs --- Draw Steel/draw-steel.html | 44 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Draw Steel/draw-steel.html b/Draw Steel/draw-steel.html index 68bb7dfe094f..27b93f520b20 100644 --- a/Draw Steel/draw-steel.html +++ b/Draw Steel/draw-steel.html @@ -294,31 +294,31 @@
- + - + - +
- + - + - +
- - - - - - + + + + + +
@@ -330,19 +330,19 @@
- + - + - + - + - +
- +
@@ -357,8 +357,8 @@
- - + +
@@ -378,8 +378,8 @@
- - + +
From e3ebfc25457c1049e3bad30b2ccd86950271c6fd Mon Sep 17 00:00:00 2001 From: Zen-Raven Date: Tue, 4 Nov 2025 10:25:18 -0800 Subject: [PATCH 2/6] Delay Potency Feature - Commented out section of new potency feature to allow time to get it prepared before release. --- Draw Steel/draw-steel.html | 240 +++++++++++++++++++++++++++++++++++-- 1 file changed, 227 insertions(+), 13 deletions(-) diff --git a/Draw Steel/draw-steel.html b/Draw Steel/draw-steel.html index 27b93f520b20..c63b7a98d90f 100644 --- a/Draw Steel/draw-steel.html +++ b/Draw Steel/draw-steel.html @@ -7,6 +7,10 @@

style="height: 60px" />

+ + + +
@@ -27,6 +31,7 @@

Paste the contents of the Forge Steel data export (*.ds-hero file) into the text box below, then click "Import".

This will overwrite attributes on this sheet. This cannot be undone.

+

The import feature is a work in progress. Check for mistakes.

@@ -157,20 +162,20 @@
-
- +
+
-
- +
+
-
- +
+
@@ -193,6 +198,10 @@
+
+ + +
@@ -284,6 +293,16 @@
+
+

Save Ends =

+ +

or higher at the end of your turn removes the effect

+ +
+
+

Bleeding or Dying:

+ +
@@ -585,13 +604,46 @@

+ + + +
+ + +
+ + < [weak] + +
+
+
+ + +
+ + < [average] + +
+
+
+ + +
+ + < [strong] + +
- - - - - -
@@ -891,6 +943,29 @@

+ +
+ + + + + +
+ + +
+ + +
+ + +
+
+
+
@@ -2319,6 +2394,14 @@
+
+

Save Ends = 6 or higher at the end of your turn removes the effect

+ +
+
+

Bleeding or Dying:

+ +
@@ -3093,6 +3176,64 @@
buildRoll(ROLLTEMPLATES.POWERROLL, {characteristic},{characteristicValue}, true); } + /** + ** Simple Roll to display Recovery use + ** And adjust automatically in sheet + **/ + async function rollRecovery() { + console.log("~~~ rollRecovery ~~~"); + // Get the recovery amount and populate the translations for use in the roll. + const attrs = await getAttrsAsync(['recovery_amount']); + const recovery_amount = attrs.recovery_amount; + + buildRoll(ROLLTEMPLATES.SIMPLE, { name: "Recovery", rolltitle: "Stamina Gain", roll: `[[${recovery_amount}]]` }, {} ); + } + + function calculateRecoverySpent() { + console.log('~~~ calculateRecoverySpent ~~~'); + getAttrs(["stamina_max", "recovery_amount", "stamina", "recoveries"], function(values) { + let stamina_max = parseInt(values.stamina_max)||0; + let recovery_amount = parseInt(values.recovery_amount)||0; + let stamina = parseInt(values.stamina)||0; + let stamina_set = Math.min(stamina_max, stamina + recovery_amount); + let recoveries = parseInt(values.recoveries)||0; + let recoveries_set = Math.max(recoveries - 1,0); + setAttrs({ + "stamina": stamina_set, + "recoveries": recoveries_set + }); + }); + } + + /** + ** Simple Roll to display Heroic Resource gain + ** And adjust automatically in sheet + **/ + async function rollResource() { + console.log("~~~ rollResource ~~~"); + // Get the resource name. + const attrs = await getAttrsAsync(['resource_name']); + const resource_name = attrs.resource_name; + + buildRoll(ROLLTEMPLATES.SIMPLE, { name: "@{resource_name} Gain", rolltitle: "@{resource_name}", roll: `[[?{Type d3 or number|0}]]` }, {} ); + } + + /** + ** Simple Roll to apply Surges + ** And adjust automatically in sheet + **/ + async function rollSurges() { + console.log("~~~ rollSurges ~~~"); + const attrs = await getAttrsAsync(["might", "agility", "reason", "intuition", "presence"]); + let highest = 0; + for(const val of Object.values(attrs)) { + const num = parseInt(val) || 0; + highest = Math.max(highest, num); + } + + buildRoll(ROLLTEMPLATES.SIMPLE, { name: "Using Surges", rolltitle: "Surges", roll: `?{Choose|Damage,?{How many?|1,[[1*${highest}]] additional damage|2,[[2*${highest}]] additional damage|3,[[3*${highest}]] additional damage}|Potency,2 surges used to increase potency by 1}` }, {} ); + } + /** ** Displays a feature using the monsterability template ** @param {string} name The feature's name. @@ -3411,6 +3552,7 @@
'charge': false, 'defend': false, 'heal': false, + 'convert': false, 'aid_attack': false, 'catch_breath': false, 'hide': false, @@ -3462,6 +3604,25 @@
const characteristic = e.htmlAttributes['data-characteristic']; rollPower(characteristic); }); + + // Recovery Simple Roll + + $20('.recovery-input-group > label').on('click', (e) => { + rollRecovery(); + calculateRecoverySpent(); + }); + + // Heroic Resource Simple Roll + + $20('.resource-input-group > label').on('click', (e) => { + rollResource(); + }); + + // Surges Simple Roll + + $20('.surges-input-group > label').on('click', (e) => { + rollSurges(); + }); // Configuration map for feature sharing // Maps the trigger event to its specific attribute names and translation key @@ -3557,6 +3718,59 @@
}); } +// Start Combat Calculation + on("clicked:startcombat", function() { + calculateStartCombat(); + }); + + function calculateStartCombat() { + console.log('~~~ calculateStartCombat ~~~'); + getAttrs(["victories"], function(values) { + let victories = parseInt(values.victories)||0; + setAttrs({ + "resource": victories + }); + }); + } + +// End Combat Calculation + on("clicked:endcombat", function() { + calculateEndCombat(); + }); + + function calculateEndCombat() { + console.log('~~~ calculateEndCombat ~~~'); + getAttrs(["resource", "surges"], function(values) { + let resource = parseInt(values.resource)||0; + let surges = parseInt(values.surges)||0; + setAttrs({ + "resource": 0, + "surges": 0 + }); + }); + } + +// Respite Calculation + on("clicked:respite", function() { + calculateRespite(); + }); + + function calculateRespite() { + console.log('~~~ calculateRespite ~~~'); + getAttrs(["stamina_max", "recoveries_max", "victories", "experience"], function(values) { + let stamina_max = parseInt(values.stamina_max)||0; + let recoveries_max = parseInt(values.recoveries_max)||0; + let victories = parseInt(values.victories)||0; + let experience = parseInt(values.experience)||0; + setAttrs({ + "stamina": stamina_max, + "recoveries": recoveries_max, + "experience": experience + victories, + "victories": 0, + }); + }); + } + /** ** Import from Forge Steel Start **/ @@ -4490,4 +4704,4 @@
calculateSurgePotency(); }); - + \ No newline at end of file From aa9dedc922d63391fb7fb1626220b45ff6aa79a2 Mon Sep 17 00:00:00 2001 From: FoxRobinHood Date: Tue, 18 Nov 2025 12:05:49 -0600 Subject: [PATCH 3/6] Added pre-effect and opportunity attack --- Draw Steel/draw-steel.html | 124 ++++++++++++++++++++++++++++--------- 1 file changed, 96 insertions(+), 28 deletions(-) diff --git a/Draw Steel/draw-steel.html b/Draw Steel/draw-steel.html index b2fd872e9ba1..1aec8c54c07b 100644 --- a/Draw Steel/draw-steel.html +++ b/Draw Steel/draw-steel.html @@ -625,6 +625,11 @@

+
+ + + +
@@ -725,6 +730,11 @@

+ +
+ + +
@@ -1039,6 +1049,11 @@

+
+ + + +
@@ -1097,6 +1112,11 @@

+ +
+ + +
@@ -1553,7 +1573,7 @@

- @@ -1613,6 +1633,11 @@

+
+ + + +
@@ -1671,6 +1696,11 @@

+ +
+ + +
@@ -1707,6 +1737,37 @@

+ +
+ +
+ + + + + + +
+ + +
+ + +
+ + +
+
+ + +
+
+
+
+
@@ -2485,6 +2546,11 @@
+ +
+ + + +
@@ -2548,6 +2614,11 @@
+ +
+ : + +
@@ -2649,6 +2720,12 @@
{{trigger}}
{{/rollTotal() hasTrigger 1}} + {{#rollTotal() hasPreEffect 1}} +
+
:
+
{{pre_effect}}
+
+ {{/rollTotal() hasPreEffect 1}} {{#rollTotal() hasPowerRoll 1}}
{{powerRoll}}:
@@ -2903,6 +2980,12 @@
{{trigger}}
{{/rollTotal() hasTrigger 1}} + {{#rollTotal() hasPreEffect 1}} +
+
:
+
{{pre_effect}}
+
+ {{/rollTotal() hasPreEffect 1}} {{#rollTotal() hasPowerRoll 1}}
{{powerRoll}}:
@@ -3345,11 +3428,12 @@
'subtype', 'malice_cost', 'trait', 'solo_1_name', 'solo_1_text', 'solo_2_name', 'solo_2_text', 'special', 'malice', 'spend', 'custom', 'spend_cost', 'custom_label', + 'pre_effect' ]; const allAbilityToggles = [ 'has_power_roll', 'has_effect', 'has_special', 'has_malice', - 'has_spend', 'has_custom', + 'has_spend', 'has_custom', 'has_pre_effect' ]; const abPrefix = abilityPrefix ? `${abilityPrefix}_` : ''; @@ -3476,6 +3560,7 @@
tier3: attrs[`${prefix}_${abilityPrefix}tier_3`] ? damageCalculation(attrs[`${prefix}_${abilityPrefix}tier_3`], kit[2] || 0) : '', special: attrs[`${prefix}_${abilityPrefix}special`] || '', effect: attrs[`${prefix}_${abilityPrefix}effect`] || '', + pre_effect: attrs[`${prefix}_${abilityPrefix}pre_effect`] || '', malprop: attrs[`${prefix}_${abilityPrefix}malice`] || '', malpropcost: attrs[`${prefix}_${abilityPrefix}cost`] || '', solo1name: attrs[`${prefix}_${abilityPrefix}solo_1_name`] || '', @@ -3497,6 +3582,7 @@
hasPowerRoll: attrs[`${prefix}_has_power_roll`] === 'on' ? 1 : 0, hasSpecial: attrs[`${prefix}_has_special`] === 'on' ? 1 : 0, hasEffect: hasEffect, + hasPreEffect: attrs[`${prefix}_has_pre_effect`] === 'on' ? 1 : 0, hasMalprop: attrs[`${prefix}_has_malice`] === 'on' ? 1 : 0, hasSpend: attrs[`${prefix}_has_spend`] === 'on' ? 1 : 0, hasCustom: attrs[`${prefix}_has_custom`] === 'on' ? 1 : 0, @@ -3636,7 +3722,8 @@
'hide': false, 'search': false, 'standup': false, - 'consumable': false + 'consumable': false, + 'opportunity_attack': false }; for (const [action, hasPowerRoll] of Object.entries(simpleActionHandlers)) { @@ -4223,32 +4310,13 @@
const disengage = calculateCombatStat(allFeatures, 'Disengage', 1); const stability = calculateCombatStat(allFeatures, 'Stability', 0); - // --- Stamina & Recoveries (including Kit stats) --- - // 1) Base stamina from Bonus features (class, ancestry, etc.) - const staminaFromBonuses = allFeatures - .filter(f => f.type === FEATURE_TYPES.BONUS && f.data.field === 'Stamina') - .reduce((sum, f) => { - const base = Number(f.data.value) || 0; - const perLevel = Number(f.data.valuePerLevel) || 0; - return sum + base + ((level - 1) * perLevel); - }, 0); - - // 2) Stamina from Kits (KitStats), using the highest equipped kit value - // This mirrors how calculateCombatStat uses KIT_STATS for Speed/Stability. - const staminaFromKits = Math.max( - 0, // avoid -Infinity if no kits - ...allFeatures - .filter(f => f.type === FEATURE_TYPES.KIT_STATS) - .map(f => Number(f.stamina) || 0) - ); - - // 3) Final maximum Stamina - const stamina_max = staminaFromBonuses + staminaFromKits; - - const recoveries_max = allFeatures - .filter(f => f.type === FEATURE_TYPES.BONUS && f.data.field === 'Recoveries') - .reduce((sum, f) => sum + (Number(f.data.value) || 0), 0); + const stamina_max = allFeatures + .filter(f => f.type === FEATURE_TYPES.BONUS && f.data.field === 'Stamina') + .reduce((sum, f) => sum + (f.data.value + ((level - 1) * f.data.valuePerLevel)), 0); + const recoveries_max = allFeatures + .filter(f => f.type === FEATURE_TYPES.BONUS && f.data.field === 'Recoveries') + .reduce((sum, f) => sum + f.data.value, 0); const immunity = getDamageModifiers(allFeatures, level, 'Immunity'); const weakness = getDamageModifiers(allFeatures, level, 'Weakness'); From bab4d30b07159c8d974315d132c9f023bb789a2d Mon Sep 17 00:00:00 2001 From: FoxRobinHood Date: Tue, 18 Nov 2025 12:06:24 -0600 Subject: [PATCH 4/6] Added pre-effect and opportunity attack --- Draw Steel/draw-steel.css | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Draw Steel/draw-steel.css b/Draw Steel/draw-steel.css index fc1df81a2877..ac2413c7835a 100644 --- a/Draw Steel/draw-steel.css +++ b/Draw Steel/draw-steel.css @@ -1332,7 +1332,9 @@ label[for*='minimize']::before { .ability-details:has([name="attr_has_power_roll"]:not(:checked)) > .power-roll .power-roll-mods label[data-i18n="potency"], .ability-details:has([name="attr_has_potency"]:not(:checked)) .potency-tier-1, .ability-details:has([name="attr_has_potency"]:not(:checked)) .potency-tier-2, -.ability-details:has([name="attr_has_potency"]:not(:checked)) .potency-tier-3 { +.ability-details:has([name="attr_has_potency"]:not(:checked)) .potency-tier-3, +.ability-details:has([name="attr_has_power_roll"]:not(:checked)) > .power-roll .power-roll-mods input[name="attr_has_pre_effect"], +.ability-details:has([name="attr_has_power_roll"]:not(:checked)) > .power-roll .power-roll-mods label[data-i18n="pre-effect"] { display: none; } @@ -1350,14 +1352,21 @@ label[for*='minimize']::before { display: none; } -.ability-card .ability-effect, .ability-card .ability-trigger { +.ability-card .ability-effect, +.ability-card .ability-trigger { height: auto; } +.ability-card .ability-pre-effect { + height: auto; + margin-bottom: 0.2em; +} + .ability-custom, .ability-malice, .ability-trigger > span, -.ability-effect > span { +.ability-effect > span, +.ability-pre-effect > span { white-space-collapse: preserve; } @@ -2069,6 +2078,7 @@ label[for="npc-checkbox"] { background-color: var(--color-bg-section); border: var(--border-main); border-radius: var(--border-radius); + margin-bottom: 14px; } .sheet-rolltemplate-monsterability .sheet-effect-text, From 00b42ff63ec57e5d34b8b332eed027df9126d64d Mon Sep 17 00:00:00 2001 From: FoxRobinHood Date: Tue, 18 Nov 2025 12:07:00 -0600 Subject: [PATCH 5/6] Added pre-effect and opportunity attack --- Draw Steel/translation.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Draw Steel/translation.json b/Draw Steel/translation.json index 2af4ff095b78..b0e587f3c313 100644 --- a/Draw Steel/translation.json +++ b/Draw Steel/translation.json @@ -143,6 +143,7 @@ "trigger": "Trigger", "power-roll": "Power Roll", "effect": "Effect", + "pre-effect": "Pre-roll Effect", "knockback": "Knockback", "knockback-keywords": "Melee, Weapon", "one-creature": "One Creature", From f3909b32ff7ec5c71455d20ca46120e34ed8931c Mon Sep 17 00:00:00 2001 From: Zen-Raven Date: Tue, 18 Nov 2025 10:53:41 -0800 Subject: [PATCH 6/6] Damage Calc Updated damage calculations to use new magic/psionic and sanctified weapon fields --- Draw Steel/draw-steel.html | 29 +++++++++++++++++++++++------ Draw Steel/translation.json | 4 +++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Draw Steel/draw-steel.html b/Draw Steel/draw-steel.html index c63b7a98d90f..fcca8f37dd08 100644 --- a/Draw Steel/draw-steel.html +++ b/Draw Steel/draw-steel.html @@ -3038,7 +3038,7 @@
SIMPLE: 'simpleroll', } /** - ** Builds and executes a roll using the rolltemplate + ** Processes two sources of information and constructs the roll string used by Roll20. Then it executes the roll. ** @param {string} templateName The template to be used for the roll ** @param {Array} directMap A dictionary of rolltemplate keys and their values. ** @param {Array} rollValueMap A dictionary of rolltemplate keys and their values that will be added as inline rolls. @@ -3279,7 +3279,7 @@
// 2. Define the character-level attributes needed for both. const globalAttrsToGet = prefix === 'repeating_npcabilities' ? [] // Monster abilities need nothing - : ['resource_name','kit_melee_damage_1','kit_melee_damage_2','kit_melee_damage_3','kit_ranged_damage_1','kit_ranged_damage_2','kit_ranged_damage_3' ]; // Player abilities need resource names and kit damage info + : ['resource_name','kit_melee_damage_1','kit_melee_damage_2','kit_melee_damage_3','kit_ranged_damage_1','kit_ranged_damage_2','kit_ranged_damage_3', 'mag_psi_dam', 'sanct_weap' ]; // Player abilities need resource names and kit damage info const characteristics = isPowerRoll ? ['might', 'agility', 'reason', 'intuition', 'presence'] : []; @@ -3363,6 +3363,7 @@
const weapon = getTranslationByKey('weapon'); const abilityKeywords = attrs[`${prefix}_${abilityPrefix}keywords`] ? attrs[`${prefix}_${abilityPrefix}keywords`].split(',').map(item => item.trim()) : ''; const kit = []; + let otherdamage = ""; if (abilityKeywords.includes(weapon)) { if (abilityKeywords.includes(melee)) { kit.push(parseInt(attrs['kit_melee_damage_1'])); @@ -3373,7 +3374,23 @@
kit.push(parseInt(attrs['kit_ranged_damage_2'])); kit.push(parseInt(attrs['kit_ranged_damage_3'])); } + if (attrs['sanct_weap']) { + const sanctweaptrans = getTranslationByKey('sanct-weap'); + otherdamage = otherdamage + '+' + attrs['sanct_weap'] + `[${sanctweaptrans}]`; + } } + if (attrs['mag_psi_dam']) { + const psionic = getTranslationByKey('psionic'); + const magic = getTranslationByKey('magic'); + + if (abilityKeywords.includes(psionic)) { + otherdamage = otherdamage + '+' + attrs['mag_psi_dam'] + `[${psionic}]`; + } + else if (abilityKeywords.includes(magic)) { + otherdamage = otherdamage + '+' + attrs['mag_psi_dam'] + `[${magic}]`; + } + } + @@ -3393,9 +3410,9 @@
target: attrs[`${prefix}_${abilityPrefix}target`] || '', trigger: attrs[`${prefix}_${abilityPrefix}trigger`] || '', characteristic: characteristic, - tier1: attrs[`${prefix}_${abilityPrefix}tier_1`] ? damageCalculation(attrs[`${prefix}_${abilityPrefix}tier_1`], kit[0] || 0) : '', - tier2: attrs[`${prefix}_${abilityPrefix}tier_2`] ? damageCalculation(attrs[`${prefix}_${abilityPrefix}tier_2`], kit[1] || 0) : '', - tier3: attrs[`${prefix}_${abilityPrefix}tier_3`] ? damageCalculation(attrs[`${prefix}_${abilityPrefix}tier_3`], kit[2] || 0) : '', + tier1: attrs[`${prefix}_${abilityPrefix}tier_1`] ? damageCalculation(attrs[`${prefix}_${abilityPrefix}tier_1`], kit[0] || 0, otherdamage) : '', + tier2: attrs[`${prefix}_${abilityPrefix}tier_2`] ? damageCalculation(attrs[`${prefix}_${abilityPrefix}tier_2`], kit[1] || 0, otherdamage) : '', + tier3: attrs[`${prefix}_${abilityPrefix}tier_3`] ? damageCalculation(attrs[`${prefix}_${abilityPrefix}tier_3`], kit[2] || 0, otherdamage) : '', special: attrs[`${prefix}_${abilityPrefix}special`] || '', effect: attrs[`${prefix}_${abilityPrefix}effect`] || '', malprop: attrs[`${prefix}_${abilityPrefix}malice`] || '', @@ -3496,7 +3513,7 @@
} if (other) { - //Add Other Damage + damageCalc.push(other); } // rebuild the tier effect String const newTierEffect = '[[' + diff --git a/Draw Steel/translation.json b/Draw Steel/translation.json index 41be8a88875a..c135e8efa7d2 100644 --- a/Draw Steel/translation.json +++ b/Draw Steel/translation.json @@ -203,5 +203,7 @@ "custom": "Custom", "custom-text": "Custom text", "characteristic-roll-tooltip": "Click to make a power roll with this characteristic.", - "roll-d3-tooltip": "Roll 1d3" + "roll-d3-tooltip": "Roll 1d3", + "psionic": "Psionic", + "magic": "Magic" }