continues development on mini character sheet

pull/65/head
macniel 2025-11-14 15:57:07 +01:00
parent 0fffebdab9
commit accd2d1f16
15 changed files with 672 additions and 16 deletions

View File

@ -0,0 +1,118 @@
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
export class StandaloneADVSF extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'actor', 'character', 'standalone', 'advsf'],
tag: 'form',
actions: {
rollFlaw: StandaloneADVSF.#rollFlaw,
openEmbeddedDocument: StandaloneADVSF.#openEmbeddedDocument,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/character/standalone/advsf.hbs`
}
}
_actor = null
constructor(actor) {
super(actor)
this._actor = actor
this.render(true)
this.options.window.title = `${this.document.name} Vor und Nachteile`
}
static async #rollFlaw(event, target) {
this._actor?.sheet.options.actions.rollFlaw.bind(this)(event, target)
}
static async #openEmbeddedDocument(event, target) {
this._actor?.sheet.options.actions.openEmbeddedDocument.bind(this)(event, target)
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
options.window.title = `${this.document.name}: Vor und Nachteile`
return options
}
async _prepareContext(context, options, object) {
if (this._actor) {
const actorData = this.document
context.system = actorData.system
context.flags = actorData.flags
context.derived = actorData.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
context.advantages = []
context.flaws = []
actorData.itemTypes.Advantage.forEach((item) => {
if (!item.system.schlechteEigenschaft) {
context.advantages.push({
id: item._id,
name: item.name,
value: item.system.value,
options: item.system.auswahl,
description: item.system.description,
isAdvantage: !item.system.nachteil,
isDisadvantage: item.system.nachteil,
isBadAttribute: item.system.schlechteEigenschaft,
fav: item.getFlag("DSA_4-1", "favourite")
})
} else {
context.flaws.push({
id: item._id,
name: item.name,
value: item.system.value,
options: item.system.auswahl,
description: item.system.description,
isAdvantage: !item.system.nachteil,
isDisadvantage: item.system.nachteil,
isBadAttribute: item.system.schlechteEigenschaft,
fav: item.getFlag("DSA_4-1", "favourite")
})
}
}
)
context.specialAbilities = []
actorData.itemTypes.SpecialAbility.forEach((item) => {
context.specialAbilities.push({
id: item._id,
name: item.system.value ? item.system.value : item.name,
fav: item.getFlag("DSA_4-1", "favourite")
});
}
);
return context
}
}
_onRender(context, options) {
if (this._actor) {
new foundry.applications.ux.DragDrop.implementation({
dropSelector: ".advantages, .special-abilities",
permissions: {
drop: this._actor.sheet._canDragDrop.bind(this._actor.sheet)
},
callbacks: {
drop: this._actor.sheet._onDrop.bind(this._actor.sheet),
}
}).bind(this.element);
}
}
}

View File

@ -0,0 +1,98 @@
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
export class Bagpack extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'actor', 'character', 'standalone', 'bagpack'],
tag: 'form',
actions: {
openItemBrowser: Bagpack.#openItemBrowser,
newItem: Bagpack.#newItem,
openEmbeddedDocument: Bagpack.#openEmbeddedDocument,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/character/standalone/bagpack.hbs`
}
}
_actor = null
constructor(actor) {
super(actor)
this._actor = actor
this.render(true)
}
static async #openItemBrowser(event, target) {
this._actor?.sheet.options.actions.openItemBrowser().bind(this)(event, target)
}
static async #newItem(event, target) {
this._actor?.sheet.options.actions.newItem.bind(this)(event, target)
}
static async #openEmbeddedDocument(event, target) {
this._actor?.sheet.options.actions.openEmbeddedDocument.bind(this)(event, target)
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
options.window.title = `${this.document.name}: Inventar`
return options
}
async _prepareContext(context, options, object) {
const actorData = this.document
context.system = actorData.system
context.flags = actorData.flags
context.equipments = []
context.carryingweight = 0
actorData.itemTypes["Equipment"].sort((a, b) => a.sort - b.sort).forEach((item, index) => {
// worn items are halved weight
let effectiveWeight = item.system.weight ?? 0
if (this.document.isWorn(item._id)) {
effectiveWeight = item.system.weight ? item.system.weight / 2 : 0
}
context.equipments.push({
index: index,
id: item._id,
quantity: item.system.quantity,
name: item.name,
icon: item.img ?? "",
weight: item.system.weight,
worn: this.document.isWorn(item._id)
})
context.carryingweight += item.system.quantity * effectiveWeight;
})
context.maxcarryingcapacity = actorData.system.attribute.kk.aktuell
context.carryingpercentage = Math.min((context.carryingweight / context.maxcarryingcapacity) * 100, 100);
context.wealth = 0
actorData.itemTypes["Equipment"].forEach(coin => {
if (coin.system.category.indexOf("Währung") !== -1) {
context.wealth += (coin.system.quantity * coin.system.currencyDenominator)
}
})
return context
}
_onRender(context, options) {
}
}

View File

@ -0,0 +1,127 @@
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
export class StandaloneSkills extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'actor', 'character', 'standalone', 'skills'],
tag: 'form',
actions: {
rollCombatSkill: StandaloneSkills.#rollCombatSkill,
rollSkill: StandaloneSkills.#rollSkill,
openEmbeddedDocument: StandaloneSkills.#openEmbeddedDocument,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/character/standalone/skills.hbs`
}
}
_actor = null
constructor(actor) {
super(actor)
this._actor = actor
this.render(true)
}
static async #rollCombatSkill(event, target) {
this._actor?.sheet.options.actions.rollCombatSkill.bind(this)(event, target)
}
static async #rollSkill(event, target) {
this._actor?.sheet.options.actions.rollSkill.bind(this)(event, target)
}
static async #openEmbeddedDocument(event, target) {
this._actor?.sheet.options.actions.openEmbeddedDocument.bind(this)(event, target)
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
options.window.title = `${this.document.name}: Talente`
return options
}
async _prepareContext(context, options, object) {
const actorData = this.document
context.system = actorData.system
context.flags = actorData.flags
context.derived = this.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
const prepareEigenschaftRoll = (actorData, name) => {
if (name && name !== "*") {
return actorData.system.attribute[name.toLowerCase()].aktuell
} else {
return 0
}
}
context.skills = {};
context.flatSkills = [];
actorData.itemTypes.Skill.forEach((item, index) => {
const talentGruppe = item.system.gruppe;
const eigenschaften = Object.values(item.system.probe);
const werte = [
{name: eigenschaften[0], value: prepareEigenschaftRoll(actorData, eigenschaften[0])},
{name: eigenschaften[1], value: prepareEigenschaftRoll(actorData, eigenschaften[1])},
{name: eigenschaften[2], value: prepareEigenschaftRoll(actorData, eigenschaften[2])}
]
if (context.skills[talentGruppe] == null) {
context.skills[talentGruppe] = [];
}
const obj = {
type: "talent",
gruppe: talentGruppe,
name: item.name.replace(/Sprachen kennen/g, "Sprache:").replace(/Lesen\/Schreiben/g, "Schrift: "),
taw: "" + item.system.taw,
tawPath: `system.items.${index}.taw`,
werte,
rollEigenschaft1: werte[0].value,
rollEigenschaft2: werte[1].value,
rollEigenschaft3: werte[2].value,
eigenschaft1: werte[0].name,
eigenschaft2: werte[1].name,
eigenschaft3: werte[2].name,
probe: `(${eigenschaften.join("/")})`,
id: item._id,
at: item.system.at,
pa: item.system.pa,
komplexität: item.system.komplexität,
fav: item.getFlag("DSA_4-1", "favourite")
};
if (talentGruppe === "Kampf") {
if (item.system.pa != null) { // has no parry value so it must be ranged talent (TODO: but it isnt as there can be combatstatistics which has no pa value assigned to)
obj.at = item.system.at + context.derived.at.aktuell
obj.pa = item.system.pa + context.derived.pa.aktuell
} else {
obj.at = item.system.at + context.derived.fk.aktuell
}
}
context.skills[talentGruppe].push(obj);
context.flatSkills.push(obj);
}
)
return context
}
_onRender(context, options) {
}
}

View File

@ -2,7 +2,6 @@ export default {
_prepareContext: (context) => { _prepareContext: (context) => {
const actorData = context.document const actorData = context.document
context.spells = []
context.system = actorData.system context.system = actorData.system
context.flags = actorData.flags context.flags = actorData.flags
context.derived = context.document.system context.derived = context.document.system

View File

@ -16,6 +16,9 @@ import {TalentDialog} from "../dialog/talentDialog.mjs";
import {AttributeDialog} from "../dialog/attributeDialog.mjs"; import {AttributeDialog} from "../dialog/attributeDialog.mjs";
import {ItemBrowserDialog} from "../dialog/itemBrowserDialog.mjs"; import {ItemBrowserDialog} from "../dialog/itemBrowserDialog.mjs";
import * as EquipmentDocument from "../documents/equipment.mjs"; import * as EquipmentDocument from "../documents/equipment.mjs";
import {StandaloneADVSF} from "./character-standalone/advsf.mjs";
import {StandaloneSkills} from "./character-standalone/skills.mjs";
import {Bagpack} from "./character-standalone/bagpack.mjs";
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets const {ActorSheetV2} = foundry.applications.sheets
@ -39,24 +42,27 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
actions: { actions: {
rollCombatSkill: CharacterSheet.#rollCombatSkill, rollCombatSkill: CharacterSheet.#rollCombatSkill,
rollSkill: CharacterSheet.#rollSkill, rollSkill: CharacterSheet.#rollSkill,
rollFlaw: CharacterSheet.#rollFlaw, rollFlaw: CharacterSheet.rollFlaw,
rollAttribute: CharacterSheet.#rollAttribute, rollAttribute: CharacterSheet.#rollAttribute,
editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage, editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage,
openEmbeddedDocument: CharacterSheet.#openEmbeddedDocument, openEmbeddedDocument: CharacterSheet.openEmbeddedDocument,
openCultureDocument: CharacterSheet.#openCultureDocument, openCultureDocument: CharacterSheet.#openCultureDocument,
openSpeciesDocument: CharacterSheet.#openSpeciesDocument, openSpeciesDocument: CharacterSheet.#openSpeciesDocument,
openCombatAction: CharacterSheet.#openCombatAction, openCombatAction: CharacterSheet.#openCombatAction,
openLiturgyDialog: CharacterSheet.#openLiturgyDialog, openLiturgyDialog: CharacterSheet.openLiturgyDialog,
openSpellDialog: CharacterSheet.#openSpellDialog, openSpellDialog: CharacterSheet.openSpellDialog,
progressCooldown: CharacterSheet.#progressCooldown, progressCooldown: CharacterSheet.#progressCooldown,
cancelCooldown: CharacterSheet.#cancelCooldown, cancelCooldown: CharacterSheet.#cancelCooldown,
activateCooldown: CharacterSheet.#activateCooldown, activateCooldown: CharacterSheet.#activateCooldown,
rest: CharacterSheet.#startResting, rest: CharacterSheet.#startResting,
removeEffect: CharacterSheet.#removeEffect, removeEffect: CharacterSheet.#removeEffect,
rollDamage: CharacterSheet.#rollDamage, rollDamage: CharacterSheet.#rollDamage,
openItemBrowser: CharacterSheet.#openItemBrowser, openItemBrowser: CharacterSheet.openItemBrowser,
newItem: CharacterSheet.#addNewItem, newItem: CharacterSheet.addNewItem,
toggleFav: CharacterSheet.#toggleFav, toggleFav: CharacterSheet.toggleFav,
openStandaloneADVSF: CharacterSheet.#openStandaloneADVSF,
openStandaloneSkills: CharacterSheet.#openStandaloneSkills,
openBagpack: CharacterSheet.#openBagpack
} }
} }
@ -144,7 +150,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
}).render(true) }).render(true)
} }
static async #rollFlaw(event, target) { static async rollFlaw(event, target) {
new AttributeDialog(this.document, target.dataset.itemId).render(true) new AttributeDialog(this.document, target.dataset.itemId).render(true)
} }
@ -199,7 +205,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
* *
* @param {MouseEvent} event * @param {MouseEvent} event
*/ */
static #openEmbeddedDocument(event) { static openEmbeddedDocument(event) {
let dataset = event.target.dataset let dataset = event.target.dataset
if (!dataset.itemId && !dataset.id) { if (!dataset.itemId && !dataset.id) {
dataset = event.target.parentElement.dataset dataset = event.target.parentElement.dataset
@ -230,12 +236,12 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
} }
} }
static #openLiturgyDialog(event, target) { static openLiturgyDialog(event, target) {
const {id, lkp, deity} = target.dataset const {id, lkp, deity} = target.dataset
new LiturgyDialog(this.document, lkp, id, deity).render(true) new LiturgyDialog(this.document, lkp, id, deity).render(true)
} }
static #openSpellDialog(event, target) { static openSpellDialog(event, target) {
const {itemId} = target.dataset const {itemId} = target.dataset
console.log(itemId) console.log(itemId)
this.document.itemTypes["Spell"]?.find(p => p.id === itemId)?.sheet.render(true) this.document.itemTypes["Spell"]?.find(p => p.id === itemId)?.sheet.render(true)
@ -262,11 +268,11 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
} }
static async #openItemBrowser(event, target) { static async openItemBrowser(event, target) {
new ItemBrowserDialog(this.document).render(true) new ItemBrowserDialog(this.document).render(true)
} }
static async #addNewItem(event, target) { static async addNewItem(event, target) {
let item = new EquipmentDocument.Equipment({ let item = new EquipmentDocument.Equipment({
name: "Neuer Gegenstand", name: "Neuer Gegenstand",
type: "Equipment", type: "Equipment",
@ -275,7 +281,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
items[0].sheet.render(true) items[0].sheet.render(true)
} }
static async #toggleFav(event, target) { static async toggleFav(event, target) {
const {itemId} = target.dataset const {itemId} = target.dataset
const doc = this.document.items.find(p => p.id === itemId) const doc = this.document.items.find(p => p.id === itemId)
@ -285,6 +291,30 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
} }
} }
static async #openStandaloneADVSF(event, target) {
new StandaloneADVSF(this.document)
}
static async #openStandaloneHealth(event, target) {
new StandaloneHealth(this.document)
}
static async #openStandaloneSkills(event, target) {
new StandaloneSkills(this.document)
}
static async #openBagpack(event, target) {
new Bagpack(this.document)
}
static async #openStandaloneSpells(event, target) {
new StandaloneSpells(this.document)
}
static async #openStandaloneLiturgies(event, target) {
new StandaloneLiturgies(this.document)
}
_configureRenderOptions(options) { _configureRenderOptions(options) {
super._configureRenderOptions(options) super._configureRenderOptions(options)
@ -655,9 +685,12 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
if (position.width < 300) { if (position.width < 300) {
this.element.classList.add("tiny") this.element.classList.add("tiny")
this.element.querySelector(".sidebuttons").style.left = (position.width + position.left) + "px"
this.element.querySelector(".sidebuttons").style.top = (position.top) + "px"
} else { } else {
this.element.classList.remove("tiny") this.element.classList.remove("tiny")
} }
} }
_onRender(context, options) { _onRender(context, options) {

View File

@ -25,6 +25,7 @@
input, input,
.cooldown > span, .cooldown > span,
.mini-skill, .mini-weaponskill, .mini-language-skill,
.attribute.rollable > .name, .attribute.rollable > .name,
.attribute.rollable > .wert { .attribute.rollable > .wert {
font-family: Andalus, sans-serif; font-family: Andalus, sans-serif;

View File

@ -157,6 +157,33 @@
} }
} }
.favourites {
h3 {
text-align: center;
margin: 8px 0;
}
.mini-skill, .mini-weaponskill, .mini-language-skill {
background-image: url("/systems/DSA_4-1/assets/velvet_button.png");
background-repeat: repeat-y;
background-size: cover;
border: 1px outset #ccc;
color: white;
border-radius: 4px;
padding: 0 8px;
margin-bottom: 4px;
cursor: pointer;
&:hover {
text-shadow: 0 0 2px rgba(255, 0, 0, 1);
}
}
}
.cooldowns { .cooldowns {
h3 { h3 {

View File

@ -111,9 +111,12 @@
@include liturgies.tab; @include liturgies.tab;
} }
.sidebuttons {
display: none;
}
} }
&.tiny { &.tiny:not(.minimized):not(.minimizing) {
.header-fields { .header-fields {
& > div:not(.attributes) { & > div:not(.attributes) {
@ -151,6 +154,29 @@
section.tab.tab.tab { section.tab.tab.tab {
display: none; display: none;
} }
.sidebuttons {
position: fixed;
display: block;
margin-left: -136px;
margin-top: 182px;
pointer-events: none;
rotate: 90deg;
div {
display: flex;
gap: 4px;
button {
background-image: url("/systems/DSA_4-1/assets/velvet_button.png");
background-repeat: repeat-y;
background-size: cover;
color: white;
font-weight: bold;
pointer-events: all;
}
}
}
} }
} }

View File

@ -98,3 +98,11 @@
} }
} }
.dsa41.actor.character.standalone.advsf {
@include tab;
.advantages-and-specialabilities {
flex-direction: column;
}
}

View File

@ -114,3 +114,52 @@
} }
} }
} }
.dsa41.sheet.actor.character.standalone.bagpack {
@include tab;
.coinage {
position: absolute;
bottom: 0;
right: 8px;
height: 38px;
line-height: 38px;
vertical-align: middle;
}
.capacity {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.buttons {
position: absolute;
bottom: 4px;
left: 4px;
button {
display: inline-block;
}
}
.inventory-table {
overflow-x: hidden;
overflow-y: auto;
position: absolute;
left: 4px;
top: 4px;
right: 4px;
bottom: 38px;
.equipment {
@include equipment.equipment
}
}
}

View File

@ -46,3 +46,17 @@
} }
} }
.mini-skills {
overflow-x: hidden;
overflow-y: auto;
height: 100%;
@include tab;
container: unset;
ul {
list-style-type: none;
padding-left: 0;
}
}

View File

@ -128,4 +128,17 @@
{{/each}} {{/each}}
</nav> </nav>
<div class="sidebuttons">
<div>
<button data-action="openStandaloneADVSF">Vorteile</button>
<button data-action="openStandaloneHealth">Gesundheit</button>
<button data-action="openStandaloneSkills">Talente</button>
<button data-action="openBagpack">Inventar</button>
{{#if this.hasLiturgies}}
<button data-action="openLiturgies">Liturgien</button>{{/if}}
{{#if this.hasSpells}}
<button data-action="openSpells">Zauber</button>{{/if}}
</div>
</div>
</div> </div>

View File

@ -0,0 +1,26 @@
<div class="advantages-and-specialabilities">
<div class="flaws">
<h3>Schlechte Eigenschaften</h3>
<ul>
{{#each this.flaws}}
<li>{{> "systems/DSA_4-1/templates/ui/partial-advantage-button.hbs" this}}</li>
{{/each}}
</ul>
</div>
<div class="advantages">
<h3>Vor- und Nachteile</h3>
<ul>
{{#each this.advantages}}
<li>{{> "systems/DSA_4-1/templates/ui/partial-advantage-button.hbs" this}}</li>
{{/each}}
</ul>
</div>
<div class="special-abilities">
<h3>Sonderfertigkeiten</h3>
<ul>
{{#each this.specialAbilities}}
<li>{{> "systems/DSA_4-1/templates/ui/partial-sf-button.hbs" this}}</li>
{{/each}}
</ul>
</div>
</div>

View File

@ -0,0 +1,20 @@
<div>
<div class="resource">
<span class="fill {{#if (gte this.carryingpercentage 75)}}danger{{/if}}"
style="width: {{this.carryingpercentage}}%"></span>
</div>
<div class="coinage">
<label>{{currency this.wealth}}</label>
</div>
<div class="buttons">
<button data-action="newItem"><i class="fa-solid fa-sack" data-tooltip="Neuer Gegenstand"></i></button>
<button data-action="openItemBrowser"><i class="fa-solid fa-store" data-tooltip="Einkaufen"></i></button>
</div>
{{> "systems/DSA_4-1/templates/ui/partial-equipment-button.hbs" equipments}}
</div>

View File

@ -0,0 +1,97 @@
<div class="mini-skills">
<div class="talent-group combat">
<fieldset>
<legend><h2>Kampftalente</h2></legend>
<ul>
{{#each skills.Kampf}}
<li>
{{> "systems/DSA_4-1/templates/ui/partial-rollable-weaponskill-button.hbs" this}}
</li>
{{/each}}
</ul>
</fieldset>
</div>
<div class="talent-group body">
<fieldset>
<legend><h2>Körperliche Talente</h2></legend>
<ul>
<li>
{{#each skills.Körperlich}}
<li>
{{> "systems/DSA_4-1/templates/ui/partial-rollable-button.hbs" this}}
</li>
{{/each}}
</ul>
</fieldset>
</div>
<div class="talent-group social">
<fieldset>
<legend><h2>Gesellschaftliche Talente</h2></legend>
<ul>
<li>
{{#each skills.Gesellschaft}}
<li>
{{> "systems/DSA_4-1/templates/ui/partial-rollable-button.hbs" this}}
</li>
{{/each}}
</ul>
</fieldset>
</div>
<div class="talent-group nature">
<fieldset>
<legend><h2>Natur Talente</h2></legend>
<ul>
<li>
{{#each skills.Natur}}
<li>
{{> "systems/DSA_4-1/templates/ui/partial-rollable-button.hbs" this}}
</li>
{{/each}}
</ul>
</fieldset>
</div>
<div class="talent-group knowledge">
<fieldset>
<legend><h2>Wissenstalente</h2></legend>
<ul>
<li>
{{#each skills.Wissen}}
<li>
{{> "systems/DSA_4-1/templates/ui/partial-rollable-button.hbs" this}}
</li>
{{/each}}
</ul>
</fieldset>
</div>
<div class="talent-group languages">
<fieldset>
<legend><h2>Schriften & Sprachen</h2></legend>
<ul>
<li>
{{#each skills.Schriften}}
<li>
{{> "systems/DSA_4-1/templates/ui/partial-rollable-language-button.hbs" this}}
</li>
{{/each}}
{{#each skills.Sprachen}}
<li>
{{> "systems/DSA_4-1/templates/ui/partial-rollable-language-button.hbs" this}}
</li>
{{/each}}
</ul>
</fieldset>
</div>
<div class="talent-group crafting">
<fieldset>
<legend><h2>Handwerkliche Talente</h2></legend>
<ul>
<li>
{{#each skills.Handwerk}}
<li>
{{> "systems/DSA_4-1/templates/ui/partial-rollable-button.hbs" this}}
</li>
{{/each}}
</ul>
</fieldset>
</div>
</div>