Compare commits

..

No commits in common. "main" and "0.3.3" have entirely different histories.
main ... 0.3.3

384 changed files with 1524 additions and 9746 deletions

View File

@ -44,14 +44,6 @@ Es ist möglich via Kontextmenü Gegenstands-Stapel in zwei Stapel aufzuteilen,
Es ist möglich den Rasten und Regenerations Dialog von dem Charakterbogen eines Charakters aufzurufen worin man die Einstellungen vornehmen kann die die Regeneration während einer Rest von Lebensenergie und Astralenergie sowie der Heilung von Wunden beeinflussen.
### Mini Charakterbogen
Wenn der Charakterbogen schmall genug gezogen ist, wird dieser aktiviert und enthält lediglich die Seitenleiste sowie die Attribute auf denen gewürfelt werden können. Daneben allerdings befinden sich die Reiter des großen Charakterbogens welche die jeweiligen Seiten in kleiner Form als separates Fenster öffnen lassen.
### Item Browser
Es ist nun möglich auf dem Charakterbogen unter dem Reiter "Inventar" den Item Browser zu öffnen wo alle Gegenstände der aktuellen Welt enthalten sind und mit rudimentären Filtern durchsucht werden können. Ein Spieler kann hierrüber neue Gegenstände kaufen, der Spielleiter hingegen kann per Drag and Drop die gesuchten Gegenstände auf alle Actor Sheets hinzufügen.
## GM Tools
### Gruppenmanagement
@ -82,5 +74,3 @@ Um heimlich vergleichend Talentproben von zwei Charakteren und oder Kreaturen du
Icon Theme of Equipment Items (e.g. Weapons, Armory, Adventuring Gear) is made by https://soda-1.itch.io/
Tanja für den UI UX Support.

View File

@ -1,9 +1,8 @@
import {dest, series, src} from 'gulp';
import gulp from 'gulp';
import process from 'node:process';
import replace from 'gulp-replace';
import jsonModify from 'gulp-json-modify';
import {subtle} from 'node:crypto';
import {getRandomValues} from 'node:crypto';
import * as dartSass from 'sass';
import gulpSass from 'gulp-sass';
import {deleteAsync} from 'del';
@ -12,42 +11,28 @@ import {join} from 'node:path';
import {compilePack} from '@foundryvtt/foundryvtt-cli';
const sass = gulpSass(dartSass)
const sass = gulpSass(dartSass);
/**
* Generate a random alphanumeric string ID of a given requested length using `crypto.getRandomValues()`.
* @param {string} reference The reference which should be used to generate a semi random ID
* @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(reference = "", length = 16) {
const encoder = new TextEncoder()
const data = encoder.encode(reference)
return subtle.digest('SHA-256', data).then(hashBuffer => {
// Step 2: Convert the hash to a Base62 string
const base62Chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const hashArray = new Uint8Array(hashBuffer)
let num = BigInt(0)
// Convert hash buffer to a BigInt
for (let byte of hashArray) {
num = (num << BigInt(8)) | BigInt(byte)
}
let base62Id = '';
while (num > 0) {
const remainder = num % BigInt(62)
base62Id = base62Chars[Number(remainder)] + base62Id
num = num / BigInt(62)
}
// Step 3: Return the first 16 characters
return base62Id.slice(-length)
});
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, overwrite = true) {
const SOURCE = from;
const DEST = to;
const TYPE = ofType;
@ -60,19 +45,16 @@ const convert = function (from, to, ofType, overwrite = true) {
mkdirSync(DEST)
}
let promises = []
const filewalker = async (source) => {
console.debug("entering directory", source)
for (let file of readdirSync(source)) {
const filewalker = (source) => {
console.debug("entering directory", source);
readdirSync(source).forEach(file => {
if (statSync(join(source, file)).isDirectory()) {
await filewalker(join(source, file))
filewalker(join(source, file));
} else {
if (file.endsWith(".json")) {
console.debug("processing file", join(source, file))
let originalSource = JSON.parse(readFileSync(join(source, file), {encoding: "utf8"}))
promises.push(new Promise((resolve2) => {
randomID("DSA_4-1" + TYPE + originalSource.name.trim()).then(id => {
let originalSource = JSON.parse(readFileSync(join(source, file), {encoding: "utf8"}));
let id = randomID();
let targetSource = {
_id: id,
_key: "!items!" + id,
@ -82,28 +64,19 @@ const convert = function (from, to, ofType, overwrite = true) {
system: {...originalSource},
}
delete targetSource.system.image;
let target = JSON.stringify(targetSource, null, 2)
let newFileName = "./" + join(DEST, id + ".json")
if (!existsSync(join("./", DEST))) {
mkdirSync(join("./", DEST))
}
writeFileSync(newFileName, target, {encoding: "utf8"})
resolve2()
})
}))
}
}
let target = JSON.stringify(targetSource, null, 2);
let newFileName = "./" + join(DEST, id + ".json");
writeFileSync(newFileName, target, {encoding: "utf8"});
}
});
}
filewalker(SOURCE)
return Promise.allSettled(promises)
}
function cleanDist() {
return deleteAsync(['dist/**'])
return deleteAsync(['dist/**']);
}
function buildStyles() {
@ -139,44 +112,44 @@ function updateManifestFile() {
.pipe(dest('src/'))
}
gulp.task('prepareDB', async function (done) {
async function prepareDB() {
try {
if (!existsSync("./src/packs/__source")) {
mkdirSync("./src/packs/__source");
}
await convert("./src/packs/_source/talente", "./src/packs/__source/talente", "Skill")
await convert("./src/packs/_source/zauber-brw", "./src/packs/__source/zauber", "Spell")
await convert("./src/packs/_source/vorteile", "./src/packs/__source/vorteile", "Advantage")
await convert("./src/packs/_source/nachteile", "./src/packs/__source/vorteile", "Advantage", false)
await convert("./src/packs/_source/sonderfertigkeiten", "./src/packs/__source/sonderfertigkeiten", "SpecialAbility")
await convert("./src/packs/_source/waehrungen", "./src/packs/__source/waehrungen", "Equipment")
await convert("./src/packs/_source/Gegenstaende/Waffen", "./src/packs/__source/waffen", "Equipment")
await convert("./src/packs/_source/Gegenstaende/Munition", "./src/packs/__source/munition", "Equipment")
await convert("./src/packs/_source/Gegenstaende/Ruestzeug", "./src/packs/__source/ruestzeug", "Equipment")
await convert("./src/packs/_source/Gegenstaende/Behaelter", "./src/packs/__source/gegenstaende", "Equipment", false)
await convert("./src/packs/_source/Gegenstaende/Bekleidung", "./src/packs/__source/gegenstaende", "Equipment", false)
await convert("./src/packs/_source/Gegenstaende/Beleuchtung", "./src/packs/__source/gegenstaende", "Equipment", false)
await convert("./src/packs/_source/Gegenstaende/Buecher", "./src/packs/__source/gegenstaende", "Equipment", false)
await convert("./src/packs/_source/Gegenstaende/Essutensilien", "./src/packs/__source/gegenstaende", "Equipment", false)
await convert("./src/packs/_source/Gegenstaende/Sonstiges", "./src/packs/__source/gegenstaende", "Equipment", false)
await convert("./src/packs/_source/Gegenstaende/Werkzeug", "./src/packs/__source/gegenstaende", "Equipment", false)
await convert("./src/packs/_source/Gegenstaende/Seile", "./src/packs/__source/gegenstaende", "Equipment", false)
await convert("./src/packs/_source/liturgien-und-segnungen", "./src/packs/__source/liturgien", "Liturgy")
await convert("./src/packs/_source/wunden", "./src/packs/__source/wunden", "ActiveEffect")
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/nachteile", "./src/packs/__source/vorteile", "Advantage", false);
convert("./src/packs/_source/sonderfertigkeiten", "./src/packs/__source/sonderfertigkeiten", "SpecialAbility");
convert("./src/packs/_source/waehrungen", "./src/packs/__source/waehrungen", "Equipment");
convert("./src/packs/_source/Gegenstaende/Waffen", "./src/packs/__source/Waffen", "Equipment");
convert("./src/packs/_source/Gegenstaende/Munition", "./src/packs/__source/Munition", "Equipment");
convert("./src/packs/_source/Gegenstaende/Ruestzeug", "./src/packs/__source/Ruestzeug", "Equipment");
convert("./src/packs/_source/Gegenstaende/Behaelter", "./src/packs/__source/Gegenstaende", "Equipment", false);
convert("./src/packs/_source/Gegenstaende/Bekleidung", "./src/packs/__source/Gegenstaende", "Equipment", false);
convert("./src/packs/_source/Gegenstaende/Beleuchtung", "./src/packs/__source/Gegenstaende", "Equipment", false);
convert("./src/packs/_source/Gegenstaende/Buecher", "./src/packs/__source/Gegenstaende", "Equipment", false);
convert("./src/packs/_source/Gegenstaende/Essutensilien", "./src/packs/__source/Gegenstaende", "Equipment", false);
convert("./src/packs/_source/Gegenstaende/Sonstiges", "./src/packs/__source/Gegenstaende", "Equipment", false);
convert("./src/packs/_source/Gegenstaende/Werkzeug", "./src/packs/__source/Gegenstaende", "Equipment", false);
convert("./src/packs/_source/Gegenstaende/Seile", "./src/packs/__source/Gegenstaende", "Equipment", false);
convert("./src/packs/_source/liturgien-und-segnungen", "./src/packs/__source/liturgien", "Liturgy");
convert("./src/packs/_source/wunden", "./src/packs/__source/wunden", "ActiveEffect");
convert("./src/packs/_source/kulturen", "./src/packs/__source/kulturen", "Culture");
convert("./src/packs/_source/spezien", "./src/packs/__source/spezien", "Species");
convert("./src/packs/_source/professionen", "./src/packs/__source/professionen", "Profession");
await convert("./src/packs/_source/kulturen", "./src/packs/__source/kulturen", "Culture")
await convert("./src/packs/_source/spezien", "./src/packs/__source/spezien", "Species")
await convert("./src/packs/_source/professionen", "./src/packs/__source/professionen", "Profession")
done()
} catch (err) {
console.error(err)
console.error(err);
}
})
}
gulp.task('buildDB', function (done) {
function buildDB() {
// Determine which source folders to process
const PACK_SRC = "src/packs/__source"
@ -188,16 +161,15 @@ gulp.task('buildDB', function (done) {
);
for (const folder of folders) {
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, nedb: false})
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, nedb: false});
}
resolve()
done()
})
})
}
export default series(
@ -206,6 +178,6 @@ export default series(
copySource,
copyAssets,
buildStyles,
gulp.task('prepareDB'),
gulp.task('buildDB')
prepareDB,
buildDB
)

8
package-lock.json generated
View File

@ -12,7 +12,7 @@
"gulp-json-modify": "^1.0.2"
},
"devDependencies": {
"@foundryvtt/foundryvtt-cli": "^3.0.2",
"@foundryvtt/foundryvtt-cli": "^3.0.0",
"cb": "^0.1.1",
"del": "^8.0.1",
"fvtt-types": "npm:@league-of-foundry-developers/foundry-vtt-types@^13.346.0-beta.20250812191140",
@ -209,9 +209,9 @@
}
},
"node_modules/@foundryvtt/foundryvtt-cli": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@foundryvtt/foundryvtt-cli/-/foundryvtt-cli-3.0.2.tgz",
"integrity": "sha512-coh4Cf4FD/GHxk2QMsd+3wLMivNeih4rfkbZy8CaYjdlpo6iciFQwxLqznZWtn+5p06zekvS2xLUF55NnbXQDw==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@foundryvtt/foundryvtt-cli/-/foundryvtt-cli-3.0.0.tgz",
"integrity": "sha512-OiF4HtnYg5An1ivVxB68mOj5LO5gMHd4uHmC5nWdD8IYxpK0pSYw3t+cHrUYDp+Tic78uwFuHxLyc+ZNeZXulA==",
"dev": true,
"dependencies": {
"chalk": "^5.4.1",

View File

@ -10,11 +10,10 @@
"scripts": {
"test": "true",
"build": "gulp",
"localBuild": "VERSION=0.0.1 gulp",
"installToFoundry": "node installToFoundry.mjs"
},
"devDependencies": {
"@foundryvtt/foundryvtt-cli": "^3.0.2",
"@foundryvtt/foundryvtt-cli": "^3.0.0",
"cb": "^0.1.1",
"del": "^8.0.1",
"fvtt-types": "npm:@league-of-foundry-developers/foundry-vtt-types@^13.346.0-beta.20250812191140",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 B

After

Width:  |  Height:  |  Size: 281 B

View File

@ -1,59 +1 @@
{
"TYPES": {
"Actor": {
"Character": "Held",
"Creature": "Kreatur",
"Group": "Heldengruppe",
"Merchant": "Händler"
},
"Item": {
"ActiveEffect": "Aktiver Effekt",
"Equipment": "Ausrüstungsgegenstand",
"Skill": "Talent",
"Advantage": "Vor-/Nachteil",
"SpecialAbility": "Sonderfertigkeit",
"Spell": "Zauber",
"Liturgy": "Liturgie",
"Species": "Spezies",
"Culture": "Kultur",
"Profession": "Profession"
}
},
"COOLDOWN": {
"cancel": "{t} abbrechen"
},
"WEAPON": {
"attack": "Mit {weapon} angreifen",
"parry": "Mit {weapon} parrieren",
"damage": "Mit {weapon} schaden machen",
"initiative": "Initiative würfeln"
},
"COMBAT_DIALOG": {
"notReadyReason": {
"title": "Angriff kann aus folgenden Gründen nicht ausgeführt werden:",
"noTarget": "Kein Ziel ausgewählt",
"noWeapon": "Keine Waffe ausgewählt",
"noSkill": "Kein Waffentalent ausgewählt",
"noManeuver": "Kein Manöver ausgewählt",
"impossible": "Erschwernis zu hoch für Talentwert"
}
},
"COMBAT_DIALOG_TP": {
"windowTitle": "Schaden Würfeln",
"regularFormula": "Schadensformel:",
"bonusDamage": "Zusätzlicher Schaden:",
"buttonText": "Würfeln"
},
"SPELL_DIALOG": {
"notReadyReason": {
"title": "Zauber kann aus folgenden Gründen nicht gewirkt werden:",
"noRepresentation": "Keine Repräsentation gewählt",
"tooManySpoMods": "Zu viele Spontane Modifikationen ausgewählt",
"noZFPDataAvailable": "Noch keine Zauberprobe gewürfelt",
"overspentZFP": "Zu viele ZfP ausgegeben"
}
},
"ITEM_BROWSER": {
"progress": "{current}/{max}: Importiere von {compendium}"
}
}
{}

View File

@ -1,50 +0,0 @@
{
"TYPES": {
"Actor": {
"Character": "Hero",
"Creature": "Creature",
"Group": "Adventure Group",
"Merchant": "Merchant"
},
"Item": {
"ActiveEffect": "Active Effect",
"Equipment": "Equipment",
"Skill": "Skill",
"Advantage": "Dis-/advantage",
"SpecialAbility": "Special Ability",
"Spell": "Spell",
"Liturgy": "Liturgy",
"Species": "Species",
"Culture": "Culture",
"Profession": "Profession"
}
},
"COOLDOWN": {
"cancel": "cancel {t}"
},
"WEAPON": {
"attack": "Attack with {weapon}",
"parry": "Parry with {weapon}",
"damage": "Deal damage with {weapon}",
"initiative": "Roll initiative"
},
"COMBAT_DIALOG": {
"notReadyReason": {
"title": "Attack can't be executed due to:",
"noTarget": "No Target selected",
"noWeapon": "No Weapon selected",
"noSkill": "No Skill selected",
"noManeuver": "No Maneuver selected",
"impossible": "Difficulty exceeds Skill Value"
}
},
"COMBAT_DIALOG_TP": {
"windowTitle": "Roll Damage",
"regularFormula": "Damage formula:",
"bonusDamage": "Additional damage formula:",
"buttonText": "Roll Dice"
},
"ITEM_BROWSER": {
"progress": "{current}/{max}: imported from {compendium}"
}
}

View File

@ -1,43 +1,401 @@
import {PlayerCharacterDataModel} from "./module/data/character.mjs";
import {SkillSheet} from "./module/sheets/skillSheet.mjs";
import {SpellSheet} from "./module/sheets/spellSheet.mjs";
import {SkillDataModel} from "./module/data/skill.mjs";
import {SpellDataModel} from "./module/data/spell.mjs";
import {VornachteileDataModel} from "./module/data/vornachteile.mjs";
import {Character} from "./module/documents/character.mjs";
import CharacterSheet from "./module/sheets/characterSheet.mjs";
import {AdvantageSheet} from "./module/sheets/advantageSheet.mjs";
import {GroupDataModel} from "./module/data/group.mjs";
import {GroupSheet} from "./module/sheets/groupSheet.mjs";
import {EquipmentDataModel} from "./module/data/equipment.mjs";
import {EquipmentSheet} 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";
import {ProfessionDataModel} from "./module/data/profession.mjs";
import {SpeciesDataModel} from "./module/data/species.mjs";
import {CultureDataModel} from "./module/data/culture.mjs";
import {CultureSheet} from "./module/sheets/cultureSheet.mjs";
import {SpeciesSheet} from "./module/sheets/SpeciesSheet.mjs";
import {ProfessionSheet} from "./module/sheets/professionSheet.mjs";
import {XmlImportDialog} from "./module/dialog/xmlImportDialog.mjs";
import {initGlobalSettings, initUserSettings} from "./module/settings/global-settings.mjs";
import {setUpActorSheets, setUpItemSheets} from "./module/setup/sheets.mjs";
import {loadPartials} from "./module/setup/partials.mjs";
import {
initSocketLib,
initCombat,
initDataModels,
initDocumentClasses,
initGlobalAccess
} from "./module/setup/config.mjs";
import {initHandlebarHelpers} from "./module/handlebar-helpers/index.mjs";
import {MerchantDataModel} from "./module/data/merchant.mjs";
import {MerchantSheet} from "./module/sheets/merchantSheet.mjs";
import {RestingDialog} from "./module/dialog/restingDialog.mjs";
import {BattleDialog} from "./module/dialog/battleDialog.mjs";
async function preloadHandlebarsTemplates() {
return foundry.applications.handlebars.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-array-editor.hbs',
'systems/DSA_4-1/templates/dialog/liturgy-dialog.hbs'
]);
}
Hooks.once("init", () => {
game.DSA41 = {
rollItemMacro,
Zonenruestung,
Zonenwunde,
Trefferzone,
Wunde,
RestingDialog,
BattleDialog
}
// Configure custom Document implementations.
CONFIG.Actor.documentClass = Character;
// Configure System Data Models.
CONFIG.Actor.dataModels = {
character: PlayerCharacterDataModel,
group: GroupDataModel,
creature: CreatureDataModel,
Merchant: MerchantDataModel,
};
CONFIG.Item.dataModels = {
Skill: SkillDataModel,
Spell: SpellDataModel,
Advantage: VornachteileDataModel,
Equipment: EquipmentDataModel,
Liturgy: LiturgyDataModel,
Blessing: BlessingDataModel,
SpecialAbility: SpecialAbilityDataModel,
ActiveEffect: ActiveEffectDataModel,
Profession: ProfessionDataModel,
Spezies: SpeciesDataModel,
Kultur: CultureDataModel,
}
CONFIG.Combat.initiative = {
formula: `(@ini.wuerfel)d6 + @ini.aktuell`,
decimals: 0
}
const setMovementSpeeds = () => {
const movementActions = CONFIG.Token.movement.actions
for (const key of ["swim", "climb", "crawl", "walk", "drive", "ride", "fly"]) {
delete movementActions[key]?.getCostFunction
}
movementActions.climb.canSelect = (token) => {
const actor = token.actor | null;
return (actor.type === "Character" && actor.system.itemTypes["Skill"].find(p => p.name === "Klettern")?.system.taw > 0) || actor.type === "Creature"
}
movementActions.crawl.canSelect = (token) => {
const actor = token.actor | null;
return actor.type === "Character" || actor.type === "Creature"
}
movementActions.walk.canSelect = (token) => {
const actor = token.actor | null;
return actor.type === "Character" || actor.type === "Creature"
}
movementActions.swim = {
label: "TOKEN.MOVEMENT.ACTIONS.swim.label",
icon: "fa-solid fa-swim",
order: 0,
canSelect: (token) => {
const actor = token.actor | null;
return actor.type === "Character" || actor.type === "Creature"
},
deriveTerrainDifficulty: () => 1,
}
movementActions.drive = {
label: "TOKEN.MOVEMENT.ACTIONS.drive.label",
icon: "fa-solid fa-car-side",
order: 0,
canSelect: (token) => {
const actor = token.actor | null;
return (actor.type === "Character" && actor.system.itemTypes["Skill"].find(p => p.name === "Fahrzeuge lenken")?.system.taw > 0) || actor.type === "Creature"
},
deriveTerrainDifficulty: () => 1,
}
movementActions.ride = {
label: "TOKEN.MOVEMENT.ACTIONS.ride.label",
icon: "fa-solid fa-horse",
order: 0,
canSelect: (token) => {
const actor = token.actor | null;
return (actor.type === "Character" && actor.system.itemTypes["Skill"].find(p => p.name === "Reiten")?.system.taw > 0) || actor.type === "Creature"
},
deriveTerrainDifficulty: () => 1,
}
movementActions.fly = {
label: "TOKEN.MOVEMENT.ACTIONS.fly.label",
icon: "fa-solid fa-wings",
order: 0,
canSelect: (token) => {
const actor = token.actor | null;
return (actor.type === "Character" && actor.system.itemTypes["Skill"].find(p => p.name === "Fliegen")?.system.taw > 0) || actor.type === "Creature"
},
deriveTerrainDifficulty: () => 1,
}
}
setMovementSpeeds()
console.log("DSA 4.1 is ready for development!")
game.DSA41 = {
...game.DSA41,
...initGlobalAccess()
}
initDocumentClasses(CONFIG)
initUserSettings(game.settings)
initGlobalSettings(game.settings)
initDataModels(CONFIG)
initCombat(CONFIG)
setUpActorSheets(foundry.documents.collections.Actors)
setUpItemSheets(foundry.documents.collections.Items)
loadPartials(foundry.applications.handlebars).then(() => {
foundry.documents.collections.Actors.registerSheet('dsa41.character', CharacterSheet, {
types: ["character"],
makeDefault: true,
})
foundry.documents.collections.Actors.registerSheet('dsa41.creature', CreatureSheet, {
types: ["creature"],
makeDefault: true,
})
foundry.documents.collections.Actors.registerSheet('dsa41.group', GroupSheet, {
types: ["group"],
makeDefault: true,
})
foundry.documents.collections.Items.registerSheet('dsa41.skill', SkillSheet, {
types: ["Skill"],
makeDefault: true,
});
foundry.documents.collections.Items.registerSheet('dsa41.spell', SpellSheet, {
types: ["Spell"],
makeDefault: true,
});
foundry.documents.collections.Items.registerSheet('dsa41.advantage', AdvantageSheet, {
types: ["Advantage"],
makeDefault: true,
})
foundry.documents.collections.Items.registerSheet('dsa41.equipment', EquipmentSheet, {
types: ["Equipment"],
makeDefault: false,
})
foundry.documents.collections.Items.registerSheet('dsa41.liturgy', LiturgySheet, {
types: ["Liturgy"],
makeDefault: true,
})
foundry.documents.collections.Items.registerSheet('dsa41.specialAbility', SpecialAbilitySheet, {
types: ["SpecialAbility"],
makeDefault: true,
})
foundry.documents.collections.Items.registerSheet('dsa41.activeEffect', ActiveEffectSheet, {
types: ['ActiveEffect'],
makeDefault: true,
})
foundry.documents.collections.Items.registerSheet('dsa41.culture', CultureSheet, {
types: ['Culture'],
makeDefault: true,
label: 'DSA41.CultureLabels.Culture'
})
foundry.documents.collections.Items.registerSheet('dsa41.spezien', SpeciesSheet, {
types: ['Species'],
makeDefault: true,
})
foundry.documents.collections.Items.registerSheet('dsa41.profession', ProfessionSheet, {
types: ['Profession'],
makeDefault: true,
})
foundry.documents.collections.Actors.registerSheet('dsa41.merchant', MerchantSheet, {
types: ['Merchant'],
makeDefault: true,
})
initHandlebarHelpers(Handlebars)
game.settings.register('DSA_4-1', 'optional_colorfuldice', {
name: "Optional: Farbige Würfel nach Paramanthus",
hint: "Färbt die Würfel je nach Attribut ein",
scope: "client",
config: true,
type: Boolean,
default: false,
onChange: value => {
},
requiresReload: false
})
game.settings.register('DSA_4-1', 'optional_trefferzonen', {
name: "Optional: Trefferzonen",
hint: "Ersetzt das Wundensystem aus dem BRW durch das Trefferzonensystem aus WdH",
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
})
game.settings.register('DSA_4-1', 'optional_aufstufen_von_liturgien', {
name: "Optional: Aufstufen von Liturgien",
hint: "Aktiviert die Regeln zum Aufstufen von Liturgien",
scope: "world",
config: true,
type: Boolean,
default: false,
disabled: true,
requiresReload: true
})
Handlebars.registerHelper("weight", (data) => {
const baseValue = data * 1000 // to get to gramms (1/1000 Stone)
const stone = Math.floor(baseValue / 1000)
const remainder = baseValue - (stone * 1000)
const ounces = remainder / 25
let stoneRepresentation = ''
let ouncesRepresentation = ''
if (stone > 0) {
stoneRepresentation = `<span class="stone">${stone}</span>`
}
if (ounces > 0) {
ouncesRepresentation = `<span class="ounces">${ounces}</span>`
}
return new Handlebars.SafeString(`<span class="weight">${stoneRepresentation}${ouncesRepresentation}</span>`)
})
Handlebars.registerHelper("fieldTooltip", (...args) => {
const [fieldName, actorId] = args
const actor = game.actors.find(p => p._id === actorId)
let tooltip = ""
if (actor) {
Object.entries(actor.getModificationsOn(fieldName)).forEach(([key, value]) => {
tooltip += `${key}: ${value}<br/>`
})
} else {
tooltip = `${fieldName} not found`
}
return new Handlebars.SafeString(tooltip)
})
Handlebars.registerHelper("currency", (data) => {
// schema for Mittelreich: 1 Ducat = 10 Silver = 100 Kreutzer = 1000 Heller
// internally the price is always given in Silver
// so we need to inflate the value of price by 100 to be able to divide beginning from Heller
const baseValue = data * 100
// then we can regex over it
const currencyRegexp = /(.*)(.)(.)(.)/g
const withDucats = currencyRegexp.exec(baseValue)
let _ = undefined
let ducats = 0
let silver = 0
let kreutzer = 0
let heller = 0
if (withDucats) {
[_, ducats, silver, kreutzer, heller] = withDucats
} else {
const currencyRegexp = /(.)(.)(.)/g
const withSilver = currencyRegexp.exec(baseValue)
if (withSilver) {
[_, silver, kreutzer, heller] = withSilver
} else {
const currencyRegexp = /(.)(.)/g
const withKreutzer = currencyRegexp.exec(baseValue)
if (withKreutzer) {
[_, kreutzer, heller] = withKreutzer
} else {
heller = baseValue
}
}
}
let str = `<span class='coins' data-tooltip="${ducats > 0 ? ducats + ' Dukaten ' : ''}${silver > 0 ? silver + ' Silbertaler ' : ''}${kreutzer > 0 ? kreutzer + ' Kreuzer ' : ''}${heller > 0 ? heller + ' Heller' : ''}">`
if (ducats > 0) {
str += ducats + "<i class='symbol ducat'></i>"
}
if (silver > 0) {
str += silver + "<i class='symbol silver'></i>"
}
if (kreutzer > 0) {
str += kreutzer + "<i class='symbol kreutzer'></i>"
}
if (heller > 0) {
str += heller + "<i class='symbol heller'></i>"
}
str = str + "</span>"
return new Handlebars.SafeString(str)
})
return preloadHandlebarsTemplates();
})
game.DSA41 = {}
initSocketLib(game.DSA41)
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) => {
return createTalentMacro(data, slot)
});
});
Hooks.on("getActorContextOptions", (application, menuItems) => {
menuItems.push({
@ -46,7 +404,44 @@ Hooks.on("getActorContextOptions", (application, menuItems) => {
callback: (li) => {
const actorId = li.getAttribute("data-entry-id")
const actor = game.actors.get(actorId)
//actor.import()
new XmlImportDialog(actor).render(true)
}
})
})
async function createTalentMacro(data, slot) {
if (data.type !== "Item") return;
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("${data.uuid}");`;
const macro = await Macro.create({
name: item.name,
type: "script",
img: item.img,
command: command,
flags: {"dsa41.skillMacro": true}
});
game.user.assignHotbarMacro(macro, slot);
return false;
}
function rollItemMacro(_uuid) {
const speaker = ChatMessage.getSpeaker();
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}`);
return item.system.roll();
}

View File

@ -1,4 +1,4 @@
import BaseItem from "./baseItem.mjs";
import BaseItem from "./base-item.mjs";
const {ArrayField, BooleanField, NumberField, AnyField, StringField, HTMLField} = foundry.data.fields;

View File

@ -6,8 +6,7 @@ export const ATTRIBUTE = {
"ff": "Fingerfertigkeit",
"ge": "Gewandtheit",
"ko": "Konstitution",
"kk": "Körperkraft",
"UNKNOWN": "Fehlende Variante"
"kk": "Körperkraft"
}
export const ATTRIBUTE_DESCRIPTIONS = {

View File

@ -138,10 +138,6 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
key: new StringField(),
notiz: new StringField(),
})),
erschoepfung: new SchemaField({ // only with DSA_4-1.optional_erschoepfung
max: new NumberField({required: true, integer: true}),
aktuell: new NumberField({required: true, integer: true}),
}),
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

View File

@ -1,9 +1,6 @@
import BaseItem from "./baseItem.mjs";
import BaseItem from "./base-item.mjs";
const {
StringField,
HTMLField
} = foundry.data.fields;
const {BooleanField, StringField, HTMLField} = foundry.data.fields;
export class CultureDataModel extends BaseItem {

View File

@ -1,35 +0,0 @@
import BaseItem from "./baseItem.mjs";
import {Liturgy} from "../documents/liturgy.mjs";
const {
ArrayField,
NumberField,
StringField,
HTMLField,
SchemaField,
DocumentIdField,
} = foundry.data.fields;
export class DeityDataModel extends BaseItem {
static defineSchema() {
return {
alias: new ArrayField(new StringField()),
description: new HTMLField(),
miracleMinus: new ArrayField(new StringField()),
miraclePlus: new ArrayField(new StringField()),
karmicEnergy: new NumberField({integer: true}),
liturgies: new SchemaField({
rank0: new ArrayField(new StringField()),
rank1: new ArrayField(new StringField()),
rank2: new ArrayField(new StringField()),
rank3: new ArrayField(new StringField()),
rank4: new ArrayField(new StringField()),
rank5: new ArrayField(new StringField()),
rank6: new ArrayField(new StringField()),
rank7: new ArrayField(new StringField()),
rank8: new ArrayField(new StringField()),
}),
}
}
}

View File

@ -1,11 +1,8 @@
import BaseItem from "./baseItem.mjs";
import BaseItem from "./base-item.mjs";
import {Equipment} from "../documents/equipment.mjs";
const {
ArrayField,
SchemaField,
NumberField,
StringField,
HTMLField
ArrayField, EmbeddedCollectionField, SchemaField, NumberField, StringField, HTMLField
} = foundry.data.fields;
export class EquipmentDataModel extends BaseItem {

View File

@ -3,8 +3,10 @@ const {
ObjectField,
NumberField,
StringField,
EmbeddedDocumentField,
DocumentIdField,
ArrayField,
ForeignDocumentField
} = foundry.data.fields;
export class GroupDataModel extends foundry.abstract.TypeDataModel {

View File

@ -1,12 +1,6 @@
import BaseItem from "./baseItem.mjs";
import BaseItem from "./base-item.mjs";
const {
NumberField,
SchemaField,
ArrayField,
StringField,
HTMLField
} = foundry.data.fields;
const {BooleanField, NumberField, SchemaField, ArrayField, StringField, HTMLField} = foundry.data.fields;
export class LiturgyDataModel extends BaseItem {
@ -23,14 +17,14 @@ export class LiturgyDataModel extends BaseItem {
wirkungsdauer: new StringField(),
zauberdauer: new StringField(),
auswirkung: new SchemaField({
I: new HTMLField(),
II: new HTMLField(),
III: new HTMLField(),
IV: new HTMLField(),
V: new HTMLField(),
VI: new HTMLField(),
VII: new HTMLField(),
VIII: new HTMLField(),
I: new StringField(),
II: new StringField(),
III: new StringField(),
IV: new StringField(),
V: new StringField(),
VI: new StringField(),
VII: new StringField(),
VIII: new StringField(),
})
}

View File

@ -1,9 +1,11 @@
const {
SchemaField,
NumberField,
ObjectField,
StringField,
HTMLField,
FilePathField,
DocumentIdField,
ArrayField,
} = foundry.data.fields;

View File

@ -165,6 +165,7 @@ export class LiturgyData {
if (found) {
durationText = this.#ranks[currentDuration].duration
console.log({currentDuration, durationText, adjustedDurationText})
return {currentDuration, durationText, adjustedDurationText}
}

View File

@ -1,10 +1,6 @@
import BaseItem from "./baseItem.mjs";
import BaseItem from "./base-item.mjs";
const {
BooleanField,
StringField,
HTMLField
} = foundry.data.fields;
const {BooleanField, StringField, HTMLField} = foundry.data.fields;
export class ProfessionDataModel extends BaseItem {

View File

@ -1,7 +1,7 @@
import BaseItem from "./baseItem.mjs";
import {evaluateRoll} from "../globals/DSARoll.mjs";
import BaseItem from "./base-item.mjs";
const {
BooleanField,
DocumentIdField,
ArrayField,
NumberField,
@ -131,7 +131,7 @@ export class SkillDataModel extends BaseItem {
let evaluated1 = (await roll1.evaluate())
const dsaDieRollEvaluated = evaluateRoll(evaluated1.terms[0].results, {
const dsaDieRollEvaluated = this._evaluateRoll(evaluated1.terms[0].results, {
taw: this.taw,
werte: [this.probe[0], this.probe[1], this.probe[2]],
})
@ -151,4 +151,37 @@ export class SkillDataModel extends BaseItem {
}
}
_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,
}
}
}

View File

@ -1,12 +1,14 @@
import BaseItem from "./baseItem.mjs";
import BaseItem from "./base-item.mjs";
const {
AnyField,
BooleanField,
NumberField,
SchemaField,
ArrayField,
StringField,
HTMLField,
ObjectField
} = foundry.data.fields;
export class SpecialAbilityDataModel extends BaseItem {
@ -33,7 +35,6 @@ export class SpecialAbilityDataModel extends BaseItem {
}))
}),
),
gruppe: new StringField(),
seite: new NumberField(),
aktionsText: new HTMLField(),
text: new HTMLField(),

View File

@ -1,12 +1,6 @@
import BaseItem from "./baseItem.mjs";
import BaseItem from "./base-item.mjs";
const {
ArrayField,
SchemaField,
NumberField,
StringField,
HTMLField
} = foundry.data.fields;
const {BooleanField, ArrayField, SchemaField, NumberField, StringField, HTMLField} = foundry.data.fields;
export class SpeciesDataModel extends BaseItem {

View File

@ -1,14 +1,6 @@
import BaseItem from "./baseItem.mjs";
import BaseItem from "./base-item.mjs";
const {
SchemaField,
BooleanField,
NumberField,
ArrayField,
StringField,
HTMLField,
ObjectField,
} = foundry.data.fields;
const {BooleanField, NumberField, SchemaField, ArrayField, StringField, HTMLField} = foundry.data.fields;
export class SpellDataModel extends BaseItem {
@ -21,23 +13,9 @@ export class SpellDataModel extends BaseItem {
probeMod: new StringField(),
hauszauber: new BooleanField(),
technik: new StringField(),
zauberdauer: new SchemaField({
min: new StringField(),
normal: new StringField(),
additionalFormula: new StringField(),
variables: new ArrayField(new StringField()),
additionalFormulaTimeUnit: new StringField(),
}),
wirkung: new HTMLField(),
kosten: new ArrayField(
new SchemaField({
min: new NumberField(),
cost: new NumberField(),
additionalFormula: new StringField(),
variables: new ArrayField(new StringField()),
repräsentation: new StringField()
})
),
zauberdauer: new StringField(),
wirkung: new StringField(),
kosten: new StringField(),
zielobjekt: new StringField(),
reichweite: new StringField({required: true}),
wirkungsdauer: new StringField({required: true}),
@ -46,14 +24,8 @@ export class SpellDataModel extends BaseItem {
antimagie: new StringField(),
merkmal: new StringField(),
komplexität: new StringField(),
repräsentation: new ObjectField(),
info: new StringField(),
varianten: new ArrayField(new SchemaField({
name: new StringField(),
description: new HTMLField(),
mod: new StringField(),
limit: new NumberField(),
}))
repräsentation: new StringField(),
info: new StringField()
}
}

View File

@ -1,91 +0,0 @@
export const leadingAttribute = {
"Alchimist": "KL",
"Borbaradianer": "KL",
"Druide": "KL",
"Geode (Herren der Erde)": "KL",
"Magier": "KL",
"Scharlatane": "KL",
"Zibilijas": "KL",
"Achaz": "IN",
"Derwisch": "IN",
"Durro-Dûn": "IN",
"Elfe": "IN",
"Ferkina": "IN",
"Geode (Diener Sumus)": "IN",
"Hexe": "IN",
"Schamane": "IN",
"Schelm": "IN",
"Zaubertänzer": "IN"
}
/*
die ohne ZfP Kosten mussen vorher ausgewählt werden
*/
/**
*
* @type {[String: {name: string, description: string, modFn: string, castTimeModFn: string, costModFn: string}]}
*/
export const spoModData = {
"Veränderte Technik": {
name: "Veränderte Technik",
description: "Verändert die Technik",
modFn: "mod -7",
castTimeModFn: "castTime +3",
costModFn: "cost",
},
"Veränderte Technik, zentral": {
name: "Veränderte Technik, zentral",
description: "Verändert die Technik",
modFn: "mod -12",
castTimeModFn: "castTime +3",
costModFn: "cost",
},
"Halbierte Zauberdauer": {
name: "Halbierte Zauberdauer",
description: "Halbiert die Zauberdauer für eine Erschwernis von 5",
modFn: "mod -5",
castTimeModFn: "castTime / 2",
costModFn: "cost",
},
"Verdoppelte Zauberdauer": {
name: "Verdoppelte Zauberdauer",
description: "Verdoppelt die Zauberdauer für eine Erleichterung von 3",
modFn: "mod +3",
castTimeModFn: "castTime *2",
costModFn: "cost",
},
"Erzwingen": {
name: "Erzwingen",
description: "Verringert Erschwernis um 1 je quadrierten AsP Punkt",
modFn: "mod +1",
castTimeModFn: "castTime +1",
costModFn: "cost ** cost",
},
"Kosten einsparen": {
name: "Kosten einsparen",
description: "Reduziert die Kosten des Zaubers um 10% für jede zusätzlich aufgewendete Aktion",
modFn: "mod -3",
castTimeModFn: "castTime +1",
costModFn: "cost - Math.max(cost * 0.1, 1)", // at least a reduction of 1 AsP
},
// more to come
"Vergrößerung von Reichweite oder Wirkungsradius": {
name: "Vergrößerung von Reichweite oder Wirkungsradius",
description: "Vergrößert die Reichweite oder wenn möglich den Wirkungsradius auf kosten von Aktionen",
modFn: "mod -5",
castTimeModFn: "castTime +1",
costModFn: "cost",
},
"Verkleinerung von Reichweite oder Wirkungsradius": {
name: "Verkleinerung von Reichweite oder Wirkungsradius",
description: "Verkleinert die Reichweite oder wenn möglich den Wirkungsradius auf kosten von Aktionen",
modFn: "mod -3",
castTimeModFn: "castTime +1",
costModFn: "cost",
},
}

View File

@ -1,15 +1,8 @@
import BaseItem from "./baseItem.mjs";
import BaseItem from "./base-item.mjs";
const {
ArrayField,
SchemaField,
BooleanField,
NumberField,
StringField,
HTMLField
} = foundry.data.fields;
const {ArrayField, SchemaField, BooleanField, NumberField, StringField, HTMLField} = foundry.data.fields;
export class AdvantageDataModel extends BaseItem {
export class VornachteileDataModel extends BaseItem {
static defineSchema() {
return {
@ -28,7 +21,7 @@ export class AdvantageDataModel extends BaseItem {
auswahl: new ArrayField(
new SchemaField({
name: new StringField(),
requirements: new ArrayField(
requirement: new ArrayField(
new SchemaField({
attribute: new StringField(),
minValue: new NumberField(),

View File

@ -1,9 +1,8 @@
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
import {Talent} from "../data/talent.mjs";
import {ATTRIBUTE, ATTRIBUTE_DESCRIPTIONS} from "../data/attribute.mjs";
const {
ApplicationV2,
HandlebarsApplicationMixin
} = foundry.applications.api
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
export class AttributeDialog extends HandlebarsApplicationMixin(ApplicationV2) {

View File

@ -1,9 +1,7 @@
import {ActionManager} from "../sheets/actions/action-manager.mjs";
import {Talent} from "../data/talent.mjs";
const {
ApplicationV2,
HandlebarsApplicationMixin
} = foundry.applications.api
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
/**
@ -181,7 +179,7 @@ export class BattleDialog extends HandlebarsApplicationMixin(ApplicationV2) {
async _prepareContext(options) {
const context = await super._prepareContext(options)
context.actors = game.actors.filter(actor => actor.type === "Character" || actor.type === "Creature")
context.actors = game.actors.filter(actor => actor.type === "character" || actor.type === "creature")
context.offenseTalent = this._offenseTalent ?? ''
context.offenseTalents = {}

View File

@ -1,9 +1,6 @@
import {ActionManager} from "../sheets/actions/action-manager.mjs";
const {
ApplicationV2,
HandlebarsApplicationMixin
} = foundry.applications.api
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
/**
@ -50,60 +47,40 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
*/
_actor = null
constructor(actor, data) {
constructor(actor) {
super();
this._actor = actor
this._targetId = null
this._skillId = data.skill ? data.skill : null
this._weaponId = data.weapon ? data.weapon : null
this._skillId = null
this._weaponId = null
this._defenseManeuverId = null
this._actionManager = new ActionManager(this._actor)
CombatActionDialog._instance = this
}
static _instance = null
async #processOnSelectTarget(event, target) {
static async #onSelectTarget(event, target) {
const {targetId} = target.dataset
this._targetId = this._targetId === targetId ? null : targetId
this.render({parts: ["form"]})
}
static async #onSelectTarget(event, target) {
event.preventDefault()
CombatActionDialog._instance.#processOnSelectTarget(event, target)
}
async #processOnSelectManeuver(event, target) {
static async #onSelectManeuver(event, target) {
const {maneuverId} = target.dataset
this._defenseManeuverId = this._defenseManeuverId === maneuverId ? null : maneuverId
this.render({parts: ["form"]})
}
static async #onSelectManeuver(event, target) {
event.preventDefault()
CombatActionDialog._instance.#processOnSelectManeuver(event, target)
}
async #processOnSelectWeaponAndSkill(event, target) {
static async #onSelectWeaponAndSkill(event, target) {
const {weaponId, skillId} = target.dataset
this._weaponId = this._weaponId === weaponId ? null : weaponId
this._skillId = this._skillId === skillId ? null : skillId
this.render({parts: ["form"]})
}
static async #onSelectWeaponAndSkill(event, target) {
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
CombatActionDialog._instance.#processOnSelectWeaponAndSkill(event, target)
}
async #processOnSubmitForm(event, form, formData) {
const maneuver = CombatActionDialog._instance.#evaluateManeuvers().find(p => p.id === this._defenseManeuverId)
const maneuver = this.#evaluateManeuvers().find(p => p.id === this._defenseManeuverId)
const weapon = this._actor.itemTypes["Equipment"].find(p => p._id === this._weaponId)
const skill = this._actor.itemTypes["Skill"].find(p => p._id === this._skillId)
const target = game.actors.get(game.scenes.current.tokens.find(p => p._id === this._targetId).actorId)
@ -112,8 +89,7 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
weapon: this._weaponId,
skill: this._skillId,
target: this._targetId,
title: maneuver.name,
maneuver: maneuver.activate.toString(),
maneuver,
mod: this._mod,
circumstance: this._circumstance,
penalty: this._penalty,
@ -126,14 +102,12 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
/** @type Cooldown */
const newCooldown = {
start: maneuver.cooldown({weapon, skill, target, mod: this._mod}),
current: 0,
current: maneuver.cooldown({weapon, skill, target, mod: this._mod}),
data: {
cssClass: "Kampf",
weapon: this._weaponId,
skill: this._skillId,
target: this._targetId,
title: maneuver.name,
maneuver: maneuver.activate.toString(),
maneuver,
mod: this._mod,
circumstance: this._circumstance,
penalty: this._penalty,
@ -152,12 +126,6 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
}
}
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
CombatActionDialog._instance.#processOnSubmitForm(event, form, formData)
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
if (options.window) {
@ -288,9 +256,9 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
const context = await super._prepareContext(options)
context.actor = this._actor
context.distanceUnit = game.scenes.current?.grid.units
context.distanceUnit = game.scenes.current.grid.units
if (context.distanceUnit && this._actor.getActiveTokens()[0]?.id) {
if (this._actor.getActiveTokens()[0]?.id) {
context.tokenDistances = this.#evaluateDistances()
context.weapons = this.#evaluateWeapons()
@ -307,28 +275,7 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
// TODO get W/M of weapon NOW
if (this._targetNumber >= 0 && this._targetId && this._weaponId && this._skillId && maneuver) {
context.ready = true
} else {
context.notReadyReason = `<em>${game.i18n.format("COMBAT_DIALOG.notReadyReason.title")}</em><ul>`
if (!this._targetId) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noTarget")}</li>`
}
if (!this._weaponId) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noWeapon")}</li>`
}
if (!this._skillId) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noSkill")}</li>`
}
if (!maneuver) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noManeuver")}</li>`
}
if (!this._targetNumber < 0) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.impossible")}</li>`
}
context.notReadyReason += "</ul>"
context.ready = false
}
context.ready = this._targetId && this._weaponId && this._skillId && this._defenseManeuverId
return context
} else {
ui.notifications.error(`Feature funktioniert nur wenn der Akteur ein Token auf der aktuellen Szene hat`);
@ -356,10 +303,12 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
target.textContent = `(${result})`
targetDescription.textContent = this._modDescription
if (result <= 0 || !context.ready) {
if (result <= 0) {
context.ready = false
this.element.querySelector(".actions button").classList.remove("ready")
this.element.querySelector(".actions button").setAttribute("disabled", true)
} else {
context.ready = true
this.element.querySelector(".actions button").classList.add("ready")
this.element.querySelector(".actions button").removeAttribute("disabled")
}

View File

@ -1,9 +1,6 @@
import {ActionManager} from "../sheets/actions/action-manager.mjs";
const {
ApplicationV2,
HandlebarsApplicationMixin
} = foundry.applications.api
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
/**
@ -49,16 +46,16 @@ export class DefenseActionDialog extends HandlebarsApplicationMixin(ApplicationV
*/
_actor = null
constructor(actor, data, attackData) {
constructor(actor, attackData) {
super();
/*this._attackData = attackData ?? {
this._attackData = attackData ?? {
modToDefense: 0,
attacker: null,
weapon: null, // is important to note as weapons like Chain Weapons or Flails can ignore Shields
}*/
}
this._actor = actor
this._skillId = data.skill ? data.skill : null
this._weaponId = data.weapon ? data.weapon : null
this._skillId = null
this._weaponId = null
this._defenseManeuverId = null
this._actionManager = new ActionManager(this._actor)
//if (this._actor) {
@ -254,26 +251,7 @@ export class DefenseActionDialog extends HandlebarsApplicationMixin(ApplicationV
// TODO get W/M of weapon NOW
if (this._weaponId && this._skillId && this._defenseManeuverId) {
context.ready = true
} else {
context.notReadyReason = `<em>${game.i18n.format("COMBAT_DIALOG.notReadyReason.title")}</em><ul>`
if (!this._weaponId) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noWeapon")}</li>`
}
if (!this._skillId) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noSkill")}</li>`
}
if (!maneuver) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.noManeuver")}</li>`
}
if (!this._targetNumber < 0) {
context.notReadyReason += `<li>${game.i18n.format("COMBAT_DIALOG.notReadyReason.impossible")}</li>`
}
context.notReadyReason += "</ul>"
context.ready = false
}
context.ready = this._targetId && this._weaponId && this._skillId && this._defenseManeuverId
return context
} else {
ui.notifications.error(`Feature funktioniert nur wenn der Akteur ein Token auf der aktuellen Szene hat`);
@ -301,10 +279,12 @@ export class DefenseActionDialog extends HandlebarsApplicationMixin(ApplicationV
target.textContent = `(${result})`
targetDescription.textContent = this._modDescription
if (result <= 0 || !context.ready) {
if (result <= 0) {
context.ready = false
this.element.querySelector(".actions button").classList.remove("ready")
this.element.querySelector(".actions button").setAttribute("disabled", true)
} else {
context.ready = true
this.element.querySelector(".actions button").classList.add("ready")
this.element.querySelector(".actions button").removeAttribute("disabled")
}

View File

@ -1,243 +0,0 @@
import {Equipment} from "../documents/equipment.mjs";
const {
ApplicationV2,
HandlebarsApplicationMixin
} = foundry.applications.api
export class ItemBrowserDialog extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
classes: ['dsa41', 'dialog', 'item-browser'],
tag: "form",
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: ItemBrowserDialog.#onSubmitForm
},
position: {
width: 640,
height: 480
},
window: {
resizable: true,
title: "Gegenstände Browser"
},
actions: {
select: ItemBrowserDialog.#selectItem,
buy: ItemBrowserDialog.#buyItem
}
}
static PARTS = {
form: {
template: 'systems/DSA_4-1/templates/dialog/item-browser-dialog.hbs',
}
}
/**
* @type {Actor}
* @private
*/
_actor = null
/**
*
* @type {[Equipment]}
* @private
*/
_items = []
_selectedItem = null
filter_price_lower = 0
filter_price_upper = 0
filter_weight_lower = 0
filter_weight_upper = 0
filter_name = ""
filter_category = ""
constructor(actor) {
super();
this._actor = actor
this._items = []
this._selectedItem = null
}
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
this.filter_price_lower = formData.object.filter_price_lower
this.filter_price_upper = formData.object.filter_price_upper
this.filter_weight_lower = formData.object.filter_weight_lower
this.filter_weight_upper = formData.object.filter_weight_upper
this.filter_name = formData.object.filter_name
this.filter_category = formData.object.filter_category
this.render({parts: ["form"]})
}
static async #selectItem(event, target) {
const {itemId} = target.dataset
const selectedItem = this._items.find(item => item.uuid === itemId)
this._items?.forEach((item) => {
item.selected = item.uuid === itemId
})
if (selectedItem) {
this._selectedItem = selectedItem
}
this.render({parts: ["form"]})
}
static async #buyItem(event, target) {
if (this._actor && this._selectedItem) {
const canBuy = await this._actor.reduceWealth(this._selectedItem.price)
if (canBuy) {
const document = await foundry.utils.fromUuid(this._selectedItem.uuid)
if (document) {
await this._actor.createEmbeddedDocuments("Item", [document])
ui.notifications.info(this._selectedItem.name + " wurde von " + this._actor.name + " gekauft")
}
} else {
ui.notifications.error(this._selectedItem.name + " ist zu teuer für " + this._actor.name)
}
}
}
_canDragDrop(event, options) {
return game.user.isGM
}
_canDrag(event, options) {
return true
}
/**
* An event that occurs when a drag workflow begins for a draggable item on the sheet.
* @param {DragEvent} event The initiating drag start event
* @returns {Promise<void>}
* @protected
*/
async _onDragStart(event) {
const target = event.currentTarget;
let dragData;
if (target.dataset.itemId) {
dragData = {
type: "Item",
uuid: target.dataset.itemId
}
}
// Set data transfer
if (!dragData) return;
event.dataTransfer.setData("text/plain", JSON.stringify(dragData));
}
async _prepareContext(options) {
const context = await super._prepareContext(options)
context.categories = {
"": "",
"Gegenstand": "Gegenstand",
"Nahkampfwaffe": "Nahkampfwaffe",
"Fernkampfwaffe": "Fernkampfwaffe",
"Munition": "Munition",
"Währung": "Währung"
}
context.filterName = this.filter_name
context.filterCategory = this.filter_category
context.filter_price_lower = this.filter_price_lower ?? this._minPrice
context.filter_price_upper = this.filter_price_upper ?? this._maxPrice
context.filter_weight_lower = this.filter_weight_lower ?? this._minWeight
context.filter_weight_upper = this.filter_weight_upper ?? this._maxWeight
context.price_lower = this._minPrice
context.price_upper = this._maxPrice
context.weight_lower = this._minWeight
context.weight_upper = this._maxWeight
context.hasSelectedItem = this._selectedItem != null
context.items = this._items
?.filter(p => p.name.toLowerCase().indexOf(context.filterName.toLowerCase()) !== -1 || context.filterName === "")
?.filter(p => p.category.indexOf(context.filterCategory) !== -1 || context.filterCategory === "")
?.filter(p => Number(context.filter_price_lower) <= p.price && p.price <= Number(context.filter_price_upper))
?.filter(p => Number(context.filter_weight_lower) <= p.weight && p.weight <= Number(context.filter_weight_upper))
return context
}
#updateProgress(compendiumName, current, max) {
if (compendiumName && current && max) {
this.element.querySelector('.progress').style.display = 'block';
this.element.querySelector('.progress .fill').style.width = (current / max * 100) + "%";
this.element.querySelector('.progress .text').textContent = game.i18n.format("ITEM_BROWSER.progress", {
compendium: compendiumName,
current: current,
max: max
})
} else {
this.element.querySelector('.progress').style.display = 'none';
}
}
async _onRender(context, options) {
if (this._items.length === 0) {
const compendia = [
game.packs.get('DSA_4-1.Armor'),
game.packs.get('DSA_4-1.Weapons'),
game.packs.get('DSA_4-1.Ammunition'),
game.packs.get('DSA_4-1.Items')
]
let totalEntries = compendia.reduce((p, c) => p + c.index.size, 0)
let parsedEntries = 0
let currentCompendiumName = ""
for (const c of compendia) {
const it = await c.getDocuments()
currentCompendiumName = c.metadata.label
it.forEach((item) => {
const uuid = item.uuid
const e = new Equipment(item)
this._items.push({
img: item.img,
uuid,
type: item.type,
name: item.name,
price: e.system.price,
weight: e.system.weight,
category: e.system.category.join(", "),
selected: false,
})
parsedEntries += 1
this.#updateProgress(currentCompendiumName, parsedEntries, totalEntries)
})
}
this._minPrice = Math.min(...this._items.map(item => item.price))
this._maxPrice = Math.max(...this._items.map(item => item.price))
this._minWeight = Math.min(...this._items.map(item => item.weight))
this._maxWeight = Math.max(...this._items.map(item => item.weight))
this.#updateProgress()
this.render({parts: ["form"]})
}
new foundry.applications.ux.DragDrop.implementation({
dropSelector: ".window-content",
dragSelector: ".item",
permissions: {
drag: this._canDrag.bind(this)
},
callbacks: {
dragstart: this._onDragStart.bind(this),
}
}).bind(this.element)
}
}

View File

@ -1,10 +1,7 @@
import {LiturgyData} from "../data/miracle/liturgyData.mjs";
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
import {Talent} from "../data/talent.mjs";
const {
ApplicationV2,
HandlebarsApplicationMixin
} = foundry.applications.api
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
export class LiturgyDialog extends HandlebarsApplicationMixin(ApplicationV2) {
@ -138,55 +135,10 @@ export class LiturgyDialog extends HandlebarsApplicationMixin(ApplicationV2) {
const castingTime = this.#normalizeCastingTime(this._liturgy)
//TODO push it into the sun eeerh cooldown queue
if (castingTime > 0) {
const cooldowns = this._actor.system.cooldowns
let m = (queue, data) => {
new game.DSA41.Talent({
name: data.title,
taw: data.taw,
mod: data.mod,
eigenschaften: {
mu: data.eigenschaften.mu,
in: data.eigenschaften.in,
ch: data.eigenschaften.ch,
},
eigenschaft1: "mu",
eigenschaft2: "in",
eigenschaft3: "ch"
}).evaluate("publicroll").then(result => {
//if (castingTime > 0) {
result.evaluatedRoll.toMessage({
speaker: ChatMessage.getSpeaker({actor: game.actors.get(data.actorId)}),
flavor: `Liturgie: ${data.title}<br/>LkP*: ${result.tap}<br/>${result.meisterlich ? "Meisterlich" : ""}${result.patzer ? "Petzer" : ""}<br/>${data.variant}`,
})
})
}
cooldowns.push({
start: castingTime,
current: 0,
data: {
cssClass: "Karmal",
title: this._liturgy.name,
taw: lkp,
mod: mod,
actorId: this._actor._id,
variant: this._variation.effect,
eigenschaften: {
mu: this._actor.system.attribute.mu.aktuell,
in: this._actor.system.attribute.in.aktuell,
ch: this._actor.system.attribute.ch.aktuell,
},
eigenschaft1: "mu",
eigenschaft2: "in",
eigenschaft3: "ch",
circumstance: circumstance,
maneuver: m.toString()
}
})
await this._actor.update({"system.cooldowns": cooldowns})
ui.notifications.info(`Neue Aktion für ${this._liturgy.name} mit Abklingzeit von ${castingTime} Aktionen hinzugefügt`);
} else {
// this._actor.system.cooldowns.push()
//} else {
const result = await new Talent({
name: this._liturgy.name,
taw: lkp,
@ -205,7 +157,7 @@ export class LiturgyDialog extends HandlebarsApplicationMixin(ApplicationV2) {
speaker: ChatMessage.getSpeaker({actor: this._actor}),
flavor: `Liturgie: ${this._liturgy.name}<br/>Zauberdauer: ${castingTime > 0 ? castingTime + " Aktionen" : resultingLiturgy.castduration}<br/>LkP*: ${result.tap}<br/>${result.meisterlich ? "Meisterlich" : ""}${result.patzer ? "Petzer" : ""}<br/>${this._variation.effect}`,
})
}
//}
this.close()
}
@ -223,7 +175,7 @@ export class LiturgyDialog extends HandlebarsApplicationMixin(ApplicationV2) {
const [_, actions] = castingTime.match(stoßgebetRegExp)
return actions
} else if (castingTime.match(gebetRegExp)) {
const [_, actions] = castingTime.match(gebetRegExp)
const [_, actions] = castingTime.match(stoßgebetRegExp)
return actions * 20
} else if (castingTime.match(invalidForCooldownRegExp)) {
return -1

View File

@ -1,7 +1,6 @@
const {
ApplicationV2,
HandlebarsApplicationMixin
} = foundry.applications.api
import {XmlImport} from "../xml-import/xml-import.mjs";
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
export class RestingDialog extends HandlebarsApplicationMixin(ApplicationV2) {
@ -99,7 +98,6 @@ export class RestingDialog extends HandlebarsApplicationMixin(ApplicationV2) {
lepMod: [-1, -2, -3, -4, -5],
display: "range",
noLabel: "Gutes Wetter",
nestingLevel: 1,
labels: [
"Schlechtes Wetter I",
"Schlechtes Wetter II",
@ -115,7 +113,6 @@ export class RestingDialog extends HandlebarsApplicationMixin(ApplicationV2) {
aspMod: -1,
lepMod: -1,
display: "boolean",
nestingLevel: 1,
group: "bad_camp",
active: false,
value: "on"
@ -222,14 +219,6 @@ export class RestingDialog extends HandlebarsApplicationMixin(ApplicationV2) {
const elementInMod = this.element.querySelector('output[name="inMod"]')
const elementWoundMod = this.element.querySelector('output[name="woundMod"]')
if (this.restingType === this.#type.DRAUßEN) {
this.element.querySelector('input[name="bad_weather"]').removeAttribute('disabled')
this.element.querySelector('input[name="bad_camp"]').removeAttribute('disabled')
} else {
this.element.querySelector('input[name="bad_weather"]').setAttribute('disabled', 'disabled')
this.element.querySelector('input[name="bad_camp"]').setAttribute('disabled', 'disabled')
}
const context = this.#updateData()
elementLepMod.value = context.lepModDisplay
elementKoMod.value = context.koRollDisplay
@ -344,6 +333,8 @@ export class RestingDialog extends HandlebarsApplicationMixin(ApplicationV2) {
context.inRollDisplay = `1w20+${this.regInMod}`
}
console.log(this, context)
return context
}

View File

@ -1,584 +0,0 @@
import {ATTRIBUTE} from "../data/attribute.mjs";
import {spoModData, leadingAttribute} from "../data/spellData/spellData.mjs";
import {evaluateRoll} from "../globals/DSARoll.mjs";
const {
ApplicationV2,
HandlebarsApplicationMixin
} = foundry.applications.api
export class SpellDialog extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
classes: ['dsa41', 'dialog', 'spell'],
tag: "form",
position: {
width: 480,
height: 800
},
window: {
resizable: false,
title: "Zauber wirken"
},
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: SpellDialog.#onSubmitForm
},
actions: {
cast: SpellDialog.#cast,
diceRoll: SpellDialog.#diceRoll
}
}
static PARTS = {
form: {
template: 'systems/DSA_4-1/templates/dialog/spell-dialog.hbs',
}
}
static data = {}
/**
*
* @type {Actor}
* @private
*/
_actor = null
_costMutators = {}
_castTimeMutators = {}
_variants = {}
_costModel = {}
_castTimeModel = {}
_spoMods = {}
displayModResult = 0
constructor(actor, spellId) {
super()
this._actor = actor
this._spell = this._actor.itemTypes["Spell"].find(p => p._id === spellId)
this._circumstance = 0
this._mods = []
this.mod = 0
this._costMutators = {}
this._castTimeMutators = {}
this._selectedRepresentation = this._spell.getFlag("DSA_4-1", "representation")
this._spellDie = null
this._variants = {}
this._costModel = this._spell.system.kosten.find(c => c.repräsentation === context.selectedRepresentation) ?? this._spell.system.kosten.find(c => c.repräsentation === "")
this._castTimeModel = this._spell.system.zauberdauer
this._castTimeMutators = {}
this._costMutators = {}
this._costModel.variables.forEach(v => this._costMutators[v] = 0)
this._castTimeModel.variables.forEach(v => this._castTimeMutators[v] = 0)
this.cost = this.normalizeCastingCost() ?? 0
this.castingTime = this.#normalizeCastingTime(this._spell)
this.zfp = null
this.zfpDetermined = false
if (this._selectedRepresentation) {
this._costModel = this._spell.system.kosten.find(c => c.repräsentation === context.selectedRepresentation) ?? this._spell.system.kosten.find(c => c.repräsentation === "")
this._castTimeModel = this._spell.system.zauberdauer
this._castTimeMutators = {}
this._costMutators = {}
this._costModel.variables.forEach(v => this._costMutators[v] = 0)
this._castTimeModel.variables.forEach(v => this._castTimeMutators[v] = 0)
}
}
/**
* @typedef FormulaData
* @property {String} additionalFormula mathematical expression that can be eval'd by replacing the variables with the user added input
* @property {[String]} variables contains all tokens which will be replaceable inside the formula
* @property {[String: Number]} substitutions user input with which the variables with the same key will be replaced in the formula text
* @property {"Aktionen"|"SR"} unit gives the evaluated formula its appropriate time unit
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
// handle changes in variable Inputs
this._selectedRepresentation = formData.object.representation ?? this._selectedRepresentation
this._variants = foundry.utils.expandObject(formData.object)["variants"] ?? this._variants
if (this._spell.system.probe.includes("*")) { // ATTRIBUTO
if (this._variants["Mut"]) {
this._spellDie = "MU"
} else if (this._variants["Klugheit"]) {
this._spellDie = "KL"
} else if (this._variants["Intuition"]) {
this._spellDie = "IN"
} else if (this._variants["Charisma"]) {
this._spellDie = "CH"
} else if (this._variants["Fingerfertigkeit"]) {
this._spellDie = "FF"
} else if (this._variants["Gewandtheit"]) {
this._spellDie = "GE"
} else if (this._variants["Konstitution"]) {
this._spellDie = "KO"
} else if (this._variants["Konstitution"]) {
this._spellDie = "KK"
} else {
this._spellDie = null
}
}
let costMutators = foundry.utils.expandObject(formData.object)["costMutators"] ?? this._costMutators
if (costMutators) {
this._costMutators = costMutators
}
this.cost = this.normalizeCastingCost()
let castTimeMutators = foundry.utils.expandObject(formData.object)["castTimeMutators"] ?? this._castTimeMutators
this._castTimeMutators = castTimeMutators
this.mod = 0
this._activeVariants = Object.entries(this._variants)
.filter(([key, truthiness]) => truthiness)
.map(([key, truthiness]) => this._spell.system.varianten.find(v => v.name === key))
this._activeVariants.forEach(variant => {
if (variant.mod) {
this.mod += Number(variant.mod)
}
})
this.castingTime = this.#normalizeCastingTime(this._spell, this._castTimeMutators)
// eval probeMod
if (formData.object["checkMod"]) {
this.mod -= formData.object["checkMod"]
this._checkModValue = formData.object["checkMod"]
}
// eval spomods
this._spoMods = foundry.utils.expandObject(formData.object)["spoMods"] ?? {}
let totalMod = this.mod
let totalCost = this.cost
let totalCastingTime = Number(this.castingTime)
Object.entries(this._spoMods).forEach(([modName, times]) => {
const actualMod = spoModData[modName]
for (let i = 0; i < times; i++) {
const ctfn = new Function("castTime", "return " + actualMod.castTimeModFn)
totalCastingTime = ctfn(totalCastingTime)
const cfn = new Function("cost", "return " + actualMod.costModFn)
totalCost = cfn(totalCost)
const zmfn = new Function("mod", "return " + actualMod.modFn)
totalMod = zmfn(totalMod)
}
})
this.mod = totalMod
this.cost = totalCost
this.castingTime = totalCastingTime
this.render({parts: ["form"]})
}
static async #cast(event, target) {
ChatMessage.create({
user: game.user._id,
speaker: {actor: this._actor},
content: `beginnt ${this._spell.name} zu wirken`,
type: CONST.CHAT_MESSAGE_TYPES.IC
})
const cooldowns = this._actor.system.cooldowns
let m = (queue, data) => {
ChatMessage.create({
user: game.user._id,
speaker: {actor: this._actor},
content: data.message,
type: CONST.CHAT_MESSAGE_TYPES.IC
})
}
let message = this._spell.system.wirkung
if (this._activeVariants.length > 0) {
message += "<hr/>"
message += this._activeVariants.map(v => v.name).join(", ")
}
if (Object.keys(this._spoMods).length > 0) {
message += "<hr/>"
Object.entries(this._spoMods).forEach(([modName, times]) => {
if (times > 0) {
message += times + "x" + modName + "<br/>"
}
})
}
if (Object.keys({...this._castTimeMutators, ...this._costMutators}).length > 0) {
message += "<hr/>"
Object.entries({...this._castTimeMutators, ...this._costMutators}).forEach(([mutatorName, mutatorValue]) => {
message += mutatorName + ": " + mutatorValue + "<br/>"
})
}
message += "<hr/>" + this.zfp + " ZfP*<br/>" + this._spell.system.zfw + " ZfW"
cooldowns.push({
start: this.castingTime,
current: 0,
data: {
cssClass: "Magisch",
title: this._spell.name,
taw: this.zfp,
mod: 0,
actorId: this._actor._id,
spellId: this._spell._id,
message,
maneuver: m.toString()
}
})
await this._actor.update({"system.cooldowns": cooldowns})
}
static async #diceRoll(event, target) {
const result = await evaluateRoll(
"3d20",
{
value: this._spell.system.zfw + this.mod,
werte: this.#getProbenWerte(),
owner: this._actor
}
)
if (result.tap >= 0) { // erfolg
await result.evaluated.toMessage({
speaker: ChatMessage.getSpeaker({actor: this._actor}),
flavor: ` ${result.meisterlich ? 'Meisterlich geschafft' : 'Geschafft'} mit ${result.tap} Punkten übrig`,
})
} else { // misserfolg
await result.evaluated.toMessage({
speaker: ChatMessage.getSpeaker({actor: this._actor}),
flavor: ` ${result.meisterlich ? 'Gepatzt' : ''} mit ${Math.abs(result.tap)} Punkten daneben`,
})
}
this.zfp = result.tap
this.zfpDetermined = true
this.render({parts: ["form"]})
}
normalizeCastingCost() {
let costFormula = this._costModel.additionalFormula
if (costFormula) {
this._costModel.variables.forEach(v => {
costFormula = costFormula.replace(v, this._costMutators[v])
})
costFormula = Number(eval(costFormula)) + Number(this._costModel.cost)
} else {
costFormula = this._costModel.cost
}
if (costFormula <= this._costModel.min) {
costFormula = this._costModel.min
}
return costFormula
}
/**
*
* @param spell
* @param {FormulaData} additionalFormulaData
* @returns {number|*}
*/
#normalizeCastingTime(spell, additionalFormulaData) {
// min: Wenn ein Zauber eine mindest dauer hat kann diese nachdem diese abgelaufen ist jederzeit abgebrochen werden
// normal: Standard Zauberzeit eines Zaubers
// additionalFormulaData: enthält die zur Normalzeit zusätzlichen Zauberdauer
const castingTime = spell.system.zauberdauer.normal ?? 0
let baseCastTime = 0
const minCastingTime = spell.system.zauberdauer.min ?? 0
let baseMinCastTime = 0
if (castingTime) {
baseCastTime = castingTime.replace(/(.*) Aktionen/g, (_, aktionen) => {
return aktionen
})
baseCastTime = baseCastTime.replace(/(.*) SR/g, (_, aktionen) => {
return aktionen * 20
})
}
if (minCastingTime) {
baseMinCastTime = minCastingTime.replace(/(.*) Aktionen/g, (_, aktionen) => {
return aktionen
})
baseMinCastTime = baseMinCastTime.replace(/(.*) SR/g, (_, aktionen) => {
return aktionen * 20
})
}
let actualCastingTime = 0
let formula = spell.system.zauberdauer.additionalFormula
if (formula) {
Object.entries(additionalFormulaData).forEach(([variableName, variableValue]) => {
formula = formula.replaceAll(variableName, variableValue)
})
if (spell.system.zauberdauer.additionalFormulaTimeUnit == "Aktionen") {
actualCastingTime = (Number(baseCastTime) + Number(eval(formula)) ?? 0)
} else {
actualCastingTime = (Number(baseCastTime) + (Number(eval(formula)) * 20) ?? 0)
}
} else {
actualCastingTime = baseCastTime
}
if (Number(actualCastingTime) <= Number(baseMinCastTime)) {
actualCastingTime = baseMinCastTime
}
return actualCastingTime
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
if (options.window) {
if (this._spell) {
options.window.title = `${this._spell.name} [${this._spell.system.zfw}]`
}
}
return options
}
#getProbenWerte() {
let dice = []
this._spell.system.probe.map(p => {
if (p === "*") {
return this._spellDie ?? null
} else {
return p
}
}).forEach(p => {
if (p !== null) {
dice.push(
this._actor.system.attribute[p.toLowerCase()].aktuell
)
} else {
dice.push(
"??"
)
}
})
return dice
}
async _prepareContext(options) {
const context = await super._prepareContext(options)
context.actor = this._actor
context.spell = this._spell
context.representationOptions = {}
context.selectedRepresentation = this._selectedRepresentation
context.text = this._spell.system.wirkung
context.dice = []
context.colorfulDice = game.settings.get('DSA_4-1', 'optional_colorfuldice')
context.modResult = this._spell.system.zfw + this.mod
context.penalty = (this.mod > 0 ? "+" : "") + this.mod
context.displayModResult = (context.modResult > 0 ? "+" : "") + context.modResult
context.castingTime = this.castingTime
context.ready = true
context.zfpDetermined = this.zfpDetermined
// variable probe (should consider Achaz as they can replace one KL in a KL/KL/* spell with IN
this._spell.system.probe.map(p => {
if (p === "*") {
return this._spellDie ?? null
} else {
return p
}
}).forEach(p => {
if (p !== null) {
context.dice.push({
wert: this._actor.system.attribute[p.toLowerCase()].aktuell,
name: p,
tooltip: ATTRIBUTE[p.toLowerCase()],
})
} else {
context.dice.push({
wert: "??",
name: "??",
tooltip: ATTRIBUTE["UNKNOWN"],
})
}
})
context.variants = this._spell.system.varianten.map(v => {
return {
variantText: v.description,
variantName: v.name,
variantPenalty: v.mod ?? "0",
variantChecked: this._variants[v.name]
}
})
// Repräsentation
context.representationOptions[""] = ""
Object.entries(this._spell.system.repräsentation).forEach(([key, value]) => {
context.representationOptions[key] = key
})
if (!this._selectedRepresentation) {
context.ready = false
}
// Costs and Mutators
context.castingCosts = this.cost
// set probe to current held probe variables or take from _spell
context.costMutators = this._costMutators
if (this._costModel) {
context.costVariables = this._costModel.variables
} else {
context.costVariables = []
}
// probeMod
if (this._spell.system.probeMod) {
context.checkModTest = this._spell.system.probeMod
context.checkModValue = this._checkModValue
}
// SpoMods
context.spoModCount = Object.values(this._spoMods).reduce((previousValue, currentValue) => previousValue + currentValue, 0)
context.maxSpoModCount = 0
if (this._selectedRepresentation) {
const leadingAttributKey = leadingAttribute[this._selectedRepresentation]
context.maxSpoModCount = (this._actor.system.attribute[leadingAttributKey.toLowerCase()].aktuell ?? 0) - 12
if (context.maxSpoModCount < 0) {
context.maxSpoModCount = 0
}
}
if (context.spoModCount > context.maxSpoModCount) {
context.ready = false
}
const mapper = (spoModName) => {
let data = spoModData[spoModName]
let value = this._spoMods[data.name] ?? 0
let totalModValue = data.mod * value
return {
...data,
value,
totalModValue
}
}
context.spoMods = []
if (this._spell.system.modifikationen) {
this._spell.system.modifikationen.split(",").forEach(spoMod => {
switch (spoMod.trim()) {
case "Zauberdauer":
context.spoMods.push(mapper("Halbierte Zauberdauer"))
context.spoMods.push(mapper("Verdoppelte Zauberdauer"))
break;
case "Kosten":
context.spoMods.push(mapper("Kosten einsparen"))
break;
case "Reichweite":
context.spoMods.push(mapper("Verkleinerung von Reichweite oder Wirkungsradius"))
context.spoMods.push(mapper("Vergrößerung von Reichweite oder Wirkungsradius"))
break;
}
})
}
// if this.zfp is null then we are in the first step pre dice roll
if (this.zfp == null) {
context.ready = false
context.diceRoll = true
} else {
if (this.zfp === 0) {
this.zfp = 1
}
let zfpMod = 0
Object.entries(this._spoMods).forEach(([modName, times]) => {
const actualMod = spoModData[modName]
for (let i = 0; i < times; i++) {
const zmfn = new Function("mod", "return " + actualMod.modFn)
zfpMod = zmfn(zfpMod)
}
})
if (this.zfp + zfpMod > this._spell.system.zfw) { // cant be higher than the learnt level
context.zfpModified = this._spell.system.zfw
} else {
context.zfpModified = this.zfp + zfpMod
}
context.spellName = this._spell.system.name
context.variant = context.variants.filter(v => v.variantChecked).map(v => `<em>${v.variantName}</em>&mdash;${v.variantText}`).join("<br/><br/>")
if (context.zfpModified < 0) {
context.ready = false
}
}
if (!context.ready) { // rules have changed, it cant be cast when zfp - selected mutators is below 0
context.notReadyReasons = `<em>${game.i18n.format("SPELL_DIALOG.notReadyReason.title")}</em><ul>`
if (this.zfp == null) {
context.notReadyReasons += `<li>${game.i18n.format("SPELL_DIALOG.notReadyReason.noZFPDataAvailable")}</li>`
}
if (context.zfpModified < 0) {
context.notReadyReasons += `<li>${game.i18n.format("SPELL_DIALOG.notReadyReason.overspentZFP")}</li>`
}
if (context.spoModCount > context.maxSpoModCount) {
context.notReadyReasons += `<li>${game.i18n.format("SPELL_DIALOG.notReadyReason.tooManySpoMods")}</li>`
}
if (!this._selectedRepresentation) {
context.notReadyReasons += `<li>${game.i18n.format("SPELL_DIALOG.notReadyReason.noRepresentation")}</li>`
}
context.notReadyReasons += "</ul>"
}
return context
}
async _onRender(context, options) {
}
}

View File

@ -1,10 +1,8 @@
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
import {Talent} from "../data/talent.mjs";
import {ATTRIBUTE} from "../data/attribute.mjs";
const {
ApplicationV2,
HandlebarsApplicationMixin
} = foundry.applications.api
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
export class TalentDialog extends HandlebarsApplicationMixin(ApplicationV2) {

View File

@ -1,9 +1,6 @@
import {XmlImport} from "../xml-import/xml-import.mjs";
const {
ApplicationV2,
HandlebarsApplicationMixin
} = foundry.applications.api
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
export class XmlImportDialog extends HandlebarsApplicationMixin(ApplicationV2) {

View File

@ -1,5 +1,5 @@
import {LiturgyData} from "../data/miracle/liturgyData.mjs";
import {Zonenruestung, Zonenwunde, Wunde} from "../data/trefferzone.mjs";
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
import {Zonenruestung, Zonenwunde, Wunde} from "../data/Trefferzone.js";
import {PlayerCharacterDataModel} from "../data/character.mjs";
export class Character extends Actor {
@ -26,7 +26,7 @@ export class Character extends Actor {
*/
prepareDerivedData() {
if (this.type === "Character") {
if (this.type === "character") {
const actorData = this;
const systemData = actorData.system;
@ -49,9 +49,9 @@ export class Character extends Actor {
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.lep.max = Math.round((ko + ko + kk) / 2)
systemData.aup.max = Math.round((mu + ko + ge) / 2)
systemData.asp.max = Math.round((mu + _in + ch) / 2)
systemData.regeneration = systemData.regeneration ?? {
lep: "1d6",
@ -105,19 +105,6 @@ export class Character extends Actor {
systemData.ausweichen.basis = systemData.pa.basis
systemData.ausweichen.aktuell = systemData.ausweichen.basis
systemData.ueberanstrengung = 0
if (game.settings.get("DSA_4-1", "optional_erschoepfung")) {
systemData.erschoepfung = {
aktuell: systemData.erschoepfung.aktuell ?? 0,
max: ko
}
if (systemData.erschoepfung.aktuell > systemData.erschoepfung.max) {
systemData.ueberanstrengung = systemData.erschoepfung.aktuell - systemData.erschoepfung.max
}
}
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs = {
@ -132,11 +119,11 @@ export class Character extends Actor {
} else {
systemData.rs = 0; // only with DSA_4-1.optional_trefferzonen = false
}
systemData.be = 0 + systemData.ueberanstrengung;
systemData.be = 0;
// half KO is the maximum a character can sustain wounds before collapsing
systemData.wunden.max = Math.round(ko / 2);
systemData.wunden.max = ko / 2;
if (game.settings.get("DSA_4-1", "optional_trefferzonen")) {
systemData.wunden.kopf = 0
systemData.wunden.brust = 0
@ -323,136 +310,6 @@ export class Character extends Actor {
return updateObject;
}
/**
* reduce the wealth owned by this character by the given amount in Silver
* @param by amount of Silver to reduce the wealth by the character
* @returns {boolean}
*/
async reduceWealth(by) {
const ducats = this.itemTypes["Equipment"].filter(p => p.name === "Dukate")
const silver = this.itemTypes["Equipment"].filter(p => p.name === "Silbertaler")
const kreutzer = this.itemTypes["Equipment"].filter(p => p.name === "Kreuzer")
const heller = this.itemTypes["Equipment"].filter(p => p.name === "Heller")
let ducatsAmount = 0
let silverAmount = 0
let kreutzerAmount = 0
let hellerAmount = 0
ducats.forEach(d => {
ducatsAmount += d.system.quantity ?? 1
})
silver.forEach(s => {
silverAmount += s.system.quantity ?? 1
})
kreutzer.forEach(k => {
kreutzerAmount += k.system.quantity ?? 1
})
heller.forEach(h => {
hellerAmount += h.system.quantity ?? 1
})
// Convert total wealth to silver
let totalSilver = ducatsAmount * 10 + silverAmount + kreutzerAmount * 0.1 + hellerAmount * 0.01;
if (totalSilver < by) {
return false; // Indicate that the reduction can't be performed
}
// Subtract the given sum from total silver
totalSilver -= by;
// Handle if the total goes below zero
if (totalSilver < 0) totalSilver = 0;
// Convert back to coinages
let newDucats = Math.floor(totalSilver / 10);
totalSilver %= 10;
let newSilver = Math.floor(totalSilver);
totalSilver %= 1;
let newKreutzer = Math.floor(totalSilver / 0.1);
totalSilver %= 0.1;
let newHeller = Math.round(totalSilver / 0.01);
// remove all coinage items
let deleteDocuments = []
ducats.forEach(d => deleteDocuments.push(d._id))
silver.forEach(s => deleteDocuments.push(s._id))
kreutzer.forEach(k => deleteDocuments.push(k._id))
heller.forEach(h => deleteDocuments.push(h._id))
await this.deleteEmbeddedDocuments('Item', deleteDocuments)
// rebuild coinage documents
const compendiumOfCoins = game.packs.get('DSA_4-1.Currency');
if (newDucats > 0) {
let coin = compendiumOfCoins.index.find(coin => coin.name === "Dukate")
const ducatDocument = await compendiumOfCoins.getDocument(coin._id);
try {
this.createEmbeddedDocuments('Item', [ducatDocument]).then(
embeddedDocuments => {
embeddedDocuments[0].update({"system.quantity": newDucats}).then(_ => {
console.log("created new Ducats with qty of", newDucats)
})
})
} catch (err) {
}
}
if (newSilver > 0) {
let coin = compendiumOfCoins.index.find(coin => coin.name === "Silbertaler")
const silverDocument = await compendiumOfCoins.getDocument(coin._id);
try {
this.createEmbeddedDocuments('Item', [silverDocument]).then(
embeddedDocuments => {
embeddedDocuments[0].update({"system.quantity": newSilver}).then(_ => {
console.log("created new Silver with qty of", newSilver)
})
})
} catch (err) {
}
}
if (newKreutzer > 0) {
let coin = compendiumOfCoins.index.find(coin => coin.name === "Kreuzer")
const kreutzerDocument = await compendiumOfCoins.getDocument(coin._id);
try {
this.createEmbeddedDocuments('Item', [kreutzerDocument]).then(
embeddedDocuments => {
embeddedDocuments[0].update({"system.quantity": newKreutzer}).then(_ => {
console.log("created new Kreutzer with qty of", newKreutzer)
})
})
} catch (err) {
}
}
if (newHeller > 0) {
let coin = compendiumOfCoins.index.find(coin => coin.name === "Heller")
const hellerDocument = await compendiumOfCoins.getDocument(coin._id);
try {
this.createEmbeddedDocuments('Item', [hellerDocument]).then(
embeddedDocuments => {
embeddedDocuments[0].update({"system.quantity": newHeller}).then(_ => {
console.log("created new Heller with qty of", newHeller)
})
})
} catch (err) {
}
}
return true
}
isWorn(itemId) {

View File

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

View File

@ -1,72 +0,0 @@
/**
*
* @param {[{result: Number}]|String} rolledDice either the result of a roll or a roll-formula
* @param {Number} value the value of this dice roll
* @param {[number]} werte an array of values that the dice roll is compared against
* @param {{getRollData:() => {} }} owner the actor of this roll that is required when rolledDice is a roll-formula
* @param {Number} lowerThreshold this is the threshold against a critical success is counted against
* @param {Number} upperThreshold this is the threshold against a critical fumble is counted against
* @param {Number} countToMeisterlich amount of critical success are needed for the dice roll to be a critical success
* @param {Number} countToPatzer amount of critical fumbles are needed for the dice roll to be a critical failure
* @returns {{tap: number, meisterlich: boolean, patzer: boolean, evaluated: Roll.Evaluated}}
*/
const evaluateRoll = async (rolledDice, {
value,
werte = [],
owner,
lowerThreshold = 1,
upperThreshold = 20,
countToMeisterlich = 3,
countToPatzer = 3,
}) => {
let tap = value;
let meisterlichCounter = 0;
let patzerCounter = 0;
let failCounter = 0;
let evaluated = null
if (typeof rolledDice == "string") { // we need to roll it ourself
let roll1 = new Roll(rolledDice, owner.getRollData());
evaluated = await roll1.evaluate()
rolledDice = evaluated.terms[0].results
}
if (tap < 0) { // increases rolledDice by |tap| (as this defacto lowers the target value)
rolledDice = rolledDice.map(({result, active}) => {
return {
result: result - tap,
active
}
})
tap = 0 // and then reset tap to 0 as we applied the reduction
}
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,
evaluated
}
}
export {
evaluateRoll
}

View File

@ -1,75 +0,0 @@
function currency(st) {
// schema for Mittelreich: 1 Ducat = 10 Silver = 100 Kreutzer = 1000 Heller
// internally the price is always given in Silver
// so we need to inflate the value of price by 100 to be able to divide beginning from Heller
const baseValue = Math.round(st * 100)
// then we can regex over it
const currencyRegexp = /(.*)(.)(.)(.)/g
const withDucats = currencyRegexp.exec(baseValue)
let _ = undefined
let ducats = 0
let silver = 0
let kreutzer = 0
let heller = 0
if (withDucats) {
[_, ducats, silver, kreutzer, heller] = withDucats
} else {
const currencyRegexp = /(.)(.)(.)/g
const withSilver = currencyRegexp.exec(baseValue)
if (withSilver) {
[_, silver, kreutzer, heller] = withSilver
} else {
const currencyRegexp = /(.)(.)/g
const withKreutzer = currencyRegexp.exec(baseValue)
if (withKreutzer) {
[_, kreutzer, heller] = withKreutzer
} else {
heller = baseValue
}
}
}
return {
ducats,
silver,
kreutzer,
heller
}
}
function registerHelper(hbs) {
hbs?.registerHelper('currency', (data) => {
const {ducats, silver, kreutzer, heller} = currency(data)
let str = `<span class='coins' data-tooltip="${ducats > 0 ? ducats + ' Dukaten ' : ''}${silver > 0 ? silver + ' Silbertaler ' : ''}${kreutzer > 0 ? kreutzer + ' Kreuzer ' : ''}${heller > 0 ? heller + ' Heller' : ''}">`
if (ducats > 0) {
str += ducats + "<i class='symbol ducat'></i>"
}
if (silver > 0) {
str += silver + "<i class='symbol silver'></i>"
}
if (kreutzer > 0) {
str += kreutzer + "<i class='symbol kreutzer'></i>"
}
if (heller > 0) {
str += heller + "<i class='symbol heller'></i>"
}
str += "</span>"
return new Handlebars.SafeString(str)
})
}
export {
currency,
registerHelper
}

View File

@ -1,36 +0,0 @@
function fieldTooltip(forActor, fieldName) {
let tooltip = {}
if (forActor) {
Object.entries(forActor.getModificationsOn(fieldName)).forEach(([key, value]) => {
tooltip[key] = value
})
}
return tooltip
}
function registerHelper(hbs) {
hbs?.registerHelper('fieldTooltip', (data) => {
const [fieldName, actorId] = data
const forActor = game.actors.find(p => p._id === actorId)
const tooltip = fieldTooltip(forActor, fieldName)
let template = ``
Object.entries(tooltip).forEach(([key, value]) => {
template += `${key}: ${value}<br/>`
})
return new Handlebars.SafeString(template)
})
}
export {
fieldTooltip,
registerHelper
}

View File

@ -1,13 +0,0 @@
import * as currency from "./currency.mjs";
import * as fieldTooltip from "./field-tooltip.mjs";
import * as weight from "./weight.mjs";
function initHandlebarHelpers(hbs) {
currency.registerHelper(hbs)
fieldTooltip.registerHelper(hbs)
weight.registerHelper(hbs)
}
export {
initHandlebarHelpers
}

View File

@ -1,38 +0,0 @@
function weight(money) {
const baseValue = money * 1000 // to get to gramms (1/1000 Stone)
const stone = Math.floor(baseValue / 1000)
const remainder = baseValue - (stone * 1000)
const ounces = remainder / 25
return {
stone,
ounces,
}
}
function registerHelper(hbs) {
hbs?.registerHelper('weight', (data) => {
let template = `<span class="weight">`
const {stone, ounces} = weight(data)
if (stone > 0) {
template += `<span class="stone">${stone}</span>`
}
if (ounces > 0) {
template += `<span class="ounces">${ounces}</span>`
}
template += `</span>`
return new Handlebars.SafeString(template)
})
}
export {
weight,
registerHelper
}

View File

@ -1,95 +0,0 @@
function initGlobalSettings(settings) {
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
})
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
})
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
})
settings.register('DSA_4-1', 'optional_erschoepfung', {
name: "Optional: Erschöpfung",
hint: "Aktiviert Regeln für das Spiel mit Erschöpfung und Überanstregung",
scope: "world",
config: true,
type: Boolean,
default: false,
onChange: value => {
},
requiresReload: true
})
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
})
settings.register('DSA_4-1', 'optional_aufstufen_von_liturgien', {
name: "Optional: Aufstufen von Liturgien",
hint: "Aktiviert die Regeln zum Aufstufen von Liturgien",
scope: "world",
config: true,
type: Boolean,
default: false,
disabled: true,
requiresReload: true
})
}
function initUserSettings(settings) {
settings.register('DSA_4-1', 'optional_colorfuldice', {
name: "Optional: Farbige Würfel nach Paramanthus",
hint: "Färbt die Würfel je nach Attribut ein",
scope: "client",
config: true,
type: Boolean,
default: false,
onChange: value => {
},
requiresReload: false
})
}
export {
initGlobalSettings,
initUserSettings
}

View File

@ -1,121 +0,0 @@
import {PlayerCharacterDataModel} from "../data/character.mjs";
import {GroupDataModel} from "../data/group.mjs";
import {CreatureDataModel} from "../data/creature.mjs";
import {MerchantDataModel} from "../data/merchant.mjs";
import {SkillDataModel} from "../data/skill.mjs";
import {SpellDataModel} from "../data/spell.mjs";
import {AdvantageDataModel} from "../data/advantage.mjs";
import {EquipmentDataModel} from "../data/equipment.mjs";
import {LiturgyDataModel} from "../data/liturgy.mjs";
import {BlessingDataModel} from "../data/blessing.mjs";
import {SpecialAbilityDataModel} from "../data/specialAbility.mjs";
import {ActiveEffectDataModel} from "../data/activeEffect.mjs";
import {ProfessionDataModel} from "../data/profession.mjs";
import {SpeciesDataModel} from "../data/species.mjs";
import {CultureDataModel} from "../data/culture.mjs";
import {Trefferzone, Wunde, Zonenruestung, Zonenwunde} from "../data/trefferzone.mjs";
import {RestingDialog} from "../dialog/restingDialog.mjs";
import {BattleDialog} from "../dialog/battleDialog.mjs";
import {Talent} from "../data/talent.mjs";
import {Character} from "../documents/character.mjs";
import {currency} from "../handlebar-helpers/currency.mjs";
import {DeityDataModel} from "../data/deity.mjs";
import {ItemBrowserDialog} from "../dialog/itemBrowserDialog.mjs";
function initGlobalAccess() {
return {
Zonenruestung,
Zonenwunde,
Trefferzone,
Wunde,
RestingDialog,
BattleDialog,
ItemBrowserDialog,
Talent,
displayCurrency: currency
}
}
function initDocumentClasses(config) {
config.Actor.documentClass = Character
}
function initDataModels(config) {
config.Actor.dataModels = {
Character: PlayerCharacterDataModel,
Group: GroupDataModel,
Creature: CreatureDataModel,
Merchant: MerchantDataModel,
}
config.Item.dataModels = {
Skill: SkillDataModel,
Spell: SpellDataModel,
Advantage: AdvantageDataModel,
Equipment: EquipmentDataModel,
Liturgy: LiturgyDataModel,
Blessing: BlessingDataModel,
SpecialAbility: SpecialAbilityDataModel,
ActiveEffect: ActiveEffectDataModel,
Profession: ProfessionDataModel,
Spezies: SpeciesDataModel,
Kultur: CultureDataModel,
Deity: DeityDataModel,
}
}
function initCombat(config) {
config.Combat.initiative = {
formula: `(@ini.wuerfel)d6 + @ini.aktuell`,
decimals: 0
}
}
function initSocketLib() {
Hooks.on("socketlib.ready", () => {
let socket = socketlib.registerSystem("DSA_4-1")
socket.register("removeFromLootTable", removeFromLootTable)
socket.register("buyFromLootTable", buyFromLootTable)
if (!game.DSA41) {
game.DSA41 = {}
}
game.DSA41.socket = socket
})
async function removeFromLootTable(actorId, itemId) {
if (actorId && game.actors.get(actorId)) {
const actor = game.actors.get(actorId)
return await actor.deleteEmbeddedDocuments('Item', [itemId])
}
}
async function buyFromLootTable(actorId, itemId) {
if (actorId && game.actors.get(actorId)) {
const actor = game.actors.get(actorId)
const item = actor.items.find(p => p.id === itemId)
if (item.system.quantity != -1) { // -1 means infinite
if (item.system.quantity > 1) {
item.update({'system.quantity': item.system.quantity - 1})
} else {
actor.deleteEmbeddedDocuments('Item', [item._id]) // delete when the quantity is equal to 0
}
}
return true
}
}
}
export {
initSocketLib,
initGlobalAccess,
initDocumentClasses,
initDataModels,
initCombat,
}

View File

@ -1,32 +0,0 @@
function loadPartials(hbs) {
return new Promise(resolve => {
hbs.loadTemplates([
// ui partials.
'systems/DSA_4-1/templates/ui/partial-rollable-button.hbs',
'systems/DSA_4-1/templates/ui/partial-cooldown.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-array-editor.hbs',
'systems/DSA_4-1/templates/actor/character/tab-set.hbs',
'systems/DSA_4-1/templates/dialog/liturgy-dialog.hbs',
'systems/DSA_4-1/templates/ui/partial-mini-rollable-button.hbs',
'systems/DSA_4-1/templates/ui/partial-mini-rollable-liturgy-button.hbs',
'systems/DSA_4-1/templates/ui/partial-mini-rollable-weaponskill-button.hbs',
'systems/DSA_4-1/templates/ui/partial-mini-rollable-language-button.hbs',
'systems/DSA_4-1/templates/ui/partial-mini-rollable-spell-button.hbs',
]).then(resolve);
})
}
export {
loadPartials,
}

View File

@ -1,102 +0,0 @@
import CharacterSheet from "../sheets/characterSheet.mjs";
import {CreatureSheet} from "../sheets/creatureSheet.mjs";
import {GroupSheet} from "../sheets/groupSheet.mjs";
import {SkillSheet} from "../sheets/skillSheet.mjs";
import {SpellSheet} from "../sheets/spellSheet.mjs";
import {AdvantageSheet} from "../sheets/advantageSheet.mjs";
import EquipmentSheet from "../sheets/equipmentSheet.mjs";
import {LiturgySheet} from "../sheets/liturgySheet.mjs";
import {SpecialAbilitySheet} from "../sheets/specialAbilitySheet.mjs";
import {ActiveEffectSheet} from "../sheets/activeEffectSheet.mjs";
import {CultureSheet} from "../sheets/cultureSheet.mjs";
import {SpeciesSheet} from "../sheets/SpeciesSheet.mjs";
import {ProfessionSheet} from "../sheets/professionSheet.mjs";
import {MerchantSheet} from "../sheets/merchantSheet.mjs";
import {DeitySheet} from "../sheets/deitySheet.mjs";
function setUpActorSheets(registry) {
registry.registerSheet('dsa41.character', CharacterSheet, {
types: ["Character"],
makeDefault: true,
})
registry.registerSheet('dsa41.creature', CreatureSheet, {
types: ["Creature"],
makeDefault: true,
})
registry.registerSheet('dsa41.group', GroupSheet, {
types: ["Group"],
makeDefault: true,
})
registry.registerSheet('dsa41.merchant', MerchantSheet, {
types: ['Merchant'],
makeDefault: true,
})
}
function setUpItemSheets(registry) {
registry.registerSheet('dsa41.skill', SkillSheet, {
types: ["Skill"],
makeDefault: true,
});
registry.registerSheet('dsa41.spell', SpellSheet, {
types: ["Spell"],
makeDefault: true,
});
registry.registerSheet('dsa41.advantage', AdvantageSheet, {
types: ["Advantage"],
makeDefault: true,
})
registry.registerSheet('dsa41.equipment', EquipmentSheet, {
types: ["Equipment"],
makeDefault: false,
})
registry.registerSheet('dsa41.liturgy', LiturgySheet, {
types: ["Liturgy"],
makeDefault: true,
})
registry.registerSheet('dsa41.specialAbility', SpecialAbilitySheet, {
types: ["SpecialAbility"],
makeDefault: true,
})
registry.registerSheet('dsa41.activeEffect', ActiveEffectSheet, {
types: ['ActiveEffect'],
makeDefault: true,
})
registry.registerSheet('dsa41.culture', CultureSheet, {
types: ['Culture'],
makeDefault: true,
label: 'DSA41.CultureLabels.Culture'
})
registry.registerSheet('dsa41.spezien', SpeciesSheet, {
types: ['Species'],
makeDefault: true,
})
registry.registerSheet('dsa41.profession', ProfessionSheet, {
types: ['Profession'],
makeDefault: true,
})
registry.registerSheet('dsa41.deity', DeitySheet, {
types: ['Deity'],
makeDefault: true,
})
}
export {
setUpActorSheets,
setUpItemSheets,
}

View File

@ -80,11 +80,7 @@ export class ActionManager {
type: ActionManager.ATTACK,
cost: ActionManager.REGULAR,
source: ActionManager.DEFAULT,
activate: (queue, data) => {
data.actor.rollAttack(data)
return true
},
eval: (options) => this.#hatWaffeinHand(options),
eval: (options) => this.#hatWaffeinHand(options)
},
{
name: "Fernkampfangriff",
@ -93,8 +89,6 @@ export class ActionManager {
source: ActionManager.DEFAULT,
cooldown: (options) => 1,
activate: (queue, data) => {
data.actor.rollAttack(data)
return true
},
eval: (options) => this.#hatFernkampfWaffeinHand(options),
},
@ -104,10 +98,6 @@ export class ActionManager {
cost: ActionManager.CONTINUING,
source: ActionManager.DEFAULT,
cooldown: (options) => options.mod,
activate: (queue, data) => {
data.actor.rollAttack(data)
return true
},
eval: (options) => {
const step1 = this.#hatFernkampfWaffeinHand(options)
const step2 = !this.#hatSonderfertigkeit("Scharfschütze", options)
@ -221,10 +211,6 @@ export class ActionManager {
source: ActionManager.SF,
modDescription: "verringert PA des Ziels um {}",
mod: (value) => value,
activate: (queue, data) => {
data.actor.rollAttack(data)
return true
},
eval: (options) => {
const step1 = this.#hatWaffeinHand(options) && this.#hatSonderfertigkeit("Finte", options)
const step2WithBenefits = this.#evalSonderfertigkeitRequirements("Finte", options)
@ -241,10 +227,6 @@ export class ActionManager {
source: ActionManager.DEFAULT,
modDescription: "erhöht TP vom Angriff um {}",
mod: (value) => -(value * 2),
activate: (queue, data) => {
data.actor.rollAttack(data)
return true
},
eval: (options) => {
const step1 = !this.#hatFernkampfWaffeinHand(options)
const step2 = !this.#hatSonderfertigkeit("Wuchtschlag", options)
@ -262,10 +244,6 @@ export class ActionManager {
source: ActionManager.SF,
modDescription: "erhöht TP vom Angriff um {}",
mod: (value) => -(value),
activate: (queue, data) => {
data.actor.rollAttack(data)
return true
},
eval: (options) => {
const step1 = !this.#hatFernkampfWaffeinHand(options) && this.#hatSonderfertigkeit("Wuchtschlag", options)
const step2WithBenefits = this.#evalSonderfertigkeitRequirements("Wuchtschlag", options)
@ -280,10 +258,6 @@ export class ActionManager {
type: ActionManager.ATTACK,
cost: ActionManager.REGULAR,
source: ActionManager.SF,
activate: (queue, data) => {
data.actor.rollAttack(data)
return true
},
eval: (options) => {
const step1 = !this.#hatFernkampfWaffeinHand(options) && this.#hatSonderfertigkeit("Betäubungsschlag", options)
const step2WithBenefits = this.#evalSonderfertigkeitRequirements("Betäubungsschlag", options)

View File

@ -7,20 +7,10 @@ export class AdvantageSheet extends HandlebarsApplicationMixin(DocumentSheetV2)
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'item', 'advantage'],
tag: 'form',
window: {
resizable: true
},
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: AdvantageSheet.#onSubmitForm
},
actions: {
addRequirement: AdvantageSheet.#addRequirement,
removeRequirement: AdvantageSheet.#removeRequirement,
addMod: AdvantageSheet.#addMod,
removeMod: AdvantageSheet.#removeMod,
saveVariant: AdvantageSheet.#saveVariant
}
}
@ -33,7 +23,6 @@ export class AdvantageSheet extends HandlebarsApplicationMixin(DocumentSheetV2)
}
}
/** @inheritDoc */
static PARTS = {
form: {
@ -41,14 +30,9 @@ export class AdvantageSheet extends HandlebarsApplicationMixin(DocumentSheetV2)
},
advantage: {
template: `systems/DSA_4-1/templates/item/advantage/tab-advantage.hbs`
},
variants: {
template: `systems/DSA_4-1/templates/ui/tab-variants.hbs`
}
}
static _instance = null
_configureRenderOptions(options) {
super._configureRenderOptions(options)
@ -59,173 +43,6 @@ export class AdvantageSheet extends HandlebarsApplicationMixin(DocumentSheetV2)
return options
}
constructor(...args) {
super(...args);
AdvantageSheet._instance = this
}
static async #addRequirement(event, target) {
const selections =
"<option value='talentMin'>Mindest Talentwert</option>" +
"<option value='talentMax'>Maximal Talentwert</option>" +
"<option value='attributeMin'>Mindest Attributswert</option>" +
"<option value='attributeMax'>Maximal Attributswert</option>" +
"<option value='compare'>Vergleich</option>"
const type = await foundry.applications.api.DialogV2.prompt({
window: {title: "Neue Voraussetzung"},
content: `<select name="type">${selections}</select>`,
ok: {
label: `Hinzufügen`,
callback: (event, button, dialog) => button.form.elements.type.value
}
});
if (type) {
let newReq = {}
switch (type) {
case 'talentMin':
newReq = {
talent: 'Klettern',
minValue: '0'
}
break;
case 'talentMax':
newReq = {
talent: 'Zechen',
maxValue: '0'
}
break;
case 'attributeMin':
newReq = {
attribute: 'ge',
minValue: '0'
}
break;
case 'attributeMax':
newReq = {
attribute: 'mu',
maxValue: '0'
}
break;
case 'compare':
newReq = {
compare: {
ownAttribute: 'ini.aktuell',
operation: 'eq',
targetAttribute: 'ini.aktuell'
}
}
break;
}
const lastIndex = Object.keys(AdvantageSheet._instance._currentSelectedVariant.requirements).length
AdvantageSheet._instance._currentSelectedVariant.requirements[lastIndex] = newReq
AdvantageSheet._instance.render({parts: ["form", "advantage", "variants"]})
}
}
static async #removeRequirement(event, target) {
const {index} = target.dataset
if (index) {
delete AdvantageSheet._instance._currentSelectedVariant.requirements[index]
}
}
static async #addMod(event, target) {
const selections =
"<option value='talent'>Talent</option>" +
"<option value='talentGroup'>Talentgruppe</option>" +
"<option value='attribute'>Attribute</option>" +
"<option value='trait'>Merkmal</option>"
const type = await foundry.applications.api.DialogV2.prompt({
window: {title: "Neuer Modifikator"},
content: `<select name="type">${selections}</select>`,
ok: {
label: `Hinzufügen`,
callback: (event, button, dialog) => button.form.elements.type.value
}
});
if (type) {
let newReq = {}
switch (type) {
case 'talent':
newReq = {
talent: 'Klettern',
value: '0'
}
break;
case 'talentGroup':
newReq = {
talentGruppe: 'Gesellschaft',
value: '0'
}
break;
case 'trait':
newReq = {
merkmal: 'Elementar',
value: '0'
}
break;
case 'attribute':
newReq = {
name: 'ge',
value: '0'
}
break;
}
const lastIndex = Object.keys(AdvantageSheet._instance._currentSelectedVariant.mod).length
AdvantageSheet._instance._currentSelectedVariant.mod[lastIndex] = newReq
AdvantageSheet._instance.render({parts: ["form", "advantage", "variants"]})
}
}
static async #removeMod(event, target) {
const {index} = target.dataset
if (index) {
delete AdvantageSheet._instance._currentSelectedVariant.mod[index]
}
}
static async #saveVariant(event, target) {
/**
* @type {HTMLFormElement}
*/
const form = AdvantageSheet._instance.form
let flattenObject = {}
Object.values(form).forEach(input => {
if (input.name.startsWith('mod') || input.name.startsWith('requirements')) {
flattenObject[`${input.name}`] = input.value
}
if (input.name === "vName") {
flattenObject[`name`] = input.value
}
})
let auswahl = AdvantageSheet._instance.document.system.auswahl
const fo = foundry.utils.expandObject(flattenObject)
auswahl[AdvantageSheet._instance._currentSelectedVariantIndex] = {
name: fo.name,
mod: Object.values(fo.mod),
requirements: Object.values(fo.requirements)
}
AdvantageSheet._instance.document.update({system: {auswahl}})
}
/**
* Handle form submission
* @this {AdvantageSheet}
@ -235,32 +52,10 @@ export class AdvantageSheet extends HandlebarsApplicationMixin(DocumentSheetV2)
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
if (!form.querySelector('.tab.advantage.active')) {
const obj = foundry.utils.expandObject(formData.object)
if (obj.mod) this._currentSelectedVariant.mod = obj.mod
if (obj.vName) this._currentSelectedVariant.name = obj.vName
if (obj.requirements) this._currentSelectedVariant.requirements = obj.requirements
} else {
delete formData.object.mod
delete formData.object.vName
delete formData.object.requirements
delete formData.object.variant
await this.document.update(formData.object) // Note: formData.object
}
this.render({parts: ["form", "advantage", "variants"]})
}
_getTabsConfig(group) {
const tabs = foundry.utils.deepClone(super._getTabsConfig(group))
if (this.document.system.auswahl) {
tabs.tabs.push({id: 'variants', group: 'sheet', label: 'Varianten'})
}
return tabs
console.log(formData.object, this.document)
}
/** @override */
@ -276,34 +71,7 @@ export class AdvantageSheet extends HandlebarsApplicationMixin(DocumentSheetV2)
context.choices[a.name] = a.name
})
context.hasModality = context.system.value != null
context.name = advantageData.name
context.variantChoices = {}
context.variants = []
advantageData.system.auswahl?.forEach(variant => {
context.variantChoices[variant.name] = variant.name
context.variants.push(variant)
})
context.currentSelectedVariantName = this._currentSelectedVariant?.name
context.currentSelectedVariant = this._currentSelectedVariant
context.currentSelectedVariantIndex = this._currentSelectedVariantIndex
return context;
}
_onRender(context, options) {
if (this._selectedVariant == null) {
this._selectedVariant = this.document.system.auswahl[0].name
this._currentSelectedVariant = this.document.system.auswahl?.find(p => p.name === this._selectedVariant)
this._currentSelectedVariantIndex = this.document.system.auswahl?.findIndex(p => p.name === this._selectedVariant)
}
this.element.querySelector('select[name="variant"]').addEventListener('change', (event, target) => {
if (event.target.value != this._selectedVariant) {
this._selectedVariant = event.target.value
this._currentSelectedVariant = this.document.system.auswahl?.find(p => p.name === this._selectedVariant)
this._currentSelectedVariantIndex = this.document.system.auswahl?.findIndex(p => p.name === this._selectedVariant)
}
})
}
}

View File

@ -1,118 +0,0 @@
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
export class StandaloneADVSF extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'actor', 'character', 'standalone', 'advsf'],
tag: 'form',
actions: {
rollFlaw: StandaloneADVSF.#rollFlaw,
openEmbeddedDocument: StandaloneADVSF.#openEmbeddedDocument,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/character/standalone/advsf.hbs`
}
}
_actor = null
constructor(actor) {
super(actor)
this._actor = actor
this.render(true)
this.options.window.title = `${this.document.name} Vor und Nachteile`
}
static async #rollFlaw(event, target) {
this._actor?.sheet.options.actions.rollFlaw.bind(this)(event, target)
}
static async #openEmbeddedDocument(event, target) {
this._actor?.sheet.options.actions.openEmbeddedDocument.bind(this)(event, target)
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
options.window.title = `${this.document.name}: Vor und Nachteile`
return options
}
async _prepareContext(context, options, object) {
if (this._actor) {
const actorData = this.document
context.system = actorData.system
context.flags = actorData.flags
context.derived = actorData.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
context.advantages = []
context.flaws = []
actorData.itemTypes.Advantage.forEach((item) => {
if (!item.system.schlechteEigenschaft) {
context.advantages.push({
id: item._id,
name: item.name,
value: item.system.value,
options: item.system.auswahl,
description: item.system.description,
isAdvantage: !item.system.nachteil,
isDisadvantage: item.system.nachteil,
isBadAttribute: item.system.schlechteEigenschaft,
fav: item.getFlag("DSA_4-1", "favourite")
})
} else {
context.flaws.push({
id: item._id,
name: item.name,
value: item.system.value,
options: item.system.auswahl,
description: item.system.description,
isAdvantage: !item.system.nachteil,
isDisadvantage: item.system.nachteil,
isBadAttribute: item.system.schlechteEigenschaft,
fav: item.getFlag("DSA_4-1", "favourite")
})
}
}
)
context.specialAbilities = []
actorData.itemTypes.SpecialAbility.forEach((item) => {
context.specialAbilities.push({
id: item._id,
name: item.system.value ? item.system.value : item.name,
fav: item.getFlag("DSA_4-1", "favourite")
});
}
);
return context
}
}
_onRender(context, options) {
if (this._actor) {
new foundry.applications.ux.DragDrop.implementation({
dropSelector: ".advantages, .special-abilities",
permissions: {
drop: this._actor.sheet._canDragDrop.bind(this._actor.sheet)
},
callbacks: {
drop: this._actor.sheet._onDrop.bind(this._actor.sheet),
}
}).bind(this.element);
}
}
}

View File

@ -1,98 +0,0 @@
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
export class Bagpack extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'actor', 'character', 'standalone', 'bagpack'],
tag: 'form',
actions: {
openItemBrowser: Bagpack.#openItemBrowser,
newItem: Bagpack.#newItem,
openEmbeddedDocument: Bagpack.#openEmbeddedDocument,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/character/standalone/bagpack.hbs`
}
}
_actor = null
constructor(actor) {
super(actor)
this._actor = actor
this.render(true)
}
static async #openItemBrowser(event, target) {
this._actor?.sheet.options.actions.openItemBrowser().bind(this)(event, target)
}
static async #newItem(event, target) {
this._actor?.sheet.options.actions.newItem.bind(this)(event, target)
}
static async #openEmbeddedDocument(event, target) {
this._actor?.sheet.options.actions.openEmbeddedDocument.bind(this)(event, target)
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
options.window.title = `${this.document.name}: Inventar`
return options
}
async _prepareContext(context, options, object) {
const actorData = this.document
context.system = actorData.system
context.flags = actorData.flags
context.equipments = []
context.carryingweight = 0
actorData.itemTypes["Equipment"].sort((a, b) => a.sort - b.sort).forEach((item, index) => {
// worn items are halved weight
let effectiveWeight = item.system.weight ?? 0
if (this.document.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,
worn: this.document.isWorn(item._id)
})
context.carryingweight += item.system.quantity * effectiveWeight;
})
context.maxcarryingcapacity = actorData.system.attribute.kk.aktuell
context.carryingpercentage = Math.min((context.carryingweight / context.maxcarryingcapacity) * 100, 100);
context.wealth = 0
actorData.itemTypes["Equipment"].forEach(coin => {
if (coin.system.category.indexOf("Währung") !== -1) {
context.wealth += (coin.system.quantity * coin.system.currencyDenominator)
}
})
return context
}
_onRender(context, options) {
}
}

View File

@ -1,111 +0,0 @@
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
export class StandaloneHealth extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 716},
classes: ['dsa41', 'sheet', 'actor', 'character', 'standalone', 'health'],
tag: 'form',
actions: {
openEmbeddedDocument: StandaloneHealth.#openEmbeddedDocument,
setWounds: StandaloneHealth.#setWounds,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/character/standalone/health.hbs`
}
}
_actor = null
constructor(actor) {
super(actor)
this._actor = actor
this.render(true)
}
static async #openEmbeddedDocument(event, target) {
this._actor?.sheet.options.actions.openEmbeddedDocument.bind(this)(event, target)
}
static async #setWounds(event, target) {
this._actor?.sheet.options.actions.setWounds.bind(this)(event, target)
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
options.window.title = `${this.document.name}: Gesundheit`
return options
}
async _prepareContext(context, options, object) {
const actorData = this.document
context.system = actorData.system
context.flags = actorData.flags
context.derived = this.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
const findEquipmentOnSlot = (slot, setNumber, object) => {
return object.items.get(object.system.heldenausruestung[setNumber]?.[slot])
}
context.inidice = actorData.system.ini.wuerfel
context.inivalue = actorData.system.ini.aktuell
context.inimod = actorData.system.ini.mod
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")
context.colorfulDice = game.settings.get('DSA_4-1', 'optional_colorfuldice')
context.aupper = Math.min((actorData.system.aup.aktuell / actorData.system.aup.max) * 100, 100)
context.lepper = Math.min((actorData.system.lep.aktuell / actorData.system.lep.max) * 100, 100)
context.keper = Math.min((actorData.system.kap.aktuell / actorData.system.kap.max) * 100, 100)
context.aspper = Math.min((actorData.system.asp.aktuell / actorData.system.asp.max) * 100, 100)
context.lepcurrent = actorData.system.lep.aktuell ?? 0
context.aupcurrent = actorData.system.aup.aktuell ?? 0
context.aspcurrent = actorData.system.asp.aktuell ?? 0
context.kapcurrent = actorData.system.kap.aktuell ?? 0
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.withErschoepfung = game.settings.get("DSA_4-1", "optional_erschoepfung")
context.ueberanstrengung = actorData.system.ueberanstrengung
context.erschoepfung = actorData.system.erschoepfung.aktuell
context.maxErschoepfung = actorData.system.erschoepfung.max
context.erschoepfungFilled = []
for (let i = 1; i <= context.maxErschoepfung; i++) {
context.erschoepfungFilled[i] = i <= context.erschoepfung
}
context.effects = []
for (let i = 0; i < actorData.appliedEffects.length; i++) {
const item = actorData.appliedEffects[i]
context.effects.push(item.name)
}
return context
}
_onRender(context, options) {
}
}

View File

@ -1,151 +0,0 @@
import {LiturgyData} from "../../data/miracle/liturgyData.mjs";
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
export class StandaloneLiturgies extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'actor', 'character', 'standalone', 'liturgies'],
tag: 'form',
actions: {
openEmbeddedDocument: StandaloneLiturgies.#openEmbeddedDocument,
openLiturgyDialog: StandaloneLiturgies.#openLiturgyDialog,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/character/standalone/liturgies.hbs`
}
}
_actor = null
constructor(actor) {
super(actor)
this._actor = actor
this.render(true)
}
static async #openEmbeddedDocument(event, target) {
this._actor?.sheet.options.actions.openEmbeddedDocument.bind(this)(event, target)
}
static async #openLiturgyDialog(event, target) {
this._actor?.sheet.options.actions.openLiturgyDialog.bind(this)(event, target)
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
options.window.title = `${this.document.name}: Segnungen und Liturgien`
return options
}
async _prepareContext(context, options, object) {
const actorData = this.document
context.system = actorData.system
context.flags = actorData.flags
context.derived = this.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
context.liturgies = [];
context.blessings = [];
actorData.itemTypes.Blessing.forEach((item, index) => {
context.blessings.push({
deity: item.system.gottheit,
value: item.system.wert
})
})
actorData.itemTypes.Liturgy.forEach((item, index) => {
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,
fav: item.getFlag("DSA_4-1", "favourite"),
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;
return context
}
_onRender(context, options) {
}
}

View File

@ -1,127 +0,0 @@
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
export class StandaloneSkills extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'actor', 'character', 'standalone', 'skills'],
tag: 'form',
actions: {
rollCombatSkill: StandaloneSkills.#rollCombatSkill,
rollSkill: StandaloneSkills.#rollSkill,
openEmbeddedDocument: StandaloneSkills.#openEmbeddedDocument,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/character/standalone/skills.hbs`
}
}
_actor = null
constructor(actor) {
super(actor)
this._actor = actor
this.render(true)
}
static async #rollCombatSkill(event, target) {
this._actor?.sheet.options.actions.rollCombatSkill.bind(this)(event, target)
}
static async #rollSkill(event, target) {
this._actor?.sheet.options.actions.rollSkill.bind(this)(event, target)
}
static async #openEmbeddedDocument(event, target) {
this._actor?.sheet.options.actions.openEmbeddedDocument.bind(this)(event, target)
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
options.window.title = `${this.document.name}: Talente`
return options
}
async _prepareContext(context, options, object) {
const actorData = this.document
context.system = actorData.system
context.flags = actorData.flags
context.derived = this.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
const prepareEigenschaftRoll = (actorData, name) => {
if (name && name !== "*") {
return actorData.system.attribute[name.toLowerCase()].aktuell
} else {
return 0
}
}
context.skills = {};
context.flatSkills = [];
actorData.itemTypes.Skill.forEach((item, index) => {
const talentGruppe = item.system.gruppe;
const eigenschaften = Object.values(item.system.probe);
const werte = [
{name: eigenschaften[0], value: prepareEigenschaftRoll(actorData, eigenschaften[0])},
{name: eigenschaften[1], value: prepareEigenschaftRoll(actorData, eigenschaften[1])},
{name: eigenschaften[2], value: prepareEigenschaftRoll(actorData, eigenschaften[2])}
]
if (context.skills[talentGruppe] == null) {
context.skills[talentGruppe] = [];
}
const obj = {
type: "talent",
gruppe: talentGruppe,
name: item.name.replace(/Sprachen kennen/g, "Sprache:").replace(/Lesen\/Schreiben/g, "Schrift: "),
taw: "" + item.system.taw,
tawPath: `system.items.${index}.taw`,
werte,
rollEigenschaft1: werte[0].value,
rollEigenschaft2: werte[1].value,
rollEigenschaft3: werte[2].value,
eigenschaft1: werte[0].name,
eigenschaft2: werte[1].name,
eigenschaft3: werte[2].name,
probe: `(${eigenschaften.join("/")})`,
id: item._id,
at: item.system.at,
pa: item.system.pa,
komplexität: item.system.komplexität,
fav: item.getFlag("DSA_4-1", "favourite")
};
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);
}
)
return context
}
_onRender(context, options) {
}
}

View File

@ -1,102 +0,0 @@
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
export class StandaloneSpells extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'actor', 'character', 'standalone', 'spells'],
tag: 'form',
actions: {
openEmbeddedDocument: StandaloneSpells.#openEmbeddedDocument,
castSpell: StandaloneSpells.castSpell,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/character/standalone/spells.hbs`
}
}
_actor = null
constructor(actor) {
super(actor)
this._actor = actor
this.render(true)
}
static async #openEmbeddedDocument(event, target) {
this._actor?.sheet.options.actions.openEmbeddedDocument.bind(this)(event, target)
}
static async castSpell(event, target) {
this._actor?.sheet.options.actions.castSpell.bind(this)(event, target)
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
options.window.title = `${this.document.name}: Zauber und Rituale`
return options
}
async _prepareContext(context, options, object) {
const actorData = this.document
context.spells = []
context.system = actorData.system
context.flags = actorData.flags
context.derived = this.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
const cleanUpMerkmal = (merkmale) => {
return merkmale.split(",").map((merkmal) => merkmal.trim())
}
const prepareEigenschaftRoll = (actorData, name) => {
if (name && name !== "*") {
return actorData.system.attribute[name.toLowerCase()].aktuell
} else {
return 0
}
}
actorData.itemTypes["Spell"].forEach((item, index) => {
const eigenschaften = item.system.probe;
const werte = [
{name: eigenschaften[0], value: prepareEigenschaftRoll(actorData, eigenschaften[0])},
{name: eigenschaften[1], value: prepareEigenschaftRoll(actorData, eigenschaften[1])},
{name: eigenschaften[2], value: prepareEigenschaftRoll(actorData, eigenschaften[2])}
]
context.spells.push({
id: item._id,
name: item.name,
zfw: item.system.zfw,
hauszauber: item.system.hauszauber,
merkmal: 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,
fav: item.getFlag("DSA_4-1", "favourite")
})
})
context.hasSpells = context.spells.length > 0
return context
}
_onRender(context, options) {
}
}

View File

@ -21,8 +21,7 @@ export default {
description: item.system.description,
isAdvantage: !item.system.nachteil,
isDisadvantage: item.system.nachteil,
isBadAttribute: item.system.schlechteEigenschaft,
fav: item.getFlag("DSA_4-1", "favourite")
isBadAttribute: item.system.schlechteEigenschaft
})
} else {
context.flaws.push({
@ -33,8 +32,7 @@ export default {
description: item.system.description,
isAdvantage: !item.system.nachteil,
isDisadvantage: item.system.nachteil,
isBadAttribute: item.system.schlechteEigenschaft,
fav: item.getFlag("DSA_4-1", "favourite")
isBadAttribute: item.system.schlechteEigenschaft
})
}
}
@ -45,7 +43,6 @@ export default {
context.specialAbilities.push({
id: item._id,
name: item.system.value ? item.system.value : item.name,
fav: item.getFlag("DSA_4-1", "favourite")
});
}
);

View File

@ -1,3 +1,5 @@
import {ActionManager} from "../actions/action-manager.mjs";
export default {
_prepareContext: async (context, object) => {
@ -13,6 +15,9 @@ export default {
return object.items.get(object.system.heldenausruestung[setNumber]?.[slot])
}
const am = new ActionManager(actorData)
context.actions = am.evaluate().filter(action => action.type !== ActionManager.ATTACK)
context.inidice = actorData.system.ini.wuerfel
context.inivalue = actorData.system.ini.aktuell
context.inimod = actorData.system.ini.mod
@ -21,35 +26,9 @@ export default {
context.lepper = Math.min((actorData.system.lep.aktuell / actorData.system.lep.max) * 100, 100)
context.keper = Math.min((actorData.system.kap.aktuell / actorData.system.kap.max) * 100, 100)
context.aspper = Math.min((actorData.system.asp.aktuell / actorData.system.asp.max) * 100, 100)
context.lepcurrent = actorData.system.lep.aktuell ?? 0
context.aupcurrent = actorData.system.aup.aktuell ?? 0
context.aspcurrent = actorData.system.asp.aktuell ?? 0
context.kapcurrent = actorData.system.kap.aktuell ?? 0
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.withErschoepfung = game.settings.get("DSA_4-1", "optional_erschoepfung")
context.ueberanstrengung = actorData.system.ueberanstrengung
context.erschoepfung = actorData.system.erschoepfung.aktuell
context.maxErschoepfung = actorData.system.erschoepfung.max
context.erschoepfungFilled = []
for (let i = 1; i <= context.maxErschoepfung; i++) {
context.erschoepfungFilled[i] = i <= context.erschoepfung
}
context.effects = []
for (let i = 0; i < actorData.appliedEffects.length; i++) {
const item = actorData.appliedEffects[i]
context.effects.push(item.name)
}
return context
@ -58,7 +37,7 @@ export default {
},
_getTabConfig: (group) => {
group.tabs.push({id: "health", group: "sheet", label: "Gesundheit"})
group.tabs.push({id: "combat", group: "sheet", label: "Kampf"})
},
template: `systems/DSA_4-1/templates/actor/character/tab-health.hbs`
template: `systems/DSA_4-1/templates/actor/character/tab-combat.hbs`
}

View File

@ -1,5 +1,7 @@
import {PlayerCharacterDataModel} from "../../data/character.mjs";
export default {
_prepareContext: (context, actor, thisObject) => {
_prepareContext: (context) => {
const actorData = context.document
context.spells = []
@ -50,11 +52,9 @@ export default {
for (let setIndex = 0; setIndex < maxSets; setIndex++) {
context.sets.push({
tab: "pane" + (setIndex + 1),
label: romanNumerals[setIndex],
tab: "set" + (setIndex + 1),
name: romanNumerals[setIndex],
index: setIndex,
actorId: actorData.id,
setEquipped: actorData.system.setEquipped === setIndex,
slots: [
{
target: "links",
@ -131,8 +131,6 @@ export default {
]
})
}
context.selectedTab = thisObject.selectedTab ?? context.sets[0].tab
context.setEquipped = actorData.system.setEquipped
return context
},
@ -152,50 +150,9 @@ export default {
}
}).bind(thisObject.element);
const tabs = new foundry.applications.ux.Tabs({
navSelector: ".set .tabs.sets",
contentSelector: ".set .tab",
initial: thisObject.actor.system.setEquipped ? "pane" + (thisObject.actor.system.setEquipped + 1) : "pane1",
group: "set-tabs",
callback: (event, tab, tabName) => {
thisObject.selectedTab = tabName
thisObject.element.querySelectorAll(tab._contentSelector).forEach(
(tab) => {
if (tab.dataset["tab"] === tabName) {
tab.classList.add("active")
} else {
tab.classList.remove("active")
}
}
)
}
})
tabs.bind(thisObject.element)
new ContextMenu(
thisObject.element,
".paperdoll .equipped",
[
{
name: "Abrüsten",
icon: '<i class="fa-solid fa-suitcase"></i>',
callback: (targetElement) => {
const {setId, target} = targetElement.dataset
const updateObject = thisObject.document.getEquipmentSetUpdateObject()
delete updateObject[`system.heldenausruestung.${setId}.${target}`]
thisObject.document.update(updateObject)
},
condition: (target) => {
const {itemId} = target.dataset
return thisObject.document.isWorn(itemId)
}
},
], {jQuery: false})
new ContextMenu(
thisObject.element,
".inventory-table .equipment",
".equipment",
[
{
name: "Abrüsten",
@ -323,7 +280,7 @@ export default {
name: "Aus dem Inventar entfernen",
icon: '<i class="fa-solid fa-trash"></i>',
callback: (target) => {
game.DSA41.socket.executeAsGM("removeFromLootTable", thisObject.document.id, target.dataset.itemId)
thisObject.document.deleteEmbeddedDocuments('Item', [target.dataset.itemId])
},
condition: (target) => {
const {itemId} = target.dataset
@ -333,7 +290,7 @@ export default {
], {jQuery: false});
},
_getTabConfig: (group) => {
group?.tabs.push({id: "equipment", group: "sheet", label: "Ausrüstung"})
group.tabs.push({id: "equipment", group: "sheet", label: "Ausrüstung"})
},
template: `systems/DSA_4-1/templates/actor/character/tab-equipment.hbs`
}

View File

@ -1,4 +1,4 @@
import {LiturgyData} from "../../data/miracle/liturgyData.mjs";
import {LiturgyData} from "../../data/miracle/liturgydata.mjs";
export default {
_prepareContext: (context) => {
@ -57,17 +57,17 @@ export default {
// sort by rank
const rankData = LiturgyData.getRankOfLiturgy(item.system, deity)
if (rankData) {
console.log(rankData)
let {index, name, lkp, mod, costKaP} = rankData;
insertObject["count" + name] = insertObject["count" + name] + 1;
insertObject[name]?.push({
insertObject[name].push({
id: item._id,
name: item.name,
lkpReq: lkp,
lkpMod: mod,
costKaP,
fav: item.getFlag("DSA_4-1", "favourite"),
rank: index, // get effective liturgy rank based on deity
liturgiekenntnis: deity,
})
@ -104,7 +104,7 @@ export default {
_getTabConfig: (group, thisObject) => {
const hasLiturgies = thisObject.document.items.filter(p => p.type === "Liturgy").length > 0 ?? false
if (hasLiturgies) {
group?.tabs.push({id: "liturgies", group: "sheet", label: "Liturgien"})
group.tabs.push({id: "liturgies", group: "sheet", label: "Liturgien"})
}
},
template: `systems/DSA_4-1/templates/actor/character/tab-liturgies.hbs`

View File

@ -14,7 +14,7 @@ export default {
},
_getTabConfig: (group) => {
group?.tabs.push({id: "meta", group: "sheet", label: "Meta"})
group.tabs.push({id: "meta", group: "sheet", label: "Meta"})
},
template: `systems/DSA_4-1/templates/actor/character/tab-meta.hbs`
}

View File

@ -2,6 +2,7 @@ export default {
_prepareContext: (context) => {
const actorData = context.document
context.spells = []
context.system = actorData.system
context.flags = actorData.flags
context.derived = context.document.system
@ -50,8 +51,7 @@ export default {
id: item._id,
at: item.system.at,
pa: item.system.pa,
komplexität: item.system.komplexität,
fav: item.getFlag("DSA_4-1", "favourite")
komplexität: item.system.komplexität
};
if (talentGruppe === "Kampf") {

View File

@ -14,21 +14,14 @@ export default {
return merkmale.split(",").map((merkmal) => merkmal.trim())
}
const prepareEigenschaftRoll = (actorData, name) => {
if (name && name !== "*") {
return actorData.system.attribute[name.toLowerCase()].aktuell
} else {
return 0
}
}
actorData.itemTypes["Spell"].forEach((item, index) => {
Object.values(actorData.items).forEach((item, index) => {
if (item.type === "Spell") {
const eigenschaften = item.system.probe;
const werte = [
{name: eigenschaften[0], value: prepareEigenschaftRoll(actorData, eigenschaften[0])},
{name: eigenschaften[1], value: prepareEigenschaftRoll(actorData, eigenschaften[1])},
{name: eigenschaften[2], value: prepareEigenschaftRoll(actorData, eigenschaften[2])}
{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,
@ -42,9 +35,8 @@ export default {
eigenschaft1: werte[0].name,
eigenschaft2: werte[1].name,
eigenschaft3: werte[2].name,
fav: item.getFlag("DSA_4-1", "favourite")
})
}
})
context.hasSpells = context.spells.length > 0

View File

@ -1,5 +1,5 @@
import Advsf from "./character/advsf.mjs"
import Health from "./character/health.mjs"
import Combat from "./character/combat.mjs"
import Effects from "./character/effects.mjs"
import Equipment from "./character/equipment.mjs"
import Liturgies from "./character/liturgies.mjs"
@ -11,21 +11,14 @@ import {CombatActionDialog} from "../dialog/combatAction.mjs";
import {ActionManager} from "./actions/action-manager.mjs";
import {DefenseActionDialog} from "../dialog/defenseAction.mjs";
import {RestingDialog} from "../dialog/restingDialog.mjs";
import {Character} from "../documents/character.mjs";
import {LiturgyDialog} from "../dialog/liturgyDialog.mjs";
import {TalentDialog} from "../dialog/talentDialog.mjs";
import {AttributeDialog} from "../dialog/attributeDialog.mjs";
import {ItemBrowserDialog} from "../dialog/itemBrowserDialog.mjs";
import * as EquipmentDocument from "../documents/equipment.mjs";
import {StandaloneADVSF} from "./character-standalone/advsf.mjs";
import {StandaloneSkills} from "./character-standalone/skills.mjs";
import {Bagpack} from "./character-standalone/bagpack.mjs";
import {StandaloneSpells} from "./character-standalone/spells.mjs";
import {StandaloneLiturgies} from "./character-standalone/liturgies.mjs";
import {StandaloneHealth} from "./character-standalone/health.mjs";
import {SpellDialog} from "../dialog/spellDialog.mjs";
const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
const {ContextMenu} = foundry.applications.ux
class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
@ -46,33 +39,19 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
actions: {
rollCombatSkill: CharacterSheet.#rollCombatSkill,
rollSkill: CharacterSheet.#rollSkill,
rollFlaw: CharacterSheet.rollFlaw,
rollFlaw: CharacterSheet.#rollFlaw,
rollAttribute: CharacterSheet.#rollAttribute,
editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage,
openEmbeddedDocument: CharacterSheet.openEmbeddedDocument,
openEmbeddedDocument: CharacterSheet.#openEmbeddedDocument,
openCultureDocument: CharacterSheet.#openCultureDocument,
openSpeciesDocument: CharacterSheet.#openSpeciesDocument,
openCombatAction: CharacterSheet.#openCombatAction,
openLiturgyDialog: CharacterSheet.openLiturgyDialog,
openSpellDialog: CharacterSheet.openSpellDialog,
castSpell: CharacterSheet.castSpell,
openLiturgyDialog: CharacterSheet.#openLiturgyDialog,
progressCooldown: CharacterSheet.#progressCooldown,
cancelCooldown: CharacterSheet.#cancelCooldown,
activateCooldown: CharacterSheet.#activateCooldown,
rest: CharacterSheet.#startResting,
removeEffect: CharacterSheet.#removeEffect,
rollDamage: CharacterSheet.#rollDamage,
openItemBrowser: CharacterSheet.openItemBrowser,
newItem: CharacterSheet.addNewItem,
toggleFav: CharacterSheet.toggleFav,
openStandaloneADVSF: CharacterSheet.#openStandaloneADVSF,
openStandaloneSkills: CharacterSheet.#openStandaloneSkills,
openBagpack: CharacterSheet.#openBagpack,
openStandaloneSpells: CharacterSheet.#openStandaloneSpells,
openStandaloneLiturgies: CharacterSheet.#openStandaloneLiturgies,
openStandaloneHealth: CharacterSheet.#openStandaloneHealth,
setWounds: CharacterSheet.#setWounds,
switchSet: CharacterSheet.#switchSet
}
}
@ -81,7 +60,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
sheet: {
tabs: [],
initial: 'meta'
},
}
}
/** @inheritDoc */
@ -98,16 +77,15 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
advsf: {
template: Advsf.template
},
health: {
template: Health.template
combat: {
template: Combat.template
},
equipment: {
template: Equipment.template,
scrollable: ['.inventory']
scrollable: ['']
},
skills: {
template: Skills.template,
scrollable: ['.tab.skills']
template: Skills.template
},
spells: {
template: Spells.template
@ -117,7 +95,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
},
effects: {
template: Effects.template
},
}
}
@ -152,7 +130,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
}).render(true)
}
static async rollFlaw(event, target) {
static async #rollFlaw(event, target) {
new AttributeDialog(this.document, target.dataset.itemId).render(true)
}
@ -163,12 +141,12 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
cooldowns.splice(cooldownId, 1)
if (cooldown) {
cooldown.current = cooldown.current + 1
cooldown.current = cooldown.current - 1
}
cooldowns.push(cooldown)
this.document.update({"system.cooldowns": cooldowns.sort((a, b) => a.current - b.current)})
ui.notifications.info(`Abklingzeit von ${cooldown.data.title} um 1 Aktion reduziert`)
ui.notifications.info(`Abklingzeit von ${cooldown.data.maneuver.name} um 1 Aktion reduziert`)
}
static async #cancelCooldown(event, target) {
@ -187,26 +165,26 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
const cooldowns = this.document.system.cooldowns
const cooldown = this.document.system.cooldowns[cooldownId]
if (cooldown && cooldown.current >= cooldown.start) {
if (cooldown && cooldown.current <= 0) {
const am = new ActionManager(this.document)
const action = new Function(`return ${cooldown.data.maneuver}`)
const action = am.evaluate().find(action => action.name === cooldown.data.maneuver.id)
if (action) {
action()(this.document.system.cooldowns, {...cooldown.data, actor: this.document})
action.activate(cooldowns, {...cooldown.data, actor: this.document})
}
}
cooldowns.splice(cooldownId, 1)
this.document.update({"system.cooldowns": cooldowns.sort((a, b) => a.current - b.current)})
ui.notifications.info(`${cooldown.data.title} ausgeführt`)
ui.notifications.info(`${cooldown.data.maneuver.name} ausgeführt`)
}
/**
*
* @param {MouseEvent} event
*/
static openEmbeddedDocument(event) {
static #openEmbeddedDocument(event) {
let dataset = event.target.dataset
if (!dataset.itemId && !dataset.id) {
dataset = event.target.parentElement.dataset
@ -225,33 +203,21 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
}
static #openCombatAction(event, target) {
let {weapon, skill} = target.dataset
switch (target.dataset.mode) {
case "attack":
new CombatActionDialog(this.document, {weapon, skill}).render(true)
new CombatActionDialog(this.document).render(true)
break
case "defense":
new DefenseActionDialog(this.document, {weapon, skill}).render(true)
new DefenseActionDialog(this.document).render(true)
break
}
}
static openLiturgyDialog(event, target) {
static #openLiturgyDialog(event, target) {
const {id, lkp, deity} = target.dataset
new LiturgyDialog(this.document, lkp, id, deity).render(true)
}
static openSpellDialog(event, target) {
const {itemId} = target.dataset
this.document.itemTypes["Spell"]?.find(p => p.id === itemId)?.sheet.render(true)
}
static castSpell(event, target) {
const {itemId} = target.dataset
new SpellDialog(this.document, itemId).render(true)
}
static #startResting(event, target) {
const dialog = new RestingDialog(this.document)
@ -273,65 +239,6 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
}
static async openItemBrowser(event, target) {
new ItemBrowserDialog(this.document).render(true)
}
static async addNewItem(event, target) {
let item = new EquipmentDocument.Equipment({
name: "Neuer Gegenstand",
type: "Equipment",
})
const items = await this.document.createEmbeddedDocuments("Item", [item])
items[0].sheet.render(true)
}
static async toggleFav(event, target) {
const {itemId} = target.dataset
const doc = this.document.items.find(p => p.id === itemId)
if (doc) {
const previous = doc.getFlag("DSA_4-1", "favourite") ?? false
doc.setFlag("DSA_4-1", "favourite", !previous)
}
}
static async #openStandaloneADVSF(event, target) {
new StandaloneADVSF(this.document)
}
static async #openStandaloneHealth(event, target) {
new StandaloneHealth(this.document)
}
static async #openStandaloneSkills(event, target) {
new StandaloneSkills(this.document)
}
static async #openBagpack(event, target) {
new Bagpack(this.document)
}
static async #openStandaloneSpells(event, target) {
new StandaloneSpells(this.document)
}
static async #openStandaloneLiturgies(event, target) {
new StandaloneLiturgies(this.document)
}
static async #setWounds(event, target) {
const {value} = target.dataset
this.document.update({"system.wunden.aktuell": value})
this.render(true)
}
static async #switchSet(event, target) {
const {id} = target.dataset
this.document.update({"system.setEquipped": id})
this.render(true)
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
@ -342,7 +249,6 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
return options
}
/**
* Handle form submission
* @this {AdvantageSheet}
@ -353,49 +259,16 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object)
}
static async #rollDamage(event, target) {
let {weapon, isRanged} = target.dataset
isRanged = isRanged == "true"
weapon = this.document.items.get(weapon)
if (weapon) {
const damageFormula = isRanged ? weapon.system.rangedAttackDamage : weapon.system.meleeAttackDamage
const calculation = await foundry.applications.api.DialogV2.prompt({
window: {title: game.i18n.format("COMBAT_DIALOG_TP.windowTitle")},
content: `<div><label><span>${game.i18n.format("COMBAT_DIALOG_TP.regularFormula")}</span><input type="text" name="formula" value="${damageFormula}"></label></div><div><label><span>${game.i18n.format("COMBAT_DIALOG_TP.bonusDamage")}</span><input type="text" name="bonusDamage" value="0"></label></div>`,
ok: {
label: game.i18n.format("COMBAT_DIALOG_TP.buttonText"),
callback: (event, button, dialog) => {
return {
formula: button.form.elements.formula.value,
bonusDamage: button.form.elements.bonusDamage.value
}
}
}
});
const sanitisedFormula = calculation.formula.replace(/wW/g, "d")
const suffix = calculation.bonusDamage >= 0 ? "+" + calculation.bonusDamage : calculation.bonusDamage
let r = new Roll(sanitisedFormula + suffix, this.document.getRollData());
const label = `Schadenswurf`
await r.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.document}),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
})
}
await this.document.update(formData.object) // Note: formData.object
}
_getTabsConfig(group) {
const tabs = foundry.utils.deepClone(super._getTabsConfig(group))
Meta._getTabConfig(tabs, this)
Social._getTabConfig(tabs, this)
Meta._getTabConfig(tabs, this);
Social._getTabConfig(tabs, this);
Advsf._getTabConfig(tabs, this)
Health._getTabConfig(tabs, this)
Combat._getTabConfig(tabs, this)
Equipment._getTabConfig(tabs, this)
Skills._getTabConfig(tabs, this)
Spells._getTabConfig(tabs, this)
@ -457,6 +330,13 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
context.img = actorData.img
context.effects = actorData.effects ?? []
context.maxWounds = actorData.system.wunden.max ?? 3
context.wounds = actorData.system.wunden.gesamt ?? 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")
@ -471,11 +351,8 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
context.lepper = Math.min((actorData.system.lep.aktuell / actorData.system.lep.max) * 100, 100)
context.keper = Math.min((actorData.system.kap.aktuell / actorData.system.kap.max) * 100, 100)
context.aspper = Math.min((actorData.system.asp.aktuell / actorData.system.asp.max) * 100, 100)
context.lepcurrent = actorData.system.lep.aktuell ?? 0
context.aupcurrent = actorData.system.aup.aktuell ?? 0
context.aspcurrent = actorData.system.asp.aktuell ?? 0
context.kapcurrent = actorData.system.kap.aktuell ?? 0
const fernkampf = actorData.findEquipmentOnSlot("fernkampf", actorData.system.setEquipped, actorData)
const links = actorData.findEquipmentOnSlot("links", actorData.system.setEquipped, actorData)
@ -484,19 +361,18 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
if (fernkampf) {
const fkitems = fernkampf.system.rangedSkills.map((skillInQuestion) => actorData.items.find(p => p.name === skillInQuestion))
fkitems?.forEach(skill => {
if (skill) {
fkitems.forEach(async skill => {
const obj = await skill
context.attacks.push({
name: skill.name,
id: fernkampf._id,
skillId: skill._id,
name: obj.name,
using: fernkampf.name,
isRanged: true,
at: `${this.document.system.fk.aktuell + skill.system.at}`,
atroll: `1d20cs<${this.document.system.fk.aktuell + obj.system.at}`,
at: `${this.document.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}`,
})
}
})
}
if (links) {
@ -507,17 +383,18 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
meitems.push(item)
}
})
meitems?.forEach(skill => {
meitems.forEach(skill => {
const obj = skill
context.attacks.push({
name: obj.name,
id: links._id,
skillId: skill._id,
using: links.name,
isRanged: false,
atroll: `1d20cs<${this.document.system.at.links.aktuell + obj.system.at + links.system.attackModifier}`, // TODO consider adding W/M
at: `${this.document.system.at.links.aktuell + obj.system.at + links.system.attackModifier}`,
paroll: `1d20cs<${this.document.system.pa.links.aktuell + obj.system.pa + links.system.parryModifier}`, // TODO consider adding W/M
pa: `${this.document.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}`,
})
})
@ -530,82 +407,30 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
meitems.push(item)
}
})
meitems?.forEach(skill => {
meitems.forEach(skill => {
const obj = skill
context.attacks.push({
name: obj.name,
id: rechts._id,
skillId: skill._id,
using: rechts.name,
isRanged: false,
atroll: `1d20cs<${this.document.system.at.rechts.aktuell + obj.system.at + rechts.system.attackModifier}`, // TODO consider adding W/M
at: `${this.document.system.at.rechts.aktuell + obj.system.at + rechts.system.attackModifier}`,
paroll: `1d20cs<${this.document.system.pa.rechts.aktuell + obj.system.pa + rechts.system.parryModifier}`, // TODO consider adding W/M
pa: `${this.document.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}`,
})
})
}
context.favourites = actorData.items.filter(item => item.getFlag("DSA_4-1", "favourite") === true).map(item => {
let id = item.id
let t = null
switch (item.type) {
case "Spell":
t = "systems/DSA_4-1/templates/ui/partial-mini-rollable-spell-button.hbs"
break;
case "Skill":
switch (item.system.gruppe) {
case "Kampf":
t = "systems/DSA_4-1/templates/ui/partial-mini-rollable-weaponskill-button.hbs"
break;
case "Sprachen":
t = "systems/DSA_4-1/templates/ui/partial-mini-rollable-language-button.hbs"
break;
default:
t = "systems/DSA_4-1/templates/ui/partial-mini-rollable-button.hbs"
}
break;
case "Liturgy":
t = "systems/DSA_4-1/templates/ui/partial-mini-rollable-liturgy-button.hbs"
break;
default:
t = null
}
let obj = Object.assign({}, item)
obj.fav = item.getFlag("DSA_4-1", "favourite")
obj.id = id
obj.group = item.system.gruppe ?? null
if (t) {
obj.template = t
}
return obj
})
context.cooldowns = actorData.system.cooldowns ?? []
context.cooldowns.forEach(cooldown => {
let weapon = null
let target = null
let tooltip = cooldown.data.title
if (cooldown.data.weapon) {
weapon = this.document.itemTypes["Equipment"].find(p => p._id === cooldown.data.weapon)
tooltip += `<br/>Waffe: ${weapon.name}`
}
if (cooldown.data.target) {
target = game.actors.get(game.scenes.current.tokens.find(p => p._id === cooldown.data.target).actorId)
tooltip += `<br/>Ziel: ${target.name}`
}
cooldown.title = cooldown.data.title
const weapon = this.document.itemTypes["Equipment"].find(p => p._id === cooldown.data.weapon)
const skill = this.document.itemTypes["Skill"].find(p => p._id === cooldown.data.skillId)
const target = game.actors.get(game.scenes.current.tokens.find(p => p._id === cooldown.data.target).actorId)
cooldown.progress = ((cooldown.current / cooldown.start) * 100) + "%"
if (cooldown.start - cooldown.current > 0) {
cooldown.tooltip = tooltip + `<br/>Aktionen verbleibend: ${cooldown.start - cooldown.current}<hr/><i class="fa-solid fa-computer-mouse"></i>: 1 Aktion aufwenden`
} else {
cooldown.tooltip = tooltip + `<br/>Aktionen verbleibend: ${cooldown.start - cooldown.current}<hr/><i class="fa-solid fa-computer-mouse"></i>: Aktion durchführen`
}
cooldown.tooltip = `${cooldown.data.maneuver.name}<br/>Waffe:${weapon.name}<br/>Ziel: ${target.name}<br/>Wurfziel: ${cooldown.data.targetNumber}<br/>Aktionen verbleibend: ${cooldown.current}`
})
context.hasSpells = actorData.itemTypes["Spell"].length > 0
@ -672,11 +497,11 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
case "advsf":
await Advsf._prepareContext(context, this.document)
break
case "health":
await Health._prepareContext(context, this.document)
case "combat":
await Combat._prepareContext(context, this.document)
break
case "equipment":
await Equipment._prepareContext(context, this.document, this)
await Equipment._prepareContext(context, this.document)
break
case "skills":
await Skills._prepareContext(context, this.document)
@ -694,23 +519,11 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
return context
}
_onPosition(position) {
if (position.width < 300) {
this.element.classList.add("tiny")
this.element.querySelector(".sidebuttons").style.left = (position.width + position.left) + "px"
this.element.querySelector(".sidebuttons").style.top = (position.top) + "px"
} else {
this.element.classList.remove("tiny")
}
}
_onRender(context, options) {
Meta._onRender(context, options, this.element)
Social._onRender(context, options, this.element)
Advsf._onRender(context, options, this)
Health._onRender(context, options, this.element)
Combat._onRender(context, options, this.element)
Effects._onRender(context, options, this.element)
Equipment._onRender(context, options, this)
Liturgies._onRender(context, options, this.element)
@ -722,28 +535,18 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
return true
}
async _onDrop(event) {
const data = TextEditor.implementation.getDragEventData(event)
const data = TextEditor.implementation.getDragEventData(event);
const actor = this.actor;
const targetDocument = this.actor.itemTypes["Equipment"].find(p => p._id === event.target.dataset['itemId'])
if (event.target.dataset["target"] && event.target.dataset["setId"]) {
const documentClass = foundry.utils.getDocumentClass(data.type)
if (documentClass) {
const document = await documentClass.fromDropData(data)
const {setId, target} = event.target.dataset
const updateObject = this.actor.getEquipmentSetUpdateObject()
updateObject[`system.heldenausruestung.${setId}.${target}`] = document.id
await this.actor.update(updateObject)
}
return
}
//const allowed = Hooks.call("dropActorSheetData", actor, this, data);
//if (allowed === false) return;
// Dropped Documents
const documentClass = foundry.utils.getDocumentClass(data.type)
const documentClass = foundry.utils.getDocumentClass(data.type);
if (documentClass) {
const document = await documentClass.fromDropData(data)
const document = await documentClass.fromDropData(data);
if (document.type === "Equipment" || document.type === "Advantage" || document.type === "Spell" || document.type === "Liturgy" || document.type === "ActiveEffect" || document.type === "SpecialAbility") {
// No duplication by moving items from one actor to another
@ -763,7 +566,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
} else {
if (document.parent && document.parent !== this.actor) {
game.DSA41.socket.executeAsGM("removeFromLootTable", document.parent.id, document._id)
document.parent.items.get(document._id).delete()
}
await this._onDropDocument(event, document)

View File

@ -25,6 +25,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
static TABS = {
sheet: {
tabs: [],
initial: 'meta'
}
}
@ -65,19 +66,6 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
tabs.tabs.push(tab)
}
}
if (!game.user.isGM) {
if (this.document.system.visibility.meta) {
tabs.initial = 'meta'
} else if (this.document.system.visibility.attacks) {
tabs.initial = 'attacks'
} else if (this.document.system.visibility.description) {
tabs.initial = 'description'
} else if (this.document.system.visibility.loot) {
tabs.initial = 'loot'
}
}
return tabs
}
@ -101,7 +89,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object)
await this.document.update(formData.object) // Note: formData.object
}
static #openEmbeddedDocument(event, target) {
@ -112,8 +100,8 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
static async #removeAttack(evt) {
const {index} = evt.srcElement.dataset;
let sans = Array.from(this.document.system.attacks)
sans.splice(index, 1)
let sans = Array.from(this.document.system.attacks);
sans.splice(index, 1);
await this.document.update({'system.attacks': sans})
}
@ -125,7 +113,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
speaker: ChatMessage.getSpeaker({actor: this.document}),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
})
});
}
static async #addAttack() {
@ -152,11 +140,11 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options)
const actorData = context.document
const context = await super._prepareContext(options);
const actorData = context.document;
context.attacks = []
context.actor = actorData
context.attacks = [];
context.actor = actorData;
actorData.system.attacks.forEach((attack, index) => {
context.attacks.push({
@ -192,7 +180,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
})
return context
return context;
}
@ -211,7 +199,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
//const allowed = Hooks.call("dropActorSheetData", actor, this, data);
// if (allowed === false) return;
// Dropped Documents
const documentClass = foundry.utils.getDocumentClass(data.type)
const documentClass = foundry.utils.getDocumentClass(data.type);
if (documentClass) {
const document = await documentClass.fromDropData(data);
@ -221,7 +209,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
document.parent.items.get(document._id).delete()
}
await this._onDropDocument(event, document)
await this._onDropDocument(event, document);
}
}
}
@ -238,7 +226,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
dragstart: this._onDragStart.bind(this),
drop: this._onDrop.bind(this)
}
}).bind(this.element)
}).bind(this.element);
}

View File

@ -44,7 +44,7 @@ export class CultureSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object)
await this.document.update(formData.object) // Note: formData.object
}
/** @override */

View File

@ -1,228 +0,0 @@
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
export class DeitySheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 848},
classes: ['dsa41', 'sheet', 'item', 'deity'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: DeitySheet.#onSubmitForm
},
window: {
resizable: true,
},
actions: {
editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage,
openLiturgy: DeitySheet.#openLiturgySheet,
removeLiturgy: DeitySheet.#removeLiturgy,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/item/deity-sheet.hbs`
},
}
/**
* Handle form submission
* @this {SpeciesSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
formData.object["system.miraclePlus"] = formData.object.miraclePlus.split(",")
formData.object["system.miracleMinus"] = formData.object.miracleMinus.split(",")
delete formData.object.miraclePlus
delete formData.object.miracleMinus
await this.document.update(formData.object) // Note: formData.object
}
static async #openLiturgySheet(event, target) {
const {rank, liturgyId} = target.dataset
if (liturgyId && rank) {
this.document.system.liturgies["rank" + rank].find(p => p._id === liturgyId)?.sheet.render(true, {editable: false})
}
}
static async #removeLiturgy(event, target) {
const {rank, liturgyId} = target.dataset
if (liturgyId && rank) {
const idx = this.document.system.liturgies["rank" + rank].findIndex(p => p._id === liturgyId)
this.document.system.liturgies["rank" + rank].splice(idx, 1)
const thisUpdateObject = {}
thisUpdateObject["system.liturgies.rank" + rank] = this.document.system.liturgies
this.document.update(thisUpdateObject)
this.render({parts: ["form"]})
}
}
_configureRenderOptions(options) {
super._configureRenderOptions(options)
if (options.window) {
options.window.title = this.document.name
}
return options
}
/** @override */
async _prepareContext(options) {
// const context = await super._prepareContext(options)
context.system = this.document.system
context.name = this.document.name
context.img = this.document.img
context.description = this.document.description
context.liturgies = {
rank0: [],
rank1: [],
rank2: [],
rank3: [],
rank4: [],
rank5: [],
rank6: [],
rank7: [],
rank8: [],
}
context.miraclePlus = this.document.system.miraclePlus?.join(",")
context.miracleMinus = this.document.system.miracleMinus?.join(",")
this.document.system.liturgies.rank0.forEach(liturgyUuid => {
fromUuid(liturgyUuid).then(liturgy => {
context.liturgies.rank0.push({
id: liturgy._id,
name: liturgy.name,
})
})
})
this.document.system.liturgies.rank1.forEach(liturgyUuid => {
fromUuid(liturgyUuid).then(liturgy => {
context.liturgies.rank1.push({
id: liturgy._id,
name: liturgy.name,
})
})
})
this.document.system.liturgies.rank2.forEach(liturgyUuid => {
fromUuid(liturgyUuid).then(liturgy => {
context.liturgies.rank2.push({
id: liturgy._id,
name: liturgy.name,
})
})
})
this.document.system.liturgies.rank3.forEach(liturgyUuid => {
fromUuid(liturgyUuid).then(liturgy => {
context.liturgies.rank3.push({
id: liturgy._id,
name: liturgy.name,
})
})
})
this.document.system.liturgies.rank4.forEach(liturgyUuid => {
fromUuid(liturgyUuid).then(liturgy => {
context.liturgies.rank4.push({
id: liturgy._id,
name: liturgy.name,
})
})
})
this.document.system.liturgies.rank5.forEach(liturgyUuid => {
fromUuid(liturgyUuid).then(liturgy => {
context.liturgies.rank5.push({
id: liturgy._id,
name: liturgy.name,
})
})
})
this.document.system.liturgies.rank6.forEach(liturgyUuid => {
fromUuid(liturgyUuid).then(liturgy => {
context.liturgies.rank6.push({
id: liturgy._id,
name: liturgy.name,
})
})
})
this.document.system.liturgies.rank7.forEach(liturgyUuid => {
fromUuid(liturgyUuid).then(liturgy => {
context.liturgies.rank7.push({
id: liturgy._id,
name: liturgy.name,
})
})
})
this.document.system.liturgies.rank8.forEach(liturgyUuid => {
fromUuid(liturgyUuid).then(liturgy => {
context.liturgies.rank8.push({
id: liturgy._id,
name: liturgy.name,
})
})
})
return context
}
async _canDragDrop() {
return true
}
_onRender(context, options) {
new foundry.applications.ux.DragDrop.implementation({
dropSelector: ".liturgy-drops",
permissions: {
drop: this._canDragDrop.bind(this)
},
callbacks: {
drop: this._onDrop.bind(this)
}
}).bind(this.element);
}
async _onDrop(event, target) {
const data = TextEditor.implementation.getDragEventData(event);
const documentClass = foundry.utils.getDocumentClass(data.type)
if (documentClass) {
const document = await documentClass.fromDropData(data);
if (document.type === "Liturgy") {
// process and drop it rightly
const {rank} = event.target.dataset
if (rank) {
let rankData = this.document.system.liturgies["rank" + rank]
rankData.push(document.uuid)
const updateData = {}
updateData["system.liturgies.rank" + rank] = rankData
this.document.update(updateData)
this.render({parts: ["form"]})
}
}
}
}
}

View File

@ -1,6 +1,17 @@
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
class EquipmentSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/**
* @typedef ApplicationTab
* @property {string} id
* @property {string} group
* @property {boolean} active
* @property {string} cssClass
* @property {string} [label]
* @property {string} [icon]
* @property {string} [tooltip]
*/
export class EquipmentSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
@ -311,5 +322,3 @@ class EquipmentSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
}
}
export default EquipmentSheet

View File

@ -106,7 +106,7 @@ export class GroupSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
}
async #onUpdateCharacterSettings(data) {
if (data.type === "Character") {
if (data.type === "character") {
// update group
let settings = {...this.document.system.settings}
@ -251,7 +251,7 @@ export class GroupSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
// Drag-drop
new foundry.applications.ux.DragDrop.implementation({
dragSelector: ".inventory-table .equipment",
dropSelector: ".inventory",
dropSelector: ".inventory-table",
permissions: {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this)
@ -265,10 +265,10 @@ export class GroupSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
// Update Group Members when either an Actor was moved into the linked Folder or removed from the linked Folder
Hooks.on('updateActor', (data) => {
if (data._id !== this.document._id) { // dont update yourself when you update yourself... baka!
if (data.type === "Character" && data.folder?._id === this.document.system.groupId) {
if (data.type === "character" && data.folder?._id === this.document.system.groupId) {
this.#onUpdateCharacterSettings(data)
this.render()
} else if (data.type === "Character") {
} else if (data.type === "character") {
this.render()
}
}

View File

@ -20,8 +20,6 @@ export class MerchantSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
resizable: true,
},
actions: {
buy: MerchantSheet.#buyWare,
editItem: MerchantSheet.#openEmbeddedDocument,
editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage,
editServiceImage: MerchantSheet.#editServiceImage,
editNewServiceImage: MerchantSheet.#editNewServiceImage,
@ -83,63 +81,6 @@ export class MerchantSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
await this.document.update(formData.object) // Note: formData.object
}
static async #buyWare(event, target) {
const {itemId} = target.dataset
const item = this.document.items.get(itemId)
let selections = ''
game.actors.filter(p => p.isOwner && p.type === "Character").forEach(actor => {
selections += `<option value=${actor.id}>${actor.name}</option>`
})
const actorId = await foundry.applications.api.DialogV2.prompt({
window: {title: `${item.name} kaufen mit wem?`},
content: `<select name="actor">${selections}</select>`,
ok: {
label: `Kaufen`,
callback: (event, button, dialog) => button.form.elements.actor.value
}
});
if (actorId) { // ignore the following when dialog was cancelled
const actor = game.actors.get(actorId)
const canBuy = await actor.reduceWealth(item.system.price)
if (canBuy) { // returns false when the wealth cant be reduced sufficiently
actor.createEmbeddedDocuments('Item', [item]).then(documents => {
documents[0].update({'system.quantity': 1})
})
game.DSA41.socket.executeAsGM("buyFromLootTable", this.document.id, item.id)
ChatMessage.create({
user: game.user._id,
speaker: {actor},
content: `hat ${item.name} für ${game.DSA41.displayCurrency(item.system.price)} gekauft`,
type: CONST.CHAT_MESSAGE_TYPES.IC
})
} else {
ui.notifications.error(item.name + " ist zu teuer für " + actor.name)
}
}
}
/**
*
* @param {MouseEvent} event
*/
static #openEmbeddedDocument(event) {
let dataset = event.target.dataset
if (!dataset.itemId && !dataset.id) {
dataset = event.target.parentElement.dataset
}
const id = dataset.itemId ?? dataset.id
if (this.document.isOwner) { // only shop owner can change stock and price
this.document.items.get(id).sheet.render(true)
}
}
static async #removeService(event, target) {
const {rowId} = target.dataset;
@ -263,7 +204,6 @@ export class MerchantSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
context.description = this.document.system.description
context.goods = this.document.itemTypes["Equipment"] ?? []
context.services = this.document.system.services
context.isOwner = this.document.isOwner
return context
}

View File

@ -12,15 +12,9 @@ export class SpecialAbilitySheet extends HandlebarsApplicationMixin(DocumentShee
closeOnSubmit: false,
handler: SpecialAbilitySheet.#onSubmitForm
},
actions: {
addRequirement: SpecialAbilitySheet.#addRequirement,
removeRequirement: SpecialAbilitySheet.#removeRequirement,
addMod: SpecialAbilitySheet.#addMod,
removeMod: SpecialAbilitySheet.#removeMod,
saveVariant: SpecialAbilitySheet.#saveVariant
}
}
static TABS = {
sheet: {
tabs: [
@ -38,12 +32,7 @@ export class SpecialAbilitySheet extends HandlebarsApplicationMixin(DocumentShee
specialability: {
template: `systems/DSA_4-1/templates/item/specialability/tab-specialability.hbs`
},
variants: {
template: `systems/DSA_4-1/templates/ui/tab-variants.hbs`
}
}
_instance = null
_configureRenderOptions(options) {
super._configureRenderOptions(options)
@ -55,173 +44,6 @@ export class SpecialAbilitySheet extends HandlebarsApplicationMixin(DocumentShee
return options
}
constructor(...args) {
super(...args);
SpecialAbilitySheet._instance = this
}
static async #addRequirement(event, target) {
const selections =
"<option value='talentMin'>Mindest Talentwert</option>" +
"<option value='talentMax'>Maximal Talentwert</option>" +
"<option value='attributeMin'>Mindest Attributswert</option>" +
"<option value='attributeMax'>Maximal Attributswert</option>" +
"<option value='compare'>Vergleich</option>"
const type = await foundry.applications.api.DialogV2.prompt({
window: {title: "Neue Voraussetzung"},
content: `<select name="type">${selections}</select>`,
ok: {
label: `Hinzufügen`,
callback: (event, button, dialog) => button.form.elements.type.value
}
});
if (type) {
let newReq = {}
switch (type) {
case 'talentMin':
newReq = {
talent: 'Klettern',
minValue: '0'
}
break;
case 'talentMax':
newReq = {
talent: 'Zechen',
maxValue: '0'
}
break;
case 'attributeMin':
newReq = {
attribute: 'ge',
minValue: '0'
}
break;
case 'attributeMax':
newReq = {
attribute: 'mu',
maxValue: '0'
}
break;
case 'compare':
newReq = {
compare: {
ownAttribute: 'ini.aktuell',
operation: 'eq',
targetAttribute: 'ini.aktuell'
}
}
break;
}
const lastIndex = Object.keys(SpecialAbilitySheet._instance._currentSelectedVariant.requirements).length
SpecialAbilitySheet._instance._currentSelectedVariant.requirements[lastIndex] = newReq
SpecialAbilitySheet._instance.render({parts: ["form", "advantage", "variants"]})
}
}
static async #removeRequirement(event, target) {
const {index} = target.dataset
if (index) {
delete SpecialAbilitySheet._instance._currentSelectedVariant.requirements[index]
}
}
static async #addMod(event, target) {
const selections =
"<option value='talent'>Talent</option>" +
"<option value='talentGroup'>Talentgruppe</option>" +
"<option value='attribute'>Attribute</option>" +
"<option value='trait'>Merkmal</option>"
const type = await foundry.applications.api.DialogV2.prompt({
window: {title: "Neuer Modifikator"},
content: `<select name="type">${selections}</select>`,
ok: {
label: `Hinzufügen`,
callback: (event, button, dialog) => button.form.elements.type.value
}
});
if (type) {
let newReq = {}
switch (type) {
case 'talent':
newReq = {
talent: 'Klettern',
value: '0'
}
break;
case 'talentGroup':
newReq = {
talentGruppe: 'Gesellschaft',
value: '0'
}
break;
case 'trait':
newReq = {
merkmal: 'Elementar',
value: '0'
}
break;
case 'attribute':
newReq = {
name: 'ge',
value: '0'
}
break;
}
const lastIndex = Object.keys(SpecialAbilitySheet._instance._currentSelectedVariant.mod).length
SpecialAbilitySheet._instance._currentSelectedVariant.mod[lastIndex] = newReq
SpecialAbilitySheet._instance.render({parts: ["form", "advantage", "variants"]})
}
}
static async #removeMod(event, target) {
const {index} = target.dataset
if (index) {
delete SpecialAbilitySheet._instance._currentSelectedVariant.mod[index]
}
}
static async #saveVariant(event, target) {
/**
* @type {HTMLFormElement}
*/
const form = AdvantageSheet._instance.form
let flattenObject = {}
Object.values(form).forEach(input => {
if (input.name.startsWith('mod') || input.name.startsWith('requirements')) {
flattenObject[`${input.name}`] = input.value
}
if (input.name === "vName") {
flattenObject[`name`] = input.value
}
})
let auswahl = SpecialAbilitySheet._instance.document.system.auswahl
const fo = foundry.utils.expandObject(flattenObject)
auswahl[AdvantageSheet._instance._currentSelectedVariantIndex] = {
name: fo.name,
mod: Object.values(fo.mod),
requirements: Object.values(fo.requirements)
}
SpecialAbilitySheet._instance.document.update({system: {auswahl}})
}
/**
* Handle form submission
* @this {EquipmentSheet}
@ -231,34 +53,10 @@ export class SpecialAbilitySheet extends HandlebarsApplicationMixin(DocumentShee
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
if (!form.querySelector('.tab.specialability.active')) {
const obj = foundry.utils.expandObject(formData.object)
if (obj.mod) this._currentSelectedVariant.mod = obj.mod
if (obj.vName) this._currentSelectedVariant.name = obj.vName
if (obj.requirements) this._currentSelectedVariant.requirements = obj.requirements
} else {
delete formData.object.mod
delete formData.object.vName
delete formData.object.requirements
delete formData.object.variant
await this.document.update(formData.object) // Note: formData.object
}
this.render({parts: ["form", "specialability", "variants"]})
}
_getTabsConfig(group) {
const tabs = foundry.utils.deepClone(super._getTabsConfig(group))
if (this.document.system.auswahl) {
tabs.tabs.push({id: 'variants', group: 'sheet', label: 'Varianten'})
}
return tabs
}
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options);
@ -284,35 +82,7 @@ export class SpecialAbilitySheet extends HandlebarsApplicationMixin(DocumentShee
})
context.hasModality = context.system.value != null
context.name = specialabilityData.name
context.variantChoices = {}
context.variants = []
specialabilityData.system.auswahl?.forEach(variant => {
context.variantChoices[variant.name] = variant.name
context.variants.push(variant)
})
context.currentSelectedVariantName = this._currentSelectedVariant?.name
context.currentSelectedVariant = this._currentSelectedVariant
context.currentSelectedVariantIndex = this._currentSelectedVariantIndex
return context;
}
_onRender(context, options) {
if (this._selectedVariant == null) {
this._selectedVariant = this.document.system.auswahl[0].name
this._currentSelectedVariant = this.document.system.auswahl?.find(p => p.name === this._selectedVariant)
this._currentSelectedVariantIndex = this.document.system.auswahl?.findIndex(p => p.name === this._selectedVariant)
}
this.element.querySelector('select[name="variant"]').addEventListener('change', (event, target) => {
if (event.target.value != this._selectedVariant) {
this._selectedVariant = event.target.value
this._currentSelectedVariant = this.document.system.auswahl?.find(p => p.name === this._selectedVariant)
this._currentSelectedVariantIndex = this.document.system.auswahl?.findIndex(p => p.name === this._selectedVariant)
}
})
}
}

View File

@ -1,4 +1,4 @@
import {LiturgyData} from "../data/miracle/liturgyData.mjs";
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
import {Blessing} from "../documents/blessing.mjs";
import {Profession} from "../documents/profession.mjs";
import {Culture} from "../documents/culture.mjs";
@ -383,7 +383,7 @@ export class XmlImport {
}
async #addSpellsFromCompendiumByNameToActor(spellName, zfw, representation, hauszauber, actor) {
const compendiumOfSpells = game.packs.get('DSA_4-1.Spells')
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) {
@ -392,8 +392,7 @@ export class XmlImport {
try {
const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [spell]))[0]
embeddedDocument.update({system: {zfw: zfw, hauszauber: hauszauber}})
embeddedDocument.setFlag("DSA_4-1", "representation", representation)
embeddedDocument.update({system: {zfw: zfw, hauszauber: hauszauber, repräsentation: representation}});
} catch (error) {
console.error(`${spell} not found in items`, error)
}

View File

@ -1,7 +1,6 @@
{
"name": "Aufmerksamkeit",
"seite": "54",
"gruppe": "Kampf",
"requirement": [
{
"attribute": "attribute.in.aktuell",

View File

@ -2,7 +2,6 @@
"todo": "Wir brauchen hier ein Konztept!",
"name": "Ausfall",
"seite": "59",
"gruppe": "Kampf",
"sfPflicht": true,
"requirement": [
{

View File

@ -1,7 +1,6 @@
{
"name": "Ausweichen",
"value": ["Ausweichen I","Ausweichen II","Ausweichen III"],
"gruppe": "Kampf",
"auswahl": [
{
"name": "Ausweichen I",

View File

@ -1,7 +1,6 @@
{
"name": "Befreiungsschlag",
"seite": "60",
"gruppe": "Kampf",
"sfPflicht": true,
"requirement": [
{

View File

@ -1,6 +1,5 @@
{
"name": "Beidhändiger Kampf",
"gruppe": "Kampf",
"value": ["Beidhändiger Kampf I","Beidhändiger Kampf II"],
"auswahl": [
{

View File

@ -1,6 +1,5 @@
{
"name": "Betäubungsschlag",
"gruppe": "Kampf",
"seite": "61",
"sfPflicht": true,
"requirement": [

View File

@ -1,6 +1,5 @@
{
"name": "Binden",
"gruppe": "Kampf",
"seite": "67",
"requirement": [
{

View File

@ -1,6 +1,5 @@
{
"name": "Blindkampf",
"gruppe": "Kampf",
"seite": "67",
"requirement": [
{

View File

@ -1,6 +1,5 @@
{
"name": "Defensiver Kampfstil",
"gruppe": "Kampf",
"seite": "81",
"requirement": [
{

View File

@ -1,6 +1,5 @@
{
"name": "Doppelangriff",
"gruppe": "Kampf",
"seite": "61",
"sfPflicht": true,
"requirement": [

View File

@ -1,6 +1,5 @@
{
"name": "Entwaffnen",
"gruppe": "Kampf",
"seite": "61",
"sfPflicht": true,
"requirement": [

View File

@ -1,6 +1,5 @@
{
"name": "Festnageln",
"gruppe": "Kampf",
"seite": "62",
"sfPflicht": true,
"requirement": [

View File

@ -1,6 +1,5 @@
{
"name": "Finte",
"gruppe": "Kampf",
"seite": "62",
"sfPflicht": true,
"requirement": [

View File

@ -1,6 +1,5 @@
{
"name": "Formation",
"gruppe": "Kampf",
"seite": "62",
"sfPflicht": true,
"requirement": [

View File

@ -1,6 +1,5 @@
{
"name": "Kampfreflexe",
"gruppe": "Kampf",
"seite": "",
"requirement": [
{

View File

@ -1,6 +1,5 @@
{
"name": "Meisterliches Entwaffnen",
"gruppe": "Kampf",
"seite": "61",
"sfPflicht": true,
"requirement": [

View File

@ -1,57 +0,0 @@
{
"seite": "11",
"name": "ABVENENUM REINE SPEISE",
"probe": [
"KL",
"KL",
"FF"
],
"probeMod": "+Mod.",
"technik": "Der Elf spricht bhasama venya bhaza yalza über die zu reinigende Nahrung.",
"zauberdauer": {
"min": "15 Aktionen"
},
"wirkung": "Der Zauber reinigt Nahrungsmittel und Ge tränke von sämtlichen Giften und Krankheitskeimen; verdorbene Nahr ung wird frisch und genießbar. Die Zauberprobe ist um die doppelte Stufe des Giftes oder der beim Verzehr zu befürchtenden Krankheit erschwert. Verdorbene Nahrung kann je nach Zustand einen Zuschlag von 2 bis 12 Punkten auf die Probe bedeuten. Dieser Zauber versetzt Nahrung in einen Zustand, die dem Spruchanwender nicht gefährlich werden kann. Da es aber von dem Entwickler des Spruches abhängt, in welchem Zustand Nahrung als gefährlich angesehen werden muss und in welchem nicht, variiert die genaue Wirkung je nach Repräsentation recht erheblich. So wandelt die elfische Repräsentation selbst Wein in Traubensaft um, während die Achazform des Spruches manche für Menschen und Elfen giftige Mahlzeiten unverändert lässt. Gift, das sich nicht in Nahrung befindet, wird von dem Zauber nicht als solches erkannt, weswegen das Gift auf der Klinge eines Meuchlers oder in der Phiole eines Alchimisten nicht verändert wird. Wenn allerdings zum Beispiel in einer Pilzpfanne giftige Pilze enthalten sind, dann wirkt der Zauber sehr wohl.",
"kosten": [
{
"cost": 4,
"repräsentation": ""
},
{
"cost": 3,
"repräsentation": "Schamane"
}
],
"zielobjekt": "Nahrungsmenge (mehrere Objekte) nach AsP-Aufwand",
"reichweite": "1 Schritt",
"wirkungsdauer": "augenblicklich",
"modifikationen": "Zauberdauer, Reichweite",
"varianten": [
{
"name": "Schutz vor Übelkeit",
"description": "Nahrung, die an sich nicht giftig ist, deren Genuss aber heftige Übelkeit erzeugt, wird in einen Zustand versetzt, der als harmlos gelten kann. So ist es zum Beispiel möglich, auch Meerwasser in Trinkwasser zu verwandeln. Horasische Sahnetorten werden jedoch nur von den wenigsten Repräsentationen in Mitleidenschaft gezogen.",
"mod": "+5",
"limit": ""
},
{
"name": "Schutz vor Vergiftung",
"description": "Bewahrt die Nahrung für die nächsten ZfP* Stunden vor jeglicher in diesem Zeitraum geschehener Vergiftung und jedem Verschimmeln oder Verderben. Der Zaubernde muss sich hierbei selbst einen Aufschlag auf die Zauberprobe auferlegen, und zwar in Höhe der Stufe der Krankheit/des Giftes, gegen die der Schutz höchstens wirksam sein soll. Eine bereits vergiftete Speise wird dabei entgiftet.Vereinzelt existieren eingeschränkte Variationen dieses Spruches, die (bei geringerem Probenaufschlag und AsP-Aufwand) nur gegen gewisse Giftgruppen (Schlangengifte) oder gar nur gegen einzelne Gifte und Krankheiten wirken.",
"mod": "+5",
"limit": ""
}
],
"reversalis": "bewirkt das schlagartige Verfaulen und Verschimmeln von L ebensmitteln.",
"antimagie": "kann in einer Zone des OBJEKT ENTZAUBERN nur erschwert gesprochen wer den; lässt sich wegen der Wirkungsdauer augenblicklich nicht mittels Antimagie widerrufen.",
"merkmal": "Objekt",
"komplexität": "C",
"repräsentation": {
"Druide": 6,
"Elf": 6,
"Hexe": 6,
"Magier": 6,
"Geomant": 5,
"Achaz": 4,
"Schelm": 4
},
"info": "Dieser ursprünglich von den Waldelfen stammende Spruch ist bei fast allen Zauberkundigen weit verbreitet. An allen Orten, an denen die lokalen Potentaten in ständiger Furcht vor Meuchlern und Giftmischern leben, werden Meisterinnen und Meister des ABVENENUM mit Kusshand in den Hofstaat aufgenommen (wenn sie nach einem Demonstrationszaubern den Genuss der vorher präparierten Narung überlebt haben)."
}

View File

@ -1,46 +0,0 @@
{
"seite": "15",
"name": "ADLERAUGE LUCHSENOHR",
"probe": [
"KL",
"IN",
"FF"
],
"probeMod": "",
"technik": "Die Elfe legt die Hände an die Schläfen und konzentriert sich auf die Melodie des a'dao bunda visya'roel.",
"zauberdauer": {
"normal": "5 Aktionen"
},
"wirkung": "Das gesamte Wahrnehmungsvermögen der Zaubernden (alle fünf Sinne) wird so stark verbessert, dass alle Proben auf das Talent Sinnesschärfe um ZfP* Punkte erleichtert werden. Man kann auf große Distanz feinste Geräusche, Gerüche oder Bewegungen wahrnehmen. Der Zauber ermöglicht keine Nachtsicht, aber verstärkt tatsächlich vorhandene Sinneswahrnehmung. Geschärfte Sinne können zu Desorientierung führen, wenn plötzliche Reize auftreten (Selbstbeherrschungs-Probe nötig). Mit ADLERAUGE können auch andere Sinneszauber wie KATZENAUGEN (Nachtsicht) oder WARMES BLUT (Wärmesicht) verstärkt werden.",
"kosten": [
{
"cost": 4,
"repräsentation": ""
}
],
"zielobjekt": "Einzelperson, freiwillig",
"reichweite": "selbst",
"wirkungsdauer": "1 Spielrunde (A)",
"modifikationen": "Zauberdauer, Reichweite, Wirkungsdauer",
"varianten": [
{
"name": "Einzelsinn schärfen",
"description": "Der Zauber betrifft nur einen einzigen Sinn, der dafür umso stärker wird. Alle Sinnesschärfe-Proben für diesen Sinn sind um die doppelten ZfP* erleichtert.",
"mod": "+4",
"limit": ""
}
],
"reversalis": "Die Sinne der Verzauberten werden abgestumpft, dadurch verliert sie ZfP* Punkte Sinnesschärfe.",
"antimagie": "HELLSICHT TRÜBEN und EIGENSCHAFT WIEDERHERSTELLEN heben die Wirkung des Spruches auf; kann in entsprechenden Zonen nur erschwert gewirkt werden.",
"merkmal": "Hellsicht, Eigenschaften",
"komplexität": "B",
"repräsentation": {
"Druide": 3,
"Elf": 7,
"Hexe": 3,
"Magier": 3,
"Geomant": 3,
"Achaz": 3
},
"info": "Der ADLERAUGE ist Ursprung vieler elfischer Sagen über Sinnesleistungen und gilt als Kanonspruch. Jede Hellsicht-Akademie lehrt ihn, ebenso Norburg und Donnerbach."
}

View File

@ -1,75 +0,0 @@
{
"seite": "16",
"name": "ADLERSCHWINGE WOLFSGESTALT",
"probe": [
"MU",
"IN",
"GE"
],
"probeMod": "+Mod.",
"technik": "Der Elf kauert sich auf den Boden zusammen und spricht leise die Formel adao valva iama — es folgt der Name des Tieres, in das er sich verwandeln will.",
"zauberdauer": {
"normal": "20 Aktionen"
},
"wirkung": "Der Zaubernde nimmt die Gestalt des beim Erlernen des Zaubers gewählten Tieres an. Kleidung und Ausrüstung werden nicht mitverwandelt. Der Elf behält seinen Verstand, erhält jedoch die körperlichen Eigenschaften, Sinne und Lebensenergie des Tieres. Zaubern im Tiergestalt ist nicht möglich. Körperliche Aktivitäten entsprechen den Möglichkeiten des Tieres, eingeschränkt durch das Maß an Menschenverständnis des Elfen. Lebenspunkte werden bei Rückverwandlung umgerechnet: Hat der Elf vor der Rückwandlung die Hälfte seiner LeP verloren, so hat er auch danach die Hälfte seiner LeP. Verletzungen werden übertragen. Der Zauber ist ein Kernzauber der Elfenmagie und wird häufig zur Jagd oder als Schutz genutzt.",
"kosten": [
{
"cost": 4,
"repräsentation": ""
}
],
"zielobjekt": "Einzelperson, freiwillig",
"reichweite": "selbst",
"wirkungsdauer": "nach AsP-Aufwand (Meisterentscheid)",
"modifikationen": "Zauberdauer, Erzwingen, Kosten, Wirkungsdauer",
"varianten": [
{
"name": "Achaz-Repräsentation",
"description": "Erlaubt lediglich die Verwandlung in Reptilien oder Amphibien.",
"mod": "",
"limit": ""
},
{
"name": "Extreme Tiere",
"description": "Bei Tieren mit extrem hoher LE steigen die Kosten um 1 bis 7 AsP (mindestens 7 AsP, wenn LE mindestens doppelt so hoch ist wie die des Helden).",
"mod": "",
"limit": ""
},
{
"name": "Bewusste Gestalt",
"description": "Die Wirkungsdauer ist veränderbar, wenn der Zaubernde seine Aufmerksamkeit ändert.",
"mod": "+7",
"limit": "11"
},
{
"name": "Weitere Tierarten",
"description": "Erlaubt das Erlernen zusätzlicher Tierarten, nach den Regeln für Hexalogien (WdZ 383ff).",
"mod": "",
"limit": ""
},
{
"name": "Grenzenlose Gestalt",
"description": "Wenn mindestens eine Version mit ZfW 15+ beherrscht wird, können alle Tiere bis Pferdgröße erlernt werden.",
"mod": "+7",
"limit": ""
},
{
"name": "Haut des Seelentiers",
"description": "Der Elf verschmilzt mit seinem Seelentier und übernimmt dessen Instinkte vollständig. Dauer: 1 Tag. Alle Selbstbeherrschungsproben +10 erleichtert.",
"mod": "+7",
"limit": ""
}
],
"reversalis": "Verwandelt eine Tierform zurück in einen Elfen.",
"antimagie": "Kann in einer Zone des VERWANDLUNG BEENDEN nur erschwert gewirkt werden; endet durch Antimagie-Spruch.",
"merkmal": "Form",
"komplexität": "D",
"repräsentation": {
"Druide": 2,
"Elf": 6,
"Hexe": 2,
"Magier": 3,
"Achaz": 3
},
"info": "Die elfische Variante dient vor allem dazu, sich mit dem Seelentier zu verbinden, sodass jeder Elf beim Erlernen ein zu ihm passendes Tier wählt. Manche Waldelfen beherrschen mehrere Varianten (Seelen- und Sipptier). Bei Firn- und Auelfen ist das Verwandeln in Seelentiere verbreitet, aber das Beherrschen unterschiedlicher Tierformen unbekannt. Menschen fällt dieser Zauber schwer; er ist nur an wenigen Akademien (z. B. Lowangen, Kuslik, Punin, Tuzak, Verformung zu Lowangen) bekannt. Die Formel wird auch in Zauberstern, Silberhaar und Corpus Mutantis behandelt."
}

View File

@ -1,37 +0,0 @@
{
"seite": "22",
"name": "ANALYS ARCANSTRUKTUR",
"probe": [
"KL",
"KL",
"IN"
],
"probeMod": "+Mod",
"technik": "Die Magierin fixiert das Ziel ihres Interesses und spricht die Formel.",
"zauberdauer": {
"min": "1 SR"
},
"wirkung": "Ermöglicht es, magische Wirkungsstrukturen und Gewebe aus den Fäden und Bahnen der Kraft zu erkennen. Damit können Artefakte, magische Wesen oder Zauber identifiziert und klassifiziert werden. Je nach Höhe der ZfP* können Merkmale, Repräsentationen, Matrixfehler oder versteckte Zauber ermittelt werden. Erweiterte Effekte: Erkennen von Zaubertradition, Signatur des Schöpfers, Art von Artefakten, Wirkungen von Elixieren oder Tränken. Je länger die Konzentration aufrechterhalten wird, desto mehr Details können sichtbar werden.",
"kosten": [
{
"cost": 6,
"repräsentation": ""
}
],
"zielobjekt": "Einzelobjekt, Einzelwesen",
"reichweite": "1 Schritt",
"wirkungsdauer": "Identisch mit Zauberdauer, nach AsP-Aufwand",
"modifikationen": "Zauberdauer, Kosten, Reichweite",
"reversalis": "keine Wirkung",
"antimagie": "HELLSICHT TRÜBEN und SCHLEIER DER UNWISSENHEIT erschweren die Analyse.",
"merkmal": "Hellsicht, Metamagie",
"komplexität": "D",
"repräsentation": {
"Magier": 6,
"Achaz": 3,
"Druide": 2,
"Geomant": 2,
"Hexe": 2
},
"info": "Bietet weitgehende Freiheit, Zauberstrukturen zu beschreiben. In vielen Werken enthalten. Nahe Verwandtschaft zum OCULUS ASTRALIS. Wird fast jeder Akademie gelehrt."
}

View File

@ -1,55 +0,0 @@
{
"seite": "28",
"name": "ARMATRUTZ",
"probe": [
"IN",
"GE",
"KO"
],
"probeMod": "",
"technik": "Die Elfe streicht mit den Händen über ihre Brust, während sie ama tharza spricht.",
"zauberdauer": {
"normal": "3 Aktionen"
},
"wirkung": "Die Zaubernde erhält eine zusätzliche 'stählerne Haut', die ihren natürlichen Rüstungsschutz erhöht. Die zusätzliche Rüstung beträgt maximal ZfW/2 Punkte. Sie schützt gegen materielle Angriffe (auch Dämonen- und Elementarattacken), jedoch nicht gegen Zauber, die direkten Schaden verursachen. Bei Bissen von Tieren wie Ratten oder Fledermäusen schützt der Zauber ebenfalls.",
"kosten": [
{
"cost": 4,
"additionalFormula": "RS * RS - ZFP*/2",
"variables": [
"RS",
"ZFP*"
],
"repräsentation": ""
}
],
"zielobjekt": "Einzelperson, freiwillig",
"reichweite": "selbst",
"wirkungsdauer": "maximal eine Spielrunde (A)",
"modifikationen": "Zauberdauer, Kosten, Reichweite (Berührung), Wirkungsdauer",
"varianten": [
{
"name": "Körperschild",
"description": "Der Zauber wirkt nur auf einen Teil des Körpers. AsP-Kosten halbiert, aber mindestens 3 AsP.",
"mod": -4,
"limit": ""
},
{
"name": "Kraft des Fakirs",
"description": "Verleiht Resistenz gegen kleine Schadenswirkungen. Opfer kann z. B. schmerzlos über Scherben gehen.",
"mod": -3,
"limit": 7
}
],
"reversalis": "hebt einen wirkenden ARMATRUTZ auf",
"antimagie": "EIGENSCHAFT WIEDERHERSTELLEN und ERZBANN können die Wirkung beenden; in den entsprechenden Zonen erschwert.",
"merkmal": "Eigenschaften, Elementar (Erz)",
"komplexität": "B",
"repräsentation": {
"Elf": 6,
"Magier": 6,
"Hexe": 3,
"Druide": 2
},
"info": "Ursprünglich ein elfischer Schutzzauber, später in menschlichen Akademien verbreitet. Auch Hexen und Druiden nutzen ihn, um Leib und Leben zu schützen. Vergleichbar mit dem Flim Flam als Basiszauber."
}

View File

@ -1,112 +0,0 @@
{
"seite": "30",
"name": "ATTRIBUTO",
"probe": [
"KL",
"CH",
"*"
],
"probeMod": "[gesteigerte Eigenschaft]",
"technik": "Der Magier berührt seinen Gefährten mit beiden Händen und spricht die Formel. Je nach Eigenschaft z. B. Oberarm für Körperkraft, Schläfen für Klugheit, Augen für Intuition, Hände für Fingerfertigkeit usw.",
"zauberdauer": {
"normal": "30 Aktionen"
},
"wirkung": "Der Zauber hebt die in der Probe durch 'Eigenschaft' bezeichneten Werte des Verzauberten für die Dauer einer Stunde um ZfP* / 3 Punkte.",
"kosten": [
{
"cost": 7,
"repräsentation": ""
},
{
"cost": 5,
"repräsentation": "Schelm"
}
],
"zielobjekt": "Einzelperson, Einzelwesen (s. u.), freiwillig",
"reichweite": "Berührung",
"wirkungsdauer": "1 Stunde",
"modifikationen": "Zauberdauer, Reichweite (Selbst), Wirkungsdauer",
"varianten": [
{
"name": "Mut",
"description": "Wirkt auch auf Tiere beliebiger Art. In diesem Fall ist die Probe um 3 Punkte erschwert.",
"mod": "",
"limit": ""
},
{
"name": "Klugheit",
"description": "Kann auch auf Vertraute, Reit- und Haustiere angewandt werden, sogar auf Pflanzen. 35 Punkte für Tiere, 7 Punkte für Pflanzen. Erlaubt auch eine Art 'Erinnerungslesen'.",
"mod": "",
"limit": ""
},
{
"name": "Intuition",
"description": "Bei Lebewesen ohne höhere Verstandesfunktionen (Tiere) wird Wahrnehmung und Aufmerksamkeit um 3 Punkte gesteigert.",
"mod": "",
"limit": ""
},
{
"name": "Charisma",
"description": "Wirkt ausschließlich auf kulturschaffende Wesen.",
"mod": "",
"limit": ""
},
{
"name": "Fingerfertigkeit",
"description": "Kann nur auf Lebewesen angewandt werden, die über einen Greifhand verfügen.",
"mod": "",
"limit": ""
},
{
"name": "Gewandtheit",
"description": "Erhöht GE von Vierbeinern oder Schlangen: +3 Punkte, Vögel/Schlangen +5 Punkte, Achtbeiner/Flugwesen +7 Punkte.",
"mod": "",
"limit": ""
},
{
"name": "Konstitution",
"description": "Verändert die KO von Tieren. Kosten und Schwierigkeit steigen je nach Körpermasse des Tieres. Mindestens 40 AsP bei Pferden.",
"mod": "",
"limit": ""
},
{
"name": "Körperkraft",
"description": "Wie Konstitution.",
"mod": "",
"limit": ""
},
{
"name": "Schnellsteigerung",
"description": "Nur in gildenmagischer, elfischer, druidischer oder saurischer Rep. Kleine Mutanda: +ZfP* Punkte für ZfW KR, Kosten 1 AsP pro Punkt. Mächtigere Version bringt Dauerbonus.",
"mod": "-3",
"limit": "ab ZfW 11"
},
{
"name": "Übernatürliche Begabung",
"description": "Nur für Magiedilettanten. Steigert eine Eigenschaft um ZfP* Punkte für ZfP* KR, kostet ZfP* AsP.",
"mod": "",
"limit": ""
},
{
"name": "Scharlatanische Version",
"description": "Ermöglicht Reichweite 'selbst', Zielobjekt 'Einzelperson, freiwillig'.",
"mod": "",
"limit": ""
}
],
"reversalis": "Die entsprechende Eigenschaft des Opfers sinkt um den entsprechenden Betrag.",
"antimagie": "In einer Zone des EIGENSCHAFT WIEDERHERSTELLEN nur erschwert wirksam und kann mit diesem Antimagie-Spruch beendet werden.",
"merkmal": "Eigenschaften",
"komplexität": "B",
"repräsentation": {
"Druide": 5,
"Elf": 5,
"Geomant": 5,
"Hexe": 5,
"Magier": 5,
"Achaz": 3,
"Schelm": 3,
"Scharlatan": 3
},
"info": "Die Varianten dieses Zaubers wurden lange Zeit als eigenständige Sprüche (z. B. KLU, WIS, INTELLECT, CHARISMA AUGETE, VORAHNUNG, STARKE STEIGERN) gelehrt. Heute meist zusammengefasst. Verbreitet bei fast allen Zauberkundigen."
}

View File

@ -1,77 +0,0 @@
{
"seite": "35",
"name": "AURIS NASUS OCULUS",
"probe": [
"KL",
"CH",
"FF"
],
"technik": "Der Magier konzentriert sich auf den Ort, an dem die Illusion erscheinen soll, und murmelt mit geschlossenen Augen die Formel.",
"zauberdauer": {
"min": 0
},
"wirkung": "Mit dieser Formel kann der Magier illusorische Geräusche, Gerüche und dreidimensionale (aber unbewegte) Bilder erschaffen. Beispiele: Schrift an der Wand, eine vorgelagerte Wand, eine Stimme aus dem Nichts, Brandgeruch. Jede einzelne Bild-, Geräusch- oder Geruchskomponente zählt als eine Illusionskomponente. Die Größe der Bilder beträgt maximal ZfW × 5 RaumSchritt. Mehrere Illusionsarten können kombiniert werden, die Probe erschwert sich dabei um 2 Punkte pro zusätzliche Komponente. Einmal erschaffene Illusionen bestehen ohne weiteres Zutun fort. Die Realitätsdichte beträgt ZfP* ÷ 2 + 7.",
"kosten": [
{
"cost": 0,
"repräsentation": ""
}
],
"zielobjekt": "Zone",
"reichweite": "ZfW × 3 Schritt",
"wirkungsdauer": "maximal ZfP* ÷ 2 Spielrunden",
"modifikationen": "Zauberdauer, Kosten, Wirkungsdauer",
"varianten": {
"Verpuffung": {
"description": "Illusionen lösen sich langsam auf oder verpuffen schlagartig.",
"mod": -3,
"cost": 4,
"zauberdauer": 5,
"limit": ""
},
"Außer Sicht": {
"description": "Illusionen erscheinen auch an Orten, die der Zaubernde nicht direkt sehen kann, sofern er sie zuvor gesehen hat.",
"mod": -3,
"cost": 4,
"zauberdauer": 5,
"limit": ""
},
"Entfernte Phantasmagorie": {
"description": "Illusionen können weiter entfernt erscheinen, bis zu ZfW × 30 Schritt.",
"mod": -5,
"cost": 4,
"zauberdauer": 5,
"limit": ""
},
"Bewegte Bilder": {
"description": "Illusionen sind beweglich (z. B. ein fliegender Drache).",
"mod": -7,
"cost": 4,
"zauberdauer": 5,
"limit": "11"
},
"Selbst leuchtend": {
"description": "Illusionen leuchten und können eine Lichtquelle ersetzen.",
"mod": -7,
"cost": 4,
"zauberdauer": 5,
"limit": "11"
},
"Geschmack und Tastsinn": {
"description": "Illusionen täuschen auch den Geschmack oder Tastsinn.",
"mod": -12,
"cost": 4,
"zauberdauer": 5,
"limit": "14"
}
},
"reversalis": "Der Zauber wird aufgehoben.",
"antimagie": "In einer Zone des ILLUSION AUFLÖSEN erschwert wirksam und kann den Zauber aufheben.",
"merkmal": "Illusion",
"komplexität": "D",
"repräsentation": {
"Magier": 5,
"Scharlatan": 5
},
"info": "AURIS NASUS gilt als Mutter aller Illusionen und erlaubt Täuschung von Bild, Ton und Geruch, mit Erweiterungen auch von Tastsinn und Geschmack. Sehr mächtige Formel, die in Zorgan, Grangor, Khunchom und Punin gelehrt wird. Wichtige Werke wie Theorie der Wahrnehmung und Beobachtung und Liber Metheslesae behandeln den Spruch ausführlich."
}

View File

@ -1,53 +0,0 @@
{
"seite": "36",
"name": "AXXELERATUS BLITZGESCHWIND",
"probe": [
"KL",
"GE",
"KO"
],
"technik": "Die Elfe konzentriert sich auf alle Muskeln und Sehnen ihres Körpers und spricht asela dulo biwandan.",
"zauberdauer": {
"normal": "2 Aktionen"
},
"wirkung": "Der Zauber verleiht dem Verzauberten enorme Beschleunigung. Bewegungen wirken fließend und schnell, jedoch etwas verschwommen. Körperliche Aktionen werden stark erleichtert, geistige Tätigkeiten bleiben unbeeinflusst. Erhöhte Koordination vermindert Sturz- oder Verstauchungsrisiken. Parade-Basiswert +2, Ausweichen +2, TP von Nahkampfangriffen +2, Abwehr von bewaffneten Angriffen +2, INI-Basiswert +2. Geschwindigkeit für Sprints verdoppelt. Während der Wirkungsdauer entsprechen die Werte den Sonderfertigkeiten Schnellelfen und Schnellerladen. Laden und Ziehen einer Waffe kann um 1 Aktion verkürzt werden.",
"kosten": [
{
"cost": 7,
"repräsentation": ""
},
{
"cost": 5,
"repräsentation": "Schelm"
}
],
"zielobjekt": "Einzelperson, freiwillig",
"reichweite": "selbst, 7 Schritt",
"wirkungsdauer": "ZfP* × 3 Kampfrunden (A)",
"modifikationen": "Zauberdauer, Kosten, Zielobjekt (mehrere), Reichweite",
"varianten": [
{
"name": "Blitzgeschwind",
"description": "Der Verzauberte erhält die Geschwindigkeit eines Pfeils. Zusätzlich wird der Athletik-Wert für Sprints und Sprungweiten verdoppelt.",
"mod": -7,
"limit": "11"
},
{
"name": "Koboldisch",
"description": "Die Beschleunigung wird auf die Sprache der Elfen angewendet. Der Zaubernde spricht Koboldisch. Schabernack-Variante ohne Kampfvorteile.",
"mod": 0,
"limit": "3"
}
],
"reversalis": "Der Zauber bewirkt Verlangsamung: GS halbiert, AT/PA/Ausweichen halbiert, TP -2, Athletik stark reduziert.",
"antimagie": "In einer Zone des EIGENSCHAFT WIEDERHERSTELLEN erschwert wirksam und kann den Zauber aufheben.",
"merkmal": "Eigenschaften",
"komplexität": "C",
"repräsentation": {
"Elf": 6,
"Achaz": 5,
"Schelm": 5,
"Magier": 3
},
"info": "Die Waldelfen nutzen den Zauber traditionell für Jagd und Tanz. Acheer-Schamanen verwenden ihn für rituelle Tänze. Gildenmagische Repräsentation selten, gelehrt in Gerasim, Belhanka und der Akademie der Verformungen zu Lowangen. Auch Tamara beschrieb die Formel in ihren Werken."
}

View File

@ -1,58 +0,0 @@
{
"seite": "37",
"name": "BALSAM SALABUNDE",
"probe": [
"KL",
"IN",
"CH"
],
"probeMod": "evtl. +Mod",
"technik": "Der Elf legt dem Verletzten sanft eine Hand auf die Verletzung (bei großflächiger oder innerer Verletzung aufs Herz) und wiederholt die Melodie des bhassama sala bian dao so lange, bis die heilende Wirkung einsetzt.",
"zauberdauer": {
"min": "5 Aktionen",
"normal": "1 SR"
},
"wirkung": "Heilt sämtliche Wunden und inneren Verletzungen des Verzauberten, je nach eingesetzten AsP. Pro AsP wird 1 LeP zurückgewonnen (maximal jedoch ZfW × 2 LeP). Der Zauber kann auch auf den Zaubernden selbst gewirkt werden. Schädliche Wirkungen von Krankheiten und Giften werden nicht gestoppt.",
"kosten": [{
"cost": 5,
"additionalFormula": "LeP",
"variables": [
"LeP"
],
"repräsentation": ""
}],
"zielobjekt": "Einzelwesen, freiwillig",
"reichweite": "selbst, Berührung",
"wirkungsdauer": "augenblicklich",
"modifikationen": "Zauberdauer, Kosten, Reichweite",
"varianten": [
{
"name": "Lebenskraft stärken",
"description": "Die Lebensdauer des Nutznießers wird für kurze Zeit über das normale Maß hinaus verlängert. Für je 3 AsP erhält der Verzauberte 1 LeP über den LeP-Grundwert hinaus, bis max. ZfW LeP. Jeder ZfP* überzieht den Körper mit zusätzlichem LeP, der aber wieder verloren geht.",
"mod": -5,
"limit": "11"
},
{
"name": "Sofortige Regeneration",
"description": "Der Körper erhält sofortige Regeneration. Jede KR regeneriert der Nutznießer 1W6 LeP, Wunden schließen sich sofort wieder.",
"mod": -15,
"limit": "18"
}
],
"reversalis": "Als schleichender Schadenszauber: Das Opfer verliert pro eingesetztem AsP 1 LeP.",
"antimagie": "Während der Zauber gewirkt wird, kann er mit HEILKRAFT BANNEN und VERWANDLUNG BEENDEN zum Scheitern gebracht werden; erschwert wirksam in entsprechenden Zonen.",
"merkmal": [
"Heilung",
"Form"
],
"komplexität": "C",
"repräsentation": {
"Elf": 9,
"Magier": 7,
"Achaz": 5,
"Geomant": 5,
"Druide": 4,
"Hexe": 3
},
"info": "Eine der bekanntesten Heilzauberformeln, ursprünglich von den Elfen entwickelt und später von Gildenmagiern übernommen. Weit verbreitet, auch unter Geoden und Achaz. In klassischen Magierkreisen lange abgelehnt, da man glaubte, dass Heilung göttlicher Macht vorbehalten sei."
}

View File

@ -1,62 +0,0 @@
{
"seite": "39",
"name": "BANNBALADIN",
"probe": [
"IN",
"CH",
"CH"
],
"probeMod": "+MR",
"technik": "Der Elf blickt seinem Opfer in die Augen und spricht blah bla ladin.",
"zauberdauer": {
"normal": "5 Aktionen"
},
"wirkung": "Der Verzauberte sieht in dem Spruchanwender einen Freund. Die Intensität hängt von den ZfP* ab: 1 ZfP* = unbestimmtes Gefühl, 4 ZfP* = freundschaftlich verbunden, 7 ZfP* = enger Freund, 10 ZfP* = bedingungslos loyal, 13 ZfP* = bereit, schweren Schaden auf sich zu nehmen, 16 ZfP* = vollkommen verfallen. Der Zauberer kann Charisma-, Überreden- oder Eigenschaften-Proben durch die Bindung erleichtern. Der Bannbaladin wirkt nur, solange Sichtkontakt besteht. Mehrere Opfer gleichzeitig sind möglich.",
"kosten": [
{
"cost": 7,
"repräsentation": ""
}
],
"zielobjekt": "Einzelperson",
"reichweite": "3 Schritt",
"wirkungsdauer": {
"formula": "ZfP* Spielrunden",
"variables": [
"ZfP*"
]
},
"modifikationen": "Zauberdauer, Zielobjekt (mehrere), Reichweite, Wirkungsdauer",
"varianten": [
{
"name": "Tiefenruf",
"description": "Nur in elfischer Repräsentation. Auch Tiere können beeinflusst werden.",
"mod": -5,
"limit": ""
},
{
"name": "Gemeinsame Erinnerungen",
"description": "Der Zauberer pflanzt gemeinsame Erinnerungen in den Geist des Opfers ein. Spieler dürfen diese Erinnerungen beschreiben.",
"mod": -3,
"limit": "7"
},
{
"name": "Keine Erinnerung",
"description": "Das Opfer erinnert sich nach Ende des Zaubers nicht daran, dass es verzaubert wurde.",
"mod": -7,
"limit": "11"
}
],
"reversalis": "Hebt einen bestehenden BANNBALADIN auf.",
"antimagie": "EINFLUSS BANNEN beendet die Wirkung und löscht die Gefühle.",
"merkmal": "Einfluss",
"komplexität": "B",
"repräsentation": {
"Elf": 7,
"Magier": 6,
"Druide": 3,
"Hexe": 3,
"Scharlatan": 3
},
"info": "Ursprünglich elfischer Freundschaftszauber, später von Gildenmagiern zu einem der wichtigsten Herrschafts- und Beeinflussungszauber gewandelt. Zwischen Elfen und Magiern sorgt die Anwendung regelmäßig für Konflikte."
}

View File

@ -1,82 +0,0 @@
{
"seite": "47",
"name": "BLICK IN DIE GEDANKEN",
"probe": [
"KL",
"KL",
"CH"
],
"probeMod": "+MR",
"technik": "Die Elfe blickt ihrem Opfer ins Gesicht und konzentriert sich dann auf die Melodie des ibhanda dhara feya dendra.",
"zauberdauer": {
"normal": "10 Aktionen"
},
"wirkung": "Die Zaubernde erhält Einblick in die momentanen Gedankengänge des Opfers, die sie als verschwommene Bilder vor ihrem geistigen Auge sieht. Mit einer gelungenen Selbstbeherrschungs-Probe kann das Opfer versuchen, andere Gedanken vorzuschieben oder falsche Fährten zu legen. Das Lesen fremder Gedanken (z. B. Drachen, Einhörner, Chimären) ist erschwert.",
"kosten": [
{
"cost": 0,
"additionalFormula": "6 * 5 KR",
"variables": [
"5 KR"
],
"repräsentation": ""
}
],
"zielobjekt": "Einzelperson",
"reichweite": "3 Schritt",
"wirkungsdauer": "nach AsP-Aufwand (A)",
"modifikationen": "Zauberdauer, Erzwingen, Reichweite",
"varianten": [
{
"name": "Keine Sicht",
"description": "Das Opfer muss nicht innerhalb der Reichweite erkennbar sein.",
"mod": -5,
"limit": ""
},
{
"name": "Traumlese",
"description": "Wirkt auf Träumende, erlaubt Teilhabe und Einwirkung in die Träume.",
"mod": -3,
"limit": "7"
},
{
"name": "Drachisch",
"description": "Ermöglicht das Lesen drachischer Gedanken. Erfordert GE-DANKENBILDER und idealerweise einen Muttersprachler.",
"mod": 0,
"limit": "7"
},
{
"name": "Liebessinn",
"description": "Während der Kampfsinn auf den Gegner harmonisiert, teilt der Zaubernde seine Gedanken und Gefühle mit dem Opfer.",
"mod": -3,
"limit": "11"
},
{
"name": "Kampfsinn",
"description": "Der Zaubernde teilt die Aktionen und Wahrnehmung des Gegners im Kampf.",
"mod": -5,
"limit": "11"
},
{
"name": "Tiefentelepathie",
"description": "Intensive Lesung, bei der Gedanken, Gefühle und Erinnerungen tiefgehend untersucht werden. Hohe Kosten, aber präzise Ergebnisse.",
"mod": -7,
"limit": "11"
}
],
"reversalis": "Offenbart dem Gegner die Gedanken der Zaubernden.",
"antimagie": "In einer Zone des HELLSICHT TRÜBEN nur erschwert wirksam, gezielter Einsatz von HELLSICHT TRÜBEN hebt den Zauber auf.",
"merkmal": [
"Hellsicht"
],
"komplexität": "D",
"repräsentation": {
"Elf": 5,
"Magier": 5,
"Achaz": 4,
"Druide": 4,
"Hexe": 4,
"Geomant": 3
},
"info": "Ursprünglich ein Verständigungszauber der Elfen, wurde er von den Gildenmagiern zu einem Verhörzauber entwickelt. Unter Elfen ist der Spruch verbreitet, aber mit Vorsicht wird er an Menschen weitergegeben, da er als stark verfälschtes Werkzeug gilt."
}

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