Skip to content

Commit

Permalink
Merge pull request #4901 from foundryvtt/natural-weapons
Browse files Browse the repository at this point in the history
Allow natural weapons to make ranged attacks without "thrown"
  • Loading branch information
arbron authored Jan 2, 2025
2 parents 33ade66 + 1366688 commit 7ff3045
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 21 deletions.
1 change: 1 addition & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@
"Label": "Attack Mode",
"Offhand": "Offhand",
"OneHanded": "One-Handed",
"Ranged": "Ranged",
"Thrown": "Thrown",
"ThrownOffhand": "Offhand Throw",
"TwoHanded": "Two-Handed"
Expand Down
6 changes: 4 additions & 2 deletions module/applications/activity/attack-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ export default class AttackSheet extends ActivitySheet {

context.attackTypeOptions = Object.entries(CONFIG.DND5E.attackTypes)
.map(([value, config]) => ({ value, label: config.label }));
if ( this.item.system.attackType ) context.attackTypeOptions.unshift({
if ( this.item.system.validAttackTypes?.size ) context.attackTypeOptions.unshift({
value: "",
label: game.i18n.format("DND5E.DefaultSpecific", {
default: CONFIG.DND5E.attackTypes[this.item.system.attackType].label.toLowerCase()
default: game.i18n.getListFormatter({ type: "disjunction" }).format(
Array.from(this.item.system.validAttackTypes).map(t => CONFIG.DND5E.attackTypes[t].label.toLowerCase())
)
})
});

Expand Down
3 changes: 3 additions & 0 deletions module/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2833,6 +2833,9 @@ DND5E.attackModes = Object.seal({
offhand: {
label: "DND5E.ATTACK.Mode.Offhand"
},
ranged: {
label: "DND5E.ATTACK.Mode.Ranged"
},
thrown: {
label: "DND5E.ATTACK.Mode.Thrown"
},
Expand Down
19 changes: 4 additions & 15 deletions module/data/activity/attack-data.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,7 @@ export default class AttackActivityData extends BaseActivityData {
get validAttackTypes() {
const sourceType = this._source.attack.type.value;
if ( sourceType ) return new Set([sourceType]);
if ( this.item.type !== "weapon" ) return new Set();

const types = new Set();
const attackType = this.attack.type.value || this.item.system.attackType;
if ( attackType === "melee" ) types.add("melee");
if ( (attackType === "ranged") || ((this.item.system.attackType === "melee")
&& this.item.system.properties.has("thr")) ) types.add("ranged");
return types;
return this.item.system.validAttackTypes ?? new Set();
}

/* -------------------------------------------- */
Expand Down Expand Up @@ -236,8 +229,7 @@ export default class AttackActivityData extends BaseActivityData {
const key = attackMode.split("-").map(s => s.capitalize()).join("");
attackModeLabel = game.i18n.localize(`DND5E.ATTACK.Mode.${key}`);
}
let actionType = this.actionType;
if ( (actionType === "mwak") && (attackMode?.startsWith("thrown")) ) actionType = "rwak";
const actionType = this.getActionType(attackMode);
let actionTypeLabel = game.i18n.localize(`DND5E.Action${actionType.toUpperCase()}`);
const isLegacy = game.settings.get("dnd5e", "rulesVersion") === "legacy";
const isUnarmed = this.attack.type.classification === "unarmed";
Expand Down Expand Up @@ -267,9 +259,6 @@ export default class AttackActivityData extends BaseActivityData {
const rollData = this.getRollData();
if ( this.attack.flat ) return CONFIG.Dice.BasicRoll.constructParts({ toHit: this.attack.bonus }, rollData);

let actionType = this.actionType;
if ( (actionType === "mwak") && attackMode?.startsWith("thrown") ) actionType = "rwak";

const weapon = this.item.system;
const ammo = this.actor?.items.get(ammunition)?.system;
const { parts, data } = CONFIG.Dice.BasicRoll.constructParts({
Expand All @@ -278,7 +267,7 @@ export default class AttackActivityData extends BaseActivityData {
bonus: this.attack.bonus,
weaponMagic: weapon.magicAvailable ? weapon.magicalBonus : null,
ammoMagic: ammo?.magicAvailable ? ammo.magicalBonus : null,
actorBonus: this.actor?.system.bonuses?.[actionType]?.attack,
actorBonus: this.actor?.system.bonuses?.[this.getActionType(attackMode)]?.attack,
situational
}, rollData);

Expand Down Expand Up @@ -420,7 +409,7 @@ export default class AttackActivityData extends BaseActivityData {
}

const criticalBonusDice = this.actor?.getFlag("dnd5e", "meleeCriticalDamageDice") ?? 0;
if ( (this.actionType === "mwak") && (parseInt(criticalBonusDice) !== 0) ) {
if ( (this.getActionType(rollConfig.attackMode) === "mwak") && (parseInt(criticalBonusDice) !== 0) ) {
foundry.utils.setProperty(roll, "options.critical.bonusDice", criticalBonusDice);
}

Expand Down
16 changes: 14 additions & 2 deletions module/data/activity/base-activity.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,19 @@ export default class BaseActivityData extends foundry.abstract.DataModel {
/* Helpers */
/* -------------------------------------------- */

/**
* Retrieve the action type reflecting changes based on the provided attack mode.
* @param {string} [attackMode=""]
* @returns {string}
*/
getActionType(attackMode="") {
let actionType = this.actionType;
if ( (actionType === "mwak") && (attackMode?.startsWith("thrown") || (attackMode === "ranged")) ) return "rwak";
return actionType;
}

/* -------------------------------------------- */

/**
* Get the roll parts used to create the damage rolls.
* @param {Partial<DamageRollProcessConfiguration>} [config={}]
Expand Down Expand Up @@ -683,8 +696,7 @@ export default class BaseActivityData extends foundry.abstract.DataModel {
const data = { ...rollData };

if ( index === 0 ) {
let actionType = this.actionType;
if ( (actionType === "mwak") && rollConfig.attackMode?.startsWith("thrown") ) actionType = "rwak";
const actionType = this.getActionType(rollConfig.attackMode);
const bonus = foundry.utils.getProperty(this.actor ?? {}, `system.bonuses.${actionType}.damage`);
if ( bonus && !/^0+$/.test(bonus) ) parts.push(bonus);
}
Expand Down
23 changes: 22 additions & 1 deletion module/data/item/weapon.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ export default class WeaponData extends ItemDataModel.mixin(
* @param {object} source The candidate source data from which the model will be constructed.
*/
static #migrateReach(source) {
if ( !source.properties || !source.range?.value || !source.type?.value || source.range?.reach ) return;
if ( !source.properties || !source.range?.value || !source.type?.value
|| (source.range?.reach !== undefined) ) return;
if ( (CONFIG.DND5E.weaponTypeMap[source.type.value] !== "melee") || source.properties.includes("thr") ) return;
// Range of `0` or greater than `10` is always included, and so is range longer than `5` without reach property
if ( (source.range.value === 0) || (source.range.value > 10)
Expand Down Expand Up @@ -354,6 +355,11 @@ export default class WeaponData extends ItemDataModel.mixin(
});
}

else if ( !this.attackType && this.range.value ) {
if ( modes.length ) modes.push({ rule: true });
modes.push({ value: "ranged", label: CONFIG.DND5E.attackModes.ranged.label });
}

return modes;
}

Expand Down Expand Up @@ -513,6 +519,21 @@ export default class WeaponData extends ItemDataModel.mixin(
return Number(isProficient);
}

/* -------------------------------------------- */

/**
* Attack types that can be used with this item by default.
* @type {Set<string>}
*/
get validAttackTypes() {
const types = new Set();
const attackType = this.attackType;
if ( (attackType === "melee") || (attackType === null) ) types.add("melee");
if ( (attackType === "ranged") || this.properties.has("thr")
|| ((attackType === null) && this.range.value) ) types.add("ranged");
return types;
}

/* -------------------------------------------- */
/* Socket Event Handlers */
/* -------------------------------------------- */
Expand Down
2 changes: 1 addition & 1 deletion module/enrichers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ async function enrichAttack(config, label, options) {
config.type = "attack";
if ( label ) return createRollLink(label, config);

let displayFormula = simplifyRollFormula(config.formula);
let displayFormula = simplifyRollFormula(config.formula) || "+0";
if ( !displayFormula.startsWith("+") && !displayFormula.startsWith("-") ) displayFormula = `+${displayFormula}`;

const span = document.createElement("span");
Expand Down

0 comments on commit 7ff3045

Please sign in to comment.