Compare commits

..

43 Commits

Author SHA1 Message Date
macniel 53c5c7b53a Merge pull request 'feature/applicationv2' (#57) from feature/applicationv2 into main
Reviewed-on: #57
2025-10-21 01:19:41 +02:00
macniel 5132649a7c Merge branch 'main' into feature/applicationv2
Pull Request Check / testing (pull_request) Successful in 16s Details
2025-10-21 01:18:40 +02:00
macniel 905b0eb405 prepares xml-import to no longer append items, but instead cleans them out before adding. This will be controllable via dialog
Pull Request Check / testing (pull_request) Successful in 17s Details
2025-10-21 01:17:41 +02:00
macniel 2e1d747661 Merge branch 'main' into feature/applicationv2
Pull Request Check / testing (pull_request) Successful in 17s Details
2025-10-20 21:22:24 +02:00
macniel faa5853aa4 adjusts styling of actions
Pull Request Check / testing (pull_request) Successful in 17s Details
2025-10-20 20:57:14 +02:00
macniel b3f5e68c23 reestablishes armor values
Pull Request Check / testing (pull_request) Successful in 17s Details
2025-10-20 20:30:54 +02:00
macniel 89d427b711 fixes kinks in equipping weapons 2025-10-20 19:43:43 +02:00
macniel 321ba7d3d6 restores drag and drop functionality for items onto character sheet
Pull Request Check / testing (pull_request) Successful in 21s Details
2025-10-20 18:11:52 +02:00
macniel ad28cf48f3 fixes Wounds display 2025-10-20 12:47:25 +02:00
macniel c9f9c920a6 fixes Wounds for simplified system 2025-10-20 12:46:05 +02:00
macniel eb88377f14 repairs visuals advantages and special abilities 2025-10-20 12:34:00 +02:00
macniel 523cbb9f62 repairs visuals of combat tab 2025-10-20 12:24:46 +02:00
macniel 49896e0966 repairs visuals of combat tab 2025-10-20 12:24:43 +02:00
macniel e118e8ba92 restores opening of linked documents in their respective sheet 2025-10-20 10:43:49 +02:00
macniel ee578e430e restores equipment drag and drop while sacrificing paperdoll view 2025-10-20 10:31:15 +02:00
yuna 32503a03c1 restores inventory template 2025-10-18 11:55:37 +02:00
yuna b13fc29791 fixes display of liturgies and preliminary styling 2025-10-18 11:47:04 +02:00
macniel 2dd920a094 shortens display of language skills 2025-10-17 23:28:02 +02:00
macniel 2900a45959 restores rollability also for combat skills 2025-10-17 22:55:44 +02:00
macniel e031fe712c restores rollability 2025-10-17 22:16:15 +02:00
macniel aa6a8d1bcc restores visuals of skills 2025-10-17 22:06:10 +02:00
macniel 355f55e2bd finally fixes the stubborn tab error 2025-10-17 21:52:40 +02:00
macniel d0c2d74721 fixes pathing 2025-10-17 18:29:08 +02:00
macniel 69ceb48871 restores hiding of unimportant sheets 2025-10-17 18:24:58 +02:00
macniel 34a5028e30 removes labeling and adds custom fonts 2025-10-17 18:10:22 +02:00
macniel 74e91d206f adds optional styling according to Paramanthus 2025-10-17 00:10:24 +02:00
macniel 96d7b18742 requires retargeting of styling 2025-10-16 21:46:35 +02:00
macniel 561e34d0ff first push of charactersheet 2025-10-16 21:16:57 +02:00
macniel f74bb38f3a fixes groupsheet dragging and dropping 2025-10-16 18:22:09 +02:00
macniel 6f1bad0b67 partial migration of groupsheet 2025-10-16 16:24:36 +02:00
macniel e14e4e7108 WIP group sheet 2025-10-16 00:06:54 +02:00
macniel 5e41285b1c WIP group sheet 2025-10-16 00:06:50 +02:00
macniel 9984db4ca6 migrates CreatureSheet to ActorSheetV2 2025-10-15 22:29:48 +02:00
macniel 6bfd509c2c modifies structuring of stylesheets to reflect the first semblance of a design pattern 2025-10-15 21:07:56 +02:00
macniel 626474178d migrates active effect to DocumentV2 2025-10-15 21:02:56 +02:00
macniel 7ea6b4a2e0 Fixes styling of attack-statistics and visibility of different statistics base on the nature of the given skill 2025-10-15 20:19:59 +02:00
macniel f505a233df Migrates Specialabilities and Liturgies to DocumentV2 2025-10-15 20:10:52 +02:00
macniel 962fc482a8 Removes old spell sheet 2025-10-15 19:55:24 +02:00
macniel 3f5ef8fbd7 migrates spells and equipment to DocumentV2 2025-10-15 19:55:13 +02:00
macniel 343d180568 removes old sheet 2025-10-15 16:25:11 +02:00
macniel 3869982927 migrates advantages to DocumentV2 2025-10-15 16:24:51 +02:00
macniel 232347aae5 fixes typos and stuff 2025-10-15 16:24:35 +02:00
macniel 64ae1b44b5 migrates Skill sheet to DocumentV2 2025-10-15 15:50:27 +02:00
168 changed files with 6556 additions and 5066 deletions

View File

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

View File

@ -5,12 +5,12 @@ 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 {VornachteilSheet} from "./module/sheets/vornachteilSheet.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 {AusruestungSheet} from "./module/sheets/equipmentSheet.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";
@ -21,6 +21,12 @@ 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";
async function preloadHandlebarsTemplates() {
return foundry.applications.handlebars.loadTemplates([
@ -35,7 +41,6 @@ async function preloadHandlebarsTemplates() {
'systems/DSA_4-1/templates/ui/partial-sf-button.hbs',
'systems/DSA_4-1/templates/ui/partial-action-button.hbs',
'systems/DSA_4-1/templates/ui/partial-equipment-button.hbs',
'systems/DSA_4-1/templates/ui/partial-equipment-group-button.hbs',
'systems/DSA_4-1/templates/ui/partial-array-editor.hbs',
'systems/DSA_4-1/templates/dialog/modify-liturgy.hbs'
]);
@ -70,6 +75,9 @@ Hooks.once("init", () => {
Blessing: BlessingDataModel,
SpecialAbility: SpecialAbilityDataModel,
ActiveEffect: ActiveEffectDataModel,
Profession: ProfessionDataModel,
Spezies: SpeciesDataModel,
Kultur: CultureDataModel,
}
CONFIG.Combat.initiative = {
@ -94,8 +102,6 @@ Hooks.once("init", () => {
makeDefault: true,
label: 'DSA41.GroupLabel.Item'
})
// Register sheet application classes
foundry.documents.collections.Items.registerSheet('dsa41.skill', SkillSheet, {
types: ["Skill"],
makeDefault: true,
@ -106,33 +112,58 @@ Hooks.once("init", () => {
makeDefault: true,
label: 'DSA41.SpellLabels.Item',
});
foundry.documents.collections.Items.registerSheet('dsa41.advantage', VornachteilSheet, {
foundry.documents.collections.Items.registerSheet('dsa41.advantage', AdvantageSheet, {
types: ["Advantage"],
makeDefault: true,
label: 'DSA41.VornachteilLabels.Item'
})
foundry.documents.collections.Items.registerSheet('dsa41.equipment', AusruestungSheet, {
foundry.documents.collections.Items.registerSheet('dsa41.equipment', EquipmentSheet, {
types: ["Equipment"],
makeDefault: false,
label: 'DSA41.AusruestungLabels.Item'
})
foundry.documents.collections.Items.registerSheet('dsa41.liturgy', LiturgySheet, {
types: ["SpecialAbility"],
makeDefault: true,
label: 'DSA41.SpecialAbilityLabels.Item'
})
foundry.documents.collections.Items.registerSheet('dsa41.specialAbility', SpecialAbilitySheet, {
types: ["Liturgy"],
makeDefault: true,
label: 'DSA41.LiturgyLabels.Item'
})
foundry.documents.collections.Items.registerSheet('dsa41.specialAbility', SpecialAbilitySheet, {
types: ["SpecialAbility"],
makeDefault: true,
label: 'DSA41.SpecialAbilityLabels.Item'
})
foundry.documents.collections.Items.registerSheet('dsa41.activeEffect', ActiveEffectSheet, {
types: ['ActiveEffect'],
makeDefault: true,
label: 'DSA41.ActiveEffectLabels.ActiveFfect'
label: 'DSA41.ActiveEffectLabels.ActiveEffect'
})
foundry.documents.collections.Items.registerSheet('dsa41.culture', CultureSheet, {
types: ['Culture'],
makeDefault: true,
label: 'DSA41.CultureLabels.Culture'
})
foundry.documents.collections.Items.registerSheet('dsa41.spezien', SpeciesSheet, {
types: ['Species'],
makeDefault: true,
label: 'DSA41.SpeciesLabels.Species'
})
foundry.documents.collections.Items.registerSheet('dsa41.profession', ProfessionSheet, {
types: ['Profession'],
makeDefault: true,
label: 'DSA41.ProfessionLabels.Profession'
})
game.settings.register('DSA_4-1', 'optional_colorfuldice', {
name: "Optional: Farbige Würfel nach Paramanthus",
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",
@ -144,7 +175,6 @@ Hooks.once("init", () => {
},
requiresReload: true
})
game.settings.register('DSA_4-1', 'optional_ruestungzonen', {
name: "Optional: Zonenrüstung",
hint: "Ersetzt das Rüstungssystem aus dem BRW durch das Zonenrüstungssystem aus WdH",
@ -156,7 +186,6 @@ Hooks.once("init", () => {
},
requiresReload: true
})
game.settings.register('DSA_4-1', 'optional_ausdauer', {
name: "Optional: Ausdauerregeln",
hint: "Aktiviert Regeln für das Spiel mit Ausdauer",
@ -168,7 +197,6 @@ Hooks.once("init", () => {
},
requiresReload: true
})
game.settings.register('DSA_4-1', 'optional_distanzklassen', {
name: "Optional: Distanzklassen",
hint: "Aktiviert Regeln für das Spiel mit Distanzklassen",
@ -184,16 +212,6 @@ Hooks.once("init", () => {
return preloadHandlebarsTemplates();
})
Hooks.on('dropActorSheetData', (actor, sheet, data) => {
if (data.uuid) {
if (actor.type === "character") {
return CharacterSheet.onDroppedData(actor, sheet, data);
} else {
return GroupSheet.onDroppedData(actor, sheet, data);
}
}
})
Hooks.once("ready", async function () {
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
Hooks.on("hotbarDrop", (bar, data, slot) => {

View File

@ -1,12 +1,12 @@
import {Equipment} from "../documents/equipment.mjs";
const {
SchemaField,
NumberField,
StringField,
HTMLField,
EmbeddedDocumentField,
DocumentIdField,
ArrayField,
ForeignDocumentField
} = foundry.data.fields;
export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
@ -29,6 +29,8 @@ export class PlayerCharacterDataModel extends foundry.abstract.TypeDataModel {
familie: new HTMLField(),
titel: new StringField(),
stand: new StringField(),
verbindungen: new HTMLField(),
notizen: new HTMLField(),
}),
setEquipped: new NumberField({required: true, initial: 0, max: 3, integer: true}),
ini: new SchemaField({

View File

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

View File

@ -1,7 +1,8 @@
import BaseItem from "./base-item.mjs";
import {Equipment} from "../documents/equipment.mjs";
const {
ArrayField, NumberField, StringField, HTMLField
ArrayField, EmbeddedCollectionField, SchemaField, NumberField, StringField, HTMLField
} = foundry.data.fields;
export class EquipmentDataModel extends BaseItem {
@ -34,9 +35,25 @@ export class EquipmentDataModel extends BaseItem {
rangedAttackDamage: new StringField(),
rangedReloadTime: new NumberField({required: false}),
armorValue: new NumberField({required: false}),
armorValue: new SchemaField({
total: new NumberField({required: true, initial: 0}),
armlinks: new NumberField({required: true, initial: 0}),
beinlinks: new NumberField({required: true, initial: 0}),
armrechts: new NumberField({required: true, initial: 0}),
beinrechts: new NumberField({required: true, initial: 0}),
ruecken: new NumberField({required: true, initial: 0}),
bauch: new NumberField({required: true, initial: 0}),
brust: new NumberField({required: true, initial: 0}),
kopf: new NumberField({required: true, initial: 0}),
}, {required: false}),
armorHandicap: new NumberField({required: false}),
ammunition: new SchemaField({
max: new NumberField({required: true, initial: 1}),
count: new NumberField({required: true, initial: 1}),
}, {required: false}),
}
}
}

View File

@ -18,6 +18,7 @@ export class GroupDataModel extends foundry.abstract.TypeDataModel {
quantity: new NumberField(),
item: new DocumentIdField(Item)
}),
groupId: new DocumentIdField(Actor),
characters: new ArrayField(
new DocumentIdField(Actor)
),
@ -25,4 +26,13 @@ export class GroupDataModel extends foundry.abstract.TypeDataModel {
}
}
_onCreate(data, options, userId) {
super._onCreate(data, options, userId);
Folder.implementation.createDocuments([{name: data.name, type: "Actor"}]).then((
folder
) => {
this.parent.update({"system.groupId": folder[0]._id});
})
}
}

View File

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

View File

@ -44,12 +44,89 @@ export class SkillDataModel extends BaseItem {
}
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
* Determines the values to consult for the given type of roll (normal: talent roll, attack: AT, parry: PA
* @returns {{NORMAL: string, ATTACK: string, PARRY: string}}
* @constructor
*/
async roll() {
get SKILL_MODE() {
return {
NORMAL: "NORMAL",
ATTACK: "ATTACK",
PARRY: "PARRY",
}
}
/**
*
* @param rollMode {["publicroll","gmroll"] }
* @param mode
* @returns {Promise<void>}
*/
async roll(rollMode = null, mode = this.SKILL_MODE.NORMAL) {
rollMode = rollMode ?? game.settings.get('core', 'rollMode');
switch (mode) {
case this.SKILL_MODE.NORMAL:
return this.#talentRoll(rollMode)
case this.SKILL_MODE.ATTACK:
case this.SKILL_MODE.PARRY:
return this.#combatRoll(rollMode, mode)
}
}
async #combatRoll(rollMode, mode) {
const owner = this.parent.parent
const rollData = owner.getRollData()
let targetNumber = 0
if (mode === this.SKILL_MODE.ATTACK) {
targetNumber = this.at + owner.system.at.basis
} else {
targetNumber = this.pa + owner.system.pa.basis
}
let roll1 = new Roll(`1d20cs<${targetNumber}`, owner.getRollData());
let evaluated1 = (await roll1.evaluate())
const rolledValue = evaluated1.terms[0].results[0].result
if (rolledValue === 1 || rolledValue === 20) { // TODO: Modify this target
// fill with actual evaluation (targetNumber should be reduced by X and roll against that again)
}
let message = ""
if (mode === this.SKILL_MODE.ATTACK) {
if (rolledValue <= targetNumber) {
message = `Würde treffen [${rolledValue}]`
} else {
message = `Verfehlt [${rolledValue}]`
}
} else {
if (rolledValue <= targetNumber) {
message = `Würde parrieren [${rolledValue}]`
} else {
message = `Verfehlt die parade [${rolledValue}]`
}
}
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: owner}),
flavor: message,
rollMode,
})
}
async #talentRoll(rollMode) {
const owner = this.parent.parent
let roll1 = new Roll("3d20", owner.getRollData());
let evaluated1 = (await roll1.evaluate())
@ -63,13 +140,13 @@ export class SkillDataModel extends BaseItem {
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: owner}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Meisterlich geschafft' : 'Geschafft'} mit ${dsaDieRollEvaluated.tap} Punkten übrig`,
rollMode: game.settings.get('core', 'rollMode'),
rollMode,
})
} else { // misserfolg
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: owner}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Gepatzt' : ''} mit ${Math.abs(dsaDieRollEvaluated.tap)} Punkten daneben`,
rollMode: game.settings.get('core', 'rollMode'),
rollMode,
})
}
}

View File

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

View File

@ -1,6 +1,7 @@
import {importCharacter} from "../xml-import/xml-import.mjs";
import {LiturgyData} from "../data/miracle/liturgydata.mjs";
import {Zonenruestung, Zonenwunde} from "../data/Trefferzone.js";
import {Zonenruestung, Zonenwunde, Wunde} from "../data/Trefferzone.js";
import {PlayerCharacterDataModel} from "../data/character.mjs";
export class Character extends Actor {
@ -91,7 +92,7 @@ export class Character extends Actor {
systemData.mr.basis = Math.round((mu + kl + ko) / 5)
systemData.mr.aktuell = systemData.mr.basis + (systemData.mr.mod ?? 0);
systemData.gs.basis = 6;
systemData.gs.aktuell = systemData.gs.basis + (systemData.gs.mod ?? 0); // TOOD: get GS from species
systemData.gs.aktuell = systemData.gs.basis + (systemData.gs.mod ?? 0); // TOOD: get GS from spezien
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
@ -116,6 +117,7 @@ export class Character extends Actor {
systemData.wunden.kopf = 0;
systemData.wunden.brust = 0;
systemData.wunden.bauch = 0;
systemData.wunden.ruecken = 0;
systemData.wunden.armlinks = 0;
systemData.wunden.armrechts = 0;
systemData.wunden.beinlinks = 0;
@ -127,73 +129,40 @@ export class Character extends Actor {
// map current set to RS and BE
const ausruestung = systemData.heldenausruestung[systemData.setEquipped];
if (ausruestung) {
if (ausruestung.brust) {
systemData.be += systemData.parent.items.get(ausruestung.brust).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.brust = systemData.parent.items.get(ausruestung.brust).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.brust).system.armorValue ?? 0
}
}
if (ausruestung.bauch) {
systemData.be += systemData.parent.items.get(ausruestung.bauch).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.bauch = systemData.parent.items.get(ausruestung.bauch).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.bauch).system.armorValue ?? 0
}
}
if (ausruestung.ruecken) {
systemData.be += systemData.parent.items.get(ausruestung.ruecken).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
// ruecken is not a valid trefferzone
} else {
systemData.rs += systemData.parent.items.get(ausruestung.ruecken).system.armorValue ?? 0
}
}
if (ausruestung.armlinks) {
systemData.be += systemData.parent.items.get(ausruestung.armlinks).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.armlinks = systemData.parent.items.get(ausruestung.armlinks).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.armlinks).system.armorValue ?? 0
}
}
if (ausruestung.armrechts) {
systemData.be += systemData.parent.items.get(ausruestung.armrechts).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.armrechts = systemData.parent.items.get(ausruestung.armrechts).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.armrechts).system.armorValue ?? 0
}
}
if (ausruestung.beinlinks) {
systemData.be += systemData.parent.items.get(ausruestung.beinlinks).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.beinlinks = systemData.parent.items.get(ausruestung.beinlinks).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.beinlinks).system.armorValue ?? 0
}
}
if (ausruestung.beinrechts) {
systemData.be += systemData.parent.items.get(ausruestung.beinrechts).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.beinrechts = systemData.parent.items.get(ausruestung.beinrechts).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.beinrechts).system.armorValue ?? 0
}
}
if (ausruestung.kopf) {
systemData.be += systemData.parent.items.get(ausruestung.kopf).system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.kopf = systemData.parent.items.get(ausruestung.kopf).system.armorValue ?? 0
} else {
systemData.rs += systemData.parent.items.get(ausruestung.kopf).system.armorValue ?? 0
}
}
const zonesToCheck = [
"brust",
"bauch",
"ruecken",
"kopf",
"armlinks",
"armrechts",
"beinlinks",
"beinrechts"]
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
systemData.rs.gesamt = 0
systemData.rs.brust = 0
systemData.rs.bauch = 0
systemData.rs.ruecken = 0
systemData.rs.kopf = 0
systemData.rs.armlinks = 0
systemData.rs.armrechts = 0
systemData.rs.beinlinks = 0
systemData.rs.beinrechts = 0
} else {
systemData.rs = 0
}
zonesToCheck.forEach((zone) => {
systemData.be += systemData.parent.items.get(ausruestung[zone])?.system.armorHandicap ?? 0
if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
zonesToCheck.forEach((itemZone) => {
systemData.rs[itemZone] += systemData.parent.items.get(ausruestung[zone])?.system.armorValue[itemZone] ?? 0
})
} else {
systemData.rs += systemData.parent.items.get(ausruestung[zone])?.system.armorValue.total ?? 0
}
})
systemData.kap.max = 0;
@ -223,14 +192,10 @@ export class Character extends Actor {
getRollData() {
const data = super.getRollData();
this.prepareDerivedData()
if (this.type !== 'character' && this.type !== 'creature') return;
if (data.attribute) {
for (let [k, v] of Object.entries(data.attribute)) {
data[k] = foundry.utils.deepClone(v);
}
}
// move sonderfertigkeiten into data, if it isn't in data the actor doesn't have that sonderfertigkeit
@ -245,6 +210,48 @@ export class Character extends Actor {
return data;
}
findEquipmentOnSlot(slot, setNumber) {
return this.items.get(this.system.heldenausruestung[setNumber ?? this.system.setEquipped]?.[slot])
}
getEquipmentSetUpdateObject() {
const updateObject = {}
Array.from(this.system.heldenausruestung).forEach((equipmentSet, index) => {
updateObject[`system.heldenausruestung.${index}.links`] = equipmentSet.links;
updateObject[`system.heldenausruestung.${index}.rechts`] = equipmentSet.rechts;
updateObject[`system.heldenausruestung.${index}.brust`] = equipmentSet.brust;
updateObject[`system.heldenausruestung.${index}.bauch`] = equipmentSet.bauch;
updateObject[`system.heldenausruestung.${index}.ruecken`] = equipmentSet.ruecken;
updateObject[`system.heldenausruestung.${index}.kopf`] = equipmentSet.kopf;
updateObject[`system.heldenausruestung.${index}.fernkampf`] = equipmentSet.fernkampf;
updateObject[`system.heldenausruestung.${index}.munition`] = equipmentSet.munition;
updateObject[`system.heldenausruestung.${index}.armlinks`] = equipmentSet.armlinks;
updateObject[`system.heldenausruestung.${index}.armrechts`] = equipmentSet.armrechts;
updateObject[`system.heldenausruestung.${index}.beinlinks`] = equipmentSet.beinlinks;
updateObject[`system.heldenausruestung.${index}.beinrechts`] = equipmentSet.beinrechts;
})
return updateObject;
}
isWorn(itemId) {
const slots = PlayerCharacterDataModel.getSlots()
const set = this.system.heldenausruestung[this.system.setEquipped]
if (set) {
for (const slot of slots) {
const equipmentSlotId = set[slot]
if (equipmentSlotId === itemId) {
return slot
}
}
}
return false
}
/**
*
* @param amount

View File

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

View File

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

View File

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

View File

@ -1,44 +1,65 @@
export class ActiveEffectSheet extends foundry.appv1.sheets.ItemSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'activeeffect'],
width: 520,
height: 480
});
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
export class ActiveEffectSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'item', 'activeeffect'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: ActiveEffectSheet.#onSubmitForm
},
actions: {
openEffect: ActiveEffectSheet.#openEffect,
editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage,
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/item/activeeffect/main-sheet.hbs`
},
}
static async #openEffect(evt) {
evt.preventDefault()
const {id} = evt.srcElement.dataset
const effect = await this.document.effects.get(id)
effect.sheet.render(true)
}
/**
* Handle form submission
* @this {AdvantageSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object) // Note: formData.object
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/item/item-activeeffect-sheet.hbs`;
}
async _prepareContext(options) {
/** @override */
getData() {
// Retrieve the data structure from the base sheet. You can inspect or log
// the context variable to see the structure, but some key properties for
// sheets are the actor object, the data object, whether or not it's
// editable, the items array, and the effects array.
const context = super.getData();
const effects = context.document.getEmbeddedCollection("ActiveEffect").contents;
const context = await super._prepareContext(options)
context.system = context.document.system
const effects = context.document.getEmbeddedCollection("ActiveEffect").contents
if (effects.length > 0) {
context.effectId = effects[0]._id;
context.effectId = effects[0]._id
}
return context;
context.name = context.document.name
context.img = context.document.img
context.notes = context.document.system.notes
return context
}
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
html.on('click', '.editEffects', (evt) => {
const {id} = evt.currentTarget.dataset;
const effect = this.object.effects.get(id);
effect.sheet.render(true);
})
}
}
}

View File

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

View File

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

View File

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

View File

@ -68,21 +68,21 @@ export class ActionManager {
type: ActionManager.ATTACK,
cost: ActionManager.REGULAR,
source: ActionManager.DEFAULT,
eval: () => true
eval: () => this.#hatWaffeinHand()
},
{
name: "Schnellschuss",
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.DEFAULT,
eval: () => true
eval: () => this.#hatFernkampfWaffeinHand()
},
{
name: "Schnellschuss (Scharfschütze)",
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.SF,
eval: () => this.#hatSonderfertigkeit("Scharfschütze")
eval: () => this.#hatFernkampfWaffeinHand() && this.#hatSonderfertigkeit("Scharfschütze")
},
{
name: "Abwehraktion",
@ -110,7 +110,14 @@ export class ActionManager {
type: ActionManager.ATTACK,
cost: ActionManager.REGULAR,
source: ActionManager.SF,
eval: () => this.#hatSonderfertigkeit("Finte")
eval: () => this.#hatFernkampfWaffeinHand() && this.#hatSonderfertigkeit("Finte")
},
{
name: "Wuchtschlag",
type: ActionManager.ATTACK,
cost: ActionManager.REGULAR,
source: ActionManager.DEFAULT,
eval: () => true
},
{
name: "Wuchtschlag",
@ -162,21 +169,21 @@ export class ActionManager {
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.SF,
eval: () => this.#hatSonderfertigkeit("Schnellladen (Bogen)")
eval: () => this.#hatMunition() && this.#hatFernkampfWaffeinHand("Bogen") && this.#hatSonderfertigkeit("Schnellladen (Bogen)")
},
{
name: "Schnellladen (Armbrust)",
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.SF,
eval: () => this.#hatSonderfertigkeit("Schnellladen (Armbrust)")
eval: () => this.#hatMunition() && this.#hatFernkampfWaffeinHand("Armbrust") && this.#hatSonderfertigkeit("Schnellladen (Armbrust)")
},
{
name: "Nachladen",
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.DEFAULT,
eval: () => true
eval: () => this.#hatMunition()
},
{
name: "Talenteinsatz",
@ -201,6 +208,23 @@ export class ActionManager {
}
]
#hatWaffeinHand() {
const item = this.actor.findEquipmentOnSlot("links") ?? this.actor.findEquipmentOnSlot("rechts")
return item
}
#hatMunition() {
const item = this.actor.findEquipmentOnSlot("munition")
const weapon = this.actor.findEquipmentOnSlot("fernkampf")
return item
}
#hatFernkampfWaffeinHand(art) {
const item = this.actor.findEquipmentOnSlot("fernkampf")
return item
}
#hatSonderfertigkeitBeginnendMit(name) {
return this.actor.system.sonderfertigkeiten?.find(p => p.name.startsWith(name)) != null
}
@ -211,8 +235,6 @@ export class ActionManager {
evaluate() {
let actionArray = [...this.#freeActions, ...this.#regularActions, ...this.#continuingActions]
return actionArray.filter(action => action.eval());
}

View File

@ -0,0 +1,65 @@
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
export class AdvantageSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'item', 'advantage'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: AdvantageSheet.#onSubmitForm
}
}
static TABS = {
sheet: {
tabs: [
{id: 'advantage', group: 'sheet', label: 'Vorteil'},
],
initial: 'advantage'
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/item/advantage/main-sheet.hbs`
},
advantage: {
template: `systems/DSA_4-1/templates/item/advantage/tab-advantage.hbs`
}
}
/**
* Handle form submission
* @this {AdvantageSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object) // Note: formData.object
}
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options);
const advantageData = context.document;
context.system = advantageData.system;
context.flags = context.system.flags;
context.hasChoices = context.system.auswahl.length > 0;
context.choices = {}
context.system.auswahl.forEach(a => {
context.choices[a] = a
})
context.hasModality = context.system.value != null
return context;
}
}

View File

@ -0,0 +1,53 @@
export default {
_prepareContext: async (context, options, object) => {
const actorData = context.document
context.system = actorData.system
context.flags = actorData.flags
context.derived = context.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
context.advantages = []
actorData.itemTypes.Advantage.forEach((item) => {
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
})
}
)
context.specialAbilities = []
actorData.itemTypes.SpecialAbility.forEach((item) => {
context.specialAbilities.push({
id: item._id,
name: item.name,
});
}
);
return context
},
_onRender: (context, options, thisObject) => {
new foundry.applications.ux.DragDrop.implementation({
dropSelector: ".advantages, .special-abilities",
permissions: {
drop: thisObject._canDragDrop.bind(thisObject)
},
callbacks: {
drop: thisObject._onDrop.bind(thisObject),
}
}).bind(thisObject.element);
},
_getTabConfig: (group) => {
group.tabs.push({id: "advsf", group: "sheet", label: "Vorteile"})
},
template: `systems/DSA_4-1/templates/actor/character/tab-advsf.hbs`
}

View File

@ -0,0 +1,114 @@
import {ActionManager} from "../actions/action-manager.mjs";
export default {
_prepareContext: async (context, object) => {
const actorData = context.document
context.system = actorData.system
context.flags = actorData.flags
context.derived = context.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
const findEquipmentOnSlot = (slot, setNumber, object) => {
return object.items.get(object.system.heldenausruestung[setNumber]?.[slot])
}
const am = new ActionManager(actorData)
context.actions = am.evaluate()
context.inidice = actorData.system.ini.wuerfel
context.inivalue = actorData.system.ini.aktuell
context.inimod = actorData.system.ini.mod
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
const fernkampf = findEquipmentOnSlot("fernkampf", actorData.system.setEquipped, actorData)
const links = findEquipmentOnSlot("links", actorData.system.setEquipped, actorData)
const rechts = findEquipmentOnSlot("rechts", actorData.system.setEquipped, actorData)
context.attacks = [];
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<${object.system.fk.aktuell + obj.system.at}`,
at: `${object.system.fk.aktuell + obj.system.at}`,
tproll: `${fernkampf.system.rangedAttackDamage}`, // TODO consider adding TP/KK mod and Range mod
tp: `${fernkampf.system.rangedAttackDamage}`,
iniroll: `(${context.inidice})d6 + ${context.inivalue + fernkampf.system.iniModifier ?? 0}`,
ini: `${context.inidice}w6 + ${context.inivalue + fernkampf.system.iniModifier ?? 0}`,
})
})
}
if (links) {
const meitems = []
links.system.meleeSkills.forEach((skillInQuestion) => {
const item = actorData.items.find(p => p.name === skillInQuestion)
if (item) {
meitems.push(item)
}
})
meitems.forEach(skill => {
const obj = skill
context.attacks.push({
name: obj.name,
using: links.name,
atroll: `1d20cs<${object.system.at.links.aktuell + obj.system.at + links.system.attackModifier}`, // TODO consider adding W/M
at: `${object.system.at.links.aktuell + obj.system.at + links.system.attackModifier}`,
paroll: `1d20cs<${object.system.pa.links.aktuell + obj.system.pa + links.system.parryModifier}`, // TODO consider adding W/M
pa: `${object.system.pa.links.aktuell + obj.system.pa + links.system.parryModifier}`,
tproll: `${links.system.meleeAttackDamage}`, // TODO consider adding TP/KK mod
tp: `${links.system.meleeAttackDamage}`,
iniroll: `(${context.inidice})d6 + ${context.inivalue + links.system.iniModifier ?? 0}`,
ini: `${context.inidice}w6 + ${context.inivalue + links.system.iniModifier ?? 0}`,
})
})
}
if (rechts) {
const meitems = []
rechts.system.meleeSkills.forEach((skillInQuestion) => {
const item = actorData.items.find(p => p.name === skillInQuestion)
if (item) {
meitems.push(item)
}
})
meitems.forEach(skill => {
const obj = skill
context.attacks.push({
name: obj.name,
using: rechts.name,
atroll: `1d20cs<${object.system.at.rechts.aktuell + obj.system.at + rechts.system.attackModifier}`, // TODO consider adding W/M
at: `${object.system.at.rechts.aktuell + obj.system.at + rechts.system.attackModifier}`,
paroll: `1d20cs<${object.system.pa.rechts.aktuell + obj.system.pa + rechts.system.parryModifier}`, // TODO consider adding W/M
pa: `${object.system.pa.rechts.aktuell + obj.system.pa + rechts.system.parryModifier}`,
tproll: `${rechts.system.meleeAttackDamage}`, // TODO consider adding TP/KK mod
tp: `${rechts.system.meleeAttackDamage}`,
iniroll: `(${context.inidice})d6 + ${context.inivalue + rechts.system.iniModifier ?? 0}`,
ini: `${context.inidice}w6 + ${context.inivalue + rechts.system.iniModifier ?? 0}`,
})
})
}
return context
},
_onRender: (context, options) => {
},
_getTabConfig: (group) => {
group.tabs.push({id: "combat", group: "sheet", label: "Kampf"})
},
template: `systems/DSA_4-1/templates/actor/character/tab-combat.hbs`
}

View File

@ -0,0 +1,55 @@
export default {
_prepareContext: (context, object) => {
const actorData = context.document
context.spells = []
context.system = actorData.system
context.flags = actorData.flags
context.derived = context.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
context.isGM = game.user.isGM
context.effects = []
Object.values(actorData.items).forEach((item, index) => {
if (item.type === "ActiveEffect") {
const effect = item.effects[0];
const conditions = []
if (effect) {
effect.changes.forEach(change => {
if (change.key.indexOf("wunden") === -1) {
const key = change.key
.replace(/system\./g, "")
.replace(/\.mod/g, "")
.replace(/attribute./g, "")
.replace(/.links/g, "(Links)")
.replace(/.rechts/g, "(Rechts)")
const value = Number(change.value) > 0 ? "+" + change.value : change.value
conditions.push(
`${key}${value}`
)
}
})
}
context.effects.push({
name: item.name,
conditions: conditions.join(" "),
id: item._id,
actor: actorData._id
});
}
})
return context
},
_onRender: (context, options) => {
},
_getTabConfig: (group) => {
group.tabs.push({id: "effects", group: "sheet", label: "Effekte"})
},
template: `systems/DSA_4-1/templates/actor/character/tab-effects.hbs`
}

View File

@ -0,0 +1,264 @@
import {PlayerCharacterDataModel} from "../../data/character.mjs";
export default {
_prepareContext: (context) => {
const actorData = context.document
context.spells = []
context.system = actorData.system
context.flags = actorData.flags
context.derived = context.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
context.equipments = []
context.carryingweight = 0
actorData.itemTypes.Equipment.forEach((item, index) => {
// worn items are halved weight
let effectiveWeight = item.system.weight ?? 0
if (context.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: context.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);
const maxSets = 3
const romanNumerals = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"]
context.sets = []
for (let setIndex = 0; setIndex < maxSets; setIndex++) {
context.sets.push({
tab: "set" + (setIndex + 1),
name: romanNumerals[setIndex],
index: setIndex,
slots: [
{
target: "links",
id: actorData.system.heldenausruestung[setIndex]?.links,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.links)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.links)?.img
},
{
target: "rechts",
id: actorData.system.heldenausruestung[setIndex]?.rechts,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.rechts)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.rechts)?.img
},
{
target: "brust",
id: actorData.system.heldenausruestung[setIndex]?.brust,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.brust)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.brust)?.img
},
{
target: "ruecken",
id: actorData.system.heldenausruestung[setIndex]?.ruecken,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.ruecken)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.ruecken)?.img
},
{
target: "kopf",
id: actorData.system.heldenausruestung[setIndex]?.kopf,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.kopf)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.kopf)?.img
},
{
target: "fernkampf",
id: actorData.system.heldenausruestung[setIndex]?.fernkampf,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.fernkampf)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.fernkampf)?.img
},
{
target: "munition",
id: actorData.system.heldenausruestung[setIndex]?.munition,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.munition)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.munition)?.img
},
{
target: "armlinks",
id: actorData.system.heldenausruestung[setIndex]?.armlinks,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.armlinks)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.armlinks)?.img
},
{
target: "armrechts",
id: actorData.system.heldenausruestung[setIndex]?.armrechts,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.armrechts)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.armrechts)?.img
},
{
target: "bauch",
id: actorData.system.heldenausruestung[setIndex]?.bauch,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.bauch)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.bauch)?.img
},
{
target: "beinlinks",
id: actorData.system.heldenausruestung[setIndex]?.beinlinks,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.beinlinks)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.beinlinks)?.img
},
{
target: "beinrechts",
id: actorData.system.heldenausruestung[setIndex]?.beinrechts,
name: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.beinrechts)?.name,
icon: actorData.items.get(actorData.system.heldenausruestung[setIndex]?.beinrechts)?.img
}
]
})
}
return context
},
_onRender: (context, options, thisObject) => {
new foundry.applications.ux.DragDrop.implementation({
dragSelector: ".inventory-table .equipment",
dropSelector: ".inventory-table",
permissions: {
dragstart: thisObject._canDragStart.bind(thisObject),
drop: thisObject._canDragDrop.bind(thisObject)
},
callbacks: {
dragstart: thisObject._onDragStart.bind(thisObject),
drop: thisObject._onDrop.bind(thisObject),
dragover: thisObject._onDragOver.bind(thisObject)
}
}).bind(thisObject.element);
new ContextMenu(
thisObject.element,
".equipment",
[
{
name: "Abrüsten",
icon: '<i class="fa-solid fa-suitcase"></i>',
callback: (target) => {
const {itemId} = target.dataset
const itemSlot = thisObject.document.isWorn(itemId)
const updateObject = thisObject.document.getEquipmentSetUpdateObject()
delete updateObject[`system.heldenausruestung.${thisObject.document.system.setEquipped}.${itemSlot}`]
thisObject.document.update(updateObject)
},
condition: (target) => {
const {itemId} = target.dataset
const itemIsWorn = thisObject.document.isWorn(itemId)
return itemIsWorn
}
},
{
name: "Ausrüsten (Munition)",
callback: (target) => {
const updateObject = thisObject.document.getEquipmentSetUpdateObject()
updateObject[`system.heldenausruestung.${thisObject.document.system.setEquipped}.munition`] = target.dataset.itemId
thisObject.document.update(updateObject)
},
condition: (target) => {
const {itemId} = target.dataset
const item = thisObject.document.items.get(itemId)
console.log(item.system.category)
return !thisObject.document.isWorn(itemId) && item.system.category.indexOf("Munition") != -1
}
},
{
name: "Ausrüsten",
callback: (target) => {
const updateObject = thisObject.document.getEquipmentSetUpdateObject()
// find next unoccupied slot and enter the item
console.log(updateObject)
const nextUnoccupiedSlot = Object.entries(updateObject).find(([key, value]) => {
// but not when it is a weapon slot
console.log(key, value === null
&& key.indexOf(".links") === -1
&& key.indexOf(".rechts") === -1
&& key.indexOf(".fernkampf") === -1
&& key.indexOf(".munition") === -1)
return value === null
&& key.indexOf(".links") === -1
&& key.indexOf(".rechts") === -1
&& key.indexOf(".fernkampf") === -1
&& key.indexOf(".munition") === -1
})
updateObject[nextUnoccupiedSlot[0]] = target.dataset.itemId
thisObject.document.update(updateObject)
},
condition: (target) => {
const {itemId} = target.dataset
const item = thisObject.document.items.get(itemId)
return !thisObject.document.isWorn(itemId) && item.system.category.indexOf("Rüstung") != -1
}
},
{
name: "Ausrüsten (Rechte Hand)",
callback: (target) => {
const updateObject = thisObject.document.getEquipmentSetUpdateObject()
updateObject[`system.heldenausruestung.${thisObject.document.system.setEquipped}.rechts`] = target.dataset.itemId
thisObject.document.update(updateObject)
},
condition: (target) => {
const {itemId} = target.dataset
const item = thisObject.document.items.get(itemId)
return !thisObject.document.isWorn(itemId) && item.system.category.indexOf("Nahkampfwaffe") != -1
}
},
{
name: "Ausrüsten (Linke Hand)",
callback: (target) => {
const updateObject = thisObject.document.getEquipmentSetUpdateObject()
updateObject[`system.heldenausruestung.${thisObject.document.system.setEquipped}.links`] = target.dataset.itemId
thisObject.document.update(updateObject)
},
condition: (target) => {
const {itemId} = target.dataset
const item = thisObject.document.items.get(itemId)
return !thisObject.document.isWorn(itemId) && item.system.category.indexOf("Nahkampfwaffe") != -1
}
},
{
name: "Ausrüsten (Fernkampf)",
callback: (target) => {
const updateObject = thisObject.document.getEquipmentSetUpdateObject()
updateObject[`system.heldenausruestung.${thisObject.document.system.setEquipped}.fernkampf`] = target.dataset.itemId
thisObject.document.update(updateObject)
},
condition: (target) => {
const {itemId} = target.dataset
const item = thisObject.document.items.get(itemId)
return !thisObject.document.isWorn(itemId) && item.system.category.indexOf("Fernkampfwaffe") != -1
}
},
{
name: "Aus dem Inventar entfernen",
icon: '<i class="fa-solid fa-trash"></i>',
callback: (target) => {
thisObject.document.deleteEmbeddedDocuments('Item', [target.dataset.itemId])
},
condition: (target) => {
const {itemId} = target.dataset
return !thisObject.document.isWorn(itemId)
}
}
], {jQuery: false});
},
_getTabConfig: (group) => {
group.tabs.push({id: "equipment", group: "sheet", label: "Ausrüstung"})
},
template: `systems/DSA_4-1/templates/actor/character/tab-equipment.hbs`
}

View File

@ -0,0 +1,110 @@
import {LiturgyData} from "../../data/miracle/liturgydata.mjs";
export default {
_prepareContext: (context) => {
const actorData = context.document
context.system = actorData.system
context.flags = actorData.flags
context.derived = context.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
context.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,
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) => {
},
_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"})
}
},
template: `systems/DSA_4-1/templates/actor/character/tab-liturgies.hbs`
}

View File

@ -0,0 +1,20 @@
export default {
_prepareContext: (context, object) => {
const actorData = context.document
context.system = actorData.system
context.flags = actorData.flags
context.derived = context.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
return context
},
_onRender: (context, options) => {
},
_getTabConfig: (group) => {
group.tabs.push({id: "meta", group: "sheet", label: "Meta"})
},
template: `systems/DSA_4-1/templates/actor/character/tab-meta.hbs`
}

View File

@ -0,0 +1,82 @@
export default {
_prepareContext: (context) => {
const actorData = context.document
context.spells = []
context.system = actorData.system
context.flags = actorData.flags
context.derived = context.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
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
};
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) => {
},
_getTabConfig: (group) => {
group.tabs.push({id: "skills", group: "sheet", label: "Talente"})
},
template: `systems/DSA_4-1/templates/actor/character/tab-skills.hbs`
}

View File

@ -0,0 +1,20 @@
export default {
_prepareContext: (context, object) => {
const actorData = context.document
context.system = actorData.system
context.flags = actorData.flags
context.derived = context.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
return context
},
_onRender: (context, options) => {
},
_getTabConfig: (group) => {
group.tabs.push({id: "social", group: "sheet", label: "Soziales"})
},
template: `systems/DSA_4-1/templates/actor/character/tab-social.hbs`
}

View File

@ -0,0 +1,55 @@
export default {
_prepareContext: (context) => {
const actorData = context.document
context.spells = []
context.system = actorData.system
context.flags = actorData.flags
context.derived = context.document.system
context.originalName = actorData.name
context.name = context.derived.name ?? actorData.name
context.effects = actorData.effects ?? []
const cleanUpMerkmal = (merkmale) => {
return merkmale.split(",").map((merkmal) => merkmal.trim())
}
Object.values(actorData.items).forEach((item, index) => {
if (item.type === "Spell") {
const eigenschaften = item.system.probe;
const werte = [
{name: eigenschaften[0], value: this.prepareEigenschaftRoll(actorData, eigenschaften[0])},
{name: eigenschaften[1], value: this.prepareEigenschaftRoll(actorData, eigenschaften[1])},
{name: eigenschaften[2], value: this.prepareEigenschaftRoll(actorData, eigenschaften[2])}
]
context.spells.push({
id: item._id,
name: item.name,
zfw: item.system.zfw,
hauszauber: item.system.hauszauber,
merkmal: cleanUpMerkmal(item.system.merkmal),
rollEigenschaft1: werte[0].value,
rollEigenschaft2: werte[1].value,
rollEigenschaft3: werte[2].value,
eigenschaft1: werte[0].name,
eigenschaft2: werte[1].name,
eigenschaft3: werte[2].name,
})
}
})
context.hasSpells = context.spells.length > 0
return context
},
_onRender: (context, options) => {
},
_getTabConfig: (group, thisObject) => {
const hasSpells = thisObject.document.items.filter(p => p.type === "Spell").length > 0 ?? false
if (hasSpells) {
group.tabs.push({id: "spells", group: "sheet", label: "Zauber"})
}
},
template: `systems/DSA_4-1/templates/actor/character/tab-spells.hbs`
}

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,114 @@
export class CreatureSheet extends foundry.appv1.sheets.ActorSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'actor', 'creature'],
width: 520,
height: 480,
const {HandlebarsApplicationMixin} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'actor', 'creature'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: CreatureSheet.#onSubmitForm
},
actions: {
removeAttack: CreatureSheet.#removeAttack,
addAttack: CreatureSheet.#addAttack,
roll: CreatureSheet.#dieRoll,
editImage: ActorSheetV2.DEFAULT_OPTIONS.actions.editImage,
}
}
static TABS = {
sheet: {
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'meta',
},
{id: 'meta', group: 'sheet', label: 'Meta'},
{id: 'attacks', group: 'sheet', label: 'Attacken'},
{id: 'description', group: 'sheet', label: 'Beschreibung'},
],
initial: 'meta'
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/creature/main-sheet.hbs`
},
meta: {
template: `systems/DSA_4-1/templates/actor/creature/tab-meta.hbs`
},
attacks: {
template: `systems/DSA_4-1/templates/actor/creature/tab-attacks.hbs`
},
description: {
template: `systems/DSA_4-1/templates/actor/creature/tab-description.hbs`
}
}
/**
* Handle form submission
* @this {AdvantageSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object) // Note: formData.object
}
static async #removeAttack(evt) {
const {index} = evt.srcElement.dataset;
let sans = Array.from(this.document.system.attacks);
sans.splice(index, 1);
await this.document.update({'system.attacks': sans})
}
static async #dieRoll(evt) {
const {rollType, rollName, roll} = evt.srcElement.dataset;
let r = new Roll(roll, this.document.getRollData());
const label = `${rollType} (${rollName})`
await r.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.document}),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
});
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/actor/actor-creature-sheet.hbs`;
static async #addAttack() {
const name = this.element.querySelector('#attack_name').value
const at = this.element.querySelector('#attack_at').value
const pa = this.element.querySelector('#attack_pa').value
const tp = this.element.querySelector('#attack_tp').value
const newAttack = {
name,
at,
pa,
tp
}
await this.document.update({'system.attacks': [...this.document.system.attacks, newAttack]})
this.element.querySelector('#attack_name').value = ""
this.element.querySelector('#attack_at').value = ""
this.element.querySelector('#attack_pa').value = ""
this.element.querySelector('#attack_tp').value = ""
}
/** @override */
getData() {
async _prepareContext(options) {
const context = super.getData();
const actorData = context.data;
const context = await super._prepareContext(options);
const actorData = context.document;
context.attacks = [];
context.actor = actorData;
actorData.system.attacks.forEach((attack, index) => {
context.attacks.push({
@ -43,55 +125,7 @@ export class CreatureSheet extends foundry.appv1.sheets.ActorSheet {
})
})
return context;
}
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
html.on('click', '.remove-attack', async (evt) => {
const {index} = evt.target.dataset;
let sans = Array.from(this.object.system.attacks);
sans.splice(index, 1);
await this.object.update({'system.attacks': sans})
})
html.on('click', '.attacks-die.die', async (evt) => {
const {rollType, rollName, roll} = evt.currentTarget.dataset;
let r = new Roll(roll, this.actor.getRollData());
const label = `${rollType} (${rollName})`
await r.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.object}),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
});
})
html.on('click', '.editor .add-attack', async (evt) => {
const name = html[0].querySelector('#attack_name').value
const at = html[0].querySelector('#attack_at').value
const pa = html[0].querySelector('#attack_pa').value
const tp = html[0].querySelector('#attack_tp').value
const newAttack = {
name,
at,
pa,
tp
}
await this.object.update({'system.attacks': [...this.object.system.attacks, newAttack]})
evt.target.parentElement.querySelector('#attack_name').value = ""
evt.target.parentElement.querySelector('#attack_at').value = ""
evt.target.parentElement.querySelector('#attack_pa').value = ""
evt.target.parentElement.querySelector('#attack_tp').value = ""
})
}
}

View File

@ -1,42 +1,139 @@
export class AusruestungSheet extends foundry.appv1.sheets.ItemSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'item', 'equipment'],
width: 520,
height: 480,
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) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 640, height: 480},
classes: ['dsa41', 'sheet', 'item', 'equipment'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: EquipmentSheet.#onSubmitForm
},
window: {
resizable: true,
},
actions: {
editImage:
DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage
}
}
static TABS = {
sheet: {
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'description',
},
{id: 'meta', group: 'sheet', label: 'Meta'},
// Additional Tabs are added based on the nature of this item
],
});
initial: 'meta'
}
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/item/item-equipment-sheet.hbs`;
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/item/equipment/main-sheet.hbs`
},
meta: {
template: `systems/DSA_4-1/templates/item/equipment/tab-meta.hbs`
},
melee: {
template: `systems/DSA_4-1/templates/item/equipment/tab-melee.hbs`
},
ranged: {
template: `systems/DSA_4-1/templates/item/equipment/tab-ranged.hbs`
},
ammunition: {
template: `systems/DSA_4-1/templates/item/equipment/tab-ammunition.hbs`
},
armor: {
template: `systems/DSA_4-1/templates/item/equipment/tab-armor.hbs`
},
settings: {
template: `systems/DSA_4-1/templates/item/equipment/tab-settings.hbs`
},
}
/** @override */
getData() {
// Retrieve the data structure from the base sheet. You can inspect or log
// the context variable to see the structure, but some key properties for
// sheets are the actor object, the data object, whether or not it's
// editable, the items array, and the effects array.
const context = super.getData();
/**
* Handle form submission
* @this {EquipmentSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
// Use a safe clone of the actor data for further operations.
const equipmentData = context.data;
let normalisedFormData = {}
// Add the actor's data to context.data for easier access, as well as flags.
context.system = equipmentData.system;
context.flags = equipmentData.flags;
Object.entries(formData.object).forEach(([key, value]) => {
if (Array.isArray(value)) {
normalisedFormData[key] = value[0]
} else {
normalisedFormData[key] = value
}
})
context.quantity = context.system.quantity;
context.description = context.system.description;
// manage categories into array
normalisedFormData['system.category'] = []
if (normalisedFormData.isMelee) normalisedFormData['system.category'].push("Nahkampfwaffe")
delete normalisedFormData.isMelee
if (normalisedFormData.isRanged) normalisedFormData['system.category'].push("Fernkampfwaffe")
delete normalisedFormData.isRanged
if (normalisedFormData.isAmmunition) normalisedFormData['system.category'].push("Munition")
delete normalisedFormData.isAmmunition
if (normalisedFormData.isArmor) normalisedFormData['system.category'].push("Rüstung")
delete normalisedFormData.isArmor
await this.document.update(normalisedFormData) // Note: formData.object
}
async _preparePartContext(partId, context) {
switch (partId) {
case 'meta':
this.#prepareMetaContext(context)
break;
case 'melee':
this.#prepareMeleeContext(context)
break;
case 'ranged':
this.#prepareRangedContext(context)
break;
case 'armor':
this.#prepareArmorContext(context)
break;
case 'settings':
this.#prepareSettingsContext(context)
break;
}
context.tab = context.tabs[partId]
return context
}
#prepareMetaContext(context) {
const equipmentData = context.document.system
context.system = equipmentData
context.quantity = equipmentData.quantity
context.description = equipmentData.description
context.name = context.document.name
context.img = context.document.img
context.categoryAndOptions = {
options: {
@ -44,25 +141,23 @@ export class AusruestungSheet extends foundry.appv1.sheets.ItemSheet {
Nahkampfwaffe: "Nahkampfwaffe",
Fernkampfwaffe: "Fernkampfwaffe",
Behälter: "Behälter",
Rüstung: "Rüstung",
},
entries: context.system.category,
entries: equipmentData.category,
targetField: "category"
};
context.isMeleeWeapon = context.system.category.includes("Nahkampfwaffe");
context.isRangedWeapon = context.system.category.includes("Fernkampfwaffe");
context.isContainer = context.system.category.includes("Behälter");
context.isArmor = context.system.category.includes("Rüstung");
context.price = context.system.price;
context.weight = context.system.weight;
}
#prepareMeleeContext(context) {
const equipmentData = context.document.system
context.system = equipmentData
context.meleeSkillsAndOptions = {
options: {
"": "",
Dolche: "Dolche",
Fechtwaffen: "Fechtwaffen",
Säbel: "Säbel",
"Säbel": "Säbel",
Schwerter: "Schwerter",
Anderthalbhänder: "Anderthalbhänder",
"Anderthalbhänder": "Anderthalbhänder",
"Zweihandschwerter/-säbel": "Zweihandschwerter/-säbel",
"Infanteriewaffen": "Infanteriewaffen",
"Speere": "Speere",
@ -72,9 +167,13 @@ export class AusruestungSheet extends foundry.appv1.sheets.ItemSheet {
"Kettenwaffen": "Kettenwaffen",
"Raufen": "Raufen"
},
entries: context.system.meleeSkills,
entries: equipmentData.meleeSkills,
targetField: "meleeSkills"
}
}
#prepareRangedContext(context) {
const equipmentData = context.document.system
context.rangedSkillsAndOptions = {
options: {
"": "",
@ -84,24 +183,117 @@ export class AusruestungSheet extends foundry.appv1.sheets.ItemSheet {
"Armbrust": "Armbrust",
"Bogen": "Bogen",
},
entries: context.system.rangedSkills,
entries: equipmentData.rangedSkills,
targetField: "rangedSkills"
}
return context;
}
activateListeners(html) {
super.activateListeners(html);
#prepareAmmunitionContext(context) {
html.on('change', '.array-editor select', (evt) => {
const addingValue = evt.currentTarget.value;
const fieldToTarget = evt.currentTarget.dataset.targetField;
const newSkills = [...this.object.system[fieldToTarget], addingValue];
}
this.object.update({system: {[fieldToTarget]: newSkills}});
evt.currentTarget.value = "";
#prepareArmorContext(context) {
}
#prepareSettingsContext(context) {
context.isMelee = this.document.system.category.includes("Nahkampfwaffe")
context.isRanged = this.document.system.category.includes("Fernkampfwaffe")
context.isAmmunition = this.document.system.category.includes("Munition")
context.isArmor = this.document.system.category.includes("Rüstung")
}
/**
* Adds Tabs based on the items nature
*
* @param {String} group
* @private
*/
_getTabsConfig(group) {
const tabs = foundry.utils.deepClone(super._getTabsConfig(group))
const category = this.document.system.category
/**
*
* @type {[{ApplicationTab}]}
*/
if (category.includes("Nahkampfwaffe")) {
tabs.tabs.push({
id: 'melee', group: group, label: 'Nahkampfwaffe'
})
}
if (category.includes("Fernkampfwaffe")) {
tabs.tabs.push({
id: 'ranged', group: group, label: 'Fernkampfwaffe'
})
}
if (category.includes("Rüstung")) {
tabs.tabs.push({
id: 'armor', group: group, label: 'Rüstung'
})
}
tabs.tabs.push({
id: 'settings', group: group, label: 'Einstellungen'
})
return tabs
}
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options)
context.price = context.document.system.price
context.weight = context.document.system.weight
context.inventoryItems = []
context.containerVolume = context.document.system.containerVolume
return context
}
/**
* Actions performed after any render of the Application.
* Post-render steps are not awaited by the render process.
* @param {ApplicationRenderContext} context Prepared context data
* @param {RenderOptions} options Provided render options
* @protected
*/
_onRender(context, options) {
new foundry.applications.ux.DragDrop.implementation({
dropSelector: ".inventory-table",
permissions: {
drop: this._canDragDrop.bind(this)
},
callbacks: {
drop: this._onDrop.bind(this)
}
}).bind(this.element);
}
_canDragDrop(event) {
console.log(event)
return true
}
async _onDrop(event) {
const data = TextEditor.implementation.getDragEventData(event);
// Dropped Documents
const documentClass = foundry.utils.getDocumentClass(data.type);
if (documentClass) {
const document = await documentClass.fromDropData(data)
console.log(document, document.parent)
// Dropped Documents
document.update({"parent": this.document})
}
}
}

View File

@ -1,85 +1,143 @@
export class GroupSheet extends foundry.appv1.sheets.ActorSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'actor', 'group'],
width: 520,
height: 480,
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'description',
},
],
});
const {HandlebarsApplicationMixin} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
const {ContextMenu} = foundry.applications.ux
export class GroupSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'actor', 'group'],
tag: 'form',
dragDrop: [{
dropSelector: '.tab.inventory.active'
}],
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: GroupSheet.#onSubmitForm
},
window: {
resizable: true,
},
actions: {
roll: GroupSheet.#dieRoll,
editImage: ActorSheetV2.DEFAULT_OPTIONS.actions.editImage,
openEmbeddedDocument: GroupSheet.#openEmbeddedDocument,
openActorDocument: GroupSheet.#openActorDocument,
}
}
static async onDroppedData(group, sheet, data) {
if (data.type === "Actor") {
const uuid = await foundry.utils.parseUuid(data.uuid);
const character = await (game.actors.get(uuid.id))
// check if character already is part of the group
if (group.system.characters.includes(character._id)) {
ui.notifications.warn(`${character.name} befindet sich bereits in der Heldengruppe ${group.name}`)
return false;
}
// update group
let settings = {...group.system.settings}
character.items.filter((i) => i.type === "Advantage").forEach((advantage) => {
if (!settings[sheet.#stringToKeyFieldName(advantage.name)]) {
settings[sheet.#stringToKeyFieldName(advantage.name)] = false
}
})
character.items.filter((i) => i.type === "Skill").forEach((skill) => {
if (!settings[sheet.#stringToKeyFieldName(skill.name)]) {
settings[sheet.#stringToKeyFieldName(skill.name)] = false
}
})
await group.update({
system: {
characters: [
...group.system.characters,
character._id
],
settings: settings
}
})
ui.notifications.info(`${character.name} ist der Heldengruppe ${group.name} beigetreten`)
static TABS = {
sheet: {
tabs: [
{id: 'members', group: 'sheet', label: 'Gruppenmitglieder'},
{id: 'inventory', group: 'sheet', label: 'Gruppeninventar'},
],
initial: 'members'
}
if (data.type === "Equipment") {
const uuid = await foundry.utils.parseUuid(data.uuid);
const equipment = await (game.actors.get(uuid.id))
ui.notifications.info(`${equipment.name} befindet sich nun im Inventar der Heldengruppe ${group.name}`)
return true;
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/group/main-sheet.hbs`
},
members: {
template: `systems/DSA_4-1/templates/actor/group/tab-members.hbs`
},
inventory: {
template: `systems/DSA_4-1/templates/actor/group/tab-inventory.hbs`
},
settings: {
template: `systems/DSA_4-1/templates/actor/group/tab-settings.hbs`
}
}
constructor(options = {}) {
super(options);
}
/**
* Handle form submission
* @this {AdvantageSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
if (formData.object.name) {
await (game.folders.get(this.document.system.groupId)).update({name: formData.object.name})
}
await this.document.update(formData.object) // Note: formData.object
}
#stringToKeyFieldName(s) {
return s
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/actor/group-sheet.hbs`;
static #openEmbeddedDocument(event) {
const dataset = event.target.parentElement.dataset
const id = dataset.itemId ?? dataset.id
this.document.items.get(id).sheet.render(true)
}
static async #openActorDocument(event) {
const dataset = event.target.parentElement.dataset
const id = dataset.itemId ?? dataset.id
game.actors.get(id).sheet.render(true)
}
static async #dieRoll(evt) {
console.log(evt)
}
async #onUpdateCharacterSettings(data) {
if (data.type === "character") {
// update group
let settings = {...this.document.system.settings}
data.items.filter((i) => i.type === "Advantage").forEach((advantage) => {
if (!settings[this.#stringToKeyFieldName(advantage.name)]) {
settings[this.#stringToKeyFieldName(advantage.name)] = false
}
})
data.items.filter((i) => i.type === "Skill").forEach((skill) => {
if (!settings[this.#stringToKeyFieldName(skill.name)]) {
settings[this.#stringToKeyFieldName(skill.name)] = false
}
})
await this.document.update({"system.settings": settings})
}
}
_getTabsConfig(group) {
const tabs = foundry.utils.deepClone(super._getTabsConfig(group))
// Modify tabs based on document properties
if (game.user.isGM) {
tabs.tabs.push({id: "settings", group: "sheet", label: "Einstellungen"})
}
return tabs
}
/** @override */
async getData() {
const context = super.getData();
// Use a safe clone of the actor data for further operations.
const groupData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = groupData.system;
context.flags = groupData.flags;
async _prepareContext(options) {
const context = await super._prepareContext(options)
const groupData = context.document
context.system = groupData.system
context.flags = groupData.flags
context.characters = []
context.isGM = game.user.isGM;
context.isGM = game.user.isGM
context.name = groupData.name
context.img = groupData.img
context.fields = [];
@ -101,8 +159,11 @@ export class GroupSheet extends foundry.appv1.sheets.ActorSheet {
}
}
for (const characterId of groupData.system.characters) {
const character = await game.actors.get(characterId)
// TODO hook on changes in the given folder
const characters = await game.folders.get(groupData.system.groupId).contents
console.log(characters)
for (const character of characters) {
character.items.filter((i) => i.type === "Advantage").filter((i) => hiddenFields.includes(this.#stringToKeyFieldName(i.name))).map((advantage) => {
const n = this.#stringToKeyFieldName(advantage.name)
@ -117,15 +178,8 @@ export class GroupSheet extends foundry.appv1.sheets.ActorSheet {
if (!context.fields[n]) {
context.fields[n] = {}
}
const eigenschaften = Object.values(skill.system.probe);
context.fields[n][character.name] = {
taw: skill.system.taw,
eigenschaft1: eigenschaften[0],
eigenschaft2: eigenschaften[1],
eigenschaft3: eigenschaften[2],
rollEigenschaft1: character.system.attribute[eigenschaften[0].toLowerCase()].aktuell,
rollEigenschaft2: character.system.attribute[eigenschaften[1].toLowerCase()].aktuell,
rollEigenschaft3: character.system.attribute[eigenschaften[2].toLowerCase()].aktuell,
taw: skill.system.taw ?? "0",
name: skill.name,
actor: character._id,
}
@ -153,11 +207,11 @@ export class GroupSheet extends foundry.appv1.sheets.ActorSheet {
}
}
context.equipments = [];
const actorData = context.data;
Object.values(actorData.items).forEach((item, index) => {
context.inventoryItems = [];
const actorData = context.document;
actorData.items.forEach((item, index) => {
if (item.type === "Equipment") {
context.equipments.push({
context.inventoryItems.push({
index: index,
id: item._id,
quantity: item.system.quantity,
@ -173,110 +227,8 @@ export class GroupSheet extends foundry.appv1.sheets.ActorSheet {
return await context;
}
openEmbeddedDocument(documentId) {
this.object.items.get(documentId).sheet.render(true)
}
_evaluateRoll(rolledDice, {
taw,
lowerThreshold = 1,
upperThreshold = 20,
countToMeisterlich = 3,
countToPatzer = 3,
werte = []
}) {
let tap = taw;
let meisterlichCounter = 0;
let patzerCounter = 0;
let failCounter = 0;
rolledDice.forEach((rolledDie, index) => {
if (tap < 0 && rolledDie.result > werte[index]) {
tap -= rolledDie.result - werte[index];
if (tap < 0) { // konnte nicht vollständig ausgeglichen werden
failCounter++;
}
} else if (rolledDie.result > werte[index]) { // taw ist bereits aufgebraucht und wert kann nicht ausgeglichen werden
tap -= rolledDie.result - werte[index];
failCounter++;
}
if (rolledDie.result <= lowerThreshold) meisterlichCounter++;
if (rolledDie.result > upperThreshold) patzerCounter++;
})
return {
tap,
meisterlich: meisterlichCounter === countToMeisterlich,
patzer: patzerCounter === countToPatzer,
}
}
async _onTalentRoll(event) {
event.preventDefault();
const dataset = event.currentTarget.dataset;
const actor = await game.actors.get(dataset.actorId);
console.log(dataset, actor)
if (dataset.rolleigenschaft1) {
let roll1 = new Roll("3d20", actor.getRollData());
let evaluated1 = (await roll1.evaluate())
const dsaDieRollEvaluated = this._evaluateRoll(evaluated1.terms[0].results, {
taw: dataset.taw,
werte: [dataset.rolleigenschaft1, dataset.rolleigenschaft2, dataset.rolleigenschaft3],
})
if (dsaDieRollEvaluated.tap >= 0) { // erfolg
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: actor}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Meisterlich geschafft' : 'Geschafft'} mit ${dsaDieRollEvaluated.tap} Punkten übrig`,
}, {rollMode: "gmroll"})
} else { // misserfolg
evaluated1.toMessage({
speaker: ChatMessage.getSpeaker({actor: actor}),
flavor: ` ${dsaDieRollEvaluated.meisterlich ? 'Gepatzt' : ''} mit ${Math.abs(dsaDieRollEvaluated.tap)} Punkten daneben`,
}, {rollMode: "gmroll"})
}
}
}
activateListeners(html) {
super.activateListeners(html);
html.on('click', '.owneroption.clickable', async (evt) => {
const dataset = evt.target.dataset;
const group = this.object;
switch (dataset.operation) {
case "removeFromParty":
const charactersWithoutMember = group.system.characters.filter(id => id !== dataset.id)
await group.update({
system: {
characters: charactersWithoutMember
}
})
}
});
html.on('click', '.header.clickable', async (evt) => {
const {id, operation} = evt.currentTarget.dataset;
if (operation === "openActorSheet") {
evt.stopPropagation();
(await game.actors.get(id)).sheet.render(true);
}
})
html.on('click', '.equipment', (evt) => {
this.openEmbeddedDocument(evt.target.dataset.id);
evt.stopPropagation();
})
html.on('click', ".rollable", (evt) => {
this._onTalentRoll(evt)
evt.stopPropagation()
})
new ContextMenu(html, '.equipment', [
_onRender(context, options) {
/*ContextMenu.implementation.create(this, this.element, ".equipment", [
{
name: "Aus dem Gruppeninventar entfernen",
icon: '<i class="fa-solid fa-trash"></i>',
@ -285,6 +237,84 @@ export class GroupSheet extends foundry.appv1.sheets.ActorSheet {
},
condition: () => true
}
]);
], {
jQuery: false
});*/
// Drag-drop
new foundry.applications.ux.DragDrop.implementation({
dragSelector: ".inventory-table .equipment",
dropSelector: ".inventory-table",
permissions: {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this)
},
callbacks: {
dragstart: this._onDragStart.bind(this),
drop: this._onDrop.bind(this)
}
}).bind(this.element);
// 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) {
this.#onUpdateCharacterSettings(data)
this.render()
} else if (data.type === "character") {
this.render()
}
}
});
}
/**
* 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;
if ("link" in event.target.dataset) return;
let dragData;
// Owned Items
if (target.dataset.itemId) {
const item = this.actor.items.get(target.dataset.itemId);
dragData = item.toDragData();
}
// Active Effect
if (target.dataset.effectId) {
const effect = this.actor.effects.get(target.dataset.effectId);
dragData = effect.toDragData();
}
// Set data transfer
if (!dragData) return;
event.dataTransfer.setData("text/plain", JSON.stringify(dragData));
}
// TODO needs to be fixed once Character Sheet is migrated to ActorSheetV2
async _onDrop(event) {
const data = TextEditor.implementation.getDragEventData(event);
const actor = this.actor;
const allowed = Hooks.call("dropActorSheetData", actor, this, data);
if (allowed === false) return;
// Dropped Documents
const documentClass = foundry.utils.getDocumentClass(data.type);
if (documentClass) {
const document = await documentClass.fromDropData(data);
await this._onDropDocument(event, document);
// No duplication by moving items from one actor to another
if (document.parent) {
document.parent.items.get(document._id).delete()
}
}
}
}

View File

@ -1,50 +1,62 @@
export class LiturgySheet extends foundry.appv1.sheets.ItemSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'item', 'liturgy'],
width: 520,
height: 480,
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
export class LiturgySheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'item', 'liturgy'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: LiturgySheet.#onSubmitForm
},
}
static TABS = {
sheet: {
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'description',
},
{id: 'json', group: 'sheet', label: 'JSON'},
],
});
initial: 'json'
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/item/liturgy/main-sheet.hbs`
},
json: {
template: `systems/DSA_4-1/templates/item/liturgy/tab-json.hbs`
},
}
/**
* Handle form submission
* @this {EquipmentSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object) // Note: formData.object
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/item/item-liturgy-sheet.hbs`;
}
async _prepareContext(options) {
const context = await super._prepareContext(options);
/** @override */
getData() {
// Retrieve the data structure from the base sheet. You can inspect or log
// the context variable to see the structure, but some key properties for
// sheets are the actor object, the data object, whether or not it's
// editable, the items array, and the effects array.
const context = super.getData();
const liturgyData = context.document;
// Use a safe clone of the actor data for further operations.
const liturgyData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = liturgyData.system;
context.flags = liturgyData.flags;
context.json = JSON.stringify(liturgyData);
return context;
}
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (this.isEditable) {
}
}
}

View File

@ -1,37 +1,61 @@
export class SkillSheet extends foundry.appv1.sheets.ItemSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'item', 'skill'],
width: 520,
height: 480,
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
export class SkillSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'item', 'skill'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: SkillSheet.#onSubmitForm
}
}
static TABS = {
sheet: {
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'meta',
},
{id: 'meta', group: 'sheet', label: 'Meta'},
{id: 'description', group: 'sheet', label: 'Beschreibung'},
],
});
initial: 'meta'
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/item/skill/main-sheet.hbs`
},
meta: {
template: `systems/DSA_4-1/templates/item/skill/tab-meta.hbs`
},
description: {
template: `systems/DSA_4-1/templates/item/skill/tab-description.hbs`
}
}
/**
* Handle form submission
* @this {SkillSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object) // Note: formData.object
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/item/item-skill-sheet.hbs`;
}
async _prepareContext(options) {
/** @override */
getData() {
// Retrieve the data structure from the base sheet. You can inspect or log
// the context variable to see the structure, but some key properties for
// sheets are the actor object, the data object, whether or not it's
// editable, the items array, and the effects array.
const context = super.getData();
const context = await super._prepareContext(options)
const skillData = context.document;
// Use a safe clone of the actor data for further operations.
const skillData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = skillData.system;
context.flags = skillData.flags;
context.categoryOptions = {
@ -45,21 +69,11 @@ export class SkillSheet extends foundry.appv1.sheets.ItemSheet {
Handwerk: "Handwerk"
}
context.isCombat = context.system.gruppe === "Kampf"
context.isTalent = context.system.gruppe !== "Kampf"
context.isLanguage = context.system.gruppe === "Sprachen" || context.system.gruppe === "Schriften"
context.hasRequirement = context.system.voraussetzung.talent != null
context.isCombat = skillData.system.gruppe === "Kampf"
context.isTalent = skillData.system.gruppe !== "Kampf"
context.isLanguage = skillData.system.gruppe === "Sprachen" || skillData.system.gruppe === "Schriften"
context.hasRequirement = skillData.system.voraussetzung?.talent != null ?? false
return context;
}
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) {
}
}
}

View File

@ -1,51 +1,62 @@
export class SpecialAbilitySheet extends foundry.appv1.sheets.ItemSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'item', 'specialability'],
width: 520,
height: 480,
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
export class SpecialAbilitySheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'item', 'specialability'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: SpecialAbilitySheet.#onSubmitForm
},
}
static TABS = {
sheet: {
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'description',
},
{id: 'json', group: 'sheet', label: 'JSON'},
],
});
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/item/item-special-ability-sheet.hbs`;
}
/** @override */
getData() {
// Retrieve the data structure from the base sheet. You can inspect or log
// the context variable to see the structure, but some key properties for
// sheets are the actor object, the data object, whether or not it's
// editable, the items array, and the effects array.
const context = super.getData();
// Use a safe clone of the actor data for further operations.
const advantageData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = advantageData.system;
context.flags = advantageData.flags;
context.json = JSON.stringify(advantageData.system, null, 4);
return context;
}
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) {
initial: 'json'
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/item/specialability/main-sheet.hbs`
},
json: {
template: `systems/DSA_4-1/templates/item/specialability/tab-json.hbs`
},
}
/**
* Handle form submission
* @this {EquipmentSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object) // Note: formData.object
}
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options);
const specialabilityData = context.document;
context.system = specialabilityData.system;
context.flags = specialabilityData.flags;
context.json = JSON.stringify(specialabilityData);
return context;
}
}

View File

@ -1,49 +1,69 @@
export class SpellSheet extends foundry.appv1.sheets.ItemSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'item', 'spell'],
width: 520,
height: 480,
const {DocumentSheetV2, HandlebarsApplicationMixin} = foundry.applications.api
export class SpellSheet extends HandlebarsApplicationMixin(DocumentSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'item', 'spell'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: SpellSheet.#onSubmitForm
}
}
static TABS = {
sheet: {
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'meta',
},
{id: 'meta', group: 'sheet', label: 'Meta'},
{id: 'variants', group: 'sheet', label: 'Varianten'},
{id: 'commonality', group: 'sheet', label: 'Verbreitung'},
],
});
initial: 'meta'
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/item/spell/main-sheet.hbs`
},
meta: {
template: `systems/DSA_4-1/templates/item/spell/tab-meta.hbs`
},
variants: {
template: `systems/DSA_4-1/templates/item/spell/tab-variants.hbs`
},
commonality: {
template: `systems/DSA_4-1/templates/item/spell/tab-commonality.hbs`
}
}
/**
* Handle form submission
* @this {SpellSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object) // Note: formData.object
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/item/item-spell-sheet.hbs`;
}
async _prepareContext(options) {
const context = await super._prepareContext(options);
const spellData = context.document;
/** @override */
getData() {
// Retrieve the data structure from the base sheet. You can inspect or log
// the context variable to see the structure, but some key properties for
// sheets are the actor object, the data object, whether or not it's
// editable, the items array, and the effects array.
const context = super.getData();
// Use a safe clone of the actor data for further operations.
const skillData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = skillData.system;
context.flags = skillData.flags;
context.system = spellData.system;
context.flags = spellData.flags;
return context;
}
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
}
}

View File

@ -1,56 +0,0 @@
export class VornachteilSheet extends foundry.appv1.sheets.ItemSheet {
/**@override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['dsa41', 'sheet', 'item', 'advantage'],
width: 520,
height: 480,
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'description',
},
],
});
}
/** @override */
get template() {
return `systems/DSA_4-1/templates/item/item-advantage-sheet.hbs`;
}
/** @override */
getData() {
// Retrieve the data structure from the base sheet. You can inspect or log
// the context variable to see the structure, but some key properties for
// sheets are the actor object, the data object, whether or not it's
// editable, the items array, and the effects array.
const context = super.getData();
// Use a safe clone of the actor data for further operations.
const advantageData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = advantageData.system;
context.flags = advantageData.flags;
context.hasChoices = context.system.auswahl.length > 0;
context.choices = {}
context.system.auswahl.forEach(a => {
context.choices[a] = a
})
context.hasModality = context.system.value != null
return context;
}
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) {
}
}
}

View File

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

View File

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

View File

@ -2,24 +2,14 @@
"name": "Munition (Balläster)",
"image": "systems/DSA_4-1/assets/arsenal/arrow3.png",
"category": [
"Gegenstand"
"Gegenstand",
"Munition"
],
"weight": 0.125,
"price": 0.6,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 0,
"armorHandicap": 0,
"description": ""
"description": "",
"ammunition": {
"max": 1,
"count": 1
}
}

View File

@ -2,24 +2,14 @@
"name": "Munition (Eisenwalder)",
"image": "systems/DSA_4-1/assets/arsenal/arrow3.png",
"category": [
"Gegenstand"
"Gegenstand",
"Munition"
],
"weight": 0,
"price": 1.5,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 0,
"armorHandicap": 0,
"description": ""
"description": "",
"ammunition": {
"max": 10,
"count": 10
}
}

View File

@ -2,24 +2,14 @@
"name": "Munition (Elfenbogen)",
"image": "systems/DSA_4-1/assets/arsenal/arrow2.png",
"category": [
"Gegenstand"
"Gegenstand",
"Munition"
],
"weight": 0.075,
"price": 0.4,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 0,
"armorHandicap": 0,
"description": ""
"description": "",
"ammunition": {
"max": 1,
"count": 1
}
}

View File

@ -2,24 +2,14 @@
"name": "Munition (Kompositbogen)",
"image": "systems/DSA_4-1/assets/arsenal/arrow1.png",
"category": [
"Gegenstand"
"Gegenstand",
"Munition"
],
"weight": 0.05,
"price": 0.25,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 0,
"armorHandicap": 0,
"description": ""
"description": "",
"ammunition": {
"max": 1,
"count": 1
}
}

View File

@ -2,24 +2,14 @@
"name": "Munition (Kriegsbogen)",
"image": "systems/DSA_4-1/assets/arsenal/arrow1.png",
"category": [
"Gegenstand"
"Gegenstand",
"Munition"
],
"weight": 0.1,
"price": 0.6,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 0,
"armorHandicap": 0,
"description": ""
"description": "",
"ammunition": {
"max": 1,
"count": 1
}
}

View File

@ -2,24 +2,14 @@
"name": "Munition (Kurzbogen)",
"image": "systems/DSA_4-1/assets/arsenal/arrow1.png",
"category": [
"Gegenstand"
"Gegenstand",
"Munition"
],
"weight": 0.05,
"price": 0.25,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 0,
"armorHandicap": 0,
"description": ""
"description": "",
"ammunition": {
"max": 1,
"count": 1
}
}

View File

@ -2,24 +2,14 @@
"name": "Munition (Langbogen)",
"image": "systems/DSA_4-1/assets/arsenal/arrow1.png",
"category": [
"Gegenstand"
"Gegenstand",
"Munition"
],
"weight": 0.075,
"price": 0.4,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 0,
"armorHandicap": 0,
"description": ""
"description": "",
"ammunition": {
"max": 1,
"count": 1
}
}

View File

@ -2,24 +2,14 @@
"name": "Munition (Leichte Armbrust)",
"image": "systems/DSA_4-1/assets/arsenal/arrow3.png",
"category": [
"Gegenstand"
"Gegenstand",
"Munition"
],
"weight": 0.075,
"price": 1.5,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 0,
"armorHandicap": 0,
"description": ""
"description": "",
"ammunition": {
"max": 1,
"count": 1
}
}

View File

@ -2,24 +2,14 @@
"name": "Munition (Windenarmbrust)",
"image": "systems/DSA_4-1/assets/arsenal/arrow3.png",
"category": [
"Gegenstand"
"Gegenstand",
"Munition"
],
"weight": 0.1,
"price": 2,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 0,
"armorHandicap": 0,
"description": ""
"description": "",
"ammunition": {
"max": 1,
"count": 1
}
}

View File

@ -8,21 +8,13 @@
"weight": 1,
"price": 40,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 1,
"iniModifier": -1,
"attackModifier": -2,
"parryModifier": -1,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [
"Schilde"
],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 1,
"armorHandicap": 1,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 3,
"price": 0,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 1,
"armorValue": {
"total": 1,
"kopf": 0,
"brust": 1,
"ruecken": 1,
"bauch": 1,
"armlinks": 1,
"beinlinks": 1,
"armrechts": 1,
"beinrechts": 1
},
"armorHandicap": 1,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 14,
"price": 750,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 6,
"armorValue": {
"total": 5,
"kopf": 0,
"brust": 6,
"ruecken": 5,
"bauch": 6,
"armlinks": 5,
"armrechts": 5,
"beinlinks": 4,
"beinrechts": 4
},
"armorHandicap": 4,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 17,
"price": 1000,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 8,
"armorHandicap": 5,
"armorValue": {
"total": 6,
"kopf": 3,
"brust": 7,
"ruecken": 5,
"bauch": 7,
"armlinks": 5,
"beinlinks": 5,
"armrechts": 5,
"beinrechts": 5
},
"armorHandicap": 4,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 6.5,
"price": 150,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 3,
"armorHandicap": 3,
"armorValue": {
"total": 3,
"kopf": 0,
"brust": 4,
"ruecken": 4,
"bauch": 4,
"armlinks": 2,
"armrechts": 2,
"beinlinks": 1,
"beinrechts": 1
},
"armorHandicap": 2,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 30,
"price": 2500,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 12,
"armorHandicap": 10,
"armorValue": {
"total": 8,
"kopf": 8,
"brust": 8,
"ruecken": 7,
"bauch": 8,
"armlinks": 7,
"beinlinks": 7,
"armrechts": 7,
"beinrechts": 7
},
"armorHandicap": 8,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 4,
"price": 60,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 3,
"armorValue": {
"total": 2,
"kopf": 0,
"brust": 3,
"ruecken": 2,
"bauch": 2,
"armlinks": 1,
"beinlinks": 0,
"armrechts": 1,
"beinrechts": 0
},
"armorHandicap": 2,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 4,
"price": 110,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 3,
"armorHandicap": 2,
"armorValue": {
"total": 2,
"kopf": 0,
"brust": 5,
"ruecken": 1,
"bauch": 2,
"armlinks": 0,
"beinlinks": 0,
"armrechts": 0,
"beinrechts": 0
},
"armorHandicap": 1,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 10,
"price": 180,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 4,
"armorHandicap": 4,
"armorValue": {
"total": 3,
"kopf": 0,
"brust": 4,
"ruecken": 4,
"bauch": 4,
"armlinks": 3,
"armrechts": 3,
"beinlinks": 2,
"beinrechts": 2
},
"armorHandicap": 2,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 4.5,
"price": 80,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 3,
"armorHandicap": 3,
"armorValue": {
"total": 2,
"kopf": 0,
"brust": 3,
"ruecken": 3,
"bauch": 3,
"armlinks": 0,
"beinlinks": 0,
"armrechts": 0,
"beinrechts": 0
},
"armorHandicap": 1,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 7.5,
"price": 250,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 4,
"armorHandicap": 3,
"armorValue": {
"total": 3,
"kopf": 0,
"brust": 5,
"ruecken": 4,
"bauch": 5,
"armlinks": 0,
"armrechts": 0,
"beinlinks": 2,
"beinrechts": 2
},
"armorHandicap": 2,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 12,
"price": 1000,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 5,
"armorHandicap": 5,
"armorValue": {
"total": 4,
"kopf": 0,
"brust": 5,
"ruecken": 5,
"bauch": 5,
"armlinks": 3,
"beinlinks": 3,
"armrechts": 3,
"beinrechts": 3
},
"armorHandicap": 4,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 10,
"price": 1000,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 5,
"armorHandicap": 4,
"armorValue": {
"total": 4,
"kopf": 0,
"brust": 5,
"ruecken": 5,
"bauch": 5,
"armlinks": 3,
"armrechts": 3,
"beinlinks": 2,
"beinrechts": 2
},
"armorHandicap": 3,
"description": ""
}

View File

@ -20,7 +20,17 @@
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 1,
"armorValue": {
"total": 1.5,
"kopf": 0,
"brust": 1,
"ruecken": 1,
"bauch": 1,
"armlinks": 1,
"beinlinks": 1,
"armrechts": 1,
"beinrechts": 1
},
"armorHandicap": 1,
"description": ""
}

View File

@ -7,20 +7,17 @@
],
"weight": 3,
"price": 40,
"breakFactor": 0,
"iniModifier": 0,
"attackModifier": 0,
"parryModifier": 0,
"meleeAttackModifier": 0,
"meleeAttackModifierIncrement": 0,
"meleeSkills": [],
"meleeAttackDamage": "",
"rangedSkills": [],
"rangedRangeModifier": "",
"rangedRangeDamageModifier": "",
"rangedAttackDamage": "",
"rangedReloadTime": 0,
"armorValue": 2,
"armorValue": {
"total": 2,
"kopf": 0,
"brust": 2,
"ruecken": 2,
"bauch": 2,
"armlinks": 1,
"beinlinks": 1,
"armrechts": 1,
"beinrechts": 1
},
"armorHandicap": 2,
"description": ""
}

View File

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

View File

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

View File

@ -1,95 +0,0 @@
@use "./_colours";
@use "./_numbers";
@use "./_assets";
@use "sass:color";
.dsa41.sheet.actor.character {
.sheet-header {
position: relative;
.attributes {
position: absolute;
top: 8px;
right: 4px;
height: 48px;
display: flex;
.attribute.rollable {
width: 48px;
height: 48px;
position: relative;
box-shadow: inset numbers.$dice-box-inset numbers.$dice-box-inset numbers.$dice-box-blur-radius colours.$dice-box-shadow;
background: assets.$dice-box-background;
margin-left: 2px;
border: numbers.$dice-box-border-width inset colours.$dice-box-border-color;
.die {
stroke-width: 0.5;
svg {
position: absolute;
left: 4px;
top: 4px;
bottom: 4px;
right: 4px;
}
.border {
fill: rgba(0, 0, 0, 0);
stroke: colours.$attribute-die-border-color;
}
.center {
fill: colours.$attribute-die-color;
stroke: colours.$attribute-die-border-color;
}
.topleft {
fill: color.adjust(colours.$attribute-die-color, $lightness: numbers.$lighter_factor);
stroke: colours.$attribute-die-border-color;
}
.bottomleft {
fill: color.adjust(colours.$attribute-die-color, $lightness: numbers.$lightest_factor);
stroke: colours.$attribute-die-border-color;
}
.topright {
fill: color.adjust(colours.$attribute-die-color, $lightness: numbers.$darken_factor);
stroke: colours.$attribute-die-border-color;
}
.bottomright, .bottom {
fill: color.adjust(colours.$attribute-die-color, $lightness: numbers.$darkest_factor);
stroke: colours.$attribute-die-border-color;
}
}
.wert {
font-weight: bold;
position: absolute;
left: 0;
width: 48px;
top: 0;
line-height: 48px;
vertical-align: middle;
text-align: center;
color: colours.$attribute-die-label-color;
}
.name {
position: absolute;
left: 0;
right: 0;
bottom: 0;
line-height: 12px;
vertical-align: middle;
text-align: center;
color: colours.$attribute-label-color;
background-color: colours.$attribute-label-background-color;
}
}
}
}
}

View File

@ -1,651 +0,0 @@
@use "sass:color";
@use "_numbers";
@use "_colours" as colour;
.dsa41.sheet.actor.character {
.window-header.flexrow.draggable.resizable {
}
$sidebar-width: 224px;
$attribute-height: 60px;
$tabs-spacing: 14px;
$tabs-height: 26px;
.window-content {
display: unset; /* we are on our own */
position: relative;
header.sheet-header {
position: absolute;
top: 0;
left: 0;
height: $attribute-height;
right: 0;
}
div.head-data {
position: absolute;
left: 0;
top: $attribute-height;
width: $sidebar-width;
bottom: 0;
padding: 8px;
.profile-img {
width: $sidebar-width - 16px;
}
}
nav.sheet-tabs.tabs {
position: absolute;
left: $sidebar-width;
top: $attribute-height+$tabs-spacing;
right: 0;
height: $tabs-height;
}
section.sheet-body {
position: absolute;
top: $attribute-height+$tabs-height+$tabs-spacing+4px;
left: $sidebar-width;
right: 4px;
bottom: 4px;
padding: 8px;
overflow: auto;
}
.tab.overview.active {
display: flex;
flex-direction: column;
height: 100%;
.meta-data {
flex: 0;
columns: 2;
gap: 0 16px;
div {
break-inside: avoid;
padding: 4px 0;
}
.double {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-areas: 'label label' 'left right';
gap: 0 8px;
label {
grid-area: label;
}
}
.editor {
background-color: rgba(0, 0, 0, 0.2)
}
& + .meta-data {
flex: 1;
}
}
.meta-data.html {
& > div {
display: flex;
flex-direction: column;
height: 100%;
label {
flex: 0;
}
.editor {
flex: 1;
}
}
}
}
.tab.attributes.active {
height: 100%;
.attribute {
padding: 8px 0;
display: flex;
gap: 0 8px;
label {
width: 120px;
text-align: right;
vertical-align: middle;
line-height: 24px;
}
input {
max-width: 80px;
text-align: right;
}
.mod {
color: grey;
text-shadow: 0 -1px 0 #ccc;
box-Shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
border-radius: 4px;
height: 24px;
width: 24px;
background: linear-gradient(0deg, rgba(24, 24, 24, 1) 0%, rgba(80, 80, 80, 1) 100%);;
display: inline-block;
text-align: center;
vertical-align: middle;
line-height: 24px;
margin-left: 4px;
}
}
.attributes-overview {
columns: 2;
gap: 0 16px;
}
.resource-overview {
.attribute {
}
}
.advantages, .special-abilities {
margin-bottom: 16px;
ul {
list-style-type: none;
padding: 0;
margin: 0;
text-indent: 0;
li {
display: inline-block;
}
.advantage, .special-ability {
position: relative;
border: 1px solid gold;
box-shadow: 2px 2px 4px #000;
border-radius: 8px;
height: 24px;
color: gold;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2);
display: inline-block;
padding: 0 8px;
margin-left: 0;
margin-bottom: 4px;
background-image: url("../assets/velvet_button.png");
background-repeat: repeat-y;
background-size: cover;
span {
position: relative;
z-index: 2;
line-height: 24px;
vertical-align: middle;
}
&.special-ability {
&::after {
background: rgba(128, 0, 96, 0.5);
}
}
&::after {
content: "";
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-radius: 8px;
background: rgba(0, 128, 0, 0.5);
}
& + .advantage, & + .special-ability {
margin-left: 8px;
}
&.disadvantage {
font-style: italic;
&::after {
background: rgba(128, 0, 0, 0.5);
}
}
}
}
}
}
.backpack.active {
display: grid;
grid-template-columns: 1fr 320px;
grid-template-rows: 74px 1fr;
gap: 10px;
height: 100%;
grid-template-areas:
"capacity capacity"
"inventory equipment";
.capacity {
grid-area: capacity;
.resource {
position: relative;
border: 1px inset #ccc;
background-color: rgba(0, 0, 0, 0.2);
height: 8px;
span.fill {
position: absolute;
left: 0;
top: 0;
bottom: 0;
background: linear-gradient(to bottom, #0bad29 0%, #11f128 50%, #0cde24 51%, #6ff77b 100%);
}
}
}
.inventory {
grid-area: inventory;
.equipment:hover {
.item-name {
text-shadow: 0 0 10px rgb(255 0 0);
}
}
}
}
.tab.combat.active {
display: grid;
grid-template-columns: 1fr 320px;
grid-template-rows: 32px 32px 1fr;
grid-template-areas: "res res" "wounds wounds" "actions actions";
gap: 10px;
.tab-resources {
grid-area: res;
}
.wounds {
position: relative;
height: 24px;
display: flex;
margin-bottom: 8px;
padding-left: 130px;
grid-area: wounds;
label {
position: absolute;
left: 0;
top: 0;
line-height: 24px;
width: 120px;
bottom: 0;
vertical-align: middle;
text-align: right;
height: 24px;
display: inline-block;
z-index: 2;
}
.filled-segment {
border: 1px solid black;
background-image: url('../assets/gradient.png');
background-size: 24px 100%;
position: relative;
flex: 1;
text-align: center;
vertical-align: middle;
line-height: 24px;
color: white;
text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.3);
background-color: rgba(255, 0, 0, 0.8);
background-blend-mode: multiply;
}
.empty-segment {
border: 1px solid black;
background-image: url('../assets/gradient.png');
background-size: 32px 100%;
position: relative;
flex: 1;
text-align: center;
vertical-align: middle;
line-height: 24px;
color: gold;
text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.3);
background-color: rgba(0, 0, 0, 0.8);
background-blend-mode: multiply;
}
}
.actions {
grid-area: actions;
}
&.zones {
grid-template-areas: "res res" "wounds wounds" "actions paperdoll";
.paperdoll {
grid-area: paperdoll;
div {
position: relative;
margin-left: 9px;
margin-top: 42px;
.wound {
position: absolute;
width: 32px;
height: 32px;
border-radius: 16px;
border: 1px solid black;
background-color: rgba(0, 0, 0, 0.5);
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3);
line-height: 32px;
vertical-align: middle;
text-align: center;
color: red;
&.armlinks {
top: 146px;
left: 210px;
}
&.armrechts {
top: 146px;
left: 60px;
}
&.beinlinks {
top: 346px;
left: 210px;
}
&.beinrechts {
top: 346px;
left: 60px;
}
&.bauch {
top: 166px;
left: 136px;
}
&.kopf {
top: 6px;
left: 136px
}
&.brust {
top: 86px;
left: 110px;
}
}
.armor {
position: absolute;
width: 32px;
height: 32px;
border-radius: 0 0 16px 16px;
line-height: 32px;
vertical-align: middle;
text-align: center;
border: 1px solid silver;
background-color: rgba(0, 0, 0, 0.5);
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3);
color: silver;
&.armlinks {
top: 180px;
left: 210px;
}
&.armrechts {
top: 180px;
left: 60px;
}
&.beinlinks {
top: 380px;
left: 210px;
}
&.beinrechts {
top: 380px;
left: 60px;
}
&.bauch {
top: 200px;
left: 136px;
}
&.kopf {
top: 40px;
left: 136px
}
&.brust {
top: 120px;
left: 110px;
}
}
}
}
}
}
.tab.spells {
tr {
height: 24px;
margin: 0;
padding: 0;
}
td {
margin: 0;
padding: 0;
height: 24px;
}
$color: #05f;
.spell.rollable svg {
width: 24px;
height: 24px;
top: 1px;
z-index: 1;
position: relative;
.border {
fill: #0000;
}
.center {
fill: $color;
stroke: colour.$rollable-die-border-color;
}
.topleft {
fill: color.adjust($color, $lightness: numbers.$lighter_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomleft {
fill: color.adjust($color, $lightness: numbers.$lightest_factor);
stroke: colour.$rollable-die-border-color;
}
.topright {
fill: color.adjust($color, $lightness: numbers.$darken_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomright, .bottom {
fill: color.adjust($color, $lightness: numbers.$darkest_factor);
stroke: colour.$rollable-die-border-color;
}
}
.die-column {
width: 24px;
}
.clickable {
span {
position: relative;
z-index: 1;
}
}
tbody {
tr {
position: relative;
&::after {
content: '';
background-image: linear-gradient(to right, rgba(color.scale($color, $lightness: numbers.$zebra_light), numbers.$start_gradient), rgba(color.scale($color, $lightness: numbers.$zebra_light), numbers.$end_2_gradient));
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
position: absolute;
top: 2px;
left: 12px;
bottom: 2px;
right: 33%;
z-index: 0;
pointer-events: none;
}
&:nth-child(odd) {
&::after {
background-image: linear-gradient(to right, rgba(color.scale($color, $lightness: numbers.$zebra_dark), numbers.$start_gradient), rgba(color.scale($color, $lightness: numbers.$zebra_dark), numbers.$end_2_gradient));
}
}
}
}
.merkmal-list {
list-style: none;
margin: 0;
padding: 0;
text-indent: 0;
li {
display: inline-block;
padding: 0 4px;
}
}
}
.tab.liturgies {
table {
border-top: unset;
border-bottom: unset;
position: relative;
}
.liturgy-header {
background: unset;
border: unset;
tr {
height: 90px;
th {
vertical-align: middle;
color: black;
text-shadow: 2px 2px 1px rgba(0, 0, 0, 0.2);
}
}
}
td, th {
padding-left: 8px;
}
}
.tab-resources {
display: flex;
justify-content: center;
gap: 0 16px;
padding-bottom: 8px;
& > div {
height: 32px;
position: relative;
label {
width: 80px;
line-height: 32px;
vertical-align: middle;
}
input {
display: inline-block;
width: 40px;
height: 32px;
}
span.inline {
line-height: 32px;
vertical-align: middle;
width: 40px;
text-align: center;
}
}
}
}
}

View File

@ -1,174 +0,0 @@
@use "_colours" as colour;
@use 'sass:color';
@use "_numbers";
.dsa41.sheet.actor.creature {
.window-content {
form {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 74px 30px 1fr;
.sheet-header {
height: 64px;
display: grid;
grid-template-columns: 64px max-content;
grid-template-rows: 1fr;
gap: 0 8px;
img {
width: 64px;
height: 64px;
}
input {
line-height: 64px;
height: 64px;
font-size: 2rem;
}
}
.sheet-body {
.tab {
margin: 8px;
height: 100%;
div.input {
height: 32px;
display: grid;
grid-template-columns: 120px 1fr;
&.compound {
grid-template-columns: 120px 140px 1fr;
span {
width: 120px;
}
input {
width: 100px;
}
}
label {
height: 32px;
width: 100%;
span {
width: 120px;
text-align: left;
line-height: 32px;
vertical-align: middle;
display: inline-block;
}
input {
text-align: right;
line-height: 32px;
vertical-align: middle;
padding-left: 120px;
display: inline-block;
}
}
}
table.attacks-table {
td {
padding: 2px 4px;
}
}
.rollable.tablecell {
position: relative;
.attacks-die {
width: 24px;
height: 24px;
position: absolute;
left: 4px;
top: 4px;
bottom: 2px;
z-index: 1;
svg {
stroke-width: 0.5;
$color: #f30;
.border {
fill: #0000;
}
.center {
fill: $color;
stroke: colour.$rollable-die-border-color;
}
.topleft {
fill: color.adjust($color, $lightness: numbers.$lighter_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomleft {
fill: color.adjust($color, $lightness: numbers.$lightest_factor);
stroke: colour.$rollable-die-border-color;
}
.topright {
fill: color.adjust($color, $lightness: numbers.$darken_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomright, .bottom {
fill: color.adjust($color, $lightness: numbers.$darkest_factor);
stroke: colour.$rollable-die-border-color;
}
}
}
input {
position: absolute;
left: 16px;
top: 4px;
bottom: 2px;
right: 4px;
width: unset;
text-indent: 12px;
}
}
.button-inline {
border: unset;
background: unset;
}
.editor {
height: 100%;
}
}
}
}
}
}

View File

@ -1,18 +0,0 @@
.dsa41.sheet.actor.character {
.tab.skills {
columns: 2;
column-gap: 20px;
.talent-group {
break-inside: avoid-column;
}
ul {
list-style-type: none;
padding-left: 0;
}
}
}

View File

@ -1,247 +0,0 @@
@use "sass:map";
@use "sass:color";
@use "colours" as colour;
@use "numbers";
$deity_colours_border: (
"Praios": orange,
"Rondra": red,
"Efferd": #74d0ec,
"Travia": #c6491a,
"Boron": #515151,
"Hesinde": #089e08,
"Firun": #8fdeff,
"Tsa": #6a24d8,
"Phex": #1569da,
"Peraine": #56a116,
"Ingerimm": #a53d19,
"Rahja": #ed70c2,
);
$deity_colours_tint: (
"Praios": rgba(orange, 0.5),
"Rondra": rgba(red, 0.5),
"Efferd": rgba(#74d0ec, 0.5),
"Travia": rgba(#c6491a, 0.5),
"Boron": rgba(#515151, 0.5),
"Hesinde": rgba(#089e08, 0.5),
"Firun": rgba(#8fdeff, 0.5),
"Tsa": linear-gradient(
-45deg,
rgba(255, 0, 0, 0.5) 0%,
rgba(255, 154, 0, 0.5) 10%,
rgba(208, 222, 33, 0.5) 20%,
rgba(79, 220, 74, 0.5) 30%,
rgba(63, 218, 216, 0.5) 40%,
rgba(47, 201, 226, 0.5) 50%,
rgba(28, 127, 238, 0.5) 60%,
rgba(95, 21, 242, 0.5) 70%,
rgba(186, 12, 248, 0.5) 80%,
rgba(251, 7, 217, 0.5) 90%,
rgba(255, 0, 0, 0.5) 100%
),
"Phex": rgba(#1569da, 0.5),
"Peraine": rgba(#56a116, 0.5),
"Ingerimm": rgba(#a53d19, 0.5),
"Rahja": rgba(#ed70c2, 0.5),
);
@mixin coloring($name) {
$color: map.get($deity_colours_border, $name);
$color-tint: map.get($deity_colours_tint, $name);
&.#{$name} {
th.background {
&::after {
background: $color-tint;
}
}
.banner-bot {
border-color: $color;
}
.banner-mid {
border-color: $color;
}
.banner-top {
&::before, &::after {
border-color: $color;
}
}
}
}
.tab.liturgies {
table {
@include coloring('Praios');
@include coloring('Rondra');
@include coloring('Efferd');
@include coloring('Travia');
@include coloring('Boron');
@include coloring('Hesinde');
@include coloring('Firun');
@include coloring('Tsa');
@include coloring('Phex');
@include coloring('Peraine');
@include coloring('Ingerimm');
@include coloring('Rahja');
tr {
th.background {
&::before {
position: absolute;
content: '';
background-image: url("../assets/velvet_strip.png");
background-repeat: repeat-y;
background-size: cover;
width: 86px;
height: 100%;
top: 45px;
left: 12px;
}
&::after { /* for tinting the texture */
content: "";
position: absolute;
width: 86px;
height: 100%;
top: 45px;
left: 12px;
}
}
}
.banner-top {
position: relative;
width: 90px;
img {
position: absolute;
top: 2px;
left: 1px;
width: 90px;
height: 90px;
border: 0;
z-index: 1;
}
&::after {
z-index: 0;
border-width: 0 4px 0 4px;
//background-color: #64b;
border-style: solid;
position: absolute;
content: "";
left: -2px;
top: 45px;
bottom: 0;
width: 94px;
}
&::before {
position: absolute;
border-radius: 45px;
height: 94px;
width: 94px;
content: '';
left: -2px;
right: -2px;
top: 0;
border-width: 4px;
border-style: solid;
z-index: 2;
}
}
.banner-mid {
position: relative;
border-width: 0 4px 0 4px;
//background-color: #64b;
border-style: solid;
width: 90px;
div {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
div.rank-label {
position: absolute;
left: 2px;
right: 2px;
top: 0;
bottom: 0;
border-bottom: 2px solid black;
color: gold;
text-shadow: 2px 2px 1px black;
}
}
}
.banner-bot {
position: relative;
border-width: 4px;
border-style: solid;
width: 90px;
height: 12px;
}
}
.liturgy.rollable {
width: 24px;
svg {
$color: #e4de61;
width: 24px;
height: 24px;
top: 1px;
z-index: 1;
position: relative;
.border {
fill: #0000;
}
.center {
fill: $color;
stroke: colour.$rollable-die-border-color;
}
.topleft {
fill: color.adjust($color, $lightness: numbers.$lighter_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomleft {
fill: color.adjust($color, $lightness: numbers.$lightest_factor);
stroke: colour.$rollable-die-border-color;
}
.topright {
fill: color.adjust($color, $lightness: numbers.$darken_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomright, .bottom {
fill: color.adjust($color, $lightness: numbers.$darkest_factor);
stroke: colour.$rollable-die-border-color;
}
}
}
.clickable:hover {
text-shadow: 0 0 10px rgb(255 0 0);
}
}

View File

@ -1,26 +0,0 @@
@use "./_colours";
@use "./_numbers";
@use "sass:color";
.player-action {
display: inline-block;
width: 120px;
height: 80px;
float: left;
margin: 0 8px 8px 0;
border: 1px solid #333;
background-color: color.scale(colours.$default-action, $alpha: 20%);
color: colours.$default-action-color;
border-radius: 4px;
box-shadow: numbers.$pill-box-inset numbers.$pill-box-inset numbers.$pill-box-blur-radius colours.$pill-box-shadow;
&.special-ability {
background-color: color.scale(colours.$special-action, $alpha: 20%);
color: colours.$special-action-color;
}
}

View File

@ -1,48 +0,0 @@
@use "./numbers";
@use "./colours";
@use "./assets";
.dsa41.sheet {
nav.sheet-tabs.tabs {
position: relative;
display: flow;
border-top: unset;
border-bottom: unset;
margin-bottom: 9px;
a.item[data-tab] {
background-color: colours.$tab-inactive-background-color;
&.active {
border-left: numbers.$tab-border-width solid colours.$tab-border-color;
border-top: numbers.$tab-border-width solid colours.$tab-border-color;
border-right: numbers.$tab-border-width solid colours.$tab-border-color;
border-bottom: 0;
top: numbers.$tab-border-width*2;
background: assets.$tab-background;
position: relative;
z-index: 2;
}
}
}
section.sheet-body {
border: numbers.$tab-border-width solid colours.$tab-border-color;
background: assets.$tab-pane-background;
div.tab {
overflow: auto;
}
}
}

View File

@ -34,9 +34,35 @@ $tab-background-color: #fff8;
$dice-box-shadow: rgba(0, 0, 0, 0.25);
$pill-box-shadow: rgba(0, 0, 0, 0.3);
$tab-shadow: rgba(0, 0, 0, 0.3);
$tab-pane-shadow: rgba(0, 0, 0, 0.3);
$attribute-die-border-color: #000;
$attribute-die-color: #F00;
$attribute-die-label-color: #FFF;
$attribute-die-co-color: #b3241a;
$attribute-die-sm-color: #8259a3;
$attribute-die-in-color: #388834;
$attribute-die-ch-color: #0d0d0d;
$attribute-die-dx-color: #688ec4;
$attribute-die-ag-color: #d5b467;
$attribute-die-bd-color: #a3a3a3;
$attribute-die-st-color: #d6a878;
$attribute-die-co-text-color: #fff;
$attribute-die-sm-text-color: #fff;
$attribute-die-in-text-color: #fff;
$attribute-die-ch-text-color: #fff;
$attribute-die-dx-text-color: #000;
$attribute-die-ag-text-color: #000;
$attribute-die-bd-text-color: #000;
$attribute-die-st-text-color: #000;
$attribute-label-color: #FFF;
$attribute-label-background-color: #0008;
@ -44,9 +70,9 @@ $rollable-die-border-color: #000;
$dice-box-border-color: #333;
$default-action: #c41;
$default-action-color: #FFF;
$default-action: rgba(204, 68, 17, 0.8);
$default-action-color: #000;
$special-action: #42c;
$special-action: rgba(68, 34, 204, 0.8);
$special-action-color: #FFF;
$special-action-color: #000;

View File

@ -0,0 +1,21 @@
@font-face {
font-family: "Andalus";
src: url("/systems/DSA_4-1/assets/fonts/andlso.ttf");
}
@font-face {
font-family: "Gentium";
src: url("/systems/DSA_4-1/assets/fonts/GenBasR.ttf");
}
@font-face {
font-family: "Gentium";
src: url("/systems/DSA_4-1/assets/fonts/GenBasI.ttf");
font-style: italic;
}
@font-face {
font-family: "Gentium";
src: url("/systems/DSA_4-1/assets/fonts/GenBasB.ttf");
font-weight: bold;
}

View File

@ -16,6 +16,14 @@ $dice-box-inset: 4px;
$dice-box-blur-radius: 4px;
$dice-box-border-width: 1px;
$tab-shadow-right: 1px;
$tab-shadow-bottom: 0;
$tab-shadow-blur-radius: 0;
$tab-pane-shadow-right: 1px;
$tab-pane-shadow-bottom: 1px;
$tab-pane-shadow-blur-radius: 0;
$pill-box-inset: 2px;
$pill-box-blur-radius: 4px;

View File

@ -0,0 +1,3 @@
svg {
pointer-events: none;
}

View File

@ -0,0 +1,36 @@
.application.sheet.dsa41 {
--font-body: Gentium, sans-serif;
label,
.sheet-tabs.tabs a,
h2, h3 {
font-family: Gentium, sans-serif;
font-weight: bold;
font-size: 12pt;
}
h2 {
height: 32px;
line-height: 32px;
padding: 0;
margin: 0;
}
ul {
li {
margin-bottom: 16px;
}
}
input,
.rkp .pill {
font-family: Andalus, sans-serif;
}
.editor.prosemirror.active, .editor.prosemirror.inactive {
font-family: Gentium, sans-serif;
}
}

View File

@ -0,0 +1,159 @@
@use "../atoms/colours";
@use "../atoms/numbers";
@use "../atoms/assets";
@use "sass:color";
.dsa41.sheet.actor.character {
.sheet-header {
position: relative;
.attributes {
display: flex;
.attribute.rollable {
width: 48px;
height: 48px;
position: relative;
box-shadow: inset numbers.$dice-box-inset numbers.$dice-box-inset numbers.$dice-box-blur-radius colours.$dice-box-shadow;
background: assets.$dice-box-background;
margin-left: 2px;
border: numbers.$dice-box-border-width inset colours.$dice-box-border-color;
.die {
stroke-width: 0.5;
svg {
position: absolute;
left: -6px;
top: -6px;
bottom: -6px;
right: -6px;
path {
fill: colours.$attribute-die-color;
}
}
}
.wert {
font-weight: bold;
position: absolute;
left: -2px;
width: 48px;
top: -2px;
font-size: smaller;
line-height: 48px;
vertical-align: middle;
text-align: center;
color: colours.$attribute-die-label-color;
}
.name {
position: absolute;
left: 0;
right: 0;
bottom: 0;
line-height: 12px;
vertical-align: middle;
text-align: center;
color: colours.$attribute-label-color;
background-color: colours.$attribute-label-background-color;
}
}
&.colorfulDice {
&.Mut {
.die svg path {
fill: colours.$attribute-die-co-color;
}
.wert {
color: colours.$attribute-die-co-text-color;
}
}
.Klugheit {
.die svg path {
fill: colours.$attribute-die-sm-color;
}
.wert {
color: colours.$attribute-die-sm-text-color;
}
}
.Intuition {
.die svg path {
fill: colours.$attribute-die-in-color;
}
.wert {
color: colours.$attribute-die-in-text-color;
}
}
.Charisma {
.die svg path {
fill: colours.$attribute-die-ch-color;
}
.wert {
color: colours.$attribute-die-ch-text-color;
}
}
.Geschicklichkeit {
.die svg path {
fill: colours.$attribute-die-dx-color;
}
.wert {
color: colours.$attribute-die-dx-text-color;
}
}
.Fingerfertigkeit {
.die svg path {
fill: colours.$attribute-die-ag-color;
}
.wert {
color: colours.$attribute-die-ag-text-color;
}
}
.Konstitution {
.die svg path {
fill: colours.$attribute-die-bd-color;
}
.wert {
color: colours.$attribute-die-bd-text-color;
}
}
.Körperkraft {
.die svg path {
fill: colours.$attribute-die-st-color;
}
.wert {
color: colours.$attribute-die-st-text-color;
}
}
}
}
}
}

View File

@ -0,0 +1,27 @@
.droppable {
.inventory-table {
position: relative;
}
.inventory-table::after {
content: 'Gegenstände hier fallen lassen';
pointer-events: none;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
border-radius: 8px;
border-style: dashed;
border-color: gray;
line-height: 34px;
height: 34px;
vertical-align: middle;
text-align: center;
background-color: rgba(0, 0, 0, 0.4);
color: gray;
}
}

View File

@ -0,0 +1,4 @@
.dsa41.sheet.actor.character {
}

View File

@ -0,0 +1,251 @@
@use "sass:map";
@use "sass:color";
@use "../atoms/colours" as colour;
@use "../atoms/numbers";
$deity_colours_border: (
"Praios": orange,
"Rondra": red,
"Efferd": #74d0ec,
"Travia": #c6491a,
"Boron": #515151,
"Hesinde": #089e08,
"Firun": #8fdeff,
"Tsa": #6a24d8,
"Phex": #1569da,
"Peraine": #56a116,
"Ingerimm": #a53d19,
"Rahja": #ed70c2,
);
$deity_colours_tint: (
"Praios": rgba(orange, 0.5),
"Rondra": rgba(red, 0.5),
"Efferd": rgba(#74d0ec, 0.5),
"Travia": rgba(#c6491a, 0.5),
"Boron": rgba(#515151, 0.5),
"Hesinde": rgba(#089e08, 0.5),
"Firun": rgba(#8fdeff, 0.5),
"Tsa": linear-gradient(
-45deg,
rgba(255, 0, 0, 0.5) 0%,
rgba(255, 154, 0, 0.5) 10%,
rgba(208, 222, 33, 0.5) 20%,
rgba(79, 220, 74, 0.5) 30%,
rgba(63, 218, 216, 0.5) 40%,
rgba(47, 201, 226, 0.5) 50%,
rgba(28, 127, 238, 0.5) 60%,
rgba(95, 21, 242, 0.5) 70%,
rgba(186, 12, 248, 0.5) 80%,
rgba(251, 7, 217, 0.5) 90%,
rgba(255, 0, 0, 0.5) 100%
),
"Phex": rgba(#1569da, 0.5),
"Peraine": rgba(#56a116, 0.5),
"Ingerimm": rgba(#a53d19, 0.5),
"Rahja": rgba(#ed70c2, 0.5),
);
@mixin coloring($name) {
$color: map.get($deity_colours_border, $name);
$color-tint: map.get($deity_colours_tint, $name);
&.#{$name} {
th.background {
&::after {
background: $color-tint;
}
}
.banner-bot {
border-color: $color;
}
.banner-mid {
border-color: $color;
}
.banner-top {
&::before, &::after {
border-color: $color;
}
}
}
}
.dsa41.sheet {
.tab.liturgies {
table {
@include coloring('Praios');
@include coloring('Rondra');
@include coloring('Efferd');
@include coloring('Travia');
@include coloring('Boron');
@include coloring('Hesinde');
@include coloring('Firun');
@include coloring('Tsa');
@include coloring('Phex');
@include coloring('Peraine');
@include coloring('Ingerimm');
@include coloring('Rahja');
tr {
th.background {
&::before {
position: absolute;
content: '';
background-image: url("/systems/DSA_4-1/assets/velvet_strip.png");
background-repeat: repeat-y;
background-size: cover;
width: 86px;
height: 100%;
top: 45px;
left: 28px;
}
&::after { /* for tinting the texture */
content: "";
position: absolute;
width: 86px;
height: 100%;
top: 45px;
left: 28px;
}
}
}
.banner-top {
position: relative;
width: 90px;
img {
position: absolute;
top: 2px;
left: 1px;
width: 90px;
height: 90px;
border: 0;
z-index: 1;
}
&::after {
z-index: 0;
border-width: 0 4px 0 4px;
//background-color: #64b;
border-style: solid;
position: absolute;
content: "";
left: -2px;
top: 45px;
bottom: 0;
width: 94px;
}
&::before {
position: absolute;
border-radius: 45px;
height: 94px;
width: 94px;
content: '';
left: -2px;
right: -2px;
top: 0;
border-width: 4px;
border-style: solid;
z-index: 2;
}
}
.banner-mid {
position: relative;
border-width: 0 4px 0 4px;
//background-color: #64b;
border-style: solid;
width: 90px;
div {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
div.rank-label {
position: absolute;
left: 2px;
right: 2px;
top: 0;
bottom: 0;
border-bottom: 2px solid black;
color: gold;
text-shadow: 2px 2px 1px black;
}
}
}
.banner-bot {
position: relative;
border-width: 4px;
border-style: solid;
width: 90px;
height: 12px;
}
}
.liturgy.rollable {
width: 24px;
svg {
$color: #e4de61;
width: 24px;
height: 24px;
top: 1px;
z-index: 1;
position: relative;
.border {
fill: #0000;
}
.center {
fill: $color;
stroke: colour.$rollable-die-border-color;
}
.topleft {
fill: color.adjust($color, $lightness: numbers.$lighter_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomleft {
fill: color.adjust($color, $lightness: numbers.$lightest_factor);
stroke: colour.$rollable-die-border-color;
}
.topright {
fill: color.adjust($color, $lightness: numbers.$darken_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomright, .bottom {
fill: color.adjust($color, $lightness: numbers.$darkest_factor);
stroke: colour.$rollable-die-border-color;
}
}
}
.clickable:hover {
text-shadow: 0 0 10px rgb(255 0 0);
}
}
}

View File

@ -0,0 +1,19 @@
.application.sheet.dsa41 {
.pill {
height: 24px;
line-height: 24px;
font-size: 14px;
vertical-align: middle;
padding: 1px 4px;
margin-right: 4px;
border-radius: 8px;
border: 1px solid orange;
color: orange;
background-color: rgba(0, 0, 0, 0.6);
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5), inset -1px -1px 0 rgba(0, 0, 0, 0.1);
display: inline-block;
}
}

View File

@ -0,0 +1,47 @@
@use "../atoms/colours";
@use "../atoms/numbers";
@use "sass:color";
.dsa41.sheet {
.player-action {
display: inline-block;
width: 120px;
height: 80px;
float: left;
margin: 0 8px 8px 0;
position: relative;
border: 1px solid #333;
color: colours.$default-action-color;
background: url('../../../ui/parchment.jpg');
background-size: 128px 100%;
border-radius: 4px;
box-shadow: numbers.$pill-box-inset numbers.$pill-box-inset numbers.$pill-box-blur-radius colours.$pill-box-shadow;
span {
position: relative;
z-index: 2;
}
&::after { /* for tinting the texture */
content: "";
position: absolute;
background-color: colours.$default-action;
background-blend-mode: multiply;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
&.special-ability::after {
background-color: colours.$special-action;
color: colours.$special-action-color;
}
}
}

View File

@ -0,0 +1,13 @@
.application.sheet.dsa41 {
.editor.prosemirror {
flex: 1;
background-color: rgba(0, 0, 0, 0.1);
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);
border: 1px solid #666;
outline: 1px solid transparent;
border-radius: 4px;
}
}

View File

@ -1,7 +1,7 @@
@use 'sass:color';
@use 'sass:map';
@use "_colours" as colour;
@use "_numbers" as numbers;
@use "../atoms/colours" as colour;
@use "../atoms/numbers" as numbers;
$rollable_colours: (
"nachteil": colour.$nachteil-color,
@ -146,9 +146,8 @@ $rollable_colours_font: (
.value {
width: 28px;
height: 28px;
left: 0;
left: -1px;
top: -2px;
scale: 0.8;
img {
position: absolute;
@ -171,39 +170,22 @@ $rollable_colours_font: (
.die {
stroke-width: 0.5;
svg {
margin: -4px;
path {
fill: $color;
stroke: colour.$rollable-die-border-color;
stroke-width: 5px;
}
}
span.value {
color: $font_color;
}
.border {
fill: colour.$rollable-die-border-color;
stroke: colour.$rollable-die-border-color;
}
.center {
fill: $color;
stroke: colour.$rollable-die-border-color;
}
.topleft {
fill: color.adjust($color, $lightness: numbers.$lighter_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomleft {
fill: color.adjust($color, $lightness: numbers.$lightest_factor);
stroke: colour.$rollable-die-border-color;
}
.topright {
fill: color.adjust($color, $lightness: numbers.$darken_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomright, .bottom {
fill: color.adjust($color, $lightness: numbers.$darkest_factor);
stroke: colour.$rollable-die-border-color;
}
}
.container {

View File

@ -60,7 +60,7 @@
left: 0;
top: 0;
bottom: 0;
background: url('../assets/gradient.png');
background: url('/systems/DSA_4-1/assets/gradient.png');
background-size: 32px 100%;
&::after {

View File

@ -0,0 +1,109 @@
@use "../atoms/numbers";
@use "../atoms/colours";
@use "../atoms/assets";
.dsa41.sheet {
nav.sheet-tabs.tabs {
position: relative;
display: flow;
border-top: unset;
border-bottom: unset;
margin-bottom: 0;
a.item[data-tab] {
background-color: colours.$tab-inactive-background-color;
&.active {
border-left: numbers.$tab-border-width solid colours.$tab-border-color;
border-top: numbers.$tab-border-width solid colours.$tab-border-color;
border-right: numbers.$tab-border-width solid colours.$tab-border-color;
border-bottom: 0;
top: numbers.$tab-border-width*2;
background: assets.$tab-background;
position: relative;
z-index: 2;
}
}
}
section.sheet-body {
border: numbers.$tab-border-width solid colours.$tab-border-color;
background: assets.$tab-pane-background;
div.tab {
overflow: auto;
}
}
// Tabs v2
.sheet-tabs {
position: relative;
display: flow;
border-top: unset;
border-bottom: unset;
margin-bottom: 0;
a[data-action="tab"] {
background-color: colours.$tab-inactive-background-color;
display: inline-block;
height: 32px;
line-height: 32px;
vertical-align: middle;
padding: 0 16px;
span {
}
&.active {
border-left: numbers.$tab-border-width solid colours.$tab-border-color;
border-top: numbers.$tab-border-width solid colours.$tab-border-color;
border-right: numbers.$tab-border-width solid colours.$tab-border-color;
border-bottom: 0;
top: numbers.$tab-border-width;
background: assets.$tab-background;
position: relative;
z-index: 2;
box-shadow: numbers.$tab-shadow-right numbers.$tab-shadow-bottom numbers.$tab-shadow-blur-radius colours.$tab-shadow;
span {
}
}
}
}
section.tab {
border: numbers.$tab-border-width solid colours.$tab-border-color;
background: assets.$tab-pane-background;
box-shadow: numbers.$tab-pane-shadow-right numbers.$tab-pane-shadow-bottom numbers.$tab-pane-shadow-blur-radius colours.$tab-pane-shadow;
flex: 1;
& > div {
display: flex;
flex-direction: column;
height: 100%;
gap: 8px;
padding: 8px;
}
}
}

View File

@ -14,6 +14,11 @@
height: 48px;
line-height: 48px;
}
button {
height: 48px;
width: 48px;
}
}
.meta {

View File

@ -0,0 +1,107 @@
@use "sass:color";
@use "../atoms/numbers";
@use "../atoms/colours" as colour;
@use "./character-tabs/meta";
@use "./character-tabs/social";
@use "./character-tabs/attributes";
@use "./character-tabs/inventory";
@use "./character-tabs/combat";
@use "./character-tabs/spells";
@use "./character-tabs/liturgies";
@use "./character-tabs/skills";
.application.sheet.dsa41.actor.character {
$sidebar-width: 224px;
$attribute-height: 60px;
$tabs-spacing: 14px;
$tabs-height: 26px;
.window-content {
display: unset; /* we are on our own */
position: relative;
.header-fields {
position: absolute;
top: 0;
left: 0;
height: $attribute-height;
right: 0;
display: grid;
grid-template-columns: 1fr max-content;
gap: 0 8px;
.name {
border: unset;
font-size: large;
}
}
div.head-data {
position: absolute;
left: 16px;
top: $attribute-height+30px;
width: $sidebar-width - 16px;
bottom: 16px;
padding: 0;
.profile-img {
width: $sidebar-width - 16px;
}
}
nav.sheet-tabs.tabs {
position: absolute;
left: $sidebar-width+16px;
top: $attribute-height+$tabs-spacing+13px;
right: 16px;
height: $tabs-height;
}
section.tab {
position: absolute;
top: $attribute-height+$tabs-height+$tabs-spacing+21px;
left: $sidebar-width+16px;
right: 16px;
bottom: 16px;
padding: 0;
overflow: auto;
}
.tab.meta.active {
@include meta.tab;
}
.tab.social.active {
@include social.tab;
}
.tab.attributes.active {
@include attributes.tab;
}
.tab.equipment.active {
@include inventory.tab;
}
.tab.combat.active {
@include combat.tab;
}
.tab.skills.active {
@include skills.tab;
}
.tab.spells {
@include spells.tab;
}
.tab.liturgies {
@include liturgies.tab;
}
}
}

View File

@ -0,0 +1,168 @@
@use "../atoms/colours" as colour;
@use 'sass:color';
@use "../atoms/numbers";
.dsa41.sheet.actor.creature {
.window-content {
.sheet-header {
height: 64px;
display: grid;
grid-template-columns: 64px 1fr;
grid-template-rows: 1fr;
gap: 0 8px;
margin-bottom: 8px;
img {
width: 64px;
height: 64px;
}
input {
line-height: 64px;
height: 64px;
font-size: 2rem;
}
}
div.input {
height: 32px;
display: grid;
grid-template-columns: 120px 1fr;
&.compound {
grid-template-columns: 120px 140px 1fr;
span {
width: 120px;
}
input {
width: 100px;
}
}
label {
height: 32px;
width: 100%;
span {
width: 120px;
text-align: left;
line-height: 32px;
vertical-align: middle;
display: inline-block;
}
input {
text-align: right;
line-height: 32px;
vertical-align: middle;
padding-left: 120px;
display: inline-block;
}
}
}
table.attacks-table {
margin: 0;
td {
padding: 2px 4px;
}
}
.rollable.tablecell {
position: relative;
.attacks-die {
width: 24px;
height: 24px;
position: absolute;
left: 4px;
top: 4px;
bottom: 2px;
z-index: 1;
svg {
stroke-width: 0.5;
$color: #f30;
.border {
fill: #0000;
}
.center {
fill: $color;
stroke: colour.$rollable-die-border-color;
}
.topleft {
fill: color.adjust($color, $lightness: numbers.$lighter_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomleft {
fill: color.adjust($color, $lightness: numbers.$lightest_factor);
stroke: colour.$rollable-die-border-color;
}
.topright {
fill: color.adjust($color, $lightness: numbers.$darken_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomright, .bottom {
fill: color.adjust($color, $lightness: numbers.$darkest_factor);
stroke: colour.$rollable-die-border-color;
}
}
}
input {
position: absolute;
left: 16px;
top: 4px;
bottom: 2px;
right: 4px;
width: unset;
text-indent: 12px;
}
}
.button-inline {
border: unset;
background: unset;
}
.editor {
height: 100%;
}
.tab.active {
padding: 8px;
}
.tab.attacks.active {
padding: 16px;
}
}
}

View File

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

View File

@ -1,28 +1,15 @@
@use "./_colours";
@use "./_numbers";
@use "../atoms/colours";
@use "../atoms/numbers";
.app.window-app.dsa41.sheet.item.equipment {
.application.sheet.dsa41.item.equipment {
.sheet-body {
.tab.meta.active > div {
position: relative;
.tab.active {
padding: 4px;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.tab.meta.active {
position: absolute;
display: grid;
grid-auto-columns: 1fr 1fr;
grid-template-columns: 80px auto;
grid-template-rows: 24px 48px auto 48px;
gap: 0 0;
grid-template-rows: 32px 48px auto 48px;
gap: 8px;
grid-template-areas:
"category category"
"quantity name"
@ -57,6 +44,7 @@
grid-area: bottomline;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
.named-value {
position: relative;
@ -104,7 +92,7 @@
ul {
list-style-type: none;
padding: 0;
padding: 0 32px 0 0;
margin: 0;
text-indent: 0;
@ -124,6 +112,7 @@
position: absolute;
right: 0;
top: 0;
width: 24px;
}
}
@ -153,7 +142,4 @@
}
}
}

View File

@ -1,40 +1,14 @@
.dsa41.sheet.actor.group {
.window-content {
display: unset;
position: relative;
}
.application.sheet.dsa41.actor.group {
.sheet-header {
.sheet-name {
font-size: 24pt;
height: 48px;
}
}
display: grid;
grid-template-columns: 32px 1fr;
gap: 8px;
.sheet-tabs {
position: absolute;
left: 0;
right: 0;
top: 76px;
height: 32px;
padding: 0 16px;
}
.sheet-body {
position: absolute;
top: 98px;
left: 0;
bottom: 0;
right: 0;
padding: 8px;
margin: 8px;
div.tab {
height: 100%;
position: relative;
img {
height: 32px;
width: 32px;
}
}

View File

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

View File

@ -1,6 +1,6 @@
.sheet.item.skill {
.dsa41.sheet.item.skill {
.tab.meta.active {
.meta-details {
display: grid;
grid-template-areas:
@ -8,9 +8,7 @@
"taw statistics ebe"
"language language language"
"attack attack attack";
gap: 8px;
margin: 8px;
height: unset;
.category {
grid-area: category;
@ -44,8 +42,13 @@
grid-area: attack;
display: flex;
flex-direction: row;
gap: 16px;
}
}
.description-details {
}
}

View File

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

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