rollHelpers updated for swade 5.2.0

This commit is contained in:
Mike Bloy 2026-03-08 22:38:26 -05:00
parent ea705bc61c
commit 9a0599cf5c
4 changed files with 93 additions and 31 deletions

View File

@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [4.2.2]
### Changed
- Minimum SWADE version is now 5.2.0
- Reworked Gang Up to still handle Formation Fighter along with the new system Gang Up improvements
- BREAKING: Pack Tactics needs an effect added setting `system.stats.gangUpDamage` to `true`
### Fixed
- Gang up calculation was double-counting the attacker if the attacker had formation fighter.
## [4.2.1]
### Added

View File

@ -9,7 +9,7 @@
}
],
"url": "https://git.bloy.org/foundryvtt/swade-mb-helpers",
"version": "4.2.1",
"version": "4.2.2",
"compatibility": {
"minimum": "13",
"verified": "13"
@ -111,8 +111,8 @@
"type": "system",
"manifest": "https://gitlab.com/api/v4/projects/16269883/packages/generic/swade/latest/system.json",
"compatibility": {
"minimum": "5.1.0",
"verified": "5.1.0"
"minimum": "5.2.0",
"verified": "5.2.0"
}
}
],

View File

@ -21,9 +21,9 @@ function getEdgeToEdgeDistance(tokenA, tokenB) {
return distance - combinedRadii * conversionFactor;
}
function calcGangupBonus(sourceToken, targetToken) {
const targetActor = targetToken.actor;
function getGangupModifiers(sourceToken, targetToken, label = null) {
const sourceActor = sourceToken.actor;
const targetActor = targetToken.actor;
const scene = targetToken.parent;
const ignoreStatuses = ['defeated', 'incapacitated', 'stunned'];
const attackerAllies = scene.tokens.filter((t) => {
@ -34,9 +34,6 @@ function calcGangupBonus(sourceToken, targetToken) {
let formationFighterBonus = attackerAllies.filter((t) =>
t.actor.getSingleItemBySwid('formation-fighter', 'edge'),
).length;
if (sourceActor.getSingleItemBySwid('formation-fighter', 'edge')) {
formationFighterBonus++;
}
let numAttackerAllies = attackerAllies.length;
const numDefenderAllies = scene.tokens.filter((t) => {
if (t.disposition !== targetToken.disposition) return false;
@ -44,16 +41,49 @@ function calcGangupBonus(sourceToken, targetToken) {
if (getEdgeToEdgeDistance(targetToken, t) >= 1) return false;
return getEdgeToEdgeDistance(sourceToken, t) < 1;
}).length;
if (numAttackerAllies > 0) {
if (numAttackerAllies > 1) {
numAttackerAllies += formationFighterBonus;
}
let gangUpBonus = Math.min(4, numAttackerAllies - numDefenderAllies);
if (targetActor?.getSingleItemBySwid('improved-block', 'edge')) {
gangUpBonus -= 2;
} else if (targetActor?.getSingleItemBySwid('block', 'edge')) {
gangUpBonus -= 1;
const attackerGlobalMods = foundry.utils.getProperty(sourceActor, 'system.stats.globalMods');
if (attackerGlobalMods?.gangUp && Array.isArray(attackerGlobalMods.gangUp)) {
attackerGlobalMods.gangUp.forEach((m) => (gangUpBonus += Number(m.value)));
}
return gangUpBonus;
let targetGangUpMod = 0;
const targetGangUpLabels = [];
const targetGlobalMods = targetActor ? foundry.utils.getProperty(targetActor, 'system.stats.globalMods') : {};
if (targetGlobalMods?.gangUp && Array.isArray(targetGlobalMods.gangUp)) {
for (const m of targetGlobalMods.gangUp) {
targetGangUpMod += Number(m.value);
targetGangUpLabels.push(m.label);
}
}
const improvedBlock = targetActor?.getSingleItemBySwid('improved-block', 'edge');
if (improvedBlock) {
targetGangUpMod -= 2;
targetGangUpLabels.push(improvedBlock.name);
} else {
const block = targetActor?.getSingleItemBySwid('block', 'edge');
if (block) {
targetGangUpMod -= 1;
targetGangUpLabels.push(block.name);
}
}
const mods = [];
if (gangUpBonus > 0) {
mods.push({
label: label || game.i18n.localize('SWADE.GangUp'),
value: gangUpBonus,
});
if (targetGangUpMod !== 0) {
const value = Math.max(targetGangUpMod, -gangUpBonus);
mods.push({
label: targetGangUpLabels.join(' + ') || 'SWADE.GlobalMod.TargetGangup',
value,
});
}
}
return mods;
}
export async function preAttackRollModifiers(
@ -121,16 +151,21 @@ export async function preAttackRollModifiers(
// Gang Up correction
if (isMeleeAttack) {
const gangUpBonus = calcGangupBonus(sourceToken, targetToken);
const gangUpModIndex = additionalMods.findIndex((m) => m.label === 'SWADE.GangUp');
if (gangUpModIndex > -1) {
additionalMods.splice(gangUpModIndex, 1);
}
if (gangUpBonus && gangUpBonus > 0) {
additionalMods.push({
label: 'SWADE.GangUp',
value: gangUpBonus,
});
const gangUpMods = getGangupModifiers(sourceToken, targetToken);
const titles = gangUpMods.map((mod) => mod.label);
titles.push('SWADE.GangUp');
titles.push('SWADE.GlobalMod.TargetGangUp');
removeDuplicateMods(additionalMods, titles);
additionalMods.push(...gangUpMods);
}
}
function removeDuplicateMods(additionalMods, replacementModTitles) {
const translatedTitles = replacementModTitles.map((t) => game.i18n.localize(t));
for (const title of [...replacementModTitles, ...translatedTitles]) {
const modIndex = additionalMods.findIndex((m) => m.label === title);
if (modIndex > -1) {
additionalMods.splice(modIndex, 1);
}
}
}
@ -172,11 +207,26 @@ export async function preDamageRollModifiers(actor, item, roll, modifiers, optio
}),
);
}
if (token.actor.getSingleItemBySwid('pack-tactics', 'ability')) {
const gangupBonus = calcGangupBonus(token, target);
if (gangupBonus > 0) {
modifiers.push({ label: 'Gang Up (Pack Tactics)', value: gangupBonus, ignore: false });
if (
item.isMeleeWeapon &&
'stats' in token.actor.system &&
token.actor.system.stats.gangUpDamage &&
target &&
token
) {
const effect = token.actor.effects.find(
(e) => !e.disabled && e.changes.some((c) => c.key === 'system.stats.gangUpDamage'),
);
const gangUpMods = getGangupModifiers(token, target, effect?.name);
const titles = gangUpMods.map((mod) => mod.label);
titles.push('SWADE.GangUp');
titles.push('SWADE.GlobalMod.TargetGangUp');
removeDuplicateMods(modifiers, titles);
const gangUpModIndex = modifiers.findIndex((m) => m.label === 'SWADE.GangUp');
if (gangUpModIndex > -1) {
modifiers.splice(gangUpModIndex, 1);
}
modifiers.push(...gangUpMods);
}
}
}

File diff suppressed because one or more lines are too long