mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-11 19:25:21 +01:00
Added homebrew for armor and weapon fatures (#1166)
Co-authored-by: Chris Ryan <chrisr@blackhole>
This commit is contained in:
parent
f1b6d3851d
commit
2176038ec6
20 changed files with 560 additions and 77 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { SYSTEM } from './module/config/system.mjs';
|
||||
import * as applications from './module/applications/_module.mjs';
|
||||
import * as data from './module/data/_module.mjs';
|
||||
import * as models from './module/data/_module.mjs';
|
||||
import * as documents from './module/documents/_module.mjs';
|
||||
import * as dice from './module/dice/_module.mjs';
|
||||
|
|
@ -26,6 +27,7 @@ Hooks.once('init', () => {
|
|||
CONFIG.DH = SYSTEM;
|
||||
game.system.api = {
|
||||
applications,
|
||||
data,
|
||||
models,
|
||||
documents,
|
||||
dice,
|
||||
|
|
|
|||
|
|
@ -1917,6 +1917,7 @@
|
|||
"roll": "Roll",
|
||||
"rules": "Rules",
|
||||
"types": "Types",
|
||||
"itemFeatures": "Item Features",
|
||||
"questions": "Questions"
|
||||
},
|
||||
"Tiers": {
|
||||
|
|
@ -1934,6 +1935,7 @@
|
|||
"amount": "Amount",
|
||||
"any": "Any",
|
||||
"armor": "Armor",
|
||||
"armorFeatures": "Armor Features",
|
||||
"armors": "Armors",
|
||||
"armorScore": "Armor Score",
|
||||
"activeEffects": "Active Effects",
|
||||
|
|
@ -2046,6 +2048,7 @@
|
|||
"used": "Used",
|
||||
"uses": "Uses",
|
||||
"value": "Value",
|
||||
"weaponFeatures": "Weapon Features",
|
||||
"weapons": "Weapons",
|
||||
"withThing": "With {thing}"
|
||||
},
|
||||
|
|
@ -2276,7 +2279,9 @@
|
|||
},
|
||||
"Homebrew": {
|
||||
"newDowntimeMove": "Downtime Move",
|
||||
"newFeature": "New ItemFeature",
|
||||
"downtimeMoves": "Downtime Moves",
|
||||
"itemFeatures": "Item Features",
|
||||
"nrChoices": "# Moves Per Rest",
|
||||
"resetMovesTitle": "Reset {type} Downtime Moves",
|
||||
"resetMovesText": "Are you sure you want to reset?",
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
settings: { template: 'systems/daggerheart/templates/settings/homebrew-settings/settings.hbs' },
|
||||
domains: { template: 'systems/daggerheart/templates/settings/homebrew-settings/domains.hbs' },
|
||||
types: { template: 'systems/daggerheart/templates/settings/homebrew-settings/types.hbs' },
|
||||
itemTypes: { template: 'systems/daggerheart/templates/settings/homebrew-settings/itemFeatures.hbs' },
|
||||
downtime: { template: 'systems/daggerheart/templates/settings/homebrew-settings/downtime.hbs' },
|
||||
footer: { template: 'systems/daggerheart/templates/settings/homebrew-settings/footer.hbs' }
|
||||
};
|
||||
|
|
@ -60,7 +61,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
main: {
|
||||
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'downtime' }],
|
||||
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'itemFeatures' }, { id: 'downtime' }],
|
||||
initial: 'settings',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
|
|
@ -115,33 +116,53 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
}
|
||||
|
||||
static async addItem(_, target) {
|
||||
await this.settings.updateSource({
|
||||
[`restMoves.${target.dataset.type}.moves.${foundry.utils.randomID()}`]: {
|
||||
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
|
||||
img: 'icons/magic/life/cross-worn-green.webp',
|
||||
description: '',
|
||||
actions: []
|
||||
}
|
||||
});
|
||||
const { type } = target.dataset;
|
||||
if (['shortRest', 'longRest'].includes(type)) {
|
||||
await this.settings.updateSource({
|
||||
[`restMoves.${type}.moves.${foundry.utils.randomID()}`]: {
|
||||
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
|
||||
img: 'icons/magic/life/cross-worn-green.webp',
|
||||
description: '',
|
||||
actions: []
|
||||
}
|
||||
});
|
||||
} else if (['armorFeatures', 'weaponFeatures'].includes(type)) {
|
||||
await this.settings.updateSource({
|
||||
[`itemFeatures.${type}.${foundry.utils.randomID()}`]: {
|
||||
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newFeature'),
|
||||
img: 'icons/magic/life/cross-worn-green.webp',
|
||||
description: '',
|
||||
actions: [],
|
||||
effects: []
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async editItem(_, target) {
|
||||
const move = this.settings.restMoves[target.dataset.type].moves[target.dataset.id];
|
||||
const path = `restMoves.${target.dataset.type}.moves.${target.dataset.id}`;
|
||||
const editedMove = await game.system.api.applications.sheetConfigs.DowntimeConfig.configure(
|
||||
move,
|
||||
path,
|
||||
this.settings
|
||||
);
|
||||
if (!editedMove) return;
|
||||
const { type, id } = target.dataset;
|
||||
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
||||
const path = isDowntime ? `restMoves.${type}.moves.${id}` : `itemFeatures.${type}.${id}`;
|
||||
const featureBase = isDowntime ? this.settings.restMoves[type].moves[id] : this.settings.itemFeatures[type][id];
|
||||
|
||||
await this.updateAction.bind(this)(editedMove, target.dataset.type, target.dataset.id);
|
||||
const editedBase = await game.system.api.applications.sheetConfigs.SettingFeatureConfig.configure(
|
||||
featureBase,
|
||||
path,
|
||||
this.settings,
|
||||
{ hasIcon: isDowntime, hasEffects: !isDowntime }
|
||||
);
|
||||
if (!editedBase) return;
|
||||
|
||||
await this.updateAction.bind(this)(editedBase, target.dataset.type, target.dataset.id);
|
||||
}
|
||||
|
||||
async updateAction(data, type, id) {
|
||||
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
||||
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
||||
await this.settings.updateSource({
|
||||
[`restMoves.${type}.moves.${id}`]: {
|
||||
[`${path}.${id}`]: {
|
||||
actions: data.actions,
|
||||
name: data.name,
|
||||
icon: data.icon,
|
||||
|
|
@ -149,12 +170,16 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
description: data.description
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async removeItem(_, target) {
|
||||
const { type, id } = target.dataset;
|
||||
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
||||
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
||||
await this.settings.updateSource({
|
||||
[`restMoves.${target.dataset.type}.moves.-=${target.dataset.id}`]: null
|
||||
[`${path}.-=${id}`]: null
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ export { default as ActionConfig } from './action-config.mjs';
|
|||
export { default as CharacterSettings } from './character-settings.mjs';
|
||||
export { default as AdversarySettings } from './adversary-settings.mjs';
|
||||
export { default as CompanionSettings } from './companion-settings.mjs';
|
||||
export { default as DowntimeConfig } from './downtimeConfig.mjs';
|
||||
export { default as SettingActiveEffectConfig } from './setting-active-effect-config.mjs';
|
||||
export { default as SettingFeatureConfig } from './setting-feature-config.mjs';
|
||||
export { default as EnvironmentSettings } from './environment-settings.mjs';
|
||||
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
|
||||
export { default as DhTokenConfig } from './token-config.mjs';
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|||
};
|
||||
}
|
||||
|
||||
if (this.action.parent.metadata.isQuantifiable) {
|
||||
if (this.action.parent.metadata?.isQuantifiable) {
|
||||
options.quantity = {
|
||||
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
||||
group: 'Global'
|
||||
|
|
|
|||
|
|
@ -96,6 +96,13 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
});
|
||||
}
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
context.systemFields = context.document.system.schema.fields;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
const partContext = await super._preparePartContext(partId, context);
|
||||
switch (partId) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,227 @@
|
|||
import autocomplete from 'autocompleter';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class SettingActiveEffectConfig extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(effect) {
|
||||
super({});
|
||||
|
||||
this.effect = foundry.utils.deepClone(effect);
|
||||
const ignoredActorKeys = ['config', 'DhEnvironment'];
|
||||
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => {
|
||||
if (!ignoredActorKeys.includes(key)) {
|
||||
const model = game.system.api.models.actors[key];
|
||||
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
|
||||
const group = game.i18n.localize(model.metadata.label);
|
||||
const choices = CONFIG.Token.documentClass
|
||||
.getTrackedAttributeChoices(attributes, model)
|
||||
.map(x => ({ ...x, group: group }));
|
||||
acc.push(...choices);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config'],
|
||||
tag: 'form',
|
||||
position: {
|
||||
width: 560
|
||||
},
|
||||
form: {
|
||||
submitOnChange: false,
|
||||
closeOnSubmit: false,
|
||||
handler: SettingActiveEffectConfig.#onSubmit
|
||||
},
|
||||
actions: {
|
||||
editImage: SettingActiveEffectConfig.#editImage,
|
||||
addChange: SettingActiveEffectConfig.#addChange,
|
||||
deleteChange: SettingActiveEffectConfig.#deleteChange
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
|
||||
tabs: { template: 'templates/generic/tab-navigation.hbs' },
|
||||
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
|
||||
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
|
||||
changes: {
|
||||
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
|
||||
scrollable: ['ol[data-changes]']
|
||||
},
|
||||
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
|
||||
};
|
||||
|
||||
static TABS = {
|
||||
sheet: {
|
||||
tabs: [
|
||||
{ id: 'details', icon: 'fa-solid fa-book' },
|
||||
{ id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' },
|
||||
{ id: 'changes', icon: 'fa-solid fa-gears' }
|
||||
],
|
||||
initial: 'details',
|
||||
labelPrefix: 'EFFECT.TABS'
|
||||
}
|
||||
};
|
||||
|
||||
/**@inheritdoc */
|
||||
async _onFirstRender(context, options) {
|
||||
await super._onFirstRender(context, options);
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.source = this.effect;
|
||||
context.fields = game.system.api.documents.DhActiveEffect.schema.fields;
|
||||
context.systemFields = game.system.api.data.activeEffects.BaseEffect._schema.fields;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
const changeChoices = this.changeChoices;
|
||||
|
||||
htmlElement.querySelectorAll('.effect-change-input').forEach(element => {
|
||||
autocomplete({
|
||||
input: element,
|
||||
fetch: function (text, update) {
|
||||
if (!text) {
|
||||
update(changeChoices);
|
||||
} else {
|
||||
text = text.toLowerCase();
|
||||
var suggestions = changeChoices.filter(n => n.label.toLowerCase().includes(text));
|
||||
update(suggestions);
|
||||
}
|
||||
},
|
||||
render: function (item, search) {
|
||||
const label = game.i18n.localize(item.label);
|
||||
const matchIndex = label.toLowerCase().indexOf(search);
|
||||
|
||||
const beforeText = label.slice(0, matchIndex);
|
||||
const matchText = label.slice(matchIndex, matchIndex + search.length);
|
||||
const after = label.slice(matchIndex + search.length, label.length);
|
||||
|
||||
const element = document.createElement('li');
|
||||
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||
if (item.hint) {
|
||||
element.dataset.tooltip = game.i18n.localize(item.hint);
|
||||
}
|
||||
|
||||
return element;
|
||||
},
|
||||
renderGroup: function (label) {
|
||||
const itemElement = document.createElement('div');
|
||||
itemElement.textContent = game.i18n.localize(label);
|
||||
return itemElement;
|
||||
},
|
||||
onSelect: function (item) {
|
||||
element.value = `system.${item.value}`;
|
||||
},
|
||||
click: e => e.fetch(),
|
||||
customize: function (_input, _inputRect, container) {
|
||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
||||
},
|
||||
minLength: 0
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
if (partId in context.tabs) context.tab = context.tabs[partId];
|
||||
switch (partId) {
|
||||
case 'details':
|
||||
context.isActorEffect = false;
|
||||
context.isItemEffect = true;
|
||||
const useGeneric = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||
).showGenericStatusEffects;
|
||||
if (!useGeneric) {
|
||||
context.statuses = Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
|
||||
value: status.id,
|
||||
label: game.i18n.localize(status.name)
|
||||
}));
|
||||
}
|
||||
break;
|
||||
case 'changes':
|
||||
context.modes = Object.entries(CONST.ACTIVE_EFFECT_MODES).reduce((modes, [key, value]) => {
|
||||
modes[value] = game.i18n.localize(`EFFECT.MODE_${key}`);
|
||||
return modes;
|
||||
}, {});
|
||||
|
||||
context.priorities = ActiveEffectConfig.DEFAULT_PRIORITIES;
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async #onSubmit(event, form, formData) {
|
||||
this.data = foundry.utils.expandObject(formData.object);
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a Document image.
|
||||
* @this {DocumentSheetV2}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #editImage(_event, target) {
|
||||
if (target.nodeName !== 'IMG') {
|
||||
throw new Error('The editImage action is available only for IMG elements.');
|
||||
}
|
||||
|
||||
const attr = target.dataset.edit;
|
||||
const current = foundry.utils.getProperty(this.effect, attr);
|
||||
const fp = new FilePicker.implementation({
|
||||
current,
|
||||
type: 'image',
|
||||
callback: path => (target.src = path),
|
||||
position: {
|
||||
top: this.position.top + 40,
|
||||
left: this.position.left + 10
|
||||
}
|
||||
});
|
||||
|
||||
await fp.browse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new change to the effect's changes array.
|
||||
* @this {ActiveEffectConfig}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #addChange() {
|
||||
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
||||
const changes = Object.values(submitData.changes ?? {});
|
||||
changes.push({});
|
||||
|
||||
this.effect.changes = changes;
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a change from the effect's changes array.
|
||||
* @this {ActiveEffectConfig}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #deleteChange(event) {
|
||||
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
||||
const changes = Object.values(submitData.changes);
|
||||
const row = event.target.closest('li');
|
||||
const index = Number(row.dataset.index) || 0;
|
||||
changes.splice(index, 1);
|
||||
|
||||
this.effect.changes = changes;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async configure(effect, options = {}) {
|
||||
return new Promise(resolve => {
|
||||
const app = new this(effect, options);
|
||||
app.addEventListener('close', () => resolve(app.data), { once: true });
|
||||
app.render({ force: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ import DHActionConfig from './action-config.mjs';
|
|||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class DowntimeConfig extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(move, movePath, settings, options) {
|
||||
export default class SettingFeatureConfig extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(move, movePath, settings, optionalParts, options) {
|
||||
super(options);
|
||||
|
||||
this.move = move;
|
||||
|
|
@ -12,6 +12,10 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
|
|||
this.movePath = movePath;
|
||||
this.actionsPath = `${movePath}.actions`;
|
||||
this.settings = settings;
|
||||
|
||||
const { hasIcon, hasEffects } = optionalParts;
|
||||
this.hasIcon = hasIcon;
|
||||
this.hasEffects = hasEffects;
|
||||
}
|
||||
|
||||
get title() {
|
||||
|
|
@ -30,6 +34,7 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
|
|||
addItem: this.addItem,
|
||||
editItem: this.editItem,
|
||||
removeItem: this.removeItem,
|
||||
addEffect: this.addEffect,
|
||||
resetMoves: this.resetMoves,
|
||||
saveForm: this.saveForm
|
||||
},
|
||||
|
|
@ -41,13 +46,14 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
|
|||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||
main: { template: 'systems/daggerheart/templates/settings/downtime-config/main.hbs' },
|
||||
actions: { template: 'systems/daggerheart/templates/settings/downtime-config/actions.hbs' },
|
||||
effects: { template: 'systems/daggerheart/templates/settings/downtime-config/effects.hbs' },
|
||||
footer: { template: 'systems/daggerheart/templates/settings/downtime-config/footer.hbs' }
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'main' }, { id: 'actions' }],
|
||||
tabs: [{ id: 'main' }, { id: 'actions' }, { id: 'effects' }],
|
||||
initial: 'main',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
|
|
@ -55,6 +61,9 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
|
|||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.tabs = this._filterTabs(context.tabs);
|
||||
context.hasIcon = this.hasIcon;
|
||||
context.hasEffects = this.hasEffects;
|
||||
context.move = this.move;
|
||||
context.move.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(
|
||||
context.move.description
|
||||
|
|
@ -130,13 +139,30 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
|
|||
}
|
||||
|
||||
static async editItem(_, target) {
|
||||
const actionId = target.dataset.id;
|
||||
const action = this.move.actions.get(actionId);
|
||||
await new DHActionConfig(action, async updatedMove => {
|
||||
await this.settings.updateSource({ [`${this.actionsPath}.${actionId}`]: updatedMove });
|
||||
const { type, id } = target.dataset;
|
||||
if (type === 'effect') {
|
||||
const effectIndex = this.move.effects.findIndex(x => x.id === id);
|
||||
const effect = this.move.effects[effectIndex];
|
||||
const updatedEffect =
|
||||
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
|
||||
if (!updatedEffect) return;
|
||||
|
||||
await this.settings.updateSource({
|
||||
[`${this.movePath}.effects`]: this.move.effects.reduce((acc, effect, index) => {
|
||||
acc.push(index === effectIndex ? { ...updatedEffect, id: effect.id } : effect);
|
||||
return acc;
|
||||
}, [])
|
||||
});
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.render();
|
||||
}).render(true);
|
||||
} else {
|
||||
const action = this.move.actions.get(id);
|
||||
await new DHActionConfig(action, async updatedMove => {
|
||||
await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove });
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.render();
|
||||
}).render(true);
|
||||
}
|
||||
}
|
||||
|
||||
static async removeItem(_, target) {
|
||||
|
|
@ -145,16 +171,38 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
|
|||
this.render();
|
||||
}
|
||||
|
||||
static async addEffect(_, target) {
|
||||
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
|
||||
await this.settings.updateSource({
|
||||
[`${this.movePath}.effects`]: [
|
||||
...currentEffects,
|
||||
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
|
||||
]
|
||||
});
|
||||
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static resetMoves() {}
|
||||
|
||||
_filterTabs(tabs) {
|
||||
return this.hasEffects
|
||||
? tabs
|
||||
: Object.keys(tabs).reduce((acc, key) => {
|
||||
if (key !== 'effects') acc[key] = tabs[key];
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onClose(options = {}) {
|
||||
if (!options.submitted) this.move = null;
|
||||
}
|
||||
|
||||
static async configure(move, movePath, settings, options = {}) {
|
||||
static async configure(move, movePath, settings, optionalParts, options = {}) {
|
||||
return new Promise(resolve => {
|
||||
const app = new this(move, movePath, settings, options);
|
||||
const app = new this(move, movePath, settings, optionalParts, options);
|
||||
app.addEventListener('close', () => resolve(app.move), { once: true });
|
||||
app.render({ force: true });
|
||||
});
|
||||
|
|
@ -8,7 +8,7 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
|||
tagifyConfigs: [
|
||||
{
|
||||
selector: '.features-input',
|
||||
options: () => CONFIG.DH.ITEM.armorFeatures,
|
||||
options: () => CONFIG.DH.ITEM.orderedArmorFeatures(),
|
||||
callback: ArmorSheet.#onFeatureSelect
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
|||
tagifyConfigs: [
|
||||
{
|
||||
selector: '.features-input',
|
||||
options: () => CONFIG.DH.ITEM.weaponFeatures,
|
||||
options: () => CONFIG.DH.ITEM.orderedWeaponFeatures(),
|
||||
callback: WeaponSheet.#onFeatureSelect
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -452,6 +452,34 @@ export const armorFeatures = {
|
|||
}
|
||||
};
|
||||
|
||||
export const allArmorFeatures = () => {
|
||||
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||
.armorFeatures;
|
||||
return {
|
||||
...armorFeatures,
|
||||
...Object.keys(homebrewFeatures).reduce((acc, key) => {
|
||||
const feature = homebrewFeatures[key];
|
||||
acc[key] = { ...feature, label: feature.name };
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
};
|
||||
|
||||
export const orderedArmorFeatures = () => {
|
||||
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||
.armorFeatures;
|
||||
const allFeatures = { ...armorFeatures, ...homebrewFeatures };
|
||||
const all = Object.keys(allFeatures).map(key => {
|
||||
const feature = allFeatures[key];
|
||||
return {
|
||||
...feature,
|
||||
id: key,
|
||||
label: feature.label ?? feature.name
|
||||
};
|
||||
});
|
||||
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
|
||||
};
|
||||
|
||||
export const weaponFeatures = {
|
||||
barrier: {
|
||||
label: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.name',
|
||||
|
|
@ -1383,6 +1411,34 @@ export const weaponFeatures = {
|
|||
}
|
||||
};
|
||||
|
||||
export const allWeaponFeatures = () => {
|
||||
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||
.weaponFeatures;
|
||||
return {
|
||||
...weaponFeatures,
|
||||
...Object.keys(homebrewFeatures).reduce((acc, key) => {
|
||||
const feature = homebrewFeatures[key];
|
||||
acc[key] = { ...feature, label: feature.name };
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
};
|
||||
|
||||
export const orderedWeaponFeatures = () => {
|
||||
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||
.weaponFeatures;
|
||||
const allFeatures = { ...weaponFeatures, ...homebrewFeatures };
|
||||
const all = Object.keys(allFeatures).map(key => {
|
||||
const feature = allFeatures[key];
|
||||
return {
|
||||
...feature,
|
||||
id: key,
|
||||
label: feature.label ?? feature.name
|
||||
};
|
||||
});
|
||||
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
|
||||
};
|
||||
|
||||
export const featureTypes = {
|
||||
ancestry: {
|
||||
id: 'ancestry',
|
||||
|
|
|
|||
|
|
@ -30,4 +30,24 @@ export default class BaseEffect extends foundry.abstract.TypeDataModel {
|
|||
})
|
||||
};
|
||||
}
|
||||
|
||||
static getDefaultObject() {
|
||||
return {
|
||||
name: 'New Effect',
|
||||
id: foundry.utils.randomID(),
|
||||
disabled: false,
|
||||
img: 'icons/magic/life/heart-cross-blue.webp',
|
||||
description: '',
|
||||
statuses: [],
|
||||
changes: [],
|
||||
system: {
|
||||
rangeDependence: {
|
||||
enabled: false,
|
||||
type: CONFIG.DH.GENERAL.rangeInclusion.withinRange.id,
|
||||
target: CONFIG.DH.GENERAL.otherTargetTypes.hostile.id,
|
||||
range: CONFIG.DH.GENERAL.range.melee.id
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import AttachableItem from './attachableItem.mjs';
|
||||
import { armorFeatures } from '../../config/itemConfig.mjs';
|
||||
|
||||
export default class DHArmor extends AttachableItem {
|
||||
/** @inheritDoc */
|
||||
|
|
@ -25,7 +24,7 @@ export default class DHArmor extends AttachableItem {
|
|||
new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.ITEM.armorFeatures,
|
||||
choices: CONFIG.DH.ITEM.allArmorFeatures,
|
||||
blank: true
|
||||
}),
|
||||
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||
|
|
@ -60,13 +59,14 @@ export default class DHArmor extends AttachableItem {
|
|||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
||||
const changedArmorFeatures = changes.system?.armorFeatures ?? [];
|
||||
const removedFeatures = this.armorFeatures.filter(x => changedArmorFeatures.every(y => y.value !== x.value));
|
||||
if (changes.system?.armorFeatures) {
|
||||
const removed = this.armorFeatures.filter(x => !changes.system.armorFeatures.includes(x));
|
||||
const added = changes.system.armorFeatures.filter(x => !this.armorFeatures.includes(x));
|
||||
const added = changedArmorFeatures.filter(x => this.armorFeatures.every(y => y.value !== x.value));
|
||||
|
||||
const effectIds = [];
|
||||
const actionIds = [];
|
||||
for (var feature of removed) {
|
||||
for (var feature of removedFeatures) {
|
||||
effectIds.push(...feature.effectIds);
|
||||
actionIds.push(...feature.actionIds);
|
||||
}
|
||||
|
|
@ -76,8 +76,9 @@ export default class DHArmor extends AttachableItem {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
|
||||
for (const feature of added) {
|
||||
const featureData = armorFeatures[feature.value];
|
||||
const featureData = allFeatures[feature.value];
|
||||
if (featureData.effects?.length > 0) {
|
||||
const embeddedItems = await this.parent.createEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
|
|
@ -91,7 +92,7 @@ export default class DHArmor extends AttachableItem {
|
|||
}
|
||||
|
||||
const newActions = {};
|
||||
if (featureData.actions?.length > 0) {
|
||||
if (featureData.actions?.length > 0 || featureData.actions?.size > 0) {
|
||||
for (let action of featureData.actions) {
|
||||
const embeddedEffects = await this.parent.createEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
|
|
@ -110,10 +111,12 @@ export default class DHArmor extends AttachableItem {
|
|||
{
|
||||
...cls.getSourceConfig(this),
|
||||
...action,
|
||||
type: action.type,
|
||||
_id: actionId,
|
||||
name: game.i18n.localize(action.name),
|
||||
description: game.i18n.localize(action.description),
|
||||
effects: embeddedEffects.map(x => ({ _id: x.id }))
|
||||
effects: embeddedEffects.map(x => ({ _id: x.id })),
|
||||
systemPath: 'actions'
|
||||
},
|
||||
{ parent: this }
|
||||
);
|
||||
|
|
@ -126,6 +129,10 @@ export default class DHArmor extends AttachableItem {
|
|||
}
|
||||
}
|
||||
|
||||
_onUpdate(a, b, c) {
|
||||
super._onUpdate(a, b, c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of localized tags based on this item's type-specific properties.
|
||||
* @returns {string[]} An array of localized tag strings.
|
||||
|
|
@ -145,7 +152,8 @@ export default class DHArmor extends AttachableItem {
|
|||
*/
|
||||
_getLabels() {
|
||||
const labels = [];
|
||||
if(this.baseScore) labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`)
|
||||
if (this.baseScore)
|
||||
labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`);
|
||||
return labels;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export default class DHWeapon extends AttachableItem {
|
|||
new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.ITEM.weaponFeatures,
|
||||
choices: CONFIG.DH.ITEM.allWeaponFeatures,
|
||||
blank: true
|
||||
}),
|
||||
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||
|
|
@ -116,13 +116,14 @@ export default class DHWeapon extends AttachableItem {
|
|||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
||||
const changedWeaponFeatures = changes.system?.weaponFeatures ?? [];
|
||||
const removedFeatures = this.weaponFeatures.filter(x => changedWeaponFeatures.every(y => y.value !== x.value));
|
||||
if (changes.system?.weaponFeatures) {
|
||||
const removed = this.weaponFeatures.filter(x => !changes.system.weaponFeatures.includes(x));
|
||||
const added = changes.system.weaponFeatures.filter(x => !this.weaponFeatures.includes(x));
|
||||
const added = changedWeaponFeatures.filter(x => this.weaponFeatures.every(y => y.value !== x.value));
|
||||
|
||||
const removedEffectsUpdate = [];
|
||||
const removedActionsUpdate = [];
|
||||
for (let weaponFeature of removed) {
|
||||
for (let weaponFeature of removedFeatures) {
|
||||
removedEffectsUpdate.push(...weaponFeature.effectIds);
|
||||
removedActionsUpdate.push(...weaponFeature.actionIds);
|
||||
}
|
||||
|
|
@ -133,8 +134,9 @@ export default class DHWeapon extends AttachableItem {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
|
||||
for (let weaponFeature of added) {
|
||||
const featureData = CONFIG.DH.ITEM.weaponFeatures[weaponFeature.value];
|
||||
const featureData = allFeatures[weaponFeature.value];
|
||||
if (featureData.effects?.length > 0) {
|
||||
const embeddedItems = await this.parent.createEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
|
|
@ -148,7 +150,7 @@ export default class DHWeapon extends AttachableItem {
|
|||
}
|
||||
|
||||
const newActions = {};
|
||||
if (featureData.actions?.length > 0) {
|
||||
if (featureData.actions?.length > 0 || featureData.actions?.size > 0) {
|
||||
for (let action of featureData.actions) {
|
||||
const embeddedEffects = await this.parent.createEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
|
|
@ -170,10 +172,12 @@ export default class DHWeapon extends AttachableItem {
|
|||
{
|
||||
...cls.getSourceConfig(this),
|
||||
...action,
|
||||
type: action.type,
|
||||
_id: actionId,
|
||||
name: game.i18n.localize(action.name),
|
||||
description: game.i18n.localize(action.description),
|
||||
effects: embeddedEffects.map(x => ({ _id: x.id }))
|
||||
effects: embeddedEffects.map(x => ({ _id: x.id })),
|
||||
systemPath: 'actions'
|
||||
},
|
||||
{ parent: this }
|
||||
);
|
||||
|
|
|
|||
|
|
@ -115,7 +115,35 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
label: new fields.StringField({ required: true, label: 'DAGGERHEART.GENERAL.label' }),
|
||||
description: new fields.StringField()
|
||||
})
|
||||
)
|
||||
),
|
||||
itemFeatures: new fields.SchemaField({
|
||||
weaponFeatures: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
img: new fields.FilePathField({
|
||||
initial: 'icons/magic/life/cross-worn-green.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
description: new fields.HTMLField(),
|
||||
actions: new ActionsField(),
|
||||
effects: new fields.ArrayField(new fields.ObjectField())
|
||||
})
|
||||
),
|
||||
armorFeatures: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
img: new fields.FilePathField({
|
||||
initial: 'icons/magic/life/cross-worn-green.webp',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
description: new fields.HTMLField(),
|
||||
actions: new ActionsField(),
|
||||
effects: new fields.ArrayField(new fields.ObjectField())
|
||||
})
|
||||
)
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
15
templates/settings/downtime-config/effects.hbs
Normal file
15
templates/settings/downtime-config/effects.hbs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<section
|
||||
class='tab {{tabs.effects.cssClass}} {{tabs.effects.id}}'
|
||||
data-tab='{{tabs.effects.id}}'
|
||||
data-group='{{tabs.effects.group}}'
|
||||
>
|
||||
<fieldset class="one-column">
|
||||
<legend>{{localize "DAGGERHEART.GENERAL.Effect.plural"}} <a><i class="fa-solid fa-plus icon-button" data-action="addEffect"></i></a></legend>
|
||||
|
||||
<div class="settings-items">
|
||||
{{#each move.effects}}
|
||||
{{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" id=this.id type="effect" }}
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
</section>
|
||||
|
|
@ -3,11 +3,13 @@
|
|||
data-tab='{{tabs.main.id}}'
|
||||
data-group='{{tabs.main.group}}'
|
||||
>
|
||||
<fieldset class="one-column">
|
||||
<legend>{{localize "Icon"}}</legend>
|
||||
{{#if hasIcon}}
|
||||
<fieldset class="one-column">
|
||||
<legend>{{localize "Icon"}}</legend>
|
||||
|
||||
<input type="text" name="icon" value="{{move.icon}}" />
|
||||
</fieldset>
|
||||
<input type="text" name="icon" value="{{move.icon}}" />
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
|
||||
<fieldset class="one-column">
|
||||
<legend>{{localize "Description"}}</legend>
|
||||
|
|
|
|||
35
templates/settings/homebrew-settings/itemFeatures.hbs
Normal file
35
templates/settings/homebrew-settings/itemFeatures.hbs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<section
|
||||
class="tab {{tabs.itemFeatures.cssClass}} {{tabs.itemFeatures.id}}"
|
||||
data-tab="{{tabs.itemFeatures.id}}"
|
||||
data-group="{{tabs.itemFeatures.group}}"
|
||||
>
|
||||
<div class="two-columns even">
|
||||
<fieldset class="start-align">
|
||||
<legend>
|
||||
{{localize "DAGGERHEART.GENERAL.weaponFeatures"}}
|
||||
<a data-action="addItem" data-type="weaponFeatures"><i class="fa-solid fa-plus"></i></a>
|
||||
<a data-action="resetMoves" data-type="weaponFeatures"><i class="fa-solid fa-arrow-rotate-left"></i></a>
|
||||
</legend>
|
||||
|
||||
<div class="settings-items">
|
||||
{{#each settingFields._source.itemFeatures.weaponFeatures as |feature id|}}
|
||||
{{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" this type="weaponFeatures" id=id }}
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="start-align">
|
||||
<legend>
|
||||
{{localize "DAGGERHEART.GENERAL.armorFeatures"}}
|
||||
<a data-action="addItem" data-type="armorFeatures"><i class="fa-solid fa-plus"></i></a>
|
||||
<a data-action="resetMoves" data-type="armorFeatures"><i class="fa-solid fa-arrow-rotate-left"></i></a>
|
||||
</legend>
|
||||
|
||||
<div class="settings-items">
|
||||
{{#each settingFields._source.itemFeatures.armorFeatures as |feature id|}}
|
||||
{{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" this type="armorFeatures" id=id }}
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -8,24 +8,24 @@
|
|||
</header>
|
||||
<ol class="scrollable" data-changes>
|
||||
{{#each source.changes as |change i|}}
|
||||
{{#with ../fields.changes.element.fields as |changeFields|}}
|
||||
<li data-index="{{i}}">
|
||||
<div class="key">
|
||||
<input type="text" class="effect-change-input" name="{{concat "changes." i ".key"}}" value="{{change.key}}" />
|
||||
</div>
|
||||
<div class="mode">
|
||||
{{formInput changeFields.mode name=(concat "changes." i ".mode") value=change.mode choices=@root.modes}}
|
||||
</div>
|
||||
<div class="value">
|
||||
{{formInput changeFields.value name=(concat "changes." i ".value") value=change.value}}
|
||||
</div>
|
||||
<div class="priority">
|
||||
{{formInput changeFields.priority name=(concat "changes." i ".priority") value=change.priority
|
||||
placeholder=(lookup ../../priorities change.mode)}}
|
||||
</div>
|
||||
<div class="controls"><a data-action="deleteChange"><i class="fa-solid fa-trash"></i></a></div>
|
||||
</li>
|
||||
{{/with}}
|
||||
{{#with ../fields.changes.element.fields as |changeFields|}}
|
||||
<li data-index="{{i}}">
|
||||
<div class="key">
|
||||
<input type="text" class="effect-change-input" name="{{concat "changes." i ".key"}}" value="{{change.key}}" />
|
||||
</div>
|
||||
<div class="mode">
|
||||
{{formInput changeFields.mode name=(concat "changes." i ".mode") value=change.mode choices=@root.modes}}
|
||||
</div>
|
||||
<div class="value">
|
||||
{{formInput changeFields.value name=(concat "changes." i ".value") value=change.value}}
|
||||
</div>
|
||||
<div class="priority">
|
||||
{{formInput changeFields.priority name=(concat "changes." i ".priority") value=change.priority
|
||||
placeholder=(lookup ../../priorities change.mode)}}
|
||||
</div>
|
||||
<div class="controls"><a data-action="deleteChange"><i class="fa-solid fa-trash"></i></a></div>
|
||||
</li>
|
||||
{{/with}}
|
||||
{{/each}}
|
||||
</ol>
|
||||
</section>
|
||||
|
|
@ -2,10 +2,10 @@
|
|||
<fieldset class="one-column">
|
||||
<legend>{{localize "DAGGERHEART.ACTIVEEFFECT.Config.rangeDependence.title"}}</legend>
|
||||
|
||||
{{formGroup document.system.schema.fields.rangeDependence.fields.enabled value=source.system.rangeDependence.enabled localize=true }}
|
||||
{{formGroup document.system.schema.fields.rangeDependence.fields.type value=source.system.rangeDependence.type localize=true }}
|
||||
{{formGroup document.system.schema.fields.rangeDependence.fields.target value=source.system.rangeDependence.target localize=true }}
|
||||
{{formGroup document.system.schema.fields.rangeDependence.fields.range value=source.system.rangeDependence.range localize=true }}
|
||||
{{formGroup systemFields.rangeDependence.fields.enabled value=source.system.rangeDependence.enabled localize=true }}
|
||||
{{formGroup systemFields.rangeDependence.fields.type value=source.system.rangeDependence.type localize=true }}
|
||||
{{formGroup systemFields.rangeDependence.fields.target value=source.system.rangeDependence.target localize=true }}
|
||||
{{formGroup systemFields.rangeDependence.fields.range value=source.system.rangeDependence.range localize=true }}
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="one-column">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue