prepares xml-import to no longer append items, but instead cleans them out before adding. This will be controllable via dialog
Pull Request Check / testing (pull_request) Successful in 17s Details

feature/applicationv2
macniel 2025-10-21 01:17:41 +02:00
parent 2e1d747661
commit 905b0eb405
27 changed files with 658 additions and 78 deletions

View File

@ -43,6 +43,7 @@ const convert = function (from, to, ofType) {
mkdirSync(DEST)
readdirSync(SOURCE).forEach(file => {
console.log(file)
let originalSource = JSON.parse(readFileSync(join(SOURCE, file), {encoding: "utf8"}));
let id = randomID();
@ -104,6 +105,10 @@ async function prepareDB() {
convert("./src/packs/_source/liturgien-und-segnungen", "./src/packs/__source/liturgien", "Liturgy");
convert("./src/packs/_source/wunden", "./src/packs/__source/wunden", "ActiveEffect");
convert("./src/packs/_source/kulturen", "./src/packs/__source/kulturen", "Culture");
convert("./src/packs/_source/spezien", "./src/packs/__source/spezien", "Species");
convert("./src/packs/_source/professionen", "./src/packs/__source/professionen", "Profession");
} catch (err) {
console.error(err);
}

View File

@ -22,6 +22,11 @@ import {ActiveEffectSheet} from "./module/sheets/ActiveEffectSheet.mjs";
import {ActiveEffectDataModel} from "./module/data/activeeffect.mjs";
import {Trefferzone, Wunde, Zonenruestung, Zonenwunde} from "./module/data/Trefferzone.js";
import {ProfessionDataModel} from "./module/data/profession.mjs";
import {SpeciesDataModel} from "./module/data/species.mjs";
import {CultureDataModel} from "./module/data/culture.mjs";
import {CultureSheet} from "./module/sheets/CultureSheet.mjs";
import {SpeciesSheet} from "./module/sheets/SpeciesSheet.mjs";
import {ProfessionSheet} from "./module/sheets/ProfessionSheet.mjs";
async function preloadHandlebarsTemplates() {
return foundry.applications.handlebars.loadTemplates([
@ -71,6 +76,8 @@ Hooks.once("init", () => {
SpecialAbility: SpecialAbilityDataModel,
ActiveEffect: ActiveEffectDataModel,
Profession: ProfessionDataModel,
Spezies: SpeciesDataModel,
Kultur: CultureDataModel,
}
CONFIG.Combat.initiative = {
@ -95,8 +102,6 @@ Hooks.once("init", () => {
makeDefault: true,
label: 'DSA41.GroupLabel.Item'
})
// Register sheet application classes
foundry.documents.collections.Items.registerSheet('dsa41.skill', SkillSheet, {
types: ["Skill"],
makeDefault: true,
@ -132,6 +137,21 @@ Hooks.once("init", () => {
makeDefault: true,
label: 'DSA41.ActiveEffectLabels.ActiveEffect'
})
foundry.documents.collections.Items.registerSheet('dsa41.culture', CultureSheet, {
types: ['Culture'],
makeDefault: true,
label: 'DSA41.CultureLabels.Culture'
})
foundry.documents.collections.Items.registerSheet('dsa41.spezien', SpeciesSheet, {
types: ['Species'],
makeDefault: true,
label: 'DSA41.SpeciesLabels.Species'
})
foundry.documents.collections.Items.registerSheet('dsa41.profession', ProfessionSheet, {
types: ['Profession'],
makeDefault: true,
label: 'DSA41.ProfessionLabels.Profession'
})
game.settings.register('DSA_4-1', 'optional_colorfuldice', {
name: "Optional: Farbige Würfel nach Paramanthus",
@ -144,8 +164,6 @@ Hooks.once("init", () => {
},
requiresReload: false
})
game.settings.register('DSA_4-1', 'optional_trefferzonen', {
name: "Optional: Trefferzonen",
hint: "Ersetzt das Wundensystem aus dem BRW durch das Trefferzonensystem aus WdH",
@ -157,7 +175,6 @@ Hooks.once("init", () => {
},
requiresReload: true
})
game.settings.register('DSA_4-1', 'optional_ruestungzonen', {
name: "Optional: Zonenrüstung",
hint: "Ersetzt das Rüstungssystem aus dem BRW durch das Zonenrüstungssystem aus WdH",
@ -169,7 +186,6 @@ Hooks.once("init", () => {
},
requiresReload: true
})
game.settings.register('DSA_4-1', 'optional_ausdauer', {
name: "Optional: Ausdauerregeln",
hint: "Aktiviert Regeln für das Spiel mit Ausdauer",
@ -181,7 +197,6 @@ Hooks.once("init", () => {
},
requiresReload: true
})
game.settings.register('DSA_4-1', 'optional_distanzklassen', {
name: "Optional: Distanzklassen",
hint: "Aktiviert Regeln für das Spiel mit Distanzklassen",
@ -197,17 +212,6 @@ Hooks.once("init", () => {
return preloadHandlebarsTemplates();
})
Hooks.on('dropActorSheetData', (actor, sheet, data) => {
/*if (data.uuid) {
if (actor.type === "character") {
return true
} else {
return GroupSheet.onDroppedData(actor, sheet, data);
}
}*/
return true
})
Hooks.once("ready", async function () {
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
Hooks.on("hotbarDrop", (bar, data, slot) => {

View File

@ -0,0 +1,15 @@
import BaseItem from "./base-item.mjs";
const {BooleanField, StringField, HTMLField} = foundry.data.fields;
export class CultureDataModel extends BaseItem {
static defineSchema() {
return {
description: new HTMLField(),
variant: new StringField(),
feminineDemonym: new StringField(),
masculineDemonym: new StringField()
}
}
}

View File

@ -0,0 +1,15 @@
import BaseItem from "./base-item.mjs";
const {BooleanField, NumberField, StringField, HTMLField} = foundry.data.fields;
export class SpeciesDataModel extends BaseItem {
static defineSchema() {
return {
description: new HTMLField(),
baseSpeed: new NumberField({required: true, initial: 6, integer: true}),
feminineDemonym: new StringField(),
masculineDemonym: new StringField(),
}
}
}

View File

@ -92,7 +92,7 @@ export class Character extends Actor {
systemData.mr.basis = Math.round((mu + kl + ko) / 5)
systemData.mr.aktuell = systemData.mr.basis + (systemData.mr.mod ?? 0);
systemData.gs.basis = 6;
systemData.gs.aktuell = systemData.gs.basis + (systemData.gs.mod ?? 0); // TOOD: get GS from species
systemData.gs.aktuell = systemData.gs.basis + (systemData.gs.mod ?? 0); // TOOD: get GS from spezien
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {

View File

@ -0,0 +1,9 @@
export class Culture extends Item {
/**
* Augment the basic Item data model with additional dynamic data.
*/
prepareData() {
super.prepareData();
}
}

View File

@ -0,0 +1,9 @@
export class Species extends Item {
/**
* Augment the basic Item data model with additional dynamic data.
*/
prepareData() {
super.prepareData();
}
}

View File

@ -0,0 +1,54 @@
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
export class CultureSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'item', 'culture'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: CultureSheet.#onSubmitForm
},
actions: {
editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/item/culture-sheet.hbs`
},
}
/**
* Handle form submission
* @this {CultureSheet}
* @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 */
async _prepareContext(options) {
const context = await super._prepareContext(options)
context.system = context.document.system
context.name = context.document.name
context.img = context.document.img
context.description = context.document.system.description
context.masculineDemonym = context.document.system.masculineDemonym
context.feminineDemonym = context.document.system.feminineDemonym
return context
}
}

View File

@ -0,0 +1,55 @@
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
export class ProfessionSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'item', 'species'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: ProfessionSheet.#onSubmitForm
},
actions: {
editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/item/profession-sheet.hbs`
},
}
/**
* Handle form submission
* @this {ProfessionSheet}
* @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 */
async _prepareContext(options) {
const context = await super._prepareContext(options)
context.system = context.document.system
context.name = context.document.name
context.img = context.document.img
context.description = context.document.system.description
context.alias = context.document.system.alias
context.isOwner = context.document.owner
context.revealed = context.document.system.revealed
return context
}
}

View File

@ -0,0 +1,55 @@
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
export class SpeciesSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'item', 'species'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: SpeciesSheet.#onSubmitForm
},
actions: {
editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/item/species-sheet.hbs`
},
}
/**
* Handle form submission
* @this {SpeciesSheet}
* @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 */
async _prepareContext(options) {
const context = await super._prepareContext(options)
context.system = context.document.system
context.name = context.document.name
context.img = context.document.img
context.description = context.document.system.description
context.baseSpeed = context.document.baseSpeed
context.masculineDemonym = context.document.system.masculineDemonym
context.feminineDemonym = context.document.system.feminineDemonym
return context
}
}

View File

@ -211,20 +211,17 @@ export class ActionManager {
#hatWaffeinHand() {
const item = this.actor.findEquipmentOnSlot("links") ?? this.actor.findEquipmentOnSlot("rechts")
console.log(item)
return item
}
#hatMunition() {
const item = this.actor.findEquipmentOnSlot("munition")
const weapon = this.actor.findEquipmentOnSlot("fernkampf")
console.log(item?.system.quantity, weapon)
return item
}
#hatFernkampfWaffeinHand(art) {
const item = this.actor.findEquipmentOnSlot("fernkampf")
console.log(item)
return item
}
@ -238,8 +235,6 @@ export class ActionManager {
evaluate() {
let actionArray = [...this.#freeActions, ...this.#regularActions, ...this.#continuingActions]
return actionArray.filter(action => action.eval());
}

View File

@ -34,6 +34,9 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
roll: CharacterSheet.#dieRoll,
editImage: ActorSheetV2.DEFAULT_OPTIONS.actions.editImage,
openEmbeddedDocument: CharacterSheet.#openEmbeddedDocument,
openCultureDocument: CharacterSheet.#openCultureDocument,
openSpeciesDocument: CharacterSheet.#openSpeciesDocument,
}
}
@ -123,11 +126,19 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
* @param {MouseEvent} event
*/
static #openEmbeddedDocument(event) {
const dataset = event.target.parentElement.dataset
const dataset = event.target.dataset
const id = dataset.itemId ?? dataset.id
this.document.items.get(id).sheet.render(true)
}
static #openCultureDocument() {
this.document.itemTypes["Culture"]?.[0]?.sheet.render(true)
}
static #openSpeciesDocument() {
this.document.itemTypes["Species"]?.[0]?.sheet.render(true)
}
/**
* Handle form submission
* @this {AdvantageSheet}
@ -183,6 +194,26 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
}
})
context.spezies = ""
if (actorData.itemTypes["Species"]?.[0]) {
const speciesData = actorData.itemTypes["Species"][0]
if (actorData.system.geschlecht === "männlich") {
context.spezies = speciesData.system.masculineDemonym
} else {
context.spezies = speciesData.system.feminineDemonym
}
}
context.kultur = ""
if (actorData.itemTypes["Culture"]?.[0]) {
const cultureData = actorData.itemTypes["Culture"][0]
if (actorData.system.geschlecht === "männlich") {
context.kultur = cultureData.system.masculineDemonym
} else {
context.kultur = cultureData.system.feminineDemonym
}
}
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.img = actorData.img

View File

@ -1,7 +1,8 @@
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
import {BlessingDataModel} from "../data/blessing.mjs";
import {Blessing} from "../documents/blessing.mjs";
import {Profession} from "../documents/profession.mjs";
import {Culture} from "../documents/culture.mjs";
import {Species} from "../documents/species.mjs";
let months = [
"Praios",
@ -19,20 +20,48 @@ let months = [
"Namenloser Tag"
]
/**
* @typedef ImportOptions
* @property {Boolean} skipSpecies
* @property {Boolean} skipCulture
* @property {Boolean} skipProfessions
* @property {Boolean} skipAdvantages
* @property {Boolean} skipSpecialAbilities
* @property {Boolean} skipEquipment
* @property {Boolean} skipSpells
* @property {Boolean} skipLiturgies
* @property {Boolean} skipSkills
**/
/**
* Imports a character from a file created in the tool Helden-Software
* @param actorId the actor-id of the character
* @param file the file from which the character should be imported
* @param {ImportOptions?} options the set of item types the import should skip
*/
export async function importCharacter(actorId, file) {
export async function importCharacter(actorId, file, options = undefined) {
let actor = game.actors.get(actorId)
let xmlString = await parseFileToString(file)
let domParser = new DOMParser()
let dom = domParser.parseFromString(xmlString, 'application/xml')
let rawJson = getJsonFromXML(dom)
let characterJson = mapRawJson(actor, rawJson)
if (!options) {
options = {
skipSpecies: false,
skipCulture: false,
skipProfessions: false,
skipAdvantages: false,
skipSpecialAbilities: false,
skipEquipment: false,
skipSkills: false,
skipSpells: false,
skipLiturgies: false
}
}
let characterJson = mapRawJson(actor, rawJson, options)
actor.update(characterJson)
}
@ -75,6 +104,38 @@ function getJsonFromXML(dom) {
return jsonResult;
}
/**
* gets the text content of a file
* @param file the file with the desired content
* @returns {Promise<String>} a promise that returns the string contents of the file
*/
async function parseFileToString(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader()
reader.readAsText(file, "utf-8")
reader.onload = event => {
resolve(event.target.result)
}
reader.onerror = event => {
reject(event)
}
})
}
/**
*Calculates a Birthdate String in the Calendar of Bosparans Fall
* @param json json with the day, the month and the year of the birthday
* @returns {string} a string in the format of "DD.MMMM.YYYY BF"
*/
function calculateBirthdate(json) {
let day = json.gbtag
let month = months[parseInt(json.gbmonat) - 1]
let year = json.gbjahr
return `${day}. ${month} ${year} BF`
}
async function addSkillFromCompendiumByNameToActor(talentName, taw, actor, combatStatistics, attributes) {
const compendiumOfSkills = game.packs.get('DSA_4-1.talente');
const talentId = compendiumOfSkills.index.find(skill => skill.name === talentName)
@ -122,6 +183,7 @@ async function addAdvantageFromCompendiumByNameToActor(advantageName, advantageV
}
async function addSpellsFromCompendiumByNameToActor(spellName, zfw, representation, hauszauber, actor) {
const compendiumOfSpells = game.packs.get('DSA_4-1.spells');
const SCREAMING_NAME = spellName.toUpperCase()
const spellId = compendiumOfSpells.index.find(spell => spell.name === SCREAMING_NAME)
@ -155,38 +217,17 @@ async function addLiturgiesFromCompendiumByNameToActor(liturgyName, actor) {
}
}
/**
* gets the text content of a file
* @param file the file with the desired content
* @returns {Promise<String>} a promise that returns the string contents of the file
*/
async function parseFileToString(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader()
reader.readAsText(file, "utf-8")
reader.onload = event => {
resolve(event.target.result)
}
reader.onerror = event => {
reject(event)
}
})
}
/**
*Calculates a Birthdate String in the Calendar of Bosparans Fall
* @param json json with the day, the month and the year of the birthday
* @returns {string} a string in the format of "DD.MMMM.YYYY BF"
*/
function calculateBirthdate(json) {
let day = json.gbtag
let month = months[parseInt(json.gbmonat) - 1]
let year = json.gbjahr
return `${day}. ${month} ${year} BF`
}
function mapSkills(actor, held, kampfwerte) {
if (actor.itemTypes["Skill"].length > 0) {
actor.itemTypes["Skill"].forEach(s => {
actor.items.get(s._id).delete()
})
}
if (actor.itemTypes["Blessing"].length > 0) {
actor.itemTypes["Blessing"].forEach(p => {
actor.items.get(p._id).delete()
})
}
for (let talent in held.talentliste.talent) {
talent = held.talentliste.talent[talent]
@ -213,6 +254,11 @@ function mapSkills(actor, held, kampfwerte) {
}
function mapAdvantages(actor, held) {
if (actor.itemTypes["Advantage"].length > 0) {
actor.itemTypes["Advantage"].forEach(a => {
actor.items.get(a._id).delete()
})
}
for (let advantage in held.vt.vorteil) {
advantage = held.vt.vorteil[advantage]
addAdvantageFromCompendiumByNameToActor(advantage.name, advantage.value, actor)
@ -220,20 +266,35 @@ function mapAdvantages(actor, held) {
}
function mapSpells(actor, held) {
if (actor.itemTypes["Spell"].length > 0) {
actor.itemTypes["Spell"].forEach(s => {
actor.items.get(s._id).delete()
})
}
for (let spell in held.zauberliste.zauber) {
spell = held.zauberliste.zauber[spell]
addSpellsFromCompendiumByNameToActor(spell.name, spell.value, spell.repraesentation, spell.hauszauber === "true", actor)
}
}
function mapMiracles(actor, liturgies) {
function mapLiturgies(actor, liturgies) {
if (actor.itemTypes["Liturgy"].length > 0) {
actor.itemTypes["Liturgy"].forEach(l => {
actor.items.get(l._id).delete()
})
}
for (let liturgy in liturgies) {
liturgy = liturgies[liturgy]
addLiturgiesFromCompendiumByNameToActor(liturgy.name, actor)
}
}
function mapProfessions(actor, professions) {
async function mapProfessions(actor, professions) {
if (actor.itemTypes["Profession"].length > 0) {
actor.itemTypes["Profession"].forEach(p => {
actor.items.get(p._id).delete()
})
}
if (professions.string) {
professions = {hauptprofession: professions}
}
@ -251,23 +312,88 @@ function mapProfessions(actor, professions) {
])
})
// actor.update({"system.meta.professions": professions})
}
async function mapSpezies(actor, spezies) {
if (actor.itemTypes["Species"].length > 0) {
actor.itemTypes["Species"].forEach(s => {
actor.items.get(s._id).delete()
})
}
const compendiumOfSpecies = game.packs.get('DSA_4-1.spezien');
const speciesId = compendiumOfSpecies?.index.find(species => species.name === spezies.name)
if (speciesId) {
const species =
await compendiumOfSpecies.getDocument(speciesId);
try {
await actor.createEmbeddedDocuments('Item', [species])
} catch (e) {
}
} else {
actor.createEmbeddedDocuments('Item', [
new Species({
name: spezies.name,
type: "Species",
system: {
feminineDemonym: spezies.string,
masculineDemonym: spezies.string,
description: "Importiert",
}
})
])
}
}
async function mapKultur(actor, kultur) {
if (actor.itemTypes["Culture"].length > 0) {
actor.itemTypes["Culture"].forEach(c => {
actor.items.get(c._id).delete()
})
}
const compendiumOfCultures = game.packs.get('DSA_4-1.kulturen');
const cultureId = compendiumOfCultures?.index.find(culture => culture.name === kultur.name)
if (cultureId) {
const culture =
await compendiumOfCultures.getDocument(cultureId);
try {
await actor.createEmbeddedDocuments('Item', [culture])
} catch (e) {
}
} else {
actor.createEmbeddedDocuments('Item', [
new Culture({
name: kultur.name,
type: "Culture",
system: {
variant: kultur.variante.name ?? "",
feminineDemonym: kultur.string,
masculineDemonym: kultur.string,
description: "Importiert",
}
})
])
}
}
/**
* parses a json into a fitting character-json
* @param {Character} actor
* @param rawJson the json parsed from the Helden-Software XML
* @param {ImportOptions} options
* @returns {{}} a json representation of the character
*/
function mapRawJson(actor, rawJson) {
function mapRawJson(actor, rawJson, options) {
let json = {}
let held = rawJson.helden.held;
json.name = held.name
json.meta = {}
json.meta.spezies = held.basis.rasse.string
json.meta.kultur = held.basis.kultur.string
mapProfessions(actor, held.basis.ausbildungen.ausbildung)
if (!options.skipSpecies) mapSpezies(actor, held.basis.rasse) // as string includes the demonymized form
if (!options.skipCulture) mapKultur(actor, held.basis.kultur) // as string includes the demonymized form
if (!options.skipProfessions) mapProfessions(actor, held.basis.ausbildungen.ausbildung)
json.meta.geschlecht = held.basis.geschlecht.name
json.meta.haarfarbe = held.basis.rasse.aussehen.haarfarbe
json.meta.groesse = held.basis.rasse.groesse.value
@ -324,7 +450,7 @@ function mapRawJson(actor, rawJson) {
aktuell: attribute.value
}
json.attribute.so = getAttributeJson(attributes, "Sozialstatus")
mapAdvantages(actor, held)
if (!options.skipAdvantages) mapAdvantages(actor, held)
let specialAbilities = []
let liturgies = []
for (let specialAbility in held.sf.sonderfertigkeit) {
@ -372,9 +498,9 @@ function mapRawJson(actor, rawJson) {
}
json.kampfwerte = combatValues
mapSkills(actor, held, combatValues)
mapSpells(actor, held)
mapMiracles(actor, liturgies)
if (!options.skipSkills) mapSkills(actor, held, combatValues)
if (!options.skipSpells) mapSpells(actor, held)
if (!options.skipLiturgies) mapLiturgies(actor, liturgies)
let notes = []
for (let note in held.kommentare) {

View File

@ -0,0 +1,6 @@
{
"name": "helden.model.kultur.Mittelreich",
"masculineDemonym": "Mittelländische Landbevölkerung",
"feminineDemonym": "Mittelländische Landbevölkerung",
"description": ""
}

View File

@ -0,0 +1,7 @@
{
"name": "helden.model.rasse.Mittellaender",
"masculineDemonym": "Mittelländer",
"feminineDemonym": "Mittelländerin",
"description": "",
"baseSpeed": 6
}

View File

@ -26,7 +26,7 @@
"mod": "+7",
"description": "Der Magier ersetzt die verdrängte Erinnerung wahlweise mit einer eigenen Erinnerung oder fügt das gestohlene Wissen des Opfers seiner eigenen Erinnerung hinzu.",
"limit": "11"
},
}
},
"reversalis": "bringt die durch den MEMORABIA verdrängte Erinnerung wieder zurück.",
"antimagie": "BEHERRSCHUNG BRECHEN hebt den Zauber auf und bringt die Erinnerung wieder zurück; in einer Zone dieses Zaubers kann der MEMORABIA nur erschwert gesprochen werden.",

View File

@ -1,7 +1,11 @@
{
"seite": "287",
"name": "WINDSTILLE",
"probe": ["KL", "CH", "KK"],
"probe": [
"KL",
"CH",
"KK"
],
"technik": "Der Elf formt die Hände über seinem Kopf zu einem Dach, spricht 'rongra sala bian'dao' und breitet die Arme aus.",
"zauberdauer": "30 Aktionen",
"wirkung": "Der Zauber erschafft eine Zone völliger Windstille, unabhängig von der Windstärke außerhalb. Diese Zone bewegt sich mit dem Elfen. Innerhalb dieser Zone steht die Luft still selbst Blätter und Rauch verharren. Geräusche tragen schlechter, Pfeile und Geschosse verlieren an Reichweite, und fliegende Wesen haben Mühe, sich zu bewegen. Die Zauberprobe ist um die aktuelle Windstärke (nach Beaufort-Skala) erschwert. Der Effekt wirkt bis zur Windstärke 12 (Orkan).",
@ -29,7 +33,11 @@
},
"reversalis": "Jede Luftbewegung innerhalb der Zone wird um ZfP*/2 Windstärken verstärkt, sodass schon ein leichtes Blasen einen kräftigen Wind erzeugen kann. Diese Variante kostet 10 AsP pro Spielrunde.",
"antimagie": "VERÄNDERUNG AUFHEBEN oder LUFTBANN können den Zauber schwächen oder beenden. Befindet sich die Zone in einem Sturm, kann dieser abgeschwächt, aber nicht aufgehoben werden.",
"merkmal": ["Elementar (Luft)", "Umwelt"],
"merkmal": [
"Elementar (Luft)",
"Umwelt"
],
"komplexität": "C",
"repräsentation": "Elf 6, Mag, Dru (Mag) je 2",
"info": "WINDSTILLE gilt als Teil einer Hexalogie des Banns elementarer Gewalten, die vermutlich von el
"info": "WINDSTILLE gilt als Teil einer Hexalogie des Banns elementarer Gewalten, die vermutlich von el"
}

View File

@ -17,4 +17,4 @@
"komplexität": "E",
"repräsentation": "Mag 2",
"info": "Der Zauber wurde erstmals in Punin dokumentiert und vermutlich aus einem alten Text über Rohals Systeme abgeleitet. Heute wird er in der Puniner Akademie, der Thorwaler Hellsichtschule und dem Khunchomer Artefaktmagier-Zirkel gelehrt. XENOGRAPHUS SCHRIFTENKUNDE wird vor allem von Gelehrten, Artefaktmagiern und Historikern genutzt, um uralte Texte, magische Runen oder verschlüsselte Aufzeichnungen zu entziffern. Der Zauber übersetzt jedoch keine Sprache und vermittelt auch kein echtes Sprachverständnis."
},
}

View File

@ -0,0 +1,26 @@
.application.sheet.dsa41.item.culture {
section.culture {
display: flex;
flex-direction: column;
flex: 1;
div {
flex: 0;
}
div.editor {
flex: 1;
display: flex;
flex-direction: column;
label {
flex: 0;
}
}
}
}

View File

@ -0,0 +1,26 @@
.application.sheet.dsa41.item.profession {
section.profession {
display: flex;
flex-direction: column;
flex: 1;
div {
flex: 0;
}
div.editor {
flex: 1;
display: flex;
flex-direction: column;
label {
flex: 0;
}
}
}
}

View File

@ -0,0 +1,26 @@
.application.sheet.dsa41.item.species {
section.species {
display: flex;
flex-direction: column;
flex: 1;
div {
flex: 0;
}
div.editor {
flex: 1;
display: flex;
flex-direction: column;
label {
flex: 0;
}
}
}
}

View File

@ -20,3 +20,7 @@
@use "organisms/skill-sheet";
@use "organisms/active-effect-sheet";
@use "organisms/advantage-sheet";
@use "organisms/culture-sheet";
@use "organisms/species-sheet";
@use "organisms/profession-sheet";

View File

@ -92,6 +92,30 @@
"type": "Item",
"path": "packs/wunden",
"private": false
},
{
"name": "Professions",
"label": "Professionen",
"system": "DSA_4-1",
"type": "Item",
"path": "packs/professionen",
"private": false
},
{
"name": "Species",
"label": "Rassen",
"system": "DSA_4-1",
"type": "Item",
"path": "packs/spezien",
"private": false
},
{
"name": "Cultures",
"label": "Kulturen",
"system": "DSA_4-1",
"type": "Item",
"path": "packs/kulturen",
"private": false
}
],
"languages": [
@ -127,6 +151,19 @@
}
},
"Item": {
"Culture": {
"htmlFields": [
"description"
]
},
"Species": {
"htmlFields": [
"description"
],
"numberFields": [
"baseSpeed"
]
},
"Profession": {
"htmlFields": [
"description"

View File

@ -8,11 +8,11 @@
<div>
<input class="name" name="name" type="text" value="{{name}}" placeholder="Name"/>
<div class="rkp">
<span class="pill species">{{system.meta.spezies}}</span>
<span class="pill culture">{{system.meta.kultur}}</span>
<span class="pill species" data-action="openSpeciesDocument">{{spezies}}</span>
<span class="pill culture" data-action="openCultureDocument">{{kultur}}</span>
{{#each professions}}
<span class="pill profession" {{#if isOwner}}data-action="openEmbeddedDocument"
data-item-id="{{this.id}}"{{/if}}>{{this.name}}</span>
<span class="pill profession" data-action="openEmbeddedDocument"
data-item-id="{{this.id}}">{{this.name}}</span>
{{/each}}
</div>
</div>

View File

@ -0,0 +1,21 @@
<section class="culture">
<div>
<label>Männliche Form</label>
<input type="text" name="system.masculineDemonym" value="{{masculineDemonym}}"/>
</div>
<div>
<label>Weibliche Form</label>
<input type="text" name="system.feminineDemonym" value="{{feminineDemonym}}"/>
</div>
<div class="editor">
<label>Beschreibung</label>
<prose-mirror
name=" system.description"
button="false"
editable="{{editable}}"
toggled="true"
value="{{description}}">
{{{description}}}
</prose-mirror>
</div>
</section>

View File

@ -0,0 +1,21 @@
<section class="profession">
<div>
<label>Name</label>
<input type="text" name="system.name" value="{{name}}"/>
{{#if isOwner}}
<input type="checkbox" name="system.revealed" {{checked revealed}}><input type="text" name="system.alias"
value="{{alias}}"/>{{/if}}
</div>
<div class="editor">
<label>Beschreibung</label>
<prose-mirror
name=" system.description"
button="false"
editable="{{editable}}"
toggled="true"
value="{{system.description}}">
{{{system.description}}}
</prose-mirror>
</div>
</section>

View File

@ -0,0 +1,25 @@
<section class="species">
<div>
<label>Männliche Form</label>
<input type="text" name="system.masculineDemonym" value="{{masculineDemonym}}"/>
</div>
<div>
<label>Weibliche Form</label>
<input type="text" name="system.feminineDemonym" value="{{feminineDemonym}}"/>
</div>
<div>
<label>Basis GS</label>
<input type="number" name="system.baseSpeed" value="{{baseSpeed}}"/>
</div>
<div class="editor">
<label>Beschreibung</label>
<prose-mirror
name=" system.description"
button="false"
editable="{{editable}}"
toggled="true"
value="{{description}}">
{{{description}}}
</prose-mirror>
</div>
</section>