mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-13 12:11:07 +01:00
Merge branch 'main' into feature/death-moves
This commit is contained in:
commit
d6c3cfb83a
39 changed files with 3629 additions and 78 deletions
|
|
@ -21,6 +21,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
if (!this.actor) return context;
|
||||
|
||||
context.partyOnCanvas =
|
||||
this.actor.type === 'party' &&
|
||||
this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0);
|
||||
|
|
@ -58,14 +60,33 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
}
|
||||
|
||||
static async #onToggleCombat() {
|
||||
const tokensWithoutActors = canvas.tokens.controlled.filter(t => !t.actor);
|
||||
const warning =
|
||||
tokensWithoutActors.length === 1
|
||||
? game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorMissing', {
|
||||
name: tokensWithoutActors[0].name
|
||||
})
|
||||
: game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorsMissing', {
|
||||
names: tokensWithoutActors.map(x => x.name).join(', ')
|
||||
});
|
||||
|
||||
const tokens = canvas.tokens.controlled
|
||||
.filter(t => !t.actor || !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
|
||||
.filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
|
||||
.map(t => t.document);
|
||||
if (!this.object.controlled) tokens.push(this.document);
|
||||
if (!this.object.controlled && this.document.actor) tokens.push(this.document);
|
||||
|
||||
try {
|
||||
if (this.document.inCombat) await TokenDocument.implementation.deleteCombatants(tokens);
|
||||
else await TokenDocument.implementation.createCombatants(tokens);
|
||||
if (this.document.inCombat) {
|
||||
const tokensInCombat = tokens.filter(t => t.inCombat);
|
||||
await TokenDocument.implementation.deleteCombatants([...tokensInCombat, ...tokensWithoutActors]);
|
||||
} else {
|
||||
if (tokensWithoutActors.length) {
|
||||
ui.notifications.warn(warning);
|
||||
}
|
||||
|
||||
const tokensOutOfCombat = tokens.filter(t => !t.inCombat);
|
||||
await TokenDocument.implementation.createCombatants(tokensOutOfCombat);
|
||||
}
|
||||
} catch (err) {
|
||||
ui.notifications.warn(err.message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
||||
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||
useDowntime: this.useDowntime
|
||||
useDowntime: this.useDowntime,
|
||||
viewParty: CharacterSheet.#viewParty,
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
|
|
@ -892,6 +893,41 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(item);
|
||||
}
|
||||
|
||||
static async #viewParty(_, target) {
|
||||
const parties = this.document.parties;
|
||||
if (parties.size <= 1) {
|
||||
parties.first()?.sheet.render({ force: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const buttons = parties.map((p) => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.classList.add("plain");
|
||||
const img = document.createElement("img");
|
||||
img.src = p.img;
|
||||
button.append(img);
|
||||
const name = document.createElement("span");
|
||||
name.textContent = p.name;
|
||||
button.append(name);
|
||||
button.addEventListener("click", () => {
|
||||
p.sheet?.render({ force: true });
|
||||
game.tooltip.dismissLockedTooltips();
|
||||
});
|
||||
return button;
|
||||
});
|
||||
|
||||
const html = document.createElement("div");
|
||||
html.classList.add("party-list");
|
||||
html.append(...buttons);
|
||||
|
||||
game.tooltip.dismissLockedTooltips();
|
||||
game.tooltip.activate(target, {
|
||||
html,
|
||||
locked: true,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the downtime application.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
resource,
|
||||
active: index === combat.turn,
|
||||
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
|
||||
type: combatant.actor.system.type,
|
||||
type: combatant.actor?.system?.type,
|
||||
img: await this._getCombatantThumbnail(combatant)
|
||||
};
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
|
||||
if (this.viewed.turn !== toggleTurn) {
|
||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||
if (combatant.actor.type === 'character') {
|
||||
if (combatant.actor?.type === 'character') {
|
||||
await updateCountdowns(
|
||||
CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id,
|
||||
CONFIG.DH.GENERAL.countdownProgressionTypes.characterSpotlight.id
|
||||
|
|
|
|||
|
|
@ -34,6 +34,69 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
this.renderFlags.set({ refreshEffects: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance from this token to another token object.
|
||||
* This value is corrected to handle alternate token sizes and other grid types
|
||||
* according to the diagonal rules.
|
||||
*/
|
||||
distanceTo(target) {
|
||||
if (!canvas.ready) return NaN;
|
||||
if (this === target) return 0;
|
||||
|
||||
const originPoint = this.center;
|
||||
const destinationPoint = target.center;
|
||||
|
||||
// Compute for gridless. This version returns circular edge to edge + grid distance,
|
||||
// so that tokens that are touching return 5.
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||
const boundsCorrection = canvas.grid.distance / canvas.grid.size;
|
||||
const originRadius = this.bounds.width * boundsCorrection / 2;
|
||||
const targetRadius = target.bounds.width * boundsCorrection / 2;
|
||||
const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance;
|
||||
return distance - originRadius - targetRadius + canvas.grid.distance;
|
||||
}
|
||||
|
||||
// Compute what the closest grid space of each token is, then compute that distance
|
||||
const originEdge = this.#getEdgeBoundary(this.bounds, originPoint, destinationPoint);
|
||||
const targetEdge = this.#getEdgeBoundary(target.bounds, originPoint, destinationPoint);
|
||||
const adjustedOriginPoint = canvas.grid.getTopLeftPoint({
|
||||
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
|
||||
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
|
||||
});
|
||||
const adjustDestinationPoint = canvas.grid.getTopLeftPoint({
|
||||
x: targetEdge.x + Math.sign(destinationPoint.x - targetEdge.x),
|
||||
y: targetEdge.y + Math.sign(destinationPoint.y - targetEdge.y)
|
||||
});
|
||||
return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance;
|
||||
}
|
||||
|
||||
/** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */
|
||||
#getEdgeBoundary(bounds, originPoint, destinationPoint) {
|
||||
const points = [
|
||||
{ x: bounds.x, y: bounds.y },
|
||||
{ x: bounds.x + bounds.width, y: bounds.y },
|
||||
{ x: bounds.x + bounds.width, y: bounds.y + bounds.height },
|
||||
{ x: bounds.x, y: bounds.y + bounds.height }
|
||||
];
|
||||
const pairsToTest = [
|
||||
[points[0], points[1]],
|
||||
[points[1], points[2]],
|
||||
[points[2], points[3]],
|
||||
[points[3], points[0]]
|
||||
];
|
||||
for (const pair of pairsToTest) {
|
||||
const result = foundry.utils.lineSegmentIntersection(originPoint, destinationPoint, pair[0], pair[1]);
|
||||
if (result) return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Tests if the token is at least adjacent with another, with some leeway for diagonals */
|
||||
isAdjacentWith(token) {
|
||||
return this.distanceTo(token) <= (canvas.grid.distance * 1.5);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
_drawBar(number, bar, data) {
|
||||
const val = Number(data.value);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export const AdversaryBPPerEncounter = (adversaries, characters) => {
|
|||
);
|
||||
if (existingEntry) {
|
||||
existingEntry.nr += 1;
|
||||
} else {
|
||||
} else if (adversary.type) {
|
||||
acc.push({ adversary, nr: 1 });
|
||||
}
|
||||
return acc;
|
||||
|
|
|
|||
|
|
@ -435,7 +435,8 @@ export const armorFeatures = {
|
|||
{
|
||||
key: 'system.resistance.magical.reduction',
|
||||
mode: 2,
|
||||
value: '@system.armorScore'
|
||||
value: '@system.armorScore',
|
||||
priority: 21
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -709,7 +710,8 @@ export const weaponFeatures = {
|
|||
{
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '@system.armorScore'
|
||||
value: '@system.armorScore',
|
||||
priority: 21
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1324,7 +1326,8 @@ export const weaponFeatures = {
|
|||
{
|
||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||
mode: 2,
|
||||
value: '@system.traits.agility.value'
|
||||
value: '@system.traits.agility.value',
|
||||
priority: 21
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1416,9 +1419,9 @@ export const orderedWeaponFeatures = () => {
|
|||
};
|
||||
|
||||
export const featureForm = {
|
||||
passive: "DAGGERHEART.CONFIG.FeatureForm.passive",
|
||||
action: "DAGGERHEART.CONFIG.FeatureForm.action",
|
||||
reaction: "DAGGERHEART.CONFIG.FeatureForm.reaction"
|
||||
passive: 'DAGGERHEART.CONFIG.FeatureForm.passive',
|
||||
action: 'DAGGERHEART.CONFIG.FeatureForm.action',
|
||||
reaction: 'DAGGERHEART.CONFIG.FeatureForm.reaction'
|
||||
};
|
||||
|
||||
export const featureTypes = {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,17 @@
|
|||
/** -- Changes Type Priorities --
|
||||
* - Base Number -
|
||||
* Custom: 0
|
||||
* Multiply: 10
|
||||
* Add: 20
|
||||
* Downgrade: 30
|
||||
* Upgrade: 40
|
||||
* Override: 50
|
||||
*
|
||||
* - Changes Value Priorities -
|
||||
* Standard: +0
|
||||
* "Anything that uses another data model value as its value": +1 - Effects that increase traits have to be calculated first at Base priority. (EX: Raise evasion by half your agility)
|
||||
*/
|
||||
|
||||
export default class BaseEffect extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ export default class DhCombat extends foundry.abstract.TypeDataModel {
|
|||
get extendedBattleToggles() {
|
||||
const modifiers = CONFIG.DH.ENCOUNTER.BPModifiers;
|
||||
const adversaries =
|
||||
this.parent.turns?.filter(x => x.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? [];
|
||||
const characters = this.parent.turns?.filter(x => !x.isNPC) ?? [];
|
||||
this.parent.turns?.filter(x => x.actor && x.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ??
|
||||
[];
|
||||
const characters = this.parent.turns?.filter(x => x.actor && !x.isNPC) ?? [];
|
||||
|
||||
const activeAutomatic = Object.keys(modifiers).reduce((acc, categoryKey) => {
|
||||
const category = modifiers[categoryKey];
|
||||
|
|
|
|||
|
|
@ -272,12 +272,17 @@ export function ActionMixin(Base) {
|
|||
itemOrigin: this.item,
|
||||
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
||||
};
|
||||
|
||||
const speaker = cls.getSpeaker();
|
||||
const msg = {
|
||||
type: 'abilityUse',
|
||||
user: game.user.id,
|
||||
actor: { name: this.actor.name, img: this.actor.img },
|
||||
author: this.author,
|
||||
speaker: cls.getSpeaker(),
|
||||
speaker: {
|
||||
speaker,
|
||||
actor: speaker.actor ?? this.actor
|
||||
},
|
||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'),
|
||||
system: systemData,
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
|
|||
if (combat?.system?.battleToggles?.length) {
|
||||
await combat.toggleModifierEffects(
|
||||
true,
|
||||
tokens.map(x => x.actor)
|
||||
tokens.filter(x => x.actor).map(x => x.actor)
|
||||
);
|
||||
}
|
||||
super.createCombatants(tokens, combat ?? {});
|
||||
|
|
@ -95,7 +95,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
|
|||
if (combat?.system?.battleToggles?.length) {
|
||||
await combat.toggleModifierEffects(
|
||||
false,
|
||||
tokens.map(x => x.actor)
|
||||
tokens.filter(x => x.actor).map(x => x.actor)
|
||||
);
|
||||
}
|
||||
super.deleteCombatants(tokens, combat ?? {});
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
|||
const combat = game.combats.get(combatId);
|
||||
const adversaries =
|
||||
combat.turns?.filter(x => x.actor?.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? [];
|
||||
const characters = combat.turns?.filter(x => !x.isNPC) ?? [];
|
||||
const characters = combat.turns?.filter(x => !x.isNPC && x.actor) ?? [];
|
||||
|
||||
const nrCharacters = characters.length;
|
||||
const currentBP = AdversaryBPPerEncounter(adversaries, characters);
|
||||
|
|
@ -272,7 +272,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
|||
);
|
||||
|
||||
const categories = combat.combatants.reduce((acc, combatant) => {
|
||||
if (combatant.actor.type === 'adversary') {
|
||||
if (combatant.actor?.type === 'adversary') {
|
||||
const keyData = Object.keys(acc).reduce((identifiers, categoryKey) => {
|
||||
if (identifiers) return identifiers;
|
||||
const category = acc[categoryKey];
|
||||
|
|
@ -352,7 +352,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
|||
|
||||
await combat.toggleModifierEffects(
|
||||
event.target.checked,
|
||||
combat.combatants.filter(x => x.actor.type === 'adversary').map(x => x.actor),
|
||||
combat.combatants.filter(x => x.actor?.type === 'adversary').map(x => x.actor),
|
||||
category,
|
||||
grouping
|
||||
);
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}
|
|||
spellcheck='false'
|
||||
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
|
||||
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
|
||||
data-tooltip="${tagData.description || tagData.name}"
|
||||
data-tooltip="${tagData.description ? htmlToText(tagData.description) : tagData.name}"
|
||||
${this.getAttributes(tagData)}>
|
||||
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
|
||||
<div>
|
||||
|
|
@ -212,7 +212,7 @@ foundry.dice.terms.Die.prototype.selfCorrecting = function (modifier) {
|
|||
};
|
||||
|
||||
export const getDamageKey = damage => {
|
||||
return ['none', 'minor', 'major', 'severe', 'massive','any'][damage];
|
||||
return ['none', 'minor', 'major', 'severe', 'massive', 'any'][damage];
|
||||
};
|
||||
|
||||
export const getDamageLabel = damage => {
|
||||
|
|
@ -488,3 +488,10 @@ export async function getCritDamageBonus(formula) {
|
|||
const critRoll = new Roll(formula);
|
||||
return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.number, 0);
|
||||
}
|
||||
|
||||
export function htmlToText(html) {
|
||||
var tempDivElement = document.createElement('div');
|
||||
tempDivElement.innerHTML = html;
|
||||
|
||||
return tempDivElement.textContent || tempDivElement.innerText || '';
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue