mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-18 07:59:03 +01:00
Merge branch 'main' into bug/103-enrich-htmlfield-content-before-its-used-in-applications
This commit is contained in:
commit
d39d3d37b3
106 changed files with 1556 additions and 532 deletions
|
|
@ -1,5 +1,6 @@
|
|||
export * as characterCreation from './characterCreation/_module.mjs';
|
||||
export * as dialogs from './dialogs/_module.mjs';
|
||||
export * as hud from './hud/_module.mjs';
|
||||
export * as levelup from './levelup/_module.mjs';
|
||||
export * as settings from './settings/_module.mjs';
|
||||
export * as sheets from './sheets/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default class CostSelectionDialog extends HandlebarsApplicationMixin(Appl
|
|||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'damage-selection'],
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
|
||||
position: {
|
||||
width: 400,
|
||||
height: 'auto'
|
||||
|
|
|
|||
|
|
@ -11,11 +11,14 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'roll-selection',
|
||||
classes: ['daggerheart', 'views', 'damage-selection'],
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
|
||||
position: {
|
||||
width: 400,
|
||||
height: 'auto'
|
||||
},
|
||||
window: {
|
||||
icon: 'fa-solid fa-dice'
|
||||
},
|
||||
actions: {
|
||||
submitRoll: this.submitRoll
|
||||
},
|
||||
|
|
@ -34,9 +37,15 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
}
|
||||
};
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name');
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.title = this.config.title;
|
||||
context.title = this.config.title
|
||||
? this.config.title
|
||||
: game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name');
|
||||
context.extraFormula = this.config.extraFormula;
|
||||
context.formula = this.roll.constructFormula(this.config);
|
||||
return context;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
this.actor = actor;
|
||||
this.damage = damage;
|
||||
|
||||
const canApplyArmor = actor.system.armorApplicableDamageTypes[damageType];
|
||||
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
|
||||
const maxArmorMarks = canApplyArmor
|
||||
? Math.min(
|
||||
actor.system.armorScore - actor.system.armor.system.marks.value,
|
||||
|
|
@ -110,7 +110,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
? {
|
||||
value:
|
||||
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress,
|
||||
maxTotal: this.actor.system.resources.stress.maxTotal
|
||||
max: this.actor.system.resources.stress.max
|
||||
}
|
||||
: null;
|
||||
|
||||
|
|
@ -197,7 +197,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
: 0;
|
||||
const currentStress =
|
||||
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress;
|
||||
if (currentStress + stressReduction.cost > this.actor.system.resources.stress.maxTotal) {
|
||||
if (currentStress + stressReduction.cost > this.actor.system.resources.stress.max) {
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.notEnoughStress'));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export default class DamageSelectionDialog extends HandlebarsApplicationMixin(Ap
|
|||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'damage-selection'],
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
|
||||
position: {
|
||||
width: 400,
|
||||
height: 'auto'
|
||||
|
|
|
|||
1
module/applications/hud/_module.mjs
Normal file
1
module/applications/hud/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as DHTokenHUD } from './tokenHud.mjs';
|
||||
84
module/applications/hud/tokenHUD.mjs
Normal file
84
module/applications/hud/tokenHUD.mjs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
export default class DHTokenHUD extends TokenHUD {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart']
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
hud: {
|
||||
root: true,
|
||||
template: 'systems/daggerheart/templates/hud/tokenHUD.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
|
||||
const effect = context.statusEffects[key];
|
||||
if (effect.systemEffect) acc[key] = effect;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const useGeneric = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||
).showGenericStatusEffects;
|
||||
context.genericStatusEffects = useGeneric
|
||||
? Object.keys(context.statusEffects).reduce((acc, key) => {
|
||||
const effect = context.statusEffects[key];
|
||||
if (!effect.systemEffect) acc[key] = effect;
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
: null;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
_getStatusEffectChoices() {
|
||||
// Include all HUD-enabled status effects
|
||||
const choices = {};
|
||||
for (const status of CONFIG.statusEffects) {
|
||||
if (
|
||||
status.hud === false ||
|
||||
(foundry.utils.getType(status.hud) === 'Object' &&
|
||||
status.hud.actorTypes?.includes(this.document.actor.type) === false)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
choices[status.id] = {
|
||||
_id: status._id,
|
||||
id: status.id,
|
||||
systemEffect: status.systemEffect,
|
||||
title: game.i18n.localize(status.name ?? /** @deprecated since v12 */ status.label),
|
||||
src: status.img ?? /** @deprecated since v12 */ status.icon,
|
||||
isActive: false,
|
||||
isOverlay: false
|
||||
};
|
||||
}
|
||||
|
||||
// Update the status of effects which are active for the token actor
|
||||
const activeEffects = this.actor?.effects || [];
|
||||
for (const effect of activeEffects) {
|
||||
for (const statusId of effect.statuses) {
|
||||
const status = choices[statusId];
|
||||
if (!status) continue;
|
||||
if (status._id) {
|
||||
if (status._id !== effect.id) continue;
|
||||
} else {
|
||||
if (effect.statuses.size !== 1) continue;
|
||||
}
|
||||
status.isActive = true;
|
||||
if (effect.getFlag('core', 'overlay')) status.isOverlay = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Flag status CSS class
|
||||
for (const status of Object.values(choices)) {
|
||||
status.cssClass = [status.isActive ? 'active' : null, status.isOverlay ? 'overlay' : null].filterJoin(' ');
|
||||
}
|
||||
return choices;
|
||||
}
|
||||
}
|
||||
|
|
@ -223,8 +223,8 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
|||
|
||||
context.achievements = {
|
||||
proficiency: {
|
||||
old: this.actor.system.proficiency.total,
|
||||
new: this.actor.system.proficiency.total + achivementProficiency,
|
||||
old: this.actor.system.proficiency,
|
||||
new: this.actor.system.proficiency + achivementProficiency,
|
||||
shown: achivementProficiency > 0
|
||||
},
|
||||
damageThresholds: {
|
||||
|
|
@ -332,16 +332,16 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
|||
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
||||
},
|
||||
hitPoints: {
|
||||
old: this.actor.system.resources.hitPoints.maxTotal,
|
||||
new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0)
|
||||
old: this.actor.system.resources.hitPoints.max,
|
||||
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
|
||||
},
|
||||
stress: {
|
||||
old: this.actor.system.resources.stress.maxTotal,
|
||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
||||
old: this.actor.system.resources.stress.max,
|
||||
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
||||
},
|
||||
evasion: {
|
||||
old: this.actor.system.evasion.total,
|
||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
||||
old: this.actor.system.evasion,
|
||||
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
||||
}
|
||||
},
|
||||
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
||||
|
|
@ -349,8 +349,8 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
|||
if (!acc) acc = {};
|
||||
acc[traitKey] = {
|
||||
label: game.i18n.localize(abilities[traitKey].label),
|
||||
old: this.actor.system.traits[traitKey].total,
|
||||
new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey]
|
||||
old: this.actor.system.traits[traitKey].max,
|
||||
new: this.actor.system.traits[traitKey].max + advancement.trait[traitKey]
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
|
|
|
|||
|
|
@ -122,12 +122,12 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
|||
context.advancements = {
|
||||
statistics: {
|
||||
stress: {
|
||||
old: this.actor.system.resources.stress.maxTotal,
|
||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
||||
old: this.actor.system.resources.stress.max,
|
||||
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
||||
},
|
||||
evasion: {
|
||||
old: this.actor.system.evasion.total,
|
||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
||||
old: this.actor.system.evasion,
|
||||
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
||||
}
|
||||
},
|
||||
experiences:
|
||||
|
|
|
|||
|
|
@ -157,8 +157,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
|
||||
context.achievements = {
|
||||
proficiency: {
|
||||
old: this.actor.system.proficiency.total,
|
||||
new: this.actor.system.proficiency.total + achivementProficiency,
|
||||
old: this.actor.system.proficiency,
|
||||
new: this.actor.system.proficiency + achivementProficiency,
|
||||
shown: achivementProficiency > 0
|
||||
},
|
||||
damageThresholds: {
|
||||
|
|
@ -265,16 +265,16 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
||||
},
|
||||
hitPoints: {
|
||||
old: this.actor.system.resources.hitPoints.maxTotal,
|
||||
new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0)
|
||||
old: this.actor.system.resources.hitPoints.max,
|
||||
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
|
||||
},
|
||||
stress: {
|
||||
old: this.actor.system.resources.stress.maxTotal,
|
||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
||||
old: this.actor.system.resources.stress.max,
|
||||
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
||||
},
|
||||
evasion: {
|
||||
old: this.actor.system.evasion.total,
|
||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
||||
old: this.actor.system.evasion,
|
||||
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
||||
}
|
||||
},
|
||||
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
||||
|
|
@ -282,8 +282,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
if (!acc) acc = {};
|
||||
acc[traitKey] = {
|
||||
label: game.i18n.localize(abilities[traitKey].label),
|
||||
old: this.actor.system.traits[traitKey].total,
|
||||
new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey]
|
||||
old: this.actor.system.traits[traitKey].value,
|
||||
new: this.actor.system.traits[traitKey].value + advancement.trait[traitKey]
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
|
|
|
|||
|
|
@ -56,10 +56,6 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|||
id: 'effect',
|
||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
|
||||
}
|
||||
/* form: {
|
||||
id: 'action',
|
||||
template: 'systems/daggerheart/templates/config/action.hbs'
|
||||
} */
|
||||
};
|
||||
|
||||
static TABS = {
|
||||
|
|
@ -161,7 +157,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|||
container = foundry.utils.getProperty(this.action.parent, this.action.systemPath);
|
||||
let newActions;
|
||||
if (Array.isArray(container)) {
|
||||
newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject()); // Find better way
|
||||
newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject());
|
||||
if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data);
|
||||
} else newActions = data;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
window: {
|
||||
resizable: true
|
||||
},
|
||||
dragDrop: [],
|
||||
dragDrop: [
|
||||
{
|
||||
dragSelector: '[data-item-id][draggable="true"]',
|
||||
dropSelector: null
|
||||
}
|
||||
],
|
||||
contextMenus: [{
|
||||
handler: CharacterSheet.#getDomainCardContextOptions,
|
||||
selector: '[data-item-uuid][data-type="domainCard"]',
|
||||
|
|
@ -599,7 +604,59 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
await doc?.update({ 'system.inVault': !doc.system.inVault });
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a item
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async useItem(event, button) {
|
||||
const item = this.getItem(button);
|
||||
if (!item) return;
|
||||
|
||||
// Should dandle its actions. Or maybe they'll be separate buttons as per an Issue on the board
|
||||
if (item.type === 'feature') {
|
||||
item.use(event);
|
||||
} else if (item instanceof ActiveEffect) {
|
||||
item.toChat(this);
|
||||
} else {
|
||||
const wasUsed = await item.use(event);
|
||||
if (wasUsed && item.type === 'weapon') {
|
||||
Hooks.callAll(CONFIG.DH.HOOKS.characterAttack, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use an action
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async useAction(event, button) {
|
||||
const item = this.getItem(button);
|
||||
if (!item) return;
|
||||
|
||||
const action = item.system.actions.find(x => x.id === button.dataset.actionId);
|
||||
if (!action) return;
|
||||
|
||||
action.use(event);
|
||||
}
|
||||
|
||||
async _onDragStart(event) {
|
||||
const item = this.getItem(event);
|
||||
|
||||
const dragData = {
|
||||
type: item.documentName,
|
||||
uuid: item.uuid
|
||||
};
|
||||
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
||||
|
||||
super._onDragStart(event);
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
// Prevent event bubbling to avoid duplicate handling
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
super._onDrop(event);
|
||||
this._onDropItem(event, TextEditor.getDragEventData(event));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
export { default as DHApplicationMixin } from './application-mixin.mjs';
|
||||
export { default as DHBaseItemSheet } from './base-item.mjs';
|
||||
export { default as DHHeritageSheet } from './heritage-sheet.mjs';
|
||||
export { default as DHItemAttachmentSheet } from './item-attachment-sheet.mjs';
|
||||
export { default as DHBaseActorSheet } from './base-actor.mjs';
|
||||
export { default as DHBaseActorSettings } from './actor-setting.mjs';
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
|
||||
const systemData = {
|
||||
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
|
||||
description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}`
|
||||
description: `${experience.name} ${experience.value.signedString()}`
|
||||
};
|
||||
|
||||
foundry.documents.ChatMessage.implementation.create({
|
||||
|
|
|
|||
90
module/applications/sheets/api/item-attachment-sheet.mjs
Normal file
90
module/applications/sheets/api/item-attachment-sheet.mjs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
|
||||
export default function ItemAttachmentSheet(Base) {
|
||||
return class extends Base {
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
dragDrop: [
|
||||
...(super.DEFAULT_OPTIONS.dragDrop || []),
|
||||
{ dragSelector: null, dropSelector: '.attachments-section' }
|
||||
],
|
||||
actions: {
|
||||
...super.DEFAULT_OPTIONS.actions,
|
||||
removeAttachment: this.#removeAttachment
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
...super.PARTS,
|
||||
attachments: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-attachments.hbs',
|
||||
scrollable: ['.attachments']
|
||||
}
|
||||
};
|
||||
|
||||
static TABS = {
|
||||
...super.TABS,
|
||||
primary: {
|
||||
...super.TABS?.primary,
|
||||
tabs: [
|
||||
...(super.TABS?.primary?.tabs || []),
|
||||
{ id: 'attachments' }
|
||||
],
|
||||
initial: super.TABS?.primary?.initial || 'description',
|
||||
labelPrefix: super.TABS?.primary?.labelPrefix || 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
await super._preparePartContext(partId, context);
|
||||
|
||||
if (partId === 'attachments') {
|
||||
context.attachedItems = await prepareAttachmentContext(this.document);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
const data = TextEditor.getDragEventData(event);
|
||||
|
||||
const attachmentsSection = event.target.closest('.attachments-section');
|
||||
if (!attachmentsSection) return super._onDrop(event);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const item = await Item.implementation.fromDropData(data);
|
||||
if (!item) return;
|
||||
|
||||
// Call the data model's public method
|
||||
await this.document.system.addAttachment(item);
|
||||
}
|
||||
|
||||
|
||||
static async #removeAttachment(event, target) {
|
||||
// Call the data model's public method
|
||||
await this.document.system.removeAttachment(target.dataset.uuid);
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
await super._preparePartContext(partId, context);
|
||||
|
||||
if (partId === 'attachments') {
|
||||
// Keep this simple UI preparation in the mixin
|
||||
const attachedUUIDs = this.document.system.attached;
|
||||
context.attachedItems = await Promise.all(
|
||||
attachedUUIDs.map(async uuid => {
|
||||
const item = await fromUuid(uuid);
|
||||
return {
|
||||
uuid: uuid,
|
||||
name: item?.name || 'Unknown Item',
|
||||
img: item?.img || 'icons/svg/item-bag.svg'
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import DHBaseItemSheet from '../api/base-item.mjs';
|
||||
import ItemAttachmentSheet from '../api/item-attachment-sheet.mjs';
|
||||
|
||||
export default class ArmorSheet extends DHBaseItemSheet {
|
||||
export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['armor'],
|
||||
dragDrop: [{ dragSelector: null, dropSelector: null }],
|
||||
tagifyConfigs: [
|
||||
{
|
||||
selector: '.features-input',
|
||||
|
|
@ -30,7 +30,8 @@ export default class ArmorSheet extends DHBaseItemSheet {
|
|||
effects: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||
scrollable: ['.effects']
|
||||
}
|
||||
},
|
||||
...super.PARTS,
|
||||
};
|
||||
|
||||
/**@inheritdoc */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import DHBaseItemSheet from '../api/base-item.mjs';
|
||||
import ItemAttachmentSheet from '../api/item-attachment-sheet.mjs';
|
||||
|
||||
export default class WeaponSheet extends DHBaseItemSheet {
|
||||
export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['weapon'],
|
||||
|
|
@ -29,12 +30,13 @@ export default class WeaponSheet extends DHBaseItemSheet {
|
|||
effects: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||
scrollable: ['.effects']
|
||||
}
|
||||
},
|
||||
...super.PARTS,
|
||||
};
|
||||
|
||||
/**@inheritdoc */
|
||||
async _preparePartContext(partId, context) {
|
||||
super._preparePartContext(partId, context);
|
||||
await super._preparePartContext(partId, context);
|
||||
switch (partId) {
|
||||
case 'settings':
|
||||
context.features = this.document.system.weaponFeatures.map(x => x.value);
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true)
|
||||
damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
|
||||
|
||||
target.actor.takeDamage(damage, message.system.roll.type);
|
||||
target.actor.takeDamage(damage, message.system.damage.damageType);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ export const adversaryTypes = {
|
|||
},
|
||||
social: {
|
||||
id: 'social',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryTypee.social.label',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryType.social.label',
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.social.description'
|
||||
},
|
||||
solo: {
|
||||
|
|
|
|||
|
|
@ -7,3 +7,5 @@ export const encounterCountdown = {
|
|||
simple: 'countdown-encounter-simple',
|
||||
position: 'countdown-encounter-position'
|
||||
};
|
||||
|
||||
export const itemAttachmentSource = 'attachmentSource';
|
||||
|
|
@ -59,13 +59,13 @@ export const damageTypes = {
|
|||
id: 'physical',
|
||||
label: 'DAGGERHEART.CONFIG.DamageType.physical.name',
|
||||
abbreviation: 'DAGGERHEART.CONFIG.DamageType.physical.abbreviation',
|
||||
icon: ['fa-hand-fist']
|
||||
icon: 'fa-hand-fist'
|
||||
},
|
||||
magical: {
|
||||
id: 'magical',
|
||||
label: 'DAGGERHEART.CONFIG.DamageType.magical.name',
|
||||
abbreviation: 'DAGGERHEART.CONFIG.DamageType.magical.abbreviation',
|
||||
icon: ['fa-wand-sparkles']
|
||||
icon: 'fa-wand-sparkles'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -41,37 +41,37 @@ export const armorFeatures = {
|
|||
img: 'icons/magic/control/buff-flight-wings-red.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.traits.agility.bonus',
|
||||
key: 'system.traits.agility.value',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
},
|
||||
{
|
||||
key: 'system.traits.strength.bonus',
|
||||
key: 'system.traits.strength.value',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
},
|
||||
{
|
||||
key: 'system.traits.finesse.bonus',
|
||||
key: 'system.traits.finesse.value',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
},
|
||||
{
|
||||
key: 'system.traits.instinct.bonus',
|
||||
key: 'system.traits.instinct.value',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
},
|
||||
{
|
||||
key: 'system.traits.presence.bonus',
|
||||
key: 'system.traits.presence.value',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
},
|
||||
{
|
||||
key: 'system.traits.knowledge.bonus',
|
||||
key: 'system.traits.knowledge.value',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
},
|
||||
{
|
||||
key: 'system.evasion.bonus',
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ export const armorFeatures = {
|
|||
img: 'icons/magic/movement/abstract-ribbons-red-orange.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.evasion.bonus',
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '1'
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ export const armorFeatures = {
|
|||
img: 'icons/magic/control/control-influence-crown-gold.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.traits.presence.bonus',
|
||||
key: 'system.traits.presence.value',
|
||||
mode: 2,
|
||||
value: '1'
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ export const armorFeatures = {
|
|||
img: 'icons/commodities/metal/ingot-worn-iron.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.evasion.bonus',
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
}
|
||||
|
|
@ -372,12 +372,12 @@ export const armorFeatures = {
|
|||
img: 'icons/commodities/metal/ingot-stamped-steel.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.evasion.bonus',
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '-2'
|
||||
},
|
||||
{
|
||||
key: 'system.traits.agility.bonus',
|
||||
key: 'system.traits.agility.value',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
}
|
||||
|
|
@ -395,7 +395,7 @@ export const armorFeatures = {
|
|||
img: 'icons/magic/defensive/barrier-shield-dome-pink.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.bonuses.damageReduction.magical',
|
||||
key: 'system.resistance.magical.reduction',
|
||||
mode: 2,
|
||||
value: '@system.armorScore'
|
||||
}
|
||||
|
|
@ -413,7 +413,7 @@ export const weaponFeatures = {
|
|||
{
|
||||
changes: [
|
||||
{
|
||||
key: 'system.bonuses.armorScore',
|
||||
key: 'system.armorScore',
|
||||
mode: 2,
|
||||
value: 'ITEM.@system.tier + 1'
|
||||
}
|
||||
|
|
@ -422,7 +422,7 @@ export const weaponFeatures = {
|
|||
{
|
||||
changes: [
|
||||
{
|
||||
key: 'system.evasion.bonus',
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
}
|
||||
|
|
@ -474,7 +474,7 @@ export const weaponFeatures = {
|
|||
{
|
||||
changes: [
|
||||
{
|
||||
key: 'system.evasion.bonus',
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
}
|
||||
|
|
@ -529,7 +529,7 @@ export const weaponFeatures = {
|
|||
img: 'icons/magic/lightning/claws-unarmed-strike-teal.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.proficiency.bonus',
|
||||
key: 'system.proficiency',
|
||||
mode: 2,
|
||||
value: '1'
|
||||
}
|
||||
|
|
@ -569,7 +569,7 @@ export const weaponFeatures = {
|
|||
img: 'icons/commodities/metal/mail-plate-steel.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.traits.finesse.bonus',
|
||||
key: 'system.traits.finesse.value',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
}
|
||||
|
|
@ -615,7 +615,7 @@ export const weaponFeatures = {
|
|||
img: 'icons/skills/melee/hand-grip-sword-strike-orange.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.evasion.bonus',
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '@system.armorScore'
|
||||
}
|
||||
|
|
@ -645,7 +645,7 @@ export const weaponFeatures = {
|
|||
img: 'icons/skills/melee/strike-flail-spiked-pink.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.traits.agility.bonus',
|
||||
key: 'system.traits.agility.value',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
}
|
||||
|
|
@ -683,7 +683,7 @@ export const weaponFeatures = {
|
|||
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.bonuses.armorScore',
|
||||
key: 'system.armorScore',
|
||||
mode: 2,
|
||||
value: '1'
|
||||
},
|
||||
|
|
@ -777,7 +777,7 @@ export const weaponFeatures = {
|
|||
img: 'icons/commodities/currency/coins-crown-stack-gold.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.proficiency.bonus',
|
||||
key: 'system.proficiency',
|
||||
mode: 2,
|
||||
value: '1'
|
||||
}
|
||||
|
|
@ -819,7 +819,7 @@ export const weaponFeatures = {
|
|||
img: 'icons/commodities/metal/ingot-worn-iron.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.evasion.bonus',
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
}
|
||||
|
|
@ -941,7 +941,7 @@ export const weaponFeatures = {
|
|||
img: '',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.evasion.bonus',
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
},
|
||||
|
|
@ -1031,7 +1031,7 @@ export const weaponFeatures = {
|
|||
img: 'icons/magic/control/hypnosis-mesmerism-eye.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.traits.presence.bonus',
|
||||
key: 'system.traits.presence.value',
|
||||
mode: 2,
|
||||
value: '2'
|
||||
}
|
||||
|
|
@ -1088,7 +1088,7 @@ export const weaponFeatures = {
|
|||
img: 'icons/skills/melee/shield-block-gray-orange.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.bonuses.armorScore',
|
||||
key: 'system.armorScore',
|
||||
mode: 2,
|
||||
value: '1'
|
||||
}
|
||||
|
|
@ -1218,7 +1218,7 @@ export const weaponFeatures = {
|
|||
{
|
||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||
mode: 2,
|
||||
value: '@system.traits.agility.total'
|
||||
value: '@system.traits.agility.value'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,11 +76,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
|
|||
};
|
||||
}
|
||||
|
||||
getFormula(actor) {
|
||||
/* const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : actor.system[this.multiplier]?.total;
|
||||
return this.custom.enabled
|
||||
? this.custom.formula
|
||||
: `${multiplier ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`; */
|
||||
getFormula() {
|
||||
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`,
|
||||
bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : '';
|
||||
return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`;
|
||||
|
|
@ -93,7 +89,6 @@ export class DHDamageField extends fields.SchemaField {
|
|||
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
|
||||
includeBase: new fields.BooleanField({ initial: false })
|
||||
};
|
||||
// if (hasBase) damageFields.includeBase = new fields.BooleanField({ initial: true });
|
||||
super(damageFields, options, context);
|
||||
}
|
||||
}
|
||||
|
|
@ -102,15 +97,19 @@ export class DHDamageData extends foundry.abstract.DataModel {
|
|||
/** @override */
|
||||
static defineSchema() {
|
||||
return {
|
||||
// ...super.defineSchema(),
|
||||
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
||||
type: new fields.StringField({
|
||||
choices: CONFIG.DH.GENERAL.damageTypes,
|
||||
initial: 'physical',
|
||||
label: 'Type',
|
||||
nullable: false,
|
||||
required: true
|
||||
}),
|
||||
type: new fields.SetField(
|
||||
new fields.StringField({
|
||||
choices: CONFIG.DH.GENERAL.damageTypes,
|
||||
initial: 'physical',
|
||||
nullable: false,
|
||||
required: true
|
||||
}),
|
||||
{
|
||||
label: 'Type',
|
||||
initial: 'physical',
|
||||
}
|
||||
),
|
||||
resultBased: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label'
|
||||
|
|
|
|||
|
|
@ -179,16 +179,9 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
getRollData(data = {}) {
|
||||
const actorData = this.actor.getRollData(false);
|
||||
|
||||
// Remove when included directly in Actor getRollData
|
||||
actorData.prof = actorData.proficiency?.value ?? 1;
|
||||
actorData.cast = actorData.spellcast?.value ?? 1;
|
||||
// Add Roll results to RollDatas
|
||||
actorData.result = data.roll?.total ?? 1;
|
||||
/* actorData.scale = data.costs?.length
|
||||
? data.costs.reduce((a, c) => {
|
||||
a[c.type] = c.value;
|
||||
return a;
|
||||
}, {})
|
||||
: 1; */
|
||||
|
||||
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
||||
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
||||
: 1;
|
||||
|
|
@ -338,7 +331,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
.filter(c => c.enabled !== false)
|
||||
.map(c => {
|
||||
const resource = this.actor.system.resources[c.type];
|
||||
return { type: c.type, value: (c.total ?? c.value) * (resource.hasOwnProperty('maxTotal') ? 1 : -1) };
|
||||
return { type: c.type, value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1) };
|
||||
});
|
||||
|
||||
await this.actor.modifyResource(resources);
|
||||
|
|
@ -391,12 +384,12 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
return false;
|
||||
}
|
||||
|
||||
/* maxTotal is a sign that the resource is inverted, IE it counts upwards instead of down */
|
||||
/* isReversed is a sign that the resource is inverted, IE it counts upwards instead of down */
|
||||
const resources = this.actor.system.resources;
|
||||
return realCosts.reduce(
|
||||
(a, c) =>
|
||||
a && resources[c.type].hasOwnProperty('maxTotal')
|
||||
? resources[c.type].value + (c.total ?? c.value) <= resources[c.type].maxTotal
|
||||
a && resources[c.type].isReversed
|
||||
? resources[c.type].value + (c.total ?? c.value) <= resources[c.type].max
|
||||
: resources[c.type]?.value >= (c.total ?? c.value),
|
||||
true
|
||||
);
|
||||
|
|
@ -439,7 +432,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
name: actor.actor.name,
|
||||
img: actor.actor.img,
|
||||
difficulty: actor.actor.system.difficulty,
|
||||
evasion: actor.actor.system.evasion?.total
|
||||
evasion: actor.actor.system.evasion
|
||||
};
|
||||
}
|
||||
/* TARGET */
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ export default class DHDamageAction extends DHBaseAction {
|
|||
}
|
||||
|
||||
async rollDamage(event, data) {
|
||||
let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + ');
|
||||
let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '),
|
||||
damageTypes = [...new Set(this.damage.parts.reduce((a,c) => a.concat([...c.type]), []))];
|
||||
|
||||
damageTypes = !damageTypes.length ? ['physical'] : damageTypes;
|
||||
|
||||
if (!formula || formula == '') return;
|
||||
let roll = { formula: formula, total: formula },
|
||||
|
|
@ -25,6 +28,7 @@ export default class DHDamageAction extends DHBaseAction {
|
|||
hasSave: this.hasSave,
|
||||
isCritical: data.system?.roll?.isCritical ?? false,
|
||||
source: data.system?.source,
|
||||
damageTypes,
|
||||
event
|
||||
};
|
||||
if (this.hasSave) config.onSave = this.save.damageMod;
|
||||
|
|
@ -32,7 +36,7 @@ export default class DHDamageAction extends DHBaseAction {
|
|||
config.source.message = data._id;
|
||||
config.directDamage = false;
|
||||
}
|
||||
|
||||
|
||||
roll = CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import BaseDataActor from './base.mjs';
|
|||
const resourceField = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new foundry.data.fields.NumberField({ initial: 0, integer: true })
|
||||
max: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
isReversed: new foundry.data.fields.BooleanField({ initial: true })
|
||||
});
|
||||
|
||||
export default class DhpAdversary extends BaseDataActor {
|
||||
|
|
@ -22,6 +23,7 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
tier: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.tiers,
|
||||
|
|
@ -32,7 +34,6 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
choices: CONFIG.DH.ACTOR.adversaryTypes,
|
||||
initial: CONFIG.DH.ACTOR.adversaryTypes.standard.id
|
||||
}),
|
||||
description: new fields.StringField(),
|
||||
motivesAndTactics: new fields.StringField(),
|
||||
notes: new fields.HTMLField(),
|
||||
difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }),
|
||||
|
|
@ -63,6 +64,7 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
damage: {
|
||||
parts: [
|
||||
{
|
||||
type: ['physical'],
|
||||
value: {
|
||||
multiplier: 'flat'
|
||||
}
|
||||
|
|
@ -74,7 +76,7 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField(),
|
||||
total: new fields.NumberField({ required: true, integer: true, initial: 1 })
|
||||
value: new fields.NumberField({ required: true, integer: true, initial: 1 })
|
||||
})
|
||||
),
|
||||
bonuses: new fields.SchemaField({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
import DHBaseActorSettings from "../../applications/sheets/api/actor-setting.mjs";
|
||||
|
||||
const resistanceField = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
resistance: new foundry.data.fields.BooleanField({ initial: false }),
|
||||
immunity: new foundry.data.fields.BooleanField({ initial: false }),
|
||||
reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0 })
|
||||
});
|
||||
|
||||
/**
|
||||
* Describes metadata about the actor data model type
|
||||
* @typedef {Object} ActorDataModelMetadata
|
||||
|
|
@ -16,6 +23,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
type: 'base',
|
||||
isNPC: true,
|
||||
settingSheet: null,
|
||||
hasResistances: true
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +32,21 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
return this.constructor.metadata;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
const schema = {};
|
||||
|
||||
if(this.metadata.isNPC)
|
||||
schema.description = new fields.HTMLField({ required: true, nullable: true });
|
||||
if(this.metadata.hasResistances)
|
||||
schema.resistance = new fields.SchemaField({
|
||||
physical: resistanceField(),
|
||||
magical: resistanceField()
|
||||
})
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
|
||||
* @param {object} [options] - Options which modify the getRollData method.
|
||||
|
|
|
|||
|
|
@ -5,16 +5,15 @@ import BaseDataActor from './base.mjs';
|
|||
|
||||
const attributeField = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: null, integer: true }),
|
||||
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
tierMarked: new foundry.data.fields.BooleanField({ initial: false })
|
||||
});
|
||||
|
||||
const resourceField = max =>
|
||||
const resourceField = (max, reverse = false) =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new foundry.data.fields.NumberField({ initial: max, integer: true })
|
||||
max: new foundry.data.fields.NumberField({ initial: max, integer: true }),
|
||||
isReversed: new foundry.data.fields.BooleanField({ initial: reverse })
|
||||
});
|
||||
|
||||
const stressDamageReductionRule = () =>
|
||||
|
|
@ -36,12 +35,10 @@ export default class DhCharacter extends BaseDataActor {
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
resources: new fields.SchemaField({
|
||||
hitPoints: new fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
stress: resourceField(6),
|
||||
hitPoints: resourceField(0, true),
|
||||
stress: resourceField(6, true),
|
||||
hope: resourceField(6),
|
||||
tokens: new fields.ObjectField(),
|
||||
dice: new fields.ObjectField()
|
||||
|
|
@ -54,18 +51,17 @@ export default class DhCharacter extends BaseDataActor {
|
|||
presence: attributeField(),
|
||||
knowledge: attributeField()
|
||||
}),
|
||||
proficiency: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 1, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
evasion: new fields.SchemaField({
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
proficiency: new fields.NumberField({ initial: 1, integer: true }),
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true }),
|
||||
armorScore: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
severe: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
major: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField(),
|
||||
value: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
bonus: new fields.NumberField({ integer: true, initial: 0 })
|
||||
value: new fields.NumberField({ integer: true, initial: 0 })
|
||||
})
|
||||
),
|
||||
gold: new fields.SchemaField({
|
||||
|
|
@ -99,15 +95,6 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}),
|
||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||
bonuses: new fields.SchemaField({
|
||||
armorScore: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
damageReduction: new fields.SchemaField({
|
||||
physical: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
magical: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
severe: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
major: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
roll: new fields.SchemaField({
|
||||
attack: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
primaryWeapon: new fields.SchemaField({
|
||||
|
|
@ -306,7 +293,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
|
||||
get deathMoveViable() {
|
||||
return (
|
||||
this.resources.hitPoints.maxTotal > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.maxTotal
|
||||
this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -350,32 +337,32 @@ export default class DhCharacter extends BaseDataActor {
|
|||
for (let levelKey in this.levelData.levelups) {
|
||||
const level = this.levelData.levelups[levelKey];
|
||||
|
||||
this.proficiency.bonus += level.achievements.proficiency;
|
||||
this.proficiency += level.achievements.proficiency;
|
||||
|
||||
for (let selection of level.selections) {
|
||||
switch (selection.type) {
|
||||
case 'trait':
|
||||
selection.data.forEach(data => {
|
||||
this.traits[data].bonus += 1;
|
||||
this.traits[data].value += 1;
|
||||
this.traits[data].tierMarked = selection.tier === currentTier;
|
||||
});
|
||||
break;
|
||||
case 'hitPoint':
|
||||
this.resources.hitPoints.bonus += selection.value;
|
||||
this.resources.hitPoints.max += selection.value;
|
||||
break;
|
||||
case 'stress':
|
||||
this.resources.stress.bonus += selection.value;
|
||||
this.resources.stress.max += selection.value;
|
||||
break;
|
||||
case 'evasion':
|
||||
this.evasion.bonus += selection.value;
|
||||
this.evasion += selection.value;
|
||||
break;
|
||||
case 'proficiency':
|
||||
this.proficiency.bonus = selection.value;
|
||||
this.proficiency = selection.value;
|
||||
break;
|
||||
case 'experience':
|
||||
Object.keys(this.experiences).forEach(key => {
|
||||
const experience = this.experiences[key];
|
||||
experience.bonus += selection.value;
|
||||
experience.value += selection.value;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
@ -383,7 +370,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
|
||||
const armor = this.armor;
|
||||
this.armorScore = this.armor ? this.armor.system.baseScore + (this.bonuses.armorScore ?? 0) : 0; // Bonuses to armorScore won't have been applied yet. Need to solve in documentPreparation somehow
|
||||
this.armorScore = armor ? armor.system.baseScore : 0;
|
||||
this.damageThresholds = {
|
||||
major: armor
|
||||
? armor.system.baseThresholds.major + this.levelData.level.current
|
||||
|
|
@ -392,29 +379,12 @@ export default class DhCharacter extends BaseDataActor {
|
|||
? armor.system.baseThresholds.severe + this.levelData.level.current
|
||||
: this.levelData.level.current * 2
|
||||
};
|
||||
this.resources.hope.max -= Object.keys(this.scars).length;
|
||||
this.resources.hitPoints.max = this.class.value?.system?.hitPoints ?? 0;
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
this.resources.hope.max -= Object.keys(this.scars).length;
|
||||
this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max);
|
||||
|
||||
for (var traitKey in this.traits) {
|
||||
var trait = this.traits[traitKey];
|
||||
trait.total = (trait.value ?? 0) + trait.bonus;
|
||||
}
|
||||
|
||||
for (var experienceKey in this.experiences) {
|
||||
var experience = this.experiences[experienceKey];
|
||||
experience.total = experience.value + experience.bonus;
|
||||
}
|
||||
|
||||
this.rules.damageReduction.maxArmorMarked.total =
|
||||
this.rules.damageReduction.maxArmorMarked.value + this.rules.damageReduction.maxArmorMarked.bonus;
|
||||
|
||||
this.resources.hitPoints.maxTotal = (this.class.value?.system?.hitPoints ?? 0) + this.resources.hitPoints.bonus;
|
||||
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
|
||||
this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus;
|
||||
this.proficiency.total = this.proficiency.value + this.proficiency.bonus;
|
||||
}
|
||||
|
||||
getRollData() {
|
||||
|
|
|
|||
|
|
@ -20,24 +20,21 @@ export default class DhCompanion extends BaseDataActor {
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
|
||||
resources: new fields.SchemaField({
|
||||
stress: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new fields.NumberField({ initial: 3, integer: true })
|
||||
max: new fields.NumberField({ initial: 3, integer: true }),
|
||||
isReversed: new foundry.data.fields.BooleanField({ initial: true })
|
||||
}),
|
||||
hope: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
evasion: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
evasion: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
value: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
bonus: new fields.NumberField({ integer: true, initial: 0 })
|
||||
value: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
{
|
||||
initial: {
|
||||
|
|
@ -66,10 +63,10 @@ export default class DhCompanion extends BaseDataActor {
|
|||
damage: {
|
||||
parts: [
|
||||
{
|
||||
multiplier: 'flat',
|
||||
type: ['physical'],
|
||||
value: {
|
||||
dice: 'd6',
|
||||
multiplier: 'flat'
|
||||
multiplier: 'prof'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -83,13 +80,17 @@ export default class DhCompanion extends BaseDataActor {
|
|||
|
||||
get traits() {
|
||||
return {
|
||||
instinct: { total: this.attack.roll.bonus }
|
||||
instinct: { value: this.attack.roll.bonus }
|
||||
};
|
||||
}
|
||||
|
||||
get proficiency() {
|
||||
return this.partner?.system?.proficiency ?? 1;
|
||||
}
|
||||
|
||||
prepareBaseData() {
|
||||
const partnerSpellcastingModifier = this.partner?.system?.spellcastingModifiers?.main;
|
||||
const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.total;
|
||||
const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.value;
|
||||
this.attack.roll.bonus = spellcastingModifier ?? 0; // Needs to expand on which modifier it is that should be used because of multiclassing;
|
||||
|
||||
for (let levelKey in this.levelData.levelups) {
|
||||
|
|
@ -107,15 +108,15 @@ export default class DhCompanion extends BaseDataActor {
|
|||
}
|
||||
break;
|
||||
case 'stress':
|
||||
this.resources.stress.bonus += selection.value;
|
||||
this.resources.stress.max += selection.value;
|
||||
break;
|
||||
case 'evasion':
|
||||
this.evasion.bonus += selection.value;
|
||||
this.evasion += selection.value;
|
||||
break;
|
||||
case 'experience':
|
||||
Object.keys(this.experiences).forEach(key => {
|
||||
const experience = this.experiences[key];
|
||||
experience.bonus += selection.value;
|
||||
experience.value += selection.value;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
@ -124,17 +125,9 @@ export default class DhCompanion extends BaseDataActor {
|
|||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
for (var experienceKey in this.experiences) {
|
||||
var experience = this.experiences[experienceKey];
|
||||
experience.total = experience.value + experience.bonus;
|
||||
}
|
||||
|
||||
if (this.partner) {
|
||||
this.partner.system.resources.hope.max += this.resources.hope;
|
||||
}
|
||||
|
||||
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
|
||||
this.evasion.total = this.evasion.value + this.evasion.bonus;
|
||||
}
|
||||
|
||||
async _preDelete() {
|
||||
|
|
|
|||
|
|
@ -9,20 +9,21 @@ export default class DhEnvironment extends BaseDataActor {
|
|||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.environment',
|
||||
type: 'environment',
|
||||
settingSheet: DHEnvironmentSettings
|
||||
settingSheet: DHEnvironmentSettings,
|
||||
hasResistances: false
|
||||
});
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
tier: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.tiers,
|
||||
initial: CONFIG.DH.GENERAL.tiers.tier1.id
|
||||
}),
|
||||
type: new fields.StringField({ choices: CONFIG.DH.ACTOR.environmentTypes }),
|
||||
description: new fields.StringField(),
|
||||
impulses: new fields.StringField(),
|
||||
difficulty: new fields.NumberField({ required: true, initial: 11, integer: true }),
|
||||
potentialAdversaries: new fields.TypedObjectField(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import DHAncestry from './ancestry.mjs';
|
||||
import DHArmor from './armor.mjs';
|
||||
import DHAttachableItem from './attachableItem.mjs';
|
||||
import DHClass from './class.mjs';
|
||||
import DHCommunity from './community.mjs';
|
||||
import DHConsumable from './consumable.mjs';
|
||||
|
|
@ -13,6 +14,7 @@ import DHBeastform from './beastform.mjs';
|
|||
export {
|
||||
DHAncestry,
|
||||
DHArmor,
|
||||
DHAttachableItem,
|
||||
DHClass,
|
||||
DHCommunity,
|
||||
DHConsumable,
|
||||
|
|
@ -27,6 +29,7 @@ export {
|
|||
export const config = {
|
||||
ancestry: DHAncestry,
|
||||
armor: DHArmor,
|
||||
attachableItem: DHAttachableItem,
|
||||
class: DHClass,
|
||||
community: DHCommunity,
|
||||
consumable: DHConsumable,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import BaseDataItem from './base.mjs';
|
||||
import AttachableItem from './attachableItem.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
import { armorFeatures } from '../../config/itemConfig.mjs';
|
||||
import { actionsTypes } from '../action/_module.mjs';
|
||||
|
||||
export default class DHArmor extends BaseDataItem {
|
||||
export default class DHArmor extends AttachableItem {
|
||||
/** @inheritDoc */
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
|
|
@ -44,6 +45,12 @@ export default class DHArmor extends BaseDataItem {
|
|||
};
|
||||
}
|
||||
|
||||
get customActions() {
|
||||
return this.actions.filter(
|
||||
action => !this.armorFeatures.some(feature => feature.actionIds.includes(action.id))
|
||||
);
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, user) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
|
|
|||
152
module/data/item/attachableItem.mjs
Normal file
152
module/data/item/attachableItem.mjs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import BaseDataItem from './base.mjs';
|
||||
|
||||
export default class AttachableItem extends BaseDataItem {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
attached: new fields.ArrayField(new fields.DocumentUUIDField({ type: "Item", nullable: true }))
|
||||
};
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, user) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
||||
// Handle equipped status changes for attachment effects
|
||||
if (changes.system?.equipped !== undefined && changes.system.equipped !== this.equipped) {
|
||||
await this.#handleAttachmentEffectsOnEquipChange(changes.system.equipped);
|
||||
}
|
||||
}
|
||||
|
||||
async #handleAttachmentEffectsOnEquipChange(newEquippedStatus) {
|
||||
const actor = this.parent.parent?.type === 'character' ? this.parent.parent : this.parent.parent?.parent;
|
||||
const parentType = this.parent.type;
|
||||
|
||||
if (!actor || !this.attached?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newEquippedStatus) {
|
||||
// Item is being equipped - add attachment effects
|
||||
for (const attachedUuid of this.attached) {
|
||||
const attachedItem = await fromUuid(attachedUuid);
|
||||
if (attachedItem && attachedItem.effects.size > 0) {
|
||||
await this.#copyAttachmentEffectsToActor({
|
||||
attachedItem,
|
||||
attachedUuid,
|
||||
parentType
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Item is being unequipped - remove attachment effects
|
||||
await this.#removeAllAttachmentEffects(parentType);
|
||||
}
|
||||
}
|
||||
|
||||
async #copyAttachmentEffectsToActor({ attachedItem, attachedUuid, parentType }) {
|
||||
const actor = this.parent.parent;
|
||||
if (!actor || !attachedItem.effects.size > 0 || !this.equipped) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const effectsToCreate = [];
|
||||
for (const effect of attachedItem.effects) {
|
||||
const effectData = effect.toObject();
|
||||
effectData.origin = `${this.parent.uuid}:${attachedUuid}`;
|
||||
|
||||
const attachmentSource = {
|
||||
itemUuid: attachedUuid,
|
||||
originalEffectId: effect.id
|
||||
};
|
||||
attachmentSource[`${parentType}Uuid`] = this.parent.uuid;
|
||||
|
||||
effectData.flags = {
|
||||
...effectData.flags,
|
||||
[CONFIG.DH.id]: {
|
||||
...effectData.flags?.[CONFIG.DH.id],
|
||||
[CONFIG.DH.FLAGS.itemAttachmentSource]: attachmentSource
|
||||
}
|
||||
};
|
||||
effectsToCreate.push(effectData);
|
||||
}
|
||||
|
||||
if (effectsToCreate.length > 0) {
|
||||
return await actor.createEmbeddedDocuments('ActiveEffect', effectsToCreate);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async #removeAllAttachmentEffects(parentType) {
|
||||
const actor = this.parent.parent;
|
||||
if (!actor) return;
|
||||
|
||||
const parentUuidProperty = `${parentType}Uuid`;
|
||||
const effectsToRemove = actor.effects.filter(effect => {
|
||||
const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource);
|
||||
return attachmentSource && attachmentSource[parentUuidProperty] === this.parent.uuid;
|
||||
});
|
||||
|
||||
if (effectsToRemove.length > 0) {
|
||||
await actor.deleteEmbeddedDocuments('ActiveEffect', effectsToRemove.map(e => e.id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method for adding an attachment
|
||||
*/
|
||||
async addAttachment(droppedItem) {
|
||||
const newUUID = droppedItem.uuid;
|
||||
|
||||
if (this.attached.includes(newUUID)) {
|
||||
ui.notifications.warn(`${droppedItem.name} is already attached to this ${this.parent.type}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedAttached = [...this.attached, newUUID];
|
||||
await this.parent.update({
|
||||
'system.attached': updatedAttached
|
||||
});
|
||||
|
||||
// Copy effects if equipped
|
||||
if (this.equipped && droppedItem.effects.size > 0) {
|
||||
await this.#copyAttachmentEffectsToActor({
|
||||
attachedItem: droppedItem,
|
||||
attachedUuid: newUUID,
|
||||
parentType: this.parent.type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method for removing an attachment
|
||||
*/
|
||||
async removeAttachment(attachedUuid) {
|
||||
await this.parent.update({
|
||||
'system.attached': this.attached.filter(uuid => uuid !== attachedUuid)
|
||||
});
|
||||
|
||||
// Remove effects
|
||||
await this.#removeAttachmentEffects(attachedUuid);
|
||||
}
|
||||
|
||||
async #removeAttachmentEffects(attachedUuid) {
|
||||
const actor = this.parent.parent;
|
||||
if (!actor) return;
|
||||
|
||||
const parentType = this.parent.type;
|
||||
const parentUuidProperty = `${parentType}Uuid`;
|
||||
const effectsToRemove = actor.effects.filter(effect => {
|
||||
const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource);
|
||||
return attachmentSource &&
|
||||
attachmentSource[parentUuidProperty] === this.parent.uuid &&
|
||||
attachmentSource.itemUuid === attachedUuid;
|
||||
});
|
||||
|
||||
if (effectsToRemove.length > 0) {
|
||||
await actor.deleteEmbeddedDocuments('ActiveEffect', effectsToRemove.map(e => e.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,9 +32,9 @@ export default class DHSubclass extends BaseDataItem {
|
|||
|
||||
get features() {
|
||||
return [
|
||||
{ ...this.foundationFeature.toObject(), identifier: 'foundationFeature' },
|
||||
{ ...this.specializationFeature.toObject(), identifier: 'specializationFeature' },
|
||||
{ ...this.masteryFeature.toObject(), identifier: 'masteryFeature' }
|
||||
{ ...this.foundationFeature?.toObject(), identifier: 'foundationFeature' },
|
||||
{ ...this.specializationFeature?.toObject(), identifier: 'specializationFeature' },
|
||||
{ ...this.masteryFeature?.toObject(), identifier: 'masteryFeature' }
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import BaseDataItem from './base.mjs';
|
||||
import AttachableItem from './attachableItem.mjs';
|
||||
import { actionsTypes } from '../action/_module.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
|
||||
export default class DHWeapon extends BaseDataItem {
|
||||
export default class DHWeapon extends AttachableItem {
|
||||
/** @inheritDoc */
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
|
|
@ -37,7 +37,7 @@ export default class DHWeapon extends BaseDataItem {
|
|||
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
|
||||
})
|
||||
),
|
||||
attack: new ActionField({
|
||||
attack: new ActionField({
|
||||
initial: {
|
||||
name: 'Attack',
|
||||
img: 'icons/skills/melee/blood-slash-foam-red.webp',
|
||||
|
|
@ -56,6 +56,7 @@ export default class DHWeapon extends BaseDataItem {
|
|||
damage: {
|
||||
parts: [
|
||||
{
|
||||
type: ['physical'],
|
||||
value: {
|
||||
multiplier: 'prof',
|
||||
dice: 'd8'
|
||||
|
|
@ -73,6 +74,12 @@ export default class DHWeapon extends BaseDataItem {
|
|||
return [this.attack, ...this.actions];
|
||||
}
|
||||
|
||||
get customActions() {
|
||||
return this.actions.filter(
|
||||
action => !this.weaponFeatures.some(feature => feature.actionIds.includes(action.id))
|
||||
);
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, user) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
|||
outline: new fields.ColorField({ required: true, initial: '#ffffff' }),
|
||||
edge: new fields.ColorField({ required: true, initial: '#000000' })
|
||||
})
|
||||
}),
|
||||
showGenericStatusEffects: new fields.BooleanField({
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showGenericStatusEffects.label'
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ export default class DhRangeMeasurement extends foundry.abstract.DataModel {
|
|||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
enabled: new fields.BooleanField({ required: true, initial: false, label: 'DAGGERHEART.GENERAL.enabled' }),
|
||||
enabled: new fields.BooleanField({ required: true, initial: true, label: 'DAGGERHEART.GENERAL.enabled' }),
|
||||
melee: new fields.NumberField({ required: true, initial: 5, label: 'DAGGERHEART.CONFIG.Range.melee.name' }),
|
||||
veryClose: new fields.NumberField({
|
||||
required: true,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||
import DHRoll from './dhRoll.mjs';
|
||||
|
||||
export default class D20Roll extends DHRoll {
|
||||
|
|
@ -98,7 +97,7 @@ export default class D20Roll extends DHRoll {
|
|||
if (this.options.data.experiences?.[m])
|
||||
this.options.roll.modifiers.push({
|
||||
label: this.options.data.experiences[m].name,
|
||||
value: this.options.data.experiences[m].total ?? this.options.data.experiences[m].value
|
||||
value: this.options.data.experiences[m].value
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -137,12 +136,7 @@ export default class D20Roll extends DHRoll {
|
|||
|
||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||
if (config.evaluate !== false) await roll.evaluate();
|
||||
const advantageState =
|
||||
config.roll.advantage == this.ADV_MODE.ADVANTAGE
|
||||
? true
|
||||
: config.roll.advantage == this.ADV_MODE.DISADVANTAGE
|
||||
? false
|
||||
: null;
|
||||
|
||||
this.postEvaluate(roll, config);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ export default class DamageRoll extends DHRoll {
|
|||
super.postEvaluate(roll, config);
|
||||
config.roll.type = config.type;
|
||||
config.roll.modifierTotal = this.calculateTotalModifiers(roll);
|
||||
}
|
||||
|
||||
static async buildPost(roll, config, message) {
|
||||
await super.buildPost(roll, config, message);
|
||||
if (config.source?.message) {
|
||||
const chatMessage = ui.chat.collection.get(config.source.message);
|
||||
chatMessage.update({ 'system.damage': config });
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ export default class DHRoll extends Roll {
|
|||
|
||||
// Create Chat Message
|
||||
if (config.source?.message) {
|
||||
if(game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
|
||||
} else {
|
||||
const messageData = {};
|
||||
config.message = await this.toMessage(roll, config);
|
||||
}
|
||||
}
|
||||
|
|
@ -154,7 +154,7 @@ export const registerRollDiceHooks = () => {
|
|||
|
||||
if (updates.length) actor.modifyResource(updates);
|
||||
|
||||
if (!config.roll.hasOwnProperty('success') && !config.targets.length) return;
|
||||
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
|
||||
|
||||
const rollResult = config.roll.success || config.targets.some(t => t.hit),
|
||||
looseSpotlight = !rollResult || config.roll.result.duality === -1;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||
import D20Roll from './d20Roll.mjs';
|
||||
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||
|
||||
export default class DualityRoll extends D20Roll {
|
||||
_advantageFaces = 6;
|
||||
|
|
@ -80,7 +81,6 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
static getHooks(hooks) {
|
||||
|
||||
return [...(hooks ?? []), 'Duality'];
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ export default class DualityRoll extends D20Roll {
|
|||
if (!this.options.roll.trait) return;
|
||||
this.options.roll.modifiers.push({
|
||||
label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`,
|
||||
value: Roll.replaceFormulaData(`@traits.${this.options.roll.trait}.total`, this.data)
|
||||
value: Roll.replaceFormulaData(`@traits.${this.options.roll.trait}.value`, this.data)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -142,5 +142,7 @@ export default class DualityRoll extends D20Roll {
|
|||
total: roll.dHope.total + roll.dFear.total,
|
||||
label: roll.totalLabel
|
||||
};
|
||||
|
||||
setDiceSoNiceForDualityRoll(roll, config.roll.advantage.type);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,42 @@
|
|||
export default class DhActiveEffect extends ActiveEffect {
|
||||
get isSuppressed() {
|
||||
if (['weapon', 'armor'].includes(this.parent.type)) {
|
||||
// If this is a copied effect from an attachment, never suppress it
|
||||
// (These effects have attachmentSource metadata)
|
||||
if (this.flags?.daggerheart?.attachmentSource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Then apply the standard suppression rules
|
||||
if (['weapon', 'armor'].includes(this.parent?.type)) {
|
||||
return !this.parent.system.equipped;
|
||||
}
|
||||
|
||||
if (this.parent.type === 'domainCard') {
|
||||
if (this.parent?.type === 'domainCard') {
|
||||
return this.parent.system.inVault;
|
||||
}
|
||||
|
||||
return super.isSuppressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the parent item is currently attached to another item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isAttached() {
|
||||
if (!this.parent || !this.parent.parent) return false;
|
||||
|
||||
// Check if this item's UUID is in any actor's armor or weapon attachment lists
|
||||
const actor = this.parent.parent;
|
||||
if (!actor || !actor.items) return false;
|
||||
|
||||
return actor.items.some(item => {
|
||||
return (item.type === 'armor' || item.type === 'weapon') &&
|
||||
item.system?.attached &&
|
||||
Array.isArray(item.system.attached) &&
|
||||
item.system.attached.includes(this.parent.uuid);
|
||||
});
|
||||
}
|
||||
|
||||
async _preCreate(data, options, user) {
|
||||
const update = {};
|
||||
if (!data.img) {
|
||||
|
|
|
|||
|
|
@ -370,99 +370,19 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
|
||||
getRollData() {
|
||||
return this.system;
|
||||
}
|
||||
|
||||
formatRollModifier(roll) {
|
||||
const modifier = roll.modifier !== null ? Number.parseInt(roll.modifier) : null;
|
||||
return modifier !== null
|
||||
? [
|
||||
{
|
||||
value: modifier,
|
||||
label: roll.label
|
||||
? modifier >= 0
|
||||
? `${roll.label} +${modifier}`
|
||||
: `${roll.label} ${modifier}`
|
||||
: null,
|
||||
title: roll.label
|
||||
}
|
||||
]
|
||||
: [];
|
||||
}
|
||||
|
||||
async damageRoll(title, damage, targets, shiftKey) {
|
||||
let rollString = damage.value;
|
||||
let bonusDamage = damage.bonusDamage?.filter(x => x.initiallySelected) ?? [];
|
||||
if (!shiftKey) {
|
||||
const dialogClosed = new Promise((resolve, _) => {
|
||||
new DamageSelectionDialog(rollString, bonusDamage, resolve).render(true);
|
||||
});
|
||||
const result = await dialogClosed;
|
||||
bonusDamage = result.bonusDamage;
|
||||
rollString = result.rollString;
|
||||
|
||||
const automateHope = await game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation.Hope);
|
||||
if (automateHope && result.hopeUsed) {
|
||||
await this.update({
|
||||
'system.resources.hope.value': this.system.resources.hope.value - result.hopeUsed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const roll = new Roll(rollString);
|
||||
let rollResult = await roll.evaluate();
|
||||
|
||||
const dice = [];
|
||||
const modifiers = [];
|
||||
for (var i = 0; i < rollResult.terms.length; i++) {
|
||||
const term = rollResult.terms[i];
|
||||
if (term.faces) {
|
||||
dice.push({
|
||||
type: `d${term.faces}`,
|
||||
rolls: term.results.map(x => x.result),
|
||||
total: term.results.reduce((acc, x) => acc + x.result, 0)
|
||||
});
|
||||
} else if (term.operator) {
|
||||
} else if (term.number) {
|
||||
const operator = i === 0 ? '' : rollResult.terms[i - 1].operator;
|
||||
modifiers.push({ value: term.number, operator: operator });
|
||||
}
|
||||
}
|
||||
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const systemData = {
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: title }),
|
||||
roll: rollString,
|
||||
damage: {
|
||||
total: rollResult.total,
|
||||
type: damage.type
|
||||
},
|
||||
dice: dice,
|
||||
modifiers: modifiers,
|
||||
targets: targets
|
||||
};
|
||||
const msg = new cls({
|
||||
type: 'damageRoll',
|
||||
user: game.user.id,
|
||||
sound: CONFIG.sounds.dice,
|
||||
system: systemData,
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/damage-roll.hbs',
|
||||
systemData
|
||||
),
|
||||
rolls: [roll]
|
||||
});
|
||||
|
||||
cls.create(msg.toObject());
|
||||
const rollData = super.getRollData();
|
||||
rollData.prof = this.system.proficiency ?? 1;
|
||||
rollData.cast = this.system.spellcast ?? 1;
|
||||
return rollData;
|
||||
}
|
||||
|
||||
#canReduceDamage(hpDamage, type) {
|
||||
const availableStress = this.system.resources.stress.maxTotal - this.system.resources.stress.value;
|
||||
const availableStress = this.system.resources.stress.max - this.system.resources.stress.value;
|
||||
|
||||
const canUseArmor =
|
||||
this.system.armor &&
|
||||
this.system.armor.system.marks.value < this.system.armorScore &&
|
||||
this.system.armorApplicableDamageTypes[type];
|
||||
type.every(t => this.system.armorApplicableDamageTypes[t] === true);
|
||||
const canUseStress = Object.keys(this.system.rules.damageReduction.stressDamageReduction).reduce((acc, x) => {
|
||||
const rule = this.system.rules.damageReduction.stressDamageReduction[x];
|
||||
if (damageKeyToNumber(x) <= hpDamage) return acc || (rule.enabled && availableStress >= rule.cost);
|
||||
|
|
@ -480,11 +400,9 @@ export default class DhpActor extends Actor {
|
|||
return;
|
||||
}
|
||||
|
||||
const flatReduction = this.system.bonuses.damageReduction[type];
|
||||
const damage = Math.max(baseDamage - (flatReduction ?? 0), 0);
|
||||
const hpDamage = this.convertDamageToThreshold(damage);
|
||||
type = !Array.isArray(type) ? [type] : type;
|
||||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.postDamageTreshold`, this, hpDamage, damage, type) === false) return null;
|
||||
const hpDamage = this.calculateDamage(baseDamage, type);
|
||||
|
||||
if (!hpDamage) return;
|
||||
|
||||
|
|
@ -511,6 +429,35 @@ export default class DhpActor extends Actor {
|
|||
if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damage, type) === false) return null;
|
||||
}
|
||||
|
||||
calculateDamage(baseDamage, type) {
|
||||
if (Hooks.call(`${CONFIG.DH.id}.preCalculateDamage`, this, baseDamage, type) === false) return null;
|
||||
|
||||
/* if(this.system.resistance[type]?.immunity) return 0;
|
||||
if(this.system.resistance[type]?.resistance) baseDamage = Math.ceil(baseDamage / 2); */
|
||||
if(this.canResist(type, 'immunity')) return 0;
|
||||
if(this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2);
|
||||
|
||||
// const flatReduction = this.system.resistance[type].reduction;
|
||||
const flatReduction = this.getDamageTypeReduction(type);
|
||||
const damage = Math.max(baseDamage - (flatReduction ?? 0), 0);
|
||||
const hpDamage = this.convertDamageToThreshold(damage);
|
||||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, baseDamage, type) === false) return null;
|
||||
|
||||
return hpDamage;
|
||||
}
|
||||
|
||||
canResist(type, resistance) {
|
||||
if(!type) return 0;
|
||||
return type.every(t => this.system.resistance[t]?.[resistance] === true);
|
||||
}
|
||||
|
||||
getDamageTypeReduction(type) {
|
||||
if(!type) return 0;
|
||||
const reduction = Object.entries(this.system.resistance).reduce((a, [index, value]) => type.includes(index) ? Math.min(value.reduction, a) : a, Infinity);
|
||||
return reduction === Infinity ? 0 : reduction;
|
||||
}
|
||||
|
||||
async takeHealing(resources) {
|
||||
resources.forEach(r => (r.value *= -1));
|
||||
await this.modifyResource(resources);
|
||||
|
|
@ -538,7 +485,7 @@ export default class DhpActor extends Actor {
|
|||
updates.actor.resources[`system.resources.${r.type}.value`] = Math.max(
|
||||
Math.min(
|
||||
this.system.resources[r.type].value + r.value,
|
||||
this.system.resources[r.type].maxTotal ?? this.system.resources[r.type].max
|
||||
this.system.resources[r.type].max
|
||||
),
|
||||
0
|
||||
);
|
||||
|
|
@ -553,18 +500,6 @@ export default class DhpActor extends Actor {
|
|||
u.resources,
|
||||
u.target.uuid
|
||||
);
|
||||
/* if (game.user.isGM) {
|
||||
await u.target.update(u.resources);
|
||||
} else {
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateDocument,
|
||||
uuid: u.target.uuid,
|
||||
update: u.resources
|
||||
}
|
||||
});
|
||||
} */
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -582,7 +517,7 @@ export default class DhpActor extends Actor {
|
|||
convertStressDamageToHP(resources) {
|
||||
const stressDamage = resources.find(r => r.type === 'stress'),
|
||||
newValue = this.system.resources.stress.value + stressDamage.value;
|
||||
if (newValue <= this.system.resources.stress.maxTotal) return;
|
||||
if (newValue <= this.system.resources.stress.max) return;
|
||||
const hpDamage = resources.find(r => r.type === 'hitPoints');
|
||||
if (hpDamage) hpDamage.value++;
|
||||
else
|
||||
|
|
|
|||
|
|
@ -80,12 +80,16 @@ export default class DHItem extends foundry.documents.Item {
|
|||
async selectActionDialog(prevEvent) {
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/dialogs/actionSelect.hbs',
|
||||
{ actions: this.system.actionsList }
|
||||
{
|
||||
actions: this.system.actionsList,
|
||||
itemName: this.name
|
||||
}
|
||||
),
|
||||
title = 'Select Action';
|
||||
title = game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectAction');
|
||||
|
||||
return foundry.applications.api.DialogV2.prompt({
|
||||
window: { title },
|
||||
classes: ['daggerheart', 'dh-style'],
|
||||
content,
|
||||
ok: {
|
||||
label: title,
|
||||
|
|
|
|||
|
|
@ -2,15 +2,33 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
|||
async activate(element, options = {}) {
|
||||
let html = options.html;
|
||||
if (element.dataset.tooltip?.startsWith('#item#')) {
|
||||
const item = await foundry.utils.fromUuid(element.dataset.tooltip.slice(6));
|
||||
const splitValues = element.dataset.tooltip.slice(6).split('#action#');
|
||||
const itemUuid = splitValues[0];
|
||||
const actionId = splitValues.length > 1 ? splitValues[1] : null;
|
||||
|
||||
const baseItem = await foundry.utils.fromUuid(itemUuid);
|
||||
const item = actionId ? baseItem.system.actions.find(x => x.id === actionId) : baseItem;
|
||||
if (item) {
|
||||
const type = actionId ? 'action' : item.type;
|
||||
html = await foundry.applications.handlebars.renderTemplate(
|
||||
`systems/daggerheart/templates/ui/tooltip/${item.type}.hbs`,
|
||||
item
|
||||
`systems/daggerheart/templates/ui/tooltip/${type}.hbs`,
|
||||
{
|
||||
item: item,
|
||||
config: CONFIG.DH
|
||||
}
|
||||
);
|
||||
|
||||
this.tooltip.innerHTML = html;
|
||||
options.direction = this._determineItemTooltipDirection(element);
|
||||
}
|
||||
}
|
||||
|
||||
super.activate(element, { ...options, html: html });
|
||||
}
|
||||
|
||||
_determineItemTooltipDirection(element) {
|
||||
const pos = element.getBoundingClientRect();
|
||||
const dirs = this.constructor.TOOLTIP_DIRECTIONS;
|
||||
return dirs[pos.x - this.tooltip.offsetWidth < 0 ? 'DOWN' : 'LEFT'];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,48 @@
|
|||
import { getWidthOfText } from './utils.mjs';
|
||||
|
||||
export default class RegisterHandlebarsHelpers {
|
||||
static registerHelpers() {
|
||||
Handlebars.registerHelper({
|
||||
times: this.times,
|
||||
add: this.add,
|
||||
subtract: this.subtract,
|
||||
includes: this.includes,
|
||||
times: this.times,
|
||||
damageFormula: this.damageFormula,
|
||||
damageSymbols: this.damageSymbols,
|
||||
tertiary: this.tertiary
|
||||
});
|
||||
}
|
||||
|
||||
static times(nr, block) {
|
||||
var accum = '';
|
||||
for (var i = 0; i < nr; ++i) accum += block.fn(i);
|
||||
return accum;
|
||||
}
|
||||
|
||||
static add(a, b) {
|
||||
const aNum = Number.parseInt(a);
|
||||
const bNum = Number.parseInt(b);
|
||||
return (Number.isNaN(aNum) ? 0 : aNum) + (Number.isNaN(bNum) ? 0 : bNum);
|
||||
}
|
||||
|
||||
static subtract(a, b) {
|
||||
const aNum = Number.parseInt(a);
|
||||
const bNum = Number.parseInt(b);
|
||||
return (Number.isNaN(aNum) ? 0 : aNum) - (Number.isNaN(bNum) ? 0 : bNum);
|
||||
}
|
||||
|
||||
static includes(list, item) {
|
||||
return list.includes(item);
|
||||
}
|
||||
|
||||
static times(nr, block) {
|
||||
var accum = '';
|
||||
for (var i = 0; i < nr; ++i) accum += block.fn(i);
|
||||
return accum;
|
||||
}
|
||||
|
||||
static damageFormula(attack, actor) {
|
||||
const traitTotal = actor.system.traits?.[attack.roll.trait]?.value;
|
||||
const instances = [
|
||||
attack.damage.parts.map(x => Roll.replaceFormulaData(x.value.getFormula(), actor)).join(' + '),
|
||||
traitTotal
|
||||
].filter(x => x);
|
||||
|
||||
return instances.join(traitTotal > 0 ? ' + ' : ' - ');
|
||||
}
|
||||
|
||||
static damageSymbols(damageParts) {
|
||||
const symbols = new Set();
|
||||
damageParts.forEach(part => symbols.add(...CONFIG.DH.GENERAL.damageTypes[part.type].icon));
|
||||
return new Handlebars.SafeString(Array.from(symbols).map(symbol => `<i class="fa-solid ${symbol}"></i>`));
|
||||
}
|
||||
|
||||
static tertiary(a, b) {
|
||||
return a ?? b;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,12 +126,10 @@ export const setDiceSoNiceForDualityRoll = (rollResult, advantageState) => {
|
|||
const diceSoNicePresets = getDiceSoNicePresets();
|
||||
rollResult.dice[0].options = { appearance: diceSoNicePresets.hope };
|
||||
rollResult.dice[1].options = { appearance: diceSoNicePresets.fear }; //diceSoNicePresets.fear;
|
||||
if (rollResult.dice[2]) {
|
||||
if (advantageState === true) {
|
||||
rollResult.dice[2].options = { appearance: diceSoNicePresets.advantage };
|
||||
} else if (advantageState === false) {
|
||||
rollResult.dice[2].options = { appearance: diceSoNicePresets.disadvantage };
|
||||
}
|
||||
if (rollResult.dice[2] && advantageState) {
|
||||
rollResult.dice[2].options = {
|
||||
appearance: advantageState === 1 ? diceSoNicePresets.advantage : diceSoNicePresets.disadvantage
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -238,16 +236,7 @@ Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false
|
|||
};
|
||||
|
||||
export const getDamageKey = damage => {
|
||||
switch (damage) {
|
||||
case 3:
|
||||
return 'severe';
|
||||
case 2:
|
||||
return 'major';
|
||||
case 1:
|
||||
return 'minor';
|
||||
case 0:
|
||||
return 'none';
|
||||
}
|
||||
return ['none', 'minor', 'major', 'severe'][damage];
|
||||
};
|
||||
|
||||
export const getDamageLabel = damage => {
|
||||
|
|
@ -255,16 +244,12 @@ export const getDamageLabel = damage => {
|
|||
};
|
||||
|
||||
export const damageKeyToNumber = key => {
|
||||
switch (key) {
|
||||
case 'severe':
|
||||
return 3;
|
||||
case 'major':
|
||||
return 2;
|
||||
case 'minor':
|
||||
return 1;
|
||||
case 'none':
|
||||
return 0;
|
||||
}
|
||||
return {
|
||||
'none': 0,
|
||||
'minor': 1,
|
||||
'major': 2,
|
||||
'severe': 3
|
||||
}[key];
|
||||
};
|
||||
|
||||
export default function constructHTMLButton({
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export const preloadHandlebarsTemplates = async function () {
|
|||
'systems/daggerheart/templates/actionTypes/beastform.hbs',
|
||||
'systems/daggerheart/templates/settings/components/settings-item-line.hbs',
|
||||
'systems/daggerheart/templates/ui/chat/parts/damage-chat.hbs',
|
||||
'systems/daggerheart/templates/ui/chat/parts/target-chat.hbs'
|
||||
'systems/daggerheart/templates/ui/chat/parts/target-chat.hbs',
|
||||
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs'
|
||||
]);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const registerMenuSettings = () => {
|
|||
});
|
||||
|
||||
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement, {
|
||||
scope: 'client',
|
||||
scope: 'world',
|
||||
config: false,
|
||||
type: DhRangeMeasurement
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue