Merge pull request 'feature/applicationv2' (#57) from feature/applicationv2 into main

Reviewed-on: #57
pull/61/head
macniel 2025-10-21 01:19:41 +02:00
commit 53c5c7b53a
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