Add GM automation: templated roll cards, apply buttons, ammo tracking

- Percentile roll cards: outcome banners (crit/success/failure) with icons
  and margin-of-success text instead of color-only inline styles.
- Damage and lethality cards: Apply Damage / Apply Lethality buttons that
  hit targeted or selected tokens, auto-subtracting equipped armor
  protection reduced by the weapon's armor-piercing value.
- Sanity damage cards: apply buttons for success/failure loss with GM
  whispers for temporary insanity (5+), breaking point crossings, and 0 SAN.
- Ammo: weapon ammo is now numeric with a magazine capacity; attack rolls
  expend a round (ammoTracking setting, default on), gear tab shows an
  ammo column with a reload control, compendium weapons carry capacities.
- CSS: drop live Google Fonts imports (offline reliability); DG2 styles
  with dark-theme support.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 17:36:30 +01:00
co-authored by Claude Fable 5
parent b8eb917714
commit 69e10765f2
24 changed files with 704 additions and 164 deletions
+131 -2
View File
@@ -1,5 +1,3 @@
@import url("https://fonts.googleapis.com/css2?family=Martel:wght@400;800&family=Roboto:wght@300;400;500&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Didact+Gothic");
@font-face {
font-family: "atwriter";
@@ -1468,3 +1466,134 @@ a[data-action="applySkillImprovements"] {
padding: 5px;
grid-gap: 5px;
}
/* ------------------------------------------------------------------ */
/* Delta Green 2 additions */
/* ------------------------------------------------------------------ */
/* Roll outcome banners on chat cards */
.dg2-outcome {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 4px 6px;
margin-bottom: 2px;
border-radius: 3px;
border: 1px solid transparent;
font-family: "Special Elite", "Courier New", monospace;
font-size: 1.05em;
letter-spacing: 0.5px;
}
.dg2-outcome .dg2-margin {
font-size: 0.8em;
opacity: 0.85;
font-style: italic;
}
.dg2-success {
background: rgba(85, 107, 47, 0.15);
border-color: rgba(85, 107, 47, 0.6);
color: #3c5325;
}
.dg2-crit-success {
background: rgba(85, 107, 47, 0.35);
border-color: #556b2f;
color: #2c3e1a;
font-weight: bold;
}
.dg2-failure {
background: rgba(139, 0, 0, 0.08);
border-color: rgba(139, 0, 0, 0.45);
color: #7a1a1a;
}
.dg2-crit-failure,
.dg2-lethal {
background: rgba(139, 0, 0, 0.3);
border-color: #8b0000;
color: #5c0000;
font-weight: bold;
}
/* Keep banners readable on Foundry dark theme */
.theme-dark .dg2-success { color: #a6c47a; }
.theme-dark .dg2-crit-success { color: #c0dd8f; }
.theme-dark .dg2-failure { color: #e08a8a; }
.theme-dark .dg2-crit-failure,
.theme-dark .dg2-lethal { color: #ff9d9d; }
/* Apply buttons on damage / lethality / SAN cards */
.dg2-apply-row {
display: flex;
gap: 4px;
margin-top: 4px;
}
button.dg2-apply-button {
flex: 1;
font-size: 0.85em;
line-height: 1.6;
padding: 1px 4px;
border: 1px groove #666;
border-radius: 3px;
background: rgba(85, 107, 47, 0.2);
cursor: pointer;
}
button.dg2-apply-button:hover {
background: rgba(85, 107, 47, 0.45);
box-shadow: 0 0 4px rgba(85, 107, 47, 0.8);
}
button.dg2-apply-button.dg2-lethal,
button.dg2-apply-button.dg2-san-fail {
background: rgba(139, 0, 0, 0.15);
}
button.dg2-apply-button.dg2-lethal:hover,
button.dg2-apply-button.dg2-san-fail:hover {
background: rgba(139, 0, 0, 0.4);
box-shadow: 0 0 4px rgba(139, 0, 0, 0.8);
}
/* Ammo readout on chat cards and the gear tab */
.dg2-ammo-line {
text-align: center;
font-size: 0.85em;
margin-top: 2px;
opacity: 0.9;
}
.dg2-out-of-ammo {
color: #8b0000;
font-weight: bold;
}
.theme-dark .dg2-out-of-ammo {
color: #ff9d9d;
}
.dg2-ammo-cell {
white-space: nowrap;
}
.dg2-ammo-cell .item-control {
margin-left: 3px;
}
/* Ammo inputs on the weapon item sheet */
.dg2-ammo-inputs {
display: flex;
align-items: center;
gap: 3px;
}
.dg2-ammo-inputs input {
width: 55px;
text-align: center;
}
+19 -52
View File
@@ -3,9 +3,7 @@
"DG.AgentNameFieldTooltip": "LAST NAME, FIRST NAME (AND ALIAS IF APPLICABLE)",
"DG.RollLuck": "Roll Luck",
"DG.Luck": "Luck",
"DG.FallbackText.newItem": "New {type}",
"DG.Skills.accounting": "Accounting",
"DG.Skills.alertness": "Alertness",
"DG.Skills.anthropology": "Anthropology",
@@ -44,7 +42,6 @@
"DG.Skills.unarmed_combat": "Unarmed Combat",
"DG.Skills.unnatural": "Unnatural",
"DG.Skills.flight": "Flight",
"DG.Skills.Tooltip.accounting": "Spot financial red flags like hidden assets, shell companies, or embezzlement.",
"DG.Skills.Tooltip.alertness": "Notice hidden dangers, subtle movements, or quiet sounds before they become threats.",
"DG.Skills.Tooltip.anthropology": "Understand the beliefs, behaviors, and taboos of living cultures and societies.",
@@ -82,7 +79,6 @@
"DG.Skills.Tooltip.swim": "Survive in water under duress—rescue others, escape, or endure hazards.",
"DG.Skills.Tooltip.unarmed_combat": "Subdue or injure opponents using fists, grapples, kicks, and other hand-to-hand techniques.",
"DG.Skills.Tooltip.unnatural": "Recognize, research, or recall truths about entities and forces beyond human understanding.",
"DG.Skills.AddTypedOrCustomSkill": "Add Typed/Custom Skill",
"DG.Skills.AddSkill": "Add Skill",
"DG.Skills.Apply": "Apply",
@@ -95,7 +91,6 @@
"DG.Skills.EditSkill": "Edit Skill",
"DG.Skills.SkillGroup": "Skill Group",
"DG.Skills.SkillName": "Skill Name",
"DG.TypeSkills.Art": "Art",
"DG.TypeSkills.Craft": "Craft",
"DG.TypeSkills.ForeignLanguage": "Foreign Language",
@@ -103,7 +98,6 @@
"DG.TypeSkills.Pilot": "Pilot",
"DG.TypeSkills.Science": "Science",
"DG.TypeSkills.Other": "Other",
"DG.TypeSkills.Tooltip.Art": "Create or appraise artistic works, and spot forgeries in a given field.",
"DG.TypeSkills.Tooltip.Craft": "Build, fix, or sabotage specialized tools or systems like engines, locks, or wiring.",
"DG.TypeSkills.Tooltip.ForeignLanguage": "Speak, read, and understand a specific language.",
@@ -111,7 +105,6 @@
"DG.TypeSkills.Tooltip.Pilot": "Control and navigate air, sea, or space vehicles in critical situations or conditions.",
"DG.TypeSkills.Tooltip.Science": "Apply specialized scientific knowledge to interpret or investigate the physical world.",
"DG.TypeSkills.Tooltip.Other": "Other",
"DG.SpecialTraining.Dialog.CreateSpecialTraining": "Add Special Training",
"DG.SpecialTraining.Dialog.DropDown.CustomSkills": "Custom/Typed Skills",
"DG.SpecialTraining.Dialog.DropDown.Skills": "Skills",
@@ -120,7 +113,6 @@
"DG.SpecialTraining.Dialog.NameLabel": "Name",
"DG.SpecialTraining.Dialog.SkillLabel": "Skill or Stat",
"DG.SpecialTraining.Dialog.Title": "Create/Edit Special Training",
"DG.Attributes.HP": "HP",
"DG.Attributes.WP": "WP",
"DG.Attributes.SAN": "SAN",
@@ -130,7 +122,6 @@
"DG.Attributes.int": "INT",
"DG.Attributes.pow": "POW",
"DG.Attributes.cha": "CHA",
"DG.Attributes.Tooltip.HP": "Hit Points (HP) indicate physical health, a character becomes unconsious at 2 HP, and dies at 0 HP.",
"DG.Attributes.Tooltip.WP": "Willpower Points (WP) indicate mental fortitude and drive. Agents require Willpower Points to keep going despite exhaustion or mentally taxing situations. See Page 47 of the Agent's Handbook for more detail.",
"DG.Attributes.Tooltip.SAN": "Sanity Points (SAN) indicate a connection with humanity and reality as most people perceive it. Click to roll a SAN test",
@@ -140,7 +131,6 @@
"DG.Attributes.Tooltip.int": "See Page 19 of the Agent's Handbook - Intelligence",
"DG.Attributes.Tooltip.pow": "See Page 19 of the Agent's Handbook - Power",
"DG.Attributes.Tooltip.cha": "See Page 19 of the Agent's Handbook - Charisma",
"DG.Navigation.Agent.skills": "Skills",
"DG.Navigation.Agent.physical": "Statistics",
"DG.Navigation.Agent.motivations": "Psychological",
@@ -151,7 +141,6 @@
"DG.Navigation.NPC.gear": "Attacks & Abilities",
"DG.Navigation.Vehicle.gear": "Gear",
"DG.Navigation.Vehicle.notes": "Notes",
"DG.About.Changelog": "CHANGELOG (ONLINE)",
"DG.About.Copyright": "Published by Arc Dream Publishing by arrangement with the Delta Green Partnership. The intellectual property known as Delta Green is a trademark and copyright owned by the Delta Green Partnership, who has licensed its use here.",
"DG.About.License": "The contents of this system are licensed per the terms of the included <a href=\"systems/deltagreen2/LICENSE.txt\">license</a> file, excepting those elements that are components of the Delta Green intellectual property.",
@@ -159,7 +148,6 @@
"DG.About.OnlineHelpTooltip": "Launch browser with online GitHub help.",
"DG.About.OnlineBugTracker": "Questions, Bug Reports or Feature Requests (ONLINE)",
"DG.About.OnlineBugTrackerTooltip": "Launch browser with GitHub issue tracker.",
"DG.Roll.Rolling": "Rolling",
"DG.Roll.Target": "Target: ",
"DG.Roll.Success": "Success",
@@ -173,14 +161,11 @@
"DG.Roll.RollingDamageFor": "Rolling DAMAGE for",
"DG.Roll.Or": "Or",
"DG.Roll.Roll": "Roll",
"DG.Physical.Wounds": "Wounds",
"DG.Physical.FirstAidLabel": "First Aid Has Already Been Attempted: ",
"DG.Physical.Exhausted": "Exhausted:",
"DG.Physical.ExhaustionPenalty": "Penalty:",
"DG.Generic.AddButtonLabel": "Add",
"DG.Mental.BreakingPoint": "Breaking Point: ",
"DG.Mental.Tooltip.BreakingPoint": "When SAN falls to or below this number, Agent gains a long-term mental disorder; set a new Breaking Point to current SAN - POW. Disorders remain even if SAN later rises above a previous Breaking Point.",
"DG.Mental.Reset": "Reset",
@@ -195,7 +180,6 @@
"DG.Mental.AdaptedToHelplessness": "Adapted to Helplessness",
"DG.Mental.Motivations": "Motivation",
"DG.Mental.Disorders": "Disorder",
"DG.Gear.WeaponTypes.Firearms": "Firearms",
"DG.Gear.WeaponTypes.Melee": "Melee",
"DG.Gear.Weapons": "WEAPONS",
@@ -206,7 +190,6 @@
"DG.Gear.ArmorPiercing": "Armor Piercing",
"DG.Gear.ArmorRating": "Armor Rating",
"DG.Gear.Equipped": "Equipped",
"DG.CV.Sex": "Sex",
"DG.CV.SexPlaceholder": "Sex",
"DG.CV.AgeAndDoB": "Age and D.O.B.",
@@ -217,12 +200,10 @@
"DG.CV.NationalityPlaceholder": "Nationality",
"DG.CV.Education": "Education",
"DG.CV.EducationPlaceholder": "Education or Training",
"DG.Bonds.Bond": "Bond",
"DG.Bonds.Name": "Name",
"DG.Bonds.Score": "Score",
"DG.Bonds.NewBond": "New Bond",
"DG.Tooltip.MaximumHP": "Maximum health is the average of STR + CON, rounded UP.",
"DG.Tooltip.MaximumWP": "Maximum WP is equal to POW.",
"DG.Tooltip.CurrentSanityPartOne": "Current sanity, current breaking point is ",
@@ -238,17 +219,14 @@
"DG.Tooltip.AdaptedToHelplessness": "Being adapted to helplessness means your Agent’s personal drive suffers as they pull back from the world around them. As soon as your Agent becomes adapted to helplessness, agent permanently loses 1D6 POW.",
"DG.Tooltip.ToggleEquippedState": "Click to toggle whether this item is equipped or not.",
"DG.Tooltip.Exhaustion": "An Agent who works too long or faces extreme danger and injury without resting becomes exhausted. When this comes into play is up to the Handler, but a good rule of thumb is that going a night without sleep or refusing to rest after losing SAN or Hit Points leads to exhaustion.",
"DG.Gear.Expense.NA": "N/A or None",
"DG.Gear.Expense.Incidental": "Incidental",
"DG.Gear.Expense.Standard": "Standard",
"DG.Gear.Expense.Unusual": "Unusual",
"DG.Gear.Expense.Major": "Major",
"DG.Gear.Expense.Extreme": "Extreme",
"DG.ItemWindow.Bonds.Description": "Description/Developments",
"DG.ItemWindow.Bonds.ScoreTooltip": "A higher score represents a stronger relationship between the agent and the bond.",
"DG.ItemWindow.Motivations.Attributes": "Attributes",
"DG.ItemWindow.Motivations.Motivation": "Motivation",
"DG.ItemWindow.Motivations.Description": "Description/Notes",
@@ -259,19 +237,16 @@
"DG.ItemWindow.Motivations.IsDisorderTooltip": "Check this if adding a disorder, or if an existing motivation has been lost due to gaining a disorder.",
"DG.ItemWindow.Motivations.DisorderHasBeenCured": "Disorder Has Been \"Cured\"",
"DG.ItemWindow.Motivations.DisorderHasBeenCuredTooltip": "A critical success on a therapy luck roll can 'cure' a disorder. It may return the next time a breaking point is hit however. See Pages 78-79 of the Agent's Handbook - Therapy.",
"DG.ItemWindow.Gear.Attributes": "Attributes",
"DG.ItemWindow.Gear.Description": "Description",
"DG.ItemWindow.Gear.NamePlaceholder": "Name",
"DG.ItemWindow.Gear.Expense": "Expense",
"DG.ItemWindow.Gear.Equipped": "Equipped",
"DG.ItemWindow.Armor.Description": "Description",
"DG.ItemWindow.Armor.NamePlaceholder": "Name",
"DG.ItemWindow.Armor.ArmorRating": "Armor Rating",
"DG.ItemWindow.Armor.Expense": "Expense",
"DG.ItemWindow.Armor.Equipped": "Equipped",
"DG.ItemWindow.Weapons.Description": "Description",
"DG.ItemWindow.Weapons.NamePlaceholder": "Name",
"DG.ItemWindow.Weapons.Skill": "Skill",
@@ -285,27 +260,21 @@
"DG.ItemWindow.Weapons.Ammo": "Ammo (Amount/Type)",
"DG.ItemWindow.Weapons.Range": "Range",
"DG.ItemWindow.Weapons.KillRadius": "Kill Radius",
"DG.ModifySkillRollDialogue.Title": "Modify Roll",
"DG.ModifySkillRollDialogue.Test": "Test:",
"DG.ModifySkillRollDialogue.OriginalTarget": "Original Target:",
"DG.ModifySkillRollDialogue.TargetModifier": "Modify Target:",
"DG.ModifySkillRollDialogue.ModifierTooltip": "Give a positive or negative modifier here to add/subtract from the target for the skill check.",
"DG.ModifyDamageRollDialogue.Weapon": "Weapon:",
"DG.ModifyDamageRollDialogue.OuterModifierTooltip": "Use this field to modify the outer part of the formula, such as to double the result.",
"DG.ModifyDamageRollDialogue.InnerModifierTooltip": "Use this field to add a flat modifier such as '+1' or another dice term such as '+1d4'.",
"DG.Roll.Inhuman": "Inhuman",
"DG.Tooltip.BreakPoint": "The current breaking point, reset when current sanity falls to or below this value. The starting breaking point is SAN - POW.",
"DG.Tooltip.BreakPointReset": "Reset breaking point to be current SAN - WP.",
"DG.Skills.ritual": "Ritual",
"DG.RollMode": "Roll Mode",
"DG.Bonds.Relationship": "Relationship",
"DG.TypeSkills.Subskills.Painting": "Painting",
"DG.Sheet.BlockHeaders.Attributes": "ATTRIBUTES",
"DG.Sheet.BlockHeaders.Statistics": "STATISTICS",
"DG.Sheet.BlockHeaders.PhysicalDescriptionNotes": "PHYSICAL DESCRIPTION / NOTES",
@@ -315,9 +284,7 @@
"DG.Sheet.BlockHeaders.MotivationsAndDisorders": "MOTIVATIONS AND DISORDERS",
"DG.Sheet.BlockHeaders.PersonalInfoCV": "PERSONAL INFORMATION / CV",
"DG.Sheet.BlockHeaders.BondsRelationshipsContacts": "BONDS / RELATIONSHIPS / CONTACTS",
"DG.AgentSheet.ProfessionPlaceholder": "PROFESSION (RANK IF APPLICABLE)",
"DG.UnnaturalSheet.HideUntrainedSkillsLabel": "Hide Untrained",
"DG.UnnaturalSheet.NotesLabel": "Notes",
"DG.UnnaturalSheet.ShortDescriptionPlaceholder": "Description of creature or creature type",
@@ -325,39 +292,31 @@
"DG.UnnaturalSheet.Tabs.NotesTabLabel": "Skills & Notes",
"DG.UnnaturalSheet.Tabs.ItemsTabLabel": "Attacks & Abilities",
"DG.NPCSheet.BreakPointAbbreviation": "BP",
"DG.ImpossibleLanscapes.IHaveSeenThe": "I HAVE SEEN THE",
"DG.ImpossibleLanscapes.YellowSign": "YELLOW SIGN",
"DG.ImpossibleLanscapes.CorruptionFirstHalf": "Corrup",
"DG.ImpossibleLanscapes.CorruptionSecondHalf": "tion",
"DG.ImpossibleLanscapes.Gift": "Gift",
"DG.ImpossibleLanscapes.Insight": "Insight",
"DG.VehicleSheet.NotesTabName": "Notes",
"DG.VehicleSheet.GearTabName": "Gear",
"DG.VehicleSheet.NotesLabel": "NOTES",
"DG.VehicleSheet.DescriptionPlaceHolder": "Vehicle Description",
"DG.Generic.CheckDamagedBondsBoxUntilNextHomeScene": "Check a damaged Bond's box until the next Home Scene ends.",
"DG.Generic.ClearAll": "Clear Checks",
"DG.Generic.SanDamage": "SAN Damage",
"DG.ItemWindow.Weapons.CustomSkillTarget": "Target",
"DG.ItemWindow.Weapons.SkillModifier": "Skill Modifier",
"DG.ItemWindow.Custom": "Custom",
"DG.Sheet.class.item": "Item Sheet",
"DG.Sheet.class.agent": "Agent Sheet",
"DG.Sheet.class.npc": "NPC Sheet",
"DG.Sheet.class.unnatural": "Unnatural Sheet",
"DG.Sheet.class.vehicle": "Vehicle Sheet",
"TYPES.Actor.agent": "Agent",
"TYPES.Actor.npc": "NPC",
"TYPES.Actor.unnatural": "Unnatural",
"TYPES.Actor.vehicle": "Vehicle",
"TYPES.Item.armor": "Armor",
"TYPES.Item.bond": "Bond",
"TYPES.Item.gear": "Gear",
@@ -365,13 +324,10 @@
"TYPES.Item.weapon": "Weapon",
"TYPES.Item.tome": "Tome",
"TYPES.Item.ritual": "Ritual",
"DG.Gear.Tomes": "TOMES",
"DG.Gear.Rituals": "RITUALS",
"DG.Tomes.Name": "Name",
"DG.Ritual.Name": "Name",
"DG.Tome.Name": "Name",
"DG.Tome.Language": "Language",
"DG.Tome.StudyTime": "Study Time",
@@ -383,15 +339,12 @@
"DG.ItemWindow.Tome.StudySanityDamage": "Study Sanity Damage",
"DG.ItemWindow.Tome.HandlerOnlyTabHeader": "Handler Only",
"DG.ItemWindow.Tome.HandlerNotes": "Handler Notes",
"DG.ItemWindow.Ritual.ActivationSanityDamage": "Activation SAN Damage",
"DG.ItemWindow.Ritual.ActivationTime": "Activation Time",
"DG.ItemWindow.Ritual.ActivationCosts": "Activation Costs",
"DG.ItemWindow.Ritual.StudyTime": "Study Time",
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Learn SAN Damage",
"DG.MissionPaused": "Mission Paused",
"DG.Settings.charactersheet.name": "Character Sheet Style",
"DG.Settings.charactersheet.hint": "Choose how actor sheets should be styled. 'Program' is a more modern government style, where 'Cowboy/Outlaw' is an older, grittier looking typewriter style.",
"DG.Settings.charactersheet.cowboys": "The Cowboys",
@@ -419,15 +372,29 @@
"DG.Settings.skillFailure.name": "Auto-mark Skill Failure",
"DG.Settings.skillFailure.hint": "Activate this setting to always mark a failure checkbox upon failing a skill. The Handler can undo the action from the chat message for the roll.",
"DG.Settings.Saved": "The current settings have been saved.",
"DG.SettingsMenu.automation.name": "Automation Menu",
"DG.SettingsMenu.automation.label": "Automation Menu",
"DG.SettingsMenu.automation.title": "Automation Settings",
"DG.SettingsMenu.handler.name": "Handler Settings",
"DG.SettingsMenu.handler.label": "Handler Settings",
"DG.SettingsMenu.handler.title": "Handler-only Settings",
"DG.Messages.Skillsmark.Marked": "This skill is marked",
"DG.Messages.Skillsmark.Tooltip": "See p. 29 Agent's Handbook"
}
"DG.Messages.Skillsmark.Tooltip": "See p. 29 Agent's Handbook",
"DG.Settings.ammoTracking.name": "Automate Ammo Tracking",
"DG.Settings.ammoTracking.hint": "Weapons that track ammo automatically expend one round per attack roll. Reload from the weapon list on the Equipment tab.",
"DG2.Roll.MadeItBy": "made it by",
"DG2.Roll.MissedBy": "missed by",
"DG2.Ammo.Header": "Ammo",
"DG2.Ammo.Remaining": "Ammo",
"DG2.Ammo.OutOfAmmo": "OUT OF AMMO — reload!",
"DG2.Ammo.ReloadTooltip": "Reload to full capacity",
"DG2.Ammo.CapacityTooltip": "Magazine capacity (0 = no ammo tracking)",
"DG2.Apply.Damage": "Apply Damage",
"DG2.Apply.DamageTooltip": "Apply this damage to targeted or selected tokens. Equipped armor (minus the weapon's AP) is subtracted automatically.",
"DG2.Apply.Lethality": "Apply Lethality (0 HP)",
"DG2.Apply.LethalityTooltip": "The lethality roll succeeded: reduce targeted or selected tokens to 0 HP.",
"DG2.Apply.SanSuccessTooltip": "Apply the lower SAN loss (the SAN test succeeded).",
"DG2.Apply.SanFailTooltip": "Apply the higher SAN loss (the SAN test failed).",
"DG2.Apply.NoTarget": "Target or select a token first.",
"DG2.Apply.NoSanTarget": "No valid target with a Sanity score."
}
+2 -1
View File
@@ -15,7 +15,8 @@ export default class WeaponItemData extends foundry.abstract.TypeDataModel {
lethality: new NumberField({ initial: 0, integer: true }),
isLethal: new BooleanField({ initial: false }),
killRadius: new StringField({ initial: "N/A" }),
ammo: new StringField({ initial: "" }),
ammo: new NumberField({ initial: 0, integer: true, min: 0, nullable: false }),
ammoMax: new NumberField({ initial: 0, integer: true, min: 0, nullable: false }),
expense: new StringField({ initial: "Standard" }),
equipped: new BooleanField({ initial: true }),
};
+202
View File
@@ -0,0 +1,202 @@
import DGUtils from "./utility-functions.js";
/**
* DG2 chat-card apply actions.
*
* Resolves the tokens that damage/sanity loss should be applied to:
* the user's targets first, then their controlled tokens.
*
* @returns {Token[]}
*/
function resolveTargetTokens() {
const targeted = Array.from(game.user.targets);
if (targeted.length) return targeted;
return canvas?.tokens?.controlled ?? [];
}
/**
* Whisper an application summary to the GM (and the applying user).
*
* @param {string} content - HTML content of the whisper.
*/
async function whisperGM(content) {
const gmIds = game.users.filter((u) => u.isGM).map((u) => u.id);
const whisper = [...new Set([...gmIds, game.user.id])];
await ChatMessage.create({
content,
whisper,
speaker: { alias: "Delta Green 2" },
});
}
/**
* Guard helper - can the current user update this actor?
*
* @param {Actor} actor
* @returns {boolean}
*/
function canModify(actor) {
if (actor.canUserModify(game.user, "update")) return true;
ui.notifications.warn(
`${game.user.name} does not have permission to modify ${actor.name}.`,
);
return false;
}
/**
* Apply HP damage to targeted/selected tokens, subtracting armor
* protection (reduced by the weapon's armor-piercing value).
*
* @param {Object} options
* @param {number} options.damage - the rolled damage total.
* @param {number} [options.ap] - armor piercing value of the source weapon.
*/
export async function applyDamageToTargets({ damage, ap = 0 }) {
const tokens = resolveTargetTokens();
if (!tokens.length) {
ui.notifications.warn(
DGUtils.localizeWithFallback(
"DG2.Apply.NoTarget",
"Target or select a token first.",
),
);
return;
}
for (const token of tokens) {
const { actor } = token;
if (!actor?.system?.health || !canModify(actor)) continue;
const protection = Math.max(
0,
(actor.system.health.protection ?? 0) - Math.max(0, ap),
);
const applied = Math.max(0, damage - protection);
const oldHP = actor.system.health.value;
const newHP = Math.max(0, oldHP - applied);
// eslint-disable-next-line no-await-in-loop
await actor.update({ "system.health.value": newHP });
const armorNote = protection
? ` (${damage} &minus; ${protection} armor${ap ? `, AP ${ap}` : ""})`
: "";
// eslint-disable-next-line no-await-in-loop
await whisperGM(
`<b>${actor.name}</b> takes <b>${applied}</b> damage${armorNote}. HP ${oldHP} &rarr; ${newHP}.${
newHP === 0 ? " <b>DOWN.</b>" : ""
}`,
);
}
}
/**
* Apply a successful lethality roll: target drops to 0 HP.
*/
export async function applyLethalityToTargets() {
const tokens = resolveTargetTokens();
if (!tokens.length) {
ui.notifications.warn(
DGUtils.localizeWithFallback(
"DG2.Apply.NoTarget",
"Target or select a token first.",
),
);
return;
}
for (const token of tokens) {
const { actor } = token;
if (!actor?.system?.health || !canModify(actor)) continue;
const oldHP = actor.system.health.value;
// eslint-disable-next-line no-await-in-loop
await actor.update({ "system.health.value": 0 });
// eslint-disable-next-line no-await-in-loop
await whisperGM(
`<b>${actor.name}</b> suffers a <b>LETHAL</b> hit. HP ${oldHP} &rarr; 0.`,
);
}
}
/**
* Apply sanity loss. Applies to targeted/selected tokens if any,
* otherwise falls back to the actor who made the roll (the usual case).
* Alerts the GM if the loss crosses the actor's breaking point, or if
* the loss in one hit is 5+ (temporary insanity per the Agent's Handbook).
*
* @param {Object} options
* @param {number} options.san - the sanity loss to apply.
* @param {Actor} [options.fallbackActor] - the rolling actor.
*/
export async function applySanToTargets({ san, fallbackActor }) {
const tokens = resolveTargetTokens();
let actors = tokens.map((t) => t.actor).filter((a) => a?.system?.sanity);
if (!actors.length && fallbackActor?.system?.sanity) {
actors = [fallbackActor];
}
if (!actors.length) {
ui.notifications.warn(
DGUtils.localizeWithFallback(
"DG2.Apply.NoSanTarget",
"No valid target with a Sanity score.",
),
);
return;
}
for (const actor of actors) {
if (!canModify(actor)) continue;
const oldSan = actor.system.sanity.value;
const newSan = Math.max(0, oldSan - san);
// eslint-disable-next-line no-await-in-loop
await actor.update({ "system.sanity.value": newSan });
let note = `<b>${actor.name}</b> loses <b>${san}</b> SAN. ${oldSan} &rarr; ${newSan}.`;
if (san >= 5) {
note += ` <b>TEMPORARY INSANITY</b> (5+ SAN from one source).`;
}
const breakingPoint = actor.system.sanity.currentBreakingPoint;
if (
typeof breakingPoint === "number" &&
oldSan > breakingPoint &&
newSan <= breakingPoint
) {
note += ` <b>BREAKING POINT REACHED</b> &mdash; the agent gains a disorder and should reset their breaking point.`;
}
if (newSan === 0) {
note += ` <b>PERMANENT INSANITY.</b>`;
}
// eslint-disable-next-line no-await-in-loop
await whisperGM(note);
}
}
/**
* Router for DG2 chat-card buttons. Returns true if the action was handled.
*
* @param {HTMLElement} btn - the clicked button.
* @param {ChatMessage} message - the chat message the button lives in.
* @returns {Promise<boolean>}
*/
export async function handleDG2ChatAction(btn, message) {
const { action } = btn.dataset;
switch (action) {
case "dg2-apply-damage":
await applyDamageToTargets({
damage: parseInt(btn.dataset.damage) || 0,
ap: parseInt(btn.dataset.ap) || 0,
});
return true;
case "dg2-apply-lethality":
await applyLethalityToTargets();
return true;
case "dg2-apply-san":
await applySanToTargets({
san: parseInt(btn.dataset.san) || 0,
fallbackActor: message?.speakerActor,
});
return true;
default:
return false;
}
}
+8
View File
@@ -1,8 +1,16 @@
/* eslint-disable import/prefer-default-export */
import { handleDG2ChatAction } from "./apply-effects.js";
export async function handleInlineActions(btnWithAction, messageId) {
const action = btnWithAction.dataset?.action;
const message = game.messages.get(messageId);
// DG2 apply actions work off targets, not the message speaker.
if (action?.startsWith("dg2-")) {
await handleDG2ChatAction(btnWithAction, message);
return;
}
const actor = message?.speakerActor;
if (!actor) return;
+7
View File
@@ -6,6 +6,13 @@ export default function registerHandlebarsHelpers() {
return DGUtils.localizeWithFallback(value, fallbackValue);
});
// Logical OR - only register if the Foundry core version hasn't already.
if (!Handlebars.helpers.or) {
Handlebars.registerHelper("or", (...args) =>
args.slice(0, -1).some(Boolean),
);
}
Handlebars.registerHelper("concat", (...args) => {
let outStr = "";
for (const arg in args) {
+128 -92
View File
@@ -263,24 +263,53 @@ export class DGPercentileRoll extends DGRoll {
const label = this.createLabel();
let resultString = "";
let styleOverride = "";
let outcomeClass = "";
let outcomeIcon = "";
if (this.isSuccess) {
if (this.isCritical) {
resultString = `${game.i18n.localize("DG.Roll.CriticalSuccess")}`;
resultString = `${resultString.toUpperCase()}`;
styleOverride = "color: green";
resultString = `${game.i18n
.localize("DG.Roll.CriticalSuccess")
.toUpperCase()}`;
outcomeClass = "dg2-crit-success";
outcomeIcon = "fa-star";
} else {
resultString = `${game.i18n.localize("DG.Roll.Success")}`;
outcomeClass = "dg2-success";
outcomeIcon = "fa-check";
}
} else if (this.isCritical) {
resultString = `${game.i18n.localize("DG.Roll.CriticalFailure")}`;
resultString = `${resultString.toUpperCase()}`;
styleOverride = "color: red";
resultString = `${game.i18n
.localize("DG.Roll.CriticalFailure")
.toUpperCase()}`;
outcomeClass = "dg2-crit-failure";
outcomeIcon = "fa-skull";
} else {
resultString = `${game.i18n.localize("DG.Roll.Failure")}`;
outcomeClass = "dg2-failure";
outcomeIcon = "fa-xmark";
}
// Margin of success/failure, e.g. "made it by 23".
let marginText = "";
if (typeof this.effectiveTarget === "number") {
const margin = this.isSuccess
? this.effectiveTarget - this.total
: this.total - this.effectiveTarget;
marginText = this.isSuccess
? `${DGUtils.localizeWithFallback(
"DG2.Roll.MadeItBy",
"made it by",
)} ${margin}`
: `${DGUtils.localizeWithFallback(
"DG2.Roll.MissedBy",
"missed by",
)} ${margin}`;
}
// Expend ammunition on weapon attack rolls.
const { ammoLine, outOfAmmo } = await this.expendAmmo();
const failureMark =
this.actor?.type === "agent" &&
!this.isSuccess &&
@@ -292,11 +321,15 @@ export class DGPercentileRoll extends DGRoll {
const html = await renderTemplate(
"systems/deltagreen2/templates/roll/percentile-roll.hbs",
{
styleOverride,
outcomeClass,
outcomeIcon,
marginText,
resultString,
formula: this.formula,
total: this.total,
failureMark,
ammoLine,
outOfAmmo,
},
);
@@ -306,7 +339,7 @@ export class DGPercentileRoll extends DGRoll {
const message = await this.toMessage({
flags: {
deltagreen: {
deltagreen2: {
rollbacks: {
[keyForUpdate]: false,
},
@@ -331,6 +364,43 @@ export class DGPercentileRoll extends DGRoll {
return this.toMessage({ content: html, flavor: label });
}
/**
* Expend one round of ammunition on a weapon attack roll.
* Only applies to owned weapon items that track ammo (ammo or ammoMax > 0),
* and only when the ammoTracking setting is enabled.
*
* @returns {Promise<Object>} - { ammoLine, outOfAmmo } for the chat card, or {}.
*/
async expendAmmo() {
if (this.type !== "weapon" || !this.item?.isOwned) return {};
if (!game.settings.get(DG.ID, "ammoTracking")) return {};
const ammo = this.item.system.ammo ?? 0;
const ammoMax = this.item.system.ammoMax ?? 0;
if (ammo <= 0 && ammoMax <= 0) return {}; // This weapon does not track ammo.
if (ammo <= 0) {
return {
ammoLine: DGUtils.localizeWithFallback(
"DG2.Ammo.OutOfAmmo",
"OUT OF AMMO — reload!",
),
outOfAmmo: true,
};
}
const remaining = ammo - 1;
await this.item.update({ "system.ammo": remaining });
const maxDisplay = ammoMax > 0 ? ` / ${ammoMax}` : "";
return {
ammoLine: `${DGUtils.localizeWithFallback(
"DG2.Ammo.Remaining",
"Ammo",
)}: ${remaining}${maxDisplay}`,
outOfAmmo: remaining === 0,
};
}
/**
* Utility function, called in the DGPercentileRoll constructor.
* If this roll key corresponds to a stat, skill,
@@ -572,13 +642,21 @@ export class DGLethalityRoll extends DGPercentileRoll {
* @override
*/
async toChat() {
// Note: intentionally target + modifier, not effectiveTarget - a lethality
// roll is a property of the weapon, so exhaustion penalties do not apply.
const isLethal = this.total <= this.target + this.modifier;
let resultString = "";
let styleOverride = "";
if (this.total <= this.target) {
let outcomeClass = "";
let outcomeIcon = "";
if (isLethal) {
resultString = `${game.i18n.localize("DG.Roll.Lethal").toUpperCase()}`;
styleOverride = "color: red";
outcomeClass = "dg2-lethal";
outcomeIcon = "fa-skull-crossbones";
} else {
resultString = `${game.i18n.localize("DG.Roll.Failure")}`;
outcomeClass = "dg2-failure";
outcomeIcon = "fa-xmark";
}
const { nonLethalDamage } = this;
@@ -593,47 +671,18 @@ export class DGLethalityRoll extends DGPercentileRoll {
label += ` (${DGUtils.formatStringWithLeadingPlus(this.modifier)}%)`;
}
let html = "";
html += `<div class="dice-roll" data-action="expandRoll">`;
html += ` <div class="dice-result">`;
html += ` <div style="${styleOverride}" class="dice-formula">${resultString}</div>`;
html += ` <div class="dice-tooltip">`;
html += ` <div class="wrapper">`;
html += ` <section class="tooltip-part">`;
html += ` <div class="dice">`;
html += ` <header class="part-header flexrow">`;
html += ` <span class="part-formula">`;
html += ` d100`;
html += ` </span>`;
html += ` <span class="part-total">`;
html += ` ${this.total}`;
html += ` </span>`;
html += ` </header>`;
html += ` <ol class="dice-rolls">`;
html += ` <li class="roll die d100">${this.total}`;
html += ` </ol>`;
html += ` <hr>`;
html += ` <header class="part-header flexrow">`;
html += ` <span class="part-formula">`;
html += ` 2d10 (d10 + d10)`;
html += ` </span>`;
html += ` <span class="part-total">`;
html += ` ${nonLethalDamage.total}`;
html += ` </span>`;
html += ` </header>`;
html += ` <ol class="dice-rolls">`;
html += ` <li class="roll die d10">${nonLethalDamage.die1}</li>`;
html += ` <li class="roll die d10">${nonLethalDamage.die2}</li>`;
html += ` </ol>`;
html += ` </div>`;
html += ` </section>`;
html += ` </div>`;
html += ` </div>`;
html += ` <h4 class="dice-total">${this.total} (${
nonLethalDamage.total
} ${game.i18n.localize("DG.Roll.Damage")})</h4>`;
html += ` </div>`;
html += `</div>`;
const html = await renderTemplate(
"systems/deltagreen2/templates/roll/lethality-roll.hbs",
{
resultString,
outcomeClass,
outcomeIcon,
total: this.total,
nonLethal: nonLethalDamage,
isLethal,
ap: this.item?.system.armorPiercing ?? 0,
},
);
return this.toMessage({ content: html, flavor: label });
}
@@ -705,7 +754,20 @@ export class DGDamageRoll extends DGRoll {
// console.log(ex);
label = `Rolling <b>DAMAGE</b> for <b>${label.toUpperCase()}</b>`;
}
return this.toMessage({ content: this.total, flavor: label });
const html = await renderTemplate(
"systems/deltagreen2/templates/roll/damage-roll.hbs",
{
formula: this.formula,
total: this.total,
dice: this.dice.flatMap((die) =>
die.results.map((r) => ({ faces: die.faces, result: r.result })),
),
ap: this.item?.system.armorPiercing ?? 0,
},
);
return this.toMessage({ content: html, flavor: label });
}
async showDialog() {
@@ -790,44 +852,18 @@ export class DGSanityDamageRoll extends DGRoll {
"SAN DAMAGE",
)}</b> For <b>${lowDie.formula} / ${highDie.formula}</b>`;
let html = "";
html += `<div class="dice-roll" data-action="expandRoll">`;
html += ` <div class="dice-result">`;
html += ` <div class="dice-formula">${lowDie.formula} / ${highDie.formula}</div>`;
html += ` <div class="dice-tooltip">`;
html += ` <div class="wrapper">`;
html += ` <section class="tooltip-part">`;
html += ` <div class="dice">`;
html += ` <header class="part-header flexrow">`;
html += ` <span class="part-formula">`;
html += ` ${lowDie.formula}`;
html += ` </span>`;
html += ` <span class="part-total">`;
html += ` ${lowResult}`;
html += ` </span>`;
html += ` </header>`;
html += ` <ol class="dice-rolls">`;
html += ` <li class="roll die d${lowDie.faces}">${lowResult}`;
html += ` </ol>`;
html += ` <hr>`;
html += ` <header class="part-header flexrow">`;
html += ` <span class="part-formula">`;
html += ` ${highDie.formula}`;
html += ` </span>`;
html += ` <span class="part-total">`;
html += ` ${highResult}`;
html += ` </span>`;
html += ` </header>`;
html += ` <ol class="dice-rolls">`;
html += ` <li class="roll die d${highDie.faces}">${highResult}</li>`;
html += ` </ol>`;
html += ` </div>`;
html += ` </section>`;
html += ` </div>`;
html += ` </div>`;
html += ` <h4 class="dice-total">${lowResult} / ${highResult}</h4>`;
html += ` </div>`;
html += `</div>`;
const html = await renderTemplate(
"systems/deltagreen2/templates/roll/sanity-damage-roll.hbs",
{
lowFormula: lowDie.formula,
highFormula: highDie.formula,
lowFaces: lowDie.faces,
highFaces: highDie.faces,
lowResult,
highResult,
},
);
return this.toMessage({ content: html, flavor });
}
+4
View File
@@ -216,6 +216,10 @@ class AutomationSettings extends SettingForm {
default: false,
type: Boolean,
},
ammoTracking: {
default: true,
type: Boolean,
},
};
}
}
+32
View File
@@ -24,6 +24,8 @@ export default class DGActorSheet extends DGSheetMixin(ActorSheetV2) {
// Toggles.
toggle: this._toggleGeneric,
toggleItemSortMode: this._toggleItemSortMode,
// DG2 additions.
dg2Reload: this._onDG2Reload,
// Other actions.
browsePack: this._browsePack,
},
@@ -1324,6 +1326,36 @@ export default class DGActorSheet extends DGSheetMixin(ActorSheetV2) {
roll.toChat();
}
/**
* DG2: Reload a weapon to its magazine capacity.
* If no capacity is set yet, adopt the current ammo count as the capacity
* (covers items imported from the compendium, which only carry an ammo count).
*/
static async _onDG2Reload(event, target) {
const itemId = target.closest("[data-item-id]")?.dataset.itemId;
const item = this.actor.items.get(itemId);
if (!item) return;
const { ammo, ammoMax } = item.system;
if (!ammoMax && !ammo) {
ui.notifications.info(
`${item.name}: set an ammo capacity on the item sheet to enable reloading.`,
);
return;
}
if (!ammoMax) {
// First reload on a legacy/compendium item: current ammo becomes capacity.
await item.update({ "system.ammoMax": ammo });
ui.notifications.info(`${item.name}: magazine capacity set to ${ammo}.`);
return;
}
await item.update({ "system.ammo": ammoMax });
ChatMessage.create({
content: `${this.actor.name} reloads <b>${item.name}</b> (${ammoMax} rounds).`,
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
});
}
/** Handler for generic toggle events */
static _toggleGeneric(event, target) {
const { prop } = target.dataset;
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-17:24:23.643710 17255f000 Delete type=3 #1
2026/07/15-17:24:23.644337 173e0f000 Level-0 table #5: started
2026/07/15-17:24:23.644539 173e0f000 Level-0 table #5: 3018 bytes OK
2026/07/15-17:24:23.644633 173e0f000 Delete type=0 #3
2026/07/15-17:24:23.644717 173e0f000 Manual compaction at level-0 from '!items!0Gz1jcY28Psezqrv' @ 72057594037927935 : 1 .. '!items!xb73X4SjsbuYs4D7' @ 0 : 0; will stop at (end)
2026/07/15-17:36:13.527610 1723af000 Delete type=3 #1
2026/07/15-17:36:13.528598 17243b000 Level-0 table #5: started
2026/07/15-17:36:13.528807 17243b000 Level-0 table #5: 3054 bytes OK
2026/07/15-17:36:13.528916 17243b000 Delete type=0 #3
2026/07/15-17:36:13.528992 17243b000 Manual compaction at level-0 from '!items!0Gz1jcY28Psezqrv' @ 72057594037927935 : 1 .. '!items!xb73X4SjsbuYs4D7' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-17:24:23.654960 172d6b000 Delete type=3 #1
2026/07/15-17:24:23.655560 173e0f000 Level-0 table #5: started
2026/07/15-17:24:23.655746 173e0f000 Level-0 table #5: 3318 bytes OK
2026/07/15-17:24:23.655837 173e0f000 Delete type=0 #3
2026/07/15-17:24:23.655900 173e0f000 Manual compaction at level-0 from '!items!0gCwBTqSAbG9J20Y' @ 72057594037927935 : 1 .. '!items!u5fnrg385Z79DUK6' @ 0 : 0; will stop at (end)
2026/07/15-17:36:13.537489 170b8b000 Delete type=3 #1
2026/07/15-17:36:13.538033 17243b000 Level-0 table #5: started
2026/07/15-17:36:13.538233 17243b000 Level-0 table #5: 3361 bytes OK
2026/07/15-17:36:13.538323 17243b000 Delete type=0 #3
2026/07/15-17:36:13.538387 17243b000 Manual compaction at level-0 from '!items!0gCwBTqSAbG9J20Y' @ 72057594037927935 : 1 .. '!items!u5fnrg385Z79DUK6' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-17:24:24.337112 172d6b000 Delete type=3 #1
2026/07/15-17:24:24.359971 173e0f000 Level-0 table #5: started
2026/07/15-17:24:24.381364 173e0f000 Level-0 table #5: 3406983 bytes OK
2026/07/15-17:24:24.381556 173e0f000 Delete type=0 #3
2026/07/15-17:24:24.381873 173e0f000 Manual compaction at level-0 from '!actors!02uQjuR9EWKHVnMm' @ 72057594037927935 : 1 .. '!folders!w3IlJRYnQRCVCyhK' @ 0 : 0; will stop at (end)
2026/07/15-17:36:14.183161 171397000 Delete type=3 #1
2026/07/15-17:36:14.206130 17243b000 Level-0 table #5: started
2026/07/15-17:36:14.226780 17243b000 Level-0 table #5: 3411818 bytes OK
2026/07/15-17:36:14.226999 17243b000 Delete type=0 #3
2026/07/15-17:36:14.227636 17243b000 Manual compaction at level-0 from '!actors!02uQjuR9EWKHVnMm' @ 72057594037927935 : 1 .. '!folders!w3IlJRYnQRCVCyhK' @ 0 : 0; will stop at (end)
Binary file not shown.
+17
View File
@@ -29,6 +29,7 @@
</span>
<span class="centered-item-property">{{localize 'DG.Gear.DamageOrLethality'}}</span>
<span class="centered-item-property">{{localize 'DG.Gear.ArmorPiercing'}}</span>
<span class="centered-item-property">{{localize 'DG2.Ammo.Header'}}</span>
<div class="item-controls">
<a class="item-control action-pill"
data-action="itemAction"
@@ -110,6 +111,22 @@
<span class="centered-item-property">{{item.system.armorPiercing}}</span>
<span class="centered-item-property dg2-ammo-cell">
{{#if (or item.system.ammo item.system.ammoMax)}}
<span class="{{#unless item.system.ammo}}dg2-out-of-ammo{{/unless}}">
{{item.system.ammo}}{{#if item.system.ammoMax}}/{{item.system.ammoMax}}{{/if}}
</span>
<a class="item-control"
data-action="dg2Reload"
data-item-id="{{item._id}}"
title="{{localize 'DG2.Ammo.ReloadTooltip'}}">
<i class="fas fa-rotate-left"></i>
</a>
{{else}}
&mdash;
{{/if}}
</span>
<div class="item-controls">
<a class="item-control"
data-action="itemAction"
+6 -1
View File
@@ -104,7 +104,12 @@
<div class="weapon-sheet-secondary-grid-2col">
<span class="resource-label">{{localize 'DG.ItemWindow.Weapons.Ammo'}}:</span>
<input type="text" name="system.ammo" value="{{item.system.ammo}}" data-dtype="String"/>
<span class="dg2-ammo-inputs">
<input type="number" name="system.ammo" value="{{item.system.ammo}}" data-dtype="Number" min="0" step="1"/>
/
<input type="number" name="system.ammoMax" value="{{item.system.ammoMax}}" data-dtype="Number" min="0" step="1"
title="{{localize 'DG2.Ammo.CapacityTooltip'}}"/>
</span>
</div>
<br>
</header>
+31
View File
@@ -0,0 +1,31 @@
<div class="dice-roll" data-action="expandRoll">
<div class="dice-result">
<div class="dice-formula">{{formula}}</div>
<div class="dice-tooltip">
<div class="wrapper">
<section class="tooltip-part">
<div class="dice">
<header class="part-header flexrow">
<span class="part-formula">{{formula}}</span>
<span class="part-total">{{total}}</span>
</header>
<ol class="dice-rolls">
{{#each dice}}
<li class="roll die d{{this.faces}}">{{this.result}}</li>
{{/each}}
</ol>
</div>
</section>
</div>
</div>
<h4 class="dice-total">{{total}}</h4>
<div class="dg2-apply-row">
<button type="button" class="dg2-apply-button" data-action="dg2-apply-damage"
data-damage="{{total}}" data-ap="{{ap}}"
data-tooltip="{{localize 'DG2.Apply.DamageTooltip'}}">
<i class="fas fa-heart-crack" aria-hidden="true"></i>
{{localize 'DG2.Apply.Damage'}} ({{total}}{{#if ap}}, AP {{ap}}{{/if}})
</button>
</div>
</div>
</div>
+49
View File
@@ -0,0 +1,49 @@
<div class="dice-roll" data-action="expandRoll">
<div class="dice-result">
<div class="dg2-outcome {{outcomeClass}}">
<i class="fas {{outcomeIcon}}" aria-hidden="true"></i>
<span class="dg2-outcome-text">{{resultString}}</span>
</div>
<div class="dice-tooltip">
<div class="wrapper">
<section class="tooltip-part">
<div class="dice">
<header class="part-header flexrow">
<span class="part-formula">d100</span>
<span class="part-total">{{total}}</span>
</header>
<ol class="dice-rolls">
<li class="roll die d100">{{total}}</li>
</ol>
<hr>
<header class="part-header flexrow">
<span class="part-formula">2d10 (d10 + d10)</span>
<span class="part-total">{{nonLethal.total}}</span>
</header>
<ol class="dice-rolls">
<li class="roll die d10">{{nonLethal.die1}}</li>
<li class="roll die d10">{{nonLethal.die2}}</li>
</ol>
</div>
</section>
</div>
</div>
<h4 class="dice-total">{{total}} ({{nonLethal.total}} {{localize 'DG.Roll.Damage'}})</h4>
<div class="dg2-apply-row">
{{#if isLethal}}
<button type="button" class="dg2-apply-button dg2-lethal" data-action="dg2-apply-lethality"
data-tooltip="{{localize 'DG2.Apply.LethalityTooltip'}}">
<i class="fas fa-skull-crossbones" aria-hidden="true"></i>
{{localize 'DG2.Apply.Lethality'}}
</button>
{{else}}
<button type="button" class="dg2-apply-button" data-action="dg2-apply-damage"
data-damage="{{nonLethal.total}}" data-ap="{{ap}}"
data-tooltip="{{localize 'DG2.Apply.DamageTooltip'}}">
<i class="fas fa-heart-crack" aria-hidden="true"></i>
{{localize 'DG2.Apply.Damage'}} ({{nonLethal.total}}{{#if ap}}, AP {{ap}}{{/if}})
</button>
{{/if}}
</div>
</div>
</div>
+10 -1
View File
@@ -1,6 +1,10 @@
<div class="dice-roll" data-action="expandRoll">
<div class="dice-result">
<div style="{{styleOverride}}" class="dice-formula">{{resultString}}</div>
<div class="dg2-outcome {{outcomeClass}}">
<i class="fas {{outcomeIcon}}" aria-hidden="true"></i>
<span class="dg2-outcome-text">{{resultString}}</span>
{{#if marginText}}<span class="dg2-margin">{{marginText}}</span>{{/if}}
</div>
<div class="dice-tooltip">
<div class="wrapper">
<section class="tooltip-part">
@@ -17,6 +21,11 @@
</div>
</div>
<h4 class="dice-total">{{total}}</h4>
{{#if ammoLine}}
<div class="dg2-ammo-line {{#if outOfAmmo}}dg2-out-of-ammo{{/if}}">
<i class="fas fa-crosshairs" aria-hidden="true"></i> {{ammoLine}}
</div>
{{/if}}
</div>
{{#if failureMark}}
+43
View File
@@ -0,0 +1,43 @@
<div class="dice-roll" data-action="expandRoll">
<div class="dice-result">
<div class="dice-formula">{{lowFormula}} / {{highFormula}}</div>
<div class="dice-tooltip">
<div class="wrapper">
<section class="tooltip-part">
<div class="dice">
<header class="part-header flexrow">
<span class="part-formula">{{lowFormula}}</span>
<span class="part-total">{{lowResult}}</span>
</header>
<ol class="dice-rolls">
<li class="roll die d{{lowFaces}}">{{lowResult}}</li>
</ol>
<hr>
<header class="part-header flexrow">
<span class="part-formula">{{highFormula}}</span>
<span class="part-total">{{highResult}}</span>
</header>
<ol class="dice-rolls">
<li class="roll die d{{highFaces}}">{{highResult}}</li>
</ol>
</div>
</section>
</div>
</div>
<h4 class="dice-total">{{lowResult}} / {{highResult}}</h4>
<div class="dg2-apply-row">
<button type="button" class="dg2-apply-button" data-action="dg2-apply-san"
data-san="{{lowResult}}"
data-tooltip="{{localize 'DG2.Apply.SanSuccessTooltip'}}">
<i class="fas fa-brain" aria-hidden="true"></i>
{{localize 'DG.Roll.Success'}}: &minus;{{lowResult}} SAN
</button>
<button type="button" class="dg2-apply-button dg2-san-fail" data-action="dg2-apply-san"
data-san="{{highResult}}"
data-tooltip="{{localize 'DG2.Apply.SanFailTooltip'}}">
<i class="fas fa-brain" aria-hidden="true"></i>
{{localize 'DG.Roll.Failure'}}: &minus;{{highResult}} SAN
</button>
</div>
</div>
</div>