Skip to content

Commit

Permalink
Merge pull request foundryvtt#3921 from foundryvtt/activity/attack
Browse files Browse the repository at this point in the history
[foundryvtt#3910] Add `AttackActivity` data and sheet
  • Loading branch information
arbron authored Aug 2, 2024
2 parents 90f5d2f + 519487f commit e59ef65
Show file tree
Hide file tree
Showing 24 changed files with 649 additions and 8 deletions.
1 change: 1 addition & 0 deletions dnd5e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

Expand Down
1 change: 1 addition & 0 deletions icons/LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions icons/svg/activity/attack.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
122 changes: 119 additions & 3 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,6 @@
"Effect": "Effect",
"Identity": "Identity",
"Time": "Time"
},
"Utility": {
"Title": "Utility"
}
},

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -2128,6 +2238,7 @@
"Sr": "Short Rest",
"SrAbbreviation": "SR"
},

"DND5E.USES": {
"FIELDS": {
"uses": {
Expand Down Expand Up @@ -2162,6 +2273,11 @@
}
}
},

"DND5E.UTILITY": {
"Title": "Utility"
},

"DND5E.Vehicle": "Vehicle",
"DND5E.VehicleActions": "Actions",
"DND5E.VehicleActionsHint": "Actions taken with full crew complement",
Expand Down
3 changes: 3 additions & 0 deletions module/applications/activity/_module.mjs
Original file line number Diff line number Diff line change
@@ -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";
5 changes: 4 additions & 1 deletion module/applications/activity/activity-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
139 changes: 139 additions & 0 deletions module/applications/activity/attack-sheet.mjs
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit e59ef65

Please sign in to comment.