Poppy 2025-10-14 12:26:24 +02:00
commit dbe1d10f6a
257 changed files with 5158 additions and 1123 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
dist
node_modules
src/packs/__source

View File

@ -5,8 +5,10 @@
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
<excludeFolder url="file://$MODULE_DIR$/src/packs/__source" />
<excludeFolder url="file://$MODULE_DIR$/src/packs/__source" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
</module>

View File

@ -1,23 +1,71 @@
import {dest, series, src} from 'gulp';
import process from 'node:process';
import replace from 'gulp-replace';
import {getRandomValues} from 'node:crypto';
import * as dartSass from 'sass';
import gulpSass from 'gulp-sass';
import {deleteAsync} from 'del';
import {readdirSync} from 'node:fs';
import {readdirSync, readFileSync, writeFileSync, rmdirSync, existsSync, mkdirSync} from "fs";
import {join} from 'node:path';
import {compilePack} from '@foundryvtt/foundryvtt-cli';
const sass = gulpSass(dartSass);
/**
* Generate a random alphanumeric string ID of a given requested length using `crypto.getRandomValues()`.
* @param {number} length The length of the random string to generate, which must be at most 16384.
* @returns {string} A string containing random letters (A-Z, a-z) and numbers (0-9).
*/
function randomID(length = 16) {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const cutoff = 0x100000000 - (0x100000000 % chars.length);
const random = new Uint32Array(length);
do {
getRandomValues(random);
} while (random.some(x => x >= cutoff));
let id = "";
for (let i = 0; i < length; i++) id += chars[random[i] % chars.length];
return id;
}
const convert = function (from, to, ofType) {
const SOURCE = from;
const DEST = to;
const TYPE = ofType;
try {
rmdirSync(DEST, {force: true, recursive: true})
} catch (e) {
}
mkdirSync(DEST)
readdirSync(SOURCE).forEach(file => {
let originalSource = JSON.parse(readFileSync(join(SOURCE, file), {encoding: "utf8"}));
let id = randomID();
let targetSource = {
_id: id,
_key: "!items!" + id,
type: TYPE,
img: originalSource.image,
name: originalSource.name.trim(),
system: {...originalSource},
}
delete targetSource.system.image;
let target = JSON.stringify(targetSource, null, 2);
let newFileName = "./" + join(DEST, id + ".json");
writeFileSync(newFileName, target, {encoding: "utf8"});
});
}
function cleanDist() {
return deleteAsync(['dist/**']);
}
function buildStyles() {
return src('src/style/**/*.scss')
.pipe(sass().on('error', sass.logError))
@ -40,6 +88,28 @@ function updateManifestFile() {
.pipe(dest('dist/'))
}
async function prepareDB() {
try {
if (!existsSync("./src/packs/__source")) {
mkdirSync("./src/packs/__source");
}
convert("./src/packs/_source/talente", "./src/packs/__source/talente", "Skill");
convert("./src/packs/_source/zauber", "./src/packs/__source/zauber", "Spell");
convert("./src/packs/_source/vorteile", "./src/packs/__source/vorteile", "Advantage");
convert("./src/packs/_source/waffen", "./src/packs/__source/waffen", "Equipment");
convert("./src/packs/_source/munition", "./src/packs/__source/munition", "Equipment");
convert("./src/packs/_source/ruestzeug", "./src/packs/__source/ruestzeug", "Equipment");
convert("./src/packs/_source/liturgien-und-segnungen", "./src/packs/__source/liturgien", "Liturgy");
convert("./src/packs/_source/wunden", "./src/packs/__source/wunden", "ActiveEffect");
} catch (err) {
console.error(err);
}
}
function buildDB() {
// Determine which source folders to process
@ -55,7 +125,7 @@ function buildDB() {
const src = join(PACK_SRC, folder.name);
const dest = join(PACK_DEST, folder.name);
console.info(`Compiling pack ${folder.name}`);
await compilePack(src, dest, {recursive: true, log: true, nedb: false});
await compilePack(src, dest, {recursive: true, nedb: false});
}
resolve()
@ -68,6 +138,7 @@ export default series(
copySource,
copyAssets,
buildStyles,
prepareDB,
buildDB,
updateManifestFile
)

BIN
src/assets/Aves.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
src/assets/Kor.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
src/assets/Phex.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
src/assets/Tsa.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -13,27 +13,42 @@ import {EquipmentDataModel} from "./module/data/equipment.mjs";
import {AusruestungSheet} from "./module/sheets/equipmentSheet.mjs";
import {CreatureDataModel} from "./module/data/creature.mjs";
import {CreatureSheet} from "./module/sheets/creatureSheet.mjs";
import {LiturgySheet} from "./module/sheets/liturgySheet.mjs";
import {LiturgyDataModel} from "./module/data/liturgy.mjs";
import {BlessingDataModel} from "./module/data/blessing.mjs";
import {SpecialAbilityDataModel} from "./module/data/specialAbility.mjs";
import {SpecialAbilitySheet} from "./module/sheets/specialAbilitySheet.mjs";
import {ActiveEffectSheet} from "./module/sheets/ActiveEffectSheet.mjs";
import {ActiveEffectDataModel} from "./module/data/activeeffect.mjs";
import {Trefferzone, Wunde, Zonenruestung, Zonenwunde} from "./module/data/Trefferzone.js";
async function preloadHandlebarsTemplates() {
return loadTemplates([
// ui partials.
'systems/DSA_4-1/templates/ui/partial-rollable-button.hbs',
'systems/DSA_4-1/templates/ui/partial-rollable-weaponskill-button.hbs',
'systems/DSA_4-1/templates/ui/partial-rollable-language-button.hbs',
'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-sf-button.hbs',
'systems/DSA_4-1/templates/ui/partial-action-button.hbs',
'systems/DSA_4-1/templates/ui/partial-equipment-button.hbs',
'systems/DSA_4-1/templates/ui/partial-equipment-group-button.hbs',
'systems/DSA_4-1/templates/ui/partial-array-editor.hbs'
'systems/DSA_4-1/templates/ui/partial-array-editor.hbs',
'systems/DSA_4-1/templates/dialog/modify-liturgy.hbs'
]);
}
Hooks.once("init", () => {
game.DSA41 = {
rollItemMacro
rollItemMacro,
Zonenruestung,
Zonenwunde,
Trefferzone,
Wunde
}
// Configure custom Document implementations.
@ -51,6 +66,10 @@ Hooks.once("init", () => {
Spell: SpellDataModel,
Advantage: VornachteileDataModel,
Equipment: EquipmentDataModel,
Liturgy: LiturgyDataModel,
Blessing: BlessingDataModel,
SpecialAbility: SpecialAbilityDataModel,
ActiveEffect: ActiveEffectDataModel,
}
CONFIG.Combat.initiative = {
@ -94,9 +113,73 @@ Hooks.once("init", () => {
})
Items.registerSheet('dsa41.equipment', AusruestungSheet, {
types: ["Equipment"],
makeDefault: true,
makeDefault: false,
label: 'DSA41.AusruestungLabels.Item'
})
Items.registerSheet('dsa41.liturgy', LiturgySheet, {
types: ["SpecialAbility"],
makeDefault: true,
label: 'DSA41.SpecialAbilityLabels.Item'
})
Items.registerSheet('dsa41.specialAbility', SpecialAbilitySheet, {
types: ["Liturgy"],
makeDefault: true,
label: 'DSA41.LiturgyLabels.Item'
})
Items.registerSheet('dsa41.activeEffect', ActiveEffectSheet, {
types: ['ActiveEffect'],
makeDefault: true,
label: 'DSA41.ActiveEffectLabels.ActiveFfect'
})
game.settings.register('DSA_4-1', 'optional_trefferzonen', {
name: "Optional: Trefferzonen",
hint: "Ersetzt das Wundensystem aus dem BRW durch das Trefferzonensystem aus WdH",
scope: "world",
config: true,
type: Boolean,
default: false,
onChange: value => {
},
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",
scope: "world",
config: true,
type: Boolean,
default: false,
onChange: value => {
},
requiresReload: true
})
game.settings.register('DSA_4-1', 'optional_ausdauer', {
name: "Optional: Ausdauerregeln",
hint: "Aktiviert Regeln für das Spiel mit Ausdauer",
scope: "world",
config: true,
type: Boolean,
default: false,
onChange: value => {
},
requiresReload: true
})
game.settings.register('DSA_4-1', 'optional_distanzklassen', {
name: "Optional: Distanzklassen",
hint: "Aktiviert Regeln für das Spiel mit Distanzklassen",
scope: "world",
config: true,
type: Boolean,
default: false,
onChange: value => {
},
requiresReload: true
})
return preloadHandlebarsTemplates();
})
@ -113,39 +196,44 @@ Hooks.on('dropActorSheetData', (actor, sheet, data) => {
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) => createBoilerplateMacro(data, slot));
Hooks.on("hotbarDrop", (bar, data, slot) => {
return createTalentMacro(data, slot)
});
});
async function createBoilerplateMacro(data, slot) {
async function createTalentMacro(data, slot) {
console.log(data, slot)
if (data.type !== "Item") return;
if (!("data" in data)) return ui.notifications.warn("You can only create macro buttons for owned Items");
const item = data.data;
const uuid = foundry.utils.parseUuid(data.uuid)
const itemId = uuid.id;
const actorId = uuid.primaryId;
const item = await game.actors.get(actorId).items.get(itemId);
// Create the macro command
const command = `game.DSA41.rollItemMacro("${item.name}");`;
let macro = game.macros.entities.find(m => (m.name === item.name) && (m.command === command));
if (!macro) {
macro = await Macro.create({
const command = `game.DSA41.rollItemMacro("${data.uuid}");`;
const macro = await Macro.create({
name: item.name,
type: "script",
img: item.img,
command: command,
flags: {"dsa41.itemMacro": true}
});
}
flags: {"dsa41.skillMacro": true}
});
game.user.assignHotbarMacro(macro, slot);
return false;
}
function rollItemMacro(itemName) {
function rollItemMacro(_uuid) {
const speaker = ChatMessage.getSpeaker();
let actor;
if (speaker.token) actor = game.actors.tokens[speaker.token];
if (!actor) actor = game.actors.get(speaker.actor);
const item = actor ? actor.items.find(i => i.name === itemName) : null;
if (!item) return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemName}`);
const uuid = foundry.utils.parseUuid(_uuid)
const itemId = uuid.id;
const actorId = uuid.primaryId;
let actor = game.actors.get(actorId);
const item = actor ? actor.items.get(itemId) : null;
if (!item) return ui.notifications.warn(`Your controlled Actor does not have an item with id ${itemId}`);
// Trigger the item roll
return item.roll();
return item.system.roll();
}

View File

@ -0,0 +1,30 @@
export const Trefferzone = {
ARM_LINKS: "armlinks",
ARM_RECHTS: "armrechts",
BEIN_LINKS: "beinlinks",
BEIN_RECHTS: "beinrechts",
BAUCH: "bauch",
KOPF: "kopf",
BRUST: "brust"
}
export const Zonenruestung = {
...Trefferzone,
WAFFE_LINKS: "links",
WAFFE_RECHTS: "rechts",
FERNKAMPF: "fernkampf",
MUNITION: "munition",
RUECKEN: "ruecken",
}
export const Zonenwunde = {
ARM_LINKS: "Wunde linker Arm",
ARM_RECHTS: "Wunde rechter Arm",
BEIN_LINKS: "Wunde rechtes Bein",
BEIN_RECHTS: "Wunde rechtes Bein",
BAUCH: "Bauchwunde",
KOPF: "Kopfwunde",
BRUST: "Brustwunde",
}
export const Wunde = "Wunde"

View File

@ -0,0 +1,52 @@
import BaseItem from "./base-item.mjs";
const {ArrayField, BooleanField, NumberField, AnyField, StringField, HTMLField} = foundry.data.fields;
export class ActiveEffectDataModel extends BaseItem {
static defineSchema() {
return {
name: new StringField({required: true}),
notes: new HTMLField(),
unique: new BooleanField({initial: false}),
effects: new AnyField()
}
/*
name: String, // Name of Vornachteil will be used for rendering and referencing by other Items
description: HTMLString, // only used for rendering
variant: [String]?, // variant name of Vornachteil e.g. "Mut" in the case of "Herausragende Eigenschaft"
levels: [Number]?, // available levels e.g. 1, 2 in the case of "Flink"
mods: [
{
level: Number?, // in reference to level of the Vornachteil, is null when it does not have any levels or is the only modification
field: String, // Reference to Actor Data e.g. "FF" maps to "FF.mod"
value: Number, // value of the Modification e.g. "+2" maps to 2
requirement: {
field: String // Reference to Actor Data e.g. "BE" maps "be.aktuell"
operation: String // Supported: "<=", ">"
value: Number // Target Value the referenced field has to compare against
}? // optional when the mod does not have an active requirement
}
]
*/
}
_onCreate(data, options, userId) {
super._onCreate(data, options, userId);
console.log(data);
if (this.parent.getEmbeddedCollection("ActiveEffect").contents.length === 0) {
this.parent.createEmbeddedDocuments("ActiveEffect", [{
name: data.name,
changes: data.system.effects,
duration: {},
icon: this.img,
}]);
console.log("added default activeffect");
}
}
}

View File

@ -0,0 +1,13 @@
const {
SchemaField, NumberField, StringField, EmbeddedDocumentField, DocumentIdField, ArrayField, ForeignDocumentField
} = foundry.data.fields;
export class BlessingDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
gottheit: new StringField(),
wert: new NumberField({min: 0, integer: true}),
}
}
}

View File

@ -1,5 +1,12 @@
const {
SchemaField, NumberField, StringField, EmbeddedDocumentField, DocumentIdField, ArrayField, ForeignDocumentField
SchemaField,
NumberField,
StringField,
HTMLField,
EmbeddedDocumentField,
DocumentIdField,
ArrayField,
ForeignDocumentField
} = foundry.data.fields;
export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
@ -13,16 +20,17 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
professions: new ArrayField(new StringField()),
geschlecht: new StringField(),
haarfarbe: new StringField(),
groesse: new NumberField({required: true, integer: false}),
groesse: new StringField(),
augenfarbe: new StringField(),
geburtstag: new StringField(),
alter: new NumberField({required: true, integer: true}),
gewicht: new NumberField({required: true, integer: true}),
aussehen: new ArrayField(new StringField()),
familie: new ArrayField(new StringField()),
alter: new StringField(),
gewicht: new StringField(),
aussehen: new HTMLField(),
familie: new HTMLField(),
titel: new StringField(),
stand: new StringField(),
}),
setEquipped: new NumberField({required: true, initial: 0, max: 3, integer: true}),
ini: new SchemaField({
aktuell: new NumberField({required: true, integer: true, initial: 0}),
mod: new NumberField({required: true, integer: true, initial: 0}),
@ -47,6 +55,11 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
aktuell: new NumberField({required: true, integer: true, initial: 0}),
mod: new NumberField({required: true, integer: true}),
}),
gs: new SchemaField({
aktuell: new NumberField({required: true, integer: true, initial: 0}),
mod: new NumberField({required: true, integer: true}),
}),
ws: new NumberField({required: true, integer: true, initial: 0}),
attribute: new SchemaField({
mu: new SchemaField({
start: new NumberField({required: true, integer: true}),
@ -107,23 +120,7 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
}),
gilde: new StringField(),
}),
vornachteile: new ArrayField(new SchemaField({
vornachteil: new DocumentIdField(Item),
wert: new NumberField({required: false, integer: true}),
})),
sonderfertigkeiten: new ArrayField(new SchemaField({
name: new StringField(),
auswahlen: new ArrayField(new StringField()),
})),
talente: new ArrayField(new DocumentIdField(Item)),
zauber: new ArrayField(new SchemaField({
talent: new DocumentIdField(),
zfw: new NumberField({integer: true, required: true}),
})),
liturgien: new ArrayField(new SchemaField({
name: new StringField(),
})),
kampfwerte: new ArrayField(new SchemaField({
name: new StringField(),
at: new NumberField({required: true, integer: true}),
@ -133,7 +130,18 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
key: new StringField(),
notiz: new StringField(),
})),
wunden: new SchemaField({
aktuell: new NumberField({required: true, integer: true}), // only with DSA_4-1.optional_trefferzonen = false
max: new NumberField({required: true, integer: true}), // only with DSA_4-1.optional_trefferzonen = false
mod: new NumberField({required: true, integer: true}), // only with DSA_4-1.optional_trefferzonen = false
armlinks: new NumberField({required: true, integer: true, initial: 0}),
armrechts: new NumberField({required: true, integer: true, initial: 0}),
beinlinks: new NumberField({required: true, integer: true, initial: 0}),
beinrechts: new NumberField({required: true, integer: true, initial: 0}),
bauch: new NumberField({required: true, integer: true, initial: 0}),
brust: new NumberField({required: true, integer: true, initial: 0}),
kopf: new NumberField({required: true, integer: true, initial: 0}),
}),
heldenausruestung: new ArrayField(
new SchemaField({
links: new DocumentIdField(),
@ -160,27 +168,19 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
_initializeSource(data, options) {
if (data.heldenausruestung.length === 0) {
let sets = [];
for (let i = 0; i < 3; i++) {
const preppedSet = {}
PlayerCharacterDataModel.getSlots().forEach(slot => {
preppedSet[slot] = null;
})
sets.push(preppedSet);
}
data.heldenausruestung = sets
}
return super._initializeSource(data, options);
}
async _onCreate(data, options, userId) {
}
static getSlots() {

View File

@ -1,5 +1,12 @@
const {
SchemaField, NumberField, StringField, EmbeddedDocumentField, DocumentIdField, ArrayField, ForeignDocumentField
SchemaField,
ObjectField,
NumberField,
StringField,
EmbeddedDocumentField,
DocumentIdField,
ArrayField,
ForeignDocumentField
} = foundry.data.fields;
export class GroupDataModel extends foundry.abstract.TypeDataModel {
@ -13,7 +20,8 @@ export class GroupDataModel extends foundry.abstract.TypeDataModel {
}),
characters: new ArrayField(
new DocumentIdField(Actor)
)
),
settings: new ObjectField(),
}
}

View File

@ -0,0 +1,49 @@
import BaseItem from "./base-item.mjs";
const {BooleanField, NumberField, SchemaField, ArrayField, StringField, HTMLField} = foundry.data.fields;
export class LiturgyDataModel extends BaseItem {
static defineSchema() {
return {
herkunft: new ArrayField(new SchemaField({
name: new StringField(),
grad: new NumberField(),
})),
grad: new NumberField({min: 1, max: 5}),
reichweite: new StringField(),
ziel: new StringField(),
wirkungsdauer: new StringField(),
auswirkung: new SchemaField({
I: new StringField(),
II: new StringField(),
III: new StringField(),
IV: new StringField(),
V: new StringField(),
VI: new StringField(),
VII: new StringField(),
VIII: new StringField(),
})
}
}
prepareData() {
}
/**
* Prepare a data object which is passed to any Roll formulas which are created related to this Item
* @private
*/
getRollData() {
}
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
*/
async roll() {
}
}

View File

@ -0,0 +1,115 @@
export class LiturgyData {
static ranks = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII"]
static #ranks = [
{index: 0, name: "O", lkp: 3, mod: 2, costKaP: 2, costKaPPermant: 0, duration: "{*} KR", strength: "{*}/2"},
{index: 1, name: "I", lkp: 3, mod: 0, costKaP: 5, costKaPPermant: 0, duration: "{*} KR", strength: "{*}/2"},
{
index: 2,
name: "II",
lkp: 6,
mod: -2,
costKaP: 10,
costKaPPermant: 0,
duration: "{*}*10 KR",
strength: "{*}/2+5"
},
{index: 3, name: "III", lkp: 9, mod: -4, costKaP: 15, costKaPPermant: 0, duration: "{*} SR", strength: "{*}+5"},
{
index: 4,
name: "IV",
lkp: 12,
mod: -6,
costKaP: 20,
costKaPPermant: 0,
duration: "{*} Stunden",
strength: "{*}+10"
},
{
index: 5,
name: "V",
lkp: 15,
mod: -8,
costKaP: 25,
costKaPPermant: 1,
duration: "{*} Tage",
strength: "{*}+15"
},
{
index: 6,
name: "VI",
lkp: 18,
mod: -10,
costKaP: 30,
costKaPPermant: 3,
duration: "{*} Wochen",
strength: "{*}+20"
},
{
index: 7,
name: "VII",
lkp: 21,
mod: -12,
costKaP: 35,
costKaPPermant: 5,
duration: "{*} Monate",
strength: "{*}+25"
},
{
index: 8,
name: "VIII",
lkp: 24,
mod: -14,
costKaP: 40,
costKaPPermant: 7,
duration: "{*} Jahre oder permanent",
casttime: "",
strength: "{*}+30"
},
];
static alverans = [
"Praios",
"Rondra",
"Efferd",
"Travia",
"Boron",
"Hesinde",
"Firun",
"Tsa",
"Phex",
"Peraine",
"Ingerimm",
"Rahja"
]
static #aliases = [
{
"originalName": "Handwerkssegen",
"aliases": ["Cereborns Handreichung", "Hauch der Leidenschaft"]
},
{
"originalName": "Heiliger Befehl",
"aliases": ["Wort der Wahrheit"],
},
{
"originalName": "Eidsegen",
"aliases": ["Lehnseid"],
}
]
static getRankOfLiturgy(liturgy, deity) {
const lookupData = liturgy.herkunft.find(p => p.name === deity)
const rank = lookupData?.grad;
return LiturgyData.#ranks[rank];
}
static lookupAlias(alias) {
return LiturgyData.#aliases.find((entry) => {
console.log(alias, entry.aliases.indexOf(alias) !== -1)
return entry.aliases.indexOf(alias) !== -1
})?.originalName ?? alias; // cant determine thus simply return the original query name
}
}

View File

@ -10,7 +10,7 @@ export class SkillDataModel extends BaseItem {
gruppe: new StringField({required: true}),
taw: new NumberField({integer: true, initial: 0}),
at: new NumberField({required: false, integer: true, initial: 0}),
pa: new NumberField({required: false, integer: true, initial: 0}),
pa: new NumberField({required: false, integer: true, nullable: true, initial: 0}),
probe: new ArrayField(new StringField(), {exact: 3}), // References one of the eight attributes by name
voraussetzung: new SchemaField({
talent: new StringField({model: SkillDataModel}),
@ -41,25 +41,25 @@ export class SkillDataModel extends BaseItem {
* @private
*/
async roll() {
console.log(this.parent)
let roll1 = new Roll("3d20", this.actor.getRollData());
const owner = this.parent.parent
let roll1 = new Roll("3d20", owner.getRollData());
let evaluated1 = (await roll1.evaluate())
const dsaDieRollEvaluated = this._evaluateRoll(evaluated1.terms[0].results, {
taw: dataset.taw,
werte: [this.system.probe[0], this.system.probe[1], this.system.probe[2]],
taw: this.taw,
werte: [this.probe[0], this.probe[1], this.probe[2]],
})
if (dsaDieRollEvaluated.tap >= 0) { // erfolg
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.actor}),
speaker: ChatMessage.getSpeaker({actor: owner}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Meisterlich geschafft' : 'Geschafft'} mit ${dsaDieRollEvaluated.tap} Punkten übrig`,
rollMode: game.settings.get('core', 'rollMode'),
})
} else { // misserfolg
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.actor}),
speaker: ChatMessage.getSpeaker({actor: owner}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Gepatzt' : ''} mit ${Math.abs(dsaDieRollEvaluated.tap)} Punkten daneben`,
rollMode: game.settings.get('core', 'rollMode'),
})

View File

@ -0,0 +1,12 @@
import BaseItem from "./base-item.mjs";
const {BooleanField, NumberField, SchemaField, ArrayField, StringField, HTMLField} = foundry.data.fields;
export class SpecialAbilityDataModel extends BaseItem {
static defineSchema() {
return {
name: new StringField()
}
}
}

View File

@ -7,9 +7,11 @@ export class SpellDataModel extends BaseItem {
static defineSchema() {
return {
seite: new NumberField(),
zfw: new NumberField(),
name: new StringField({required: true}),
probe: new ArrayField(new StringField(), {required: true, exact: 3}),
probeMod: new StringField(),
hauszauber: new BooleanField(),
technik: new StringField(),
zauberdauer: new StringField(),
wirkung: new StringField(),

View File

@ -0,0 +1,89 @@
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
export class ModifyLiturgy {
static data = {}
static naming = {
"range": "Reichweite",
"strength": "Wirkung",
"target": "Ziele",
"castduration": "Wirkzeit",
"duration": "Wirkdauer"
}
constructor(data) {
ModifyLiturgy.data = data;
ModifyLiturgy.data.maxmods = Math.round(data.lkp / 3);
ModifyLiturgy.data.variation = null;
console.log("ModifyLiturgy constructed", data)
}
static renderMods(html) {
let result = '';
ModifyLiturgy.data.mods.forEach(((mod, index) => {
result += `<tr><td>${LiturgyData.ranks[mod.rank]}</td><td>${ModifyLiturgy.naming[mod.mod]}</td><td><button class="remove-mod" data-index="${index}"><i class="fa-solid fa-xmark"></i></button></td></tr>`
}))
return result;
}
handleRender(html) {
html.off('click', 'input[name="data.variation"]')
html.on('click', 'input[name="data.variation"]', (evt) => {
if (evt.currentTarget.checked) {
ModifyLiturgy.data.variation = evt.currentTarget.dataset['rank'];
ModifyLiturgy.data.mods = [];
}
this.render(html)
})
html.off('click', 'button[class="remove-mod"]')
html.on('click', 'button[class="remove-mod"]', (evt) => {
const {index} = evt.currentTarget.dataset;
ModifyLiturgy.data.mods.splice(index, 1);
this.render(html)
})
html.off('change', 'select[name="mod"]')
html.on('change', 'select[name="mod"]', (evt) => {
const value = evt.currentTarget.value;
if (value === '') return;
const currentRank = ModifyLiturgy.data.mods.length + Number(ModifyLiturgy.data.rank);
ModifyLiturgy.data.mods.push({
rank: currentRank,
mod: value,
});
evt.currentTarget.value = "";
this.render(html)
})
// render state
$('#mods', html).html(ModifyLiturgy.renderMods(html))
// state handling
if (ModifyLiturgy.data.mods.length === ModifyLiturgy.data.maxmods) {
$(".editor, .editor *", html).attr('disabled', 'disabled');
$(".editor select", html).hide();
$('span#info', html).text('LkW lässt keine weitere Modifikationen zu')
$("#mod_rank", html).text(LiturgyData.ranks[ModifyLiturgy.data.mods.length + Number(ModifyLiturgy.data.rank)]);
} else if (ModifyLiturgy.data.variation == null) {
$(".editor select *", html).attr('disabled', 'disabled');
$(".editor select", html).hide();
$('span#info', html).text('Keine Variante ausgewählt')
$("#mod_rank", html).text('');
} else {
$(".editor, .editor *", html).removeAttr('disabled');
$(".editor select", html).show();
$('span#info', html).text('')
$("#mod_rank", html).text('');
}
}
}

View File

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

View File

@ -1,4 +1,6 @@
import {importCharacter} from "../xml-import/xml-import.mjs";
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
import {Zonenruestung, Zonenwunde} from "../data/Trefferzone.js";
export class Character extends Actor {
@ -27,27 +29,185 @@ export class Character extends Actor {
const actorData = this;
const systemData = actorData.system;
systemData.attribute.mu.aktuell = systemData.attribute.mu.start + systemData.attribute.mu.mod;
systemData.attribute.kl.aktuell = systemData.attribute.kl.start + systemData.attribute.kl.mod;
systemData.attribute.in.aktuell = systemData.attribute.in.start + systemData.attribute.in.mod;
systemData.attribute.ch.aktuell = systemData.attribute.ch.start + systemData.attribute.ch.mod;
systemData.attribute.ff.aktuell = systemData.attribute.ff.start + systemData.attribute.ff.mod;
systemData.attribute.ge.aktuell = systemData.attribute.ge.start + systemData.attribute.ge.mod;
systemData.attribute.ko.aktuell = systemData.attribute.ko.start + systemData.attribute.ko.mod;
systemData.attribute.kk.aktuell = systemData.attribute.kk.start + systemData.attribute.kk.mod;
const mu = systemData.attribute.mu.aktuell;
const kl = systemData.attribute.kl.aktuell;
const _in = systemData.attribute.in.aktuell;
const ch = systemData.attribute.ch.aktuell;
const ff = systemData.attribute.ff.aktuell;
const ge = systemData.attribute.ge.aktuell;
const ko = systemData.attribute.kk.aktuell;
const ko = systemData.attribute.ko.aktuell;
const kk = systemData.attribute.kk.aktuell;
systemData.lep.max = Math.round((ko + ko + kk) / 2) + systemData.lep.mod;
systemData.aup.max = Math.round((mu + ko + ge) / 2) + systemData.aup.mod;
systemData.asp.max = Math.round((mu + _in + ch) / 2) + systemData.asp.mod;
systemData.at = Math.round((mu + ge + kk) / 5);
systemData.pa = Math.round((_in + ge + kk) / 5);
systemData.fk = Math.round((_in + ff + kk) / 5);
systemData.ini.aktuell = Math.round((mu + mu + _in + ge) / 5) + systemData.ini.mod;
systemData.mr.aktuell = Math.round((mu + kl + ko) / 5) + systemData.mr.mod;
systemData.at = systemData.at ?? {links: {}, rechts: {}}
systemData.at.links = systemData.at.links ?? {
aktuell: 0,
mods: 0
}
systemData.at.rechts = systemData.at.rechts ?? {
aktuell: 0,
mods: 0
}
systemData.at.basis = Math.round((mu + ge + kk) / 5)
systemData.at.aktuell = systemData.at.basis + (systemData.at.mod ?? 0);
systemData.at.links.aktuell = systemData.at.basis + (systemData.at.links.mod ?? 0);
systemData.at.rechts.aktuell = systemData.at.basis + (systemData.at.rechts.mod ?? 0);
systemData.pa = systemData.pa ?? {links: {}, rechts: {}}
systemData.pa.links = systemData.pa.links ?? {
aktuell: 0,
mods: 0
}
systemData.pa.rechts = systemData.pa.rechts ?? {
aktuell: 0,
mods: 0
}
systemData.pa.basis = Math.round((_in + ge + kk) / 5);
systemData.pa.aktuell = systemData.pa.basis + (systemData.pa.mod ?? 0);
systemData.pa.links.aktuell = systemData.pa.basis + (systemData.pa.links.mod ?? 0);
systemData.pa.rechts.aktuell = systemData.pa.basis + (systemData.pa.links.mod ?? 0);
systemData.fk = systemData.fk ?? {
aktuell: 0,
mods: 0
}
systemData.fk.basis = Math.round((_in + ff + kk) / 5);
systemData.fk.aktuell = systemData.fk.basis + (systemData.fk.mod ?? 0);
systemData.ini.basis = Math.round((mu + mu + _in + ge) / 5)
systemData.ini.aktuell = systemData.ini.basis + (systemData.ini.mod ?? 0);
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
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs = {
brust: 0,
bauch: 0,
armlinks: 0,
armrechts: 0,
beinlinks: 0,
beinrechts: 0,
kopf: 0,
}; // only with DSA_4-1.optional_trefferzonen = true
} else {
systemData.rs = 0; // only with DSA_4-1.optional_trefferzonen = false
}
systemData.be = 0;
// half KO is the maximum a character can sustain wounds before collapsing
systemData.wunden.max = ko / 2;
if (game.settings.get("DSA_4-1", "optional_trefferzonen")) {
systemData.wunden.kopf = 0;
systemData.wunden.brust = 0;
systemData.wunden.bauch = 0;
systemData.wunden.armlinks = 0;
systemData.wunden.armrechts = 0;
systemData.wunden.beinlinks = 0;
systemData.wunden.beinrechts = 0;
}
systemData.ws = ko / 2;
// map current set to RS and BE
const ausruestung = systemData.heldenausruestung[systemData.setEquipped];
if (ausruestung) {
if (ausruestung.brust) {
systemData.be += systemData.parent.items.get(ausruestung.brust).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.brust = systemData.parent.items.get(ausruestung.brust).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.brust).system.armorValue ?? 0
}
}
if (ausruestung.bauch) {
systemData.be += systemData.parent.items.get(ausruestung.bauch).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.bauch = systemData.parent.items.get(ausruestung.bauch).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.bauch).system.armorValue ?? 0
}
}
if (ausruestung.ruecken) {
systemData.be += systemData.parent.items.get(ausruestung.ruecken).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
// ruecken is not a valid trefferzone
} else {
systemData.rs += systemData.parent.items.get(ausruestung.ruecken).system.armorValue ?? 0
}
}
if (ausruestung.armlinks) {
systemData.be += systemData.parent.items.get(ausruestung.armlinks).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.armlinks = systemData.parent.items.get(ausruestung.armlinks).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.armlinks).system.armorValue ?? 0
}
}
if (ausruestung.armrechts) {
systemData.be += systemData.parent.items.get(ausruestung.armrechts).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.armrechts = systemData.parent.items.get(ausruestung.armrechts).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.armrechts).system.armorValue ?? 0
}
}
if (ausruestung.beinlinks) {
systemData.be += systemData.parent.items.get(ausruestung.beinlinks).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.beinlinks = systemData.parent.items.get(ausruestung.beinlinks).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.beinlinks).system.armorValue ?? 0
}
}
if (ausruestung.beinrechts) {
systemData.be += systemData.parent.items.get(ausruestung.beinrechts).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.beinrechts = systemData.parent.items.get(ausruestung.beinrechts).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.beinrechts).system.armorValue ?? 0
}
}
if (ausruestung.kopf) {
systemData.be += systemData.parent.items.get(ausruestung.kopf).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.kopf = systemData.parent.items.get(ausruestung.kopf).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.kopf).system.armorValue ?? 0
}
}
}
systemData.kap.max = 0;
// evaluate deities for KaP
const deities = systemData.parent.items.filter(p => p.type === "Blessing")
deities?.forEach((deity) => {
if (LiturgyData.alverans.includes(deity.system.gottheit)) {
systemData.kap.max = 24;
} else if (systemData.kap.max === 0) {
systemData.kap.max += 12;
}
}, 0)
}
@ -84,4 +244,62 @@ export class Character extends Actor {
return data;
}
/**
*
* @param amount
* @param zone either null or one of
* @returns {Promise<void>}
*/
async takeDamage(amount = null, zone = null) {
this.prepareDerivedData()
const playWithZoneArmor = game.settings.get("DSA_4-1", "optional_ruestungzonen")
const playWithWoundZones = game.settings.get("DSA_4-1", "optional_trefferzonen")
const previousLeP = this.system.lep.aktuell;
if (amount == null) {
// TODO show Dialog for entering damage amount (TP)
}
let armorReduction = 0
let setEquipped = this.system.setEquipped
let woundThreshold = this.system.ws
if (playWithZoneArmor) {
const armorId = this.system.heldenausruestung[setEquipped][Zonenruestung[zone]]
const zoneArmor = await this.items.find(p => p._id === armorId)
if (!zoneArmor) {
return console.error(`zone "${zone}" is not a valid value`)
}
armorReduction = zoneArmor.system.armorValue ?? 0
} else {
armorReduction = this.system.rs
}
let damage = amount - armorReduction
let wounds = damage / woundThreshold
let wound = null
if (playWithWoundZones) {
wound = await game.packs.get("DSA_4-1.Wounds").index.find(p => p.name === Zonenwunde[zone])
if (!wound) {
return console.error(`Wunden Dokument zu "${zone}" konnten nicht gefunden werden`)
}
} else {
wound = await game.packs.get("DSA_4-1.Wounds").index.find(p => p.name === Wunde)
if (!wound) {
return console.error(`Wunden Dokument zu "${Wunde}" konnten nicht gefunden werden`)
}
}
// TODO this doesnt work yet, wound documents wont get expanded
for (let i = 0; i < wounds; i++) {
await this.createEmbeddedDocuments('Item', [wound])
}
await this.update({system: {lep: {aktuell: previousLeP - damage}}})
}
}

View File

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

View File

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

View File

@ -0,0 +1,44 @@
export class ActiveEffectSheet extends ItemSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'activeeffect'],
width: 520,
height: 480
});
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/item/item-activeeffect-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();
const effects = context.document.getEmbeddedCollection("ActiveEffect").contents;
if (effects.length > 0) {
context.effectId = effects[0]._id;
}
return context;
}
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
html.on('click', '.editEffects', (evt) => {
const {id} = evt.currentTarget.dataset;
const effect = this.object.effects.get(id);
effect.sheet.render(true);
})
}
}

View File

@ -202,11 +202,11 @@ export class ActionManager {
]
#hatSonderfertigkeitBeginnendMit(name) {
return this.actor.system.sonderfertigkeiten.find(p => p.name.startsWith(name)) != null
return this.actor.system.sonderfertigkeiten?.find(p => p.name.startsWith(name)) != null
}
#hatSonderfertigkeit(name) {
return this.actor.system.sonderfertigkeiten.find(p => p.name === name) != null
return this.actor.system.sonderfertigkeiten?.find(p => p.name === name) != null
}
evaluate() {

View File

@ -1,5 +1,7 @@
import {PlayerCharacterDataModel} from "../data/character.mjs";
import {ActionManager} from "./actions/action-manager.mjs";
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
import {ModifyLiturgy} from "../dialog/modify-liturgy.mjs";
export class CharacterSheet extends ActorSheet {
/**@override */
@ -23,25 +25,32 @@ export class CharacterSheet extends ActorSheet {
return `systems/DSA_4-1/templates/actor/actor-character-sheet.hbs`;
}
/** @override */
async getData() {
const context = super.getData();
static onDroppedData(actor, characterSheet, data) {
const uuid = foundry.utils.parseUuid(data.uuid);
const collection = uuid.collection.index ?? uuid.collection;
const document = CharacterSheet.getElementByName(collection, uuid.id);
const {
name,
type
} = document
console.log(name, type)
switch (type) {
case "Skill":
return characterSheet.#handleDroppedSkill(actor, document); // on false cancel this whole operation
case "Advantage":
return characterSheet.#handleDroppedAdvantage(actor, document);
case "ActiveEffect":
return characterSheet.#handleDroppedActiveEffect(actor, document);
case "Equipment":
return characterSheet.#handleDroppedEquipment(actor, document);
case "Liturgy":
return characterSheet.#handleDroppedLiturgy(actor, document);
case "SpecialAbility":
return characterSheet.#handleDroppedSpecialAbility(actor, document);
default:
return false;
}
// Use a safe clone of the actor data for further operations.
const actorData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = actorData.system;
context.flags = actorData.flags;
this.#addSkillsToContext(context)
this.#addAdvantagesToContext(context)
this.#addAttributesToContext(context)
this.#addEquipmentsToContext(context)
await this.#addCombatStatistics(context)
this.#addActionsToContext(context)
return context;
}
static getElementByName(collection, id) {
@ -85,10 +94,22 @@ export class CharacterSheet extends ActorSheet {
eigenschaft2: werte[1].name,
eigenschaft3: werte[2].name,
probe: `(${eigenschaften.join("/")})`,
id: item._id,
at: item.system.at,
pa: item.system.pa,
id: item._id,
komplexität: item.system.komplexität
};
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);
}
@ -96,56 +117,194 @@ export class CharacterSheet extends ActorSheet {
);
}
#addAttributesToContext(context) {
#cleanUpMerkmal(merkmale) {
return merkmale.split(",").map((merkmal) => merkmal.trim())
}
/** @override */
async getData() {
const context = super.getData();
// Use a safe clone of the actor data for further operations.
const actorData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = actorData.system;
context.flags = actorData.flags;
context.derived = context.document.system;
context.originalName = actorData.name;
context.name = context.derived.name ?? actorData.name;
context.effects = actorData.effects ?? [];
context.maxWounds = actorData.system.wunden.max ?? 3;
context.wounds = actorData.system.wunden.aktuell ?? 0;
context.woundsFilled = [];
for (let i = 1; i <= context.maxWounds; i++) {
context.woundsFilled[i] = i <= context.wounds
}
context.zonenruestung = game.settings.get("DSA_4-1", "optional_ruestungzonen")
context.trefferzonen = game.settings.get("DSA_4-1", "optional_trefferzonen")
context.ausdauer = game.settings.get("DSA_4-1", "optional_ausdauer")
this.#addEffectsToContext(context)
this.#addSkillsToContext(context)
this.#addAdvantagesToContext(context)
this.#addSpecialAbilitiesToContext(context)
await this.#addAttributesToContext(context)
this.#addEquipmentsToContext(context)
await this.#addCombatStatistics(context)
this.#addActionsToContext(context)
this.#addSpellsToContext(context)
this.#addLiturgiesToContext(context)
return context;
}
#addEffectsToContext(context) {
const actorData = context.data;
context.isGM = game.user.isGM
context.effects = [];
Object.values(actorData.items).forEach((item, index) => {
if (item.type === "ActiveEffect") {
const effect = item.effects[0];
const conditions = []
if (effect) {
effect.changes.forEach(change => {
if (change.key.indexOf("wunden") === -1) {
const key = change.key
.replace(/system\./g, "")
.replace(/\.mod/g, "")
.replace(/attribute./g, "")
.replace(/.links/g, "(Links)")
.replace(/.rechts/g, "(Rechts)")
const value = Number(change.value) > 0 ? "+" + change.value : change.value;
conditions.push(
`${key}${value}`
)
}
})
}
context.effects.push({
name: item.name,
conditions: conditions.join(" "),
id: item._id,
actor: actorData._id
});
}
})
}
#addSpellsToContext(context) {
const actorData = context.data;
context.spells = [];
Object.values(actorData.items).forEach((item, index) => {
if (item.type === "Spell") {
const eigenschaften = item.system.probe;
const werte = [
{name: eigenschaften[0], value: this.prepareEigenschaftRoll(actorData, eigenschaften[0])},
{name: eigenschaften[1], value: this.prepareEigenschaftRoll(actorData, eigenschaften[1])},
{name: eigenschaften[2], value: this.prepareEigenschaftRoll(actorData, eigenschaften[2])}
]
context.spells.push({
id: item._id,
name: item.name,
zfw: item.system.zfw,
hauszauber: item.system.hauszauber,
merkmal: this.#cleanUpMerkmal(item.system.merkmal),
rollEigenschaft1: werte[0].value,
rollEigenschaft2: werte[1].value,
rollEigenschaft3: werte[2].value,
eigenschaft1: werte[0].name,
eigenschaft2: werte[1].name,
eigenschaft3: werte[2].name,
})
}
})
context.hasSpells = context.spells.length > 0;
}
async #getModsOfAttribute(keyPath) {
let returnValue = [];
Array.from(this.object.appliedEffects).forEach(
(e) =>
e.changes.filter(c => c.key === keyPath).forEach(change => {
returnValue.push({
name: e.name,
value: change.value > 0 ? "+" + change.value : "" + change.value,
icon: e.icon,
})
}))
return returnValue;
}
async #addAttributesToContext(context) {
context.mods = {
"mu": await this.#getModsOfAttribute('system.attribute.mu.mod'),
"kl": await this.#getModsOfAttribute('system.attribute.kl.mod'),
"in": await this.#getModsOfAttribute('system.attribute.in.mod'),
"ch": await this.#getModsOfAttribute('system.attribute.ch.mod'),
"ff": await this.#getModsOfAttribute('system.attribute.ff.mod'),
"ge": await this.#getModsOfAttribute('system.attribute.ge.mod'),
"ko": await this.#getModsOfAttribute('system.attribute.ko.mod'),
"kk": await this.#getModsOfAttribute('system.attribute.kk.mod'),
"at": await this.#getModsOfAttribute('system.at.mod'),
"pa": await this.#getModsOfAttribute('system.pa.mod'),
"fk": await this.#getModsOfAttribute('system.fk.mod'),
}
context.attributes = [
{
eigenschaft: "mu",
name: "MU",
tooltip: "Mut",
wert: actorData.system.attribute.mu.aktuell ?? 0,
wert: context.derived.attribute.mu.aktuell ?? 0,
},
{
eigenschaft: "kl",
name: "KL",
tooltip: "Klugheit",
wert: actorData.system.attribute.kl.aktuell ?? 0,
wert: context.derived.attribute.kl.aktuell ?? 0,
},
{
eigenschaft: "in",
name: "IN",
tooltip: "Intuition",
wert: actorData.system.attribute.in.aktuell ?? 0,
wert: context.derived.attribute.in.aktuell ?? 0,
},
{
eigenschaft: "ch",
name: "CH",
tooltip: "Charisma",
wert: actorData.system.attribute.ch.aktuell ?? 0,
wert: context.derived.attribute.ch.aktuell ?? 0,
},
{
eigenschaft: "ff",
name: "FF",
tooltip: "Fingerfertigkeit",
wert: actorData.system.attribute.ff.aktuell ?? 0,
wert: context.derived.attribute.ff.aktuell ?? 0,
},
{
eigenschaft: "ge",
name: "GE",
tooltip: "Geschicklichkeit",
wert: actorData.system.attribute.ge.aktuell ?? 0,
wert: context.derived.attribute.ge.aktuell ?? 0,
},
{
eigenschaft: "ko",
name: "KO",
tooltip: "Konstitution",
wert: actorData.system.attribute.ko.aktuell ?? 0,
wert: context.derived.attribute.ko.aktuell ?? 0,
},
{
eigenschaft: "kk",
name: "KK",
tooltip: "Körperkraft",
wert: actorData.system.attribute.kk.aktuell ?? 0,
wert: context.derived.attribute.kk.aktuell ?? 0,
},
];
@ -169,12 +328,22 @@ export class CharacterSheet extends ActorSheet {
);
}
#findEquipmentOnSlot(slot, setNumber) {
return this.object.items.get(this.object.system.heldenausruestung[setNumber][slot])
#addSpecialAbilitiesToContext(context) {
context.specialAbilities = [];
const actorData = context.data;
Object.values(actorData.items).forEach((item) => {
if (item.type === "SpecialAbility") {
context.specialAbilities.push({
id: item._id,
name: item.name,
});
}
}
);
}
#findTalentsOfEquipment(equipment) {
#findEquipmentOnSlot(slot, setNumber) {
return this.object.items.get(this.object.system.heldenausruestung[setNumber]?.[slot])
}
#addActionsToContext(context) {
@ -182,14 +351,16 @@ export class CharacterSheet extends ActorSheet {
context.actions = am.evaluate()
}
#isWorn(itemId, setId) {
#isWorn(itemId) {
const slots = PlayerCharacterDataModel.getSlots()
const set = this.object.system.heldenausruestung[setId]
for (const slot of slots) {
const equipmentSlotId = set[slot]
if (equipmentSlotId === itemId) {
return slot
const set = this.object.system.heldenausruestung[this.object.system.setEquipped]
if (set) {
for (const slot of slots) {
const equipmentSlotId = set[slot]
if (equipmentSlotId === itemId) {
return slot
}
}
}
@ -205,25 +376,28 @@ export class CharacterSheet extends ActorSheet {
context.aupper = Math.min((context.actor.system.aup.aktuell / context.actor.system.aup.max) * 100, 100);
context.lepper = Math.min((context.actor.system.lep.aktuell / context.actor.system.lep.max) * 100, 100);
context.keper = Math.min((context.actor.system.kap.aktuell / context.actor.system.kap.max) * 100, 100);
context.aspper = Math.min((context.actor.system.asp.aktuell / context.actor.system.asp.max) * 100, 100);
context.lepcurrent = context.actor.system.lep.aktuell ?? 0
context.aupcurrent = context.actor.system.aup.aktuell ?? 0
const fernkampf = this.#findEquipmentOnSlot("fernkampf", 0)
const links = this.#findEquipmentOnSlot("links", 0)
const rechts = this.#findEquipmentOnSlot("rechts", 0)
const fernkampf = this.#findEquipmentOnSlot("fernkampf", context.actor.system.setEquipped)
const links = this.#findEquipmentOnSlot("links", context.actor.system.setEquipped)
const rechts = this.#findEquipmentOnSlot("rechts", context.actor.system.setEquipped)
context.attacks = [];
if (fernkampf) {
const fkitems = fernkampf.system.rangedSkills.map(async (skillInQuestion) => await this.object.items.getName(skillInQuestion))
fkitems.forEach(async skill => {
const obj = await skill
console.log(this.object.system.fk, obj.system.at);
context.attacks.push({
name: obj.name,
using: fernkampf.name,
atroll: `1d20 + ${this.object.system.fk + obj.system.at}`,
at: `1w20 + ${this.object.system.fk + obj.system.at}`,
atroll: `1d20cs<${this.object.system.fk.aktuell + obj.system.at}`,
at: `${this.object.system.fk.aktuell + obj.system.at}`,
tproll: `${fernkampf.system.rangedAttackDamage}`, // TODO consider adding TP/KK mod and Range mod
tp: `${fernkampf.system.rangedAttackDamage}`,
iniroll: `(${context.inidice})d6 + ${context.inivalue + fernkampf.system.iniModifier ?? 0}`,
ini: `${context.inidice}w6 + ${context.inivalue + fernkampf.system.iniModifier ?? 0}`,
})
@ -236,10 +410,12 @@ export class CharacterSheet extends ActorSheet {
context.attacks.push({
name: obj.name,
using: links.name,
atroll: `1d20 + ${this.object.system.at + obj.system.at + links.system.attackModifier}`,
at: `1w20 + ${this.object.system.at + obj.system.at + links.system.attackModifier}`,
paroll: `1d20 + ${this.object.system.pa + obj.system.pa + links.system.parryModifier}`,
pa: `1w20 + ${this.object.system.pa + obj.system.pa + links.system.parryModifier}`,
atroll: `1d20cs<${this.object.system.at.links.aktuell + obj.system.at + links.system.attackModifier}`, // TODO consider adding W/M
at: `${this.object.system.at.links.aktuell + obj.system.at + links.system.attackModifier}`,
paroll: `1d20cs<${this.object.system.pa.links.aktuell + obj.system.pa + links.system.parryModifier}`, // TODO consider adding W/M
pa: `${this.object.system.pa.links.aktuell + obj.system.pa + links.system.parryModifier}`,
tproll: `${links.system.meleeAttackDamage}`, // TODO consider adding TP/KK mod
tp: `${links.system.meleeAttackDamage}`,
iniroll: `(${context.inidice})d6 + ${context.inivalue + links.system.iniModifier ?? 0}`,
ini: `${context.inidice}w6 + ${context.inivalue + links.system.iniModifier ?? 0}`,
})
@ -253,10 +429,12 @@ export class CharacterSheet extends ActorSheet {
context.attacks.push({
name: obj.name,
using: rechts.name,
atroll: `1d20 + ${this.object.system.at + obj.system.at + rechts.system.attackModifier}`,
at: `1w20 + ${this.object.system.at + obj.system.at + rechts.system.attackModifier}`,
paroll: `1d20 + ${this.object.system.pa + obj.system.pa + rechts.system.parryModifier}`,
pa: `1w20 + ${this.object.system.pa + obj.system.pa + rechts.system.parryModifier}`,
atroll: `1d20cs<${this.object.system.at.rechts.aktuell + obj.system.at + rechts.system.attackModifier}`, // TODO consider adding W/M
at: `${this.object.system.at.rechts.aktuell + obj.system.at + rechts.system.attackModifier}`,
paroll: `1d20cs<${this.object.system.pa.rechts.aktuell + obj.system.pa + rechts.system.parryModifier}`, // TODO consider adding W/M
pa: `${this.object.system.pa.rechts.aktuell + obj.system.pa + rechts.system.parryModifier}`,
tproll: `${rechts.system.meleeAttackDamage}`, // TODO consider adding TP/KK mod
tp: `${rechts.system.meleeAttackDamage}`,
iniroll: `(${context.inidice})d6 + ${context.inivalue + rechts.system.iniModifier ?? 0}`,
ini: `${context.inidice}w6 + ${context.inivalue + rechts.system.iniModifier ?? 0}`,
})
@ -267,7 +445,7 @@ export class CharacterSheet extends ActorSheet {
}
prepareEigenschaftRoll(actorData, name) {
if (name) {
if (name && name !== "*") {
return actorData.system.attribute[name.toLowerCase()].aktuell
} else {
return 0
@ -280,16 +458,24 @@ export class CharacterSheet extends ActorSheet {
context.carryingweight = 0;
Object.values(actorData.items).forEach((item, index) => {
if (item.type === "Equipment") {
// worn items are halved weight
let effectiveWeight = item.system.weight ?? 0
if (this.#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 ?? 0,
worn: this.#isWorn(item._id, 0)
weight: item.system.weight,
worn: this.#isWorn(item._id)
})
context.carryingweight += item.system.quantity * item.system.weight;
context.carryingweight += item.system.quantity * effectiveWeight;
}
})
context.maxcarryingcapacity = actorData.system.attribute.kk.aktuell
@ -409,8 +595,6 @@ export class CharacterSheet extends ActorSheet {
rollMode: game.settings.get('core', 'rollMode'),
})
}
}
}
@ -482,22 +666,23 @@ export class CharacterSheet extends ActorSheet {
}
}
#getEquipmentset(setId) {
const equipmentSet = this.object.system.heldenausruestung[setId]
#mapAllSets() {
const updateObject = {}
// TODO: there's got to be a better angle!
updateObject[`system.heldenausruestung.${setId}.links`] = equipmentSet.links;
updateObject[`system.heldenausruestung.${setId}.rechts`] = equipmentSet.rechts;
updateObject[`system.heldenausruestung.${setId}.brust`] = equipmentSet.brust;
updateObject[`system.heldenausruestung.${setId}.bauch`] = equipmentSet.bauch;
updateObject[`system.heldenausruestung.${setId}.ruecken`] = equipmentSet.ruecken;
updateObject[`system.heldenausruestung.${setId}.kopf`] = equipmentSet.kopf;
updateObject[`system.heldenausruestung.${setId}.fernkampf`] = equipmentSet.fernkampf;
updateObject[`system.heldenausruestung.${setId}.munition`] = equipmentSet.munition;
updateObject[`system.heldenausruestung.${setId}.armlinks`] = equipmentSet.armlinks;
updateObject[`system.heldenausruestung.${setId}.armrechts`] = equipmentSet.armrechts;
updateObject[`system.heldenausruestung.${setId}.beinlinks`] = equipmentSet.beinlinks;
updateObject[`system.heldenausruestung.${setId}.beinrechts`] = equipmentSet.beinrechts;
Array.from(this.object.system.heldenausruestung).forEach((equipmentSet, index) => {
updateObject[`system.heldenausruestung.${index}.links`] = equipmentSet.links;
updateObject[`system.heldenausruestung.${index}.rechts`] = equipmentSet.rechts;
updateObject[`system.heldenausruestung.${index}.brust`] = equipmentSet.brust;
updateObject[`system.heldenausruestung.${index}.bauch`] = equipmentSet.bauch;
updateObject[`system.heldenausruestung.${index}.ruecken`] = equipmentSet.ruecken;
updateObject[`system.heldenausruestung.${index}.kopf`] = equipmentSet.kopf;
updateObject[`system.heldenausruestung.${index}.fernkampf`] = equipmentSet.fernkampf;
updateObject[`system.heldenausruestung.${index}.munition`] = equipmentSet.munition;
updateObject[`system.heldenausruestung.${index}.armlinks`] = equipmentSet.armlinks;
updateObject[`system.heldenausruestung.${index}.armrechts`] = equipmentSet.armrechts;
updateObject[`system.heldenausruestung.${index}.beinlinks`] = equipmentSet.beinlinks;
updateObject[`system.heldenausruestung.${index}.beinrechts`] = equipmentSet.beinrechts;
})
return updateObject;
}
@ -533,13 +718,133 @@ export class CharacterSheet extends ActorSheet {
}
#addLiturgiesToContext(context) {
const actorData = context.data;
context.liturgies = [];
context.blessings = [];
Object.values(actorData.items).forEach((item, index) => {
if (item.type === "Blessing") {
context.blessings.push({
deity: item.system.gottheit,
value: item.system.wert
})
}
})
Object.values(actorData.items).forEach((item, index) => {
if (item.type === "Liturgy") {
context.blessings.forEach(({deity, value}) => {
let insertObject = context.liturgies.find(p => p.deity === deity);
if (!insertObject) {
insertObject = {
deity: deity,
lkp: value,
O: [],
I: [],
II: [],
III: [],
IV: [],
V: [],
VI: [],
VII: [],
VIII: [],
"NA": [],
countO: 1,
countI: 1,
countII: 1,
countIII: 1,
countIV: 1,
countV: 1,
countVI: 1,
countVII: 1,
countVIII: 1,
countNA: 0,
total: 3,
}
context.liturgies.push(insertObject);
}
// sort by rank
const rankData = LiturgyData.getRankOfLiturgy(item.system, deity)
if (rankData) {
let {index, name, lkp, mod, costKaP} = rankData;
insertObject["count" + name] = insertObject["count" + name] + 1;
insertObject[name].push({
id: item._id,
name: item.name,
lkpReq: lkp,
lkpMod: mod,
costKaP,
rank: index, // get effective liturgy rank based on deity
liturgiekenntnis: deity,
})
insertObject.total = insertObject.total + 2;
}
})
}
})
// clean up counter
Object.values(context.liturgies).forEach((litObject) => {
if (litObject.I.length === 0) litObject.countI = false;
if (litObject.II.length === 0) litObject.countII = false;
if (litObject.III.length === 0) litObject.countIII = false;
if (litObject.IV.length === 0) litObject.countIV = false;
if (litObject.V.length === 0) litObject.countV = false;
if (litObject.VI.length === 0) litObject.countVI = false;
if (litObject.VII.length === 0) litObject.countVII = false;
if (litObject.VIII.length === 0) litObject.countVIII = false;
if (litObject.NA.length === 0) litObject.countNA = false;
})
context.hasLiturgies = context.blessings.length > 0;
}
#handleDroppedSkill(actor, skill) {
const array = Array.from(actor.items);
for (let i = 0; i < array.length; i++) {
if (array[i].name === skill.name) {
return false;
}
}
}
async #handleDroppedActiveEffect(actor, activeEffect) {
const array = Array.from(actor.items);
for (let i = 0; i < array.length; i++) {
if (array[i].name === activeEffect.name) {
// replace active effect if its unique
return true;
}
}
}
#handleDroppedAdvantage(actor, advantage) {
const array = Array.from(actor.items);
for (let i = 0; i < array.length; i++) {
if (array[i].name === advantage.name) { // TODO: adjust for uniqueness
return false;
}
}
}
activateListeners(html) {
super.activateListeners(html);
const tabs = new Tabs({
navSelector: ".paperdoll-tabs.tabs",
contentSelector: ".sheet-body.paperdoll-sets",
initial: "set1"
initial: "set" + (this.object.system.setEquipped + 1)
});
tabs.bind(html[0]);
@ -547,6 +852,19 @@ export class CharacterSheet extends ActorSheet {
this._onAttributeRoll(evt);
});
html.on('click', '[data-operation="switchSet"]', (evt) => {
const {id} = evt.currentTarget.dataset;
console.log(id);
this.object.update({"system.setEquipped": id})
})
html.on('click', '[data-operation="removeEffect"]', (evt) => {
const {actorId, effectId} = evt.currentTarget.dataset;
if (game.user.isGM) {
this.object.items.get(effectId).delete();
}
})
html.on('click', '.talent.rollable', (evt) => {
this._onTalentRoll(evt);
});
@ -555,21 +873,32 @@ export class CharacterSheet extends ActorSheet {
this._onRoll(evt);
});
// TODO: merge into click.clickable handler
html.on('click', '.talent .name', (evt) => {
this.openEmbeddedDocument(evt.target.dataset.id);
evt.stopPropagation();
})
// TODO: merge into click.clickable handler
html.on('click', '.advantage .name', (evt) => {
this.openEmbeddedDocument(evt.target.dataset.id);
evt.stopPropagation();
})
// TODO: merge into click.clickable handler
html.on('click', '.equipment', (evt) => {
this.openEmbeddedDocument(evt.target.parentElement.dataset.id);
evt.stopPropagation();
})
html.on('click', '.clickable', async (evt) => {
const {id, operation} = evt.currentTarget.dataset;
if (operation === "openActorSheet") {
this.openEmbeddedDocument(id);
evt.stopPropagation();
}
})
html.on('dragstart', '.equipment', (evt) => {
evt.originalEvent.dataTransfer.setData("application/json", JSON.stringify({
documentId: evt.currentTarget.dataset.id
@ -584,7 +913,8 @@ export class CharacterSheet extends ActorSheet {
if (actor === this.object._id && documentId) { // managing equipped items
//const slot = this.#isWorn(documentId, setId)
const updateObject = this.#getEquipmentset(setId)
//const updateObject = await this.#getEquipmentset(Number(setId))
const updateObject = this.#mapAllSets()
updateObject[`system.heldenausruestung.${setId}.${target}`] = documentId;
console.log(updateObject);
@ -619,7 +949,15 @@ export class CharacterSheet extends ActorSheet {
}
]);
let handler = ev => this._onDragStart(ev);
let handler = evt => {
const talentId = evt.target.dataset.id
evt.dataTransfer.setData("application/json", JSON.stringify({
talentId
}));
this._onDragStart(evt)
}
// Find all items on the character sheet.
html.find('.talent.rollable').each((i, li) => {
// Add draggable attribute and dragstart listener.
@ -645,7 +983,7 @@ export class CharacterSheet extends ActorSheet {
callback: (event) => {
const {setId, target, actor} = event[0].dataset
const updateObject = this.#getEquipmentset(setId)
const updateObject = this.#mapAllSets()
updateObject[`system.heldenausruestung.${setId}.${target}`] = null;
this.object.update(updateObject);
@ -654,57 +992,95 @@ export class CharacterSheet extends ActorSheet {
}
]);
}
html.on('click', '[data-operation="addWounds"]', async (evt) => {
const {value} = evt.currentTarget.dataset
this.object.update({"system.wunden.aktuell": value})
})
#handleDroppedSkill(actor, skill) {
const array = Array.from(actor.items);
for (let i = 0; i < array.length; i++) {
if (array[i].name === skill.name) {
return false;
}
}
}
html.on('click', '[data-operation="reduceWounds"]', async (evt) => {
const {value} = evt.currentTarget.dataset
this.object.update({"system.wunden.aktuell": value})
})
#handleDroppedAdvantage(actor, advantage) {
const array = Array.from(actor.items);
for (let i = 0; i < array.length; i++) {
if (array[i].name === advantage.name) { // TODO: adjust for uniqueness
return false;
html.on('click', '.liturgy.rollable', async (evt) => {
evt.stopPropagation();
const {id, rank, lkp, deity} = evt.currentTarget.dataset;
const document = await this.object.items.get(id)
const data = {};
data.rank = rank;
data.lkp = lkp;
data.deity = deity;
data.variations = [];
const ranks = LiturgyData.ranks
ranks.forEach(rank => {
if (document.system.auswirkung[rank]) {
data.variations.push({
rank,
effect: document.system.auswirkung[rank]
})
}
})
data.mods = [];
const htmlContent = await renderTemplate('systems/DSA_4-1/templates/dialog/modify-liturgy.hbs', data);
const dialogData = {
title: document.name,
content: htmlContent,
data: {},
buttons: {
submit: {
label: "Wirken",
icon: '<i class="fas fa-die"></i>',
callback: (html) => {
},
},
},
}
}
dialogData.render = new ModifyLiturgy(data).handleRender
const dialog = new Dialog(dialogData, {
classes: ['dsa41', 'dialog', 'liturgy'],
height: 480
})
dialog.render(true);
return false;
})
}
#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 onDroppedData(actor, characterSheet, data) {
const uuid = foundry.utils.parseUuid(data.uuid);
const collection = uuid.collection.index ?? uuid.collection;
const document = CharacterSheet.getElementByName(collection, uuid.id);
const {
name,
type
} = document
console.log(name, type)
switch (type) {
case "Skill":
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:
#handleDroppedLiturgy(actor, liturgy) {
const array = Array.from(actor.items);
for (let i = 0; i < array.length; i++) {
if (array[i].name === liturgy.name) { // TODO: allow multiple miracles with the same name
return false;
}
}
}
#handleDroppedSpecialAbility(actor, specialAbility) {
const array = Array.from(actor.items);
for (let i = 0; i < array.length; i++) {
if (array[i].name === specialAbility.name) { // TODO: allow multiple miracles with the same name
return false;
}
}
}
}

View File

@ -72,10 +72,10 @@ export class CreatureSheet extends foundry.appv1.sheets.ActorSheet {
})
html.on('click', '.editor .add-attack', async (evt) => {
const name = evt.target.parentElement.querySelector('#attack_name').value
const at = evt.target.parentElement.querySelector('#attack_at').value
const pa = evt.target.parentElement.querySelector('#attack_pa').value
const tp = evt.target.parentElement.querySelector('#attack_tp').value
const name = html[0].querySelector('#attack_name').value
const at = html[0].querySelector('#attack_at').value
const pa = html[0].querySelector('#attack_pa').value
const tp = html[0].querySelector('#attack_tp').value
const newAttack = {
name,

View File

@ -15,6 +15,59 @@ export class GroupSheet extends ActorSheet {
});
}
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))
// 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
let settings = {...group.system.settings}
character.items.filter((i) => i.type === "Advantage").forEach((advantage) => {
if (!settings[sheet.#stringToKeyFieldName(advantage.name)]) {
settings[sheet.#stringToKeyFieldName(advantage.name)] = false
}
})
character.items.filter((i) => i.type === "Skill").forEach((skill) => {
if (!settings[sheet.#stringToKeyFieldName(skill.name)]) {
settings[sheet.#stringToKeyFieldName(skill.name)] = false
}
})
await group.update({
system: {
characters: [
...group.system.characters,
character._id
],
settings: settings
}
})
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;
}
}
#stringToKeyFieldName(s) {
return s
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/actor/group-sheet.hbs`;
}
/** @override */
async getData() {
const context = super.getData();
@ -25,12 +78,61 @@ export class GroupSheet extends ActorSheet {
// Add the actor's data to context.data for easier access, as well as flags.
context.system = groupData.system;
context.flags = groupData.flags;
context.characters = []
context.isGM = game.user.isGM;
context.characters = [];
context.fields = [];
const hiddenFields = Object.entries(groupData.system.settings)
.sort(([key, value], [otherKey, otherValue]) => key.localeCompare(otherKey))
.filter(([key, value]) => value === true)
.map(([key, value]) => key)
context.fields = {}
context.fields["head"] = {}
for (const field of hiddenFields) {
context.fields[field] = {}
for (const characterId of groupData.system.characters) {
const character = await game.actors.get(characterId)
context.fields[field][character.name] = "-"
}
}
for (const characterId of groupData.system.characters) {
const character = await game.actors.get(characterId)
context.characters.push(
character.items.filter((i) => i.type === "Advantage").filter((i) => hiddenFields.includes(this.#stringToKeyFieldName(i.name))).map((advantage) => {
const n = this.#stringToKeyFieldName(advantage.name)
if (!context.fields[n]) {
context.fields[n] = {}
}
context.fields[n][character.name] = advantage.system.value ?? "Ja" // TODO: Allow GM roll
})
character.items.filter((i) => i.type === "Skill").filter((i) => hiddenFields.includes(this.#stringToKeyFieldName(i.name))).map((skill) => {
const n = this.#stringToKeyFieldName(skill.name)
if (!context.fields[n]) {
context.fields[n] = {}
}
const eigenschaften = Object.values(skill.system.probe);
context.fields[n][character.name] = {
taw: skill.system.taw,
eigenschaft1: eigenschaften[0],
eigenschaft2: eigenschaften[1],
eigenschaft3: eigenschaften[2],
rollEigenschaft1: character.system.attribute[eigenschaften[0].toLowerCase()].aktuell,
rollEigenschaft2: character.system.attribute[eigenschaften[1].toLowerCase()].aktuell,
rollEigenschaft3: character.system.attribute[eigenschaften[2].toLowerCase()].aktuell,
name: skill.name,
actor: character._id,
}
?? 0
})
context.fields.head[character.name] =
{
img: character.img,
name: character.name,
@ -45,28 +147,12 @@ export class GroupSheet extends ActorSheet {
{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
}
}),
isLimited: character.isOwner || !character.limited,
isVisible: character.isOwner || character.visible,
isOwner: character.isOwner
}
)
}
context.equipments = [];
const actorData = context.data;
Object.values(actorData.items).forEach((item, index) => {
@ -81,48 +167,80 @@ export class GroupSheet extends ActorSheet {
}
})
context.settings = Object.fromEntries(Object.entries(groupData.system.settings)
.sort(([key, value], [otherKey, otherValue]) => key.localeCompare(otherKey)))
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))
// 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
]
}
})
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)
}
_evaluateRoll(rolledDice, {
taw,
lowerThreshold = 1,
upperThreshold = 20,
countToMeisterlich = 3,
countToPatzer = 3,
werte = []
}) {
let tap = taw;
let meisterlichCounter = 0;
let patzerCounter = 0;
let failCounter = 0;
rolledDice.forEach((rolledDie, index) => {
if (tap < 0 && rolledDie.result > werte[index]) {
tap -= rolledDie.result - werte[index];
if (tap < 0) { // konnte nicht vollständig ausgeglichen werden
failCounter++;
}
} else if (rolledDie.result > werte[index]) { // taw ist bereits aufgebraucht und wert kann nicht ausgeglichen werden
tap -= rolledDie.result - werte[index];
failCounter++;
}
if (rolledDie.result <= lowerThreshold) meisterlichCounter++;
if (rolledDie.result > upperThreshold) patzerCounter++;
})
return {
tap,
meisterlich: meisterlichCounter === countToMeisterlich,
patzer: patzerCounter === countToPatzer,
}
}
async _onTalentRoll(event) {
event.preventDefault();
const dataset = event.currentTarget.dataset;
const actor = await game.actors.get(dataset.actorId);
console.log(dataset, actor)
if (dataset.rolleigenschaft1) {
let roll1 = new Roll("3d20", actor.getRollData());
let evaluated1 = (await roll1.evaluate())
const dsaDieRollEvaluated = this._evaluateRoll(evaluated1.terms[0].results, {
taw: dataset.taw,
werte: [dataset.rolleigenschaft1, dataset.rolleigenschaft2, dataset.rolleigenschaft3],
})
if (dsaDieRollEvaluated.tap >= 0) { // erfolg
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: actor}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Meisterlich geschafft' : 'Geschafft'} mit ${dsaDieRollEvaluated.tap} Punkten übrig`,
}, {rollMode: "gmroll"})
} else { // misserfolg
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: actor}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Gepatzt' : ''} mit ${Math.abs(dsaDieRollEvaluated.tap)} Punkten daneben`,
}, {rollMode: "gmroll"})
}
}
}
activateListeners(html) {
super.activateListeners(html);
@ -153,6 +271,11 @@ export class GroupSheet extends ActorSheet {
evt.stopPropagation();
})
html.on('click', ".rollable", (evt) => {
this._onTalentRoll(evt)
evt.stopPropagation()
})
new ContextMenu(html, '.equipment', [
{
name: "Aus dem Gruppeninventar entfernen",

View File

@ -0,0 +1,50 @@
export class LiturgySheet extends ItemSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'item', 'liturgy'],
width: 520,
height: 480,
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'description',
},
],
});
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/item/item-liturgy-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 liturgyData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = liturgyData.system;
context.flags = liturgyData.flags;
context.json = JSON.stringify(liturgyData);
return context;
}
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (this.isEditable) {
}
}
}

View File

@ -0,0 +1,51 @@
export class SpecialAbilitySheet extends ItemSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'item', 'specialability'],
width: 520,
height: 480,
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'description',
},
],
});
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/item/item-special-ability-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 advantageData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = advantageData.system;
context.flags = advantageData.flags;
context.json = JSON.stringify(advantageData.system, null, 4);
return context;
}
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) {
}
}
}

View File

@ -34,7 +34,13 @@ export class VornachteilSheet extends ItemSheet {
// Add the actor's data to context.data for easier access, as well as flags.
context.system = advantageData.system;
context.flags = advantageData.flags;
context.json = JSON.stringify(advantageData.system, null, 4);
context.choices = {}
context.system.auswahl.forEach(a => {
context.choices[a] = a
})
context.hasChoices = context.system.auswahl.length > 0;
context.hasModality = context.system.value == null
return context;
}
@ -43,7 +49,9 @@ export class VornachteilSheet extends ItemSheet {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
if (!this.isEditable) {
}
}
}
}

View File

@ -1,3 +1,7 @@
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
import {BlessingDataModel} from "../data/blessing.mjs";
import {Blessing} from "../documents/blessing.mjs";
let months = [
"Praios",
"Rondra",
@ -70,8 +74,8 @@ function getJsonFromXML(dom) {
return jsonResult;
}
async function addSkillFromCompendiumByNameToActor(talentName, taw, actor) {
const compendiumOfSkills = game.packs.get('DSA_4-1.talente-brw');
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)
if (talentId) {
@ -79,7 +83,21 @@ async function addSkillFromCompendiumByNameToActor(talentName, taw, actor) {
try {
const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [talent]))[0]
embeddedDocument.update({system: {taw: taw}});
if (talent.system.gruppe === "Kampf") {
const atbasis = attributes.find(p => p.name === "at").value
const pabasis = attributes.find(p => p.name === "pa").value
const combatStatistic = combatStatistics.find(p => p.name === talent.name)
if (combatStatistic) { // melee with AT/PA values
let at = combatStatistic.at - atbasis ?? 0
let pa = combatStatistic.pa - pabasis ?? 0
console.log({system: {taw, at, pa}})
embeddedDocument.update({system: {taw, at, pa}});
} else { // ranged with only AT values which is equal to taw
embeddedDocument.update({system: {taw: taw, at: taw, pa: null}}); // at is already at raw taw and wasn't influenced by helden-software precalculations
}
} else {
embeddedDocument.update({system: {taw: taw, at: null, pa: null}}); // just regular talent with taw
}
} catch (error) {
console.error(`${talentName} not found in items`, error)
}
@ -102,6 +120,40 @@ 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)
if (spellId) {
const spell = await compendiumOfSpells.getDocument(spellId._id);
try {
const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [spell]))[0]
embeddedDocument.update({system: {zfw: zfw, hauszauber: hauszauber, repräsentation: representation}});
} catch (error) {
console.error(`${spell} not found in items`, error)
}
}
}
async function addLiturgiesFromCompendiumByNameToActor(liturgyName, actor) {
const compendiumOfLiturgies = game.packs.get('DSA_4-1.liturgien');
const liturgyId = compendiumOfLiturgies.index.find(liturgy => {
return liturgy.name === LiturgyData.lookupAlias(liturgyName.split(" (")[0])
})
if (liturgyId) {
const liturgy = await compendiumOfLiturgies.getDocument(liturgyId._id);
try {
await actor.createEmbeddedDocuments('Item', [liturgy])
} catch (error) {
console.error(`${liturgy} not found in items`, error)
}
}
}
/**
* gets the text content of a file
* @param file the file with the desired content
@ -133,10 +185,29 @@ function calculateBirthdate(json) {
return `${day}. ${month} ${year} BF`
}
function mapSkills(actor, held) {
function mapSkills(actor, held, kampfwerte) {
for (let talent in held.talentliste.talent) {
talent = held.talentliste.talent[talent]
addSkillFromCompendiumByNameToActor(talent.name, talent.value, actor)
// hook liturgy
if (talent.name.startsWith("Liturgiekenntnis")) {
actor.createEmbeddedDocuments('Item', [
new Blessing({
name: talent.name,
type: "Blessing",
system: {
gottheit: new RegExp("\\((.+)\\)").exec(talent.name)[1],
wert: talent.value
}
})
])
} else {
// proceed
const eigenschaften = held.eigenschaften.eigenschaft
addSkillFromCompendiumByNameToActor(talent.name, talent.value, actor, kampfwerte, eigenschaften)
}
}
}
@ -147,6 +218,20 @@ function mapAdvantages(actor, held) {
}
}
function mapSpells(actor, held) {
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) {
for (let liturgy in liturgies) {
liturgy = liturgies[liturgy]
addLiturgiesFromCompendiumByNameToActor(liturgy.name, actor)
}
}
/**
* parses a json into a fitting character-json
* @param rawJson the json parsed from the Helden-Software XML
@ -181,16 +266,14 @@ function mapRawJson(actor, rawJson) {
held.basis.rasse.aussehen.aussehentext0,
held.basis.rasse.aussehen.aussehentext1,
held.basis.rasse.aussehen.aussehentext2,
held.basis.rasse.aussehen.aussehentext3,
]
held.basis.rasse.aussehen.aussehentext3].join('\n')
json.meta.familie = [
held.basis.rasse.aussehen.familietext0,
held.basis.rasse.aussehen.familietext1,
held.basis.rasse.aussehen.familietext2,
held.basis.rasse.aussehen.familietext3,
held.basis.rasse.aussehen.familietext4,
held.basis.rasse.aussehen.familietext5,
]
held.basis.rasse.aussehen.familietext5].join('\n')
json.meta.titel = held.basis.rasse.aussehen.titel
json.meta.stand = held.basis.rasse.aussehen.stand
let attributes = held.eigenschaften.eigenschaft
@ -265,20 +348,6 @@ function mapRawJson(actor, rawJson) {
json.sonderfertigkeiten = specialAbilities
json.liturgien = liturgies
mapSkills(actor, held)
let spells = []
/*for (let spell in held.zauberliste.zauber) {
spell = held.zauberliste.zauber[spell]
spells.push({
name: spell.name,
rep: spell.repraesentation,
hauszauber: spell.hauszauber === "true",
zfw: spell.value,
anmerkungen: spell.zauberkommentar,
komplexitaet: spell.k,
})
}*/
json.zauber = spells
let combatValues = []
for (let combatValue in held.kampf.kampfwerte) {
combatValue = held.kampf.kampfwerte[combatValue]
@ -289,6 +358,11 @@ function mapRawJson(actor, rawJson) {
})
}
json.kampfwerte = combatValues
mapSkills(actor, held, combatValues)
mapSpells(actor, held)
mapMiracles(actor, liturgies)
let notes = []
for (let note in held.kommentare) {
note = held.kommentare[note]

View File

@ -0,0 +1,21 @@
{
"name": "Argelions bannende Hand",
"grad": 5,
"herkunft": [
{
"name": "Hesinde",
"grad": 5
},
{
"name": "Praios",
"grad": 5
}
],
"reichweite": "Sicht",
"ziel": "Zauber (auf Person, Objekt oder Zone)",
"zauberdauer": "1 Spielrunde (Gebet)",
"wirkungsdauer": "LkP* Stunden, eventuell augenblicklich",
"auswirkung": {
"V": "Diese Liturgie schwächt die Wirkung von Zaubern und Ritualen, die auf einem Objekt, auf einer Person oder (bei Flächenzaubern) auf einer Zone liegen, und kann sie gar völlig aufheben. Die LkP*+15 der Geweihten werden von den ZfP* des Zaubers oder Rituals abgezogen und so die Wirkung vermindert; fällt die Wirkung unter 0 ZfP*, ist die Magie dauerhaft gebannt; fällt die Wirkung des Zaubers genau auf 0 ZfP*, entspricht sie wie gehabt der Wirkung von 1 ZfP*."
}
}

View File

@ -0,0 +1,16 @@
{
"name": "Auge des Mondes",
"grad": 2,
"herkunft": [
{
"name": "Phex",
"grad": 2
}
],
"reichweite": "Selbst",
"ziel": "Geweihter",
"wirkungsdauer": "LkP* Stunden, maximal bis zum nächsten Sonnenaufgang",
"auswirkung": {
"II": "Der Geweihte ignoriert Dunkelheit (und daraus resultierende Abzüge) vollständig; er sieht, als wäre es helllichter Tag."
}
}

View File

@ -0,0 +1,71 @@
{
"name": "Exkommunikation",
"grad": 3,
"herkunft": [
{
"name": "Praios",
"grad": 3
},
{
"name": "Rondra",
"grad": 3
},
{
"name": "Efferd",
"grad": 3
},
{
"name": "Travia",
"grad": 3
},
{
"name": "Boron",
"grad": 3
},
{
"name": "Hesinde",
"grad": 3
},
{
"name": "Firun",
"grad": 3
},
{
"name": "Tsa",
"grad": 3
},
{
"name": "Phex",
"grad": 3
},
{
"name": "Peraine",
"grad": 3
},
{
"name": "Ingrerimm",
"grad": 3
},
{
"name": "Rahja",
"grad": 3
},
{
"name": "Angrosch",
"grad": 3
},
{
"name": "H'Ranga",
"grad": 3
}
],
"reichweite": "Sicht",
"ziel": "1 Person",
"zauberdauer": "Andacht",
"wirkungsdauer": "permanent",
"auswirkung": {
"III": "Dieses Ritual schließt eine Person rechtlich aus der Zwölfgöttlichen Gemeinschaft aus (üblicherweise jemanden, der sich gegen die göttlichen Gebote vergangen hat) und zeichnet sie als Frevler, so dass sie keinen Zugang zu den Paradiesen der Zwölfe erhalten kann, wenn sie vor Tilgung des Makels stirbt. Zudem profitieren Frevlerin geringerem Maß von segnenden Liturgien. Wenn die vom Exkommunizierenden verhängte Bußqueste vollbracht ist, endet der Ausschluss von kirchlichen Segnungen automatisch.",
"IV": "Exkommunizierte Geweihte verlieren sämtliche Karmaenergie und können natürlich auch keine durch Gebete o.ä. wieder gewinnen; ihr geistlicher Stand ruht, bis sie Buße getan haben.",
"V": "Eine nur <i>Kirchenoberhäuptern</i> bekannte Variante der EXKOMMUNIKATION nimmt auch die Weihen dauerhaft von einem Priester."
}
}

View File

@ -0,0 +1,65 @@
{
"name": "Handwerkssegen",
"alias": [
"Cereborns Handreichung (Handwerkssegen)",
"Hauch der Leidenschaft (Handwerkssegen)"
],
"grad": 1,
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "Berührung",
"ziel": "1 Person",
"zauberdauer": "1 Spielrunde (Gebet)",
"wirkungsdauer": "bis zum Ende der Probe, maximal LkP* Tage",
"auswirkung": {
"I": "Mit dieser Liturgie erfährt eine Person durch den Geweihten eine göttliche Inspiration in einem Talent, das für den Geweihten ein Mirakel+ Talent ist. Der TaW der inspirierten Person steigt für eine Probe um LkP*/2+5 Punkte. Solcherart geschaffene Werkstücke können bei vielen TaP* der Talentprobe nach Maßgabe des Meisters entweder besonders kunstfertig oder aber besonders robust sein, was beim Bau von Behelfsbrücken oder dergleichen wichtig sein kann."
}
}

View File

@ -0,0 +1,62 @@
{
"name": "Heiliger Befehl",
"alias": [],
"grad": 2,
"herkunft": [
{
"name": "Praios",
"grad": 2
},
{
"name": "Rondra",
"grad": 2
},
{
"name": "Efferd",
"grad": 2
},
{
"name": "Travia",
"grad": 2
},
{
"name": "Boron",
"grad": 2
},
{
"name": "Hesinde",
"grad": 2
},
{
"name": "Firun",
"grad": 2
},
{
"name": "Tsa",
"grad": 2
},
{
"name": "Phex",
"grad": 2
},
{
"name": "Peraine",
"grad": 2
},
{
"name": "Ingrerimm",
"grad": 2
},
{
"name": "Rahja",
"grad": 2
}
],
"reichweite": "Sicht",
"ziel": "1 Person",
"zauberdauer": "10 Aktionen (Stoßgebet)",
"wirkungsdauer": "bis der Befehl ausgeführt wurde, längstens LkP* Tage",
"auswirkung": {
"II": "Die Stimme des Geweihten wird laut und auch auf weitere Entfernung hörbar. Die angesprochene Person kann sich gegen einen so gegebenen Befehl, der dem Wesen der Gottheit entsprechen muss, nur mit einer Selbstbeherrschungs-Probe zur Wehr setzen, die um LkP*/2+5 Punkte erschwert ist, ansonsten befolgt sie den Befehl."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Initiation",
"alias": [],
"grad": 2,
"primärHerkunft": "Praios",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 2
},
{
"name": "Efferd",
"grad": 2
},
{
"name": "Travia",
"grad": 2
},
{
"name": "Boron",
"grad": 2
},
{
"name": "Hesinde",
"grad": 2
},
{
"name": "Firun",
"grad": 2
},
{
"name": "Tsa",
"grad": 2
},
{
"name": "Phex",
"grad": 2
},
{
"name": "Peraine",
"grad": 2
},
{
"name": "Ingrerimm",
"grad": 2
},
{
"name": "Rahja",
"grad": 2
}
],
"reichweite": "Berührung",
"ziel": "1 Person",
"zauberdauer": "1/2 Stund (Andacht)",
"wirkungsdauer": "permanent; kann nur durch eine Exkommunikation aufgehoben werden",
"auswirkung": {
"II": "Zwölfjährige oder Bekehrte werden mittels dieser Liturgie in den Zwölfgötterkult eingeführt, ihren Seelen steht prinzipiell eines der zwölfgöttlichen Paradiese offen. Im Zuge der Initiation erkennen Geweihte auch, ob Kinder potenzielle Novizen darstellen."
}
}

View File

@ -0,0 +1,62 @@
{
"name": "Objektweihe",
"alias": [],
"grad": 1,
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "Berührung",
"ziel": "1 Objekt",
"zauberdauer": "mehrere Stunden (Zeremonie)",
"wirkungsdauer": "solang sich der Träger des Objekts der jeweiligen Gottheit gefällig verhält oder bis die gespeicherte Segnung ausgelöst wurde, maximal LkP* Wochen",
"auswirkung": {
"I": "Diese Liturgie bindet neine der Zwölf Segnungen in einen Gegenstand. Die Wirkung dieser Segnung kann dann mit einer Anrufung der Gottheit (1 Aktion) jederzeit hervorgeholt werden. Der Gegenstand verliert seine Weihe damit jedoch. Solange der Gegenstand geweiht ist, ist er durch profane Gewalteinwirkung unzerstörbar. Handelt es sich um eine Rüstung, steigt der RS um LkP*/4 Punkte, jedoch nur im Kampf gegen unheilige Wesenheiten."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Eidsegen",
"alias": [],
"grad": 1,
"primärHerkunft": "",
"herkunft": [
{
"name": "Praios",
"grad": 0
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "Berührung",
"ziel": "1 Person",
"zauberdauer": "1 Spielrunde (Gebet)",
"wirkungsdauer": "frei festzulegen, lägstens aber LkP* Monate",
"auswirkung": {
"I": "Der Schwur einer Person wird mit dieser Liturgie bekräftigt. Wer den Eid ablegt, nennt dabei eine Strafe, die ihn bei Eidbruch ereilen soll. Diese Strafe sollte einem regeltechnischen Nachteil entsprechen. Wird der Eid gebrochen, trifft ihn dieser Nachteil. Zusätzlich wirken Liturgien, von denen der Eidbrüchige profitieren würde, auf ihn nur mit halber Stärke."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Feuersegen",
"alias": [],
"grad": 1,
"primärHerkunft": "Ingerimm",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 0
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "selbst / Sicht (Variante des Feuerschutzes)",
"ziel": "Gewewihter / 1 Gegenstand (Variante des Feuerschutzes)",
"zauberdauer": "1 Spielrunde (Gebet)",
"wirkungsdauer": "bis zum Ende der Probe, maximal LkP* Tage",
"auswirkung": {
"I": "Aus der Fingerspitze des Geweihten schlägt ein Flämmchen, das gegen Wind und Wetter geschützt ist; es verlischt nur, wenn es in Wasser getaucht wird / In dieser Variante schützt der Geweihte ein Feuer mit einer Brennfläche von maximal einem Rechtschritt, das so nicht verlöschen kann, solange genug Brennmaterial nachgelegt wird."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Geburtssegen",
"alias": [],
"grad": 1,
"primärHerkunft": "Tsa",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 0
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "Berührung",
"ziel": "1 Person",
"zauberdauer": "1 Spielrunde (Gebet)",
"wirkungsdauer": "bis zum 12. Geburtstag des Kindes",
"auswirkung": {
"I": "Die Liturgie schützt ein Kind vor dämonischen Einflüsterungen und Entführung durch Kobolde. Entsprechende Proben sind um 12 Punkte erschwert."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Geburtssegen",
"alias": [],
"grad": 1,
"primärHerkunft": "Phex",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 0
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "Berührung",
"ziel": "1 Person",
"zauberdauer": "6 Aktionen (Stoßgebet)",
"wirkungsdauer": "LkP* Spielrunden, maximal bis zur nächsten Talentprobe",
"auswirkung": {
"I": "Wer diesen Segen empfängt, darf einmal eine Probe wiederholen und das für ihn günstigere Ergebnis wählen. Dies funktioniert jedoch nicht mit Fertigkeiten, die beim segnenden Geweihten unter Mirakel- gelistet sind."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Grabsegen",
"alias": [],
"grad": 1,
"primärHerkunft": "Boron",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 0
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "Berührung",
"ziel": "1 Person",
"zauberdauer": "1 Spielrunde (Gebet)",
"wirkungsdauer": "permanent; es sei denn, das Grab wird entweiht.",
"auswirkung": {
"I": "Das Grab eines solcherart Gesegneten wirkt abschreckend auf Grabräuber und verhindert nekromantische Rituale, sodass der Leichnam schwerer zu einem Untoten erhoben werden kann. Sterblichen, die sich am Grab vergehenwollen, muss eine MU-Probe erschwert um eventuell vorhandene Totenangst gelingen. Zauber, die den Geist oder den Leichnam des Toten betreffen, sind um LkP*/2 Punkte erschwert."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Harmoniesegen",
"alias": [],
"grad": 1,
"primärHerkunft": "Rahja",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 0
}
],
"reichweite": "Berührung",
"ziel": "1 Person",
"zauberdauer": "12 Aktionen (Stoßgebet)",
"wirkungsdauer": "permanent; es sei denn, das Grab wird entweiht.",
"auswirkung": {
"I": "Die gesegnete Person wird innerlich ausgeglichen und zuversichtlich. Proben auf MU, IN und CH sind um LkP*/2 Punkte erleichtert, Proben auf Schlechte Eigenschaften ebenso erschwert. Zauber, die den so Gesegneten geistig verwirren oder aufregen sollen (wie etwa der HORRIPHOBUS), werden in ihrer von den ZfP* abhängigen Wirkungsstärke um LkP*/2 Punkte vermindert. Fallen die ZfP* dabei unter 0, endet der Zauber."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Heilungssegen",
"alias": [],
"grad": 1,
"primärHerkunft": "Peraine",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 0
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "selbst/ Berührung",
"ziel": "Geweihte / 1 Person",
"zauberdauer": "1 Spielrunde (Gebet)",
"wirkungsdauer": "augenblicklich",
"auswirkung": {
"I": "Der Nutznießer dieser Liturgie (dies kann sowohl der Geweihte selbst als auch eine weitere Person sein) erhält LkP*/2+3 Lebenspunkte zurück. Hierdurch schließen sich jedoch keine regeltechnischen Wunden."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Märtyrersegen",
"alias": [],
"grad": 1,
"primärHerkunft": "Firun",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 0
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "selbst",
"ziel": "Geweihter",
"zauberdauer": "4 Aktionen",
"wirkungsdauer": "permanent; es sei denn, das Grab wird entweiht.",
"auswirkung": {
"I": "Der Geweihte spürt keinen Schmerz: Er erleidet keine Abzüge durch niedrige LeP oder Wunden, auch stirbt er nicht. Erst mit Ende der Wirkungsdauer offenbaren sich dem Geweihten die Folgen des Kampfes. Der Meister sollte darum währenddessen den erlittenen Schaden verdeckt notieren. Der Selbstbeherrschungs-Wert des Gesegneten steigt darüber hinaus um LkP*/2 Punkte."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Schutzsegen",
"alias": [],
"grad": 1,
"primärHerkunft": "Rondra",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 0
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "Berührung",
"ziel": "Zone von 10 Schritt Radius",
"zauberdauer": "10 Aktionen (Stoßgebet)",
"wirkungsdauer": "LkP* Spielrunden",
"auswirkung": {
"I": "eine bestimmte Art von unheiligen Kreaturen kann den so geschützten Boden nur dann betreten, wenn ihre Magieresistenz höher ist als LkP*/2. Sie erleiden dort außerdem Schaden in Höhe von 1W6 SP pro Spielrunde bzw. sogar 1W6 SP pro Kampfrunde, falls es sich bei der gebannten Art um einen der Gottheit des Geweihten entgegengesetzten Dämon handelt (oder um eine Untotenart bei einem Borongeweihten). Der Geweihte muss nicht unbedingt wissen, worum es sich bei einem Wesen, das er abhalten möchte, genau handelt; es muss nur klarsein, welches Wesen er meint, und alle gleichartigen Kreaturen werden ebenfalls ferngehalten."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Speisesegen",
"alias": [],
"grad": 1,
"primärHerkunft": "Travia",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 0
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "Berührung",
"ziel": "mehrere Gegenstände",
"zauberdauer": "8 Aktionen (Stoßgebet)",
"wirkungsdauer": "augenblicklich",
"auswirkung": {
"I": "Der Segen macht eine Mahlzeit von zweifelhafter Qualität für bis zu LkW Personen genießbar und durchschnittlich schmackhaft. Krankheiten bis zu einer Stufe von LkP*/2 die durch diese Mahlzeit aufgetreten wären, werden verhindert, hinzugefügtes Gift wird jedoch nicht neutralisiert."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Tranksegen",
"alias": [],
"grad": 1,
"primärHerkunft": "Efferd",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 0
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 1
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "Berührung",
"ziel": "mehrere Gegenstände",
"zauberdauer": "8 Aktionen (Stoßgebet)",
"wirkungsdauer": "augenblicklich",
"auswirkung": {
"I": "Der Segen macht Getränke (hauptsächlich Wasser), die für bis zu LkW Personen einen Tag lang ausreichen, genießbar. Krankheiten bis zu einer Stufe von LkP*/2, die durch diese Getränke aufgetreten wären, werden verhindert, hinzugefügtes Gift wird jedoch nicht neutralisiert. Meerwasser kann hiermit in Trinkwasser gewandelt werden."
}
}

View File

@ -0,0 +1,63 @@
{
"name": "Weisheitssegen",
"alias": [],
"grad": 1,
"primärHerkunft": "Hesinde",
"herkunft": [
{
"name": "Praios",
"grad": 1
},
{
"name": "Rondra",
"grad": 1
},
{
"name": "Efferd",
"grad": 1
},
{
"name": "Travia",
"grad": 1
},
{
"name": "Boron",
"grad": 1
},
{
"name": "Hesinde",
"grad": 0
},
{
"name": "Firun",
"grad": 1
},
{
"name": "Tsa",
"grad": 1
},
{
"name": "Phex",
"grad": 1
},
{
"name": "Peraine",
"grad": 1
},
{
"name": "Ingrerimm",
"grad": 1
},
{
"name": "Rahja",
"grad": 1
}
],
"reichweite": "selbst/ Berührung",
"ziel": "Geweihter / 1 Person",
"zauberdauer": "15 Aktionen (Stoßgebet)",
"wirkungsdauer": "LkP* Stunden",
"auswirkung": {
"I": "Der so Gesegnete (der Geweihte oder eine andere Person) gewinnt göttliche Einsichten und ist gefeit gegen Torheit. Er verteilt möglichst gleichwertig LkP*/2 Punkte auf KL und IN, außerdem steigt seine MR um LkP*/4 Punkte."
}
}

View File

@ -1,9 +1,5 @@
{
"_id": "XxPXNovZd9AX2sHM",
"_key": "!items!XxPXNovZd9AX2sHM",
"type": "Skill",
"name": "Abrichten",
"system": {
"gruppe": "Handwerk",
"probe": [
"MU",
@ -14,5 +10,4 @@
],
"behinderung": "situationsbedingt",
"talent": "Wann immer ein Held seinem Tier ein Kunststück (einem Pferd den stummen Alarm, einem Hund das Apportieren oder Männchen machen) beibringen will, ist eine Probe auf Abrichten fällig. Zuschläge auf die Probe entstehen durch Fehlversuche (+2 für jede gescheiterte Probe), durch die Schwierigkeit des Kunststücks und die grundsätzliche Lernfähigkeit und potentielle Loyalität des Tieres. Übungen, die einem Tier wegen körperlicher oder geistiger Beschränkungen nicht möglich sind, kann ihm auch ein meisterlicher Abrichter nicht beibringen."
}
}

View File

@ -1,9 +1,5 @@
{
"_id": "w3wHyimJXv6EjnMw",
"_key": "!items!w3wHyimJXv6EjnMw",
"type": "Skill",
"name": "Ackerbau",
"system": {
"gruppe": "Handwerk",
"probe": [
"IN",
@ -14,5 +10,4 @@
],
"behinderung": "situationsbedingt",
"talent": "Dies ist die grundlegende Kenntnis von Bodenverhältnissen, Aussaat und Ernte, Feldbestellungs- und Lagerungsmethoden. Mit dem Talent kann man Nutzpflanzen erkennen und unterscheiden und auf diese Art und Weise z.B. eine auf einer einsamen Insel gestrandete Heldengruppe vor dem Verhungern bewahren. Zudem erkennt ein Ackerbau-Kundiger leicht Wert und Haltbarkeit von Nahrungsmitteln. Auf der aktiven Seite heißt dies auch, dass ein entsprechend ausgebildeter Held mit Pflug, Hacke und Dreschflegel umzugehen weiß."
}
}

View File

@ -1,9 +1,5 @@
{
"_id": "peize2dihvjf2N7p",
"_key": "!items!peize2dihvjf2N7p",
"type": "Skill",
"name": "Akrobatik",
"system": {
"gruppe": "Körperlich",
"probe": [
"MU",
@ -18,5 +14,4 @@
],
"behinderung": "*2",
"talent": "In diesem Talent sind die Dinge zusammengefasst, in denen sich Gaukler seit ihrer Kindheit üben: Balancieren, Schwingen an Seilen und Trapezen, Radschlagen und halsbrecherische Salti. Wann immer ein Held eine Aktion unternimmt, die eines Zirkusartisten würdig wäre also bei allen willentlich ausgeführten akrobatischen Aktionen , können Sie als Meister eine Akrobatik-Probe verlangen. Eher gewöhnliche Aktionen der Körperbeherrschung, wie speziell das Abrollen nach Stürzen, fallen unter das Talent Körperbeherrschung."
}
}

View File

@ -1,9 +1,5 @@
{
"_id": "EHrjrxETwhx1mB63",
"_key": "!items!EHrjrxETwhx1mB63",
"type": "Skill",
"name": "Sprachen kennen: Alaani",
"system": {
"name": "Sprachen kennen Alaani",
"gruppe": "Sprachen",
"probe": [
"KL",
@ -14,5 +10,4 @@
],
"komplexität": "21",
"talent": "Ähnlich wie bei den Schriften wird jede aventurische Sprache als eigenes Talent gewertet. Je der Aventurier beherrscht seine Muttersprache auf einem Startwert in Höhe seiner Klugheit2, manche darüber hinaus noch eine Zweitsprache in Höhe von KL 4. Weitere Sprachen müssen dann explizit erlernt werden.<br/>Um die Sprache identifizieren zu können, ist ein TaW von 1 nötig, um grundlegende Konzepte (“Ich Hunger”) verstehen und vermitteln zu können, ist ein TaW von 2 nötig; um einfache Sätze bilden und verstehen zu können, benötigt man einen TaW von 4. Ein TaW von 1/3 der Komplexität bedeutet recht fließenden Umgang mit allen üblichen grammatischen Konstruktionen und die Kenntnis auch seltener Wörter, während ein TaW in Höhe der halben Komplexität heißt, dass man die Sprache so gut wie ein durchschnittlicher Einheimischer beherrscht (wenn man auch immer noch einen leichten Akzent aufweist). Selbst philosophische oder magietheoretische Schriften gehen selten über eine Komplexität von 15 hinaus."
}
}

View File

@ -1,9 +1,5 @@
{
"_id": "oHnVR4rpCZes1MBk",
"_key": "!items!oHnVR4rpCZes1MBk",
"type": "Skill",
"name": "Alchimie",
"system": {
"gruppe": "Handwerk",
"probe": [
"MU",
@ -22,5 +18,4 @@
],
"behinderung": "situationsbedingt",
"talent": "Dieses Talent regelt die Herstellung normaler Chemikalien und wundertätiger Mittel. Der Spieler teilt dem Meister mit, welches alchimistische Gemisch sein Held herstellen will, und der Spielleiter legt den Zuschlag (oder Abzug) auf die erforderliche Probe fest. Bevor es zur Probe kommt, muss der Held natürlich erst einmal in den Besitz der benötigten Zutaten und auch der Rezeptur kommen. Eine gescheiterte Probe auf diesem gefährlichen Gebiet kann mancherlei bewirken: ein harmloses, aber bestialisch stinkendes, grünes Wölkchen zum Beispiel, oder aber einen Urknall, der ein halbes Stadtviertel in Schutt und Asche legt. Der Meister sollte so fair sein, seinen Helden in etwa anzudeuten, was eine gescheiterte Probe für sie bedeuten könnte. Talentproben in Alchimie können auch zur Analyse unbekannter Mixturen dienen aber auch auf diesem Gebiet kann ein Irrtum verhängnisvolle Folgen haben (und hier sollte der Meister auch ruhig verdeckt würfeln)."
}
}

Some files were not shown because too many files have changed in this diff Show More