diff --git a/Table Top Defined/Table_Top_Defined.css b/Table Top Defined/Table_Top_Defined.css index e55b314798..8f6249b71d 100644 --- a/Table Top Defined/Table_Top_Defined.css +++ b/Table Top Defined/Table_Top_Defined.css @@ -213,7 +213,8 @@ .charsheet .sheet-spells, .charsheet .sheet-accessories, .charsheet .sheet-inventory, -.charsheet .sheet-profitloss{ +.charsheet .sheet-profitloss, +.charsheet .sheet-settings{ display: none; text-align: center; } @@ -227,24 +228,41 @@ .charsheet .sheet-tabstoggle[value="spells"] ~ div.sheet-spells, .charsheet .sheet-tabstoggle[value="accessories"] ~ div.sheet-accessories, .charsheet .sheet-tabstoggle[value="inventory"] ~ div.sheet-inventory, -.charsheet .sheet-tabstoggle[value="profitloss"] ~ div.sheet-profitloss{ +.charsheet .sheet-tabstoggle[value="profitloss"] ~ div.sheet-profitloss, +.charsheet .sheet-tabstoggle[value="settings"] ~ div.sheet-settings { display: grid; width: 800px; + z-index: 2; +} + +.ui-dialog .charsheet .SAbutton { + position: relative; + width: 800px; + text-align: left !important; + padding-left: 10px; + padding-top: 4px; + white-space: nowrap; + margin-bottom: -1px; } .ui-dialog .charsheet .SAbutton button{ + position: relative; + z-index: 1; background-color: #ffffff; color: #000000; - padding: 0 4px 0 4px; - margin: 0 1px 0 1px; + padding: 0 8px 0 8px; + margin: 0 -6px 0 0; font-weight: bold; border: solid 1px black; border-radius: 5px 5px 0 0; - border-bottom: 0; + border-bottom: 1px #323232 solid; + transform: skewX(-8deg); + transform-origin: bottom left; transition-duration: 0.1s; } .ui-dialog .charsheet .SAbutton button:hover{ + z-index: 2; background-color: #323232; color: #FFFFFF; } @@ -255,6 +273,21 @@ box-shadow: 0 0px; } +.ui-dialog .charsheet .SAbutton button::after { + content: ""; + position: absolute; + right: -7px; + top: -1px; + width: 12px; + height: calc(100%); + background-color: inherit; + border-top: 1px solid black; + border-right: 1px solid black; + border-radius: 0 12px 0 0; + z-index: 0; + pointer-events: none; +} + .ui-dialog .charsheet .sheet-tabstoggle[value="main"] ~ .SAbutton .sheet-tab-button-main, .ui-dialog .charsheet .sheet-tabstoggle[value="definitions"] ~ .SAbutton .sheet-tab-button-definitions, .ui-dialog .charsheet .sheet-tabstoggle[value="abilities"] ~ .SAbutton .sheet-tab-button-abilities, @@ -263,9 +296,12 @@ .ui-dialog .charsheet .sheet-tabstoggle[value="gear"] ~ .SAbutton .sheet-tab-button-gear, .ui-dialog .charsheet .sheet-tabstoggle[value="accessories"] ~ .SAbutton .sheet-tab-button-accessories, .ui-dialog .charsheet .sheet-tabstoggle[value="inventory"] ~ .SAbutton .sheet-tab-button-inventory, -.ui-dialog .charsheet .sheet-tabstoggle[value="profitloss"] ~ .SAbutton .sheet-tab-button-profitloss { +.ui-dialog .charsheet .sheet-tabstoggle[value="profitloss"] ~ .SAbutton .sheet-tab-button-profitloss, +.ui-dialog .charsheet .sheet-tabstoggle[value="settings"] ~ .SAbutton .sheet-tab-button-settings{ background-color: #323232; color: #FFFFFF; + position: relative; + z-index: 3; } /**/ @@ -351,6 +387,7 @@ text-align: center; border-radius: 10px 10px 0 0; width: 100%; + overflow: hidden; } .row-footer{ @@ -635,6 +672,8 @@ background-color: #000000; padding-bottom: 18px; border-radius: 12px; + overflow: hidden; + z-index: 5; } .main-full{grid-column: 1/-1;} @@ -761,6 +800,24 @@ } +.ui-dialog .charsheet .attribute-block input[type=text]{ + height: 18px; + width: 100%; + box-sizing: border-box; + padding: 2px; + border: 1px solid #909090; + background-color: #FFFFFF; + color: #000000; + text-align: center; +} + +.ui-dialog .charsheet .attribute-block input[type=text][readonly]{ + background-color: #5A5A5A; + color: #FFFFFF; + text-align: center; + font-weight: bold; +} + .ui-dialog .charsheet .attribute-block button[type=roll], .ui-dialog .charsheet .attribute-block button[type=compendium]{ height: 16px; @@ -1568,9 +1625,10 @@ input.sptoggle[value="1"] ~ .sheet-repeating-fields{ color: #FFFFFF; text-align: center; font-weight: bold; - font-size: medium; + font-size: small; border: 0; padding: 3px; + width: 350px; } .weapon-column-span{ @@ -1597,6 +1655,7 @@ input.sptoggle[value="1"] ~ .sheet-repeating-fields{ display: none; } + .ui-dialog .charsheet .advancedAttackButton button[type="roll"]::before{ content: none; } @@ -1606,12 +1665,14 @@ input.sptoggle[value="1"] ~ .sheet-repeating-fields{ color: #000000; border: solid 1px black; border-radius: 5px; + font-size: small; transition-duration: 0.4s; box-shadow: 0 2px #909090; - margin: 1px 1px 6px 1px; + margin: 1px; padding: 1px; content: none; width: fit-content; + height: 16px; } .ui-dialog .charsheet .advancedAttackButton button:hover{ @@ -1633,7 +1694,7 @@ input.sptoggle[value="1"] ~ .sheet-repeating-fields{ /**/ /**/ -.sheet-profitloss{ +.sheet-profitloss { width: 800px; border-radius: 12px; border: 1px solid; @@ -1641,3 +1702,59 @@ input.sptoggle[value="1"] ~ .sheet-repeating-fields{ } /**/ +/* --> + + +
@@ -10,6 +13,7 @@ +
@@ -89,8 +93,8 @@
Base
-
-
+
+
@@ -125,8 +129,8 @@
Total
-
-
+
+
@@ -942,7 +946,7 @@
Components:
Range:
Duration:
-
Dice Rolled: +
Dice Rolled:
Range:
Duration:
-
Dice Rolled: +
Dice Rolled:
Range:
Duration:
-
Dice Rolled: +
Dice Rolled:
Range:
Duration:
-
Dice Rolled: +
Dice Rolled:
Range:
Duration:
-
Dice Rolled: +
Dice Rolled:
Range:
Duration:
-
Dice Rolled: +
Dice Rolled:
Range:
Duration:
-
Dice Rolled: +
Dice Rolled:
Range:
Duration:
-
Dice Rolled: +
Dice Rolled:
Range:
Duration:
-
Dice Rolled: +
Dice Rolled: - - Full Attack:  - +
+ + Single Attack:  +   Full Attack:  + +
@@ -2459,6 +2471,14 @@ + + + + + + + +
@@ -2526,10 +2546,10 @@
-
+ -
+
-
+
@@ -2671,12 +2691,20 @@
- - Full Attack:  - +
+ + Single Attack:  + Full Attack:  + +
@@ -2685,6 +2713,14 @@ + + + + + + + +
@@ -2751,10 +2787,10 @@
-
+ -
+
-
+
@@ -3098,6 +3134,49 @@ + +
+
Settings
+ +
+
+
+
Skill Growth Modifier:
+
+ +
+
+
+ +
+
+
No Constitution Score:
+
+ +
+
+ +
+
No Intelligence Score:
+
+ +
+
+
+
+
+ +
@@ -3301,6 +3380,63 @@ }); }); + // Always make sure the character has an "On Person" location. + // This row is recreated if deleted and normalized if renamed with odd casing/spaces. + const ON_PERSON_LOCATION_SECTION = "repeating_locationtotals"; + const ON_PERSON_LOCATION_NAME = "On Person"; + + const normalizeLocationName = function(value) { + return String(value || "").trim().toLowerCase(); + }; + + const ensureOnPersonLocation = function() { + getSectionIDs(ON_PERSON_LOCATION_SECTION, function(rowIds) { + const attrsToGet = rowIds.map(rowId => + `${ON_PERSON_LOCATION_SECTION}_${rowId}_inventoryLocationName` + ); + + getAttrs(attrsToGet, function(values) { + const matchingRows = rowIds.filter(rowId => + normalizeLocationName(values[`${ON_PERSON_LOCATION_SECTION}_${rowId}_inventoryLocationName`]) === + normalizeLocationName(ON_PERSON_LOCATION_NAME) + ); + + const attrsToSet = {}; + + if (matchingRows.length > 0) { + const keeperRowId = matchingRows[0]; + + attrsToSet[`${ON_PERSON_LOCATION_SECTION}_${keeperRowId}_inventoryLocationName`] = ON_PERSON_LOCATION_NAME; + attrsToSet[`${ON_PERSON_LOCATION_SECTION}_${keeperRowId}_locationOnPerson`] = "1"; + + // Optional cleanup: "On Person" should be unique. + // This prevents the inventory matching logic from having duplicate location names. + matchingRows.slice(1).forEach(rowId => { + removeRepeatingRow(`${ON_PERSON_LOCATION_SECTION}_${rowId}`); + }); + } else { + const rowId = generateRowID(); + + attrsToSet[`${ON_PERSON_LOCATION_SECTION}_${rowId}_inventoryLocationName`] = ON_PERSON_LOCATION_NAME; + attrsToSet[`${ON_PERSON_LOCATION_SECTION}_${rowId}_locationOnPerson`] = "1"; + attrsToSet[`${ON_PERSON_LOCATION_SECTION}_${rowId}_locationItemCount`] = "0"; + attrsToSet[`${ON_PERSON_LOCATION_SECTION}_${rowId}_locationTotalWeight`] = "0"; + } + + if (Object.keys(attrsToSet).length > 0) { + setAttrs(attrsToSet); + } + }); + }); + }; + + on( + "sheet:opened remove:repeating_locationtotals change:repeating_locationtotals:inventorylocationname", + function() { + ensureOnPersonLocation(); + } + ); + // On Person Total Weight on("change:repeating_locationtotals remove:repeating_locationtotals", function(){ getSectionIDs(`repeating_locationtotals`,function(locationArray) { @@ -3544,11 +3680,12 @@ }); }; - on(watch(["Specialty", "AttackCurve", "Versatile"]), function(){ - getAttrs(["Specialty", "AttackCurve", "Versatile"], function(values){ + on(watch(["Specialty", "AttackCurve", "Versatile", "skill_growth_modifier"]), function(){ + getAttrs(["Specialty", "AttackCurve", "Versatile", "skill_growth_modifier"], function(values){ let attack = 2; let specialty = 0; let versatile = 0; + let skill_growth_modifier = num(values, "skill_growth_modifier"); if(values.AttackCurve === "Medium") attack = 4; if(values.Specialty === "Specialist") specialty = 4; @@ -3556,15 +3693,16 @@ if(values.Versatile === "on") versatile = 1; setAttrs({ - BaseSkillGrowth: attack + specialty + versatile + BaseSkillGrowth: attack + specialty + versatile + skill_growth_modifier }); }); }); - on(watch(["intelligence", "IntBonus", "IntPen", "BaseSkillGrowth", "HD"]), function() { - getAttrs(["intelligence", "IntBonus", "IntPen", "BaseSkillGrowth"], function(values) { + on(watch(["intelligence", "IntBonus", "IntPen", "BaseSkillGrowth", "HD", "no_intelligence_score"]), function() { + getAttrs(["intelligence", "IntBonus", "IntPen", "BaseSkillGrowth", "no_intelligence_score"], function(values) { + const noIntelligenceScore = values.no_intelligence_score === "1"; const total = num(values, "intelligence") + num(values, "IntBonus") - num(values, "IntPen"); - const mod = Math.floor((total - 10) / 2); + const mod = noIntelligenceScore ? 0 : Math.floor((total - 10) / 2); setAttrs({ NatIntMod: mod, SkillsKnown: mod + num(values, "BaseSkillGrowth") + 4 @@ -3703,7 +3841,8 @@ }); }); - const buttonlist = ["main", "skills", "gear", "definitions", "abilities", "spells", "accessories", "inventory", "profitloss"]; + const buttonlist = ["main", "skills", "gear", "definitions", "abilities", "spells", "accessories", "inventory", "profitloss", + "settings"]; buttonlist.forEach(button => { on(`clicked:${button}`, function() { setAttrs({ @@ -3801,26 +3940,96 @@ // // { const attrs = [stat.base, stat.bonus, stat.enhancement, stat.penalty]; + if(stat.noScore) attrs.push(stat.noScore); + if(stat.backup) attrs.push(stat.backup); on(watch(attrs), function() { getAttrs(attrs, function(values) { - const total = num(values, stat.base) + num(values, stat.bonus) + + const noScore = stat.noScore && values[stat.noScore] === "1"; + const attrsToSet = {}; + if(stat.backup && !noScore && values[stat.base] !== "-") { + attrsToSet[stat.backup] = values[stat.base]; + } + if(noScore) { + attrsToSet[stat.total] = "-"; + attrsToSet[stat.mod] = 0; + if(values[stat.base] !== "-") { + if(stat.backup && values[stat.base] !== "") { + attrsToSet[stat.backup] = values[stat.base]; + } + attrsToSet[stat.base] = "-"; + } + setAttrs(attrsToSet); + return; + } + if(stat.backup && values[stat.base] === "-") { + attrsToSet[stat.base] = values[stat.backup] || 10; + } + const baseValue = values[stat.base] === "-" + ? num(values, stat.backup, 10) + : num(values, stat.base); + const total = baseValue + num(values, stat.bonus) + num(values, stat.enhancement) - num(values, stat.penalty); - setAttrs({ - [stat.total]: total, - [stat.mod]: Math.floor((total - 10) / 2) - }); + attrsToSet[stat.total] = total; + attrsToSet[stat.mod] = Math.floor((total - 10) / 2); + + setAttrs(attrsToSet); }); }); }); @@ -4154,6 +4363,49 @@ // // + + const buildSingleAttackMacro = function(data) { + const attack = data.attacks[0] || { label: "Single Attack", base: 0, slowPenalty: 0 }; + + const damageAction = makeFullAttackActionTarget(data.section, data.rowId, "singleattackdamage"); + const confirmAction = makeFullAttackActionTarget(data.section, data.rowId, "singleattackconfirm"); + + let macro = `&{template:custom}{{title=Single Attack}}{{subtitle=${data.characterName}'s ${data.weaponName}}}`; + macro += `{{fullattack=1}}{{characterid=@{character_id}}}`; + macro += `{{attack1_next=[[0]]}}`; + macro += `{{attack1_damage_action=${damageAction}}}`; + macro += `{{attack1_confirm_action=${confirmAction}}}`; + macro += `{{attack1_label=Attack}}`; + macro += `{{attack1=[[${buildFullAttackExpression(data, attack)}]]}}`; + + return macro; + }; + + + const rollSingleAttackConfirmation = function(col, eventInfo) { + getAttrs(getFullAttackAttrs(col), function(values) { + const data = buildFullAttackData(col, values, eventInfo); + const attack = data.attacks[0] || { label: "Single Attack", base: 0, slowPenalty: 0 }; + + const normalAction = makeFullAttackActionTarget(data.section, data.rowId, "singleattackdamage"); + const criticalAction = makeFullAttackActionTarget(data.section, data.rowId, "singleattackcritdamage"); + + const macro = + `&{template:custom}` + + `{{title=Critical Confirmation}}` + + `{{subtitle=${data.characterName}'s ${data.weaponName} - Single Attack}}` + + `{{fullattackconfirm=1}}` + + `{{characterid=@{character_id}}}` + + `{{confirm_normal_action=${normalAction}}}` + + `{{confirm_critical_action=${criticalAction}}}` + + `{{confirm=[[${buildFullAttackExpression(data, attack)}]]}}`; + + startRoll(macro, function(roll) { + finishRoll(roll.rollId); + }); + }); + }; + const getRepeatingRowIdFromEvent = function(eventInfo, section) { const source = eventInfo && (eventInfo.sourceAttribute || eventInfo.sourceButton || ""); const match = source.match(new RegExp(`repeating_${section}_([^_]+)_`)); @@ -4295,7 +4547,8 @@ }; const buildFullAttackExpression = function(data, attack) { - let expression = `1d20cs>${data.critLow}+${attack.base}[Base Attack]+${data.attackKey}[Key Attribute]+${data.featAttack}[Feat Bonuses]+${data.enhancement}[Enhancement]+(0?{Do you have any miscellaneous bonuses?|0})[Misc.]`; + let expression = `1d20cs>${data.critLow}+${attack.base}[Base Attack]+${data.attackKey}[Key Attribute]+${data.featAttack}`+ + `[Feat Bonuses]+${data.enhancement}[Enhancement]+(0?{Do you have any miscellaneous bonuses?|0})[Misc.]`; if(attack.slowPenalty) { expression += `-${attack.slowPenalty}[Slow]`; } @@ -4303,7 +4556,8 @@ }; const buildFullAttackMacro = function(data) { - let macro = `&{template:custom}{{title=Full Attack}}{{subtitle=${data.characterName}'s ${data.weaponName}}}{{fullattack=1}}{{characterid=@{character_id}}}`; + let macro = `&{template:custom}{{title=Full Attack}}{{subtitle=${data.characterName}'s ${data.weaponName}}}`+ + `{{fullattack=1}}{{characterid=@{character_id}}}`; data.attacks.forEach((attack, index) => { const attackNumber = index + 1; macro += `{{attack${attackNumber}_next=[[0]]}}`; @@ -4323,9 +4577,11 @@ if(!critical) { let macro = `&{template:custom}{{title=Damage}}{{subtitle=${data.characterName}'s ${data.weaponName}}}`; if(data.twoHanded) { - macro += `{{desc=${data.characterName} deals [[${data.diceNumber}${data.diceType}+[[floor(${data.damageKey}*1.5)]][Ability]+${data.featDamage}[Feats/Other]+${data.enhancement}[Enhancement]+${miscDamage}]] physical damage `; + macro += `{{desc=${data.characterName} deals [[${data.diceNumber}${data.diceType}+[[floor(${data.damageKey}*1.5)]]`+ + `[Ability]+${data.featDamage}[Feats/Other]+${data.enhancement}[Enhancement]+${miscDamage}]] physical damage `; } else { - macro += `{{desc=${data.characterName} deals [[${data.diceNumber}${data.diceType}+[[${data.damageKey}]][Ability]+${data.featDamage}[Feats/Other]+${data.enhancement}[Enhancement]+${miscDamage}]] physical damage `; + macro += `{{desc=${data.characterName} deals [[${data.diceNumber}${data.diceType}+[[${data.damageKey}]][Ability]+`+ + `${data.featDamage}[Feats/Other]+${data.enhancement}[Enhancement]+${miscDamage}]] physical damage `; } if(data.magicDice) { macro += ` and [[${data.magicDice}[${magicDamageType}]]] ${magicDamageType} damage`; @@ -4336,9 +4592,15 @@ let macro = `&{template:custom}{{title=Critical Damage}}{{subtitle=${data.characterName}'s ${data.weaponName}}}`; if(data.twoHanded) { - macro += `{{desc=${data.characterName} deals an astonishing [[[[${data.diceNumber}*${data.criticalMultiplier}]]${data.diceType}+[[(floor(${data.damageKey}*1.5)*${data.criticalMultiplier})]][Ability]+[[(${data.featDamage}*${data.criticalMultiplier})]][Feats/Other]+[[(${data.enhancement}*${data.criticalMultiplier})]][Enhancement]+${miscDamage}]]`; + macro += `{{desc=${data.characterName} deals an astonishing [[[[${data.diceNumber}*${data.criticalMultiplier}]]`+ + `${data.diceType}+[[(floor(${data.damageKey}*1.5)*${data.criticalMultiplier})]][Ability]+[[(${data.featDamage}*`+ + `${data.criticalMultiplier})]][Feats/Other]+[[(${data.enhancement}*${data.criticalMultiplier})]][Enhancement]+`+ + `${miscDamage}]]`; } else { - macro += `{{desc=${data.characterName} deals an astonishing [[[[${data.diceNumber}*${data.criticalMultiplier}]]${data.diceType}+[[(floor(${data.damageKey})*${data.criticalMultiplier})]][Ability]+[[(${data.featDamage}*${data.criticalMultiplier})]][Feats/Other]+[[(${data.enhancement}*${data.criticalMultiplier})]][Enhancement]+${miscDamage}]]`; + macro += `{{desc=${data.characterName} deals an astonishing [[[[${data.diceNumber}*${data.criticalMultiplier}]]`+ + `${data.diceType}+[[(floor(${data.damageKey})*${data.criticalMultiplier})]][Ability]+[[(${data.featDamage}*`+ + `${data.criticalMultiplier})]][Feats/Other]+[[(${data.enhancement}*${data.criticalMultiplier})]][Enhancement]+`+ + `${miscDamage}]]`; } if(data.magicDice) { macro += ` + [[${data.magicDice}[${magicDamageType}]]] ${magicDamageType} damage`; @@ -4366,14 +4628,251 @@ const attack = data.attacks[attackIndex - 1] || data.attacks[0]; const normalAction = makeFullAttackActionTarget(data.section, data.rowId, "fullattackdamage"); const criticalAction = makeFullAttackActionTarget(data.section, data.rowId, "fullattackcritdamage"); - const macro = `&{template:custom}{{title=Critical Confirmation}}{{subtitle=${data.characterName}'s ${data.weaponName} - ${attack.label}}}{{fullattackconfirm=1}}{{characterid=@{character_id}}}{{confirm_normal_action=${normalAction}}}{{confirm_critical_action=${criticalAction}}}{{confirm=[[${buildFullAttackExpression(data, attack)}]]}}`; + const macro = `&{template:custom}{{title=Critical Confirmation}}{{subtitle=${data.characterName}'s ${data.weaponName} - `+ + `${attack.label}}}{{fullattackconfirm=1}}{{characterid=@{character_id}}}{{confirm_normal_action=${normalAction}}}`+ + `{{confirm_critical_action=${criticalAction}}}{{confirm=[[${buildFullAttackExpression(data, attack)}]]}}`; startRoll(macro, function(roll) { finishRoll(roll.rollId); }); }); }; + const FOCUS_POINTS_QUERY = "?{Focus Points Spent|1|2|3|4}"; + + const clampFocusPoints = function(value) { + let focusPoints = parseInt(value, 10); + if(isNaN(focusPoints)) focusPoints = 1; + focusPoints = Math.max(1, focusPoints); + focusPoints = Math.min(4, focusPoints); + return focusPoints; + }; + + const rollResultTotal = function(rollResult, fallback = 0) { + if(rollResult && rollResult.result !== undefined) { + const parsed = parseFloat(rollResult.result); + return isNaN(parsed) ? fallback : parsed; + } + + if(rollResult && rollResult.total !== undefined) { + const parsed = parseFloat(rollResult.total); + return isNaN(parsed) ? fallback : parsed; + } + + return fallback; + }; + + const focusRowAttr = function(data, attr) { + if(data.rowId) return `repeating_${data.section}_${data.rowId}_${attr}`; + return `repeating_${data.section}_${attr}`; + }; + + const getFocusAttackAttrs = function(col) { + return getFullAttackAttrs(col).concat([ + `repeating_weapons${col}_focus_points_used` + ]); + }; + + const buildFocusAttackData = function(col, values, eventInfo) { + const data = buildFullAttackData(col, values, eventInfo); + const prefix = `repeating_weapons${col}_`; + + data.focusPointsUsed = clampFocusPoints(values[`${prefix}focus_points_used`]); + + return data; + }; + + const buildFocusAttackExpression = function(data, attack, focusPointsTerm) { + let expression = + `1d20cs>${data.critLow}` + + `+${attack.base}[Base Attack]` + + `+${data.attackKey}[Key Attribute]` + + `+${data.featAttack}[Feat Bonuses]` + + `+${data.enhancement}[Enhancement]` + + `+(${focusPointsTerm}*3)[Focus]` + + `+(0?{Do you have any miscellaneous bonuses?|0})[Misc.]`; + + if(attack.slowPenalty) { + expression += `-${attack.slowPenalty}[Slow]`; + } + + return expression; + }; + + const buildFocusAttackMacro = function(data) { + const attack = data.attacks[0] || { + label: "Focus Attack", + base: 0, + slowPenalty: 0 + }; + + const damageAction = makeFullAttackActionTarget(data.section, data.rowId, "focusattackdamage"); + const confirmAction = makeFullAttackActionTarget(data.section, data.rowId, "focusattackconfirm"); + + let macro = + `&{template:custom}` + + `{{title=Focus Attack}}` + + `{{subtitle=${data.characterName}'s ${data.weaponName}}}` + + `{{fullattack=1}}` + + `{{characterid=@{character_id}}}` + + `{{Focus Points=[[${FOCUS_POINTS_QUERY}]]}}` + + `{{attack1_next=[[0]]}}` + + `{{attack1_damage_action=${damageAction}}}` + + `{{attack1_confirm_action=${confirmAction}}}` + + `{{attack1_label=Focus Attack}}` + + `{{attack1=[[${buildFocusAttackExpression(data, attack, FOCUS_POINTS_QUERY)}]]}}`; + + return macro; + }; + + const buildFocusDamageMacro = function(data, critical = false) { + const focusPoints = clampFocusPoints(data.focusPointsUsed); + const focusDamageBonus = focusPoints * 3; + const focusedDiceNumber = `[[${data.diceNumber}+${focusPoints}]]`; + const magicDamageType = data.damageType ? data.damageType : "energy"; + const miscDamage = `(0?{Do you have any miscellaneous bonuses?|0})[Misc.]`; + const focusPointText = focusPoints === 1 ? "focus point" : "focus points"; + + if(!critical) { + let macro = + `&{template:custom}` + + `{{title=Focus Damage}}` + + `{{subtitle=${data.characterName}'s ${data.weaponName}}}`; + + if(data.twoHanded) { + macro += + `{{desc=${data.characterName} spends ${focusPoints} ${focusPointText} and deals ` + + `[[${focusedDiceNumber}${data.diceType}` + + `+[[floor(${data.damageKey}*1.5)]][Ability]` + + `+${data.featDamage}[Feats/Other]` + + `+${data.enhancement}[Enhancement]` + + `+${focusDamageBonus}[Focus]` + + `+${miscDamage}]] physical damage`; + } else { + macro += + `{{desc=${data.characterName} spends ${focusPoints} ${focusPointText} and deals ` + + `[[${focusedDiceNumber}${data.diceType}` + + `+[[${data.damageKey}]][Ability]` + + `+${data.featDamage}[Feats/Other]` + + `+${data.enhancement}[Enhancement]` + + `+${focusDamageBonus}[Focus]` + + `+${miscDamage}]] physical damage`; + } + + if(data.magicDice) { + macro += ` and [[${data.magicDice}[${magicDamageType}]]] ${magicDamageType} damage`; + } + + macro += ` with ${data.weaponName}.}}`; + + return macro; + } + + let macro = + `&{template:custom}` + + `{{title=Focus Critical Damage}}` + + `{{subtitle=${data.characterName}'s ${data.weaponName}}}`; + + if(data.twoHanded) { + macro += + `{{desc=${data.characterName} spends ${focusPoints} ${focusPointText} and deals an astonishing ` + + `[[[[(${data.diceNumber}+${focusPoints})*${data.criticalMultiplier}]]${data.diceType}` + + `+[[(floor(${data.damageKey}*1.5)*${data.criticalMultiplier})]][Ability]` + + `+[[(${data.featDamage}*${data.criticalMultiplier})]][Feats/Other]` + + `+[[(${data.enhancement}*${data.criticalMultiplier})]][Enhancement]` + + `+[[(${focusDamageBonus}*${data.criticalMultiplier})]][Focus]` + + `+${miscDamage}]]`; + } else { + macro += + `{{desc=${data.characterName} spends ${focusPoints} ${focusPointText} and deals an astonishing ` + + `[[[[(${data.diceNumber}+${focusPoints})*${data.criticalMultiplier}]]${data.diceType}` + + `+[[(floor(${data.damageKey})*${data.criticalMultiplier})]][Ability]` + + `+[[(${data.featDamage}*${data.criticalMultiplier})]][Feats/Other]` + + `+[[(${data.enhancement}*${data.criticalMultiplier})]][Enhancement]` + + `+[[(${focusDamageBonus}*${data.criticalMultiplier})]][Focus]` + + `+${miscDamage}]]`; + } + + if(data.magicDice) { + macro += ` + [[${data.magicDice}[${magicDamageType}]]] ${magicDamageType} damage`; + } + + if(data.bonusCriticalDice) { + macro += ` + [[${data.bonusCriticalDice}[Bonus Dice]]] bonus damage`; + } + + macro += ` with ${data.weaponName}.}}`; + + return macro; + }; + + const rollFocusDamage = function(col, critical, eventInfo) { + getAttrs(getFocusAttackAttrs(col), function(values) { + const data = buildFocusAttackData(col, values, eventInfo); + const macro = buildFocusDamageMacro(data, critical); + + startRoll(macro, function(roll) { + finishRoll(roll.rollId); + }); + }); + }; + + const rollFocusAttackConfirmation = function(col, eventInfo) { + getAttrs(getFocusAttackAttrs(col), function(values) { + const data = buildFocusAttackData(col, values, eventInfo); + const attack = data.attacks[0] || { + label: "Focus Attack", + base: 0, + slowPenalty: 0 + }; + + const normalAction = makeFullAttackActionTarget(data.section, data.rowId, "focusattackdamage"); + const criticalAction = makeFullAttackActionTarget(data.section, data.rowId, "focusattackcritdamage"); + + const macro = + `&{template:custom}` + + `{{title=Focus Critical Confirmation}}` + + `{{subtitle=${data.characterName}'s ${data.weaponName}}}` + + `{{fullattackconfirm=1}}` + + `{{characterid=@{character_id}}}` + + `{{confirm_normal_action=${normalAction}}}` + + `{{confirm_critical_action=${criticalAction}}}` + + `{{confirm=[[${buildFocusAttackExpression(data, attack, data.focusPointsUsed)}]]}}`; + + startRoll(macro, function(roll) { + finishRoll(roll.rollId); + }); + }); + }; + WeaponCol.forEach(col => { + on(`clicked:repeating_weapons${col}:singleattack`, function(eventInfo) { + getAttrs(getFullAttackAttrs(col), function(values) { + const data = buildFullAttackData(col, values, eventInfo); + const macro = buildSingleAttackMacro(data); + + startRoll(macro, function(roll) { + const naturalDie = getFirstDie(roll.results.attack1); + const computed = { + attack1_next: naturalDie >= data.critLow ? 1 : 0 + }; + + finishRoll(roll.rollId, computed); + }); + }); + }); + + on(`clicked:repeating_weapons${col}:singleattackdamage`, function(eventInfo) { + rollWeaponDamageFromFullAttack(col, false, eventInfo); + }); + + on(`clicked:repeating_weapons${col}:singleattackcritdamage`, function(eventInfo) { + rollWeaponDamageFromFullAttack(col, true, eventInfo); + }); + + on(`clicked:repeating_weapons${col}:singleattackconfirm`, function(eventInfo) { + rollSingleAttackConfirmation(col, eventInfo); + }); + on(`clicked:repeating_weapons${col}:fullattackroll`, function(eventInfo) { getAttrs(getFullAttackAttrs(col), function(values) { const data = buildFullAttackData(col, values, eventInfo); @@ -4403,6 +4902,41 @@ rollFullAttackConfirmation(col, attackIndex, eventInfo); }); } + on(`clicked:repeating_weapons${col}:focusattack`, function(eventInfo) { + getAttrs(getFocusAttackAttrs(col), function(values) { + const data = buildFocusAttackData(col, values, eventInfo); + const macro = buildFocusAttackMacro(data); + + startRoll(macro, function(roll) { + const naturalDie = getFirstDie(roll.results.attack1); + const focusPoints = clampFocusPoints(rollResultTotal(roll.results["Focus Points"], 1)); + + const computed = { + attack1_next: naturalDie >= data.critLow ? 1 : 0 + }; + + const attrsToSet = { + [focusRowAttr(data, "focus_points_used")]: focusPoints + }; + + setAttrs(attrsToSet, function() { + finishRoll(roll.rollId, computed); + }); + }); + }); + }); + + on(`clicked:repeating_weapons${col}:focusattackdamage`, function(eventInfo) { + rollFocusDamage(col, false, eventInfo); + }); + + on(`clicked:repeating_weapons${col}:focusattackcritdamage`, function(eventInfo) { + rollFocusDamage(col, true, eventInfo); + }); + + on(`clicked:repeating_weapons${col}:focusattackconfirm`, function(eventInfo) { + rollFocusAttackConfirmation(col, eventInfo); + }); }); // @@ -4644,85 +5178,102 @@ //Focus // weapondamage is the name of the damage button - - WeaponCol.forEach(col => { - on(`clicked:repeating_weapons${col}:focusattack`, function(){ - getAttrs([ - `repeating_weapons${col}_bonustoattack`, - `repeating_weapons${col}_critlow`, - `repeating_weapons${col}_weaponhaste`, - `repeating_weapons${col}_weaponbab`, - `repeating_weapons${col}_enhancetoattack`, - `repeating_weapons${col}_weapontodamage`, - `repeating_weapons${col}_bonustodamage`, - `repeating_weapons${col}_dnum`, - `repeating_weapons${col}_dtype`, - `repeating_weapons${col}_enchdamagedice`, - `repeating_weapons${col}_extraenchdicecrit`, - `repeating_weapons${col}_weaponcritmod`, - `repeating_weapons${col}_twohanded`, - `repeating_weapons${col}_weapontohit`, - // `repeating_weapons${col}_character_name`, - // `repeating_weapons${col}_arrayID`, - `repeating_weapons${col}_weapondamagtype`], function(values){ - // const arrayID = values[`repeating_weapons${col}_arrayID`] - // const characterName = values[`repeating_weapons${col}_character_name`]; - const featBonusToAttack = values[`repeating_weapons${col}_bonustoattack`] || 0; - const featBonusToDamage = values[`repeating_weapons${col}_bonustodamage`] || 0; - const dieQuantity = values[`repeating_weapons${col}_dnum`] || 1; - const dieType = values[`repeating_weapons${col}_dtype`]; - const weaponEnhancement = values[`repeating_weapons${col}_enhancetoattack`] || 0; - const baseAttack = values[`repeating_weapons${col}_weaponbab`] || 0; - const minCritThreshold = values[`repeating_weapons${col}_critlow`]|| 20 ; - const keyAttackAttribute = values[`repeating_weapons${col}_weapontohit`] || 0; - const keyDamageAttribute = values[`repeating_weapons${col}_weapontodamage`] ; - const twoHandedToggle = values[`repeating_weapons${col}_twohanded`]; - const magicDice = values[`repeating_weapons${col}_enchdamagedice`]; - const magicCriticalDice = values[`repeating_weapons${col}_extraenchdicecrit`]; - const magicDamageType = values[`repeating_weapons${col}_weapondamagtype`]; - const criticalModifier = values[`repeating_weapons${col}_weaponcritmod`]; - let macroRoll = `&{template:custom} {{title=Focus Attack}}{{Attack Roll=[[1d20cs>${minCritThreshold}+${baseAttack}`+ - `[BAB]+${keyAttackAttribute}[Key Attribute]+${featBonusToAttack}[Feats]+${weaponEnhancement}[Enhancement]`+ - `-@{slow}[Slow]+((0?{Focus Points Spent})*3)[Focus]+(0?{Miscellaneous attack bonuses?})[Misc.]]]}}`; - if (twoHandedToggle === "on"){ - macroRoll = macroRoll + `{{Damage=[[[[${dieQuantity}+(0?{Focus Points Spent})]]${dieType}+floor(`+ - `(${keyDamageAttribute})*1.5)[Key Attribute]+${featBonusToDamage}[Feat]+${weaponEnhancement}[Magic]+((`+ - `0?{Focus Points Spent})*3)[Focus]]]}}`; - } - if (twoHandedToggle !== "on"){ - macroRoll = macroRoll + `{{Damage=[[[[${dieQuantity}+(0?{Focus Points Spent})]]${dieType}+floor(`+ - `${keyDamageAttribute})[Key Attribute]+${featBonusToDamage}[Feat]+${weaponEnhancement}[Magic]+((0?{`+ - `Focus Points Spent})*3)[Focus]]]}}`; - } - if (magicDice){ - macroRoll = macroRoll + `{{${magicDamageType} Damage=[[${magicDice}[${magicDamageType}]]]}}`; - } - macroRoll = macroRoll + `{{Critical Confirmation Roll=[[1d20cs>${minCritThreshold}+${baseAttack}`+ - `[BAB]+${keyAttackAttribute}[Key Attribute]+${featBonusToAttack}[Feats]+${weaponEnhancement}[Enhancement]`+ - `-@{slow}[Slow]+((0?{Focus Points Spent})*3)[Focus]+(0?{Miscellaneous attack bonuses?})[Misc.]]]}}`; - if (twoHandedToggle === "on"){ - macroRoll = macroRoll + `{{Critical Damage=[[[[((${dieQuantity}+(0?{Focus Points Spent}))*${criticalModifier})]]`+ - `${dieType}+(floor(${keyDamageAttribute}*1.5)*${criticalModifier})[Key Attribute]+(`+ - `${featBonusToDamage}*${criticalModifier}[Feat])+(${weaponEnhancement}*${criticalModifier})+(((0?{Focus`+ - ` Points Spent})*3)*${criticalModifier})[Focus]]]}}`; - } - if (twoHandedToggle !== "on"){ - macroRoll = macroRoll + `{{Critical Damage=[[[[((${dieQuantity}+(0?{Focus Points Spent}))*${criticalModifier})]]`+ - `${dieType}+(${keyDamageAttribute}*${criticalModifier})[Key Attribute]+(`+ - `${featBonusToDamage}*${criticalModifier}[Feat])+(${weaponEnhancement}*${criticalModifier})+(((0?{Focus`+ - ` Points Spent})*3)*${criticalModifier})[Focus]]]}}`; - } - if (magicCriticalDice){ - macroRoll = macroRoll + `{{Bonus Critical Damage = [[${magicCriticalDice}]]}}` - } - startRoll(macroRoll,function(roll){ - finishRoll(roll.rollId); - }); - }); - }); - }) +// Focus +// WeaponCol.forEach(col => { +// on(`clicked:repeating_weapons${col}:focusattack`, function(){ +// getAttrs([ +// `repeating_weapons${col}_bonustoattack`, +// `repeating_weapons${col}_critlow`, +// `repeating_weapons${col}_weaponhaste`, +// `repeating_weapons${col}_weaponbab`, +// `repeating_weapons${col}_enhancetoattack`, +// `repeating_weapons${col}_weapontodamage`, +// `repeating_weapons${col}_bonustodamage`, +// `repeating_weapons${col}_dnum`, +// `repeating_weapons${col}_dtype`, +// `repeating_weapons${col}_enchdamagedice`, +// `repeating_weapons${col}_extraenchdicecrit`, +// `repeating_weapons${col}_weaponcritmod`, +// `repeating_weapons${col}_twohanded`, +// `repeating_weapons${col}_weapontohit`, +// // `repeating_weapons${col}_character_name`, +// // `repeating_weapons${col}_arrayID`, +// `repeating_weapons${col}_weapondamagtype`], function(values){ +// // const arrayID = values[`repeating_weapons${col}_arrayID`] +// // const characterName = values[`repeating_weapons${col}_character_name`]; +// const featBonusToAttack = values[`repeating_weapons${col}_bonustoattack`] || 0; +// const featBonusToDamage = values[`repeating_weapons${col}_bonustodamage`] || 0; +// const dieQuantity = values[`repeating_weapons${col}_dnum`] || 1; +// const dieType = values[`repeating_weapons${col}_dtype`]; +// const weaponEnhancement = values[`repeating_weapons${col}_enhancetoattack`] || 0; +// const baseAttack = values[`repeating_weapons${col}_weaponbab`] || 0; +// const minCritThreshold = values[`repeating_weapons${col}_critlow`]|| 20 ; +// const keyAttackAttribute = values[`repeating_weapons${col}_weapontohit`] || 0; +// const keyDamageAttribute = values[`repeating_weapons${col}_weapontodamage`] ; +// const twoHandedToggle = values[`repeating_weapons${col}_twohanded`]; +// const magicDice = values[`repeating_weapons${col}_enchdamagedice`]; +// const magicCriticalDice = values[`repeating_weapons${col}_extraenchdicecrit`]; +// const magicDamageType = values[`repeating_weapons${col}_weapondamagtype`]; +// const criticalModifier = values[`repeating_weapons${col}_weaponcritmod`]; +// let macroRoll = `&{template:custom} {{title=Focus Attack}}{{Attack Roll=[[1d20cs>${minCritThreshold}+${baseAttack}`+ +// `[BAB]+${keyAttackAttribute}[Key Attribute]+${featBonusToAttack}[Feats]+${weaponEnhancement}[Enhancement]`+ +// `-@{slow}[Slow]+((0?{Focus Points Spent})*3)[Focus]+(0?{Miscellaneous attack bonuses?})[Misc.]]]}}`; +// if (twoHandedToggle === "on"){ +// macroRoll = macroRoll + `{{Damage=[[[[${dieQuantity}+(0?{Focus Points Spent})]]${dieType}+floor(`+ +// `(${keyDamageAttribute})*1.5)[Key Attribute]+${featBonusToDamage}[Feat]+${weaponEnhancement}[Magic]+((`+ +// `0?{Focus Points Spent})*3)[Focus]]]}}`; +// } +// if (twoHandedToggle !== "on"){ +// macroRoll = macroRoll + `{{Damage=[[[[${dieQuantity}+(0?{Focus Points Spent})]]${dieType}+floor(`+ +// `${keyDamageAttribute})[Key Attribute]+${featBonusToDamage}[Feat]+${weaponEnhancement}[Magic]+((0?{`+ +// `Focus Points Spent})*3)[Focus]]]}}`; +// } +// if (magicDice){ +// macroRoll = macroRoll + `{{${magicDamageType} Damage=[[${magicDice}[${magicDamageType}]]]}}`; +// } +// macroRoll = macroRoll + `{{Critical Confirmation Roll=[[1d20cs>${minCritThreshold}+${baseAttack}`+ +// `[BAB]+${keyAttackAttribute}[Key Attribute]+${featBonusToAttack}[Feats]+${weaponEnhancement}[Enhancement]`+ +// `-@{slow}[Slow]+((0?{Focus Points Spent})*3)[Focus]+(0?{Miscellaneous attack bonuses?})[Misc.]]]}}`; +// if (twoHandedToggle === "on"){ +// macroRoll = macroRoll + `{{Critical Damage=[[[[((${dieQuantity}+(0?{Focus Points Spent}))*${criticalModifier})]]`+ +// `${dieType}+(floor(${keyDamageAttribute}*1.5)*${criticalModifier})[Key Attribute]+(`+ +// `${featBonusToDamage}*${criticalModifier}[Feat])+(${weaponEnhancement}*${criticalModifier})+(((0?{Focus`+ +// ` Points Spent})*3)*${criticalModifier})[Focus]]]}}`; +// } +// if (twoHandedToggle !== "on"){ +// macroRoll = macroRoll + `{{Critical Damage=[[[[((${dieQuantity}+(0?{Focus Points Spent}))*${criticalModifier})]]`+ +// `${dieType}+(${keyDamageAttribute}*${criticalModifier})[Key Attribute]+(`+ +// `${featBonusToDamage}*${criticalModifier}[Feat])+(${weaponEnhancement}*${criticalModifier})+(((0?{Focus`+ +// ` Points Spent})*3)*${criticalModifier})[Focus]]]}}`; +// } +// if (magicCriticalDice){ +// macroRoll = macroRoll + `{{Bonus Critical Damage = [[${magicCriticalDice}]]}}` +// } +// startRoll(macroRoll,function(roll){ +// finishRoll(roll.rollId); +// }); +// }); +// }); +// }) // + // + const CURRENT_SHEET_VERSION = 1; + + on("sheet:opened", function() { + getAttrs(["sheet_version"], function(values) { + const sheetVersion = int(values, "sheet_version", 0); + if(sheetVersion < CURRENT_SHEET_VERSION) { + // Future migrations go here. + + setAttrs({ + sheet_version: CURRENT_SHEET_VERSION + }); + } + }); + }); + // + \ No newline at end of file