Merge remote-tracking branch 'origin/main' into refactor/84-data-models-structure

This commit is contained in:
Joaquin Pereyra 2025-06-01 15:31:29 -03:00
commit a6acbe750a
368 changed files with 5489 additions and 2581 deletions

0
module/_types.d.ts vendored Normal file
View file

View file

@ -0,0 +1,110 @@
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
/**
* A UI element which displays the Users defined for this world.
* Currently active users are always displayed, while inactive users can be displayed on toggle.
*
* @extends ApplicationV2
* @mixes HandlebarsApplication
*/
export default class Resources extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(options={}) {
super(options);
}
/** @inheritDoc */
static DEFAULT_OPTIONS = {
id: "resources",
classes: [],
tag: "div",
window: {
frame: true,
title: "Fear",
positioned: true,
resizable: true
},
actions: {
setFear: Resources.setFear,
increaseFear: Resources.increaseFear
},
position: {
width: 222,
height: 222,
// top: "200px",
// left: "120px"
}
};
/** @override */
static PARTS = {
resources: {
root: true,
template: "systems/daggerheart/templates/views/resources.hbs"
// template: "templates/ui/players.hbs"
}
};
get currentFear() {
return game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
}
get maxFear() {
return game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.MaxFear);
}
/* -------------------------------------------- */
/* Rendering */
/* -------------------------------------------- */
/** @override */
async _prepareContext(_options) {
const display = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.DisplayFear),
current = this.currentFear,
max = this.maxFear,
percent = (current / max) * 100,
isGM = game.user.isGM;
// Return the data for rendering
return {display, current, max, percent, isGM};
}
/** @override */
async _preFirstRender(context, options) {
options.position = game.user.getFlag(SYSTEM.id, 'app.resources.position') ?? Resources.DEFAULT_OPTIONS.position;
}
/** @override */
async _preRender(context, options) {
if(this.currentFear > this.maxFear) await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
}
_onPosition(position) {
game.user.setFlag(SYSTEM.id, 'app.resources.position', position);
}
async close(options={}) {
if(!options.allowed) return;
else super.close(options);
}
static async setFear(event, target) {
if(!game.user.isGM) return;
const fearCount = Number(target.dataset.index ?? 0);
await this.updateFear(this.currentFear === fearCount + 1 ? fearCount : fearCount + 1);
}
static async increaseFear(event, target) {
let value = target.dataset.increment ?? 0,
operator = value.split('')[0] ?? null;
value = Number(value);
await this.updateFear(operator ? this.currentFear + value : value);
}
async updateFear(value) {
if(!game.user.isGM) return;
value = Math.max(0, Math.min(this.maxFear, value));
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear, value);
await this.render(true);
}
}

View file

@ -182,6 +182,38 @@ export const registerDHSettings = () => {
default: 0
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.MaxFear, {
name: game.i18n.localize('DAGGERHEART.Settings.Resources.MaxFear.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.Resources.MaxFear.Hint'),
scope: 'world',
config: true,
type: Number,
default: 12,
onChange: () => {
if(ui.resources) ui.resources.render({force: true});
}
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.DisplayFear, {
name: game.i18n.localize('DAGGERHEART.Settings.Resources.DisplayFear.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.Resources.DisplayFear.Hint'),
scope: 'client',
config: true,
type: String,
choices: {
'token': 'Tokens',
'bar': 'Bar',
'hide': 'Hide'
},
default: 'token',
onChange: value => {
if(ui.resources) {
if(value === 'hide') ui.resources.close({allowed: true});
else ui.resources.render({force: true});
}
}
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope, {
name: game.i18n.localize('DAGGERHEART.Settings.Automation.Hope.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.Automation.Hope.Hint'),

View file

@ -37,9 +37,10 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
attackRoll: this.attackRoll,
tabToLoadout: () => this.domainCardsTab(false),
tabToVault: () => this.domainCardsTab(true),
sendToVault: (_, button) => this.moveDomainCard(button, true),
sentToLoadout: (_, button) => this.moveDomainCard(button, false),
sendToVault: this.moveDomainCard,
sendToLoadout: this.moveDomainCard,
useDomainCard: this.useDomainCard,
removeCard: this.removeDomainCard,
selectClass: this.selectClass,
selectSubclass: this.selectSubclass,
selectAncestry: this.selectAncestry,
@ -641,7 +642,8 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
this.render();
}
static async moveDomainCard(button, toVault) {
static async moveDomainCard(_, button) {
const toVault = button.dataset.action === 'sendToVault';
if (!toVault && this.document.system.domainCards.loadout.length >= this.document.system.domainData.maxLoadout) {
return;
}
@ -656,6 +658,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: `${game.i18n.localize('DAGGERHEART.Chat.DomainCard.Title')} - ${capitalize(button.dataset.domain)}`,
origin: this.document.id,
img: card.img,
name: card.name,
description: card.system.effect,
@ -674,6 +677,13 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
cls.create(msg.toObject());
}
static async removeDomainCard(_, button) {
if (button.dataset.type === 'domainCard') {
const card = this.document.items.find(x => x.uuid === button.dataset.key);
await card.delete();
}
}
static async selectClass() {
(await game.packs.get('daggerheart.classes'))?.render(true);
}
@ -873,6 +883,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: game.i18n.localize('DAGGERHEART.Chat.FeatureTitle'),
origin: this.document.id,
img: item.img,
name: item.name,
description: item.system.description,
@ -903,6 +914,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
: type === 'community'
? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle')
: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
origin: this.document.id,
img: item.img,
name: item.name,
description: item.system.description,
@ -930,14 +942,20 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
const title = `${item.name} - ${game.i18n.localize(`DAGGERHEART.Sheets.PC.DomainCard.${capitalize(button.dataset.key)}Title`)}`;
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
origin: this.document.id,
name: title,
img: item.img,
description: ability.description
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
{
title: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
card: { name: title, img: item.img, description: ability.description }
}
systemData
)
});
@ -949,14 +967,19 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
const item = this.document.items.find(x => x.uuid === button.dataset.id);
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
origin: this.document.id,
name: item.name,
img: item.img,
description: item.system.description
};
const msg = new cls({
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
{
title: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
card: { name: item.name, img: item.img, description: item.system.description }
}
systemData
)
});

View file

@ -19,7 +19,9 @@ export const gameSettings = {
ActionPoints: 'AutomationActionPoints'
},
Resources: {
Fear: 'ResourcesFear'
Fear: 'ResourcesFear',
MaxFear: 'ResourcesMaxFear',
DisplayFear: 'DisplayFear'
},
General: {
AbilityArray: 'AbilityArray',

View file

@ -4,11 +4,12 @@ export default class DhpAbilityUse extends foundry.abstract.TypeDataModel {
return {
title: new fields.StringField({}),
origin: new fields.StringField({}),
img: new fields.StringField({}),
name: new fields.StringField({}),
description: new fields.StringField({}),
actions: new fields.ArrayField(
new fields.SchemaField({
new fields.ObjectField({
name: new fields.StringField({}),
damage: new fields.SchemaField({
type: new fields.StringField({}),
@ -19,11 +20,11 @@ export default class DhpAbilityUse extends foundry.abstract.TypeDataModel {
value: new fields.StringField({})
}),
cost: new fields.SchemaField({
type: new fields.StringField({ nullable: true }),
value: new fields.NumberField({ nullable: true })
type: new fields.StringField({}),
value: new fields.NumberField({})
}),
target: new fields.SchemaField({
type: new fields.StringField({})
type: new fields.StringField({ nullable: true })
})
})
)

View file

@ -5,12 +5,15 @@ import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
export default class DhpActor extends Actor {
_preCreate(data, changes, user) {
if (data.type === 'pc') {
data.prototypeToken = { actorLink: true, disposition: 1, sight: { enabled: true } };
}
super._preCreate(data, changes, user);
async _preCreate(data, options, user) {
if ( (await super._preCreate(data, options, user)) === false ) return false;
// Configure prototype token settings
const prototypeToken = {};
if ( this.type === "pc" ) Object.assign(prototypeToken, {
sight: { enabled: true }, actorLink: true, disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
});
this.updateSource({ prototypeToken });
}
prepareData() {
@ -307,10 +310,20 @@ export default class DhpActor extends Actor {
let update = {};
switch (type) {
case SYSTEM.GENERAL.healingTypes.health.id:
update = { 'system.resources.health.value': Math.max(this.system.resources.health.value - healing, 0) };
update = {
'system.resources.health.value': Math.min(
this.system.resources.health.value + healing,
this.system.resources.health.max
)
};
break;
case SYSTEM.GENERAL.healingTypes.stress.id:
update = { 'system.resources.stress.value': Math.max(this.system.resources.stress.value - healing, 0) };
update = {
'system.resources.stress.value': Math.min(
this.system.resources.stress.value + healing,
this.system.resources.stress.max
)
};
break;
}
@ -343,7 +356,10 @@ export default class DhpActor extends Actor {
}
if (action.cost.type != null && action.cost.value != null) {
if (this.system.resources[action.cost.type].value < action.cost.value - 1) {
if (
this.system.resources[action.cost.type].value - action.cost.value <=
this.system.resources[action.cost.type].min
) {
ui.notifications.error(game.i18n.localize(`Insufficient ${action.cost.type} to use this ability`));
return;
}

View file

@ -36,7 +36,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
element.addEventListener('click', event => this.selectAdvantage.bind(this)(event, data.message))
);
html.querySelectorAll('.ability-use-button').forEach(element =>
element.addEventListener('click', this.abilityUseButton.bind(this)(event, data.message))
element.addEventListener('click', event => this.abilityUseButton.bind(this)(event, data.message))
);
};
@ -134,6 +134,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
event.stopPropagation();
const action = message.system.actions[Number.parseInt(event.currentTarget.dataset.index)];
await game.user.character.useAction(action);
const actor = game.actors.get(message.system.origin);
await actor.useAction(action);
};
}