Character Creation with Default Values is no longer possible (got to be but its not scope of MVP anyway).

Skills either from Compendia or Imported Entries can now be uniquely added to a Character.

This approach will help us later with adding other Elements like Advantages (these need a "uniqueness" Attribute), Spells (always unique), Miracles (also always unique), Equipment
pull/47/head
macniel 2025-10-02 16:52:56 +02:00
parent 32031eb548
commit 1afdd483e6
6 changed files with 159 additions and 245 deletions

View File

@ -72,7 +72,7 @@ Hooks.once("init", () => {
}) })
Hooks.on('dropActorSheetData', (actor, sheet, data) => { Hooks.on('dropActorSheetData', (actor, sheet, data) => {
CharacterSheet.onDroppedData(actor, sheet, data); return CharacterSheet.onDroppedData(actor, sheet, data);
} ) } )
Hooks.once("ready", async function() { Hooks.once("ready", async function() {

View File

@ -1,41 +1,12 @@
import {Skill} from "../documents/skill.mjs"; import {Skill} from "../documents/skill.mjs";
import {SkillDataModel} from "./skill.mjs";
const { const {
SchemaField, NumberField, StringField, EmbeddedDocumentField, DocumentIdField, ArrayField, ForeignDocumentField SchemaField, NumberField, StringField, EmbeddedDocumentField, DocumentIdField, ArrayField, ForeignDocumentField
} = foundry.data.fields; } = foundry.data.fields;
/**
* @extends {Map<string, Set<VornachteileDataModel>>}
*/
class SkillMap extends Map {
/** @inheritDoc */
get(key, { type }={}) {
const result = super.get(key);
if ( !result?.size || !type ) return result;
return result.filter(i => i.type === type);
}
/* -------------------------------------------- */
/** @inheritDoc */
set(key, value) {
if ( !this.has(key) ) super.set(key, new Set());
this.get(key).add(value);
return this;
}
}
export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel { export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
/**
* Mapping of item identifiers to the items.
* @type {SkillMap<string, Set<VornachteileDataModel>>}
*/
skills = this.skills
static defineSchema() { static defineSchema() {
return { return {
name: new StringField(), name: new StringField(),
@ -143,11 +114,7 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
auswahlen: new ArrayField(new StringField()), auswahlen: new ArrayField(new StringField()),
})), })),
talente: new ArrayField(new SchemaField({ talente: new ArrayField(new DocumentIdField(Item)),
talent: new DocumentIdField(Item),
taw: new NumberField({integer: true, required: true}),
})
),
zauber: new ArrayField(new SchemaField({ zauber: new ArrayField(new SchemaField({
talent: new DocumentIdField(), talent: new DocumentIdField(),
zfw: new NumberField({integer: true, required: true}), zfw: new NumberField({integer: true, required: true}),
@ -171,106 +138,8 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
super._initialize(options); super._initialize(options);
} }
/**
* Adds base skills according to the BRW to the given actor
* @param actor
* @returns {Promise<void>}
*/
async #createBaseSkills(actor) {
const compendiumOfSkills = await game.packs.get('DSA_4-1.talente-brw');
const talentsByName = [
"Athletik", "Klettern", "Körperbeherrschung", "Schleichen", "Schwimmen", "Selbstbeherrschung", "Sich Verstecken", "Singen", "Sinnenschärfe", "Tanzen", "Zechen",
"Menschenkenntnis", "Überreden",
"Fährtensuchen", "Orientierung", "Wildnisleben",
"Götter/Kulte", "Rechnen", "Sagen/Legenden",
"Heilkunde: Wunden", "Holzbearbeitung", "Kochen", "Lederverarbeitung", "Malen/Zeichnen", "Schneidern"
]
const talente = []
const mappedCompendiumItems = talentsByName.map( talentName => {
const talent = compendiumOfSkills.index.find( skill => skill.name === talentName)
return talent._id
})
for (const talentId of mappedCompendiumItems) {
const talent = await compendiumOfSkills.getDocument(talentId);
try {
const embeddedDocument = (await thisCharacter.createEmbeddedDocuments('Item', [talent]))[0]
if (embeddedDocument.type === "Skill") {
if (talent) {
talente.push({
taw: 0,
talent: embeddedDocument.id,
})
}
}
} catch (error) {
console.error(`${talentId} not found in items`)
}
}
await actor.update({system: { talente: talente}})
}
/**
* Sets the attributes of the given actor to their default values
* @param actor
* @returns {Promise<void>}
*/
async #setBaseAttributes(actor) {
const startEigenschaften = {
"mu": {
start: 10,
aktuell: 10,
mod: 0
},
"kl": {
start: 10,
aktuell: 10,
mod: 0
},
"in": {
start: 10,
aktuell: 10,
mod: 0
},
"ch": {
start: 10,
aktuell: 10,
mod: 0
},
"ff": {
start: 10,
aktuell: 10,
mod: 0
},
"ge": {
start: 10,
aktuell: 10,
mod: 0
},
"ko": {
start: 10,
aktuell: 10,
mod: 0
},
"kk": {
start: 10,
aktuell: 10,
mod: 0
}
}
await actor.update({system: {attribute: startEigenschaften}})
}
async _onCreate(data, options, userId) { async _onCreate(data, options, userId) {
const thisCharacter = await game.actors.getName(data.name);
await this.#createBaseSkills(thisCharacter)
await this.#setBaseAttributes(thisCharacter)
super._onCreate(data, options, userId);
} }

View File

@ -8,6 +8,7 @@ export class SkillDataModel extends BaseItem {
return { return {
name: new StringField({ required: true }), name: new StringField({ required: true }),
gruppe: new StringField({ required: true }), gruppe: new StringField({ required: true }),
taw: new NumberField({ integer: true, initial: 0 }),
probe: new ArrayField(new StringField(), { exact: 3 }), // References one of the eight attributes by name probe: new ArrayField(new StringField(), { exact: 3 }), // References one of the eight attributes by name
voraussetzung: new SchemaField({ voraussetzung: new SchemaField({
talent: new StringField({ model: SkillDataModel }), talent: new StringField({ model: SkillDataModel }),
@ -86,5 +87,4 @@ export class SkillDataModel extends BaseItem {
patzer: patzerCounter === countToPatzer, patzer: patzerCounter === countToPatzer,
} }
} }
} }

View File

@ -41,4 +41,103 @@ export class Character extends Actor {
} }
async addSkillFromCompendiumByNameToActor(talentName, actor) {
const compendiumOfSkills = game.packs.get('DSA_4-1.talente-brw');
const talentId = compendiumOfSkills.index.find( skill => skill.name === talentName)
let talentObject = {}
const talent = await compendiumOfSkills.getDocument(talentId);
try {
const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [talent]))[0]
if (embeddedDocument.type === "Skill") {
if (talent) {
talentObject = {
taw: 0,
talent: embeddedDocument.id,
}
}
}
} catch (error) {
console.error(`${talentName} not found in items`, error)
}
await actor.update({system: { talente: talentObject, ...actor.system.talente}})
}
/**
* Adds base skills according to the BRW to the given actor
* @param actor
* @returns {Promise<void>}
*/
async #createBaseSkills(actor) {
const talentsByName = [
"Athletik", "Klettern", "Körperbeherrschung", "Schleichen", "Schwimmen", "Selbstbeherrschung", "Sich Verstecken", "Singen", "Sinnenschärfe", "Tanzen", "Zechen",
"Menschenkenntnis", "Überreden",
"Fährtensuchen", "Orientierung", "Wildnisleben",
"Götter/Kulte", "Rechnen", "Sagen/Legenden",
"Heilkunde: Wunden", "Holzbearbeitung", "Kochen", "Lederverarbeitung", "Malen/Zeichnen", "Schneidern"
]
const talente = []
talentsByName.forEach(talentName => {
this.addSkillFromCompendiumByNameToActor(
talentName,
)
})
await actor.update({system: { talente: talente}})
}
/**
* Sets the attributes of the given actor to their default values
* @param actor
* @returns {Promise<void>}
*/
async #setBaseAttributes(actor) {
const startEigenschaften = {
"mu": {
start: 10,
aktuell: 10,
mod: 0
},
"kl": {
start: 10,
aktuell: 10,
mod: 0
},
"in": {
start: 10,
aktuell: 10,
mod: 0
},
"ch": {
start: 10,
aktuell: 10,
mod: 0
},
"ff": {
start: 10,
aktuell: 10,
mod: 0
},
"ge": {
start: 10,
aktuell: 10,
mod: 0
},
"ko": {
start: 10,
aktuell: 10,
mod: 0
},
"kk": {
start: 10,
aktuell: 10,
mod: 0
}
}
await actor.update({system: {attribute: startEigenschaften}})
}
} }

View File

@ -39,25 +39,15 @@ export class CharacterSheet extends ActorSheet {
} }
#addSkillsToContext(context) { #addSkillsToContext(context) {
const actorSkills = {}
const actorData = context.data; const actorData = context.data;
context.skills = {}; context.skills = {};
context.flatSkills = []; context.flatSkills = [];
Object.values(actorData.items).forEach( (item) => { Object.values(actorData.items).forEach( (item, index) => {
if (item.type === "Skill") { if (item.type === "Skill") {
actorSkills[item._id] = item;
}
}
);
if ( context.system.talente?.length >= 0) { const talentGruppe = item.system.gruppe;
context.system.talente.forEach( ( { taw, talent }, index) => { const eigenschaften = Object.values(item.system.probe);
if (actorSkills[talent]) {
const talentObjekt = actorSkills[talent];
if (talentObjekt.type === 'Skill') {
const talentGruppe = talentObjekt.system.gruppe;
const eigenschaften = Object.values(talentObjekt.system.probe);
const werte = [ const werte = [
{name: eigenschaften[0], value: this.prepareEigenschaftRoll(actorData, eigenschaften[0])}, {name: eigenschaften[0], value: this.prepareEigenschaftRoll(actorData, eigenschaften[0])},
{name: eigenschaften[1], value: this.prepareEigenschaftRoll(actorData, eigenschaften[1])}, {name: eigenschaften[1], value: this.prepareEigenschaftRoll(actorData, eigenschaften[1])},
@ -69,9 +59,9 @@ export class CharacterSheet extends ActorSheet {
const obj = { const obj = {
type: "talent", type: "talent",
gruppe: talentGruppe, gruppe: talentGruppe,
name: talentObjekt.name, name: item.name,
taw: "" + taw, taw: "" + item.system.taw,
tawPath: `system.talente.${index}.taw`, tawPath: `system.items.${index}.taw`,
werte, werte,
rollEigenschaft1: werte[0].value, rollEigenschaft1: werte[0].value,
rollEigenschaft2: werte[1].value, rollEigenschaft2: werte[1].value,
@ -85,8 +75,7 @@ export class CharacterSheet extends ActorSheet {
context.flatSkills.push(obj); context.flatSkills.push(obj);
} }
} }
}) );
}
} }
#addAdvantagesToContext(context) { #addAdvantagesToContext(context) {
@ -298,88 +287,40 @@ export class CharacterSheet extends ActorSheet {
} }
async #handleDroppedSkill(actor, data) { #handleDroppedSkill(actor, skill) {
let alreadyInSet = false; const array = Array.from(actor.items);
let previousTaw = 0; for ( let i = 0; i < array.length; i++ ) {
const id = foundry.utils.parseUuid(data.uuid).id; if (array[i].name === skill.name) {
return false;
const item = (await actor.createEmbeddedDocuments('Item', [await game.items.get(id)]))[0]
actor.system.talente.forEach(({taw, talent}) => {
if (talent) {
if (talent._id === item._id) {
alreadyInSet = talent;
previousTaw = taw;
} }
} }
})
const myContent = `TaW: <input id="taw" type="number" value="${previousTaw}" />`;
new Dialog({
title: `Talent ${item.name} ${alreadyInSet?'ersetzen':'hinzufügen'}`,
content: myContent,
buttons: {
button1: {
label: "hinzufügen",
callback: (html) => myCallback(html),
icon: `<i class="fas fa-check"></i>`
}
}
}).render(true);
async function myCallback(html) {
const taw = html.find("input#taw").val();
let index = actor.system.talente.findIndex( predicate => predicate.talent && predicate.talent._id === alreadyInSet._id )
let sorted = [];
if (alreadyInSet) {
actor.system.talente[index].taw = taw;
sorted = actor.system.talente;
} else {
const newItem = {
taw: taw,
talent: {_id: item._id, name: item.name}
}
console.log(newItem, await game.items.get(item._id))
sorted = [newItem, ...actor.system.talente].sort((a, b) => {
return a.talent.name.localeCompare(b.talent.name)
}
);
} }
const serialised = sorted.map(({taw, talent}) => { static getElementByName(collection, id) {
return { const array = Array.from(collection);
taw: taw, for (const element of array) {
talent: talent._id if (element._id === id) {
return element;
} }
});
await actor.update({
system: {
talente: [
...serialised
]
}
});
ui.notifications.info(`Talent ${item.name} auf TaW ${taw} hinzugefügt`);
} }
} }
static onDroppedData(actor, characterSheet, data) { static onDroppedData(actor, characterSheet, data) {
const item = game.items.get(foundry.utils.parseUuid(data.uuid).id) const uuid = foundry.utils.parseUuid(data.uuid);
const collection = uuid.collection.index ?? uuid.collection;
switch (item.type) { const document = CharacterSheet.getElementByName(collection, uuid.id);
const {
name,
type
} = document
console.log(name, type)
switch (type) {
case "Skill": case "Skill":
characterSheet.#handleDroppedSkill(actor, data); return characterSheet.#handleDroppedSkill(actor, document); // on false cancel this whole operation
default: default:
console.log(item, item.type); return false;
} }
// maybe dont
actor.items.clear()
} }
} }

View File

@ -101,6 +101,22 @@ function calculateBirthdate(json) {
return `${day}. ${month} ${year} BF` return `${day}. ${month} ${year} BF`
} }
function mapSkills(rawJson) {
let talents = []
for (let talent in held.talentliste.talent) {
talent = held.talentliste.talent[talent]
let talentItem = game.items.getName(talent.name)
if (talentItem) {
let talentJson = {
talent: talentItem,
taw: talent.value,
}
talents.push(talentJson)
}
}
return talents
}
/** /**
* parses a json into a fitting character-json * parses a json into a fitting character-json
* @param rawJson the json parsed from the Helden-Software XML * @param rawJson the json parsed from the Helden-Software XML
@ -248,19 +264,8 @@ function mapRawJson(rawJson) {
} }
json.sonderfertigkeiten = specialAbilities json.sonderfertigkeiten = specialAbilities
json.liturgien = liturgies json.liturgien = liturgies
let talents = []
for (let talent in held.talentliste.talent) { json.talente = mapSkills(rawJson)
talent = held.talentliste.talent[talent]
let talentItem = game.items.getName(talent.name)
if (talentItem) {
let talentJson = {
talent: talentItem,
taw: talent.value,
}
talents.push(talentJson)
}
}
json.talente = talents
let spells = [] let spells = []
/*for (let spell in held.zauberliste.zauber) { /*for (let spell in held.zauberliste.zauber) {
spell = held.zauberliste.zauber[spell] spell = held.zauberliste.zauber[spell]