WIP group sheet

feature/applicationv2
macniel 2025-10-16 00:06:50 +02:00
parent 9984db4ca6
commit 5e41285b1c
7 changed files with 252 additions and 254 deletions

View File

@ -18,6 +18,7 @@ export class GroupDataModel extends foundry.abstract.TypeDataModel {
quantity: new NumberField(), quantity: new NumberField(),
item: new DocumentIdField(Item) item: new DocumentIdField(Item)
}), }),
groupId: new DocumentIdField(Actor),
characters: new ArrayField( characters: new ArrayField(
new DocumentIdField(Actor) new DocumentIdField(Actor)
), ),
@ -25,4 +26,13 @@ export class GroupDataModel extends foundry.abstract.TypeDataModel {
} }
} }
_onCreate(data, options, userId) {
super._onCreate(data, options, userId);
Folder.implementation.createDocuments([{name: data.name, type: "Actor"}]).then((
folder
) => {
this.parent.update({"system.groupId": folder[0]._id});
})
}
} }

View File

@ -45,10 +45,11 @@ export class SkillDataModel extends BaseItem {
/** /**
* Handle clickable rolls. * Handle clickable rolls.
* @param {Event} event The originating click event * @param {RollMode} rollMode
* @private * @private
*/ */
async roll() { async roll(rollMode = null) {
rollMode = rollMode ?? game.settings.get('core', 'rollMode');
const owner = this.parent.parent const owner = this.parent.parent
let roll1 = new Roll("3d20", owner.getRollData()); let roll1 = new Roll("3d20", owner.getRollData());
@ -63,13 +64,13 @@ export class SkillDataModel extends BaseItem {
evaluated1.toMessage({ evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: owner}), speaker: ChatMessage.getSpeaker({actor: owner}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Meisterlich geschafft' : 'Geschafft'} mit ${dsaDieRollEvaluated.tap} Punkten übrig`, flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Meisterlich geschafft' : 'Geschafft'} mit ${dsaDieRollEvaluated.tap} Punkten übrig`,
rollMode: game.settings.get('core', 'rollMode'), rollMode,
}) })
} else { // misserfolg } else { // misserfolg
evaluated1.toMessage({ evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: owner}), speaker: ChatMessage.getSpeaker({actor: owner}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Gepatzt' : ''} mit ${Math.abs(dsaDieRollEvaluated.tap)} Punkten daneben`, flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Gepatzt' : ''} mit ${Math.abs(dsaDieRollEvaluated.tap)} Punkten daneben`,
rollMode: game.settings.get('core', 'rollMode'), rollMode,
}) })
} }
} }

View File

@ -1,20 +1,52 @@
export class GroupSheet extends foundry.appv1.sheets.ActorSheet { const {HandlebarsApplicationMixin} = foundry.applications.api
/**@override */ const {ActorSheetV2} = foundry.applications.sheets
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, { export class GroupSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
classes: ['dsa41', 'sheet', 'actor', 'group'],
width: 520, /** @inheritDoc */
height: 480, static DEFAULT_OPTIONS = {
tabs: [ position: {width: 520, height: 480},
{ classes: ['dsa41', 'sheet', 'actor', 'group'],
navSelector: '.sheet-tabs', tag: 'form',
contentSelector: '.sheet-body', form: {
initial: 'description', submitOnChange: true,
}, closeOnSubmit: false,
], handler: GroupSheet.#onSubmitForm
}); },
actions: {
roll: GroupSheet.#dieRoll,
editImage: ActorSheetV2.DEFAULT_OPTIONS.actions.editImage,
openEmbeddedDocument: GroupSheet.#openEmbeddedDocument,
openActorDocument: GroupSheet.#openActorDocument,
removeFromParty: GroupSheet.#removeFromParty,
}
} }
static TABS = {
sheet: {
tabs: [
{id: 'members', group: 'sheet', label: 'Gruppenmitglieder'},
{id: 'inventory', group: 'sheet', label: 'Gruppeninventar'},
// settings is only added on context
],
initial: 'members'
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/group/main-sheet.hbs`
},
members: {
template: `systems/DSA_4-1/templates/actor/group/tab-members.hbs`
},
inventory: {
template: `systems/DSA_4-1/templates/actor/group/tab-inventory.hbs`
},
}
static async onDroppedData(group, sheet, data) { static async onDroppedData(group, sheet, data) {
if (data.type === "Actor") { if (data.type === "Actor") {
const uuid = await foundry.utils.parseUuid(data.uuid); const uuid = await foundry.utils.parseUuid(data.uuid);
@ -63,17 +95,52 @@ export class GroupSheet extends foundry.appv1.sheets.ActorSheet {
return s return s
} }
/** @override */ static async #openEmbeddedDocument(evt) {
get template() { const {documentId} = evt.srcElement.dataset
return `systems/DSA_4-1/templates/actor/group-sheet.hbs`; this.document.items.get(documentId).sheet.render(true)
}
static async #openActorDocument(evt) {
const {id} = evt.srcElement.dataset
evt.stopPropagation()
game.actors.get(id).sheet.render(true)
}
static async #dieRoll(evt) {
console.log(evt)
}
static async #removeFromParty(evt) {
const dataset = evt.srcElement.dataset;
const group = this.document;
const charactersWithoutMember = group.system.characters.filter(id => id !== dataset.id)
group.update({
system: {
characters: charactersWithoutMember
}
})
}
/**
* Handle form submission
* @this {AdvantageSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object) // Note: formData.object
} }
/** @override */ /** @override */
async getData() { async _prepareContext(options) {
const context = super.getData(); const context = await super._prepareContext(options)
// Use a safe clone of the actor data for further operations. // Use a safe clone of the actor data for further operations.
const groupData = context.data; const groupData = context.document;
// Add the actor's data to context.data for easier access, as well as flags. // Add the actor's data to context.data for easier access, as well as flags.
context.system = groupData.system; context.system = groupData.system;
@ -101,8 +168,11 @@ export class GroupSheet extends foundry.appv1.sheets.ActorSheet {
} }
} }
for (const characterId of groupData.system.characters) { // TODO hook on changes in the given folder
const character = await game.actors.get(characterId) const characters = await game.folders.get(groupData.system.groupId).contents
console.log(characters)
for (const character of characters) {
character.items.filter((i) => i.type === "Advantage").filter((i) => hiddenFields.includes(this.#stringToKeyFieldName(i.name))).map((advantage) => { character.items.filter((i) => i.type === "Advantage").filter((i) => hiddenFields.includes(this.#stringToKeyFieldName(i.name))).map((advantage) => {
const n = this.#stringToKeyFieldName(advantage.name) const n = this.#stringToKeyFieldName(advantage.name)
@ -154,7 +224,7 @@ export class GroupSheet extends foundry.appv1.sheets.ActorSheet {
} }
context.equipments = []; context.equipments = [];
const actorData = context.data; const actorData = context.document;
Object.values(actorData.items).forEach((item, index) => { Object.values(actorData.items).forEach((item, index) => {
if (item.type === "Equipment") { if (item.type === "Equipment") {
context.equipments.push({ context.equipments.push({
@ -173,110 +243,8 @@ export class GroupSheet extends foundry.appv1.sheets.ActorSheet {
return await context; return await context;
} }
openEmbeddedDocument(documentId) { _onRender(context, options) {
this.object.items.get(documentId).sheet.render(true) new ContextMenu(this.element, ".equipment", [
}
_evaluateRoll(rolledDice, {
taw,
lowerThreshold = 1,
upperThreshold = 20,
countToMeisterlich = 3,
countToPatzer = 3,
werte = []
}) {
let tap = taw;
let meisterlichCounter = 0;
let patzerCounter = 0;
let failCounter = 0;
rolledDice.forEach((rolledDie, index) => {
if (tap < 0 && rolledDie.result > werte[index]) {
tap -= rolledDie.result - werte[index];
if (tap < 0) { // konnte nicht vollständig ausgeglichen werden
failCounter++;
}
} else if (rolledDie.result > werte[index]) { // taw ist bereits aufgebraucht und wert kann nicht ausgeglichen werden
tap -= rolledDie.result - werte[index];
failCounter++;
}
if (rolledDie.result <= lowerThreshold) meisterlichCounter++;
if (rolledDie.result > upperThreshold) patzerCounter++;
})
return {
tap,
meisterlich: meisterlichCounter === countToMeisterlich,
patzer: patzerCounter === countToPatzer,
}
}
async _onTalentRoll(event) {
event.preventDefault();
const dataset = event.currentTarget.dataset;
const actor = await game.actors.get(dataset.actorId);
console.log(dataset, actor)
if (dataset.rolleigenschaft1) {
let roll1 = new Roll("3d20", actor.getRollData());
let evaluated1 = (await roll1.evaluate())
const dsaDieRollEvaluated = this._evaluateRoll(evaluated1.terms[0].results, {
taw: dataset.taw,
werte: [dataset.rolleigenschaft1, dataset.rolleigenschaft2, dataset.rolleigenschaft3],
})
if (dsaDieRollEvaluated.tap >= 0) { // erfolg
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: actor}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Meisterlich geschafft' : 'Geschafft'} mit ${dsaDieRollEvaluated.tap} Punkten übrig`,
}, {rollMode: "gmroll"})
} else { // misserfolg
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: actor}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Gepatzt' : ''} mit ${Math.abs(dsaDieRollEvaluated.tap)} Punkten daneben`,
}, {rollMode: "gmroll"})
}
}
}
activateListeners(html) {
super.activateListeners(html);
html.on('click', '.owneroption.clickable', async (evt) => {
const dataset = evt.target.dataset;
const group = this.object;
switch (dataset.operation) {
case "removeFromParty":
const charactersWithoutMember = group.system.characters.filter(id => id !== dataset.id)
await group.update({
system: {
characters: charactersWithoutMember
}
})
}
});
html.on('click', '.header.clickable', async (evt) => {
const {id, operation} = evt.currentTarget.dataset;
if (operation === "openActorSheet") {
evt.stopPropagation();
(await game.actors.get(id)).sheet.render(true);
}
})
html.on('click', '.equipment', (evt) => {
this.openEmbeddedDocument(evt.target.dataset.id);
evt.stopPropagation();
})
html.on('click', ".rollable", (evt) => {
this._onTalentRoll(evt)
evt.stopPropagation()
})
new ContextMenu(html, '.equipment', [
{ {
name: "Aus dem Gruppeninventar entfernen", name: "Aus dem Gruppeninventar entfernen",
icon: '<i class="fa-solid fa-trash"></i>', icon: '<i class="fa-solid fa-trash"></i>',
@ -285,6 +253,9 @@ export class GroupSheet extends foundry.appv1.sheets.ActorSheet {
}, },
condition: () => true condition: () => true
} }
]); ], {
jQuery: false
});
} }
} }

View File

@ -1,121 +0,0 @@
<form class="{{cssClass}} {{actor.type}} flexcol" autocomplete="off">
{{!-- Sheet Header --}}
<header class="sheet-header">
<input class="sheet-name" type="text" name="actor.name" value="{{actor.name}}"/>
</header>
{{!-- Sheet Tab Navigation --}}
<nav class="sheet-tabs tabs" data-group="primary">
<a class="item" data-tab="characters">Charaktere</a>
<a class="item" data-tab="inventory">Inventar</a>
{{#if owner}}<a class="item" data-tab="settings">Sichtbarkeiten</a>{{/if}}
</nav>
{{!-- Sheet Body --}}
<section class="sheet-body">
<div class="tab characters" data-group="primary" data-tab="characters">
{{#unless isGM}}
<div class="characters-overview minimal">
{{#each characters}}
{{#if this.isVisible}}
<div class="character">
<div class="header clickable" data-id="{{this.id}}" data-operation="openActorSheet">
<img class="profile-img" src="{{this.img}}" style="object-fit: cover;"
title="{{this.name}}" alt="{{this.name}}"/>
<span class="name">{{this.name}}</span>
</div>
</div>
{{/if}}
{{/each}}
</div>
{{else}}
<div class="characters-overview">
<table>
<thead>
<tr>
<th></th>
{{#each this.fields.head}}
<th>
<div class="character">
<div class="header clickable" data-id="{{this.id}}"
data-operation="openActorSheet">
<img class="profile-img" src="{{this.img}}" style="object-fit: cover;"
title="{{this.name}}" alt="{{this.name}}"/>
<span class="name">{{this.name}}</span>
</div>
{{#if this.isLimited}}
<div class="mini-attributes">
{{#each this.attributes}}
<div class="mini-attribute" data-actor-id="{{actor.id}}"
data-attribute="{{this.name}}" data-roll-data="{{this.value}}">
<span class="value">{{this.value}}</span>
<span class="name">{{this.name}}</span>
</div>
{{/each}}
</div>
{{/if}}
</div>
</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each this.fields}}
{{#unless (eq @key "head")}}
<tr>
<th>
{{ @key}}
</th>
{{#each this}}
<td>
{{#if (eq this "-")}}
{{else}}
{{#if this.taw}}
<div class="rollable" data-actor-id="{{this.actor}}"
data-taw="{{this.taw}}"
data-name="{{this.name}}"
data-eigenschaft1="{{this.eigenschaft1}}"
data-eigenschaft2="{{this.eigenschaft2}}"
data-eigenschaft3="{{this.eigenschaft3}}"
data-rollEigenschaft1="{{this.rollEigenschaft1}}"
data-rollEigenschaft2="{{this.rollEigenschaft2}}"
data-rollEigenschaft3="{{this.rollEigenschaft3}}">
{{this.taw}} ({{this.eigenschaft1}}, {{this.eigenschaft2}}
, {{this.eigenschaft3}})
</div>
{{else}}
Ja
{{/if}}
{{/if}}
</td>
{{/each}}
</tr>
{{/unless}}
{{/each}}
</tbody>
</table>
</div>
{{/unless}}
</div>
<div class="tab inventory" data-group="primary" data-tab="inventory">
{{> 'systems/DSA_4-1/templates/ui/partial-equipment-group-button.hbs' equipments}}
</div>
{{#if owner}}
<div class="tab settings" data-group="primary" data-tab="settings">
{{#each settings}}
<div>
<label><input name="system.settings.{{@key}}" type="checkbox" {{checked this}}> {{@key}}</label>
</div>
{{/each}}
</div>
{{/if}}
</section>
</form>

View File

@ -0,0 +1,20 @@
<div>
<header class="sheet-header">
<input class="sheet-name" type="text" name="name" value="{{name}}"/>
</header>
{{!-- Sheet Tab Navigation --}}
<nav class="sheet-tabs tabs{{#if verticalTabs}} vertical{{/if}}"
aria-roledescription="{{localize "SHEETS.FormNavLabel"}}">
{{#each tabs as |tab|}}
<a data-action="tab" data-group="{{tab.group}}" data-tab="{{tab.id}}"
{{#if tab.cssClass}}class="{{tab.cssClass}}"{{/if}}
{{#if tab.tooltip}}data-tooltip="{{tab.tooltip}}"{{/if}}>
{{#if tab.icon}}<i class="{{tab.icon}}" inert></i>{{/if}}
{{#if tab.label}}<span>{{localize tab.label}}</span>{{/if}}
</a>
{{/each}}
</nav>
</div>

View File

@ -0,0 +1,28 @@
<section class="tab {{tabs.inventory.id}} {{tabs.inventory.cssClass}}"
data-tab="{{tabs.inventory.id}}"
data-group="{{tabs.inventory.group}}">
<table class="inventory-table">
<thead>
<tr>
<th colspan="2"></th>
<th>Anzahl</th>
<th>Gewicht</th>
</tr>
</thead>
<tbody>
{{#each inventoryItems}}
<tr class="equipment" data-id="{{this.id}}" draggable="true">
<td class="icon"><img alt="" src="{{this.icon}}" width="16" height="16"></td>
<td class="name">{{this.name}}</td>
<td class="quantity">{{this.quantity}}</td>
<td class="weight">{{this.weight}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</section>

View File

@ -0,0 +1,89 @@
<section class="tab {{tabs.members.id}} {{tabs.members.cssClass}}"
data-tab="{{tabs.members.id}}"
data-group="{{tabs.members.group}}">
{{#unless isGM}}
<div class="characters-overview minimal">
{{#each this.characters}}
{{#if this.isVisible}}
<div class="character">
<div class="header clickable" data-id="{{this.id}}" data-operation="openActorSheet">
<img class="profile-img" src="{{this.img}}" style="object-fit: cover;"
title="{{this.name}}" alt="{{this.name}}"/>
<span class="name">{{this.name}}</span>
</div>
</div>
{{/if}}
{{/each}}
</div>
{{else}}
<div class="characters-overview">
<table>
<thead>
<tr>
<th></th>
{{#each this.fields.head}}
<th>
<div class="character">
<div class="header clickable" data-id="{{this.id}}"
data-operation="openActorSheet">
<img class="profile-img" src="{{this.img}}" style="object-fit: cover;"
title="{{this.name}}" alt="{{this.name}}"/>
<span class="name">{{this.name}}</span>
</div>
{{#if this.isLimited}}
<div class="mini-attributes">
{{#each this.attributes}}
<div class="mini-attribute" data-actor-id="{{actor.id}}"
data-attribute="{{this.name}}" data-roll-data="{{this.value}}">
<span class="value">{{this.value}}</span>
<span class="name">{{this.name}}</span>
</div>
{{/each}}
</div>
{{/if}}
</div>
</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each this.fields}}
{{#unless (eq @key "head")}}
<tr>
<th>
{{ @key}}
</th>
{{#each this}}
<td>
{{#if (eq this "-")}}
{{else}}
{{#if this.taw}}
<div class="rollable" data-actor-id="{{this.actor}}"
data-taw="{{this.taw}}"
data-name="{{this.name}}"
data-eigenschaft1="{{this.eigenschaft1}}"
data-eigenschaft2="{{this.eigenschaft2}}"
data-eigenschaft3="{{this.eigenschaft3}}"
data-rollEigenschaft1="{{this.rollEigenschaft1}}"
data-rollEigenschaft2="{{this.rollEigenschaft2}}"
data-rollEigenschaft3="{{this.rollEigenschaft3}}">
{{this.taw}} ({{this.eigenschaft1}}, {{this.eigenschaft2}}
, {{this.eigenschaft3}})
</div>
{{else}}
Ja
{{/if}}
{{/if}}
</td>
{{/each}}
</tr>
{{/unless}}
{{/each}}
</tbody>
</table>
</div>
{{/unless}}
</section>