Merge branch 'main' of https://git.macniel.online/macniel/foundry-dsa41-game
|
|
@ -1,2 +1,3 @@
|
|||
dist
|
||||
node_modules
|
||||
src/packs/__source
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
81
gulpfile.mjs
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 500 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 87 KiB |
132
src/main.mjs
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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'),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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('');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export class Blessing extends Item {
|
||||
/**
|
||||
* Augment the basic Item data model with additional dynamic data.
|
||||
*/
|
||||
prepareData() {
|
||||
super.prepareData();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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}}})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
export class Liturgy extends Item {
|
||||
/**
|
||||
* Augment the basic Item data model with additional dynamic data.
|
||||
*/
|
||||
prepareData() {
|
||||
super.prepareData();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export class SpecialAbility extends Item {
|
||||
/**
|
||||
* Augment the basic Item data model with additional dynamic data.
|
||||
*/
|
||||
prepareData() {
|
||||
super.prepareData();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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*."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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ß."
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Klugheit–2, 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."
|
||||
}
|
||||
}
|
||||
|
|
@ -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)."
|
||||
}
|
||||
}
|
||||