mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
Merge branch 'main' into 157-feature-simplify-and-refactor-item-sheets
This commit is contained in:
commit
175ae7dd23
38 changed files with 2378 additions and 808 deletions
|
|
@ -14,4 +14,4 @@ export { default as DhpChatMessage } from './chatMessage.mjs';
|
|||
export { default as DhpEnvironment } from './sheets/environment.mjs';
|
||||
export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs';
|
||||
|
||||
export * as api from './sheets/api/_modules.mjs';
|
||||
export * as api from './sheets/api/_modules.mjs';
|
||||
|
|
|
|||
|
|
@ -300,9 +300,9 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
return {
|
||||
armor: characterGuide.suggestedArmor ?? null,
|
||||
primaryWeapon: characterGuide.suggestedPrimaryWeapon ?? null,
|
||||
secondaryWeapon:
|
||||
{ ...characterGuide.suggestedSecondaryWeapon, uuid: characterGuide.suggestedSecondaryWeapon.uuid } ??
|
||||
null,
|
||||
secondaryWeapon: characterGuide.suggestedSecondaryWeapon
|
||||
? { ...characterGuide.suggestedSecondaryWeapon, uuid: characterGuide.suggestedSecondaryWeapon.uuid }
|
||||
: null,
|
||||
inventory: {
|
||||
take: inventory.take ?? [],
|
||||
choiceA:
|
||||
|
|
@ -399,11 +399,19 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
const data = TextEditor.getDragEventData(event);
|
||||
const item = await foundry.utils.fromUuid(data.uuid);
|
||||
if (item.type === 'ancestry' && event.target.closest('.ancestry-card')) {
|
||||
this.setup.ancestry = { ...item, uuid: item.uuid };
|
||||
this.setup.ancestry = {
|
||||
...item,
|
||||
effects: Array.from(item.effects).map(x => x.toObject()),
|
||||
uuid: item.uuid
|
||||
};
|
||||
} else if (item.type === 'community' && event.target.closest('.community-card')) {
|
||||
this.setup.community = { ...item, uuid: item.uuid };
|
||||
this.setup.community = {
|
||||
...item,
|
||||
effects: Array.from(item.effects).map(x => x.toObject()),
|
||||
uuid: item.uuid
|
||||
};
|
||||
} else if (item.type === 'class' && event.target.closest('.class-card')) {
|
||||
this.setup.class = { ...item, uuid: item.uuid };
|
||||
this.setup.class = { ...item, effects: Array.from(item.effects).map(x => x.toObject()), uuid: item.uuid };
|
||||
this.setup.subclass = {};
|
||||
this.setup.domainCards = {
|
||||
[foundry.utils.randomID()]: {},
|
||||
|
|
@ -417,7 +425,11 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
return;
|
||||
}
|
||||
|
||||
this.setup.subclass = { ...item, uuid: item.uuid };
|
||||
this.setup.subclass = {
|
||||
...item,
|
||||
effects: Array.from(item.effects).map(x => x.toObject()),
|
||||
uuid: item.uuid
|
||||
};
|
||||
} else if (item.type === 'domainCard' && event.target.closest('.domain-card')) {
|
||||
if (!this.setup.class.uuid) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.MissingClass'));
|
||||
|
|
|
|||
|
|
@ -10,9 +10,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
/* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */
|
||||
const html = await super.renderHTML();
|
||||
|
||||
if (
|
||||
this.type === 'dualityRoll'
|
||||
) {
|
||||
if (this.type === 'dualityRoll') {
|
||||
html.classList.add('duality');
|
||||
const dualityResult = this.system.dualityResult;
|
||||
if (dualityResult === DHDualityRoll.dualityResult.hope) html.classList.add('hope');
|
||||
|
|
|
|||
339
module/applications/countdowns.mjs
Normal file
339
module/applications/countdowns.mjs
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
import { countdownTypes } from '../config/generalConfig.mjs';
|
||||
import { GMUpdateEvent, RefreshType, socketEvent } from '../helpers/socket.mjs';
|
||||
import OwnershipSelection from './ownershipSelection.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(basePath) {
|
||||
super({});
|
||||
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.format('DAGGERHEART.Countdown.Title', {
|
||||
type: game.i18n.localize(`DAGGERHEART.Countdown.Types.${this.basePath}`)
|
||||
});
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart', 'dh-style', 'countdown'],
|
||||
tag: 'form',
|
||||
position: { width: 740, height: 700 },
|
||||
window: {
|
||||
frame: true,
|
||||
title: 'Countdowns',
|
||||
resizable: true,
|
||||
minimizable: true
|
||||
},
|
||||
actions: {
|
||||
addCountdown: this.addCountdown,
|
||||
removeCountdown: this.removeCountdown,
|
||||
editImage: this.onEditImage,
|
||||
openOwnership: this.openOwnership,
|
||||
openCountdownOwnership: this.openCountdownOwnership
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true }
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
countdowns: {
|
||||
template: 'systems/daggerheart/templates/views/countdowns.hbs',
|
||||
scrollable: ['.expanded-view']
|
||||
}
|
||||
};
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement.querySelectorAll('.mini-countdown-container').forEach(element => {
|
||||
element.addEventListener('click', event => this.updateCountdownValue.bind(this)(event, true));
|
||||
element.addEventListener('contextmenu', event => this.updateCountdownValue.bind(this)(event, false));
|
||||
});
|
||||
}
|
||||
|
||||
async _onFirstRender(context, options) {
|
||||
super._onFirstRender(context, options);
|
||||
|
||||
this.element.querySelector('.expanded-view').classList.toggle('hidden');
|
||||
this.element.querySelector('.minimized-view').classList.toggle('hidden');
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
const countdownData = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
||||
|
||||
context.isGM = game.user.isGM;
|
||||
context.base = this.basePath;
|
||||
|
||||
context.canCreate = countdownData.playerOwnership[game.user.id].value === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
|
||||
context.source = {
|
||||
...countdownData,
|
||||
countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => {
|
||||
const countdown = countdownData.countdowns[key];
|
||||
|
||||
const ownershipValue = countdown.playerOwnership[game.user.id].value;
|
||||
if (ownershipValue > CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
|
||||
acc[key] = { ...countdown, canEdit: ownershipValue === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER };
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
context.systemFields = countdownData.schema.fields;
|
||||
context.countdownFields = context.systemFields.countdowns.element.fields;
|
||||
context.minimized = this.minimized || _options.isFirstRender;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async updateData(event, _, formData) {
|
||||
const data = foundry.utils.expandObject(formData.object);
|
||||
const newSetting = foundry.utils.mergeObject(
|
||||
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns).toObject(),
|
||||
data
|
||||
);
|
||||
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, newSetting);
|
||||
this.render();
|
||||
} else {
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateSetting,
|
||||
uuid: SYSTEM.SETTINGS.gameSettings.Countdowns,
|
||||
update: newSetting
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async minimize() {
|
||||
await super.minimize();
|
||||
|
||||
this.element.querySelector('.expanded-view').classList.toggle('hidden');
|
||||
this.element.querySelector('.minimized-view').classList.toggle('hidden');
|
||||
}
|
||||
|
||||
async maximize() {
|
||||
if (this.minimized) {
|
||||
const settings = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
||||
if (settings.playerOwnership[game.user.id].value <= CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED) {
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.Countdown.Notifications.LimitedOwnership'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.element.querySelector('.expanded-view').classList.toggle('hidden');
|
||||
this.element.querySelector('.minimized-view').classList.toggle('hidden');
|
||||
}
|
||||
|
||||
await super.maximize();
|
||||
}
|
||||
|
||||
async updateSetting(update) {
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, update);
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.Countdown,
|
||||
application: `${this.basePath}-countdowns`
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
} else {
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateSetting,
|
||||
uuid: SYSTEM.SETTINGS.gameSettings.Countdowns,
|
||||
update: update,
|
||||
refresh: { refreshType: RefreshType.Countdown, application: `${this.basePath}-countdowns` }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static onEditImage(_, target) {
|
||||
const setting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
||||
const current = setting.countdowns[target.dataset.countdown].img;
|
||||
const fp = new FilePicker({
|
||||
current,
|
||||
type: 'image',
|
||||
callback: async path => this.updateImage.bind(this)(path, target.dataset.countdown),
|
||||
top: this.position.top + 40,
|
||||
left: this.position.left + 10
|
||||
});
|
||||
return fp.browse();
|
||||
}
|
||||
|
||||
async updateImage(path, countdown) {
|
||||
const setting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
|
||||
await setting.updateSource({
|
||||
[`${this.basePath}.countdowns.${countdown}.img`]: path
|
||||
});
|
||||
|
||||
await this.updateSetting(setting);
|
||||
}
|
||||
|
||||
static openOwnership(_, target) {
|
||||
new Promise((resolve, reject) => {
|
||||
const setting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
||||
const ownership = { default: setting.ownership.default, players: setting.playerOwnership };
|
||||
new OwnershipSelection(resolve, reject, this.title, ownership).render(true);
|
||||
}).then(async ownership => {
|
||||
const setting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
|
||||
await setting.updateSource({
|
||||
[`${this.basePath}.ownership`]: ownership
|
||||
});
|
||||
|
||||
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, setting.toObject());
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
static openCountdownOwnership(_, target) {
|
||||
const countdownId = target.dataset.countdown;
|
||||
new Promise((resolve, reject) => {
|
||||
const countdown = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath]
|
||||
.countdowns[countdownId];
|
||||
const ownership = { default: countdown.ownership.default, players: countdown.playerOwnership };
|
||||
new OwnershipSelection(resolve, reject, countdown.name, ownership).render(true);
|
||||
}).then(async ownership => {
|
||||
const setting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
|
||||
await setting.updateSource({
|
||||
[`${this.basePath}.countdowns.${countdownId}.ownership`]: ownership
|
||||
});
|
||||
|
||||
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, setting);
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
async updateCountdownValue(event, increase) {
|
||||
const countdownSetting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
|
||||
const countdown = countdownSetting[this.basePath].countdowns[event.currentTarget.dataset.countdown];
|
||||
|
||||
if (countdown.playerOwnership[game.user.id] < CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentValue = countdown.progress.current;
|
||||
|
||||
if (increase && currentValue === countdown.progress.max) return;
|
||||
if (!increase && currentValue === 0) return;
|
||||
|
||||
await countdownSetting.updateSource({
|
||||
[`${this.basePath}.countdowns.${event.currentTarget.dataset.countdown}.progress.current`]: increase
|
||||
? currentValue + 1
|
||||
: currentValue - 1
|
||||
});
|
||||
|
||||
await this.updateSetting(countdownSetting.toObject());
|
||||
}
|
||||
|
||||
static async addCountdown() {
|
||||
const countdownSetting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
|
||||
await countdownSetting.updateSource({
|
||||
[`${this.basePath}.countdowns.${foundry.utils.randomID()}`]: {
|
||||
name: game.i18n.localize('DAGGERHEART.Countdown.NewCountdown'),
|
||||
ownership: game.user.isGM
|
||||
? {}
|
||||
: {
|
||||
players: {
|
||||
[game.user.id]: { type: CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await this.updateSetting(countdownSetting.toObject());
|
||||
}
|
||||
|
||||
static async removeCountdown(_, target) {
|
||||
const countdownSetting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
|
||||
const countdownName = countdownSetting[this.basePath].countdowns[target.dataset.countdown].name;
|
||||
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.localize('DAGGERHEART.Countdown.RemoveCountdownTitle')
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.Countdown.RemoveCountdownText', { name: countdownName })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
await countdownSetting.updateSource({ [`${this.basePath}.countdowns.-=${target.dataset.countdown}`]: null });
|
||||
|
||||
await this.updateSetting(countdownSetting.toObject());
|
||||
}
|
||||
|
||||
async open() {
|
||||
await this.render(true);
|
||||
if (
|
||||
Object.keys(game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath].countdowns)
|
||||
.length > 0
|
||||
) {
|
||||
this.minimize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class NarrativeCountdowns extends Countdowns {
|
||||
constructor() {
|
||||
super('narrative');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'narrative-countdowns'
|
||||
};
|
||||
}
|
||||
|
||||
export class EncounterCountdowns extends Countdowns {
|
||||
constructor() {
|
||||
super('encounter');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'encounter-countdowns'
|
||||
};
|
||||
}
|
||||
|
||||
export const registerCountdownApplicationHooks = () => {
|
||||
const updateCountdowns = async shouldProgress => {
|
||||
if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation).countdowns) {
|
||||
const countdownSetting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
|
||||
for (let countdownCategoryKey in countdownSetting) {
|
||||
const countdownCategory = countdownSetting[countdownCategoryKey];
|
||||
for (let countdownKey in countdownCategory.countdowns) {
|
||||
const countdown = countdownCategory.countdowns[countdownKey];
|
||||
|
||||
if (shouldProgress(countdown)) {
|
||||
await countdownSetting.updateSource({
|
||||
[`${countdownCategoryKey}.countdowns.${countdownKey}.progress.current`]:
|
||||
countdown.progress.current - 1
|
||||
});
|
||||
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, countdownSetting);
|
||||
foundry.applications.instances.get(`${countdownCategoryKey}-countdowns`)?.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Hooks.on(SYSTEM.HOOKS.characterAttack, async () => {
|
||||
updateCountdowns(countdown => {
|
||||
return (
|
||||
countdown.progress.type.value === countdownTypes.characterAttack.id && countdown.progress.current > 0
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Hooks.on(SYSTEM.HOOKS.spotlight, async () => {
|
||||
updateCountdowns(countdown => {
|
||||
return countdown.progress.type.value === countdownTypes.spotlight.id && countdown.progress.current > 0;
|
||||
});
|
||||
});
|
||||
};
|
||||
72
module/applications/ownershipSelection.mjs
Normal file
72
module/applications/ownershipSelection.mjs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class OwnershipSelection extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(resolve, reject, name, ownership) {
|
||||
super({});
|
||||
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
this.name = name;
|
||||
this.ownership = ownership;
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'ownership-selection'],
|
||||
position: {
|
||||
width: 600,
|
||||
height: 'auto'
|
||||
},
|
||||
form: { handler: this.updateData }
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
selection: {
|
||||
template: 'systems/daggerheart/templates/views/ownershipSelection.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
get title() {
|
||||
return game.i18n.format('DAGGERHEART.OwnershipSelection.Title', { name: this.name });
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.ownershipOptions = Object.keys(CONST.DOCUMENT_OWNERSHIP_LEVELS).map(level => ({
|
||||
value: CONST.DOCUMENT_OWNERSHIP_LEVELS[level],
|
||||
label: game.i18n.localize(`OWNERSHIP.${level}`)
|
||||
}));
|
||||
context.ownership = {
|
||||
default: this.ownership.default,
|
||||
players: Object.keys(this.ownership.players).reduce((acc, x) => {
|
||||
const user = game.users.get(x);
|
||||
if (!user.isGM) {
|
||||
acc[x] = {
|
||||
img: user.character?.img ?? 'icons/svg/cowled.svg',
|
||||
name: user.name,
|
||||
ownership: this.ownership.players[x].value
|
||||
};
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async updateData(event, _, formData) {
|
||||
const { ownership } = foundry.utils.expandObject(formData.object);
|
||||
|
||||
this.resolve(ownership);
|
||||
this.close(true);
|
||||
}
|
||||
|
||||
async close(fromSave) {
|
||||
if (!fromSave) {
|
||||
this.reject();
|
||||
}
|
||||
|
||||
await super.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { defaultLevelTiers, DhLevelTiers } from '../data/levelTier.mjs';
|
||||
import DhCountdowns from '../data/countdowns.mjs';
|
||||
import {
|
||||
DhAppearance,
|
||||
DhAutomation,
|
||||
|
|
@ -130,4 +131,10 @@ const registerNonConfigSettings = () => {
|
|||
ui.combat.render({ force: true });
|
||||
}
|
||||
});
|
||||
|
||||
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, {
|
||||
scope: 'world',
|
||||
config: false,
|
||||
type: DhCountdowns
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -371,7 +371,11 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
static async attackRoll(event, button) {
|
||||
const weapon = await fromUuid(button.dataset.weapon);
|
||||
if (!weapon) return;
|
||||
weapon.use(event);
|
||||
|
||||
const wasUsed = await weapon.use(event);
|
||||
if (wasUsed) {
|
||||
Hooks.callAll(SYSTEM.HOOKS.characterAttack, {});
|
||||
}
|
||||
}
|
||||
|
||||
static openLevelUp() {
|
||||
|
|
|
|||
|
|
@ -364,6 +364,20 @@ export const abilityCosts = {
|
|||
}
|
||||
};
|
||||
|
||||
export const countdownTypes = {
|
||||
spotlight: {
|
||||
id: 'spotlight',
|
||||
label: 'DAGGERHEART.Countdown.Type.Spotlight'
|
||||
},
|
||||
characterAttack: {
|
||||
id: 'characterAttack',
|
||||
label: 'DAGGERHEART.Countdown.Type.CharacterAttack'
|
||||
},
|
||||
custom: {
|
||||
id: 'custom',
|
||||
label: 'DAGGERHEART.Countdown.Type.Custom'
|
||||
}
|
||||
};
|
||||
export const rollTypes = {
|
||||
weapon: {
|
||||
id: 'weapon',
|
||||
|
|
|
|||
4
module/config/hooksConfig.mjs
Normal file
4
module/config/hooksConfig.mjs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export const hooks = {
|
||||
characterAttack: 'characterAttackHook',
|
||||
spotlight: 'spotlightHook'
|
||||
};
|
||||
|
|
@ -26,7 +26,8 @@ export const gameSettings = {
|
|||
Resources: {
|
||||
Fear: 'ResourcesFear'
|
||||
},
|
||||
LevelTiers: 'LevelTiers'
|
||||
LevelTiers: 'LevelTiers',
|
||||
Countdowns: 'Countdowns'
|
||||
};
|
||||
|
||||
export const DualityRollColor = {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import * as DOMAIN from './domainConfig.mjs';
|
|||
import * as ACTOR from './actorConfig.mjs';
|
||||
import * as ITEM from './itemConfig.mjs';
|
||||
import * as SETTINGS from './settingsConfig.mjs';
|
||||
import { hooks as HOOKS } from './hooksConfig.mjs';
|
||||
import * as EFFECTS from './effectConfig.mjs';
|
||||
import * as ACTIONS from './actionConfig.mjs';
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ export const SYSTEM = {
|
|||
ACTOR,
|
||||
ITEM,
|
||||
SETTINGS,
|
||||
HOOKS,
|
||||
EFFECTS,
|
||||
ACTIONS,
|
||||
ACTIONS
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
import DHAbilityUse from "./abilityUse.mjs";
|
||||
import DHAdversaryRoll from "./adversaryRoll.mjs";
|
||||
import DHDamageRoll from "./damageRoll.mjs";
|
||||
import DHDualityRoll from "./dualityRoll.mjs";
|
||||
import DHAbilityUse from './abilityUse.mjs';
|
||||
import DHAdversaryRoll from './adversaryRoll.mjs';
|
||||
import DHDamageRoll from './damageRoll.mjs';
|
||||
import DHDualityRoll from './dualityRoll.mjs';
|
||||
|
||||
export {
|
||||
DHAbilityUse,
|
||||
DHAdversaryRoll,
|
||||
DHDamageRoll,
|
||||
DHDualityRoll,
|
||||
}
|
||||
export { DHAbilityUse, DHAdversaryRoll, DHDamageRoll, DHDualityRoll };
|
||||
|
||||
export const config = {
|
||||
abilityUse: DHAbilityUse,
|
||||
adversaryRoll: DHAdversaryRoll,
|
||||
damageRoll: DHDamageRoll,
|
||||
dualityRoll: DHDualityRoll,
|
||||
};
|
||||
abilityUse: DHAbilityUse,
|
||||
adversaryRoll: DHAdversaryRoll,
|
||||
damageRoll: DHDamageRoll,
|
||||
dualityRoll: DHDualityRoll
|
||||
};
|
||||
|
|
|
|||
139
module/data/countdowns.mjs
Normal file
139
module/data/countdowns.mjs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import { countdownTypes } from '../config/generalConfig.mjs';
|
||||
import { RefreshType, socketEvent } from '../helpers/socket.mjs';
|
||||
|
||||
export default class DhCountdowns extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
narrative: new fields.EmbeddedDataField(DhCountdownData),
|
||||
encounter: new fields.EmbeddedDataField(DhCountdownData)
|
||||
};
|
||||
}
|
||||
|
||||
static CountdownCategories = { narrative: 'narrative', combat: 'combat' };
|
||||
}
|
||||
|
||||
class DhCountdownData extends foundry.abstract.DataModel {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Countdown']; // Nots ure why this won't work. Setting labels manually for now
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
countdowns: new fields.TypedObjectField(new fields.EmbeddedDataField(DhCountdown)),
|
||||
ownership: new fields.SchemaField({
|
||||
default: new fields.NumberField({
|
||||
required: true,
|
||||
choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS),
|
||||
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE
|
||||
}),
|
||||
players: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
type: new fields.NumberField({
|
||||
required: true,
|
||||
choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS),
|
||||
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
get playerOwnership() {
|
||||
return Array.from(game.users).reduce((acc, user) => {
|
||||
acc[user.id] = {
|
||||
value: user.isGM
|
||||
? CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER
|
||||
: this.ownership.players[user.id] && this.ownership.players[user.id].type !== -1
|
||||
? this.ownership.players[user.id].type
|
||||
: this.ownership.default,
|
||||
isGM: user.isGM
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
||||
class DhCountdown extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
name: new fields.StringField({
|
||||
required: true,
|
||||
label: 'DAGGERHEART.Countdown.FIELDS.countdowns.element.name.label'
|
||||
}),
|
||||
img: new fields.FilePathField({
|
||||
categories: ['IMAGE'],
|
||||
base64: false,
|
||||
initial: 'icons/magic/time/hourglass-yellow-green.webp'
|
||||
}),
|
||||
ownership: new fields.SchemaField({
|
||||
default: new fields.NumberField({
|
||||
required: true,
|
||||
choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS),
|
||||
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE
|
||||
}),
|
||||
players: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
type: new fields.NumberField({
|
||||
required: true,
|
||||
choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS),
|
||||
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||
})
|
||||
})
|
||||
)
|
||||
}),
|
||||
progress: new fields.SchemaField({
|
||||
current: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.Countdown.FIELDS.countdowns.element.progress.current.label'
|
||||
}),
|
||||
max: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.Countdown.FIELDS.countdowns.element.progress.max.label'
|
||||
}),
|
||||
type: new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true,
|
||||
choices: countdownTypes,
|
||||
initial: countdownTypes.spotlight.id,
|
||||
label: 'DAGGERHEART.Countdown.FIELDS.countdowns.element.progress.type.value.label'
|
||||
}),
|
||||
label: new fields.StringField({
|
||||
label: 'DAGGERHEART.Countdown.FIELDS.countdowns.element.progress.type.label.label'
|
||||
})
|
||||
})
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
get playerOwnership() {
|
||||
return Array.from(game.users).reduce((acc, user) => {
|
||||
acc[user.id] = {
|
||||
value: user.isGM
|
||||
? CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER
|
||||
: this.ownership.players[user.id] && this.ownership.players[user.id].type !== -1
|
||||
? this.ownership.players[user.id].type
|
||||
: this.ownership.default,
|
||||
isGM: user.isGM
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
||||
export const registerCountdownHooks = () => {
|
||||
Hooks.on(socketEvent.Refresh, ({ refreshType, application }) => {
|
||||
if (refreshType === RefreshType.Countdown) {
|
||||
foundry.applications.instances.get(application)?.render();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -10,45 +10,44 @@
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||
/** @returns {ItemDataModelMetadata}*/
|
||||
static get metadata() {
|
||||
return {
|
||||
label: "Base Item",
|
||||
type: "base",
|
||||
hasDescription: false,
|
||||
isQuantifiable: false,
|
||||
};
|
||||
}
|
||||
/** @returns {ItemDataModelMetadata}*/
|
||||
static get metadata() {
|
||||
return {
|
||||
label: 'Base Item',
|
||||
type: 'base',
|
||||
hasDescription: false,
|
||||
isQuantifiable: false
|
||||
};
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
static defineSchema() {
|
||||
const schema = {};
|
||||
/** @inheritDoc */
|
||||
static defineSchema() {
|
||||
const schema = {};
|
||||
|
||||
if (this.metadata.hasDescription)
|
||||
schema.description = new fields.HTMLField({ required: true, nullable: true });
|
||||
if (this.metadata.hasDescription) schema.description = new fields.HTMLField({ required: true, nullable: true });
|
||||
|
||||
if (this.metadata.isQuantifiable)
|
||||
schema.quantity = new fields.NumberField({ integer: true, initial: 1, min: 0, required: true });
|
||||
if (this.metadata.isQuantifiable)
|
||||
schema.quantity = new fields.NumberField({ integer: true, initial: 1, min: 0, required: true });
|
||||
|
||||
return schema;
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient access to the item's actor, if it exists.
|
||||
* @returns {foundry.documents.Actor | null}
|
||||
*/
|
||||
get actor() {
|
||||
return this.parent.actor;
|
||||
}
|
||||
/**
|
||||
* Convenient access to the item's actor, if it exists.
|
||||
* @returns {foundry.documents.Actor | null}
|
||||
*/
|
||||
get actor() {
|
||||
return this.parent.actor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
|
||||
* @param {object} [options] - Options which modify the getRollData method.
|
||||
* @returns {object}
|
||||
*/
|
||||
getRollData(options = {}) {
|
||||
const actorRollData = this.actor?.getRollData() ?? {};
|
||||
const data = { ...actorRollData, item: { ...this } };
|
||||
return data;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
|
||||
* @param {object} [options] - Options which modify the getRollData method.
|
||||
* @returns {object}
|
||||
*/
|
||||
getRollData(options = {}) {
|
||||
const actorRollData = this.actor?.getRollData() ?? {};
|
||||
const data = { ...actorRollData, item: { ...this } };
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,16 +19,16 @@ export default class DHClass extends BaseDataItem {
|
|||
return {
|
||||
...super.defineSchema(),
|
||||
domains: new fields.ArrayField(new fields.StringField(), { max: 2 }),
|
||||
classItems: new ForeignDocumentUUIDArrayField({type: 'Item', required: false}),
|
||||
classItems: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true }),
|
||||
hopeFeatures: new foundry.data.fields.ArrayField(new ActionField()),
|
||||
classFeatures: new foundry.data.fields.ArrayField(new ActionField()),
|
||||
subclasses: new ForeignDocumentUUIDArrayField({type: 'Item', required: false}),
|
||||
subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||
inventory: new fields.SchemaField({
|
||||
take: new ForeignDocumentUUIDArrayField({type: 'Item', required: false}),
|
||||
choiceA: new ForeignDocumentUUIDArrayField({type: 'Item', required: false}),
|
||||
choiceB: new ForeignDocumentUUIDArrayField({type: 'Item', required: false}),
|
||||
take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||
choiceA: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||
choiceB: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false })
|
||||
}),
|
||||
characterGuide: new fields.SchemaField({
|
||||
suggestedTraits: new fields.SchemaField({
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import BaseDataItem from "./base.mjs";
|
||||
import BaseDataItem from './base.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
|
||||
export default class DHConsumable extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
/** @inheritDoc */
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: "TYPES.Item.consumable",
|
||||
type: "consumable",
|
||||
label: 'TYPES.Item.consumable',
|
||||
type: 'consumable',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isQuantifiable: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ export default class DHMiscellaneous extends BaseDataItem {
|
|||
/** @inheritDoc */
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: "TYPES.Item.miscellaneous",
|
||||
type: "miscellaneous",
|
||||
label: 'TYPES.Item.miscellaneous',
|
||||
type: 'miscellaneous',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isQuantifiable: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import BaseDataItem from './base.mjs';
|
|||
const featureSchema = () => {
|
||||
return new foundry.data.fields.SchemaField({
|
||||
name: new foundry.data.fields.StringField({ required: true }),
|
||||
effects: new ForeignDocumentUUIDArrayField({type: 'Item', required: false}),
|
||||
effects: new ForeignDocumentUUIDArrayField({ type: 'ActiveEffect', required: false }),
|
||||
actions: new foundry.data.fields.ArrayField(new ActionField())
|
||||
});
|
||||
};
|
||||
|
|
@ -64,7 +64,7 @@ export default class DHSubclass extends BaseDataItem {
|
|||
} else if (subclassData) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.SubclassAlreadySelected'));
|
||||
return false;
|
||||
} else if (classData.system.subclasses.every(x => x.uuid !== data.uuid ?? `Item.${data._id}`)) {
|
||||
} else if (classData.system.subclasses.every(x => x.uuid !== (data.uuid ?? `Item.${data._id}`))) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.SubclassNotInClass'));
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
const fields = foundry.data.fields;
|
||||
return {
|
||||
hope: new fields.BooleanField({ required: true, initial: false }),
|
||||
actionPoints: new fields.BooleanField({ required: true, initial: false })
|
||||
actionPoints: new fields.BooleanField({ required: true, initial: false }),
|
||||
countdowns: new fields.BooleanField({ requireD: true, initial: false })
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ export default class DhpActor extends Actor {
|
|||
);
|
||||
|
||||
if (this.type === 'character') {
|
||||
const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope);
|
||||
const automateHope = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation).hope;
|
||||
|
||||
if (automateHope && result.hopeUsed) {
|
||||
await this.update({
|
||||
|
|
@ -330,7 +330,7 @@ export default class DhpActor extends Actor {
|
|||
hope = roll.dice[0].results[0].result;
|
||||
fear = roll.dice[1].results[0].result;
|
||||
if (
|
||||
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope) &&
|
||||
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation).hope &&
|
||||
config.roll.type === 'action'
|
||||
) {
|
||||
if (hope > fear) {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,68 @@
|
|||
export function handleSocketEvent({ action = null, data = {} } = {}) {
|
||||
switch (action) {
|
||||
case socketEvent.GMUpdate:
|
||||
Hooks.callAll(socketEvent.GMUpdate, data.action, data.uuid, data.update);
|
||||
Hooks.callAll(socketEvent.GMUpdate, data);
|
||||
break;
|
||||
case socketEvent.DhpFearUpdate:
|
||||
Hooks.callAll(socketEvent.DhpFearUpdate);
|
||||
break;
|
||||
case socketEvent.Refresh:
|
||||
Hooks.call(socketEvent.Refresh, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export const socketEvent = {
|
||||
GMUpdate: 'DhpGMUpdate',
|
||||
DhpFearUpdate: 'DhpFearUpdate'
|
||||
GMUpdate: 'DhGMUpdate',
|
||||
Refresh: 'DhRefresh',
|
||||
DhpFearUpdate: 'DhFearUpdate'
|
||||
};
|
||||
|
||||
export const GMUpdateEvent = {
|
||||
UpdateDocument: 'DhpGMUpdateDocument',
|
||||
UpdateFear: 'DhpUpdateFear'
|
||||
UpdateDocument: 'DhGMUpdateDocument',
|
||||
UpdateSetting: 'DhGMUpdateSetting',
|
||||
UpdateFear: 'DhGMUpdateFear'
|
||||
};
|
||||
|
||||
export const RefreshType = {
|
||||
Countdown: 'DhCoundownRefresh'
|
||||
};
|
||||
|
||||
export const registerSocketHooks = () => {
|
||||
Hooks.on(socketEvent.GMUpdate, async data => {
|
||||
if (game.user.isGM) {
|
||||
const document = data.uuid ? await fromUuid(data.uuid) : null;
|
||||
switch (data.action) {
|
||||
case GMUpdateEvent.UpdateDocument:
|
||||
if (document && data.update) {
|
||||
await document.update(data.update);
|
||||
}
|
||||
break;
|
||||
case GMUpdateEvent.UpdateSetting:
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(SYSTEM.id, data.uuid, data.update);
|
||||
}
|
||||
break;
|
||||
case GMUpdateEvent.UpdateFear:
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(
|
||||
SYSTEM.id,
|
||||
SYSTEM.SETTINGS.gameSettings.Resources.Fear,
|
||||
Math.max(Math.min(data.update, 6), 0)
|
||||
);
|
||||
Hooks.callAll(socketEvent.DhpFearUpdate);
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, { action: socketEvent.DhpFearUpdate });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (data.refresh) {
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: data.refresh
|
||||
});
|
||||
Hooks.call(socketEvent.Refresh, data.refresh);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { EncounterCountdowns } from '../applications/countdowns.mjs';
|
||||
|
||||
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||
static DEFAULT_OPTIONS = {
|
||||
actions: {
|
||||
requestSpotlight: this.requestSpotlight,
|
||||
toggleSpotlight: this.toggleSpotlight,
|
||||
setActionTokens: this.setActionTokens
|
||||
setActionTokens: this.setActionTokens,
|
||||
openCountdowns: this.openCountdowns
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -83,6 +86,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
.map(x => x.id)
|
||||
.indexOf(combatantId);
|
||||
|
||||
if (this.viewed.turn !== toggleTurn) Hooks.callAll(SYSTEM.HOOKS.spotlight, {});
|
||||
|
||||
await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn });
|
||||
await combatant.update({ 'system.spotlight.requesting': false });
|
||||
}
|
||||
|
|
@ -97,4 +102,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
await combatant.update({ 'system.actionTokens': newIndex });
|
||||
this.render();
|
||||
}
|
||||
|
||||
static openCountdowns() {
|
||||
new EncounterCountdowns().open();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue