Compare commits
No commits in common. "main" and "v4.2.0" have entirely different histories.
26
CHANGELOG.md
26
CHANGELOG.md
@ -5,32 +5,6 @@ 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]
|
||||
|
||||
### Added
|
||||
|
||||
- Added a setting for a Power Actors folder along side the compendium folder.
|
||||
Powers that require non-transient actors (eg: PC companions or familiars) can
|
||||
use a duplicate folder structure under the Power Actors folder to define an
|
||||
actor that will stick around.
|
||||
|
||||
### 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.
|
||||
- Fixed summons and deletions of linked tokens.
|
||||
|
||||
## [4.2.1]
|
||||
|
||||
### Added
|
||||
|
||||
- Summoned creatures will now automatically follow their summoners in the combat tracker.
|
||||
|
||||
## [4.2.0]
|
||||
|
||||
### Added
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"mbhelpers.settings.powerActorsCompendiumName": "Power Actors Compendium",
|
||||
"mbhelpers.settings.powerActorsCompendiumHint": "Identifier of a compendium that holds all the actor helpers for powers. See the documentation for details on the structure of this compendium.",
|
||||
"mbhelpers.settings.powerActorsFolderName": "Power Actors Library Folder",
|
||||
"mbhelpers.settings.powerActorsFolderHint": "Name of a Sidebar actor folder that holds all the actor helpers for powers. See the documentation for details on the structure of this folder. Note that the compendium is preferred, and the folder should be used for things like named animal companions or familiars."
|
||||
"mbhelpers.settings.powersJournalName": "Powers Journal",
|
||||
"mbhelpers.settings.powersJournalHint": "UUID of a helper journal for actor-based powers (summonables and morphables)."
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
}
|
||||
],
|
||||
"url": "https://git.bloy.org/foundryvtt/swade-mb-helpers",
|
||||
"version": "4.2.2",
|
||||
"version": "4.2.0",
|
||||
"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.2.0",
|
||||
"verified": "5.2.0"
|
||||
"minimum": "5.1.0",
|
||||
"verified": "5.1.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@ -2,7 +2,6 @@ export const moduleName = 'swade-mb-helpers';
|
||||
|
||||
export const settingKeys = {
|
||||
powerActorsCompendium: 'powerActorsCompendium',
|
||||
powerActorsFolder: 'powerActorsFolder',
|
||||
};
|
||||
|
||||
export function log(...args) {
|
||||
|
||||
@ -808,42 +808,10 @@ export class ActorFolderEffect extends PowerEffect {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
static get actorLibraryFolderName() {
|
||||
const folderName = moduleHelpers.getSetting(settingKeys.powerActorsFolder);
|
||||
return folderName;
|
||||
}
|
||||
|
||||
get actorFolder() {
|
||||
return `${this.actorFolderBase}/${this.name}`;
|
||||
}
|
||||
|
||||
static getLibraryFolderByPath(path) {
|
||||
const baseFolder = ActorFolderEffect.actorLibraryFolderName;
|
||||
if (!baseFolder) {
|
||||
return undefined;
|
||||
}
|
||||
const sep = path[0] == '/' ? '' : '/';
|
||||
const fullPath = `${baseFolder}${sep}${path}`;
|
||||
const names = fullPath.split('/');
|
||||
if (names[0] === '') {
|
||||
names.shift();
|
||||
}
|
||||
let name = names.shift();
|
||||
let folder = game.folders.filter((f) => f.type === 'Actor' && !f.folder).find((f) => f.name === name);
|
||||
if (!folder) {
|
||||
return undefined;
|
||||
}
|
||||
while (names.length > 0) {
|
||||
name = names.shift();
|
||||
folder = folder.children.find((c) => c.folder.name === name);
|
||||
if (!folder) {
|
||||
return undefined;
|
||||
}
|
||||
folder = folder.folder;
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
static getPackFolderByPath(path) {
|
||||
const names = path.split('/');
|
||||
if (names[0] === '') {
|
||||
@ -870,7 +838,7 @@ export class ActorFolderEffect extends PowerEffect {
|
||||
return folder;
|
||||
}
|
||||
|
||||
static getActorsInFolder(inFolder) {
|
||||
static getPackActorsInFolder(inFolder) {
|
||||
const prefixStack = [''];
|
||||
const actors = {};
|
||||
const folderStack = [inFolder];
|
||||
@ -890,8 +858,7 @@ export class ActorFolderEffect extends PowerEffect {
|
||||
}
|
||||
|
||||
prepFolders() {
|
||||
const packFolders = [];
|
||||
const libFolders = [];
|
||||
const folders = [];
|
||||
const folderNames = [
|
||||
this.actorFolder,
|
||||
`${this.actorFolder} - Default`,
|
||||
@ -902,31 +869,23 @@ export class ActorFolderEffect extends PowerEffect {
|
||||
`${this.actorFolder}/${this.source.actor.name}`,
|
||||
];
|
||||
for (const folderName of folderNames) {
|
||||
const packFolder = ActorFolderEffect.getPackFolderByPath(folderName);
|
||||
if (packFolder) {
|
||||
const folder = ActorFolderEffect.getPackFolderByPath(folderName);
|
||||
if (folder) {
|
||||
log(`Found actor folder ${folderName}`);
|
||||
packFolders.push(packFolder);
|
||||
}
|
||||
const libFolder = ActorFolderEffect.getLibraryFolderByPath(folderName);
|
||||
if (libFolder) {
|
||||
log(`Found library actor folder ${folderName}`);
|
||||
libFolders.push(libFolder);
|
||||
folders.push(folder);
|
||||
}
|
||||
}
|
||||
if (packFolders.length > 1) {
|
||||
packFolders.shift();
|
||||
if (folders.length > 1) {
|
||||
folders.shift();
|
||||
}
|
||||
if (libFolders.length > 1) {
|
||||
libFolders.shift();
|
||||
}
|
||||
return [...packFolders, ...libFolders];
|
||||
return folders;
|
||||
}
|
||||
|
||||
prepActors() {
|
||||
const folders = this.prepFolders();
|
||||
const actors = {};
|
||||
for (const folder of folders) {
|
||||
const folderActors = ActorFolderEffect.getActorsInFolder(folder);
|
||||
const folderActors = ActorFolderEffect.getPackActorsInFolder(folder);
|
||||
for (const key in folderActors) {
|
||||
actors[key] = folderActors[key];
|
||||
}
|
||||
@ -1040,11 +999,7 @@ export class ActorFolderEffect extends PowerEffect {
|
||||
async parseValues() {
|
||||
await super.parseValues();
|
||||
this.data.maintId = foundry.utils.randomID();
|
||||
if (this.data.actorId.startsWith('Compendium')) {
|
||||
this.targetActor = await game.tcal.importTransientActor(this.data.actorId, { preferExisting: true });
|
||||
} else {
|
||||
this.targetActor = await foundry.utils.fromUuid(this.data.actorId);
|
||||
}
|
||||
this.targetActor = await game.tcal.importTransientActor(this.data.actorId, { preferExisting: true });
|
||||
this.targetTokenDoc = await this.targetActor.getTokenDocument();
|
||||
const perm = CONST?.DOCUMENT_PERMISSION_LEVELS?.OWNER ?? CONST?.DOCUMENT_OWNERSHIP_LEVELS?.OWNER;
|
||||
const sourceUpdates = {
|
||||
@ -1095,13 +1050,6 @@ export class ActorFolderEffect extends PowerEffect {
|
||||
return doc;
|
||||
}
|
||||
|
||||
async primaryDocForTarget(doc, target) {
|
||||
const newDoc = await super.primaryDocForTarget(doc, target);
|
||||
newDoc.flags[moduleName].spawnedTempTokenSceneId = target.parent.id;
|
||||
newDoc.flags[moduleName].spawnedTempTokenId = target.id;
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
async sideEffects() {
|
||||
if (this.data.fatigue) {
|
||||
for (const target of this.data.spawned) {
|
||||
|
||||
@ -266,22 +266,8 @@ export async function powerEffectManagementHook(effect, data, userId) {
|
||||
}
|
||||
const isSpawned = effect.getFlag(moduleName, 'spawnedTempToken') ?? false;
|
||||
if (isSpawned) {
|
||||
let token = effect.parent?.token;
|
||||
let sceneId;
|
||||
let tokenId;
|
||||
if (token) {
|
||||
sceneId = token.parent.id;
|
||||
tokenId = token.id;
|
||||
} else {
|
||||
sceneId = effect.getFlag(moduleName, 'spawnedTempTokenSceneId');
|
||||
tokenId = effect.getFlag(moduleName, 'spawnedTempTokenId');
|
||||
}
|
||||
function _deleteSpawnedToken(sceneId, tokenId) {
|
||||
moduleHelpers.socket.executeAsGM(deleteToken, sceneId, tokenId);
|
||||
}
|
||||
if (sceneId && tokenId) {
|
||||
setTimeout(_deleteSpawnedToken, 500, sceneId, tokenId);
|
||||
}
|
||||
const token = effect.parent.token;
|
||||
await moduleHelpers.socket.executeAsGM(deleteToken, token.parent.id, token.id);
|
||||
}
|
||||
const targetIds = effect.getFlag(moduleName, 'targetIds') || [];
|
||||
for (const targetId of targetIds) {
|
||||
|
||||
@ -542,12 +542,6 @@ export class SummonEidolonEffect extends BaseSummonEffect {
|
||||
get modifiers() {
|
||||
return super.modifiers.filter((m) => m.id === 'actorId');
|
||||
}
|
||||
|
||||
get spawnUpdates() {
|
||||
const updates = super.spawnUpdates;
|
||||
delete updates.token.actorLink;
|
||||
return updates;
|
||||
}
|
||||
}
|
||||
|
||||
export class SummonCompanionEffect extends SummonEidolonEffect {
|
||||
|
||||
@ -18,19 +18,6 @@ export class BaseSummonEffect extends ActorFolderEffect {
|
||||
return 0;
|
||||
}
|
||||
|
||||
get spawnUpdates() {
|
||||
const updates = super.spawnUpdates;
|
||||
const actorUpdates = {
|
||||
system: {
|
||||
initiative: {
|
||||
follow: this.source.name,
|
||||
},
|
||||
},
|
||||
};
|
||||
foundry.utils.mergeObject(updates.actor, actorUpdates);
|
||||
return updates;
|
||||
}
|
||||
|
||||
async parseValuesPre() {}
|
||||
|
||||
async parseValuesMid() {}
|
||||
|
||||
@ -21,9 +21,9 @@ function getEdgeToEdgeDistance(tokenA, tokenB) {
|
||||
return distance - combinedRadii * conversionFactor;
|
||||
}
|
||||
|
||||
function getGangupModifiers(sourceToken, targetToken, label = null) {
|
||||
const sourceActor = sourceToken.actor;
|
||||
function calcGangupBonus(sourceToken, targetToken) {
|
||||
const targetActor = targetToken.actor;
|
||||
const sourceActor = sourceToken.actor;
|
||||
const scene = targetToken.parent;
|
||||
const ignoreStatuses = ['defeated', 'incapacitated', 'stunned'];
|
||||
const attackerAllies = scene.tokens.filter((t) => {
|
||||
@ -34,6 +34,9 @@ function getGangupModifiers(sourceToken, targetToken, label = null) {
|
||||
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;
|
||||
@ -41,49 +44,16 @@ function getGangupModifiers(sourceToken, targetToken, label = null) {
|
||||
if (getEdgeToEdgeDistance(targetToken, t) >= 1) return false;
|
||||
return getEdgeToEdgeDistance(sourceToken, t) < 1;
|
||||
}).length;
|
||||
if (numAttackerAllies > 1) {
|
||||
if (numAttackerAllies > 0) {
|
||||
numAttackerAllies += formationFighterBonus;
|
||||
}
|
||||
let gangUpBonus = Math.min(4, numAttackerAllies - numDefenderAllies);
|
||||
const attackerGlobalMods = foundry.utils.getProperty(sourceActor, 'system.stats.globalMods');
|
||||
if (attackerGlobalMods?.gangUp && Array.isArray(attackerGlobalMods.gangUp)) {
|
||||
attackerGlobalMods.gangUp.forEach((m) => (gangUpBonus += Number(m.value)));
|
||||
if (targetActor?.getSingleItemBySwid('improved-block', 'edge')) {
|
||||
gangUpBonus -= 2;
|
||||
} else if (targetActor?.getSingleItemBySwid('block', 'edge')) {
|
||||
gangUpBonus -= 1;
|
||||
}
|
||||
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;
|
||||
return gangUpBonus;
|
||||
}
|
||||
|
||||
export async function preAttackRollModifiers(
|
||||
@ -151,21 +121,16 @@ export async function preAttackRollModifiers(
|
||||
|
||||
// Gang Up correction
|
||||
if (isMeleeAttack) {
|
||||
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);
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -207,26 +172,11 @@ export async function preDamageRollModifiers(actor, item, roll, modifiers, optio
|
||||
}),
|
||||
);
|
||||
}
|
||||
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);
|
||||
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 });
|
||||
}
|
||||
modifiers.push(...gangUpMods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,21 +17,4 @@ export function registerSettings() {
|
||||
requiresReload: false,
|
||||
type: String,
|
||||
});
|
||||
|
||||
const folders = game.folders.filter((f) => f.type === 'Actor' && !f.folder);
|
||||
const folderChoices = {};
|
||||
for (const folder of folders) {
|
||||
folderChoices[folder.name] = folder.name;
|
||||
}
|
||||
|
||||
moduleHelpers.registerSetting(settingKeys.powerActorsFolder, {
|
||||
name: 'mbhelpers.settings.powerActorsFolderName',
|
||||
hint: 'mbhelpers.settings.powerActorsFolderHint',
|
||||
scope: 'world',
|
||||
config: true,
|
||||
choices: folderChoices,
|
||||
requiresReload: false,
|
||||
default: 'Power Actors',
|
||||
type: String,
|
||||
});
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user