From 9c356b035b1cf70bf17f95e7941271ccc654e4ad Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 8 Sep 2025 00:11:45 -0700 Subject: [PATCH] -added hit determination section - hit det mods are global and will apply to all attacks - expanded weapon row --- Top Secret/TopSecret.css | 164 +++++++++++-- Top Secret/TopSecret.html | 490 ++++++++++++++++++++++++++++++++++---- Top Secret/sheet.json | 3 +- 3 files changed, 591 insertions(+), 66 deletions(-) diff --git a/Top Secret/TopSecret.css b/Top Secret/TopSecret.css index 2a3ad12ccc..2511f1aafd 100644 --- a/Top Secret/TopSecret.css +++ b/Top Secret/TopSecret.css @@ -23,6 +23,17 @@ width: 100%; } +.ui-dialog .tab-content .charsheet label:has(input[type="checkbox"]):hover, +.ui-dialog .tab-content .charsheet label:has(input[type="radio"]):hover { + cursor: pointer; +} + +.ui-dialog .tab-content .charsheet label:has(input[type="checkbox"]:checked) > span, +.ui-dialog .tab-content .charsheet label:has(input[type="checkbox"]):hover > span { + text-shadow: 0 0 black; + background-color: #cbcbcb; +} + .ui-dialog .tab-content .charsheet .flex-row > label { display: flex; flex: 1 1 6em; @@ -64,6 +75,16 @@ margin-inline: -7px; } +.ui-dialog .tab-content .charsheet details summary > * { + display: inline; +} + +.ui-dialog .tab-content .charsheet details > summary { + display: list-item; + position: relative; + cursor: pointer; +} + .ui-dialog .tab-content .charsheet button[type=roll]:before { content: "t"; font-family: "dicefontd20"; @@ -71,6 +92,25 @@ top: 2px; } +.ui-dialog .tab-content .charsheet button[type="action"] { + line-height: normal; + margin-inline: auto; + margin-left: -8px; + width: max-content; +} + +.ui-dialog .tab-content .charsheet .hit-determination-grid [type="action"] { + margin-left: -8px; +} + +.ui-dialog .tab-content .charsheet .reset-image::after { + content: '1'; + display: inline-block; + font-family: 'Pictos'; + position: relative; + top: -1px; +} + .ui-dialog .tab-content .charsheet .logo-text { color: red; float: left; @@ -104,7 +144,8 @@ } .ui-dialog .tab-content .charsheet label, -.ui-dialog .tab-content .charsheet label span { +.ui-dialog .tab-content .charsheet label > span { + display: inline-block; font-size: 1em; margin: 2px; } @@ -116,19 +157,27 @@ color: #777; cursor: default; max-width: 3em; + text-align: center; +} + +.ui-dialog .tab-content .charsheet input.larger { + max-width: 3.75em; +} + +.ui-dialog .tab-content .charsheet nav { + display: inline-block; + margin-block: .5em 1em; } .ui-dialog .tab-content .charsheet input.tab { border: 5px #888888; cursor: pointer; height: 20px; - left: 6px; - margin: 10px -1.5px; - min-width: 132px; + margin-top: 6px; outline: none; padding: 0; position: relative; - width: 132px; + width: 95px; z-index: 1; } @@ -142,7 +191,7 @@ font-size: 16px; height: 20px; text-align: center; - width: 150px; + width: 95px; } .ui-dialog .tab-content .charsheet input.tab:checked::before { @@ -155,18 +204,18 @@ display: none; } -.ui-dialog .tab-content .charsheet input.tab1:checked ~ div.tab1, -.ui-dialog .tab-content .charsheet input.tab2:checked ~ div.tab2, -.ui-dialog .tab-content .charsheet input.tab3:checked ~ div.tab3, -.ui-dialog .tab-content .charsheet input.tab4:checked ~ div.tab4, -.ui-dialog .tab-content .charsheet input.tab5:checked ~ div.tab5, -.ui-dialog .tab-content .charsheet input.tab6:checked ~ div.tab6 { +.ui-dialog .tab-content .charsheet nav:has(input.tab1:checked) ~ div.tab1, +.ui-dialog .tab-content .charsheet nav:has(input.tab2:checked) ~ div.tab2, +.ui-dialog .tab-content .charsheet nav:has(input.tab3:checked) ~ div.tab3, +.ui-dialog .tab-content .charsheet nav:has(input.tab4:checked) ~ div.tab4, +.ui-dialog .tab-content .charsheet nav:has(input.tab5:checked) ~ div.tab5, +.ui-dialog .tab-content .charsheet nav:has(input.tab6:checked) ~ div.tab6 { display: block; } .ui-dialog .tab-content .charsheet .instruction { - font-size: 0.8em; - font-style: italic; + font-family: monospace; + font-size: 1.1rem; padding-bottom: 10px; } @@ -189,18 +238,95 @@ .ui-dialog .tab-content .charsheet .hit-determination-grid { background-color: #dddddd; - column-gap: 2px; display: grid; - grid-template-columns: auto auto auto; - justify-content: space-around; + gap: 4px; + grid-template-columns: repeat(3, minmax(18em, 1fr)); margin-bottom: 1em; + min-width: min-content; outline: 1px solid #000; + padding: 5px; +} + +.ui-dialog .tab-content .charsheet .mph { + display: none; +} + +.ui-dialog .tab-content .charsheet select:has(.show-mph:checked) + .mph { + align-items: center; + display: inline-flex; + margin: -1em auto 1em auto; +} + +.ui-dialog .tab-content .charsheet .hit-determination-misc { + margin-block: 0 .5em; + max-width: 33%; +} + +.ui-dialog .tab-content .charsheet .hit-determination-misc[open] { + background-color: #fff; + margin-bottom: 5px; + outline: 1px solid #000; + padding-left: 4px; + position: relative; + max-width: 100%; +} + +.ui-dialog .tab-content .charsheet details[open] > summary::after { + content: '*'; + opacity: .35; + font-family: 'Pictos'; + float: right; + margin-right: 8px; + margin-top: 4px; +} + +.ui-dialog .tab-content .charsheet details[open] > summary:hover::after { + opacity: 1; +} + +.ui-dialog .tab-content .charsheet .hit-determination-misc label { + font-weight: normal; +} + +.ui-dialog .tab-content .charsheet .hit-determination-automatic-grid { + display: grid; + grid-template-columns: 4em repeat(10, auto); + height: min-content; + margin: -3em auto 0 auto; + max-width: 28em; + text-align: center; + width: 55%; +} + +.ui-dialog .tab-content .charsheet .range-of-target { + align-self: center; + margin-bottom: -1em; + margin-inline: auto -2em; + max-width: 16em; + outline: 1px solid #000; + outline-offset: .5em; + width: 70%; +} + +.ui-dialog .tab-content .charsheet .range-of-target span { + display: inline-flex; + width: 100%; + justify-content: space-evenly; +} + +.ui-dialog .tab-content .charsheet .range-of-target span > * { + flex: 0 1 3em; +} + +.charsheet .hit-determination-automatic-grid > span, +.charsheet .hit-determination-automatic-grid > input { + width: 14px; } .ui-dialog .tab-content .charsheet .weapons-grid { display: grid; gap: 2px; - grid-template-columns: 2em minmax(12em, 3fr) minmax(8em, 1.5fr) 4em 2.5em 5em 3em 3em 3em 3em 3em; + grid-template-columns: 2em minmax(12em, 3fr) 3em 3em 3em 3em 3.5em 4em 3em 7em 4em 5em 3.5em; padding-bottom: 2px; } @@ -215,6 +341,7 @@ } .ui-dialog .tab-content .charsheet .weapon-range-modifier-column { + border-bottom: 1px solid #000; grid-column: span 4; margin-bottom: -1.5em; } @@ -222,6 +349,7 @@ .ui-dialog .tab-content .charsheet .general-column-header { font-weight: bold; text-align: center; + width: auto; } .ui-dialog .tab-content .charsheet .full-width-element { diff --git a/Top Secret/TopSecret.html b/Top Secret/TopSecret.html index 4c77e49161..b39309bbed 100644 --- a/Top Secret/TopSecret.html +++ b/Top Secret/TopSecret.html @@ -20,12 +20,14 @@ Code Name - - - - - - +
@@ -171,72 +173,249 @@
WEAPONS
-
- - Range of Target - Movement of Shooter - Movement of Target - - - - - - +
+ + HIT DETERMINATION + (Applies to all weapon rolls.) + +
+ +
+
+ + + Movement of Shooter + + + + + + + + Movement of Target + + + + + + + + Wounds + + + - - - Wounds - Miscellaneous - Automatic + +
+ + Miscellaneous + + + + + +
+ + + Which Shot? + +
+ +
+ + + Injury Points? + + +
+ + + + + *Note that if at any point a character takes a "steadying phase”, all consecutive shot penalties revert back to zero. +
+ + +
Range of Target
+ + PB + S + M + L + +
+ +
+
+ Automatic Weapons + + +
- - - + Shot# + 12345678910+ + + Auto + + + + + + + + + + + + Semi + + + + + + + + + + +
+
+ + TOTAL + + +
- - - Range Modifier + + + + + WEAPON - Weapon Speed - BASE SPEED PWV - BASE ACCURACY + PB S M L + + WS Rate + Ammo + BASE SPEED + BASE ACCURACY + HIT DET
+ + + + + - - + + + + / + + + - - - - + -
@@ -526,16 +705,18 @@ }); // weapon calcs - on('change:repeating_weapons:weaponspeedmodifier change:repeating_weapons:pwv', (eventInfo) => { + on('change:repeating_weapons', (eventInfo) => { console.log(`Change detected: ${eventInfo.sourceAttribute}`); const id = eventInfo.sourceAttribute.split('_')[2]; - getAttrs(['offense', `repeating_weapons_${id}_weaponspeedmodifier`, `repeating_weapons_${id}_pwv`], (v) => { + getAttrs(['offense', 'hit_det_total', `repeating_weapons_${id}_weaponspeedmodifier`, `repeating_weapons_${id}_pwv`], (v) => { const output = {}; const offense = +v.offense || 0; const weaponSpeed = +v[`repeating_weapons_${id}_weaponspeedmodifier`] || 0; const PWV = +v[`repeating_weapons_${id}_pwv`] || 0; + const hitDetTotal = v.hit_det_total || 0; output[`repeating_weapons_${id}_basespeed`] = offense + weaponSpeed; output[`repeating_weapons_${id}_baseaccuracy`] = offense + PWV; + output[`repeating_weapons_${id}_hit_det`] = hitDetTotal; setAttrs(output, {silent: true}); }); }); @@ -562,6 +743,223 @@ }); }); + // hit determination chart + + // movement of shooter + on('change:hit_det_move_of_shooter change:hit_det_shooter_speed_multiplier clicked:hitDetResetAll clicked:hitDetResetMovementOfShooter', (eventInfo) => { + console.log(`Change detected: ${eventInfo.sourceAttribute}`); + getAttrs(['hit_det_move_of_shooter', 'hit_det_shooter_speed_multiplier'], (v) => { + const output = {}; + let actionButton = eventInfo.triggerName; + actionButton = actionButton.replace('clicked:', ''); + if (actionButton !== 'hitdetresetmovementofshooter' && actionButton !== 'hitdetresetall') { + let moveShooter = +v['hit_det_move_of_shooter'] || 0; + let speedMult = 0; + if (moveShooter === 7) { + speedMult = +Math.max(0, v['hit_det_shooter_speed_multiplier'] || 0); + } + const moveShooterValues = { + 0: 0, // Stationary + 1: 5, // Stationary and prone + 2: -5, // Walking + 3: -10, // Wading + 4: -10, // Crawling + 5: -30, // Running and dodging + 6: -20, // Running + 7: -10, // Vehicular Movement + 8: -20, // Drops Prone + }; + moveShooter = moveShooterValues[moveShooter] || 0; + output.hit_det_move_of_shooter_total = moveShooter - speedMult; + output.hit_det_shooter_speed_multiplier = speedMult; + } else { + console.log(`Change detected: Reset`); + output.hit_det_move_of_shooter_total = 0; + output.hit_det_move_of_shooter = 0; + output.hit_det_shooter_speed_multiplier = 0; + } + setAttrs(output); + }); + }); + + // movement of target + on('change:hit_det_move_of_target change:hit_det_target_speed_multiplier clicked:hitDetResetAll clicked:hitDetResetMovementOfTarget', (eventInfo) => { + console.log(`Change detected: ${eventInfo.sourceAttribute}`); + getAttrs(['hit_det_move_of_target', 'hit_det_target_speed_multiplier'], (v) => { + const output = {}; + let actionButton = eventInfo.triggerName; + actionButton = actionButton.replace('clicked:', ''); + if (actionButton !== 'hitdetresetmovementoftarget' && actionButton !== 'hitdetresetall') { + let moveTarget = +v['hit_det_move_of_target'] || 0; + let speedMult = 0; + if (moveTarget === 6) { + speedMult = +Math.max(0, v['hit_det_target_speed_multiplier'] || 0); + } + const moveTargetValues = { + 0: 0, // Stationary + 1: -5, // Walking + 2: -5, // Wading + 3: -5, // Crawling + 4: -10, // Running + 5: -20, // Running and dodging + 6: -10, // Vehicular Movement + 7: -5, // Drops Prone + }; + moveTarget = moveTargetValues[moveTarget] || 0; + output.hit_det_move_of_target_total = moveTarget - speedMult; + output.hit_det_target_speed_multiplier = speedMult; + } else { + console.log(`Change detected: Reset`); + output.hit_det_move_of_target_total = 0; + output.hit_det_move_of_target = 0; + output.hit_det_target_speed_multiplier = 0; + } + setAttrs(output); + }); + }); + + // wounds + on('change:hit_det_wounds clicked:hitDetResetAll clicked:hitDetResetWounds', (eventInfo) => { + console.log(`Change detected: ${eventInfo.sourceAttribute}`); + getAttrs(['hit_det_wounds'], (v) => { + const output = {}; + let actionButton = eventInfo.triggerName; + actionButton = actionButton.replace('clicked:', ''); + if (actionButton !== 'hitdetresetwounds' && actionButton !== 'hitdetresetall') { + const valWounds = +v['hit_det_wounds'] || 0; + output.hit_det_wounds_total = valWounds; + } else { + console.log(`Change detected: Reset`); + output.hit_det_wounds_total = 0; + output.hit_det_wounds = 0; + } + setAttrs(output); + }); + }); + + // miscellaneous + on( + 'change:hit_det_misc_weapon_at_rest change:hit_det_misc_2nd_consecutive change:hit_det_misc_additional_consecutive change:hit_det_misc_additional_consecutive_multiplier change:hit_det_misc_wrong_hand change:hit_det_misc_wounded_gun_hand change:hit_det_misc_wounded_gun_hand_multiplier change:hit_det_misc_two_weapons change:hit_det_misc_hip_shooting change:hit_det_misc_obscured change:hit_det_misc_gyrojet change:hit_det_misc_total clicked:hitDetResetAll clicked:hitDetResetMisc', + (eventInfo) => { + console.log(`Change detected: ${eventInfo.sourceAttribute}`); + getAttrs( + [ + 'hit_det_misc_weapon_at_rest', + 'hit_det_misc_2nd_consecutive', + 'hit_det_misc_additional_consecutive', + 'hit_det_misc_additional_consecutive_multiplier', + 'hit_det_misc_wrong_hand', + 'hit_det_misc_wounded_gun_hand', + 'hit_det_misc_wounded_gun_hand_multiplier', + 'hit_det_misc_two_weapons', + 'hit_det_misc_hip_shooting', + 'hit_det_misc_obscured', + 'hit_det_misc_gyrojet', + 'hit_det_misc_total', + ], + (v) => { + const output = {}; + let actionButton = eventInfo.triggerName; + actionButton = actionButton.replace('clicked:', ''); + if (actionButton !== 'hitdetresetmisc' && actionButton !== 'hitdetresetall') { + const valWeapon_at_rest = +v.hit_det_misc_weapon_at_rest || 0; + const val2nd_consecutive = +v.hit_det_misc_2nd_consecutive || 0; + const valAdditional_consecutive = +v.hit_det_misc_additional_consecutive || 0; + const valAdditional_consecutive_multiplier = +v.hit_det_misc_additional_consecutive_multiplier || 0; + const valWrong_hand = +v.hit_det_misc_wrong_hand || 0; + const valWounded_gun_hand = +v.hit_det_misc_wounded_gun_hand || 0; + const valWounded_gun_hand_multiplier = +v.hit_det_misc_wounded_gun_hand_multiplier || 0; + const valTwo_weapons = +v.hit_det_misc_two_weapons || 0; + const valHip_shooting = +v.hit_det_misc_hip_shooting || 0; + const valObscured = +v.hit_det_misc_obscured || 0; + const valGyrojet = +v.hit_det_misc_gyrojet || 0; + + output.hit_det_misc_weapon_at_rest = valWeapon_at_rest; + output.hit_det_misc_2nd_consecutive = val2nd_consecutive; + output.hit_det_misc_additional_consecutive = valAdditional_consecutive; + output.hit_det_misc_additional_consecutive_multiplier = valAdditional_consecutive_multiplier; + output.hit_det_misc_wrong_hand = valWrong_hand; + output.hit_det_misc_wounded_gun_hand = valWounded_gun_hand; + output.hit_det_misc_wounded_gun_hand_multiplier = valWounded_gun_hand_multiplier; + output.hit_det_misc_two_weapons = valTwo_weapons; + output.hit_det_misc_hip_shooting = valHip_shooting; + output.hit_det_misc_obscured = valObscured; + output.hit_det_misc_gyrojet = valGyrojet; + output.hit_det_misc_total = + valWeapon_at_rest + + val2nd_consecutive + + valAdditional_consecutive * valAdditional_consecutive_multiplier + + valWrong_hand + + valWounded_gun_hand * valWounded_gun_hand_multiplier + + valTwo_weapons + + valHip_shooting + + valObscured + + valGyrojet; + } else { + console.log(`Change detected: Reset`); + output.hit_det_misc_weapon_at_rest = 0; + output.hit_det_misc_2nd_consecutive = 0; + output.hit_det_misc_additional_consecutive = 0; + output.hit_det_misc_additional_consecutive_multiplier = 1; + output.hit_det_misc_wrong_hand = 0; + output.hit_det_misc_wounded_gun_hand = 0; + output.hit_det_misc_wounded_gun_hand_multiplier = 1; + output.hit_det_misc_two_weapons = 0; + output.hit_det_misc_hip_shooting = 0; + output.hit_det_misc_obscured = 0; + output.hit_det_misc_gyrojet = 0; + output.hit_det_misc_total = 0; + } + setAttrs(output); + }, + ); + }, + ); + + // automatic weapons + on('change:hit_det_automatic clicked:hitDetResetAll clicked:hitDetResetAutomaticWeapons', (eventInfo) => { + console.log(`Change detected: ${eventInfo.sourceAttribute}`); + getAttrs(['hit_det_automatic'], (v) => { + const output = {}; + let actionButton = eventInfo.triggerName; + actionButton = actionButton.replace('clicked:', ''); + if (actionButton !== 'hitdetresetautomaticweapons' && actionButton !== 'hitdetresetall') { + const valAutomatic = +v['hit_det_automatic'] || 0; + output.hit_det_automatic_total = valAutomatic; + } else { + console.log(`Change detected: Reset`); + output.hit_det_automatic_total = 0; + output.hit_det_automatic = 0; + } + setAttrs(output); + }); + }); + + // hit determination total + on('change:hit_det_move_of_shooter_total change:hit_det_move_of_target_total change:hit_det_wounds_total change:hit_det_misc_total change:hit_det_automatic_total', (eventInfo) => { + console.log(`Change detected: ${eventInfo.sourceAttribute}`); + getSectionIDs('repeating_weapons', (idArray) => { + const output = {}; + const fields = ['hit_det_move_of_shooter_total', 'hit_det_move_of_target_total', 'hit_det_wounds_total', 'hit_det_misc_total', 'hit_det_automatic_total']; + _.each(idArray, (id) => { + fields.push(section_attribute('weapons', id, 'hit_det')); + }); + getAttrs(fields, (v) => { + const hit_det_move_of_shooter_total = +v.hit_det_move_of_shooter_total || 0; + const hit_det_move_of_target_total = +v.hit_det_move_of_target_total || 0; + const hit_det_wounds_total = +v.hit_det_wounds_total || 0; + const hit_det_misc_total = +v.hit_det_misc_total || 0; + const hit_det_automatic_total = +v.hit_det_automatic_total || 0; + const hitDetTotal = hit_det_move_of_shooter_total + hit_det_move_of_target_total + hit_det_wounds_total + hit_det_misc_total + hit_det_automatic_total; + output.hit_det_total = hitDetTotal; + idArray.forEach((id) => { + output[section_attribute('weapons', id, 'hit_det')] = hitDetTotal; + }); + setAttrs(output, {silent: true}); + }); + }); + }); + // one-time update on('sheet:opened', (eventInfo) => { console.log(`Change detected: sheet_version check`); diff --git a/Top Secret/sheet.json b/Top Secret/sheet.json index 48b78b9030..4e8783194f 100644 --- a/Top Secret/sheet.json +++ b/Top Secret/sheet.json @@ -4,6 +4,5 @@ "authors": "Richard Gavin (@rocketheadent)", "roll20userid": "1649310", "preview": "TopSecret.png", - "instructions": "", - "legacy": true + "instructions": "" } \ No newline at end of file