Added attribution for items, adversary and environment

This commit is contained in:
WBHarry 2025-08-17 00:37:34 +02:00
parent 2820c96259
commit 5f20505eda
22 changed files with 288 additions and 13 deletions

View file

@ -236,6 +236,9 @@
}
},
"APPLICATIONS": {
"Attribution": {
"title": "Attribution"
},
"CharacterCreation": {
"tabs": {
"ancestry": "Ancestry",
@ -1905,6 +1908,7 @@
"armorScore": "Armor Score",
"activeEffects": "Active Effects",
"armorSlots": "Armor Slots",
"artistAttribution": "Artwork By: {artist}",
"attack": "Attack",
"basics": "Basics",
"bonus": "Bonus",
@ -2005,6 +2009,11 @@
},
"ITEMS": {
"FIELDS": {
"attribution": {
"source": { "label": "Source" },
"page": { "label": "Page" },
"artist": { "label": "Artist" }
},
"resource": {
"amount": { "label": "Amount" },
"dieFaces": { "label": "Die Faces" },
@ -2406,7 +2415,8 @@
"rulesOff": "Rules Off",
"remainingUses": "Uses refresh on {type}",
"rightClickExtand": "Right-Click to extand",
"companionPartnerLevelBlock": "The companion needs an assigned partner to level up."
"companionPartnerLevelBlock": "The companion needs an assigned partner to level up.",
"configureAttribution": "Configure Attribution"
}
}
}

View file

@ -1,3 +1,4 @@
export { default as AttributionDialog } from './attributionDialog.mjs';
export { default as BeastformDialog } from './beastformDialog.mjs';
export { default as d20RollDialog } from './d20RollDialog.mjs';
export { default as DamageDialog } from './damageDialog.mjs';

View file

@ -0,0 +1,93 @@
import autocomplete from 'autocompleter';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class AttriubtionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(item) {
super({});
this.item = item;
this.sources = Object.keys(CONFIG.DH.GENERAL.attributionSources).flatMap(groupKey => {
const group = CONFIG.DH.GENERAL.attributionSources[groupKey];
return group.values.map(x => ({ group: group.label, ...x }));
});
}
get title() {
return game.i18n.localize('DAGGERHEART.APPLICATIONS.Attribution.title');
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dh-style', 'dialog', 'views', 'attribution'],
position: { width: 'auto', height: 'auto' },
window: { icon: 'fa-solid fa-signature' },
form: { handler: this.updateData, submitOnChange: false, closeOnSubmit: true }
};
static PARTS = {
main: { template: 'systems/daggerheart/templates/dialogs/attribution.hbs' }
};
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
const sources = this.sources;
htmlElement.querySelectorAll('.attribution-input').forEach(element => {
autocomplete({
input: element,
fetch: function (text, update) {
if (!text) {
update(sources);
} else {
text = text.toLowerCase();
var suggestions = sources.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 = item.label;
},
click: e => e.fetch(),
customize: function (_input, _inputRect, container) {
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
},
minLength: 0
});
});
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.item = this.item;
context.data = this.item.system.attribution;
return context;
}
static async updateData(_event, _element, formData) {
await this.item.update({ 'system.attribution': formData.object });
this.item.sheet.refreshFrame();
}
}

View file

@ -4,6 +4,7 @@ import DHBaseActorSheet from '../api/base-actor.mjs';
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
export default class AdversarySheet extends DHBaseActorSheet {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
classes: ['adversary'],
position: { width: 660, height: 766 },
@ -12,7 +13,14 @@ export default class AdversarySheet extends DHBaseActorSheet {
reactionRoll: AdversarySheet.#reactionRoll
},
window: {
resizable: true
resizable: true,
controls: [
{
icon: 'fa-solid fa-signature',
label: 'DAGGERHEART.UI.Tooltip.configureAttribution',
action: 'editAttribution'
}
]
}
};

View file

@ -11,7 +11,14 @@ export default class DhpEnvironment extends DHBaseActorSheet {
height: 725
},
window: {
resizable: true
resizable: true,
controls: [
{
icon: 'fa-solid fa-signature',
label: 'DAGGERHEART.UI.Tooltip.configureAttribution',
action: 'editAttribution'
}
]
},
actions: {},
dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]

View file

@ -101,7 +101,8 @@ export default function DHApplicationMixin(Base) {
toggleEffect: DHSheetV2.#toggleEffect,
toggleExtended: DHSheetV2.#toggleExtended,
addNewItem: DHSheetV2.#addNewItem,
browseItem: DHSheetV2.#browseItem
browseItem: DHSheetV2.#browseItem,
editAttribution: DHSheetV2.#editAttribution
},
contextMenus: [
{
@ -125,6 +126,33 @@ export default function DHApplicationMixin(Base) {
tagifyConfigs: []
};
/**@inheritdoc */
async _renderFrame(options) {
const frame = await super._renderFrame(options);
if (this.document.system.metadata.hasAttribution) {
const { source, page } = this.document.system.attribution;
const attribution = [source, page ? `pg ${page}.` : null].filter(x => x).join('. ');
const element = `<label class="attribution-header-label">${attribution}</label>`;
this.window.controls.insertAdjacentHTML('beforebegin', element);
}
return frame;
}
/**
* Refresh the custom parts of the application frame
*/
refreshFrame() {
if (this.document.system.metadata.hasAttribution) {
const { source, page } = this.document.system.attribution;
const attribution = [source, page ? `pg ${page}.` : null].filter(x => x).join('. ');
const label = this.window.header.querySelector('.attribution-header-label');
label.innerHTML = attribution;
}
}
/**
* Related documents that should cause a rerender of this application when updated.
*/
@ -548,6 +576,14 @@ export default function DHApplicationMixin(Base) {
return new ItemBrowser({ presets }).render({ force: true });
}
/**
* Open the attribution dialog
* @type {ApplicationClickAction}
*/
static async #editAttribution() {
new game.system.api.applications.dialogs.AttributionDialog(this.document).render({ force: true });
}
/**
* Create an embedded document.
* @type {ApplicationClickAction}

View file

@ -13,7 +13,16 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
static DEFAULT_OPTIONS = {
classes: ['item'],
position: { width: 600 },
window: { resizable: true },
window: {
resizable: true,
controls: [
{
icon: 'fa-solid fa-signature',
label: 'DAGGERHEART.UI.Tooltip.configureAttribution',
action: 'editAttribution'
}
]
},
form: {
submitOnChange: true
},

View file

@ -624,6 +624,13 @@ export const rollTypes = {
}
};
export const attributionSources = {
daggerheart: {
label: 'Daggerheart',
values: [{ label: 'Daggerheart SRD' }]
}
};
export const fearDisplay = {
token: { value: 'token', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.token' },
bar: { value: 'bar', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.bar' },

View file

@ -10,7 +10,8 @@ export default class DhpAdversary extends BaseDataActor {
return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.adversary',
type: 'adversary',
settingSheet: DHAdversarySettings
settingSheet: DHAdversarySettings,
hasAttribution: true
});
}

View file

@ -39,7 +39,8 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
type: 'base',
isNPC: true,
settingSheet: null,
hasResistances: true
hasResistances: true,
hasAttribution: false
};
}
@ -53,6 +54,13 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
const fields = foundry.data.fields;
const schema = {};
if (this.metadata.hasAttribution) {
schema.attribution = new fields.SchemaField({
source: new fields.StringField(),
page: new fields.NumberField(),
artist: new fields.StringField()
});
}
if (this.metadata.isNPC) schema.description = new fields.HTMLField({ required: true, nullable: true });
if (this.metadata.hasResistances)
schema.resistance = new fields.SchemaField({

View file

@ -12,7 +12,8 @@ export default class DhEnvironment extends BaseDataActor {
label: 'TYPES.Actor.environment',
type: 'environment',
settingSheet: DHEnvironmentSettings,
hasResistances: false
hasResistances: false,
hasAttribution: true
});
}

View file

@ -26,7 +26,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
hasResource: false,
isQuantifiable: false,
isInventoryItem: false,
hasActions: false
hasActions: false,
hasAttribution: true
};
}
@ -37,7 +38,13 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
/** @inheritDoc */
static defineSchema() {
const schema = {};
const schema = {
attribution: new fields.SchemaField({
source: new fields.StringField(),
page: new fields.NumberField(),
artist: new fields.StringField()
})
};
if (this.metadata.hasDescription) schema.description = new fields.HTMLField({ required: true, nullable: true });

View file

@ -0,0 +1,23 @@
.daggerheart.dh-style.dialog.attribution {
.window-content {
padding-top: 0;
}
.attribution-container {
display: flex;
flex-direction: column;
gap: 8px;
h4 {
margin-bottom: 0;
}
footer {
display: flex;
button {
flex: 1;
}
}
}
}

View file

@ -1,3 +1,4 @@
@import './attribution/sheet.less';
@import './level-up/navigation-container.less';
@import './level-up/selections-container.less';
@import './level-up/sheet.less';

View file

@ -528,6 +528,17 @@
}
}
}
.artist-attribution {
width: 100%;
display: flex;
justify-content: left;
font-style: italic;
font-family: @font-body;
margin-top: 4px;
color: light-dark(#14142599, #efe6d850);
font-size: 12px;
padding-left: 3px;
}
}
.application.setting.dh-style {

View file

@ -1,8 +1,14 @@
.application.sheet.daggerheart.actor.dh-style {
.portrait img, .profile {
.portrait img,
.profile {
width: 100%;
object-fit: cover;
object-position: top center;
}
}
.attribution-header-label {
font-style: italic;
font-family: @font-body;
color: light-dark(@chat-blue-bg, @beige-50);
}
}

View file

@ -26,3 +26,4 @@
@import './items/class.less';
@import './items/domain-card.less';
@import './items/feature.less';
@import './items/item-sheet-shared.less';

View file

@ -0,0 +1,7 @@
.application.sheet.daggerheart.dh-style.item {
.attribution-header-label {
font-style: italic;
font-family: @font-body;
color: light-dark(@chat-blue-bg, @beige-50);
}
}

View file

@ -0,0 +1,26 @@
<div class="attribution-container">
<h4>{{item.name}}</h4>
<div class="form-group">
<div class="form-fields">
<label>{{localize "DAGGERHEART.ITEMS.FIELDS.attribution.source.label"}}</label>
<input type="text" name="source" value="{{data.source}}" class="attribution-input" />
</div>
</div>
<div class="form-group">
<div class="form-fields">
<label>{{localize "DAGGERHEART.ITEMS.FIELDS.attribution.page.label"}}</label>
<input type="text" name="page" value="{{data.page}}" />
</div>
</div>
<div class="form-group">
<div class="form-fields">
<label>{{localize "DAGGERHEART.ITEMS.FIELDS.attribution.artist.label"}}</label>
<input type="text" name="artist" value="{{data.artist}}" />
</div>
</div>
<footer>
<button type="submit">{{localize "Save"}}</button>
</footer>
</div>

View file

@ -7,4 +7,8 @@
<legend>{{localize tabs.notes.label}}</legend>
{{formInput notes.field value=notes.value enriched=notes.enriched toggled=true}}
</fieldset>
{{#if document.system.attribution.artist}}
<label class="artist-attribution">{{localize "DAGGERHEART.GENERAL.artistAttribution" artist=document.system.attribution.artist}}</label>
{{/if}}
</section>

View file

@ -7,4 +7,8 @@
<legend>{{localize tabs.notes.label}}</legend>
{{formInput notes.field value=notes.value enriched=notes.value toggled=true}}
</fieldset>
{{#if document.system.attribution.artist}}
<label class="artist-attribution">{{localize "DAGGERHEART.GENERAL.artistAttribution" artist=document.system.attribution.artist}}</label>
{{/if}}
</section>

View file

@ -7,4 +7,8 @@
<legend>{{localize "DAGGERHEART.GENERAL.description"}}</legend>
{{formInput systemFields.description value=document.system.description enriched=enrichedDescription toggled=true}}
</fieldset>
{{#if document.system.attribution.artist}}
<label class="artist-attribution">{{localize "DAGGERHEART.GENERAL.artistAttribution" artist=document.system.attribution.artist}}</label>
{{/if}}
</section>