Compare commits
91 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
0e0fd22fad | |
|
|
80d1b29486 | |
|
|
24bb15f502 | |
|
|
75280e5590 | |
|
|
98864464b1 | |
|
|
8be096f464 | |
|
|
c51e281530 | |
|
|
50c28cb380 | |
|
|
2ee6ed775b | |
|
|
235dd0928d | |
|
|
07ba1c889b | |
|
|
cc60f9e57f | |
|
|
94a80eb321 | |
|
|
959f47f348 | |
|
|
5f85631679 | |
|
|
957584206d | |
|
|
9727d7bc3a | |
|
|
4a9bfe2865 | |
|
|
ce6207be95 | |
|
|
468984a83b | |
|
|
3f8794e181 | |
|
|
f5b4633f76 | |
|
|
c6829ff697 | |
|
|
3207020d57 | |
|
|
df61621565 | |
|
|
61e1bd2836 | |
|
|
f7d772b6ca | |
|
|
02f0ecc9dd | |
|
|
182aeb2dc6 | |
|
|
c00a6b11b7 | |
|
|
eca965e434 | |
|
|
6f935644c1 | |
|
|
4f4446d327 | |
|
|
e0e70d126f | |
|
|
692867f2ac | |
|
|
46b6ed8f2a | |
|
|
189db593aa | |
|
|
d355cb2d5c | |
|
|
accd2d1f16 | |
|
|
0fffebdab9 | |
|
|
6d366188ea | |
|
|
47280f7216 | |
|
|
bbf181eb8c | |
|
|
1bc6d9673a | |
|
|
811806a68b | |
|
|
bdcb09c82e | |
|
|
cfc5bc15b1 | |
|
|
29d25f8afe | |
|
|
c2b8a7d895 | |
|
|
ed893f6b9d | |
|
|
316ab90c67 | |
|
|
7e34251397 | |
|
|
28f19772f2 | |
|
|
d51aa18a19 | |
|
|
0f9032c3f5 | |
|
|
34d7175c39 | |
|
|
97fe0fa9a6 | |
|
|
7b0f407239 | |
|
|
f402661488 | |
|
|
03de483e9a | |
|
|
1adff7568d | |
|
|
97466ed45d | |
|
|
12c9b0766a | |
|
|
5104f43e2f | |
|
|
34c95891e6 | |
|
|
223ea9e26b | |
|
|
4be53924d8 | |
|
|
8258f53a3a | |
|
|
6d4f8694df | |
|
|
72248f2635 | |
|
|
1d714a3773 | |
|
|
95712704b5 | |
|
|
7788035c8c | |
|
|
b66e4e77ac | |
|
|
e862185803 | |
|
|
dc97fffba2 | |
|
|
db81e2def1 | |
|
|
373781b5b0 | |
|
|
394992d447 | |
|
|
ec4c9768dd | |
|
|
df4fd6061d | |
|
|
bb35d0d6e7 | |
|
|
7476c55e63 | |
|
|
5b46f36c58 | |
|
|
026f222718 | |
|
|
b69511f93a | |
|
|
9a57bcd77a | |
|
|
62c5702992 | |
|
|
9948521512 | |
|
|
a861245d44 | |
|
|
e69374f6f4 |
10
README.md
10
README.md
|
|
@ -44,6 +44,14 @@ 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
|
||||
|
|
@ -74,3 +82,5 @@ 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.
|
||||
|
||||
|
|
|
|||
166
gulpfile.mjs
166
gulpfile.mjs
|
|
@ -1,8 +1,9 @@
|
|||
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 {getRandomValues} from 'node:crypto';
|
||||
import {subtle} from 'node:crypto';
|
||||
import * as dartSass from 'sass';
|
||||
import gulpSass from 'gulp-sass';
|
||||
import {deleteAsync} from 'del';
|
||||
|
|
@ -11,28 +12,42 @@ 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(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;
|
||||
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)
|
||||
});
|
||||
}
|
||||
|
||||
const convert = function (from, to, ofType, overwrite = true) {
|
||||
|
||||
const SOURCE = from;
|
||||
const DEST = to;
|
||||
const TYPE = ofType;
|
||||
|
|
@ -45,38 +60,50 @@ const convert = function (from, to, ofType, overwrite = true) {
|
|||
mkdirSync(DEST)
|
||||
}
|
||||
|
||||
const filewalker = (source) => {
|
||||
console.debug("entering directory", source);
|
||||
readdirSync(source).forEach(file => {
|
||||
if (statSync(join(source, file)).isDirectory()) {
|
||||
filewalker(join(source, file));
|
||||
} else {
|
||||
console.debug("processing file", join(source, file))
|
||||
let originalSource = JSON.parse(readFileSync(join(source, file), {encoding: "utf8"}));
|
||||
let id = randomID();
|
||||
let promises = []
|
||||
|
||||
let targetSource = {
|
||||
_id: id,
|
||||
_key: "!items!" + id,
|
||||
type: TYPE,
|
||||
img: originalSource.image,
|
||||
name: originalSource.name.trim(),
|
||||
system: {...originalSource},
|
||||
const filewalker = async (source) => {
|
||||
console.debug("entering directory", source)
|
||||
for (let file of readdirSync(source)) {
|
||||
if (statSync(join(source, file)).isDirectory()) {
|
||||
await 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 targetSource = {
|
||||
_id: id,
|
||||
_key: "!items!" + id,
|
||||
type: TYPE,
|
||||
img: originalSource.image,
|
||||
name: originalSource.name.trim(),
|
||||
system: {...originalSource},
|
||||
}
|
||||
delete targetSource.system.image;
|
||||
let target = JSON.stringify(targetSource, null, 2)
|
||||
let newFileName = "./" + join(DEST, id + ".json")
|
||||
if (!existsSync(join("./", DEST))) {
|
||||
mkdirSync(join("./", DEST))
|
||||
}
|
||||
writeFileSync(newFileName, target, {encoding: "utf8"})
|
||||
resolve2()
|
||||
})
|
||||
|
||||
}))
|
||||
}
|
||||
delete targetSource.system.image;
|
||||
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() {
|
||||
|
|
@ -112,44 +139,44 @@ function updateManifestFile() {
|
|||
.pipe(dest('src/'))
|
||||
}
|
||||
|
||||
async function prepareDB() {
|
||||
gulp.task('prepareDB', async function (done) {
|
||||
|
||||
try {
|
||||
if (!existsSync("./src/packs/__source")) {
|
||||
mkdirSync("./src/packs/__source");
|
||||
}
|
||||
|
||||
convert("./src/packs/_source/talente", "./src/packs/__source/talente", "Skill");
|
||||
convert("./src/packs/_source/zauber", "./src/packs/__source/zauber", "Spell");
|
||||
convert("./src/packs/_source/vorteile", "./src/packs/__source/vorteile", "Advantage");
|
||||
convert("./src/packs/_source/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/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")
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function buildDB() {
|
||||
gulp.task('buildDB', function (done) {
|
||||
// Determine which source folders to process
|
||||
|
||||
const PACK_SRC = "src/packs/__source"
|
||||
|
|
@ -161,15 +188,16 @@ function buildDB() {
|
|||
);
|
||||
|
||||
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(
|
||||
|
|
@ -178,6 +206,6 @@ export default series(
|
|||
copySource,
|
||||
copyAssets,
|
||||
buildStyles,
|
||||
prepareDB,
|
||||
buildDB
|
||||
gulp.task('prepareDB'),
|
||||
gulp.task('buildDB')
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"gulp-json-modify": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@foundryvtt/foundryvtt-cli": "^3.0.0",
|
||||
"@foundryvtt/foundryvtt-cli": "^3.0.2",
|
||||
"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.0",
|
||||
"resolved": "https://registry.npmjs.org/@foundryvtt/foundryvtt-cli/-/foundryvtt-cli-3.0.0.tgz",
|
||||
"integrity": "sha512-OiF4HtnYg5An1ivVxB68mOj5LO5gMHd4uHmC5nWdD8IYxpK0pSYw3t+cHrUYDp+Tic78uwFuHxLyc+ZNeZXulA==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@foundryvtt/foundryvtt-cli/-/foundryvtt-cli-3.0.2.tgz",
|
||||
"integrity": "sha512-coh4Cf4FD/GHxk2QMsd+3wLMivNeih4rfkbZy8CaYjdlpo6iciFQwxLqznZWtn+5p06zekvS2xLUF55NnbXQDw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^5.4.1",
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@
|
|||
"scripts": {
|
||||
"test": "true",
|
||||
"build": "gulp",
|
||||
"localBuild": "VERSION=0.0.1 gulp",
|
||||
"installToFoundry": "node installToFoundry.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@foundryvtt/foundryvtt-cli": "^3.0.0",
|
||||
"@foundryvtt/foundryvtt-cli": "^3.0.2",
|
||||
"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: 281 B After Width: | Height: | Size: 280 B |
|
|
@ -1 +1,59 @@
|
|||
{}
|
||||
{
|
||||
"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}"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"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}"
|
||||
}
|
||||
}
|
||||
457
src/main.mjs
457
src/main.mjs
|
|
@ -1,401 +1,43 @@
|
|||
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 {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'
|
||||
]);
|
||||
}
|
||||
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";
|
||||
|
||||
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!")
|
||||
|
||||
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,
|
||||
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(() => {
|
||||
})
|
||||
|
||||
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();
|
||||
initHandlebarHelpers(Handlebars)
|
||||
})
|
||||
|
||||
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)
|
||||
});
|
||||
});
|
||||
|
||||
game.DSA41 = {}
|
||||
initSocketLib(game.DSA41)
|
||||
|
||||
Hooks.on("getActorContextOptions", (application, menuItems) => {
|
||||
menuItems.push({
|
||||
|
|
@ -404,44 +46,7 @@ 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();
|
||||
}
|
||||
})
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import BaseItem from "./base-item.mjs";
|
||||
import BaseItem from "./baseItem.mjs";
|
||||
|
||||
const {ArrayField, BooleanField, NumberField, AnyField, StringField, HTMLField} = foundry.data.fields;
|
||||
|
||||
|
|
@ -1,8 +1,15 @@
|
|||
import BaseItem from "./base-item.mjs";
|
||||
import BaseItem from "./baseItem.mjs";
|
||||
|
||||
const {ArrayField, SchemaField, BooleanField, NumberField, StringField, HTMLField} = foundry.data.fields;
|
||||
const {
|
||||
ArrayField,
|
||||
SchemaField,
|
||||
BooleanField,
|
||||
NumberField,
|
||||
StringField,
|
||||
HTMLField
|
||||
} = foundry.data.fields;
|
||||
|
||||
export class VornachteileDataModel extends BaseItem {
|
||||
export class AdvantageDataModel extends BaseItem {
|
||||
|
||||
static defineSchema() {
|
||||
return {
|
||||
|
|
@ -21,7 +28,7 @@ export class VornachteileDataModel extends BaseItem {
|
|||
auswahl: new ArrayField(
|
||||
new SchemaField({
|
||||
name: new StringField(),
|
||||
requirement: new ArrayField(
|
||||
requirements: new ArrayField(
|
||||
new SchemaField({
|
||||
attribute: new StringField(),
|
||||
minValue: new NumberField(),
|
||||
|
|
@ -6,7 +6,8 @@ export const ATTRIBUTE = {
|
|||
"ff": "Fingerfertigkeit",
|
||||
"ge": "Gewandtheit",
|
||||
"ko": "Konstitution",
|
||||
"kk": "Körperkraft"
|
||||
"kk": "Körperkraft",
|
||||
"UNKNOWN": "Fehlende Variante"
|
||||
}
|
||||
|
||||
export const ATTRIBUTE_DESCRIPTIONS = {
|
||||
|
|
|
|||
|
|
@ -138,6 +138,10 @@ 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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import BaseItem from "./base-item.mjs";
|
||||
import BaseItem from "./baseItem.mjs";
|
||||
|
||||
const {BooleanField, StringField, HTMLField} = foundry.data.fields;
|
||||
const {
|
||||
StringField,
|
||||
HTMLField
|
||||
} = foundry.data.fields;
|
||||
|
||||
export class CultureDataModel extends BaseItem {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
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()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
import BaseItem from "./base-item.mjs";
|
||||
import {Equipment} from "../documents/equipment.mjs";
|
||||
import BaseItem from "./baseItem.mjs";
|
||||
|
||||
const {
|
||||
ArrayField, EmbeddedCollectionField, SchemaField, NumberField, StringField, HTMLField
|
||||
ArrayField,
|
||||
SchemaField,
|
||||
NumberField,
|
||||
StringField,
|
||||
HTMLField
|
||||
} = foundry.data.fields;
|
||||
|
||||
export class EquipmentDataModel extends BaseItem {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,8 @@ const {
|
|||
ObjectField,
|
||||
NumberField,
|
||||
StringField,
|
||||
EmbeddedDocumentField,
|
||||
DocumentIdField,
|
||||
ArrayField,
|
||||
ForeignDocumentField
|
||||
} = foundry.data.fields;
|
||||
|
||||
export class GroupDataModel extends foundry.abstract.TypeDataModel {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import BaseItem from "./base-item.mjs";
|
||||
import BaseItem from "./baseItem.mjs";
|
||||
|
||||
const {BooleanField, NumberField, SchemaField, ArrayField, StringField, HTMLField} = foundry.data.fields;
|
||||
const {
|
||||
NumberField,
|
||||
SchemaField,
|
||||
ArrayField,
|
||||
StringField,
|
||||
HTMLField
|
||||
} = foundry.data.fields;
|
||||
|
||||
export class LiturgyDataModel extends BaseItem {
|
||||
|
||||
|
|
@ -17,14 +23,14 @@ export class LiturgyDataModel extends BaseItem {
|
|||
wirkungsdauer: new StringField(),
|
||||
zauberdauer: new StringField(),
|
||||
auswirkung: new SchemaField({
|
||||
I: new StringField(),
|
||||
II: new StringField(),
|
||||
III: new StringField(),
|
||||
IV: new StringField(),
|
||||
V: new StringField(),
|
||||
VI: new StringField(),
|
||||
VII: new StringField(),
|
||||
VIII: new StringField(),
|
||||
I: new HTMLField(),
|
||||
II: new HTMLField(),
|
||||
III: new HTMLField(),
|
||||
IV: new HTMLField(),
|
||||
V: new HTMLField(),
|
||||
VI: new HTMLField(),
|
||||
VII: new HTMLField(),
|
||||
VIII: new HTMLField(),
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
const {
|
||||
SchemaField,
|
||||
NumberField,
|
||||
ObjectField,
|
||||
StringField,
|
||||
HTMLField,
|
||||
FilePathField,
|
||||
DocumentIdField,
|
||||
ArrayField,
|
||||
} = foundry.data.fields;
|
||||
|
||||
|
|
|
|||
|
|
@ -165,7 +165,6 @@ export class LiturgyData {
|
|||
|
||||
if (found) {
|
||||
durationText = this.#ranks[currentDuration].duration
|
||||
console.log({currentDuration, durationText, adjustedDurationText})
|
||||
return {currentDuration, durationText, adjustedDurationText}
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
import BaseItem from "./base-item.mjs";
|
||||
import BaseItem from "./baseItem.mjs";
|
||||
|
||||
const {BooleanField, StringField, HTMLField} = foundry.data.fields;
|
||||
const {
|
||||
BooleanField,
|
||||
StringField,
|
||||
HTMLField
|
||||
} = foundry.data.fields;
|
||||
|
||||
export class ProfessionDataModel extends BaseItem {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import BaseItem from "./base-item.mjs";
|
||||
import BaseItem from "./baseItem.mjs";
|
||||
import {evaluateRoll} from "../globals/DSARoll.mjs";
|
||||
|
||||
const {
|
||||
BooleanField,
|
||||
DocumentIdField,
|
||||
ArrayField,
|
||||
NumberField,
|
||||
|
|
@ -131,7 +131,7 @@ export class SkillDataModel extends BaseItem {
|
|||
|
||||
let evaluated1 = (await roll1.evaluate())
|
||||
|
||||
const dsaDieRollEvaluated = this._evaluateRoll(evaluated1.terms[0].results, {
|
||||
const dsaDieRollEvaluated = evaluateRoll(evaluated1.terms[0].results, {
|
||||
taw: this.taw,
|
||||
werte: [this.probe[0], this.probe[1], this.probe[2]],
|
||||
})
|
||||
|
|
@ -151,37 +151,4 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import BaseItem from "./base-item.mjs";
|
||||
import BaseItem from "./baseItem.mjs";
|
||||
|
||||
const {
|
||||
AnyField,
|
||||
BooleanField,
|
||||
NumberField,
|
||||
SchemaField,
|
||||
ArrayField,
|
||||
StringField,
|
||||
HTMLField,
|
||||
ObjectField
|
||||
} = foundry.data.fields;
|
||||
|
||||
export class SpecialAbilityDataModel extends BaseItem {
|
||||
|
|
@ -35,6 +33,7 @@ export class SpecialAbilityDataModel extends BaseItem {
|
|||
}))
|
||||
}),
|
||||
),
|
||||
gruppe: new StringField(),
|
||||
seite: new NumberField(),
|
||||
aktionsText: new HTMLField(),
|
||||
text: new HTMLField(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import BaseItem from "./base-item.mjs";
|
||||
import BaseItem from "./baseItem.mjs";
|
||||
|
||||
const {BooleanField, ArrayField, SchemaField, NumberField, StringField, HTMLField} = foundry.data.fields;
|
||||
const {
|
||||
ArrayField,
|
||||
SchemaField,
|
||||
NumberField,
|
||||
StringField,
|
||||
HTMLField
|
||||
} = foundry.data.fields;
|
||||
|
||||
export class SpeciesDataModel extends BaseItem {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
import BaseItem from "./base-item.mjs";
|
||||
import BaseItem from "./baseItem.mjs";
|
||||
|
||||
const {BooleanField, NumberField, SchemaField, ArrayField, StringField, HTMLField} = foundry.data.fields;
|
||||
const {
|
||||
SchemaField,
|
||||
BooleanField,
|
||||
NumberField,
|
||||
ArrayField,
|
||||
StringField,
|
||||
HTMLField,
|
||||
ObjectField,
|
||||
} = foundry.data.fields;
|
||||
|
||||
export class SpellDataModel extends BaseItem {
|
||||
|
||||
|
|
@ -13,9 +21,23 @@ export class SpellDataModel extends BaseItem {
|
|||
probeMod: new StringField(),
|
||||
hauszauber: new BooleanField(),
|
||||
technik: new StringField(),
|
||||
zauberdauer: new StringField(),
|
||||
wirkung: new StringField(),
|
||||
kosten: 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()
|
||||
})
|
||||
),
|
||||
zielobjekt: new StringField(),
|
||||
reichweite: new StringField({required: true}),
|
||||
wirkungsdauer: new StringField({required: true}),
|
||||
|
|
@ -24,8 +46,14 @@ export class SpellDataModel extends BaseItem {
|
|||
antimagie: new StringField(),
|
||||
merkmal: new StringField(),
|
||||
komplexität: new StringField(),
|
||||
repräsentation: new StringField(),
|
||||
info: 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(),
|
||||
}))
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
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",
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
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) {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
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
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -179,7 +181,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 = {}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import {ActionManager} from "../sheets/actions/action-manager.mjs";
|
||||
|
||||
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
|
||||
const {
|
||||
ApplicationV2,
|
||||
HandlebarsApplicationMixin
|
||||
} = foundry.applications.api
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -47,40 +50,60 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
|
|||
*/
|
||||
_actor = null
|
||||
|
||||
constructor(actor) {
|
||||
constructor(actor, data) {
|
||||
super();
|
||||
this._actor = actor
|
||||
this._targetId = null
|
||||
this._skillId = null
|
||||
this._weaponId = null
|
||||
this._skillId = data.skill ? data.skill : null
|
||||
this._weaponId = data.weapon ? data.weapon : null
|
||||
this._defenseManeuverId = null
|
||||
this._actionManager = new ActionManager(this._actor)
|
||||
CombatActionDialog._instance = this
|
||||
}
|
||||
|
||||
static _instance = null
|
||||
|
||||
|
||||
static async #onSelectTarget(event, target) {
|
||||
|
||||
async #processOnSelectTarget(event, target) {
|
||||
const {targetId} = target.dataset
|
||||
this._targetId = this._targetId === targetId ? null : targetId
|
||||
this.render({parts: ["form"]})
|
||||
}
|
||||
|
||||
static async #onSelectManeuver(event, target) {
|
||||
static async #onSelectTarget(event, target) {
|
||||
event.preventDefault()
|
||||
CombatActionDialog._instance.#processOnSelectTarget(event, target)
|
||||
}
|
||||
|
||||
|
||||
async #processOnSelectManeuver(event, target) {
|
||||
const {maneuverId} = target.dataset
|
||||
this._defenseManeuverId = this._defenseManeuverId === maneuverId ? null : maneuverId
|
||||
this.render({parts: ["form"]})
|
||||
}
|
||||
|
||||
|
||||
static async #onSelectWeaponAndSkill(event, target) {
|
||||
static async #onSelectManeuver(event, target) {
|
||||
event.preventDefault()
|
||||
CombatActionDialog._instance.#processOnSelectManeuver(event, target)
|
||||
}
|
||||
|
||||
async #processOnSelectWeaponAndSkill(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 #onSubmitForm(event, form, formData) {
|
||||
|
||||
static async #onSelectWeaponAndSkill(event, target) {
|
||||
event.preventDefault()
|
||||
const maneuver = this.#evaluateManeuvers().find(p => p.id === this._defenseManeuverId)
|
||||
CombatActionDialog._instance.#processOnSelectWeaponAndSkill(event, target)
|
||||
}
|
||||
|
||||
async #processOnSubmitForm(event, form, formData) {
|
||||
const maneuver = CombatActionDialog._instance.#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)
|
||||
|
|
@ -89,7 +112,8 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
|
|||
weapon: this._weaponId,
|
||||
skill: this._skillId,
|
||||
target: this._targetId,
|
||||
maneuver,
|
||||
title: maneuver.name,
|
||||
maneuver: maneuver.activate.toString(),
|
||||
mod: this._mod,
|
||||
circumstance: this._circumstance,
|
||||
penalty: this._penalty,
|
||||
|
|
@ -102,12 +126,14 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
|
|||
/** @type Cooldown */
|
||||
const newCooldown = {
|
||||
start: maneuver.cooldown({weapon, skill, target, mod: this._mod}),
|
||||
current: maneuver.cooldown({weapon, skill, target, mod: this._mod}),
|
||||
current: 0,
|
||||
data: {
|
||||
cssClass: "Kampf",
|
||||
weapon: this._weaponId,
|
||||
skill: this._skillId,
|
||||
target: this._targetId,
|
||||
maneuver,
|
||||
title: maneuver.name,
|
||||
maneuver: maneuver.activate.toString(),
|
||||
mod: this._mod,
|
||||
circumstance: this._circumstance,
|
||||
penalty: this._penalty,
|
||||
|
|
@ -126,6 +152,12 @@ 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) {
|
||||
|
|
@ -256,9 +288,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 (this._actor.getActiveTokens()[0]?.id) {
|
||||
if (context.distanceUnit && this._actor.getActiveTokens()[0]?.id) {
|
||||
|
||||
context.tokenDistances = this.#evaluateDistances()
|
||||
context.weapons = this.#evaluateWeapons()
|
||||
|
|
@ -275,7 +307,28 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
|
|||
|
||||
// TODO get W/M of weapon NOW
|
||||
|
||||
context.ready = this._targetId && this._weaponId && this._skillId && this._defenseManeuverId
|
||||
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
|
||||
}
|
||||
return context
|
||||
} else {
|
||||
ui.notifications.error(`Feature funktioniert nur wenn der Akteur ein Token auf der aktuellen Szene hat`);
|
||||
|
|
@ -303,12 +356,10 @@ export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2
|
|||
target.textContent = `(${result})`
|
||||
targetDescription.textContent = this._modDescription
|
||||
|
||||
if (result <= 0) {
|
||||
context.ready = false
|
||||
if (result <= 0 || !context.ready) {
|
||||
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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import {ActionManager} from "../sheets/actions/action-manager.mjs";
|
||||
|
||||
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
|
||||
const {
|
||||
ApplicationV2,
|
||||
HandlebarsApplicationMixin
|
||||
} = foundry.applications.api
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -46,16 +49,16 @@ export class DefenseActionDialog extends HandlebarsApplicationMixin(ApplicationV
|
|||
*/
|
||||
_actor = null
|
||||
|
||||
constructor(actor, attackData) {
|
||||
constructor(actor, data, 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 = null
|
||||
this._weaponId = null
|
||||
this._skillId = data.skill ? data.skill : null
|
||||
this._weaponId = data.weapon ? data.weapon : null
|
||||
this._defenseManeuverId = null
|
||||
this._actionManager = new ActionManager(this._actor)
|
||||
//if (this._actor) {
|
||||
|
|
@ -251,7 +254,26 @@ export class DefenseActionDialog extends HandlebarsApplicationMixin(ApplicationV
|
|||
|
||||
// TODO get W/M of weapon NOW
|
||||
|
||||
context.ready = this._targetId && this._weaponId && this._skillId && this._defenseManeuverId
|
||||
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
|
||||
}
|
||||
|
||||
return context
|
||||
} else {
|
||||
ui.notifications.error(`Feature funktioniert nur wenn der Akteur ein Token auf der aktuellen Szene hat`);
|
||||
|
|
@ -279,12 +301,10 @@ export class DefenseActionDialog extends HandlebarsApplicationMixin(ApplicationV
|
|||
target.textContent = `(${result})`
|
||||
targetDescription.textContent = this._modDescription
|
||||
|
||||
if (result <= 0) {
|
||||
context.ready = false
|
||||
if (result <= 0 || !context.ready) {
|
||||
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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,243 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
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) {
|
||||
|
||||
|
|
@ -135,10 +138,55 @@ export class LiturgyDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
const castingTime = this.#normalizeCastingTime(this._liturgy)
|
||||
|
||||
//TODO push it into the sun eeerh cooldown queue
|
||||
//if (castingTime > 0) {
|
||||
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 => {
|
||||
|
||||
// this._actor.system.cooldowns.push()
|
||||
//} else {
|
||||
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 {
|
||||
const result = await new Talent({
|
||||
name: this._liturgy.name,
|
||||
taw: lkp,
|
||||
|
|
@ -157,7 +205,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()
|
||||
}
|
||||
|
|
@ -175,7 +223,7 @@ export class LiturgyDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
const [_, actions] = castingTime.match(stoßgebetRegExp)
|
||||
return actions
|
||||
} else if (castingTime.match(gebetRegExp)) {
|
||||
const [_, actions] = castingTime.match(stoßgebetRegExp)
|
||||
const [_, actions] = castingTime.match(gebetRegExp)
|
||||
return actions * 20
|
||||
} else if (castingTime.match(invalidForCooldownRegExp)) {
|
||||
return -1
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {XmlImport} from "../xml-import/xml-import.mjs";
|
||||
|
||||
const {ApplicationV2, HandlebarsApplicationMixin} = foundry.applications.api
|
||||
const {
|
||||
ApplicationV2,
|
||||
HandlebarsApplicationMixin
|
||||
} = foundry.applications.api
|
||||
|
||||
export class RestingDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
||||
|
|
@ -98,6 +99,7 @@ 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",
|
||||
|
|
@ -113,6 +115,7 @@ export class RestingDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
aspMod: -1,
|
||||
lepMod: -1,
|
||||
display: "boolean",
|
||||
nestingLevel: 1,
|
||||
group: "bad_camp",
|
||||
active: false,
|
||||
value: "on"
|
||||
|
|
@ -219,6 +222,14 @@ 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
|
||||
|
|
@ -333,8 +344,6 @@ export class RestingDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
context.inRollDisplay = `1w20+${this.regInMod}`
|
||||
}
|
||||
|
||||
console.log(this, context)
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,584 @@
|
|||
|
||||
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>—${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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
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) {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
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) {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
|
||||
import {Zonenruestung, Zonenwunde, Wunde} from "../data/Trefferzone.js";
|
||||
import {LiturgyData} from "../data/miracle/liturgyData.mjs";
|
||||
import {Zonenruestung, Zonenwunde, Wunde} from "../data/trefferzone.mjs";
|
||||
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.aup.max = Math.round((mu + ko + ge) / 2)
|
||||
systemData.asp.max = Math.round((mu + _in + ch) / 2)
|
||||
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.regeneration = systemData.regeneration ?? {
|
||||
lep: "1d6",
|
||||
|
|
@ -105,6 +105,19 @@ 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 = {
|
||||
|
|
@ -119,11 +132,11 @@ export class Character extends Actor {
|
|||
} else {
|
||||
systemData.rs = 0; // only with DSA_4-1.optional_trefferzonen = false
|
||||
}
|
||||
systemData.be = 0;
|
||||
systemData.be = 0 + systemData.ueberanstrengung;
|
||||
|
||||
|
||||
// half KO is the maximum a character can sustain wounds before collapsing
|
||||
systemData.wunden.max = ko / 2;
|
||||
systemData.wunden.max = Math.round(ko / 2);
|
||||
if (game.settings.get("DSA_4-1", "optional_trefferzonen")) {
|
||||
systemData.wunden.kopf = 0
|
||||
systemData.wunden.brust = 0
|
||||
|
|
@ -310,6 +323,136 @@ 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) {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
export class Deity extends Item {
|
||||
/**
|
||||
* Augment the basic Item data model with additional dynamic data.
|
||||
*/
|
||||
prepareData() {
|
||||
super.prepareData();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
*
|
||||
* @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
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
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,
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
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,
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
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,
|
||||
}
|
||||
|
|
@ -80,7 +80,11 @@ export class ActionManager {
|
|||
type: ActionManager.ATTACK,
|
||||
cost: ActionManager.REGULAR,
|
||||
source: ActionManager.DEFAULT,
|
||||
eval: (options) => this.#hatWaffeinHand(options)
|
||||
activate: (queue, data) => {
|
||||
data.actor.rollAttack(data)
|
||||
return true
|
||||
},
|
||||
eval: (options) => this.#hatWaffeinHand(options),
|
||||
},
|
||||
{
|
||||
name: "Fernkampfangriff",
|
||||
|
|
@ -89,6 +93,8 @@ export class ActionManager {
|
|||
source: ActionManager.DEFAULT,
|
||||
cooldown: (options) => 1,
|
||||
activate: (queue, data) => {
|
||||
data.actor.rollAttack(data)
|
||||
return true
|
||||
},
|
||||
eval: (options) => this.#hatFernkampfWaffeinHand(options),
|
||||
},
|
||||
|
|
@ -98,6 +104,10 @@ 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)
|
||||
|
|
@ -211,6 +221,10 @@ 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)
|
||||
|
|
@ -227,6 +241,10 @@ 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)
|
||||
|
|
@ -244,6 +262,10 @@ 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)
|
||||
|
|
@ -258,6 +280,10 @@ 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)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,20 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -23,6 +33,7 @@ export class AdvantageSheet extends HandlebarsApplicationMixin(DocumentSheetV2)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/** @inheritDoc */
|
||||
static PARTS = {
|
||||
form: {
|
||||
|
|
@ -30,9 +41,14 @@ 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)
|
||||
|
||||
|
|
@ -43,6 +59,173 @@ 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}
|
||||
|
|
@ -52,10 +235,32 @@ 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)
|
||||
|
||||
await this.document.update(formData.object) // Note: 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
|
||||
|
||||
console.log(formData.object, this.document)
|
||||
} 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
|
||||
}
|
||||
|
||||
/** @override */
|
||||
|
|
@ -71,7 +276,34 @@ 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
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) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
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) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
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) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
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) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
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) {
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,8 @@ export default {
|
|||
description: item.system.description,
|
||||
isAdvantage: !item.system.nachteil,
|
||||
isDisadvantage: item.system.nachteil,
|
||||
isBadAttribute: item.system.schlechteEigenschaft
|
||||
isBadAttribute: item.system.schlechteEigenschaft,
|
||||
fav: item.getFlag("DSA_4-1", "favourite")
|
||||
})
|
||||
} else {
|
||||
context.flaws.push({
|
||||
|
|
@ -32,7 +33,8 @@ export default {
|
|||
description: item.system.description,
|
||||
isAdvantage: !item.system.nachteil,
|
||||
isDisadvantage: item.system.nachteil,
|
||||
isBadAttribute: item.system.schlechteEigenschaft
|
||||
isBadAttribute: item.system.schlechteEigenschaft,
|
||||
fav: item.getFlag("DSA_4-1", "favourite")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +45,7 @@ export default {
|
|||
context.specialAbilities.push({
|
||||
id: item._id,
|
||||
name: item.system.value ? item.system.value : item.name,
|
||||
fav: item.getFlag("DSA_4-1", "favourite")
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import {PlayerCharacterDataModel} from "../../data/character.mjs";
|
||||
|
||||
export default {
|
||||
_prepareContext: (context) => {
|
||||
_prepareContext: (context, actor, thisObject) => {
|
||||
|
||||
const actorData = context.document
|
||||
context.spells = []
|
||||
|
|
@ -52,9 +50,11 @@ export default {
|
|||
for (let setIndex = 0; setIndex < maxSets; setIndex++) {
|
||||
|
||||
context.sets.push({
|
||||
tab: "set" + (setIndex + 1),
|
||||
name: romanNumerals[setIndex],
|
||||
tab: "pane" + (setIndex + 1),
|
||||
label: romanNumerals[setIndex],
|
||||
index: setIndex,
|
||||
actorId: actorData.id,
|
||||
setEquipped: actorData.system.setEquipped === setIndex,
|
||||
slots: [
|
||||
{
|
||||
target: "links",
|
||||
|
|
@ -131,6 +131,8 @@ export default {
|
|||
]
|
||||
})
|
||||
}
|
||||
context.selectedTab = thisObject.selectedTab ?? context.sets[0].tab
|
||||
context.setEquipped = actorData.system.setEquipped
|
||||
|
||||
return context
|
||||
},
|
||||
|
|
@ -150,9 +152,50 @@ 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,
|
||||
".equipment",
|
||||
".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",
|
||||
[
|
||||
{
|
||||
name: "Abrüsten",
|
||||
|
|
@ -280,7 +323,7 @@ export default {
|
|||
name: "Aus dem Inventar entfernen",
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
callback: (target) => {
|
||||
thisObject.document.deleteEmbeddedDocuments('Item', [target.dataset.itemId])
|
||||
game.DSA41.socket.executeAsGM("removeFromLootTable", thisObject.document.id, target.dataset.itemId)
|
||||
},
|
||||
condition: (target) => {
|
||||
const {itemId} = target.dataset
|
||||
|
|
@ -290,7 +333,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`
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import {ActionManager} from "../actions/action-manager.mjs";
|
||||
|
||||
export default {
|
||||
_prepareContext: async (context, object) => {
|
||||
|
||||
|
|
@ -15,9 +13,6 @@ 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
|
||||
|
|
@ -26,9 +21,35 @@ 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.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
|
||||
|
||||
|
|
@ -37,7 +58,7 @@ export default {
|
|||
|
||||
},
|
||||
_getTabConfig: (group) => {
|
||||
group.tabs.push({id: "combat", group: "sheet", label: "Kampf"})
|
||||
group.tabs.push({id: "health", group: "sheet", label: "Gesundheit"})
|
||||
},
|
||||
template: `systems/DSA_4-1/templates/actor/character/tab-combat.hbs`
|
||||
template: `systems/DSA_4-1/templates/actor/character/tab-health.hbs`
|
||||
}
|
||||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ export default {
|
|||
_prepareContext: (context) => {
|
||||
|
||||
const actorData = context.document
|
||||
context.spells = []
|
||||
context.system = actorData.system
|
||||
context.flags = actorData.flags
|
||||
context.derived = context.document.system
|
||||
|
|
@ -51,7 +50,8 @@ export default {
|
|||
id: item._id,
|
||||
at: item.system.at,
|
||||
pa: item.system.pa,
|
||||
komplexität: item.system.komplexität
|
||||
komplexität: item.system.komplexität,
|
||||
fav: item.getFlag("DSA_4-1", "favourite")
|
||||
};
|
||||
|
||||
if (talentGruppe === "Kampf") {
|
||||
|
|
|
|||
|
|
@ -14,14 +14,21 @@ 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: this.prepareEigenschaftRoll(actorData, eigenschaften[0])},
|
||||
{name: eigenschaften[1], value: this.prepareEigenschaftRoll(actorData, eigenschaften[1])},
|
||||
{name: eigenschaften[2], value: this.prepareEigenschaftRoll(actorData, eigenschaften[2])}
|
||||
{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,
|
||||
|
|
@ -35,8 +42,9 @@ 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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Advsf from "./character/advsf.mjs"
|
||||
import Combat from "./character/combat.mjs"
|
||||
import Health from "./character/health.mjs"
|
||||
import Effects from "./character/effects.mjs"
|
||||
import Equipment from "./character/equipment.mjs"
|
||||
import Liturgies from "./character/liturgies.mjs"
|
||||
|
|
@ -11,14 +11,21 @@ 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) {
|
||||
|
|
@ -39,19 +46,33 @@ 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,
|
||||
openLiturgyDialog: CharacterSheet.openLiturgyDialog,
|
||||
openSpellDialog: CharacterSheet.openSpellDialog,
|
||||
castSpell: CharacterSheet.castSpell,
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -60,7 +81,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
sheet: {
|
||||
tabs: [],
|
||||
initial: 'meta'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
|
|
@ -77,15 +98,16 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
advsf: {
|
||||
template: Advsf.template
|
||||
},
|
||||
combat: {
|
||||
template: Combat.template
|
||||
health: {
|
||||
template: Health.template
|
||||
},
|
||||
equipment: {
|
||||
template: Equipment.template,
|
||||
scrollable: ['']
|
||||
scrollable: ['.inventory']
|
||||
},
|
||||
skills: {
|
||||
template: Skills.template
|
||||
template: Skills.template,
|
||||
scrollable: ['.tab.skills']
|
||||
},
|
||||
spells: {
|
||||
template: Spells.template
|
||||
|
|
@ -95,7 +117,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
},
|
||||
effects: {
|
||||
template: Effects.template
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -130,7 +152,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)
|
||||
}
|
||||
|
||||
|
|
@ -141,12 +163,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.maneuver.name} um 1 Aktion reduziert`)
|
||||
ui.notifications.info(`Abklingzeit von ${cooldown.data.title} um 1 Aktion reduziert`)
|
||||
}
|
||||
|
||||
static async #cancelCooldown(event, target) {
|
||||
|
|
@ -165,26 +187,26 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
const cooldowns = this.document.system.cooldowns
|
||||
const cooldown = this.document.system.cooldowns[cooldownId]
|
||||
|
||||
if (cooldown && cooldown.current <= 0) {
|
||||
if (cooldown && cooldown.current >= cooldown.start) {
|
||||
const am = new ActionManager(this.document)
|
||||
const action = am.evaluate().find(action => action.name === cooldown.data.maneuver.id)
|
||||
const action = new Function(`return ${cooldown.data.maneuver}`)
|
||||
|
||||
if (action) {
|
||||
action.activate(cooldowns, {...cooldown.data, actor: this.document})
|
||||
action()(this.document.system.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.maneuver.name} ausgeführt`)
|
||||
ui.notifications.info(`${cooldown.data.title} 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
|
||||
|
|
@ -203,21 +225,33 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
}
|
||||
|
||||
static #openCombatAction(event, target) {
|
||||
let {weapon, skill} = target.dataset
|
||||
|
||||
switch (target.dataset.mode) {
|
||||
case "attack":
|
||||
new CombatActionDialog(this.document).render(true)
|
||||
new CombatActionDialog(this.document, {weapon, skill}).render(true)
|
||||
break
|
||||
case "defense":
|
||||
new DefenseActionDialog(this.document).render(true)
|
||||
new DefenseActionDialog(this.document, {weapon, skill}).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)
|
||||
|
||||
|
|
@ -239,6 +273,65 @@ 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)
|
||||
|
||||
|
|
@ -249,6 +342,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
return options
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle form submission
|
||||
* @this {AdvantageSheet}
|
||||
|
|
@ -259,16 +353,49 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
static async #onSubmitForm(event, form, formData) {
|
||||
event.preventDefault()
|
||||
|
||||
await this.document.update(formData.object) // Note: formData.object
|
||||
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'),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_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)
|
||||
Combat._getTabConfig(tabs, this)
|
||||
Health._getTabConfig(tabs, this)
|
||||
Equipment._getTabConfig(tabs, this)
|
||||
Skills._getTabConfig(tabs, this)
|
||||
Spells._getTabConfig(tabs, this)
|
||||
|
|
@ -330,13 +457,6 @@ 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")
|
||||
|
|
@ -351,8 +471,11 @@ 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)
|
||||
|
|
@ -361,18 +484,19 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
|
||||
if (fernkampf) {
|
||||
const fkitems = fernkampf.system.rangedSkills.map((skillInQuestion) => actorData.items.find(p => p.name === skillInQuestion))
|
||||
fkitems.forEach(async skill => {
|
||||
const obj = await skill
|
||||
context.attacks.push({
|
||||
name: obj.name,
|
||||
using: fernkampf.name,
|
||||
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}`,
|
||||
})
|
||||
fkitems?.forEach(skill => {
|
||||
if (skill) {
|
||||
context.attacks.push({
|
||||
name: skill.name,
|
||||
id: fernkampf._id,
|
||||
skillId: skill._id,
|
||||
using: fernkampf.name,
|
||||
isRanged: true,
|
||||
at: `${this.document.system.fk.aktuell + skill.system.at}`,
|
||||
tp: `${fernkampf.system.rangedAttackDamage}`,
|
||||
ini: `${context.inidice}w6 + ${context.inivalue + fernkampf.system.iniModifier ?? 0}`,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
if (links) {
|
||||
|
|
@ -383,18 +507,17 @@ 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,
|
||||
atroll: `1d20cs<${this.document.system.at.links.aktuell + obj.system.at + links.system.attackModifier}`, // TODO consider adding W/M
|
||||
isRanged: false,
|
||||
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}`,
|
||||
})
|
||||
})
|
||||
|
|
@ -407,30 +530,82 @@ 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,
|
||||
atroll: `1d20cs<${this.document.system.at.rechts.aktuell + obj.system.at + rechts.system.attackModifier}`, // TODO consider adding W/M
|
||||
isRanged: false,
|
||||
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 => {
|
||||
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)
|
||||
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
|
||||
cooldown.progress = ((cooldown.current / cooldown.start) * 100) + "%"
|
||||
cooldown.tooltip = `${cooldown.data.maneuver.name}<br/>Waffe:${weapon.name}<br/>Ziel: ${target.name}<br/>Wurfziel: ${cooldown.data.targetNumber}<br/>Aktionen verbleibend: ${cooldown.current}`
|
||||
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`
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
context.hasSpells = actorData.itemTypes["Spell"].length > 0
|
||||
|
|
@ -497,11 +672,11 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
case "advsf":
|
||||
await Advsf._prepareContext(context, this.document)
|
||||
break
|
||||
case "combat":
|
||||
await Combat._prepareContext(context, this.document)
|
||||
case "health":
|
||||
await Health._prepareContext(context, this.document)
|
||||
break
|
||||
case "equipment":
|
||||
await Equipment._prepareContext(context, this.document)
|
||||
await Equipment._prepareContext(context, this.document, this)
|
||||
break
|
||||
case "skills":
|
||||
await Skills._prepareContext(context, this.document)
|
||||
|
|
@ -519,11 +694,23 @@ 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)
|
||||
Combat._onRender(context, options, this.element)
|
||||
Health._onRender(context, options, this.element)
|
||||
Effects._onRender(context, options, this.element)
|
||||
Equipment._onRender(context, options, this)
|
||||
Liturgies._onRender(context, options, this.element)
|
||||
|
|
@ -535,18 +722,28 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
return true
|
||||
}
|
||||
|
||||
|
||||
async _onDrop(event) {
|
||||
const data = TextEditor.implementation.getDragEventData(event);
|
||||
const actor = this.actor;
|
||||
const data = TextEditor.implementation.getDragEventData(event)
|
||||
const targetDocument = this.actor.itemTypes["Equipment"].find(p => p._id === event.target.dataset['itemId'])
|
||||
//const allowed = Hooks.call("dropActorSheetData", actor, this, data);
|
||||
//if (allowed === false) return;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
@ -566,7 +763,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
} else {
|
||||
|
||||
if (document.parent && document.parent !== this.actor) {
|
||||
document.parent.items.get(document._id).delete()
|
||||
game.DSA41.socket.executeAsGM("removeFromLootTable", document.parent.id, document._id)
|
||||
}
|
||||
|
||||
await this._onDropDocument(event, document)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
static TABS = {
|
||||
sheet: {
|
||||
tabs: [],
|
||||
initial: 'meta'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +65,19 @@ 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
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +101,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
static async #onSubmitForm(event, form, formData) {
|
||||
event.preventDefault()
|
||||
|
||||
await this.document.update(formData.object) // Note: formData.object
|
||||
await this.document.update(formData.object)
|
||||
}
|
||||
|
||||
static #openEmbeddedDocument(event, target) {
|
||||
|
|
@ -100,8 +112,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})
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +125,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
speaker: ChatMessage.getSpeaker({actor: this.document}),
|
||||
flavor: label,
|
||||
rollMode: game.settings.get('core', 'rollMode'),
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
static async #addAttack() {
|
||||
|
|
@ -140,11 +152,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({
|
||||
|
|
@ -180,7 +192,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
})
|
||||
|
||||
|
||||
return context;
|
||||
return context
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -199,7 +211,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);
|
||||
|
||||
|
|
@ -209,7 +221,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
document.parent.items.get(document._id).delete()
|
||||
}
|
||||
|
||||
await this._onDropDocument(event, document);
|
||||
await this._onDropDocument(event, document)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -226,7 +238,7 @@ export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|||
dragstart: this._onDragStart.bind(this),
|
||||
drop: this._onDrop.bind(this)
|
||||
}
|
||||
}).bind(this.element);
|
||||
}).bind(this.element)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export class CultureSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
|
|||
static async #onSubmitForm(event, form, formData) {
|
||||
event.preventDefault()
|
||||
|
||||
await this.document.update(formData.object) // Note: formData.object
|
||||
await this.document.update(formData.object)
|
||||
}
|
||||
|
||||
/** @override */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
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"]})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,17 +1,6 @@
|
|||
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
class EquipmentSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
|
||||
|
||||
/** @inheritDoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
|
|
@ -322,3 +311,5 @@ export class EquipmentSheet extends HandlebarsApplicationMixin(DocumentSheetV2)
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default EquipmentSheet
|
||||
|
|
|
|||
|
|
@ -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-table",
|
||||
dropSelector: ".inventory",
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ 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,
|
||||
|
|
@ -81,6 +83,63 @@ 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;
|
||||
|
||||
|
|
@ -204,6 +263,7 @@ 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,15 @@ 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: [
|
||||
|
|
@ -32,8 +38,13 @@ 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)
|
||||
|
||||
|
|
@ -44,6 +55,173 @@ 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}
|
||||
|
|
@ -53,8 +231,32 @@ 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)
|
||||
|
||||
await this.document.update(formData.object) // Note: 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 */
|
||||
|
|
@ -82,7 +284,35 @@ 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +392,8 @@ export class XmlImport {
|
|||
|
||||
try {
|
||||
const embeddedDocument = (await actor.createEmbeddedDocuments('Item', [spell]))[0]
|
||||
embeddedDocument.update({system: {zfw: zfw, hauszauber: hauszauber, repräsentation: representation}});
|
||||
embeddedDocument.update({system: {zfw: zfw, hauszauber: hauszauber}})
|
||||
embeddedDocument.setFlag("DSA_4-1", "representation", representation)
|
||||
} catch (error) {
|
||||
console.error(`${spell} not found in items`, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"name": "Aufmerksamkeit",
|
||||
"seite": "54",
|
||||
"gruppe": "Kampf",
|
||||
"requirement": [
|
||||
{
|
||||
"attribute": "attribute.in.aktuell",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"todo": "Wir brauchen hier ein Konztept!",
|
||||
"name": "Ausfall",
|
||||
"seite": "59",
|
||||
"gruppe": "Kampf",
|
||||
"sfPflicht": true,
|
||||
"requirement": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"name": "Ausweichen",
|
||||
"value": ["Ausweichen I","Ausweichen II","Ausweichen III"],
|
||||
"gruppe": "Kampf",
|
||||
"auswahl": [
|
||||
{
|
||||
"name": "Ausweichen I",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"name": "Befreiungsschlag",
|
||||
"seite": "60",
|
||||
"gruppe": "Kampf",
|
||||
"sfPflicht": true,
|
||||
"requirement": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Beidhändiger Kampf",
|
||||
"gruppe": "Kampf",
|
||||
"value": ["Beidhändiger Kampf I","Beidhändiger Kampf II"],
|
||||
"auswahl": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Betäubungsschlag",
|
||||
"gruppe": "Kampf",
|
||||
"seite": "61",
|
||||
"sfPflicht": true,
|
||||
"requirement": [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Binden",
|
||||
"gruppe": "Kampf",
|
||||
"seite": "67",
|
||||
"requirement": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Blindkampf",
|
||||
"gruppe": "Kampf",
|
||||
"seite": "67",
|
||||
"requirement": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Defensiver Kampfstil",
|
||||
"gruppe": "Kampf",
|
||||
"seite": "81",
|
||||
"requirement": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Doppelangriff",
|
||||
"gruppe": "Kampf",
|
||||
"seite": "61",
|
||||
"sfPflicht": true,
|
||||
"requirement": [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Entwaffnen",
|
||||
"gruppe": "Kampf",
|
||||
"seite": "61",
|
||||
"sfPflicht": true,
|
||||
"requirement": [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Festnageln",
|
||||
"gruppe": "Kampf",
|
||||
"seite": "62",
|
||||
"sfPflicht": true,
|
||||
"requirement": [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Finte",
|
||||
"gruppe": "Kampf",
|
||||
"seite": "62",
|
||||
"sfPflicht": true,
|
||||
"requirement": [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Formation",
|
||||
"gruppe": "Kampf",
|
||||
"seite": "62",
|
||||
"sfPflicht": true,
|
||||
"requirement": [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Kampfreflexe",
|
||||
"gruppe": "Kampf",
|
||||
"seite": "",
|
||||
"requirement": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Meisterliches Entwaffnen",
|
||||
"gruppe": "Kampf",
|
||||
"seite": "61",
|
||||
"sfPflicht": true,
|
||||
"requirement": [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"seite": "11",
|
||||
"name": "ABVENENUM REINE SPEISE",
|
||||
"probe": [
|
||||
"KL",
|
||||
"KL",
|
||||
"FF"
|
||||
],
|
||||
"probeMod": "+Mod.",
|
||||
"technik": "Der Elf spricht bha’sama venya bha’za 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)."
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"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."
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"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 a’dao 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."
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"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."
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"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."
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
{
|
||||
"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. 3–5 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."
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"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."
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"seite": "36",
|
||||
"name": "AXXELERATUS BLITZGESCHWIND",
|
||||
"probe": [
|
||||
"KL",
|
||||
"GE",
|
||||
"KO"
|
||||
],
|
||||
"technik": "Die Elfe konzentriert sich auf alle Muskeln und Sehnen ihres Körpers und spricht a’sela 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."
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"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 bhas’sama sala bian da’o 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."
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"seite": "39",
|
||||
"name": "BANNBALADIN",
|
||||
"probe": [
|
||||
"IN",
|
||||
"CH",
|
||||
"CH"
|
||||
],
|
||||
"probeMod": "+MR",
|
||||
"technik": "Der Elf blickt seinem Opfer in die Augen und spricht blah bla la’din.",
|
||||
"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."
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"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
Loading…
Reference in New Issue