mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 23:13:39 +02:00
Merged main
This commit is contained in:
commit
b87e630a0a
84 changed files with 2046 additions and 452 deletions
|
|
@ -7,6 +7,7 @@ import EffectAction from './effectAction.mjs';
|
|||
import HealingAction from './healingAction.mjs';
|
||||
import MacroAction from './macroAction.mjs';
|
||||
import SummonAction from './summonAction.mjs';
|
||||
import TransformAction from './transformAction.mjs';
|
||||
|
||||
export const actionsTypes = {
|
||||
base: BaseAction,
|
||||
|
|
@ -17,5 +18,6 @@ export const actionsTypes = {
|
|||
summon: SummonAction,
|
||||
effect: EffectAction,
|
||||
macro: MacroAction,
|
||||
beastform: BeastformAction
|
||||
beastform: BeastformAction,
|
||||
transform: TransformAction
|
||||
};
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
async executeWorkflow(config) {
|
||||
for (const [key, part] of this.workflow) {
|
||||
if (Hooks.call(`${CONFIG.DH.id}.pre${key.capitalize()}Action`, this, config) === false) return;
|
||||
if ((await part.execute(config)) === false) return;
|
||||
if ((await part.execute(config)) === false) return false;
|
||||
if (Hooks.call(`${CONFIG.DH.id}.post${key.capitalize()}Action`, this, config) === false) return;
|
||||
}
|
||||
}
|
||||
|
|
@ -224,7 +224,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
}
|
||||
|
||||
// Execute the Action Worflow in order based of schema fields
|
||||
await this.executeWorkflow(config);
|
||||
const result = await this.executeWorkflow(config);
|
||||
if (result === false) return;
|
||||
|
||||
await config.resourceUpdates.updateResources();
|
||||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||
|
|
|
|||
5
module/data/action/transformAction.mjs
Normal file
5
module/data/action/transformAction.mjs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import DHBaseAction from './baseAction.mjs';
|
||||
|
||||
export default class DHTransformAction extends DHBaseAction {
|
||||
static extraSchemas = [...super.extraSchemas, 'transform'];
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import DHAdversarySettings from '../../applications/sheets-configs/adversary-set
|
|||
import { ActionField } from '../fields/actionField.mjs';
|
||||
import { commonActorRules } from './base.mjs';
|
||||
import DhCreature from './creature.mjs';
|
||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||
import { bonusField } from '../fields/actorField.mjs';
|
||||
import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs';
|
||||
import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs';
|
||||
|
||||
|
|
@ -65,10 +65,6 @@ export default class DhpAdversary extends DhCreature {
|
|||
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
|
||||
})
|
||||
}),
|
||||
resources: new fields.SchemaField({
|
||||
hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
|
||||
stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true)
|
||||
}),
|
||||
rules: new fields.SchemaField({
|
||||
...commonActorRules()
|
||||
}),
|
||||
|
|
@ -191,6 +187,7 @@ export default class DhpAdversary extends DhCreature {
|
|||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData();
|
||||
this.attack.roll.isStandardAttack = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
const textData = Object.keys(changes.system.resources).reduce((acc, key) => {
|
||||
const resource = changes.system.resources[key];
|
||||
if (resource.value !== undefined && resource.value !== this.resources[key].value) {
|
||||
acc.push(getScrollTextData(this.resources, resource, key));
|
||||
acc.push(getScrollTextData(this.parent, resource, key));
|
||||
}
|
||||
|
||||
return acc;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
|||
import DhLevelData from '../levelData.mjs';
|
||||
import { commonActorRules } from './base.mjs';
|
||||
import DhCreature from './creature.mjs';
|
||||
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||
import { attributeField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||
import { ActionField } from '../fields/actionField.mjs';
|
||||
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
||||
|
||||
|
|
@ -27,28 +27,6 @@ export default class DhCharacter extends DhCreature {
|
|||
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
resources: new fields.SchemaField({
|
||||
hitPoints: resourceField(
|
||||
0,
|
||||
0,
|
||||
'DAGGERHEART.GENERAL.HitPoints.plural',
|
||||
true,
|
||||
'DAGGERHEART.ACTORS.Character.maxHPBonus'
|
||||
),
|
||||
stress: resourceField(6, 0, 'DAGGERHEART.GENERAL.stress', true),
|
||||
hope: new fields.SchemaField(
|
||||
{
|
||||
value: new fields.NumberField({
|
||||
initial: 2,
|
||||
min: 0,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.hope'
|
||||
}),
|
||||
isReversed: new fields.BooleanField({ initial: false })
|
||||
},
|
||||
{ label: 'DAGGERHEART.GENERAL.hope' }
|
||||
)
|
||||
}),
|
||||
traits: new fields.SchemaField({
|
||||
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
|
||||
strength: attributeField('DAGGERHEART.CONFIG.Traits.strength.name'),
|
||||
|
|
@ -609,6 +587,7 @@ export default class DhCharacter extends DhCreature {
|
|||
}
|
||||
|
||||
prepareBaseData() {
|
||||
super.prepareBaseData();
|
||||
this.evasion += this.class.value?.system?.evasion ?? 0;
|
||||
|
||||
const currentLevel = this.levelData.level.current;
|
||||
|
|
@ -680,6 +659,7 @@ export default class DhCharacter extends DhCreature {
|
|||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData();
|
||||
let baseHope = this.resources.hope.value;
|
||||
if (this.companion) {
|
||||
for (let levelKey in this.companion.system.levelData.levelups) {
|
||||
|
|
@ -699,6 +679,7 @@ export default class DhCharacter extends DhCreature {
|
|||
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
|
||||
|
||||
this.resources.armor = {
|
||||
label: 'DAGGERHEART.GENERAL.armor',
|
||||
value: this.armor?.system?.marks?.value ?? 0,
|
||||
max: this.armorScore,
|
||||
isReversed: true
|
||||
|
|
@ -740,7 +721,8 @@ export default class DhCharacter extends DhCreature {
|
|||
const newHopeMax = this.system.resources.hope.max + diff;
|
||||
const newHopeValue = Math.min(newHopeMax, this.system.resources.hope.value);
|
||||
if (newHopeValue != this.system.resources.hope.value) {
|
||||
if (!changes.system.resources) changes.system.resources = { hope: { value: 0 } };
|
||||
if (!changes.system.resources.hope) changes.system.resources.hope = { value: 0 };
|
||||
|
||||
changes.system.resources.hope = {
|
||||
...changes.system.resources.hope,
|
||||
value: changes.system.resources.hope.value + newHopeValue
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
|||
import { ActionField } from '../fields/actionField.mjs';
|
||||
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
||||
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||
import { bonusField } from '../fields/actorField.mjs';
|
||||
|
||||
export default class DhCompanion extends DhCreature {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
|
||||
|
|
@ -26,10 +26,6 @@ export default class DhCompanion extends DhCreature {
|
|||
return {
|
||||
...super.defineSchema(),
|
||||
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
|
||||
resources: new fields.SchemaField({
|
||||
stress: resourceField(3, 0, 'DAGGERHEART.GENERAL.stress', true),
|
||||
hope: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.hope' })
|
||||
}),
|
||||
evasion: new fields.NumberField({
|
||||
required: true,
|
||||
min: 1,
|
||||
|
|
@ -127,6 +123,7 @@ export default class DhCompanion extends DhCreature {
|
|||
}
|
||||
|
||||
prepareBaseData() {
|
||||
super.prepareBaseData();
|
||||
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;
|
||||
|
||||
for (let levelKey in this.levelData.levelups) {
|
||||
|
|
@ -163,6 +160,7 @@ export default class DhCompanion extends DhCreature {
|
|||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData();
|
||||
/* Partner Related Setup */
|
||||
if (this.partner) {
|
||||
this.levelData.level.changed = this.partner.system.levelData.level.current;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { ResourcesField } from '../fields/actorField.mjs';
|
||||
import BaseDataActor from './base.mjs';
|
||||
|
||||
export default class DhCreature extends BaseDataActor {
|
||||
|
|
@ -7,6 +8,7 @@ export default class DhCreature extends BaseDataActor {
|
|||
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
resources: new ResourcesField(this.metadata.type),
|
||||
advantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ export { default as DamageField } from './damageField.mjs';
|
|||
export { default as RollField } from './rollField.mjs';
|
||||
export { default as MacroField } from './macroField.mjs';
|
||||
export { default as SummonField } from './summonField.mjs';
|
||||
export { default as TransformField } from './transformField.mjs';
|
||||
|
|
|
|||
103
module/data/fields/action/transformField.mjs
Normal file
103
module/data/fields/action/transformField.mjs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
export default class DHSummonField extends fields.SchemaField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 130;
|
||||
|
||||
constructor(options = {}, context = {}) {
|
||||
const transformFields = {
|
||||
actorUUID: new fields.DocumentUUIDField({
|
||||
type: 'Actor',
|
||||
required: true
|
||||
}),
|
||||
resourceRefresh: new fields.SchemaField({
|
||||
hitPoints: new fields.BooleanField({ initial: true }),
|
||||
stress: new fields.BooleanField({ initial: true })
|
||||
})
|
||||
};
|
||||
super(transformFields, options, context);
|
||||
}
|
||||
|
||||
static async execute() {
|
||||
if (!this.transform.actorUUID) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.noTransformActor'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const baseActor = await foundry.utils.fromUuid(this.transform.actorUUID);
|
||||
if (!baseActor) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.transformActorMissing'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canvas.scene) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.canvasError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.actor.prototypeToken.actorLink) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.actorLinkError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.actor.token) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.prototypeError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const actor = await DHSummonField.getWorldActor(baseActor);
|
||||
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||
const tokenSize = actor?.system.metadata.usesSize ? tokenSizes[actor.system.size] : actor.prototypeToken.width;
|
||||
|
||||
await this.actor.token.update(
|
||||
{ ...actor.prototypeToken.toJSON(), actorId: actor.id, width: tokenSize, height: tokenSize },
|
||||
{ diff: false, recursive: false, noHook: true }
|
||||
);
|
||||
|
||||
if (this.actor.token.combatant) {
|
||||
this.actor.token.combatant.update({ actorId: actor.id, img: actor.prototypeToken.texture.src });
|
||||
}
|
||||
|
||||
const marks = { hitPoints: 0, stress: 0 };
|
||||
if (!this.transform.resourceRefresh.hitPoints) {
|
||||
marks.hitPoints = Math.min(
|
||||
this.actor.system.resources.hitPoints.value,
|
||||
this.actor.token.actor.system.resources.hitPoints.max - 1
|
||||
);
|
||||
}
|
||||
if (!this.transform.resourceRefresh.stress) {
|
||||
marks.stress = Math.min(
|
||||
this.actor.system.resources.stress.value,
|
||||
this.actor.token.actor.system.resources.stress.max - 1
|
||||
);
|
||||
}
|
||||
if (marks.hitPoints || marks.stress) {
|
||||
this.actor.token.actor.update({
|
||||
'system.resources': {
|
||||
hitPoints: { value: marks.hitPoints },
|
||||
stress: { value: marks.stress }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const prevPosition = { ...this.actor.sheet.position };
|
||||
this.actor.sheet.close();
|
||||
this.actor.token.actor.sheet.render({ force: true, position: prevPosition });
|
||||
}
|
||||
|
||||
/* Check for any available instances of the actor present in the world, or create a world actor based on compendium */
|
||||
static async getWorldActor(baseActor) {
|
||||
if (!baseActor.inCompendium) return baseActor;
|
||||
|
||||
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
|
||||
if (dataType && baseActor.img === dataType.DEFAULT_ICON) {
|
||||
const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
|
||||
if (worldActorCopy) return worldActorCopy;
|
||||
}
|
||||
|
||||
const worldActor = await game.system.api.documents.DhpActor.create(baseActor.toObject());
|
||||
return worldActor;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,22 +6,6 @@ const attributeField = label =>
|
|||
tierMarked: new fields.BooleanField({ initial: false })
|
||||
});
|
||||
|
||||
const resourceField = (max = 0, initial = 0, label, reverse = false, maxLabel) =>
|
||||
new fields.SchemaField(
|
||||
{
|
||||
value: new fields.NumberField({ initial: initial, min: 0, integer: true, label }),
|
||||
max: new fields.NumberField({
|
||||
initial: max,
|
||||
integer: true,
|
||||
label:
|
||||
maxLabel ??
|
||||
game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) })
|
||||
}),
|
||||
isReversed: new fields.BooleanField({ initial: reverse })
|
||||
},
|
||||
{ label }
|
||||
);
|
||||
|
||||
const stressDamageReductionRule = localizationPath =>
|
||||
new fields.SchemaField({
|
||||
cost: new fields.NumberField({
|
||||
|
|
@ -37,4 +21,67 @@ const bonusField = label =>
|
|||
dice: new fields.ArrayField(new fields.StringField(), { label: `${game.i18n.localize(label)} Dice` })
|
||||
});
|
||||
|
||||
export { attributeField, resourceField, stressDamageReductionRule, bonusField };
|
||||
/**
|
||||
* Field used for actor resources. It is a resource that validates dynamically based on the config.
|
||||
* Because "max" may be defined during runtime, we don't attempt to clamp the maximum value.
|
||||
*/
|
||||
class ResourcesField extends fields.TypedObjectField {
|
||||
constructor(actorType) {
|
||||
super(
|
||||
new fields.SchemaField({
|
||||
value: new fields.NumberField({ min: 0, initial: 0, integer: true }),
|
||||
// Some resources allow changing max. A null max means its the default
|
||||
max: new fields.NumberField({ initial: null, integer: true, nullable: true })
|
||||
})
|
||||
);
|
||||
this.actorType = actorType;
|
||||
}
|
||||
|
||||
getInitialValue() {
|
||||
const resources = CONFIG.DH.RESOURCE[this.actorType].all;
|
||||
return Object.values(resources).reduce((result, resource) => {
|
||||
result[resource.id] = {
|
||||
value: resource.initial,
|
||||
max: null
|
||||
};
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
_validateKey(key) {
|
||||
return key in CONFIG.DH.RESOURCE[this.actorType].all;
|
||||
}
|
||||
|
||||
_cleanType(value, options) {
|
||||
value = super._cleanType(value, options);
|
||||
|
||||
// If not partial, ensure all data exists
|
||||
if (!options.partial) {
|
||||
value = foundry.utils.mergeObject(this.getInitialValue(), value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Initializes the original source data, returning prepared data */
|
||||
initialize(...args) {
|
||||
const data = super.initialize(...args);
|
||||
const resources = CONFIG.DH.RESOURCE[this.actorType].all;
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
// TypedObjectField only calls _validateKey when persisting, so we also call it here
|
||||
if (!this._validateKey(key)) {
|
||||
delete value[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add basic prepared data.
|
||||
const resource = resources[key];
|
||||
value.label = resource.label;
|
||||
value.isReversed = resources[key].reverse;
|
||||
value.max = typeof resource.max === 'number' ? (value.max ?? resource.max) : null;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
export { attributeField, ResourcesField, stressDamageReductionRule, bonusField };
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
const armorChanged =
|
||||
changed.system?.marks?.value !== undefined && changed.system.marks.value !== this.marks.value;
|
||||
if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') {
|
||||
const armorData = getScrollTextData(this.parent.parent.system.resources, changed.system.marks, 'armor');
|
||||
const armorData = getScrollTextData(this.parent.parent, changed.system.marks, 'armor');
|
||||
options.scrollingTextData = [armorData];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,24 +112,14 @@ export default class DHWeapon extends AttachableItem {
|
|||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
|
||||
const tier = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`);
|
||||
const trait = game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.attack.roll.trait].label);
|
||||
const range = game.i18n.localize(`DAGGERHEART.CONFIG.Range.${this.attack.range}.name`);
|
||||
const damage = Roll.replaceFormulaData(this.attack.damageFormula, this.parent.parent ?? this.parent);
|
||||
const burden = game.i18n.localize(CONFIG.DH.GENERAL.burden[this.burden].label);
|
||||
|
||||
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
|
||||
const features = this.weaponFeatures.map(x => allFeatures[x.value]).filter(x => x);
|
||||
|
||||
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/weapon/description.hbs',
|
||||
{
|
||||
features,
|
||||
tier,
|
||||
trait,
|
||||
range,
|
||||
damage,
|
||||
burden
|
||||
item: this,
|
||||
features
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
export default class DhAppearance extends foundry.abstract.DataModel {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.SETTINGS.Appearance'];
|
||||
|
||||
static sfxSchema = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
class: new foundry.data.fields.StringField({
|
||||
nullable: true,
|
||||
initial: null,
|
||||
blank: true,
|
||||
choices: CONFIG.DH.GENERAL.diceSoNiceSFXClasses
|
||||
})
|
||||
});
|
||||
|
||||
static defineSchema() {
|
||||
const { StringField, ColorField, BooleanField, SchemaField } = foundry.data.fields;
|
||||
|
||||
|
|
@ -15,7 +25,10 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
|||
colorset: new StringField({ initial: 'inspired', required: true, blank: false }),
|
||||
material: new StringField({ initial: 'metal', required: true, blank: false }),
|
||||
system: new StringField({ initial: 'standard', required: true, blank: false }),
|
||||
font: new StringField({ initial: 'auto', required: true, blank: false })
|
||||
font: new StringField({ initial: 'auto', required: true, blank: false }),
|
||||
sfx: new SchemaField({
|
||||
higher: DhAppearance.sfxSchema()
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
@ -30,7 +43,10 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
|||
hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }),
|
||||
fear: diceStyle({ fg: '#000000', bg: '#0032b1', outline: '#ffffff', edge: '#000000' }),
|
||||
advantage: diceStyle({ fg: '#ffffff', bg: '#008000', outline: '#000000', edge: '#ffffff' }),
|
||||
disadvantage: diceStyle({ fg: '#000000', bg: '#b30000', outline: '#ffffff', edge: '#000000' })
|
||||
disadvantage: diceStyle({ fg: '#000000', bg: '#b30000', outline: '#ffffff', edge: '#000000' }),
|
||||
sfx: new SchemaField({
|
||||
critical: DhAppearance.sfxSchema()
|
||||
})
|
||||
}),
|
||||
extendCharacterDescriptions: new BooleanField(),
|
||||
extendAdversaryDescriptions: new BooleanField(),
|
||||
|
|
@ -65,4 +81,48 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
|||
showGenericStatusEffects: new BooleanField({ initial: true })
|
||||
};
|
||||
}
|
||||
|
||||
get diceSoNiceData() {
|
||||
const globalOverrides = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides);
|
||||
const getSFX = (baseClientData, overrideKey) => {
|
||||
if (!globalOverrides.diceSoNice.sfx.overrideEnabled) return baseClientData;
|
||||
const overrideData = globalOverrides.diceSoNice.sfx[overrideKey];
|
||||
const clientData = foundry.utils.deepClone(baseClientData);
|
||||
return Object.keys(clientData).reduce((acc, key) => {
|
||||
const data = clientData[key];
|
||||
acc[key] = Object.keys(data).reduce((acc, dataKey) => {
|
||||
const value = data[dataKey];
|
||||
acc[dataKey] = value ? value : overrideData[key][dataKey];
|
||||
return acc;
|
||||
}, {});
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
return {
|
||||
...this.diceSoNice,
|
||||
sfx: getSFX(this.diceSoNice.sfx, 'global'),
|
||||
hope: {
|
||||
...this.diceSoNice.hope,
|
||||
sfx: getSFX(this.diceSoNice.hope.sfx, 'hope')
|
||||
},
|
||||
fear: {
|
||||
...this.diceSoNice.fear,
|
||||
sfx: getSFX(this.diceSoNice.fear.sfx, 'fear')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Invoked by the setting when data changes */
|
||||
handleChange() {
|
||||
if (this.displayFear) {
|
||||
if (ui.resources) {
|
||||
if (this.displayFear === 'hide') ui.resources.close({ allowed: true });
|
||||
else ui.resources.render({ force: true });
|
||||
}
|
||||
}
|
||||
|
||||
const globalOverrides = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides);
|
||||
globalOverrides.diceSoNiceSFXUpdate(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
module/data/settings/GlobalOverrides.mjs
Normal file
55
module/data/settings/GlobalOverrides.mjs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import DhAppearance from './Appearance.mjs';
|
||||
|
||||
/**
|
||||
* A setting to handle cases where we want to allow the GM to set a global default for client settings.
|
||||
*/
|
||||
export default class DhGlobalOverrides extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
diceSoNice: new fields.SchemaField({
|
||||
sfx: new fields.SchemaField({
|
||||
overrideEnabled: new fields.BooleanField(),
|
||||
global: new fields.SchemaField({
|
||||
critical: DhAppearance.sfxSchema()
|
||||
}),
|
||||
hope: new fields.SchemaField({
|
||||
higher: DhAppearance.sfxSchema()
|
||||
}),
|
||||
fear: new fields.SchemaField({
|
||||
higher: DhAppearance.sfxSchema()
|
||||
})
|
||||
})
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
async diceSoNiceSFXUpdate(appearanceSettings, enabled) {
|
||||
if (!game.user.isGM) return;
|
||||
|
||||
const newEnabled = enabled !== undefined ? enabled : this.diceSoNice.sfx.overrideEnabled;
|
||||
if (newEnabled) {
|
||||
const newOverrides = foundry.utils.mergeObject(this.toObject(), {
|
||||
diceSoNice: {
|
||||
sfx: {
|
||||
overrideEnabled: true,
|
||||
global: appearanceSettings.diceSoNice.sfx,
|
||||
hope: appearanceSettings.diceSoNice.hope.sfx,
|
||||
fear: appearanceSettings.diceSoNice.fear.sfx
|
||||
}
|
||||
}
|
||||
});
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides, newOverrides);
|
||||
} else {
|
||||
const newOverrides = {
|
||||
...this.toObject(),
|
||||
diceSoNice: {
|
||||
sfx: {
|
||||
overrideEnabled: false
|
||||
}
|
||||
}
|
||||
};
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides, newOverrides);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,6 +145,16 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
description: new fields.StringField()
|
||||
})
|
||||
),
|
||||
resources: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
resources: new fields.TypedObjectField(new fields.EmbeddedDataField(Resource))
|
||||
}),
|
||||
{
|
||||
initial: {
|
||||
character: { resources: {} }
|
||||
}
|
||||
}
|
||||
),
|
||||
itemFeatures: new fields.SchemaField({
|
||||
weaponFeatures: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
|
|
@ -185,4 +195,117 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
/** Invoked by the setting when data changes */
|
||||
handleChange() {
|
||||
if (this.maxFear) {
|
||||
if (ui.resources) ui.resources.render({ force: true });
|
||||
}
|
||||
|
||||
this.refreshConfig();
|
||||
this.#resetActors();
|
||||
}
|
||||
|
||||
/** Update config values based on homebrew data. Make sure the references don't change */
|
||||
refreshConfig() {
|
||||
for (const [actorType, actorData] of Object.entries(this.resources)) {
|
||||
const config = CONFIG.DH.RESOURCE[actorType];
|
||||
for (const key of Object.keys(config.all)) {
|
||||
delete config.all[key];
|
||||
}
|
||||
Object.assign(config.all, {
|
||||
...Object.entries(actorData.resources).reduce((result, [key, value]) => {
|
||||
result[key] = value.toObject();
|
||||
result[key].id = key;
|
||||
return result;
|
||||
}, {}),
|
||||
...config.custom,
|
||||
...config.base
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a reset and non-forced re-render on all given actors (if given)
|
||||
* or all world actors and actors in all scenes to show immediate results for a changed setting.
|
||||
*/
|
||||
#resetActors() {
|
||||
const actors = new Set(
|
||||
[
|
||||
game.actors.contents,
|
||||
game.scenes.contents.flatMap(s => s.tokens.contents).flatMap(t => t.actor ?? [])
|
||||
].flat()
|
||||
);
|
||||
for (const actor of actors) {
|
||||
for (const app of Object.values(actor.apps)) {
|
||||
for (const element of app.element?.querySelectorAll('prose-mirror.active')) {
|
||||
element.open = false; // This triggers a save
|
||||
}
|
||||
}
|
||||
|
||||
actor.reset();
|
||||
actor.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Resource extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
initial: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
initial: 0,
|
||||
min: 0,
|
||||
label: 'DAGGERHEART.GENERAL.initial'
|
||||
}),
|
||||
max: new fields.NumberField({
|
||||
nullable: true,
|
||||
initial: null,
|
||||
min: 0,
|
||||
label: 'DAGGERHEART.GENERAL.max'
|
||||
}),
|
||||
label: new fields.StringField({ label: 'DAGGERHEART.GENERAL.label' }),
|
||||
images: new fields.SchemaField({
|
||||
full: imageIconField('fa solid fa-circle'),
|
||||
empty: imageIconField('fa-regular fa-circle')
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
static getDefaultResourceData = label => {
|
||||
const images = Resource.schema.fields.images.getInitialValue();
|
||||
return {
|
||||
initial: 0,
|
||||
max: 0,
|
||||
label: label ?? '',
|
||||
images
|
||||
};
|
||||
};
|
||||
|
||||
static getDefaultImageData = imageKey => {
|
||||
return Resource.schema.fields.images.fields[imageKey].getInitialValue();
|
||||
};
|
||||
}
|
||||
|
||||
const imageIconField = defaultValue =>
|
||||
new foundry.data.fields.SchemaField(
|
||||
{
|
||||
value: new foundry.data.fields.StringField({
|
||||
initial: defaultValue,
|
||||
label: 'DAGGERHEART.SETTINGS.Homebrew.FIELDS.resources.resources.value.label'
|
||||
}),
|
||||
isIcon: new foundry.data.fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.SETTINGS.Homebrew.FIELDS.resources.resources.isIcon.label'
|
||||
}),
|
||||
noColorFilter: new foundry.data.fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Homebrew.FIELDS.resources.resources.noColorFilter.label'
|
||||
})
|
||||
},
|
||||
{ required: true }
|
||||
);
|
||||
|
|
|
|||
12
module/data/settings/Metagaming.mjs
Normal file
12
module/data/settings/Metagaming.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export default class DhMetagaming extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
hideObserverPermissionInChat: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hideObserverPermissionInChat.label',
|
||||
hint: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hideObserverPermissionInChat.hint'
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
export { default as DhAppearance } from './Appearance.mjs';
|
||||
export { default as DhAutomation } from './Automation.mjs';
|
||||
export { default as DhHomebrew } from './Homebrew.mjs';
|
||||
export { default as DhMetagaming } from './Metagaming.mjs';
|
||||
export { default as DhVariantRules } from './VariantRules.mjs';
|
||||
export { default as DhGlobalOverrides } from './GlobalOverrides.mjs';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue