diff --git a/dnd5e.mjs b/dnd5e.mjs index b4c9fd9f6a..3d42f6f702 100644 --- a/dnd5e.mjs +++ b/dnd5e.mjs @@ -392,6 +392,7 @@ function expandAttributeList(attributes) { */ Hooks.once("i18nInit", () => { utils.performPreLocalization(CONFIG.DND5E); + Localization.localizeDataModel(dnd5e.documents.activity.AttackActivity); Localization.localizeDataModel(dnd5e.documents.activity.UtilityActivity); }); diff --git a/icons/LICENSE b/icons/LICENSE index 0f80a8ecab..a4d8fe8397 100644 --- a/icons/LICENSE +++ b/icons/LICENSE @@ -27,6 +27,7 @@ The dnd5e system for Foundry Virtual Tabletop includes icon artwork licensed fro /svg/trait-weapon-proficiencies.svg - "Crossed swords" by Lorc under CC BY 3.0 /svg/vehicle.svg - "Ship's wheel" by Delapouite under CC BY 3.0 /svg/versatile.svg - "Swiss army knife" by Delapouite under CC BY 3.0 +/svg/activity/attack.svg - "Sword clash" by Lorc under CC BY 3.0 /svg/activity/utility.svg - "Spanner" by Lorc under CC BY 3.0 /svg/damage/acid.svg - "Fizzling flask" by Lorc under CC BY 3.0 /svg/damage/bludgeoning.svg - "Claw hammer" by Lorc under CC BY 3.0 diff --git a/icons/svg/activity/attack.svg b/icons/svg/activity/attack.svg new file mode 100644 index 0000000000..736ec29798 --- /dev/null +++ b/icons/svg/activity/attack.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lang/en.json b/lang/en.json index 503d178ce3..f4dc8e0807 100644 --- a/lang/en.json +++ b/lang/en.json @@ -292,9 +292,6 @@ "Effect": "Effect", "Identity": "Identity", "Time": "Time" - }, - "Utility": { - "Title": "Utility" } }, @@ -506,6 +503,62 @@ "DND5E.ArmorLightProficiency": "Light", "DND5E.ArmorMediumProficiency": "Medium", "DND5E.AC": "AC", + +"DND5E.ATTACK": { + "Title": { + "one": "Attack", + "other": "Attacks" + }, + "FIELDS": { + "ability": { + "label": "Attack Ability", + "hint": "Ability used for make the attack and determine damage. Available using @mod in formulas." + }, + "attack": { + "label": "Attack Details", + "bonus": { + "label": "To Hit Bonus", + "hint": "Bonus added to the to hit roll for the attack." + }, + "flat": { + "label": "Flat To Hit", + "hint": "Ignore the ability modifier, proficiency, and any other bonuses from the actor and only use the bonus defined by the activity when calculating to hit." + }, + "type": { + "label": "Attack Type", + "value": { + "label": "Attack Type", + "hint": "Is this a melee or ranged attack?" + }, + "classification": { + "label": "Attack Classification", + "hint": "Is this an unarmed, weapon, or spell attack?" + } + } + }, + "damage": { + "label": "Attack Damage", + "includeBase": { + "label": "Include Base Damage", + "hint": "Include the item's base damage with any additional damage parts." + }, + "parts": { + "label": "Damage Parts", + "hint": "Individual damage parts to include with the roll." + } + } + }, + "Classification": { + "Spell": "Spell", + "Unarmed": "Unarmed", + "Weapon": "Weapon" + }, + "Type": { + "Melee": "Melee", + "Ranged": "Ranged" + } +}, + "DND5E.Attack": "Attack", "DND5E.AttackPl": "Attacks", "DND5E.AttackRoll": "Attack Roll", @@ -850,6 +903,63 @@ "Half": "Half" } }, +"DND5E.DAMAGE": { + "FIELDS": { + "number": { + "label": "Die Number", + "hint": "Number of dice to roll." + }, + "denomination": { + "label": "Die Denomination", + "hint": "Denomination of the dice to roll." + }, + "bonus": { + "label": "Damage Bonus", + "hint": "Bonus added to the damage roll." + }, + "types": { + "label": "Damage Types", + "hint": "Type of damage inflicted or multiple for the user to select from." + }, + "custom": { + "label": "Custom Damage Formula", + "enabled": { + "label": "Enable Custom Formula", + "hint": "Should the custom formula be used rather than the default dice." + }, + "formula": { + "label": "Damage Formula", + "hint": "Custom damage formula." + } + }, + "scaling": { + "label": "Damage Scaling", + "mode": { + "label": "Scaling Mode", + "hint": "Method by which the scaling increase is calculated." + }, + "number": { + "label": "Dice Scaling", + "hint": "Number of dice to increase for each scaling step. Will be applied to the first die found in the damage formula if more than one is present." + }, + "formula": { + "label": "Scaling Formula", + "hint": "Arbitrary scaling formula that will be multiplied for each scaling step and added to the original formula." + } + } + }, + "Part": { + "Action": { + "Create": "Create Damage Part", + "Delete": "Delete Damage Part" + } + }, + "Scaling": { + "Half": "Every Other Level", + "None": "No Scaling", + "Whole": "Every Level" + } +}, "DND5E.DamImm": "Damage Immunities", "DND5E.DamMod": "Damage Modification", "DND5E.DamRes": "Damage Resistances", @@ -2128,6 +2238,7 @@ "Sr": "Short Rest", "SrAbbreviation": "SR" }, + "DND5E.USES": { "FIELDS": { "uses": { @@ -2162,6 +2273,11 @@ } } }, + +"DND5E.UTILITY": { + "Title": "Utility" +}, + "DND5E.Vehicle": "Vehicle", "DND5E.VehicleActions": "Actions", "DND5E.VehicleActionsHint": "Actions taken with full crew complement", diff --git a/module/applications/activity/_module.mjs b/module/applications/activity/_module.mjs index 99f7b1c34d..4d86151bba 100644 --- a/module/applications/activity/_module.mjs +++ b/module/applications/activity/_module.mjs @@ -1 +1,4 @@ export {default as ActivitySheet} from "./activity-sheet.mjs"; +export {default as AttackSheet} from "./attack-sheet.mjs"; + +export {default as ActivityUsageDialog} from "./activity-usage-dialog.mjs"; diff --git a/module/applications/activity/activity-sheet.mjs b/module/applications/activity/activity-sheet.mjs index 43a9bd17f6..1fcf9e5225 100644 --- a/module/applications/activity/activity-sheet.mjs +++ b/module/applications/activity/activity-sheet.mjs @@ -48,7 +48,10 @@ export default class ActivitySheet extends Application5e { template: "templates/generic/tab-navigation.hbs" }, identity: { - template: "systems/dnd5e/templates/activity/identity.hbs" + template: "systems/dnd5e/templates/activity/identity.hbs", + templates: [ + "systems/dnd5e/templates/activity/parts/activity-identity.hbs" + ] }, activation: { template: "systems/dnd5e/templates/activity/activation.hbs", diff --git a/module/applications/activity/attack-sheet.mjs b/module/applications/activity/attack-sheet.mjs new file mode 100644 index 0000000000..9c8edb2bdb --- /dev/null +++ b/module/applications/activity/attack-sheet.mjs @@ -0,0 +1,139 @@ +import ActivitySheet from "./activity-sheet.mjs"; + +/** + * Sheet for the attack activity. + */ +export default class AttackSheet extends ActivitySheet { + + /** @inheritDoc */ + static DEFAULT_OPTIONS = { + classes: ["attack-activity"], + actions: { + addDamagePart: AttackSheet.#addDamagePart, + deleteDamagePart: AttackSheet.#deleteDamagePart + } + }; + + /* -------------------------------------------- */ + + /** @inheritDoc */ + static PARTS = { + ...super.PARTS, + identity: { + template: "systems/dnd5e/templates/activity/attack-identity.hbs", + templates: [ + ...super.PARTS.identity.templates, + "systems/dnd5e/templates/activity/parts/attack-identity.hbs" + ] + }, + effect: { + template: "systems/dnd5e/templates/activity/attack-effect.hbs", + templates: [ + ...super.PARTS.effect.templates, + "systems/dnd5e/templates/activity/parts/attack-damage.hbs", + "systems/dnd5e/templates/activity/parts/attack-details.hbs", + "systems/dnd5e/templates/activity/parts/damage-parts.hbs" + ] + } + }; + + /* -------------------------------------------- */ + /* Rendering */ + /* -------------------------------------------- */ + + /** @inheritDoc */ + async _prepareEffectContext(context) { + context = await super._prepareEffectContext(context); + + // TODO: Add better default label to indicate what ability will be chosen + // TODO: Add spellcasting option to automatically select spellcasting ability + context.abilityOptions = [ + { value: "", label: "" }, + ...Object.entries(CONFIG.DND5E.abilities).map(([value, config]) => ({ value, label: config.label })) + ]; + + const denominationOptions = [ + { value: "", label: "" }, + ...CONFIG.DND5E.dieSteps.map(value => ({ value, label: `d${value}` })) + ]; + const scalingOptions = [ + { value: "", label: game.i18n.localize("DND5E.DAMAGE.Scaling.None") }, + ...Object.entries(CONFIG.DND5E.damageScalingModes).map(([value, config]) => ({ value, label: config.label })) + ]; + context.damageParts = context.activity.damage.parts.map((data, index) => ({ + data, + fields: this.activity.schema.fields.damage.fields.parts.element.fields, + prefix: `damage.parts.${index}.`, + source: context.source.damage.parts[index] ?? data, + canScale: this.activity.canScaleDamage, + denominationOptions, + scalingOptions, + typeOptions: Object.entries(CONFIG.DND5E.damageTypes).map(([value, config]) => ({ + value, label: config.label, selected: data.types.has(value) + })) + })); + + return context; + } + + /* -------------------------------------------- */ + + /** @inheritDoc */ + async _prepareIdentityContext(context) { + context = await super._prepareIdentityContext(context); + + // TODO: Add better default label to indicate what type is inferred from the item + context.attackTypeOptions = [ + { value: "", label: "" }, + ...Object.entries(CONFIG.DND5E.attackTypes).map(([value, config]) => ({ value, label: config.label })) + ]; + // TODO: Add better default label to indicate what classification is inferred from the item + context.attackClassificationOptions = [ + { value: "", label: "" }, + ...Object.entries(CONFIG.DND5E.attackClassifications).map(([value, config]) => ({ value, label: config.label })) + ]; + + return context; + } + + /* -------------------------------------------- */ + /* Event Listeners and Handlers */ + /* -------------------------------------------- */ + + /** + * Handle adding a new entry to the damage parts list. + * @this {ActivityConfig} + * @param {Event} event Triggering click event. + * @param {HTMLElement} target Button that was clicked. + */ + static #addDamagePart(event, target) { + this.activity.update({ "damage.parts": [...this.activity.toObject().damage.parts, {}] }); + } + + /* -------------------------------------------- */ + + /** + * Handle removing an entry from the damage parts list. + * @this {ActivityConfig} + * @param {Event} event Triggering click event. + * @param {HTMLElement} target Button that was clicked. + */ + static #deleteDamagePart(event, target) { + const parts = this.activity.toObject().damage.parts; + parts.splice(target.closest("[data-index]").dataset.index, 1); + this.activity.update({ "damage.parts": parts }); + } + + /* -------------------------------------------- */ + /* Form Handling */ + /* -------------------------------------------- */ + + /** @inheritDoc */ + _prepareSubmitData(event, formData) { + const submitData = super._prepareSubmitData(event, formData); + if ( foundry.utils.hasProperty(submitData, "damage.parts") ) { + submitData.damage.parts = Object.values(submitData.damage.parts); + } + return submitData; + } +} diff --git a/module/config.mjs b/module/config.mjs index 6715444207..a61d9cf34d 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -1612,7 +1612,31 @@ DND5E.currencies = { preLocalize("currencies", { keys: ["label", "abbreviation"] }); /* -------------------------------------------- */ -/* Damage Types */ +/* Damage */ +/* -------------------------------------------- */ + +/** + * Standard dice spread available for things like damage. + * @type {number[]} + */ +DND5E.dieSteps = [4, 6, 8, 10, 12, 20, 100]; + +/* -------------------------------------------- */ + +/** + * Methods by which damage scales relative to the overall scaling increase. + * @enum {{ label: string }} + */ +DND5E.damageScalingModes = { + whole: { + label: "DND5E.DAMAGE.Scaling.Whole" + }, + half: { + label: "DND5E.DAMAGE.Scaling.Half" + } +}; +preLocalize("damageScalingModes", { key: "label" }); + /* -------------------------------------------- */ /** @@ -2082,6 +2106,43 @@ DND5E.senses = { }; preLocalize("senses", { sort: true }); +/* -------------------------------------------- */ +/* Attacks */ +/* -------------------------------------------- */ + +/** + * Classifications of attacks based on what is performing them. + * @enum {{ label: string }} + */ +DND5E.attackClassifications = { + weapon: { + label: "DND5E.ATTACK.Classification.Weapon" + }, + spell: { + label: "DND5E.ATTACK.Classification.Spell" + }, + unarmed: { + label: "DND5E.ATTACK.Classification.Unarmed" + } +}; +preLocalize("attackClassifications", { key: "label" }); + +/* -------------------------------------------- */ + +/** + * Types of attacks based on range. + * @enum {{ label: string }} + */ +DND5E.attackTypes = Object.seal({ + melee: { + label: "DND5E.ATTACK.Type.Melee" + }, + ranged: { + label: "DND5E.ATTACK.Type.Ranged" + } +}); +preLocalize("attackTypes", { key: "label" }); + /* -------------------------------------------- */ /* Spellcasting */ /* -------------------------------------------- */ @@ -3172,6 +3233,9 @@ preLocalize("groupTypes"); * @property {boolean} [hidden] Should this activity type be hidden in the selection dialog? */ DND5E.activityTypes = { + attack: { + documentClass: activities.AttackActivity + }, utility: { documentClass: activities.UtilityActivity } diff --git a/module/data/activity/_module.mjs b/module/data/activity/_module.mjs index 262fc4c3d7..a04c83bbaa 100644 --- a/module/data/activity/_module.mjs +++ b/module/data/activity/_module.mjs @@ -1 +1,3 @@ export {default as BaseActivityData} from "./base-activity.mjs"; + +export {default as AttackActivityData} from "./attack-data.mjs"; diff --git a/module/data/activity/attack-data.mjs b/module/data/activity/attack-data.mjs new file mode 100644 index 0000000000..123df83c47 --- /dev/null +++ b/module/data/activity/attack-data.mjs @@ -0,0 +1,41 @@ +import FormulaField from "../fields/formula-field.mjs"; +import DamageField from "../shared/damage-field.mjs"; +import BaseActivityData from "./base-activity.mjs"; + +const { ArrayField, BooleanField, SchemaField, StringField } = foundry.data.fields; + +/** + * Data model for an attack activity. + * + * @property {string} ability Ability used to make the attack and determine damage. + * @property {object} attack + * @property {string} attack.bonus Arbitrary bonus added to the attack. + * @property {boolean} attack.flat Should the bonus be used in place of proficiency & ability modifier? + * @property {object} attack.type + * @property {string} attack.type.value Is this a melee or ranged attack? + * @property {string} attack.type.classification Is this a unarmed, weapon, or spell attack? + * @property {object} damage + * @property {boolean} damage.includeBase Should damage defined by the item be included with other damage parts? + * @property {DamageData[]} damage.parts Parts of damage to inflict. + */ +export default class AttackActivityData extends BaseActivityData { + /** @inheritDoc */ + static defineSchema() { + return { + ...super.defineSchema(), + ability: new StringField(), + attack: new SchemaField({ + bonus: new FormulaField(), + flat: new BooleanField(), + type: new SchemaField({ + value: new StringField(), + classification: new StringField() + }) + }), + damage: new SchemaField({ + includeBase: new BooleanField({ initial: true }), + parts: new ArrayField(new DamageField()) + }) + }; + } +} diff --git a/module/data/shared/_module.mjs b/module/data/shared/_module.mjs index e8a48f9458..b5476feac2 100644 --- a/module/data/shared/_module.mjs +++ b/module/data/shared/_module.mjs @@ -1,5 +1,6 @@ export {default as CreatureTypeField} from "./creature-type-field.mjs"; export {default as CurrencyTemplate} from "./currency.mjs"; +export {default as DamageField, DamageData} from "./damage-field.mjs"; export {default as MovementField} from "./movement-field.mjs"; export {default as SensesField} from "./senses-field.mjs"; export {default as SourceField} from "./source-field.mjs"; diff --git a/module/data/shared/damage-field.mjs b/module/data/shared/damage-field.mjs new file mode 100644 index 0000000000..bd00816134 --- /dev/null +++ b/module/data/shared/damage-field.mjs @@ -0,0 +1,127 @@ +import FormulaField from "../fields/formula-field.mjs"; + +const { BooleanField, EmbeddedDataField, NumberField, SchemaField, SetField, StringField } = foundry.data.fields; + +/** + * Field for storing damage data. + */ +export default class DamageField extends EmbeddedDataField { + constructor(options) { + super(DamageData, options); + } +} + +/* -------------------------------------------- */ + +/** + * Data model that stores information on a single damage part. + * + * @property {number} number Number of dice to roll. + * @property {number} denomination Die denomination to roll. + * @property {string} bonus Bonus added to the damage. + * @property {Set} types One or more damage types. If multiple are selected, then the user will be able to + * select from those types. + * @property {object} custom + * @property {boolean} custom.enabled Should the custom formula be used? + * @property {string} custom.formula Custom damage formula. + * @property {object} scaling + * @property {string} scaling.mode How the damage scales in relation with levels. + * @property {number} scaling.number Number of dice to add per scaling level. + * @property {string} scaling.formula Arbitrary scaling formula which will be multiplied by scaling increase. + */ +export class DamageData extends foundry.abstract.DataModel { + + /* -------------------------------------------- */ + /* Model Configuration */ + /* -------------------------------------------- */ + + /** @override */ + static LOCALIZATION_PREFIXES = ["DND5E.DAMAGE"]; + + /* -------------------------------------------- */ + + /** @override */ + static defineSchema() { + return { + number: new NumberField({ min: 0, integer: true }), + denomination: new NumberField({ min: 0, integer: true }), + bonus: new FormulaField(), + types: new SetField(new StringField()), + custom: new SchemaField({ + enabled: new BooleanField(), + formula: new FormulaField() + }), + scaling: new SchemaField({ + mode: new StringField(), + number: new NumberField({ initial: 1, min: 0, integer: true }), + formula: new FormulaField() + }) + }; + } + + /* -------------------------------------------- */ + /* Properties */ + /* -------------------------------------------- */ + + /** + * The default damage formula. + * @type {string} + */ + get formula() { + if ( this.custom.enabled ) return this.custom.formula ?? ""; + return this._automaticFormula(); + } + + /* -------------------------------------------- */ + /* Helpers */ + /* -------------------------------------------- */ + + /** + * Produce the auto-generated formula from the `number`, `denomination`, and `bonus`. + * @param {number} [increase=0] Amount to increase the die count. + * @returns {string} + * @protected + */ + _automaticFormula(increase=0) { + let formula; + const number = (this.number ?? 0) + increase; + if ( number && this.denomination ) formula = `${number}d${this.denomination}`; + if ( this.bonus ) formula = formula ? `${formula} + ${this.bonus}` : this.bonus; + return formula ?? ""; + } + + /* -------------------------------------------- */ + + /** + * Scale the damage by a number of steps using its configured scaling configuration. + * @param {number} increase Number of steps above base damage to scaling. + * @returns {string} + */ + scaledFormula(increase) { + switch ( this.scaling.mode ) { + case "whole": break; + case "half": increase = Math.floor(increase * .5); break; + default: increase = 0; break; + } + if ( !increase ) return this.formula; + let formula; + + // If dice count scaling, increase the count on the first die rolled + const dieIncrease = (this.scaling.number ?? 0) * increase; + if ( this.custom.enabled ) { + formula = this.custom.formula; + formula = formula.replace(/^(\d)+d/, (match, number) => `${Number(number) + dieIncrease}d`); + } else { + formula = this._automaticFormula(dieIncrease); + } + + // If custom scaling included, modify to match increase and append for formula + if ( this.scaling.formula ) { + let roll = new Roll(this.scaling.formula); + roll = roll.alter(increase, 0, { multiplyNumeric: true }); + formula = formula ? `${formula} + ${roll.formula}` : roll.formula; + } + + return formula; + } +} diff --git a/module/documents/activity/_module.mjs b/module/documents/activity/_module.mjs index 980d74fe86..053e5c4c34 100644 --- a/module/documents/activity/_module.mjs +++ b/module/documents/activity/_module.mjs @@ -1,2 +1,4 @@ export {default as ActivityMixin} from "./mixin.mjs"; + +export {default as AttackActivity} from "./attack.mjs"; export {default as UtilityActivity} from "./utility.mjs"; diff --git a/module/documents/activity/attack.mjs b/module/documents/activity/attack.mjs new file mode 100644 index 0000000000..1e2381a370 --- /dev/null +++ b/module/documents/activity/attack.mjs @@ -0,0 +1,27 @@ +import AttackSheet from "../../applications/activity/attack-sheet.mjs"; +import AttackActivityData from "../../data/activity/attack-data.mjs"; +import ActivityMixin from "./mixin.mjs"; + +/** + * Activity for making attacks and rolling damage. + */ +export default class AttackActivity extends ActivityMixin(AttackActivityData) { + /* -------------------------------------------- */ + /* Model Configuration */ + /* -------------------------------------------- */ + + /** @inheritDoc */ + static LOCALIZATION_PREFIXES = [...super.LOCALIZATION_PREFIXES, "DND5E.ATTACK"]; + + /* -------------------------------------------- */ + + /** @inheritDoc */ + static metadata = Object.freeze( + foundry.utils.mergeObject(super.metadata, { + type: "attack", + img: "systems/dnd5e/icons/svg/activity/attack.svg", + title: "DND5E.ATTACK.Title.one", + sheetClass: AttackSheet + }, { inplace: false }) + ); +} diff --git a/module/documents/activity/mixin.mjs b/module/documents/activity/mixin.mjs index 623af7b885..235f05bbf9 100644 --- a/module/documents/activity/mixin.mjs +++ b/module/documents/activity/mixin.mjs @@ -45,6 +45,16 @@ export default Base => class extends PseudoDocumentMixin(Base) { /* -------------------------------------------- */ + /** + * Can this activity's damage be scaled? + * @type {boolean} + */ + get canScaleDamage() { + return this.consumption.scaling.allowed || this.isSpell; + } + + /* -------------------------------------------- */ + /** * Is this activity on a spell? * @type {boolean} diff --git a/module/documents/activity/utility.mjs b/module/documents/activity/utility.mjs index 771c51e58b..e3a49ebb72 100644 --- a/module/documents/activity/utility.mjs +++ b/module/documents/activity/utility.mjs @@ -2,13 +2,25 @@ import ActivitySheet from "../../applications/activity/activity-sheet.mjs"; import BaseActivityData from "../../data/activity/base-activity.mjs"; import ActivityMixin from "./mixin.mjs"; +/** + * Generic activity for applying effects and rolling an arbitrary die. + */ export default class UtilityActivity extends ActivityMixin(BaseActivityData) { + /* -------------------------------------------- */ + /* Model Configuration */ + /* -------------------------------------------- */ + + /** @inheritDoc */ + static LOCALIZATION_PREFIXES = [...super.LOCALIZATION_PREFIXES, "DND5E.UTILITY"]; + + /* -------------------------------------------- */ + /** @inheritDoc */ static metadata = Object.freeze( foundry.utils.mergeObject(super.metadata, { type: "utility", img: "systems/dnd5e/icons/svg/activity/utility.svg", - title: "DND5E.ACTIVITY.Utility.Title", + title: "DND5E.UTILITY.Title", sheetClass: ActivitySheet }, { inplace: false }) ); diff --git a/templates/activity/attack-effect.hbs b/templates/activity/attack-effect.hbs new file mode 100644 index 0000000000..0b5ea8f1a9 --- /dev/null +++ b/templates/activity/attack-effect.hbs @@ -0,0 +1,5 @@ +
+ {{> "systems/dnd5e/templates/activity/parts/attack-details.hbs" }} + {{> "systems/dnd5e/templates/activity/parts/attack-damage.hbs" }} + {{> "systems/dnd5e/templates/activity/parts/activity-effects.hbs" }} +
diff --git a/templates/activity/attack-identity.hbs b/templates/activity/attack-identity.hbs new file mode 100644 index 0000000000..05c8fe9b79 --- /dev/null +++ b/templates/activity/attack-identity.hbs @@ -0,0 +1,4 @@ +
+ {{> "systems/dnd5e/templates/activity/parts/activity-identity.hbs" }} + {{> "systems/dnd5e/templates/activity/parts/attack-identity.hbs" }} +
diff --git a/templates/activity/identity.hbs b/templates/activity/identity.hbs index bec372f0e5..85fad8f196 100644 --- a/templates/activity/identity.hbs +++ b/templates/activity/identity.hbs @@ -1,4 +1,3 @@
- {{ formField fields.name value=source.name placeholder=placeholder.name }} - {{ formField fields.img value=source.img placeholder=placeholder.img }} + {{> "systems/dnd5e/templates/activity/parts/activity-identity.hbs" }}
diff --git a/templates/activity/parts/activity-identity.hbs b/templates/activity/parts/activity-identity.hbs new file mode 100644 index 0000000000..0cf9ee91ef --- /dev/null +++ b/templates/activity/parts/activity-identity.hbs @@ -0,0 +1,5 @@ +
+ {{ localize "DND5E.ACTIVITY.Title.one" }} + {{ formField fields.name value=source.name placeholder=placeholder.name }} + {{ formField fields.img value=source.img placeholder=placeholder.img }} +
diff --git a/templates/activity/parts/attack-damage.hbs b/templates/activity/parts/attack-damage.hbs new file mode 100644 index 0000000000..1f6eb43b06 --- /dev/null +++ b/templates/activity/parts/attack-damage.hbs @@ -0,0 +1,7 @@ +
+ {{ localize "DND5E.ATTACK.FIELDS.damage.label" }} + {{#if hasBaseDamage}} + {{ formField fields.damage.fields.includeBase value=source.damage.includeBase }} + {{/if}} + {{> "systems/dnd5e/templates/activity/parts/damage-parts.hbs"}} +
diff --git a/templates/activity/parts/attack-details.hbs b/templates/activity/parts/attack-details.hbs new file mode 100644 index 0000000000..9c051fd049 --- /dev/null +++ b/templates/activity/parts/attack-details.hbs @@ -0,0 +1,8 @@ +
+ {{ localize "DND5E.ATTACK.FIELDS.attack.label" }} + {{ formField fields.ability value=source.ability options=abilityOptions }} + {{#with fields.attack.fields as |fields|}} + {{ formField fields.bonus value=../source.attack.bonus }} + {{ formField fields.flat value=../source.attack.flat }} + {{/with}} +
diff --git a/templates/activity/parts/attack-identity.hbs b/templates/activity/parts/attack-identity.hbs new file mode 100644 index 0000000000..11a3c55f7b --- /dev/null +++ b/templates/activity/parts/attack-identity.hbs @@ -0,0 +1,8 @@ +
+ {{ localize "DND5E.ATTACK.Title.one" }} + {{#with fields.attack.fields.type.fields as |fields|}} + {{ formField fields.value value=@root.source.attack.type.value options=@root.attackTypeOptions }} + {{ formField fields.classification value=@root.source.attack.type.classification + options=@root.attackClassificationOptions }} + {{/with}} +
diff --git a/templates/activity/parts/damage-parts.hbs b/templates/activity/parts/damage-parts.hbs new file mode 100644 index 0000000000..b43413312a --- /dev/null +++ b/templates/activity/parts/damage-parts.hbs @@ -0,0 +1,50 @@ + + +