From 691a8112751ecd0ff2119232d10102f149f3ae45 Mon Sep 17 00:00:00 2001 From: macniel Date: Fri, 3 Oct 2025 11:03:34 +0200 Subject: [PATCH 1/4] Adds Group Management --- src/main.mjs | 15 ++- src/module/data/group.mjs | 20 ++++ src/module/documents/group.mjs | 12 +++ src/module/sheets/groupSheet.mjs | 90 ++++++++++++++++ src/style/_group-sheet.scss | 160 ++++++++++++++++++++++++++++ src/style/_tabs.scss | 5 +- src/style/styles.scss | 2 + src/system.json | 5 +- src/templates/actor/group-sheet.hbs | 55 ++++++++++ 9 files changed, 360 insertions(+), 4 deletions(-) create mode 100644 src/module/data/group.mjs create mode 100644 src/module/documents/group.mjs create mode 100644 src/module/sheets/groupSheet.mjs create mode 100644 src/style/_group-sheet.scss create mode 100644 src/templates/actor/group-sheet.hbs diff --git a/src/main.mjs b/src/main.mjs index 1992fa77..22e9aef3 100644 --- a/src/main.mjs +++ b/src/main.mjs @@ -7,6 +7,8 @@ import { VornachteileDataModel } from "./module/data/vornachteile.mjs"; import { Character } from "./module/documents/character.mjs"; import { CharacterSheet } from "./module/sheets/characterSheet.mjs"; import { VornachteilSheet } from "./module/sheets/vornachteilSheet.mjs"; +import {GroupDataModel} from "./module/data/group.mjs"; +import {GroupSheet} from "./module/sheets/groupSheet.mjs"; async function preloadHandlebarsTemplates() { return loadTemplates([ @@ -31,6 +33,7 @@ Hooks.once("init", () => { // Configure System Data Models. CONFIG.Actor.dataModels = { character: PlayerCharacterDataModel, + group: GroupDataModel }; CONFIG.Item.dataModels = { @@ -51,6 +54,11 @@ Hooks.once("init", () => { makeDefault: true, label: 'DSA41.CharacterLabels.Item' }) + Actors.registerSheet('dsa41.group', GroupSheet, { + types: ["group"], + makeDefault: true, + label : 'DSA41.GroupLabel.Item' + }) // Register sheet application classes Items.registerSheet('dsa41.skill', SkillSheet, { @@ -69,11 +77,16 @@ Hooks.once("init", () => { label: 'DSA41.VornachteilLabels.Item' }) + return preloadHandlebarsTemplates(); }) Hooks.on('dropActorSheetData', (actor, sheet, data) => { - return CharacterSheet.onDroppedData(actor, sheet, data); + if (actor.type === "character") { + return CharacterSheet.onDroppedData(actor, sheet, data); + } else { + return GroupSheet.onDroppedData(actor, sheet, data); + } } ) Hooks.once("ready", async function() { diff --git a/src/module/data/group.mjs b/src/module/data/group.mjs new file mode 100644 index 00000000..8bcbf8af --- /dev/null +++ b/src/module/data/group.mjs @@ -0,0 +1,20 @@ +const { + SchemaField, NumberField, StringField, EmbeddedDocumentField, DocumentIdField, ArrayField, ForeignDocumentField +} = foundry.data.fields; + +export class GroupDataModel extends foundry.abstract.TypeDataModel { + + static defineSchema() { + return { + name: new StringField(), + inventory: new SchemaField({ + quantity: new NumberField(), + item: new DocumentIdField(Item) + }), + characters: new ArrayField( + new DocumentIdField(Actor) + ) + } + } + +} diff --git a/src/module/documents/group.mjs b/src/module/documents/group.mjs new file mode 100644 index 00000000..837b6a23 --- /dev/null +++ b/src/module/documents/group.mjs @@ -0,0 +1,12 @@ +export class Group extends Actor { + + /** + * @override + */ + prepareData() { + + console.log("prepare", this); + super.prepareData(); + } + +} diff --git a/src/module/sheets/groupSheet.mjs b/src/module/sheets/groupSheet.mjs new file mode 100644 index 00000000..5d77b33a --- /dev/null +++ b/src/module/sheets/groupSheet.mjs @@ -0,0 +1,90 @@ +export class GroupSheet extends ActorSheet { + /**@override */ + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ['dsa41', 'sheet', 'actor', 'group'], + width: 520, + height: 480, + tabs: [ + { + navSelector: '.sheet-tabs', + contentSelector: '.sheet-body', + initial: 'description', + }, + ], + }); + } + + /** @override */ + async getData() { + const context = super.getData(); + + // Use a safe clone of the actor data for further operations. + const skillData = context.data; + + // Add the actor's data to context.data for easier access, as well as flags. + context.system = skillData.system; + context.flags = skillData.flags; + + context.characters = []; + + for (const characterId of skillData.system.characters) { + const character = await game.actors.get(characterId) + context.characters.push( + { + img: character.img, + name: character.name, + attributes: [ + { name: "MU", value: character.system.attribute.mu.aktuell }, + { name: "KL", value: character.system.attribute.kl.aktuell }, + { name: "IN", value: character.system.attribute.in.aktuell }, + { name: "CH", value: character.system.attribute.ch.aktuell }, + { name: "FF", value: character.system.attribute.ff.aktuell }, + { name: "GE", value: character.system.attribute.ge.aktuell }, + { name: "KO", value: character.system.attribute.ko.aktuell }, + { name: "KK", value: character.system.attribute.kk.aktuell }, + ], + advantages: character.items.filter( (i) => i.type === "Advantage").map( (advantage) => { + return { + name: advantage.name, + id: advantage._id, + value: advantage.system.value, + } + }), + skills: character.items.filter( (i) => i.type === "Skill").map( (skill) => { + return { + name: skill.name, + taw: skill.system.taw, + id: skill._id + } + }), + isOwner: character.isOwner + } + ) + } + console.log(context.characters) + return await context; + + } + + /** @override */ + get template() { + return `systems/DSA_4-1/templates/actor/group-sheet.hbs`; + } + + static async onDroppedData(group, sheet, data) { + if (data.type === "Actor") { + const uuid = await foundry.utils.parseUuid(data.uuid); + const character = await (game.actors.get(uuid.id)) + await group.update({ + system: { + characters: [ + ...group.system.characters, + character._id + ] + } + }) + ui.notifications.info(`${character.name} ist der Heldengruppe ${group.name} beigetreten`) + } + } +} diff --git a/src/style/_group-sheet.scss b/src/style/_group-sheet.scss new file mode 100644 index 00000000..5101d62c --- /dev/null +++ b/src/style/_group-sheet.scss @@ -0,0 +1,160 @@ +.dsa41.sheet.actor.group { + + + .window-content { + display: unset; + position: relative; + } + + .sheet-header { + .sheet-name { + font-size: 24pt; + height: 48px; + } + + } + + .sheet-tabs { + position: absolute; + left: 0; + right: 0; + top: 27px; + height: 32px; + } + + .sheet-body { + position: absolute; + top: 112px; + left: 2px; + bottom: 2px; + right: 2px; + + div.tab { + height: 100%; + position: relative; + } + } + + .characters-overview { + + display: inline-flex; + overflow-x: auto; + height: 100%; + width: 100%; + + .character { + + width: 192px; + position: relative; + + .header { + + position: relative; + height: 80px; + overflow: hidden; + left: 0; + right: 0; + + img { + position: absolute; + top: 0; + height: 80px; + width: 100%; + left: 0; + right: 0; + + &::before { + + } + } + + span.name { + position: absolute; + bottom: 0; + left: 0; + right: 0; + text-align: center; + background-color: rgba(0, 0, 0, 0.5); + color: white; + } + + } + + .mini-attributes { + + display: grid; + height: 24px; + grid-template-columns: repeat(8, 1fr); + + .mini-attribute { + + border-left: 1px solid black; + position: relative; + width: 24px; + + &:first-of-type { + border-left: 0; + } + + .value { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + line-height: 100%; + vertical-align: middle; + text-align: center; + display: block; + } + + .name { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 14px; + text-align: center; + display: block; + } + + } + + } + + .fixedElements { + position: absolute; + overflow: scroll; + top: 102px; + bottom: 20px; + left: 0; + right: 0; + + .inline-list { + list-style: none; + padding: 0; + display: block; + + .mini-skill, .mini-advantage { + display: inline-block; + padding: 0 4px; + &:hover { + text-shadow: 0 0 10px rgb(255 0 0); + } + } + } + } + + .owneroptions { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 36px; + } + + } + + } + +} diff --git a/src/style/_tabs.scss b/src/style/_tabs.scss index 10fd0d37..d2e66b98 100644 --- a/src/style/_tabs.scss +++ b/src/style/_tabs.scss @@ -2,13 +2,14 @@ @use "./colours"; @use "./assets"; -.dsa41.sheet.actor.character { +.dsa41.sheet.actor { nav.sheet-tabs.tabs { position: relative; display: flow; border-top: unset; + border-bottom: unset; a.item[data-tab] { @@ -43,4 +44,4 @@ } -} \ No newline at end of file +} diff --git a/src/style/styles.scss b/src/style/styles.scss index f6b2c084..9df419d2 100644 --- a/src/style/styles.scss +++ b/src/style/styles.scss @@ -3,4 +3,6 @@ @use "_attributes"; @use "_sidebar-elements"; @use "_character-sheet"; +@use "_group-sheet"; @use "_tabs"; + diff --git a/src/system.json b/src/system.json index 61cfad8a..1a6511c7 100644 --- a/src/system.json +++ b/src/system.json @@ -81,6 +81,9 @@ ], "documentTypes": { "Actor": { + "group": { + + }, "character": { "numberFields": [ "groesse", "alter", "gewicht" @@ -193,4 +196,4 @@ "url": "https://git.macniel.online/macniel/foundry-dsa41-game", "manifest": "https://git.macniel.online/macniel/foundry-dsa41-game/releases/download/{{VERSION}}/system.json", "download": "https://git.macniel.online/macniel/foundry-dsa41-game/releases/download/{{VERSION}}/release.zip" -} \ No newline at end of file +} diff --git a/src/templates/actor/group-sheet.hbs b/src/templates/actor/group-sheet.hbs new file mode 100644 index 00000000..69bbe95a --- /dev/null +++ b/src/templates/actor/group-sheet.hbs @@ -0,0 +1,55 @@ +
+ + {{!-- Sheet Header --}} +
+ + + +
+ + {{!-- Sheet Tab Navigation --}} + + + {{!-- Sheet Body --}} +
+
+
+ {{#each characters}} +
+
+ {{this.name}} + {{this.name}} +
+
+ {{#each this.attributes}} +
+ {{this.value}} + {{this.name}} +
+ {{/each}} +
+
+
    + {{#each skills}}
  • {{this.name}}: {{this.taw}}
  • {{/each}} +
+
    + {{#each advantages}}
  • {{this.name}} {{#if this.value}}[{{this.value}}]{{/if}}
  • {{/each}} +
+
+ {{#if this.isOwner}} +
+ +
+ {{/if}} +
+ {{/each}} +
+
+
+
+
+ +
-- 2.43.0 From edf6dcab1a1d3de8bc3573855a2961fe4c7650d1 Mon Sep 17 00:00:00 2001 From: macniel Date: Fri, 3 Oct 2025 14:44:26 +0200 Subject: [PATCH 2/4] Adds Inventory Management --- src/main.mjs | 14 +- src/module/data/equipment.mjs | 41 +++++ src/module/documents/equipment.mjs | 10 ++ src/module/sheets/characterSheet.mjs | 34 ++++ src/module/sheets/equipmentSheet.mjs | 109 ++++++++++++ src/module/sheets/groupSheet.mjs | 70 ++++++-- src/style/_equipment-sheet.scss | 133 +++++++++++++++ src/style/_group-sheet.scss | 10 ++ src/style/_tabs.scss | 2 +- src/style/styles.scss | 2 +- src/system.json | 11 ++ src/templates/actor/actor-character-sheet.hbs | 13 +- src/templates/actor/group-sheet.hbs | 29 +++- src/templates/item/item-equipment-sheet.hbs | 160 ++++++++++++++++++ src/templates/item/item-skill-sheet.hbs | 5 +- src/templates/ui/partial-array-editor.hbs | 11 ++ 16 files changed, 620 insertions(+), 34 deletions(-) create mode 100644 src/module/data/equipment.mjs create mode 100644 src/module/documents/equipment.mjs create mode 100644 src/module/sheets/equipmentSheet.mjs create mode 100644 src/style/_equipment-sheet.scss create mode 100644 src/templates/item/item-equipment-sheet.hbs create mode 100644 src/templates/ui/partial-array-editor.hbs diff --git a/src/main.mjs b/src/main.mjs index 22e9aef3..170e8dc9 100644 --- a/src/main.mjs +++ b/src/main.mjs @@ -9,6 +9,8 @@ import { CharacterSheet } from "./module/sheets/characterSheet.mjs"; import { VornachteilSheet } from "./module/sheets/vornachteilSheet.mjs"; import {GroupDataModel} from "./module/data/group.mjs"; import {GroupSheet} from "./module/sheets/groupSheet.mjs"; +import {EquipmentDataModel} from "./module/data/equipment.mjs"; +import {AusruestungSheet} from "./module/sheets/equipmentSheet.mjs"; async function preloadHandlebarsTemplates() { return loadTemplates([ @@ -17,7 +19,8 @@ async function preloadHandlebarsTemplates() { 'systems/DSA_4-1/templates/ui/partial-attribute-button.hbs', 'systems/DSA_4-1/templates/ui/partial-talent-editable.hbs', 'systems/DSA_4-1/templates/ui/partial-die.hbs', - 'systems/DSA_4-1/templates/ui/partial-advantage-button.hbs' + 'systems/DSA_4-1/templates/ui/partial-advantage-button.hbs', + 'systems/DSA_4-1/templates/ui/partial-array-editor.hbs' ]); } @@ -39,7 +42,8 @@ Hooks.once("init", () => { CONFIG.Item.dataModels = { Skill: SkillDataModel, Spell: SpellDataModel, - Advantage: VornachteileDataModel + Advantage: VornachteileDataModel, + Equipment: EquipmentDataModel, } CONFIG.Combat.initiative = { @@ -76,7 +80,11 @@ Hooks.once("init", () => { makeDefault: true, label: 'DSA41.VornachteilLabels.Item' }) - + Items.registerSheet('dsa41.equipment', AusruestungSheet, { + types: ["Equipment"], + makeDefault: true, + label: 'DSA41.AusruestungLabels.Item' + }) return preloadHandlebarsTemplates(); }) diff --git a/src/module/data/equipment.mjs b/src/module/data/equipment.mjs new file mode 100644 index 00000000..f8768127 --- /dev/null +++ b/src/module/data/equipment.mjs @@ -0,0 +1,41 @@ +import BaseItem from "./base-item.mjs"; +const { + ArrayField, NumberField, StringField, HTMLField +} = foundry.data.fields; + +export class EquipmentDataModel extends BaseItem { + + static defineSchema() { + return { + quantity: new NumberField({ required: true, integer: true, initial: 1}), + category: new ArrayField(new StringField({ required: true })), + description: new HTMLField(), + weight: new NumberField({ required: true }), + price: new StringField(), + + breakFactor: new NumberField({ required: false }), + iniModifier: new NumberField({ required: false }), + attackModifier: new NumberField({ required: false }), + parryModifier: new NumberField({ required: false }), + + meleeAttackModifier: new NumberField({ integer: true }), + meleeAttackModifierIncrement: new NumberField({ integer: true }), + meleeSkills: new ArrayField( + new StringField({ required: true }), + ), + meleeAttackDamage: new StringField(), + + rangedSkills: new ArrayField( + new StringField({ required: true }), + ), + rangedRangeModifier: new StringField({ required: false }), + rangeRangeDamageModifier: new StringField({ required: false }), + rangedAttackDamage: new StringField(), + rangedReloadTime: new NumberField({ required: false }), + + armorValue: new NumberField({ required: false }), + armorHandicap: new NumberField({ required: false }), + } + } + +} diff --git a/src/module/documents/equipment.mjs b/src/module/documents/equipment.mjs new file mode 100644 index 00000000..7dcf1bfd --- /dev/null +++ b/src/module/documents/equipment.mjs @@ -0,0 +1,10 @@ + +export class Equipment extends Item { + /** + * Augment the basic Item data model with additional dynamic data. + */ + prepareData() { + super.prepareData(); + } + +} diff --git a/src/module/sheets/characterSheet.mjs b/src/module/sheets/characterSheet.mjs index 66b453fd..96b1955e 100644 --- a/src/module/sheets/characterSheet.mjs +++ b/src/module/sheets/characterSheet.mjs @@ -34,6 +34,7 @@ export class CharacterSheet extends ActorSheet { this.#addSkillsToContext(context) this.#addAdvantagesToContext(context) this.#addAttributesToContext(context) + this.#addEquipmentsToContext(context) return context; } @@ -152,6 +153,21 @@ export class CharacterSheet extends ActorSheet { } + #addEquipmentsToContext(context) { + context.equipments = []; + const actorData = context.data; + Object.values(actorData.items).forEach( (item, index) => { + if (item.type === "Equipment") { + context.equipments.push({ + index: index, + id: item._id, + quantity: item.system.quantity, + name: item.name, + }) + } + }) + } + prepareEigenschaftRoll(actorData, name) { return actorData.system.attribute[name.toLowerCase()].aktuell } @@ -306,6 +322,11 @@ export class CharacterSheet extends ActorSheet { evt.stopPropagation(); }) + html.on('click', '.equipment .name', (evt) => { + this.openEmbeddedDocument(evt.target.dataset.id); + evt.stopPropagation(); + }) + new ContextMenu(html, '.talent.rollable', [ { name: "Entfernen", @@ -357,6 +378,16 @@ export class CharacterSheet extends ActorSheet { } } + #handleDroppedEquipment(actor, equipment) { + const array = Array.from(actor.items); + for ( let i = 0; i < array.length; i++ ) { + if (array[i].name === equipment.name) { // TODO: adjust item quantity if item is the same + console.log(equipment); + return false; + } + } + } + static getElementByName(collection, id) { const array = Array.from(collection); for (const element of array) { @@ -380,6 +411,9 @@ export class CharacterSheet extends ActorSheet { return characterSheet.#handleDroppedSkill(actor, document); // on false cancel this whole operation case "Advantage": return characterSheet.#handleDroppedAdvantage(actor, document); + case "Equipment": + return characterSheet.#handleDroppedEquipment(actor, document); + default: return false; } diff --git a/src/module/sheets/equipmentSheet.mjs b/src/module/sheets/equipmentSheet.mjs new file mode 100644 index 00000000..49e909d0 --- /dev/null +++ b/src/module/sheets/equipmentSheet.mjs @@ -0,0 +1,109 @@ +export class AusruestungSheet extends ItemSheet { + /**@override */ + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ['dsa41', 'sheet', 'item', 'equipment'], + width: 520, + height: 480, + tabs: [ + { + navSelector: '.sheet-tabs', + contentSelector: '.sheet-body', + initial: 'description', + }, + ], + }); + } + + /** @override */ + get template() { + return `systems/DSA_4-1/templates/item/item-equipment-sheet.hbs`; + } + + /** @override */ + getData() { + // Retrieve the data structure from the base sheet. You can inspect or log + // the context variable to see the structure, but some key properties for + // sheets are the actor object, the data object, whether or not it's + // editable, the items array, and the effects array. + const context = super.getData(); + + // Use a safe clone of the actor data for further operations. + const equipmentData = context.data; + + // Add the actor's data to context.data for easier access, as well as flags. + context.system = equipmentData.system; + context.flags = equipmentData.flags; + + context.quantity = context.system.quantity; + context.description = context.system.description; + + context.categoryAndOptions = { + options: { + Gegenstand: "Gegenstand", + Nahkampfwaffe: "Nahkampfwaffe", + Fernkampfwaffe: "Fernkampfwaffe", + Behälter: "Behälter", + Rüstung: "Rüstung", + }, + entries: context.system.category, + targetField: "category" + }; + context.isMeleeWeapon = context.system.category.includes("Nahkampfwaffe"); + context.isRangedWeapon = context.system.category.includes("Fernkampfwaffe"); + context.isContainer = context.system.category.includes("Behälter"); + context.isArmor = context.system.category.includes("Rüstung"); + context.price = context.system.price; + context.weight = context.system.weight; + context.meleeSkillsAndOptions = { + options: { + "": "", + Dolche: "Dolche", + Fechtwaffen: "Fechtwaffen", + Säbel: "Säbel", + Schwerter: "Schwerter", + Anderthalbhänder: "Anderthalbhänder", + "Zweihandschwerter/-säbel": "Zweihandschwerter/-säbel", + "Infanteriewaffen": "Infanteriewaffen", + "Speere": "Speere", + "Stäbe": "Stäbe", + "Hiebwaffen": "Hiebwaffen", + "Zweihand-Hiebwaffen": "Zweihand-Hiebwaffen", + "Kettenwaffen": "Kettenwaffen", + "Raufen": "Raufen" + }, + entries: context.system.meleeSkills, + targetField: "meleeSkills" + } + context.rangedSkillsAndOptions = { + options: { + "": "", + "Wurfmesser": "Wurfmesser", + "Wurfspeer": "Wurfspeer", + "Wurfbeil": "Wurfbeil", + "Armbrust": "Armbrust", + "Bogen": "Bogen", + }, + entries: context.system.rangedSkills, + targetField: "rangedSkills" + } + return context; + } + + activateListeners(html) { + super.activateListeners(html); + + html.on('change', '.array-editor select', (evt) => { + const addingValue = evt.currentTarget.value; + const fieldToTarget = evt.currentTarget.dataset.targetField; + const newSkills = [...this.object.system[fieldToTarget], addingValue]; + + this.object.update( { system: { [fieldToTarget]: newSkills }} ); + evt.currentTarget.value = ""; + }) + + // Everything below here is only needed if the sheet is editable + if (!this.isEditable) return; + } + +} diff --git a/src/module/sheets/groupSheet.mjs b/src/module/sheets/groupSheet.mjs index 5d77b33a..dd53d5c4 100644 --- a/src/module/sheets/groupSheet.mjs +++ b/src/module/sheets/groupSheet.mjs @@ -20,49 +20,52 @@ export class GroupSheet extends ActorSheet { const context = super.getData(); // Use a safe clone of the actor data for further operations. - const skillData = context.data; + const groupData = context.data; // Add the actor's data to context.data for easier access, as well as flags. - context.system = skillData.system; - context.flags = skillData.flags; + context.system = groupData.system; + context.flags = groupData.flags; context.characters = []; - for (const characterId of skillData.system.characters) { + for (const characterId of groupData.system.characters) { const character = await game.actors.get(characterId) context.characters.push( { img: character.img, name: character.name, + id: character._id, attributes: [ - { name: "MU", value: character.system.attribute.mu.aktuell }, - { name: "KL", value: character.system.attribute.kl.aktuell }, - { name: "IN", value: character.system.attribute.in.aktuell }, - { name: "CH", value: character.system.attribute.ch.aktuell }, - { name: "FF", value: character.system.attribute.ff.aktuell }, - { name: "GE", value: character.system.attribute.ge.aktuell }, - { name: "KO", value: character.system.attribute.ko.aktuell }, - { name: "KK", value: character.system.attribute.kk.aktuell }, + {name: "MU", value: character.system.attribute.mu.aktuell}, + {name: "KL", value: character.system.attribute.kl.aktuell}, + {name: "IN", value: character.system.attribute.in.aktuell}, + {name: "CH", value: character.system.attribute.ch.aktuell}, + {name: "FF", value: character.system.attribute.ff.aktuell}, + {name: "GE", value: character.system.attribute.ge.aktuell}, + {name: "KO", value: character.system.attribute.ko.aktuell}, + {name: "KK", value: character.system.attribute.kk.aktuell}, ], - advantages: character.items.filter( (i) => i.type === "Advantage").map( (advantage) => { + advantages: character.items.filter((i) => i.type === "Advantage").map((advantage) => { return { name: advantage.name, id: advantage._id, value: advantage.system.value, } }), - skills: character.items.filter( (i) => i.type === "Skill").map( (skill) => { + skills: character.items.filter((i) => i.type === "Skill").map((skill) => { return { name: skill.name, taw: skill.system.taw, id: skill._id } }), + isLimited: character.isOwner || !character.limited, + isVisible: character.isOwner || character.visible, isOwner: character.isOwner } ) } - console.log(context.characters) + console.log(groupData, context.characters) return await context; } @@ -76,15 +79,48 @@ export class GroupSheet extends ActorSheet { if (data.type === "Actor") { const uuid = await foundry.utils.parseUuid(data.uuid); const character = await (game.actors.get(uuid.id)) + // check if character already is part of the group + if (group.system.characters.includes(character._id)) { + ui.notifications.warn(`${character.name} befindet sich bereits in der Heldengruppe ${group.name}`) + return false; + } + + + // update group await group.update({ system: { characters: [ - ...group.system.characters, - character._id + ...group.system.characters, + character._id ] } }) ui.notifications.info(`${character.name} ist der Heldengruppe ${group.name} beigetreten`) } } + + 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); + } + }) + } } diff --git a/src/style/_equipment-sheet.scss b/src/style/_equipment-sheet.scss new file mode 100644 index 00000000..910033d5 --- /dev/null +++ b/src/style/_equipment-sheet.scss @@ -0,0 +1,133 @@ +.app.window-app.dsa41.sheet.item.equipment { + + .sheet-body { + + position: relative; + top: 5px; + + .tab.meta.active { + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding: 4px; + display: grid; + grid-auto-columns: 1fr 1fr; + grid-template-columns: 80px auto; + grid-template-rows: 24px 48px auto 48px; + gap: 0px 0px; + grid-template-areas: + "category category" + "quantity name" + "description description" + "bottomline bottomline"; + + .name { + grid-area: name; + } + + .bottomline { + grid-area: bottomline; + display: grid; + grid-template-columns: repeat(2, 1fr); + + .named-value { + position: relative; + height: 48px; + + label { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 80px; + line-height: 24px; + vertical-align: middle; + + input { + position: absolute; + left: 80px; + top: 0; + bottom: 0; + right: 0; + } + } + } + + + } + + .category { + grid-area: category; + position: relative; + + label { + line-height: 24px; + vertical-align: middle; + + + } + + .array-editor { + position: absolute; + right: 0; + left: 80px; + top: 0; + bottom: 0; + + ul { + list-style-type: none; + padding: 0; + margin: 0; + text-indent: 0; + + li { + display: inline-block; + border: 1px solid #333; + background-color: rgba(255, 255, 255, 0.2); + border-radius: 12px; + line-height: 24px; + vertical-align: middle; + padding: 0 8px; + box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); + } + } + + select { + position: absolute; + right: 0; + top: 0; + } + } + + } + + .quantity { + grid-area: quantity; + } + + .description { + grid-area: description; + position: relative; + label { + height: 14px; + } + .editor { + position: absolute; + top: 14px; + left: 0; + right: 0; + bottom: 0; + border-bottom: 1px inset #333; + } + } + + } + + + + } + +} diff --git a/src/style/_group-sheet.scss b/src/style/_group-sheet.scss index 5101d62c..a39b8874 100644 --- a/src/style/_group-sheet.scss +++ b/src/style/_group-sheet.scss @@ -42,6 +42,16 @@ height: 100%; width: 100%; + &.minimal { + display: unset!important; + + .character { + display: inline-block; + margin: 4px; + box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); + } + } + .character { width: 192px; diff --git a/src/style/_tabs.scss b/src/style/_tabs.scss index d2e66b98..a044082a 100644 --- a/src/style/_tabs.scss +++ b/src/style/_tabs.scss @@ -2,7 +2,7 @@ @use "./colours"; @use "./assets"; -.dsa41.sheet.actor { +.dsa41.sheet { nav.sheet-tabs.tabs { diff --git a/src/style/styles.scss b/src/style/styles.scss index 9df419d2..ce171464 100644 --- a/src/style/styles.scss +++ b/src/style/styles.scss @@ -5,4 +5,4 @@ @use "_character-sheet"; @use "_group-sheet"; @use "_tabs"; - +@use "_equipment-sheet"; diff --git a/src/system.json b/src/system.json index 1a6511c7..2e5d0f5d 100644 --- a/src/system.json +++ b/src/system.json @@ -101,6 +101,17 @@ } }, "Item": { + "Equipment": { + "stringFields": [ + "name", "category" + ], + "htmlFields": [ + "description" + ], + "numberFields": [ + "quantity" + ] + }, "Advantage": { "stringFields": [ "name" diff --git a/src/templates/actor/actor-character-sheet.hbs b/src/templates/actor/actor-character-sheet.hbs index a6852343..338afbb6 100644 --- a/src/templates/actor/actor-character-sheet.hbs +++ b/src/templates/actor/actor-character-sheet.hbs @@ -67,10 +67,6 @@ -
- -
-
@@ -150,6 +146,15 @@
+
    + {{#each equipments}} +
  • + {{this.quantity}} + {{this.name}} +
  • + {{/each}} +
+
diff --git a/src/templates/actor/group-sheet.hbs b/src/templates/actor/group-sheet.hbs index 69bbe95a..26d3b79e 100644 --- a/src/templates/actor/group-sheet.hbs +++ b/src/templates/actor/group-sheet.hbs @@ -16,16 +16,32 @@ {{!-- Sheet Body --}}
+ {{#if actor.limited}} +
+ {{#each characters}} + {{#if this.isVisible}} +
+
+ {{this.name}} + {{this.name}} +
+
+ {{/if}} + {{/each}} +
+ {{else}}
{{#each characters}} + {{#if this.isVisible}}
-
+
{{this.name}} {{this.name}}
+ {{#if this.isLimited}}
{{#each this.attributes}} -
+
{{this.value}} {{this.name}}
@@ -33,20 +49,23 @@
    - {{#each skills}}
  • {{this.name}}: {{this.taw}}
  • {{/each}} + {{#each skills}}
  • {{this.name}}: {{this.taw}}
  • {{/each}}
    - {{#each advantages}}
  • {{this.name}} {{#if this.value}}[{{this.value}}]{{/if}}
  • {{/each}} + {{#each advantages}}
  • {{this.name}} {{#if this.value}}[{{this.value}}]{{/if}}
  • {{/each}}
+ {{/if}} {{#if this.isOwner}}
- +
{{/if}}
+ {{/if}} {{/each}}
+ {{/if}}
diff --git a/src/templates/item/item-equipment-sheet.hbs b/src/templates/item/item-equipment-sheet.hbs new file mode 100644 index 00000000..ed248076 --- /dev/null +++ b/src/templates/item/item-equipment-sheet.hbs @@ -0,0 +1,160 @@ +
+ + {{!-- Sheet Tab Navigation --}} + + + {{!-- Sheet Body --}} +
+ +
+
+ +
+
+ +
+
+
+ + {{editor system.description target="system.description" button=true owner=owner editable=editable}} +
+
+ + +
+
+ + {{#if isMeleeWeapon}} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ {{/if}} + {{#if isRangedWeapon}} +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ {{/if}} + {{#if isContainer}} +
+ Behälter Specs +
+ {{/if}} + {{#if isArmor}} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ {{/if}} +
+
diff --git a/src/templates/item/item-skill-sheet.hbs b/src/templates/item/item-skill-sheet.hbs index 22cd7fd3..60cb9eb9 100644 --- a/src/templates/item/item-skill-sheet.hbs +++ b/src/templates/item/item-skill-sheet.hbs @@ -53,9 +53,8 @@
- + + {{editor system.talent target="system.talent" button=true owner=owner editable=editable}}
diff --git a/src/templates/ui/partial-array-editor.hbs b/src/templates/ui/partial-array-editor.hbs new file mode 100644 index 00000000..9831acd8 --- /dev/null +++ b/src/templates/ui/partial-array-editor.hbs @@ -0,0 +1,11 @@ +
+ +
    +{{#each entries}} +
  • {{this}}
  • +{{/each}} +
+ +
-- 2.43.0 From 39917a0f89e35c76f04fad30307ece0df1628eda Mon Sep 17 00:00:00 2001 From: macniel Date: Fri, 3 Oct 2025 15:27:48 +0200 Subject: [PATCH 3/4] Implements rudimentary item management --- src/main.mjs | 1 + src/module/sheets/characterSheet.mjs | 17 ++++++- src/module/sheets/groupSheet.mjs | 44 ++++++++++++++++++- src/style/_character-sheet.scss | 35 ++++++++++++++- src/style/_equipment-sheet.scss | 11 +++-- src/style/_group-sheet.scss | 20 ++++++--- src/templates/actor/actor-character-sheet.hbs | 14 +++--- src/templates/actor/group-sheet.hbs | 5 +++ src/templates/ui/partial-equipment-button.hbs | 4 ++ 9 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 src/templates/ui/partial-equipment-button.hbs diff --git a/src/main.mjs b/src/main.mjs index 170e8dc9..11fe8153 100644 --- a/src/main.mjs +++ b/src/main.mjs @@ -20,6 +20,7 @@ async function preloadHandlebarsTemplates() { 'systems/DSA_4-1/templates/ui/partial-talent-editable.hbs', 'systems/DSA_4-1/templates/ui/partial-die.hbs', 'systems/DSA_4-1/templates/ui/partial-advantage-button.hbs', + 'systems/DSA_4-1/templates/ui/partial-equipment-button.hbs', 'systems/DSA_4-1/templates/ui/partial-array-editor.hbs' ]); } diff --git a/src/module/sheets/characterSheet.mjs b/src/module/sheets/characterSheet.mjs index 96b1955e..5abb797b 100644 --- a/src/module/sheets/characterSheet.mjs +++ b/src/module/sheets/characterSheet.mjs @@ -156,6 +156,7 @@ export class CharacterSheet extends ActorSheet { #addEquipmentsToContext(context) { context.equipments = []; const actorData = context.data; + context.carryingweight = 0; Object.values(actorData.items).forEach( (item, index) => { if (item.type === "Equipment") { context.equipments.push({ @@ -164,8 +165,11 @@ export class CharacterSheet extends ActorSheet { quantity: item.system.quantity, name: item.name, }) + context.carryingweight += item.system.quantity * item.system.weight; } }) + context.maxcarryingcapacity = actorData.system.attribute.kk.aktuell + context.carryingpercentage = (context.carryingweight / context.maxcarryingcapacity)*100; } prepareEigenschaftRoll(actorData, name) { @@ -323,7 +327,7 @@ export class CharacterSheet extends ActorSheet { }) html.on('click', '.equipment .name', (evt) => { - this.openEmbeddedDocument(evt.target.dataset.id); + this.openEmbeddedDocument(evt.target.parentElement.dataset.id); evt.stopPropagation(); }) @@ -358,6 +362,17 @@ export class CharacterSheet extends ActorSheet { li.addEventListener("dragstart", handler, false); }); + new ContextMenu(html, '.equipment', [ + { + name: "Aus dem Inventar entfernen", + icon: '', + callback: (event) => { + this.object.deleteEmbeddedDocuments('Item', [event[0].dataset.id]) + }, + condition: () => true + } + ]); + } #handleDroppedSkill(actor, skill) { diff --git a/src/module/sheets/groupSheet.mjs b/src/module/sheets/groupSheet.mjs index dd53d5c4..1f4a9efd 100644 --- a/src/module/sheets/groupSheet.mjs +++ b/src/module/sheets/groupSheet.mjs @@ -65,9 +65,22 @@ export class GroupSheet extends ActorSheet { } ) } - console.log(groupData, context.characters) - return await context; + + context.equipments = []; + const actorData = context.data; + Object.values(actorData.items).forEach( (item, index) => { + if (item.type === "Equipment") { + context.equipments.push({ + index: index, + id: item._id, + quantity: item.system.quantity, + name: item.name, + }) + } + }) + + return await context; } /** @override */ @@ -97,6 +110,16 @@ export class GroupSheet extends ActorSheet { }) ui.notifications.info(`${character.name} ist der Heldengruppe ${group.name} beigetreten`) } + if (data.type === "Equipment") { + const uuid = await foundry.utils.parseUuid(data.uuid); + const equipment = await (game.actors.get(uuid.id)) + ui.notifications.info(`${equipment.name} befindet sich nun im Inventar der Heldengruppe ${group.name}`) + return true; + } + } + + openEmbeddedDocument(documentId) { + this.object.items.get(documentId).sheet.render(true) } activateListeners(html) { @@ -122,5 +145,22 @@ export class GroupSheet extends ActorSheet { (await game.actors.get(id)).sheet.render(true); } }) + + + html.on('click', '.equipment .name', (evt) => { + this.openEmbeddedDocument(evt.target.dataset.id); + evt.stopPropagation(); + }) + + new ContextMenu(html, '.equipment', [ + { + name: "Aus dem Gruppeninventar entfernen", + icon: '', + callback: (event) => { + this.object.deleteEmbeddedDocuments('Item', [event[0].dataset.id]) + }, + condition: () => true + } + ]); } } diff --git a/src/style/_character-sheet.scss b/src/style/_character-sheet.scss index e57c691c..4ad0709c 100644 --- a/src/style/_character-sheet.scss +++ b/src/style/_character-sheet.scss @@ -52,6 +52,39 @@ overflow: auto; } + .backpack { + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + padding: 8px; + + .resource { + + position: relative; + border: 1px inset #ccc; + background-color: rgba(0,0,0,0.2); + height: 8px; + + span.fill { + position:absolute; + left: 0; + top: 0; + bottom: 0; + background: linear-gradient(to bottom, #0bad29 0%,#11f128 50%,#0cde24 51%,#6ff77b 100%); + } + + } + + .equipment:hover { + .item-name { + text-shadow: 0 0 10px rgb(255 0 0); + } + } + } + + } -} \ No newline at end of file +} diff --git a/src/style/_equipment-sheet.scss b/src/style/_equipment-sheet.scss index 910033d5..dc8ddbc0 100644 --- a/src/style/_equipment-sheet.scss +++ b/src/style/_equipment-sheet.scss @@ -5,14 +5,17 @@ position: relative; top: 5px; - .tab.meta.active { - - position: absolute; + .tab.active { + padding: 4px; top: 0; bottom: 0; left: 0; right: 0; - padding: 4px; + } + + .tab.meta.active { + + position: absolute; display: grid; grid-auto-columns: 1fr 1fr; grid-template-columns: 80px auto; diff --git a/src/style/_group-sheet.scss b/src/style/_group-sheet.scss index a39b8874..730b6da4 100644 --- a/src/style/_group-sheet.scss +++ b/src/style/_group-sheet.scss @@ -18,16 +18,19 @@ position: absolute; left: 0; right: 0; - top: 27px; + top: 76px; height: 32px; + padding: 0 16px; } .sheet-body { position: absolute; - top: 112px; - left: 2px; - bottom: 2px; - right: 2px; + top: 98px; + left: 0; + bottom: 0; + right: 0; + padding: 8px; + margin: 8px; div.tab { height: 100%; @@ -41,6 +44,7 @@ overflow-x: auto; height: 100%; width: 100%; + gap: 8px; &.minimal { display: unset!important; @@ -167,4 +171,10 @@ } + .equipment:hover { + .item-name { + text-shadow: 0 0 10px rgb(255 0 0); + } + } + } diff --git a/src/templates/actor/actor-character-sheet.hbs b/src/templates/actor/actor-character-sheet.hbs index 338afbb6..d80b6bdc 100644 --- a/src/templates/actor/actor-character-sheet.hbs +++ b/src/templates/actor/actor-character-sheet.hbs @@ -146,14 +146,16 @@
-
    +
    + +
    + +
    +
    + {{#each equipments}} -
  • - {{this.quantity}} - {{this.name}} -
  • + {{> "systems/DSA_4-1/templates/ui/partial-equipment-button.hbs" this}} {{/each}} -
diff --git a/src/templates/actor/group-sheet.hbs b/src/templates/actor/group-sheet.hbs index 26d3b79e..cea13336 100644 --- a/src/templates/actor/group-sheet.hbs +++ b/src/templates/actor/group-sheet.hbs @@ -68,6 +68,11 @@ {{/if}}
+
    + {{#each equipments}} + {{> 'systems/DSA_4-1/templates/ui/partial-equipment-button.hbs' this}} + {{/each}} +
diff --git a/src/templates/ui/partial-equipment-button.hbs b/src/templates/ui/partial-equipment-button.hbs new file mode 100644 index 00000000..9dc4bffc --- /dev/null +++ b/src/templates/ui/partial-equipment-button.hbs @@ -0,0 +1,4 @@ +
+ {{this.quantity}} + {{this.name}} +
-- 2.43.0 From 882ebf8392519272e93ebff7ece2abb58c84b566 Mon Sep 17 00:00:00 2001 From: macniel Date: Fri, 3 Oct 2025 15:29:51 +0200 Subject: [PATCH 4/4] fixes eventlistener binding --- src/module/sheets/characterSheet.mjs | 2 +- src/module/sheets/groupSheet.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/module/sheets/characterSheet.mjs b/src/module/sheets/characterSheet.mjs index 5abb797b..27caf3e8 100644 --- a/src/module/sheets/characterSheet.mjs +++ b/src/module/sheets/characterSheet.mjs @@ -326,7 +326,7 @@ export class CharacterSheet extends ActorSheet { evt.stopPropagation(); }) - html.on('click', '.equipment .name', (evt) => { + html.on('click', '.equipment', (evt) => { this.openEmbeddedDocument(evt.target.parentElement.dataset.id); evt.stopPropagation(); }) diff --git a/src/module/sheets/groupSheet.mjs b/src/module/sheets/groupSheet.mjs index 1f4a9efd..6be64bcc 100644 --- a/src/module/sheets/groupSheet.mjs +++ b/src/module/sheets/groupSheet.mjs @@ -147,7 +147,7 @@ export class GroupSheet extends ActorSheet { }) - html.on('click', '.equipment .name', (evt) => { + html.on('click', '.equipment', (evt) => { this.openEmbeddedDocument(evt.target.dataset.id); evt.stopPropagation(); }) -- 2.43.0