Merge branch 'main' into feature/336-damage-targeted-resources

This commit is contained in:
Dapoolp 2025-07-15 17:10:28 +02:00
commit 31647d71ee
52 changed files with 701 additions and 290 deletions

View file

@ -5,7 +5,7 @@ import * as documents from './module/documents/_module.mjs';
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs'; import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
import { DhDualityRollEnricher, DhTemplateEnricher } from './module/enrichers/_module.mjs'; import { DhDualityRollEnricher, DhTemplateEnricher } from './module/enrichers/_module.mjs';
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs'; import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
import { NarrativeCountdowns, registerCountdownApplicationHooks } from './module/applications/ui/countdowns.mjs'; import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs';
import { DualityRollColor } from './module/data/settings/Appearance.mjs'; import { DualityRollColor } from './module/data/settings/Appearance.mjs';
import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/dice/_module.mjs'; import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/dice/_module.mjs';
import { renderDualityButton } from './module/enrichers/DualityRollEnricher.mjs'; import { renderDualityButton } from './module/enrichers/DualityRollEnricher.mjs';
@ -61,6 +61,14 @@ Hooks.once('init', () => {
CONFIG.Dice.rolls = [...CONFIG.Dice.rolls, ...[DHRoll, DualityRoll, D20Roll, DamageRoll]]; CONFIG.Dice.rolls = [...CONFIG.Dice.rolls, ...[DHRoll, DualityRoll, D20Roll, DamageRoll]];
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate; CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
const { DocumentSheetConfig } = foundry.applications.apps;
CONFIG.Token.documentClass = documents.DhToken;
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
DocumentSheetConfig.registerSheet(TokenDocument, 'dnd5e', applications.sheetConfigs.DhTokenConfig, {
makeDefault: true
});
CONFIG.Item.documentClass = documents.DHItem; CONFIG.Item.documentClass = documents.DHItem;
//Registering the Item DataModel //Registering the Item DataModel
@ -98,12 +106,12 @@ Hooks.once('init', () => {
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect; CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
CONFIG.ActiveEffect.dataModels = models.activeEffects.config; CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
foundry.applications.apps.DocumentSheetConfig.unregisterSheet( DocumentSheetConfig.unregisterSheet(
CONFIG.ActiveEffect.documentClass, CONFIG.ActiveEffect.documentClass,
'core', 'core',
foundry.applications.sheets.ActiveEffectConfig foundry.applications.sheets.ActiveEffectConfig
); );
foundry.applications.apps.DocumentSheetConfig.registerSheet( DocumentSheetConfig.registerSheet(
CONFIG.ActiveEffect.documentClass, CONFIG.ActiveEffect.documentClass,
SYSTEM.id, SYSTEM.id,
applications.sheetConfigs.ActiveEffectConfig, applications.sheetConfigs.ActiveEffectConfig,
@ -160,7 +168,6 @@ Hooks.on('ready', () => {
registerCountdownHooks(); registerCountdownHooks();
socketRegistration.registerSocketHooks(); socketRegistration.registerSocketHooks();
registerCountdownApplicationHooks();
registerRollDiceHooks(); registerRollDiceHooks();
registerDHActorHooks(); registerDHActorHooks();
}); });

View file

@ -1011,23 +1011,36 @@
"severe": "Severe", "severe": "Severe",
"major": "Major", "major": "Major",
"minor": "Minor", "minor": "Minor",
"none": "None" "none": "None",
"allDamage": "All Damage",
"physicalDamage": "Physical Damage",
"magicalDamage": "Magical Damage",
"primaryWeapon": "Primary Weapon Damage",
"secondaryWeapon": "Secondary Weapon Damage"
}, },
"DamageResistance": { "DamageResistance": {
"none": "None", "none": "None",
"resistance": "Resistance", "resistance": "Resistance",
"immunity": "Immunity" "immunity": "Immunity",
"physicalReduction": "Physical Damage Reduction",
"magicalReduction": "Magical Damage Reduction"
}, },
"DamageThresholds": { "DamageThresholds": {
"title": "Damage Thresholds", "title": "Damage Thresholds",
"minor": "Minor", "minor": "Minor",
"major": "Major", "major": "Major",
"severe": "Severe" "severe": "Severe",
"majorThreshold": "Major Damage Threshold",
"severeThreshold": "Severe Damage Threshold"
}, },
"Dice": { "Dice": {
"single": "Die", "single": "Die",
"plural": "Dice" "plural": "Dice"
}, },
"Difficulty": {
"all": "Difficulty: all",
"reaction": "Difficulty: reaction"
},
"Disadvantage": { "Disadvantage": {
"full": "Disadvantage", "full": "Disadvantage",
"short": "Dis" "short": "Dis"
@ -1037,39 +1050,39 @@
"plural": "Domains", "plural": "Domains",
"arcana": { "arcana": {
"label": "Arcana", "label": "Arcana",
"Description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled." "description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled."
}, },
"blade": { "blade": {
"label": "Blade", "label": "Blade",
"Description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death." "description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death."
}, },
"bone": { "bone": {
"label": "Bone", "label": "Bone",
"Description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training." "description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training."
}, },
"codex": { "codex": {
"label": "Codex", "label": "Codex",
"Description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge." "description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge."
}, },
"grace": { "grace": {
"label": "Grace", "label": "Grace",
"Description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language." "description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language."
}, },
"midnight": { "midnight": {
"label": "Midnight", "label": "Midnight",
"Description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas." "description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas."
}, },
"sage": { "sage": {
"label": "Sage", "label": "Sage",
"Description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator." "description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator."
}, },
"splendor": { "splendor": {
"label": "Splendor", "label": "Splendor",
"Description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life." "description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life."
}, },
"valor": { "valor": {
"label": "Valor", "label": "Valor",
"Description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others." "description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others."
} }
}, },
"Effect": { "Effect": {
@ -1080,10 +1093,18 @@
"single": "Experience", "single": "Experience",
"plural": "Experiences" "plural": "Experiences"
}, },
"Healing": {
"healingAmount": "Healing Amount"
},
"Neutral": { "Neutral": {
"full": "None", "full": "None",
"short": "no" "short": "no"
}, },
"Range": {
"other": "Range Increase: Other",
"spell": "Range Increase: Spell",
"weapon": "Range Increase: Weapon"
},
"RefreshType": { "RefreshType": {
"session": "Session", "session": "Session",
"shortrest": "Short Rest", "shortrest": "Short Rest",
@ -1093,6 +1114,42 @@
"single": "Resource", "single": "Resource",
"plural": "Resources" "plural": "Resources"
}, },
"Roll": {
"attack": "Attack Roll",
"primaryWeaponAttack": "Primary Weapon Attack Roll",
"secondaryWeaponAttack": "Secondary Weapon Attack Roll",
"spellcast": "Spellcast Roll",
"trait": "Trait Roll",
"action": "Action Roll",
"reaction": "Reaction Roll"
},
"Rules": {
"damageReduction": {
"increasePerArmorMark": {
"label": "Damage Reduction per Armor Slot",
"hint": "A used armor slot normally reduces damage by one step. This value increases the number of steps damage is reduced by."
},
"maxArmorMarkedBonus": "Max Armor Used",
"maxArmorMarkedStress": {
"label": "Max Armor Used With Stress",
"hint": "If this value is set you can use up to that much stress to spend additional Armor Marks beyond your normal maximum."
},
"stress": {
"severe": {
"label": "Stress Damage Reduction: Severe",
"hint": "The cost in stress you can pay to reduce severe damage down to major."
},
"major": {
"label": "Stress Damage Reduction: Major",
"hint": "The cost in stress you can pay to reduce major damage down to minor."
},
"minor": {
"label": "Stress Damage Reduction: Minor",
"hint": "The cost in stress you can pay to reduce minor damage to none."
}
}
}
},
"Tabs": { "Tabs": {
"details": "Details", "details": "Details",
"attack": "Attack", "attack": "Attack",
@ -1137,6 +1194,7 @@
"single": "Trait", "single": "Trait",
"plural": "Traits" "plural": "Traits"
}, },
"armorScore": "Armor Score",
"activeEffects": "Active Effects", "activeEffects": "Active Effects",
"armorSlots": "Armor Slots", "armorSlots": "Armor Slots",
"attack": "Attack", "attack": "Attack",
@ -1152,10 +1210,15 @@
"dualityRoll": "Duality Roll", "dualityRoll": "Duality Roll",
"enabled": "Enabled", "enabled": "Enabled",
"evasion": "Evasion", "evasion": "Evasion",
"experience": {
"single": "Experience",
"plural": "Experiences"
},
"fear": "Fear", "fear": "Fear",
"features": "Features", "features": "Features",
"hitPoints": "Hit Points", "hitPoints": "Hit Points",
"hope": "Hope", "hope": "Hope",
"hordeHp": "Horde HP",
"inactiveEffects": "Inactive Effects", "inactiveEffects": "Inactive Effects",
"inventory": "Inventory", "inventory": "Inventory",
"level": "Level", "level": "Level",
@ -1163,9 +1226,12 @@
"modifier": "Modifier", "modifier": "Modifier",
"multiclass": "Multiclass", "multiclass": "Multiclass",
"none": "None", "none": "None",
"partner": "Partner",
"proficiency": "Proficiency",
"quantity": "Quantity", "quantity": "Quantity",
"range": "Range", "range": "Range",
"recovery": "Recovery", "recovery": "Recovery",
"roll": "Roll",
"scalable": "Scalable", "scalable": "Scalable",
"stress": "Stress", "stress": "Stress",
"take": "Take", "take": "Take",
@ -1272,9 +1338,10 @@
"hint": "Automatically increase the GM's fear pool on a fear duality roll result." "hint": "Automatically increase the GM's fear pool on a fear duality roll result."
}, },
"FIELDS": { "FIELDS": {
"hope": { "hopeFear": {
"label": "Hope", "label": "Hope & Fear",
"hint": "Automatically increase a character's hope on a hope duality roll result." "gm": { "label": "GM" },
"players": { "label": "Players" }
}, },
"actionPoints": { "actionPoints": {
"label": "Action Points", "label": "Action Points",

View file

@ -64,6 +64,13 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.rollConfig = this.config; context.rollConfig = this.config;
context.hasRoll = !!this.config.roll; context.hasRoll = !!this.config.roll;
context.canRoll = true; context.canRoll = true;
context.selectedRollMode = this.config.selectedRollMode;
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
action,
label,
icon
}));
if (this.config.costs?.length) { if (this.config.costs?.length) {
const updatedCosts = this.action.calcCosts(this.config.costs); const updatedCosts = this.action.calcCosts(this.config.costs);
context.costs = updatedCosts.map(x => ({ context.costs = updatedCosts.map(x => ({
@ -99,6 +106,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
static updateRollConfiguration(event, _, formData) { static updateRollConfiguration(event, _, formData) {
const { ...rest } = foundry.utils.expandObject(formData.object); const { ...rest } = foundry.utils.expandObject(formData.object);
this.config.selectedRollMode = rest.selectedRollMode;
if (this.config.costs) { if (this.config.costs) {
this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs); this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs);
} }
@ -122,11 +131,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
} }
static selectExperience(_, button) { static selectExperience(_, button) {
/* if (this.config.experiences.find(x => x === button.dataset.key)) {
this.config.experiences = this.config.experiences.filter(x => x !== button.dataset.key);
} else {
this.config.experiences = [...this.config.experiences, button.dataset.key];
} */
this.config.experiences = this.config.experiences =
this.config.experiences.indexOf(button.dataset.key) > -1 this.config.experiences.indexOf(button.dataset.key) > -1
? this.config.experiences.filter(x => x !== button.dataset.key) ? this.config.experiences.filter(x => x !== button.dataset.key)

View file

@ -48,12 +48,22 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
: game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name'); : game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name');
context.extraFormula = this.config.extraFormula; context.extraFormula = this.config.extraFormula;
context.formula = this.roll.constructFormula(this.config); context.formula = this.roll.constructFormula(this.config);
context.directDamage = this.config.directDamage;
context.selectedRollMode = this.config.selectedRollMode;
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
action,
label,
icon
}));
return context; return context;
} }
static updateRollConfiguration(event, _, formData) { static updateRollConfiguration(_event, _, formData) {
const { ...rest } = foundry.utils.expandObject(formData.object); const { ...rest } = foundry.utils.expandObject(formData.object);
this.config.extraFormula = rest.extraFormula; this.config.extraFormula = rest.extraFormula;
this.config.selectedRollMode = rest.selectedRollMode;
this.render(); this.render();
} }

View file

@ -1 +1 @@
export { default as DHTokenHUD } from './tokenHud.mjs'; export { default as DHTokenHUD } from './tokenHUD.mjs';

View file

@ -1,4 +1,4 @@
export default class DHTokenHUD extends TokenHUD { export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['daggerheart'] classes: ['daggerheart']
}; };

View file

@ -3,3 +3,5 @@ export { default as AdversarySettings } from './adversary-settings.mjs';
export { default as CompanionSettings } from './companion-settings.mjs'; export { default as CompanionSettings } from './companion-settings.mjs';
export { default as EnvironmentSettings } from './environment-settings.mjs'; export { default as EnvironmentSettings } from './environment-settings.mjs';
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs'; export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
export { default as DhTokenConfig } from './token-config.mjs';
export { default as DhPrototypeTokenConfig } from './prototype-token-config.mjs';

View file

@ -1,4 +1,24 @@
import autocomplete from 'autocompleter';
export default class DhActiveEffectConfig extends foundry.applications.sheets.ActiveEffectConfig { export default class DhActiveEffectConfig extends foundry.applications.sheets.ActiveEffectConfig {
constructor(options) {
super(options);
const ignoredActorKeys = ['config', 'DhEnvironment'];
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => {
if (!ignoredActorKeys.includes(key)) {
const model = game.system.api.models.actors[key];
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
const group = game.i18n.localize(model.metadata.label);
const choices = CONFIG.Token.documentClass
.getTrackedAttributeChoices(attributes, model)
.map(x => ({ ...x, group: group }));
acc.push(...choices);
}
return acc;
}, []);
}
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'sheet', 'dh-style'] classes: ['daggerheart', 'sheet', 'dh-style']
}; };
@ -27,36 +47,59 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
} }
}; };
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
const changeChoices = this.changeChoices;
htmlElement.querySelectorAll('.effect-change-input').forEach(element => {
autocomplete({
input: element,
fetch: function (text, update) {
if (!text) {
update(changeChoices);
} else {
text = text.toLowerCase();
var suggestions = changeChoices.filter(n => n.label.toLowerCase().includes(text));
update(suggestions);
}
},
render: function (item, search) {
const label = game.i18n.localize(item.label);
const matchIndex = label.toLowerCase().indexOf(search);
const beforeText = label.slice(0, matchIndex);
const matchText = label.slice(matchIndex, matchIndex + search.length);
const after = label.slice(matchIndex + search.length, label.length);
const element = document.createElement('li');
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
if (item.hint) {
element.dataset.tooltip = game.i18n.localize(item.hint);
}
return element;
},
renderGroup: function (label) {
const itemElement = document.createElement('div');
itemElement.textContent = game.i18n.localize(label);
return itemElement;
},
onSelect: function (item) {
element.value = `system.${item.value}`;
},
click: e => e.fetch(),
minLength: 0
});
});
}
async _preparePartContext(partId, context) { async _preparePartContext(partId, context) {
const partContext = await super._preparePartContext(partId, context); const partContext = await super._preparePartContext(partId, context);
switch (partId) { switch (partId) {
case 'changes': case 'changes':
const fieldPaths = [];
const validFieldPath = fieldPath => this.validFieldPath(fieldPath, this.#unapplicablePaths);
context.document.parent.system.schema.apply(function () {
if (!(this instanceof foundry.data.fields.SchemaField)) {
if (validFieldPath(this.fieldPath)) {
fieldPaths.push(this.fieldPath);
}
}
});
context.fieldPaths = fieldPaths;
break; break;
} }
return partContext; return partContext;
} }
#unapplicablePaths = ['story', 'pronouns', 'description'];
validFieldPath(fieldPath, unapplicablePaths) {
const splitPath = fieldPath.split('.');
if (splitPath.length > 1 && unapplicablePaths.includes(splitPath[1])) return false;
/* The current value of a resource should not be modified */
if (new RegExp(/resources.*\.value/).exec(fieldPath)) return false;
return true;
}
} }

View file

@ -0,0 +1,20 @@
export default class DhPrototypeTokenConfig extends foundry.applications.sheets.PrototypeTokenConfig {
/** @inheritDoc */
async _prepareResourcesTab() {
const token = this.token;
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
const attributeSource =
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
? this.actor?.type
: this.actor?.system;
const TokenDocument = foundry.utils.getDocumentClass('Token');
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
return {
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
bar1: token.getBarAttribute?.('bar1'),
bar2: token.getBarAttribute?.('bar2'),
turnMarkerModes: DhPrototypeTokenConfig.TURN_MARKER_MODES,
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
};
}
}

View file

@ -0,0 +1,20 @@
export default class DhTokenConfig extends foundry.applications.sheets.TokenConfig {
/** @inheritDoc */
async _prepareResourcesTab() {
const token = this.token;
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
const attributeSource =
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
? this.actor?.type
: this.actor?.system;
const TokenDocument = foundry.utils.getDocumentClass('Token');
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
return {
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
bar1: token.getBarAttribute?.('bar1'),
bar2: token.getBarAttribute?.('bar2'),
turnMarkerModes: DhTokenConfig.TURN_MARKER_MODES,
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
};
}
}

View file

@ -103,7 +103,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => { htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
element.addEventListener('change', this.updateItemQuantity.bind(this)); element.addEventListener('change', this.updateItemQuantity.bind(this));
}); });
// Add listener for armor marks input // Add listener for armor marks input
htmlElement.querySelectorAll('.armor-marks-input').forEach(element => { htmlElement.querySelectorAll('.armor-marks-input').forEach(element => {
element.addEventListener('change', this.updateArmorMarks.bind(this)); element.addEventListener('change', this.updateArmorMarks.bind(this));
@ -669,10 +669,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
} else if (item instanceof ActiveEffect) { } else if (item instanceof ActiveEffect) {
item.toChat(this); item.toChat(this);
} else { } else {
const wasUsed = await item.use(event); item.use(event);
if (wasUsed && item.type === 'weapon') {
Hooks.callAll(CONFIG.DH.HOOKS.characterAttack, {});
}
} }
} }

View file

@ -88,7 +88,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
onRollDamage = async (event, message) => { onRollDamage = async (event, message) => {
event.stopPropagation(); event.stopPropagation();
const actor = await this.getActor(message.system.source.actor); const actor = await this.getActor(message.system.source.actor);
if (!actor || !game.user.isGM) return true; if (game.user.character?.id !== actor.id && !game.user.isGM) return true;
if (message.system.source.item && message.system.source.action) { if (message.system.source.item && message.system.source.action) {
const action = this.getAction(actor, message.system.source.item, message.system.source.action); const action = this.getAction(actor, message.system.source.item, message.system.source.action);
if (!action || !action?.rollDamage) return; if (!action || !action?.rollDamage) return;

View file

@ -66,6 +66,11 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
} }
async setCombatantSpotlight(combatantId) { async setCombatantSpotlight(combatantId) {
const update = {
system: {
'spotlight.requesting': false
}
};
const combatant = this.viewed.combatants.get(combatantId); const combatant = this.viewed.combatants.get(combatantId);
const toggleTurn = this.viewed.combatants.contents const toggleTurn = this.viewed.combatants.contents
@ -73,10 +78,18 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
.map(x => x.id) .map(x => x.id)
.indexOf(combatantId); .indexOf(combatantId);
if (this.viewed.turn !== toggleTurn) Hooks.callAll(CONFIG.DH.HOOKS.spotlight, {}); if (this.viewed.turn !== toggleTurn) {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.spotlight.id);
const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints;
if (autoPoints) {
update.system.actionTokens = Math.max(combatant.system.actionTokens - 1, 0);
}
}
await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn }); await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn });
await combatant.update({ 'system.spotlight.requesting': false }); await combatant.update(update);
} }
static async requestSpotlight(_, target) { static async requestSpotlight(_, target) {

View file

@ -1,4 +1,3 @@
import { countdownTypes } from '../../config/generalConfig.mjs';
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import constructHTMLButton from '../../helpers/utils.mjs'; import constructHTMLButton from '../../helpers/utils.mjs';
import OwnershipSelection from '../dialogs/ownershipSelection.mjs'; import OwnershipSelection from '../dialogs/ownershipSelection.mjs';
@ -328,43 +327,29 @@ export class EncounterCountdowns extends Countdowns {
}; };
} }
export const registerCountdownApplicationHooks = () => { export async function updateCountdowns(progressType) {
const updateCountdowns = async shouldProgress => { const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
if (game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).countdowns) { const update = Object.keys(countdownSetting).reduce((update, typeKey) => {
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); return foundry.utils.mergeObject(
for (let countdownCategoryKey in countdownSetting) { update,
const countdownCategory = countdownSetting[countdownCategoryKey]; Object.keys(countdownSetting[typeKey].countdowns).reduce((acc, countdownKey) => {
for (let countdownKey in countdownCategory.countdowns) { const countdown = countdownSetting[typeKey].countdowns[countdownKey];
const countdown = countdownCategory.countdowns[countdownKey]; if (countdown.progress.current > 0 && countdown.progress.type.value === progressType) {
acc[`${typeKey}.countdowns.${countdownKey}.progress.current`] = countdown.progress.current - 1;
if (shouldProgress(countdown)) {
await countdownSetting.updateSource({
[`${countdownCategoryKey}.countdowns.${countdownKey}.progress.current`]:
countdown.progress.current - 1
});
await game.settings.set(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Countdowns,
countdownSetting
);
foundry.applications.instances.get(`${countdownCategoryKey}-countdowns`)?.render();
}
} }
}
}
};
Hooks.on(CONFIG.DH.HOOKS.characterAttack, async () => { return acc;
updateCountdowns(countdown => { }, {})
return ( );
countdown.progress.type.value === countdownTypes.characterAttack.id && countdown.progress.current > 0 }, {});
);
});
});
Hooks.on(CONFIG.DH.HOOKS.spotlight, async () => { await countdownSetting.updateSource(update);
updateCountdowns(countdown => { await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSetting);
return countdown.progress.type.value === countdownTypes.spotlight.id && countdown.progress.current > 0;
}); const data = { refreshType: RefreshType.Countdown };
await game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data
}); });
}; Hooks.callAll(socketEvent.Refresh, data);
}

View file

@ -4,7 +4,6 @@ export * as domainConfig from './domainConfig.mjs';
export * as effectConfig from './effectConfig.mjs'; export * as effectConfig from './effectConfig.mjs';
export * as flagsConfig from './flagsConfig.mjs'; export * as flagsConfig from './flagsConfig.mjs';
export * as generalConfig from './generalConfig.mjs'; export * as generalConfig from './generalConfig.mjs';
export * as hooksConfig from './hooksConfig.mjs';
export * as itemConfig from './itemConfig.mjs'; export * as itemConfig from './itemConfig.mjs';
export * as settingsConfig from './settingsConfig.mjs'; export * as settingsConfig from './settingsConfig.mjs';
export * as systemConfig from './system.mjs'; export * as systemConfig from './system.mjs';

View file

@ -3,55 +3,55 @@ export const domains = {
id: 'arcana', id: 'arcana',
label: 'DAGGERHEART.GENERAL.Domain.arcana.label', label: 'DAGGERHEART.GENERAL.Domain.arcana.label',
src: 'systems/daggerheart/assets/icons/domains/arcana.svg', src: 'systems/daggerheart/assets/icons/domains/arcana.svg',
description: 'DAGGERHEART.GENERAL.Domain.Arcana' description: 'DAGGERHEART.GENERAL.Domain.arcana.description'
}, },
blade: { blade: {
id: 'blade', id: 'blade',
label: 'DAGGERHEART.GENERAL.Domain.blade.label', label: 'DAGGERHEART.GENERAL.Domain.blade.label',
src: 'systems/daggerheart/assets/icons/domains/blade.svg', src: 'systems/daggerheart/assets/icons/domains/blade.svg',
description: 'DAGGERHEART.GENERAL.Domain.Blade' description: 'DAGGERHEART.GENERAL.Domain.blade.description'
}, },
bone: { bone: {
id: 'bone', id: 'bone',
label: 'DAGGERHEART.GENERAL.Domain.bone.label', label: 'DAGGERHEART.GENERAL.Domain.bone.label',
src: 'systems/daggerheart/assets/icons/domains/bone.svg', src: 'systems/daggerheart/assets/icons/domains/bone.svg',
description: 'DAGGERHEART.GENERAL.Domain.Bone' description: 'DAGGERHEART.GENERAL.Domain.bone.description'
}, },
codex: { codex: {
id: 'codex', id: 'codex',
label: 'DAGGERHEART.GENERAL.Domain.codex.label', label: 'DAGGERHEART.GENERAL.Domain.codex.label',
src: 'systems/daggerheart/assets/icons/domains/codex.svg', src: 'systems/daggerheart/assets/icons/domains/codex.svg',
description: 'DAGGERHEART.GENERAL.Domain.Codex' description: 'DAGGERHEART.GENERAL.Domain.codex.description'
}, },
grace: { grace: {
id: 'grace', id: 'grace',
label: 'DAGGERHEART.GENERAL.Domain.grace.label', label: 'DAGGERHEART.GENERAL.Domain.grace.label',
src: 'systems/daggerheart/assets/icons/domains/grace.svg', src: 'systems/daggerheart/assets/icons/domains/grace.svg',
description: 'DAGGERHEART.GENERAL.Domain.Grace' description: 'DAGGERHEART.GENERAL.Domain.grace.description'
}, },
midnight: { midnight: {
id: 'midnight', id: 'midnight',
label: 'DAGGERHEART.GENERAL.Domain.midnight.label', label: 'DAGGERHEART.GENERAL.Domain.midnight.label',
src: 'systems/daggerheart/assets/icons/domains/midnight.svg', src: 'systems/daggerheart/assets/icons/domains/midnight.svg',
description: 'DAGGERHEART.GENERAL.Domain.Midnight' description: 'DAGGERHEART.GENERAL.Domain.midnight.description'
}, },
sage: { sage: {
id: 'sage', id: 'sage',
label: 'DAGGERHEART.GENERAL.Domain.sage.label', label: 'DAGGERHEART.GENERAL.Domain.sage.label',
src: 'systems/daggerheart/assets/icons/domains/sage.svg', src: 'systems/daggerheart/assets/icons/domains/sage.svg',
description: 'DAGGERHEART.GENERAL.Domain.Sage' description: 'DAGGERHEART.GENERAL.Domain.sage.description'
}, },
splendor: { splendor: {
id: 'splendor', id: 'splendor',
label: 'DAGGERHEART.GENERAL.Domain.splendor.label', label: 'DAGGERHEART.GENERAL.Domain.splendor.label',
src: 'systems/daggerheart/assets/icons/domains/splendor.svg', src: 'systems/daggerheart/assets/icons/domains/splendor.svg',
description: 'DAGGERHEART.GENERAL.Domain.Splendor' description: 'DAGGERHEART.GENERAL.Domain.splendor.description'
}, },
valor: { valor: {
id: 'valor', id: 'valor',
label: 'DAGGERHEART.GENERAL.Domain.valor.label', label: 'DAGGERHEART.GENERAL.Domain.valor.label',
src: 'systems/daggerheart/assets/icons/domains/valor.svg', src: 'systems/daggerheart/assets/icons/domains/valor.svg',
description: 'DAGGERHEART.GENERAL.Domain.Valor' description: 'DAGGERHEART.GENERAL.Domain.valor.description'
} }
}; };

View file

@ -376,15 +376,15 @@ export const abilityCosts = {
export const countdownTypes = { export const countdownTypes = {
spotlight: { spotlight: {
id: 'spotlight', id: 'spotlight',
label: 'DAGGERHEART.CONFIG.CountdownTypes.Spotlight' label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
}, },
characterAttack: { characterAttack: {
id: 'characterAttack', id: 'characterAttack',
label: 'DAGGERHEART.CONFIG.CountdownTypes.CharacterAttack' label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack'
}, },
custom: { custom: {
id: 'custom', id: 'custom',
label: 'DAGGERHEART.CONFIG.CountdownTypes.Custom' label: 'DAGGERHEART.CONFIG.CountdownType.custom'
} }
}; };
export const rollTypes = { export const rollTypes = {

View file

@ -1,4 +0,0 @@
export const hooks = {
characterAttack: 'characterAttackHook',
spotlight: 'spotlightHook'
};

View file

@ -3,7 +3,6 @@ import * as DOMAIN from './domainConfig.mjs';
import * as ACTOR from './actorConfig.mjs'; import * as ACTOR from './actorConfig.mjs';
import * as ITEM from './itemConfig.mjs'; import * as ITEM from './itemConfig.mjs';
import * as SETTINGS from './settingsConfig.mjs'; import * as SETTINGS from './settingsConfig.mjs';
import { hooks as HOOKS } from './hooksConfig.mjs';
import * as EFFECTS from './effectConfig.mjs'; import * as EFFECTS from './effectConfig.mjs';
import * as ACTIONS from './actionConfig.mjs'; import * as ACTIONS from './actionConfig.mjs';
import * as FLAGS from './flagsConfig.mjs'; import * as FLAGS from './flagsConfig.mjs';
@ -17,7 +16,6 @@ export const SYSTEM = {
ACTOR, ACTOR,
ITEM, ITEM,
SETTINGS, SETTINGS,
HOOKS,
EFFECTS, EFFECTS,
ACTIONS, ACTIONS,
FLAGS FLAGS

View file

@ -38,6 +38,15 @@ export default class DHAttackAction extends DHDamageAction {
}; };
} }
async use(event, ...args) {
const result = await super.use(event, args);
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id);
return result;
}
// get modifiers() { // get modifiers() {
// return []; // return [];
// } // }

View file

@ -254,7 +254,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
hasDamage: !!this.damage?.parts?.length, hasDamage: !!this.damage?.parts?.length,
hasHealing: !!this.healing, hasHealing: !!this.healing,
hasEffect: !!this.effects?.length, hasEffect: !!this.effects?.length,
hasSave: this.hasSave hasSave: this.hasSave,
selectedRollMode: game.settings.get('core', 'rollMode')
}; };
} }
@ -350,7 +351,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
} }
get modifiers() { get modifiers() {
if(!this.actor) return []; if (!this.actor) return [];
const modifiers = []; const modifiers = [];
/** Placeholder for specific bonuses **/ /** Placeholder for specific bonuses **/
return modifiers; return modifiers;

View file

@ -10,6 +10,7 @@ export default class DHDamageAction extends DHBaseAction {
} }
async rollDamage(event, data) { async rollDamage(event, data) {
const systemData = data.system ?? data;
let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '), let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '),
damageTypes = [...new Set(this.damage.parts.reduce((a, c) => a.concat([...c.type]), []))]; damageTypes = [...new Set(this.damage.parts.reduce((a, c) => a.concat([...c.type]), []))];
@ -19,15 +20,15 @@ export default class DHDamageAction extends DHBaseAction {
let roll = { formula: formula, total: formula }, let roll = { formula: formula, total: formula },
bonusDamage = []; bonusDamage = [];
if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data)); if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(systemData));
const config = { const config = {
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }), title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }),
roll: { formula }, roll: { formula },
targets: data.system?.targets.filter(t => t.hit) ?? data.targets, targets: systemData.targets.filter(t => t.hit) ?? data.targets,
hasSave: this.hasSave, hasSave: this.hasSave,
isCritical: data.system?.roll?.isCritical ?? false, isCritical: systemData.roll?.isCritical ?? false,
source: data.system?.source, source: systemData.source,
data: this.getRollData(), data: this.getRollData(),
damageTypes, damageTypes,
event event
@ -36,6 +37,8 @@ export default class DHDamageAction extends DHBaseAction {
if (data.system) { if (data.system) {
config.source.message = data._id; config.source.message = data._id;
config.directDamage = false; config.directDamage = false;
} else {
config.directDamage = true;
} }
roll = CONFIG.Dice.daggerheart.DamageRoll.build(config); roll = CONFIG.Dice.daggerheart.DamageRoll.build(config);

View file

@ -31,14 +31,29 @@ export default class DhpAdversary extends BaseDataActor {
motivesAndTactics: new fields.StringField(), motivesAndTactics: new fields.StringField(),
notes: new fields.HTMLField(), notes: new fields.HTMLField(),
difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }), difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }),
hordeHp: new fields.NumberField({ required: true, initial: 1, integer: true }), hordeHp: new fields.NumberField({
required: true,
initial: 1,
integer: true,
label: 'DAGGERHEART.GENERAL.hordeHp'
}),
damageThresholds: new fields.SchemaField({ damageThresholds: new fields.SchemaField({
major: new fields.NumberField({ required: true, initial: 0, integer: true }), major: new fields.NumberField({
severe: new fields.NumberField({ required: true, initial: 0, integer: true }) required: true,
initial: 0,
integer: true,
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
}),
severe: new fields.NumberField({
required: true,
initial: 0,
integer: true,
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
})
}), }),
resources: new fields.SchemaField({ resources: new fields.SchemaField({
hitPoints: resourceField(0, true), hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints', true),
stress: resourceField(0, true) stress: resourceField(0, 'DAGGERHEART.GENERAL.stress', true)
}), }),
attack: new ActionField({ attack: new ActionField({
initial: { initial: {
@ -75,13 +90,13 @@ export default class DhpAdversary extends BaseDataActor {
), ),
bonuses: new fields.SchemaField({ bonuses: new fields.SchemaField({
roll: new fields.SchemaField({ roll: new fields.SchemaField({
attack: bonusField(), attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
action: bonusField(), action: bonusField('DAGGERHEART.GENERAL.Roll.action'),
reaction: bonusField() reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction')
}), }),
damage: new fields.SchemaField({ damage: new fields.SchemaField({
physical: bonusField(), physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
magical: bonusField() magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage')
}) })
}) })
}; };

View file

@ -1,10 +1,10 @@
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs'; import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
const resistanceField = () => const resistanceField = reductionLabel =>
new foundry.data.fields.SchemaField({ new foundry.data.fields.SchemaField({
resistance: new foundry.data.fields.BooleanField({ initial: false }), resistance: new foundry.data.fields.BooleanField({ initial: false }),
immunity: new foundry.data.fields.BooleanField({ initial: false }), immunity: new foundry.data.fields.BooleanField({ initial: false }),
reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0 }) reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0, label: reductionLabel })
}); });
/** /**
@ -40,8 +40,8 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
if (this.metadata.isNPC) schema.description = new fields.HTMLField({ required: true, nullable: true }); if (this.metadata.isNPC) schema.description = new fields.HTMLField({ required: true, nullable: true });
if (this.metadata.hasResistances) if (this.metadata.hasResistances)
schema.resistance = new fields.SchemaField({ schema.resistance = new fields.SchemaField({
physical: resistanceField(), physical: resistanceField('DAGGERHEART.GENERAL.DamageResistance.physicalReduction'),
magical: resistanceField() magical: resistanceField('DAGGERHEART.GENERAL.DamageResistance.magicalReduction')
}); });
return schema; return schema;
} }

View file

@ -5,6 +5,8 @@ import BaseDataActor from './base.mjs';
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
export default class DhCharacter extends BaseDataActor { export default class DhCharacter extends BaseDataActor {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
static get metadata() { static get metadata() {
return foundry.utils.mergeObject(super.metadata, { return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.character', label: 'TYPES.Actor.character',
@ -19,24 +21,36 @@ export default class DhCharacter extends BaseDataActor {
return { return {
...super.defineSchema(), ...super.defineSchema(),
resources: new fields.SchemaField({ resources: new fields.SchemaField({
hitPoints: resourceField(0, true), hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints', true),
stress: resourceField(6, true), stress: resourceField(6, 'DAGGERHEART.GENERAL.stress', true),
hope: resourceField(6) hope: resourceField(6, 'DAGGERHEART.GENERAL.hope')
}), }),
traits: new fields.SchemaField({ traits: new fields.SchemaField({
agility: attributeField(), agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
strength: attributeField(), strength: attributeField('DAGGERHEART.CONFIG.Traits.strength.name'),
finesse: attributeField(), finesse: attributeField('DAGGERHEART.CONFIG.Traits.finesse.name'),
instinct: attributeField(), instinct: attributeField('DAGGERHEART.CONFIG.Traits.instinct.name'),
presence: attributeField(), presence: attributeField('DAGGERHEART.CONFIG.Traits.presence.name'),
knowledge: attributeField() knowledge: attributeField('DAGGERHEART.CONFIG.Traits.knowledge.name')
}), }),
proficiency: new fields.NumberField({ initial: 1, integer: true }), proficiency: new fields.NumberField({
evasion: new fields.NumberField({ initial: 0, integer: true }), initial: 1,
armorScore: new fields.NumberField({ integer: true, initial: 0 }), integer: true,
label: 'DAGGERHEART.GENERAL.proficiency'
}),
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
armorScore: new fields.NumberField({ integer: true, initial: 0, label: 'DAGGERHEART.GENERAL.armorScore' }),
damageThresholds: new fields.SchemaField({ damageThresholds: new fields.SchemaField({
severe: new fields.NumberField({ integer: true, initial: 0 }), severe: new fields.NumberField({
major: new fields.NumberField({ integer: true, initial: 0 }) integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
}),
major: new fields.NumberField({
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
})
}), }),
experiences: new fields.TypedObjectField( experiences: new fields.TypedObjectField(
new fields.SchemaField({ new fields.SchemaField({
@ -76,25 +90,37 @@ export default class DhCharacter extends BaseDataActor {
levelData: new fields.EmbeddedDataField(DhLevelData), levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({ bonuses: new fields.SchemaField({
roll: new fields.SchemaField({ roll: new fields.SchemaField({
attack: bonusField(), attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
spellcast: bonusField(), spellcast: bonusField('DAGGERHEART.GENERAL.Roll.spellcast'),
trait: bonusField(), trait: bonusField('DAGGERHEART.GENERAL.Roll.trait'),
action: bonusField(), action: bonusField('DAGGERHEART.GENERAL.Roll.action'),
reaction: bonusField(), reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction'),
primaryWeapon: bonusField(), primaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.primaryWeaponAttack'),
secondaryWeapon: bonusField() secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.secondaryWeaponAttack')
}), }),
damage: new fields.SchemaField({ damage: new fields.SchemaField({
physical: bonusField(), physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
magical: bonusField(), magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage'),
primaryWeapon: bonusField(), primaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon'),
secondaryWeapon: bonusField() secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon')
}), }),
healing: bonusField(), healing: bonusField('DAGGERHEART.GENERAL.Healing.healingAmount'),
range: new fields.SchemaField({ range: new fields.SchemaField({
weapon: new fields.NumberField({ integer: true, initial: 0 }), weapon: new fields.NumberField({
spell: new fields.NumberField({ integer: true, initial: 0 }), integer: true,
other: new fields.NumberField({ integer: true, initial: 0 }) initial: 0,
label: 'DAGGERHEART.GENERAL.Range.weapon'
}),
spell: new fields.NumberField({
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Range.spell'
}),
other: new fields.NumberField({
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Range.other'
})
}) })
}), }),
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
@ -102,25 +128,34 @@ export default class DhCharacter extends BaseDataActor {
damageReduction: new fields.SchemaField({ damageReduction: new fields.SchemaField({
maxArmorMarked: new fields.SchemaField({ maxArmorMarked: new fields.SchemaField({
value: new fields.NumberField({ required: true, integer: true, initial: 1 }), value: new fields.NumberField({ required: true, integer: true, initial: 1 }),
bonus: new fields.NumberField({ required: true, integer: true, initial: 0 }), bonus: new fields.NumberField({
stressExtra: new fields.NumberField({ required: true, integer: true, initial: 0 }) required: true,
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
}),
stressExtra: new fields.NumberField({
required: true,
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
})
}), }),
stressDamageReduction: new fields.SchemaField({ stressDamageReduction: new fields.SchemaField({
severe: stressDamageReductionRule(), severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
major: stressDamageReductionRule(), major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
minor: stressDamageReductionRule() minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor')
}),
increasePerArmorMark: new fields.NumberField({
integer: true,
initial: 1,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
}), }),
increasePerArmorMark: new fields.NumberField({ integer: true, initial: 1 }),
magical: new fields.BooleanField({ initial: false }), magical: new fields.BooleanField({ initial: false }),
physical: new fields.BooleanField({ initial: false }) physical: new fields.BooleanField({ initial: false })
}), }),
strangePatterns: new fields.NumberField({
integer: true,
min: 1,
max: 12,
nullable: true,
initial: null
}),
weapon: new fields.SchemaField({ weapon: new fields.SchemaField({
/* Unimplemented /* Unimplemented
-> Should remove the lowest damage dice from weapon damage -> Should remove the lowest damage dice from weapon damage

View file

@ -24,10 +24,16 @@ export default class DhCompanion extends BaseDataActor {
...super.defineSchema(), ...super.defineSchema(),
partner: new ForeignDocumentUUIDField({ type: 'Actor' }), partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
resources: new fields.SchemaField({ resources: new fields.SchemaField({
stress: resourceField(3, true), stress: resourceField(3, 'DAGGERHEART.GENERAL.stress', true),
hope: new fields.NumberField({ initial: 0, integer: true }) hope: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.hope' })
}),
evasion: new fields.NumberField({
required: true,
min: 1,
initial: 10,
integer: true,
label: 'DAGGERHEART.GENERAL.evasion'
}), }),
evasion: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
experiences: new fields.TypedObjectField( experiences: new fields.TypedObjectField(
new fields.SchemaField({ new fields.SchemaField({
name: new fields.StringField({}), name: new fields.StringField({}),
@ -74,8 +80,8 @@ export default class DhCompanion extends BaseDataActor {
levelData: new fields.EmbeddedDataField(DhLevelData), levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({ bonuses: new fields.SchemaField({
damage: new fields.SchemaField({ damage: new fields.SchemaField({
physical: bonusField(), physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
magical: bonusField() magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage')
}) })
}) })
}; };

View file

@ -102,7 +102,7 @@ class DhCountdown extends foundry.abstract.DataModel {
value: new fields.StringField({ value: new fields.StringField({
required: true, required: true,
choices: CONFIG.DH.GENERAL.countdownTypes, choices: CONFIG.DH.GENERAL.countdownTypes,
initial: CONFIG.DH.GENERAL.countdownTypes.spotlight.id, initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.type.value.label' label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.type.value.label'
}), }),
label: new fields.StringField({ label: new fields.StringField({
@ -132,7 +132,13 @@ class DhCountdown extends foundry.abstract.DataModel {
export const registerCountdownHooks = () => { export const registerCountdownHooks = () => {
Hooks.on(socketEvent.Refresh, ({ refreshType, application }) => { Hooks.on(socketEvent.Refresh, ({ refreshType, application }) => {
if (refreshType === RefreshType.Countdown) { if (refreshType === RefreshType.Countdown) {
foundry.applications.instances.get(application)?.render(); if (application) {
foundry.applications.instances.get(application)?.render();
} else {
foundry.applications.instances.get('narrative-countdowns').render();
foundry.applications.instances.get('encounter-countdowns').render();
}
return false; return false;
} }
}); });

View file

@ -1,28 +1,32 @@
const fields = foundry.data.fields; const fields = foundry.data.fields;
const attributeField = () => const attributeField = label =>
new fields.SchemaField({ new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }), value: new fields.NumberField({ initial: 0, integer: true, label }),
tierMarked: new fields.BooleanField({ initial: false }) tierMarked: new fields.BooleanField({ initial: false })
}); });
const resourceField = (max = 0, reverse = false) => const resourceField = (max = 0, label, reverse = false) =>
new fields.SchemaField({ new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }), value: new fields.NumberField({ initial: 0, integer: true, label }),
max: new fields.NumberField({ initial: max, integer: true }), max: new fields.NumberField({ initial: max, integer: true }),
isReversed: new fields.BooleanField({ initial: reverse }) isReversed: new fields.BooleanField({ initial: reverse })
}); });
const stressDamageReductionRule = () => const stressDamageReductionRule = localizationPath =>
new fields.SchemaField({ new fields.SchemaField({
enabled: new fields.BooleanField({ required: true, initial: false }), enabled: new fields.BooleanField({ required: true, initial: false }),
cost: new fields.NumberField({ integer: true }) cost: new fields.NumberField({
integer: true,
label: `${localizationPath}.label`,
hint: `${localizationPath}.hint`
})
}); });
const bonusField = () => const bonusField = label =>
new fields.SchemaField({ new fields.SchemaField({
bonus: new fields.NumberField({ integer: true, initial: 0 }), bonus: new fields.NumberField({ integer: true, initial: 0, label }),
dice: new fields.ArrayField(new fields.StringField()) dice: new fields.ArrayField(new fields.StringField())
}) });
export { attributeField, resourceField, stressDamageReductionRule, bonusField }; export { attributeField, resourceField, stressDamageReductionRule, bonusField };

View file

@ -4,20 +4,22 @@ export default class DhAutomation extends foundry.abstract.DataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { return {
hope: new fields.BooleanField({ hopeFear: new fields.SchemaField({
required: true, gm: new fields.BooleanField({
initial: false, required: true,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hope.label' initial: false,
}), // Label need to be updated into something like "Duality Roll Auto Gain" + a hint label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.gm.label'
}),
players: new fields.BooleanField({
required: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
})
}),
actionPoints: new fields.BooleanField({ actionPoints: new fields.BooleanField({
required: true, required: true,
initial: false, initial: false,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.actionPoints.label' label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.actionPoints.label'
}),
countdowns: new fields.BooleanField({
requireD: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.countdowns.label'
}) })
}; };
} }

View file

@ -102,7 +102,7 @@ export default class D20Roll extends DHRoll {
value: this.options.data.experiences[m].value value: this.options.data.experiences[m].value
}); });
}); });
this.addModifiers(); this.addModifiers();
if (this.options.extraFormula) { if (this.options.extraFormula) {
this.terms.push( this.terms.push(
@ -123,15 +123,17 @@ export default class D20Roll extends DHRoll {
applyBaseBonus() { applyBaseBonus() {
const modifiers = []; const modifiers = [];
if(this.options.roll.bonus) if (this.options.roll.bonus)
modifiers.push({ modifiers.push({
label: 'Bonus to Hit', label: 'Bonus to Hit',
value: this.options.roll.bonus value: this.options.roll.bonus
}); });
modifiers.push(...this.getBonus(`roll.${this.options.type}`, `${this.options.type.capitalize()} Bonus`)); modifiers.push(...this.getBonus(`roll.${this.options.type}`, `${this.options.type.capitalize()} Bonus`));
modifiers.push(...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type.capitalize()} Bonus`)); modifiers.push(
...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type.capitalize()} Bonus`)
);
return modifiers; return modifiers;
} }

View file

@ -34,16 +34,16 @@ export default class DamageRoll extends DHRoll {
}); });
const weapons = ['primaryWeapon', 'secondaryWeapon']; const weapons = ['primaryWeapon', 'secondaryWeapon'];
weapons.forEach(w => { weapons.forEach(w => {
if(this.options.source.item && this.options.source.item === this.data[w]?.id) if (this.options.source.item && this.options.source.item === this.data[w]?.id)
modifiers.push(...this.getBonus(`${type}.${w}`, 'Weapon Bonus')); modifiers.push(...this.getBonus(`${type}.${w}`, 'Weapon Bonus'));
}); });
return modifiers; return modifiers;
} }
constructFormula(config) { constructFormula(config) {
super.constructFormula(config); super.constructFormula(config);
if (config.isCritical) { if (config.isCritical) {
const tmpRoll = new Roll(this._formula)._evaluateSync({ maximize: true }), const tmpRoll = new Roll(this._formula)._evaluateSync({ maximize: true }),
criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll); criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll);

View file

@ -4,7 +4,7 @@ export default class DHRoll extends Roll {
baseTerms = []; baseTerms = [];
constructor(formula, data, options) { constructor(formula, data, options) {
super(formula, data, options); super(formula, data, options);
if(!this.data || !Object.keys(this.data).length) this.data = options.data; if (!this.data || !Object.keys(this.data).length) this.data = options.data;
} }
static messageType = 'adversaryRoll'; static messageType = 'adversaryRoll';
@ -87,7 +87,7 @@ export default class DHRoll extends Roll {
system: config, system: config,
rolls: [roll] rolls: [roll]
}; };
return await cls.create(msg); return await cls.create(msg, { rollMode: config.selectedRollMode });
} }
static applyKeybindings(config) { static applyKeybindings(config) {
@ -100,7 +100,7 @@ export default class DHRoll extends Roll {
} }
formatModifier(modifier) { formatModifier(modifier) {
if(Array.isArray(modifier)) { if (Array.isArray(modifier)) {
return [ return [
new foundry.dice.terms.OperatorTerm({ operator: '+' }), new foundry.dice.terms.OperatorTerm({ operator: '+' }),
...this.constructor.parse(modifier.join(' + '), this.options.data) ...this.constructor.parse(modifier.join(' + '), this.options.data)
@ -127,12 +127,12 @@ export default class DHRoll extends Roll {
getBonus(path, label) { getBonus(path, label) {
const bonus = foundry.utils.getProperty(this.data.bonuses, path), const bonus = foundry.utils.getProperty(this.data.bonuses, path),
modifiers = []; modifiers = [];
if(bonus?.bonus) if (bonus?.bonus)
modifiers.push({ modifiers.push({
label: label, label: label,
value: bonus?.bonus value: bonus?.bonus
}); });
if(bonus?.dice?.length) if (bonus?.dice?.length)
modifiers.push({ modifiers.push({
label: label, label: label,
value: bonus?.dice value: bonus?.dice
@ -175,9 +175,10 @@ export default class DHRoll extends Roll {
export const registerRollDiceHooks = () => { export const registerRollDiceHooks = () => {
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => { Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
const hopeFearAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hopeFear;
if ( if (
!config.source?.actor || !config.source?.actor ||
!game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hope || (game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
config.roll.type === 'reaction' config.roll.type === 'reaction'
) )
return; return;
@ -185,9 +186,9 @@ export const registerRollDiceHooks = () => {
const actor = await fromUuid(config.source.actor), const actor = await fromUuid(config.source.actor),
updates = []; updates = [];
if (!actor) return; if (!actor) return;
if (config.roll.isCritical || config.roll.result.duality === 1) updates.push({ type: 'hope', value: 1 }); if (config.roll.isCritical || config.roll.result.duality === 1) updates.push({ key: 'hope', value: 1 });
if (config.roll.isCritical) updates.push({ type: 'stress', value: -1 }); if (config.roll.isCritical) updates.push({ key: 'stress', value: -1 });
if (config.roll.result.duality === -1) updates.push({ type: 'fear', value: 1 }); if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1 });
if (updates.length) actor.modifyResource(updates); if (updates.length) actor.modifyResource(updates);

View file

@ -121,7 +121,7 @@ export default class DualityRoll extends D20Roll {
applyBaseBonus() { applyBaseBonus() {
const modifiers = super.applyBaseBonus(); const modifiers = super.applyBaseBonus();
if(this.options.roll.trait && this.data.traits[this.options.roll.trait]) if (this.options.roll.trait && this.data.traits[this.options.roll.trait])
modifiers.unshift({ modifiers.unshift({
label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`, label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`,
value: this.data.traits[this.options.roll.trait].value value: this.data.traits[this.options.roll.trait].value
@ -129,7 +129,7 @@ export default class DualityRoll extends D20Roll {
const weapons = ['primaryWeapon', 'secondaryWeapon']; const weapons = ['primaryWeapon', 'secondaryWeapon'];
weapons.forEach(w => { weapons.forEach(w => {
if(this.options.source.item && this.options.source.item === this.data[w]?.id) if (this.options.source.item && this.options.source.item === this.data[w]?.id)
modifiers.push(...this.getBonus(`roll.${w}`, 'Weapon Bonus')); modifiers.push(...this.getBonus(`roll.${w}`, 'Weapon Bonus'));
}); });

View file

@ -3,4 +3,5 @@ export { default as DHItem } from './item.mjs';
export { default as DhpCombat } from './combat.mjs'; export { default as DhpCombat } from './combat.mjs';
export { default as DhActiveEffect } from './activeEffect.mjs'; export { default as DhActiveEffect } from './activeEffect.mjs';
export { default as DhChatMessage } from './chatMessage.mjs'; export { default as DhChatMessage } from './chatMessage.mjs';
export { default as DhToken } from './token.mjs';
export { default as DhTooltipManager } from './tooltipManager.mjs'; export { default as DhTooltipManager } from './tooltipManager.mjs';

View file

@ -37,4 +37,15 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
e.setAttribute('data-use-perm', document.testUserPermission(game.user, 'OWNER')); e.setAttribute('data-use-perm', document.testUserPermission(game.user, 'OWNER'));
}); });
} }
async _preCreate(data, options, user) {
options.speaker = ChatMessage.getSpeaker();
const rollActorOwner = data.rolls?.[0]?.data?.parent?.owner;
if (rollActorOwner) {
data.author = rollActorOwner ? rollActorOwner.id : data.author;
await this.updateSource({ author: rollActorOwner ?? user });
}
return super._preCreate(data, options, rollActorOwner ?? user);
}
} }

View file

@ -0,0 +1,35 @@
export default class DHToken extends TokenDocument {
/**
* Inspect the Actor data model and identify the set of attributes which could be used for a Token Bar.
* @param {object} attributes The tracked attributes which can be chosen from
* @returns {object} A nested object of attribute choices to display
*/
static getTrackedAttributeChoices(attributes, model) {
attributes = attributes || this.getTrackedAttributes();
const barGroup = game.i18n.localize('TOKEN.BarAttributes');
const valueGroup = game.i18n.localize('TOKEN.BarValues');
const bars = attributes.bar.map(v => {
const a = v.join('.');
const modelLabel = model ? game.i18n.localize(model.schema.getField(`${a}.value`).label) : null;
return { group: barGroup, value: a, label: modelLabel ? modelLabel : a };
});
bars.sort((a, b) => a.label.compare(b.label));
const invalidAttributes = ['gold', 'levelData', 'rules.damageReduction.maxArmorMarked.value'];
const values = attributes.value.reduce((acc, v) => {
const a = v.join('.');
if (invalidAttributes.some(x => a.startsWith(x))) return acc;
const field = model ? model.schema.getField(a) : null;
const modelLabel = field ? game.i18n.localize(field.label) : null;
const hint = field ? game.i18n.localize(field.hint) : null;
acc.push({ group: valueGroup, value: a, label: modelLabel ? modelLabel : a, hint: hint });
return acc;
}, []);
values.sort((a, b) => a.label.compare(b.label));
return bars.concat(values);
}
}

6
package-lock.json generated
View file

@ -6,6 +6,7 @@
"": { "": {
"dependencies": { "dependencies": {
"@yaireo/tagify": "^4.17.9", "@yaireo/tagify": "^4.17.9",
"autocompleter": "^9.3.2",
"gulp": "^5.0.0", "gulp": "^5.0.0",
"gulp-less": "^5.0.0", "gulp-less": "^5.0.0",
"rollup": "^4.40.0" "rollup": "^4.40.0"
@ -608,6 +609,11 @@
"node": ">= 10.13.0" "node": ">= 10.13.0"
} }
}, },
"node_modules/autocompleter": {
"version": "9.3.2",
"resolved": "https://registry.npmjs.org/autocompleter/-/autocompleter-9.3.2.tgz",
"integrity": "sha512-rLbf2TLGOD7y+gOS36ksrZdIsvoHa2KXc2A7503w+NBRPrcF73zzFeYBxEcV/iMPjaBH3jFhNIYObZ7zt1fkCQ=="
},
"node_modules/available-typed-arrays": { "node_modules/available-typed-arrays": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",

View file

@ -1,6 +1,7 @@
{ {
"dependencies": { "dependencies": {
"@yaireo/tagify": "^4.17.9", "@yaireo/tagify": "^4.17.9",
"autocompleter": "^9.3.2",
"gulp": "^5.0.0", "gulp": "^5.0.0",
"gulp-less": "^5.0.0", "gulp-less": "^5.0.0",
"rollup": "^4.40.0" "rollup": "^4.40.0"

View file

@ -1,20 +1,34 @@
@import '../../utils/colors.less'; @import '../../utils/colors.less';
.daggerheart.dialog.dh-style.views.damage-selection { .daggerheart.dialog.dh-style.views.damage-selection {
.damage-section-container { .damage-section-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
input[type='text'], input[type='text'],
input[type='number'] { input[type='number'] {
color: light-dark(@dark, @beige); color: light-dark(@dark, @beige);
outline: 2px solid transparent; outline: 2px solid transparent;
transition: all 0.3s ease; transition: all 0.3s ease;
&:hover { &:hover {
outline: 2px solid light-dark(@dark, @beige); outline: 2px solid light-dark(@dark, @beige);
} }
} }
}
} .damage-section-controls {
display: flex;
align-items: center;
gap: 16px;
.roll-mode-select {
width: min-content;
}
button {
flex: 1;
}
}
}
}

View file

@ -114,5 +114,19 @@
} }
} }
} }
.roll-dialog-controls {
display: flex;
align-items: center;
gap: 16px;
.roll-mode-select {
width: min-content;
}
button {
flex: 1;
}
}
} }
} }

View file

@ -38,7 +38,7 @@
input[type='checkbox'], input[type='checkbox'],
input[type='radio'] { input[type='radio'] {
&:checked::after { &:checked::after {
color: light-dark(@dark-40, @golden); color: light-dark(@dark, @golden);
} }
&:checked::before { &:checked::before {
color: light-dark(@dark-40, @golden-40); color: light-dark(@dark-40, @golden-40);
@ -112,22 +112,17 @@
margin: 5px; margin: 5px;
height: inherit; height: inherit;
.tag { .tag {
box-shadow: 0 0 0 1.1em @beige inset; padding: 0.3rem 0.5rem;
vertical-align: top; color: light-dark(@dark-blue, @golden);
box-sizing: border-box; background-color: light-dark(@dark-blue-10, @golden-40);
max-width: 100%; font-family: @font-body;
padding: 0.3em 0 0.3em 0.5em;
color: black;
border-radius: 3px; border-radius: 3px;
white-space: nowrap;
transition: 0.13s ease-out; transition: 0.13s ease-out;
height: 22px; gap: 0.5rem;
font-size: 0.9rem;
gap: 0.5em;
z-index: 1; z-index: 1;
.remove { .remove {
font-size: 10px; font-size: 10px;
margin-inline: auto 4.6666666667px;
} }
} }
} }
@ -434,6 +429,34 @@
border: 1px solid light-dark(@dark, @beige); border: 1px solid light-dark(@dark, @beige);
height: 34px; height: 34px;
// tagify rule styles
--tags-disabled-bg: none;
--tags-border-color: none;
--tags-hover-border-color: none;
--tags-focus-border-color: none;
--tag-border-radius: 3px;
--tag-bg: light-dark(@dark-blue, @golden);
--tag-remove-btn-color: light-dark(@dark-blue, @golden);
--tag-hover: light-dark(@dark-blue, @golden);
--tag-text-color: light-dark(@beige, @dark);
--tag-text-color--edit: light-dark(@beige, @dark);
--tag-pad: 0.3em 0.5em;
--tag-inset-shadow-size: 1.2em;
--tag-invalid-color: #d39494;
--tag-invalid-bg: rgba(211, 148, 148, 0.5);
--tag--min-width: 1ch;
--tag--max-width: 100%;
--tag-hide-transition: 0.3s;
--tag-remove-bg: light-dark(@dark-blue-40, @golden-40);
--tag-remove-btn-color: light-dark(@beige, @dark);
--tag-remove-btn-bg: none;
--tag-remove-btn-bg--hover: light-dark(@beige, @dark);
--input-color: inherit;
--placeholder-color: light-dark(@beige-15, @dark-15);
--placeholder-color-focus: light-dark(@beige-15, @dark-15);
--loader-size: 0.8em;
--readonly-striped: 1;
border-radius: 3px; border-radius: 3px;
margin-right: 1px; margin-right: 1px;
@ -459,30 +482,27 @@
.tagify__dropdown { .tagify__dropdown {
border: 1px solid light-dark(@dark, @beige) !important; border: 1px solid light-dark(@dark, @beige) !important;
font-family: @font-body;
color: light-dark(@dark, @beige);
.tagify__dropdown__wrapper { .tagify__dropdown__wrapper {
background-image: url(../assets/parchments/dh-parchment-dark.png); background-image: url(../assets/parchments/dh-parchment-dark.png);
background-color: transparent; background-color: transparent;
border: 0; border: 0;
color: light-dark(@dark, @beige);
.tagify__dropdown__item--active { .tagify__dropdown__item--active {
background-color: light-dark(@dark, @beige); background-color: light-dark(@dark, @beige);
color: var(--color-dark-3); color: light-dark(@beige, @dark);
} }
} }
} }
&.theme-light { &.theme-light {
.tagify__dropdown { .tagify__dropdown {
color: black;
.tagify__dropdown__wrapper { .tagify__dropdown__wrapper {
background-image: url(../assets/parchments/dh-parchment-light.png); background-image: url(../assets/parchments/dh-parchment-light.png);
} }
.tagify__dropdown__item--active {
color: @beige;
}
} }
} }
} }

View file

@ -3,7 +3,8 @@
.application.sheet.daggerheart.actor.dh-style.companion { .application.sheet.daggerheart.actor.dh-style.companion {
.partner-section, .partner-section,
.attack-section { .attack-section,
.experience-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@ -12,6 +13,7 @@
display: flex; display: flex;
gap: 15px; gap: 15px;
align-items: center; align-items: center;
width: 100%;
h3 { h3 {
font-size: 20px; font-size: 20px;

View file

@ -0,0 +1,44 @@
.theme-light .autocomplete {
background-image: url('../assets/parchments/dh-parchment-light.png');
color: black;
}
.autocomplete {
padding: 2px;
border-width: 0 1px 1px 1px;
border-style: solid;
border-color: light-dark(@dark, @beige);
border-radius: 6px;
background-image: url('../assets/parchments/dh-parchment-dark.png');
z-index: 200;
max-height: 400px !important;
width: fit-content !important;
overflow-y: auto;
font-family: @font-body;
display: flex;
flex-direction: column;
gap: 2px;
scrollbar-color: light-dark(@dark-blue, @golden) transparent;
.group {
font-weight: bold;
font-size: 14px;
padding-left: 8px;
}
li[role='option'] {
font-size: 14px;
padding-left: 10px;
cursor: pointer;
&:hover {
background-color: light-dark(@dark, @beige);
color: light-dark(@beige, var(--color-dark-3));
}
> div {
white-space: nowrap;
}
}
}

View file

@ -1 +1,2 @@
@import './tooltip/tooltip.less'; @import './tooltip/tooltip.less';
@import './autocomplete/autocomplete.less';

View file

@ -264,7 +264,7 @@
"applyEffect": {} "applyEffect": {}
} }
}, },
"primaryTokenAttribute": "resources.health", "primaryTokenAttribute": "resources.hitPoints",
"secondaryTokenAttribute": "resources.stress", "secondaryTokenAttribute": "resources.stress",
"url": "https://your/hosted/system/repo/", "url": "https://your/hosted/system/repo/",
"manifest": "https://your/hosted/system/repo/system.json", "manifest": "https://your/hosted/system/repo/system.json",

View file

@ -6,8 +6,15 @@
<div class="form-group"> <div class="form-group">
<input type="text" value="{{extraFormula}}" name="extraFormula" placeholder="Situational Bonus"> <input type="text" value="{{extraFormula}}" name="extraFormula" placeholder="Situational Bonus">
</div> </div>
<button class="submit-btn" data-action="submitRoll"> <div class="damage-section-controls">
<i class="fa-solid fa-dice"></i> {{#if directDamage}}
<span class="label">Roll</span> <select class="roll-mode-select" name="selectedRollMode">
</button> {{selectOptions rollModes selected=selectedRollMode valueAttr="action" labelAttr="label" localize=true}}
</select>
{{/if}}
<button class="submit-btn" data-action="submitRoll">
<i class="fa-solid fa-dice"></i>
<span class="label">{{localize "DAGGERHEART.GENERAL.roll"}}</span>
</button>
</div>
</section> </section>

View file

@ -117,10 +117,15 @@
</fieldset> </fieldset>
{{/unless}} {{/unless}}
<span class="formula-label"><b>Formula:</b> {{@root.formula}}</span> <span class="formula-label"><b>Formula:</b> {{@root.formula}}</span>
<button class="sunmit-btn" data-action="submitRoll"{{#unless canRoll}} disabled{{/unless}}> <div class="roll-dialog-controls">
<i class="fa-solid fa-dice"></i> <select class="roll-mode-select" name="selectedRollMode">
<span class="label">Roll</span> {{selectOptions rollModes selected=selectedRollMode valueAttr="action" labelAttr="label" localize=true}}
</button> </select>
<button class="sunmit-btn" data-action="submitRoll"{{#unless canRoll}} disabled{{/unless}}>
<i class="fa-solid fa-dice"></i>
<span class="label">Roll</span>
</button>
</div>
{{else}} {{else}}
<button class="sunmit-btn" data-action="submitRoll"{{#unless canRoll}} disabled{{/unless}}> <button class="sunmit-btn" data-action="submitRoll"{{#unless canRoll}} disabled{{/unless}}>
<span class="label">Continue</span> <span class="label">Continue</span>

View file

@ -1,5 +1,10 @@
<div> <div>
{{formGroup settingFields.schema.fields.hope value=settingFields._source.hope localize=true}} <div class="form-group">
<label>{{localize "DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.label"}}</label>
{{formGroup settingFields.schema.fields.hopeFear.fields.gm value=settingFields._source.hopeFear.gm localize=true}}
{{formGroup settingFields.schema.fields.hopeFear.fields.players value=settingFields._source.hopeFear.players localize=true}}
</div>
{{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}} {{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}}
{{formGroup settingFields.schema.fields.countdowns value=settingFields._source.countdowns localize=true}} {{formGroup settingFields.schema.fields.countdowns value=settingFields._source.countdowns localize=true}}

View file

@ -20,19 +20,19 @@
<div class="fieldsets-section"> <div class="fieldsets-section">
<fieldset class="flex"> <fieldset class="flex">
<legend>{{localize "DAGGERHEART.GENERAL.hitPoints"}}</legend> <legend>{{localize "DAGGERHEART.GENERAL.hitPoints"}}</legend>
{{formGroup systemFields.resources.fields.hitPoints.fields.value value=document.system.resources.hitPoints.value}} {{formGroup systemFields.resources.fields.hitPoints.fields.value value=document.system.resources.hitPoints.value label=(localize "DAGGERHEART.ACTORS.Adversary.FIELDS.resources.hitPoints.value.label")}}
{{formGroup systemFields.resources.fields.hitPoints.fields.max value=document.system.resources.hitPoints.max}} {{formGroup systemFields.resources.fields.hitPoints.fields.max value=document.system.resources.hitPoints.max}}
</fieldset> </fieldset>
<fieldset class="flex"> <fieldset class="flex">
<legend>{{localize "DAGGERHEART.GENERAL.stress"}}</legend> <legend>{{localize "DAGGERHEART.GENERAL.stress"}}</legend>
{{formGroup systemFields.resources.fields.stress.fields.value value=document.system.resources.stress.value}} {{formGroup systemFields.resources.fields.stress.fields.value value=document.system.resources.stress.value label=(localize "DAGGERHEART.ACTORS.Adversary.FIELDS.resources.stress.value.label")}}
{{formGroup systemFields.resources.fields.stress.fields.max value=document.system.resources.stress.max}} {{formGroup systemFields.resources.fields.stress.fields.max value=document.system.resources.stress.max}}
</fieldset> </fieldset>
</div> </div>
<fieldset class="flex"> <fieldset class="flex">
<legend>{{localize "DAGGERHEART.GENERAL.DamageThresholds.title"}}</legend> <legend>{{localize "DAGGERHEART.GENERAL.DamageThresholds.title"}}</legend>
{{formGroup systemFields.damageThresholds.fields.major value=document.system.damageThresholds.major}} {{formGroup systemFields.damageThresholds.fields.major value=document.system.damageThresholds.major label=(localize "DAGGERHEART.GENERAL.DamageThresholds.majorThreshold")}}
{{formGroup systemFields.damageThresholds.fields.severe value=document.system.damageThresholds.severe}} {{formGroup systemFields.damageThresholds.fields.severe value=document.system.damageThresholds.severe label=(localize "DAGGERHEART.GENERAL.DamageThresholds.severeThreshold")}}
</fieldset> </fieldset>
</section> </section>

View file

@ -11,13 +11,13 @@
{{formGroup systemFields.resources.fields.stress.fields.max value=document.system.resources.stress.max label='Max Stress'}} {{formGroup systemFields.resources.fields.stress.fields.max value=document.system.resources.stress.max label='Max Stress'}}
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-fields"> <div class="form-fields">
<label>{{localize "DAGGERHEART.ACTORS.Companion.FIELDS.partner.label"}}</label> <label>{{localize "DAGGERHEART.ACTORS.Companion.FIELDS.partner.label"}}</label>
<select class="partner-value" name="system.partner"> <select class="partner-value" name="system.partner">
{{selectOptions playerCharacters selected=document.system.partner.uuid labelAttr="name" valueAttr="key" blank=""}} {{selectOptions playerCharacters selected=document.system.partner.uuid labelAttr="name" valueAttr="key" blank=""}}
</select> </select>
</div>
</div> </div>
</div>
</fieldset> </fieldset>
<button type="button" data-action="levelUp" {{#if (not document.system.levelData.canLevelUp)}}disabled{{/if}}>Level Up</button> <button type="button" data-action="levelUp" {{#if (not document.system.levelData.canLevelUp)}}disabled{{/if}}>Level Up</button>
</section> </section>

View file

@ -11,12 +11,7 @@
{{#with ../fields.changes.element.fields as |changeFields|}} {{#with ../fields.changes.element.fields as |changeFields|}}
<li data-index="{{i}}"> <li data-index="{{i}}">
<div class="key"> <div class="key">
<input type="text" name="{{concat "changes." i ".key"}}" value="{{change.key}}" list="change-fields" /> <input type="text" class="effect-change-input" name="{{concat "changes." i ".key"}}" value="{{change.key}}" />
<datalist id="change-fields">
{{#each @root.fieldPaths}}
<option value="{{this}}">{{this}}</option>
{{/each}}
</datalist>
</div> </div>
<div class="mode"> <div class="mode">
{{formInput changeFields.mode name=(concat "changes." i ".mode") value=change.mode choices=@root.modes}} {{formInput changeFields.mode name=(concat "changes." i ".mode") value=change.mode choices=@root.modes}}

View file

@ -6,7 +6,7 @@
<div class="partner-section"> <div class="partner-section">
<div class="title"> <div class="title">
<side-line-div class="invert"></side-line-div> <side-line-div class="invert"></side-line-div>
<h3>Partner</h3> <h3>{{localize "DAGGERHEART.GENERAL.partner"}}</h3>
<side-line-div></side-line-div> <side-line-div></side-line-div>
</div> </div>
{{#if document.system.partner}} {{#if document.system.partner}}
@ -20,7 +20,7 @@
<div class="attack-section"> <div class="attack-section">
<div class="title"> <div class="title">
<side-line-div class="invert"></side-line-div> <side-line-div class="invert"></side-line-div>
<h3>Attack</h3> <h3>{{localize "DAGGERHEART.GENERAL.attack"}}</h3>
<side-line-div></side-line-div> <side-line-div></side-line-div>
</div> </div>
<ul class="item-list"> <ul class="item-list">
@ -28,6 +28,11 @@
</ul> </ul>
</div> </div>
<div class="experience-list"> <div class="experience-list">
<div class="title">
<side-line-div class="invert"></side-line-div>
<h3>{{localize "DAGGERHEART.GENERAL.experience.plural"}}</h3>
<side-line-div></side-line-div>
</div>
{{#each source.system.experiences as |experience id|}} {{#each source.system.experiences as |experience id|}}
<div class="experience-row"> <div class="experience-row">
<div class="experience-value"> <div class="experience-value">