From c8d59588e0ca84b4e5c11205e082ce445dbc1ecf Mon Sep 17 00:00:00 2001 From: mstein Date: Tue, 5 Jul 2022 00:10:08 +0200 Subject: [PATCH] Adds mod management (WIP) --- README.md | 5 +- lang/de.json | 24 +- packs/actors/blaupause-spielfiguren/val.json | 469 ++++++++++++++++++ .../blaupause-gegenstaende/fähigkeit.json | 5 - .../items/blaupause-gegenstaende/rüstung.json | 46 ++ .../items/blaupause-gegenstaende/zauber.json | 67 +++ source/module/M5Base.ts | 101 +++- source/module/actors/M5Character.ts | 98 ++-- source/module/items/M5Item.ts | 110 +++- source/module/sheets/M5CharacterSheet.ts | 18 +- source/module/sheets/M5ItemSheet.ts | 105 ++++ source/style/Character-sheet.less | 5 + source/template.json | 48 +- templates/sheets/character/gear.hbs | 29 +- templates/sheets/character/skills.hbs | 50 +- templates/sheets/character/spells.hbs | 6 +- templates/sheets/item/item.hbs | 106 ++-- templates/sheets/item/mod.hbs | 60 +++ 18 files changed, 1181 insertions(+), 171 deletions(-) create mode 100644 packs/actors/blaupause-spielfiguren/val.json create mode 100644 packs/items/blaupause-gegenstaende/rüstung.json create mode 100644 packs/items/blaupause-gegenstaende/zauber.json create mode 100644 templates/sheets/item/mod.hbs diff --git a/README.md b/README.md index a25c534..93216f2 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,13 @@ Link to documentation: [Midgard-Forum](https://www.midgard-forum.de/forum/articl - character sheet (WIP) - rudimentary rolltables for critical rolls - basic world map +- Modification of the dice roller (detection of critical rolls and practice points) ## TODO +- Race & Class Modifiers +- Items & Mods +- GM descriptions - Modification of the standard combat tracker to fit the Midgard specific battle round rules -- Modification of the dice roller (detection of critical rolls and practice points) ## License diff --git a/lang/de.json b/lang/de.json index 3477170..b2ce85a 100644 --- a/lang/de.json +++ b/lang/de.json @@ -197,5 +197,27 @@ "midgard5.spell-effectTarget": "Wirkunsziel", "midgard5.spell-effectArea": "Wirkungsbereich", "midgard5.spell-effectDuration": "Wirkungsdauer", - "midgard5.spell-origin": "Ursprung" + "midgard5.spell-origin": "Ursprung", + + "midgard5.mod-operation-add100": "Addieren (max 100)", + "midgard5.mod-operation-add": "Addieren", + "midgard5.mod-operation-set": "Basiswert", + "midgard5.mod-operation-fixed": "Fester Wert", + + "midgard5.mod-stat-defense": "Abwehr", + "midgard5.mod-stat-attack": "Angriff", + "midgard5.mod-stat-damage": "Schaden", + "midgard5.mod-stat-movement": "Bewegung", + "midgard5.mod-stat-resistanceMind": "Resistenz Geist", + "midgard5.mod-stat-resistanceBody": "Resistenz Körper", + "midgard5.mod-stat-spellCasting": "Zaubern", + "midgard5.mod-stat-brawl": "Raufen", + "midgard5.mod-stat-poisonResistance": "Giftresistenz", + "midgard5.mod-stat-lp": "Lebenspunkte", + "midgard5.mod-stat-ap": "Ausdauerpunkte", + + "midgard5.mod-type": "Typ der Modifikation", + "midgard5.mod-id": "Was soll modifiziert werden", + "midgard5.mod-operation": "Wie soll modifiziert werden", + "midgard5.mod-value": "Wert" } diff --git a/packs/actors/blaupause-spielfiguren/val.json b/packs/actors/blaupause-spielfiguren/val.json new file mode 100644 index 0000000..c7613e4 --- /dev/null +++ b/packs/actors/blaupause-spielfiguren/val.json @@ -0,0 +1,469 @@ +{ + "name": "Val", + "type": "character", + "img": "icons/svg/mystery-man.svg", + "data": { + "lp": { + "value": 15, + "min": 0, + "max": 15 + }, + "ap": { + "value": 20, + "min": 0, + "max": 20 + }, + "attributes": { + "st": { + "value": 71, + "bonus": 0 + }, + "gs": { + "value": 87, + "bonus": 0 + }, + "gw": { + "value": 87, + "bonus": 0 + }, + "ko": { + "value": 80, + "bonus": 0 + }, + "in": { + "value": 74, + "bonus": 0 + }, + "zt": { + "value": 96, + "bonus": 0 + }, + "au": { + "value": 100, + "bonus": 0 + }, + "pa": { + "value": 62, + "bonus": 0 + }, + "wk": { + "value": 82, + "bonus": 0 + } + }, + "info": { + "description": "", + "class": "Ordenskrieger", + "race": "", + "magicUsing": true, + "gender": "", + "weight": "", + "height": "", + "shape": "", + "age": "", + "caste": "", + "occupation": "", + "origin": "", + "faith": "" + }, + "es": 4693, + "ep": 0, + "gg": 0, + "sg": 0, + "gp": 2, + "skills": { + "general": { + "akrobatik": { + "fw": 6, + "attribute": "gw", + "initial": 8 + }, + "alchimie": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "anfuehren": { + "fw": 6, + "attribute": "pa", + "initial": 8 + }, + "athletik": { + "fw": 0, + "attribute": "st", + "initial": 8 + }, + "balancieren": { + "fw": 6, + "attribute": "gw", + "initial": 8 + }, + "beidhaendigerKampf": { + "fw": 0, + "attribute": "gs", + "initial": 8 + }, + "beredsamkeit": { + "fw": 3, + "attribute": "pa", + "initial": 8 + }, + "betaeuben": { + "fw": 6, + "attribute": "gs", + "initial": 8 + }, + "bootfahren": { + "fw": 3, + "attribute": "gs", + "initial": 8 + }, + "ersteHilfe": { + "fw": 0, + "attribute": "gs", + "initial": 8 + }, + "etikette": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "fallenEntdecken": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "fallenmechanik": { + "fw": 0, + "attribute": "gs", + "initial": 8 + }, + "faelschen": { + "fw": 0, + "attribute": "gs", + "initial": 8 + }, + "fechten": { + "fw": 0, + "attribute": "gs", + "initial": 8 + }, + "gassenwissen": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "gaukeln": { + "fw": 0, + "attribute": "gs", + "initial": 8 + }, + "gelaendelauf": { + "fw": 6, + "attribute": "gw", + "initial": 8 + }, + "geraetekunde": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "geschaeftssinn": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "gluecksspiel": { + "fw": 0, + "attribute": "gs", + "initial": 8 + }, + "heilkunde": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "kampfInVollruestung": { + "fw": 0, + "attribute": "st", + "initial": 8 + }, + "klettern": { + "fw": 6, + "attribute": "st", + "initial": 8 + }, + "landeskunde": { + "fw": 6, + "attribute": "in", + "initial": 8 + }, + "laufen": { + "fw": 0, + "attribute": "ko", + "initial": 8 + }, + "lesenVonZauberschrift": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "meditieren": { + "fw": 0, + "attribute": "wk", + "initial": 8 + }, + "menschenkenntnis": { + "fw": 3, + "attribute": "in", + "initial": 8 + }, + "meucheln": { + "fw": 0, + "attribute": "gs", + "initial": 8 + }, + "musizieren": { + "fw": 0, + "attribute": "gs", + "initial": 8 + }, + "naturkunde": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "pflanzenkunde": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "reiten": { + "fw": 6, + "attribute": "gw", + "initial": 8 + }, + "reiterkampf": { + "fw": 0, + "attribute": "gw", + "initial": 8 + }, + "scharfschiessen": { + "fw": 0, + "attribute": "gs", + "initial": 8 + }, + "schleichen": { + "fw": 3, + "attribute": "gw", + "initial": 8 + }, + "schloesserOeffnen": { + "fw": 0, + "attribute": "gs", + "initial": 8 + }, + "schwimmen": { + "fw": 3, + "attribute": "gw", + "initial": 8 + }, + "seilkunst": { + "fw": 3, + "attribute": "gs", + "initial": 8 + }, + "spurensuche": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "stehlen": { + "fw": 3, + "attribute": "gs", + "initial": 8 + }, + "tarnen": { + "fw": 3, + "attribute": "gw", + "initial": 8 + }, + "tauchen": { + "fw": 6, + "attribute": "ko", + "initial": 8 + }, + "tierkunde": { + "fw": 0, + "attribute": "in", + "initial": 8 + }, + "ueberleben": { + "fw": 6, + "attribute": "in", + "initial": 8 + }, + "verfuehren": { + "fw": 3, + "attribute": "pa", + "initial": 8 + }, + "verhoeren": { + "fw": 3, + "attribute": "pa", + "initial": 8 + }, + "verstellen": { + "fw": 3, + "attribute": "pa", + "initial": 8 + }, + "wagenlenken": { + "fw": 3, + "attribute": "gs", + "initial": 8 + }, + "zauberkunde": { + "fw": 0, + "attribute": "in", + "initial": 8 + } + } + }, + "calc": {} + }, + "token": { + "name": "Val", + "img": "icons/svg/mystery-man.svg", + "displayName": 0, + "actorLink": false, + "width": 1, + "height": 1, + "scale": 1, + "mirrorX": false, + "mirrorY": false, + "lockRotation": false, + "rotation": 0, + "alpha": 1, + "vision": false, + "dimSight": 0, + "brightSight": 0, + "sightAngle": 0, + "light": { + "alpha": 0.5, + "angle": 0, + "bright": 0, + "coloration": 1, + "dim": 0, + "gradual": true, + "luminosity": 0.5, + "saturation": 0, + "contrast": 0, + "shadows": 0, + "animation": { + "speed": 5, + "intensity": 5, + "reverse": false + }, + "darkness": { + "min": 0, + "max": 1 + } + }, + "disposition": -1, + "displayBars": 0, + "bar1": { + "attribute": "lp" + }, + "bar2": { + "attribute": "ap" + }, + "flags": {}, + "randomImg": false + }, + "items": [ + { + "_id": "0c7eqO7G0q6xfleC", + "name": "Wahrnehmung", + "type": "skill", + "img": "icons/svg/item-bag.svg", + "data": { + "description": "", + "attributes": { + "st": { + "short": "midgard5.actor-st", + "long": "midgard5.actor-st-long" + }, + "gs": { + "short": "midgard5.actor-gs", + "long": "midgard5.actor-gs-long" + }, + "gw": { + "short": "midgard5.actor-gw", + "long": "midgard5.actor-gw-long" + }, + "ko": { + "short": "midgard5.actor-ko", + "long": "midgard5.actor-ko-long" + }, + "in": { + "short": "midgard5.actor-in", + "long": "midgard5.actor-in-long" + }, + "zt": { + "short": "midgard5.actor-zt", + "long": "midgard5.actor-zt-long" + }, + "au": { + "short": "midgard5.actor-au", + "long": "midgard5.actor-au-long" + }, + "pa": { + "short": "midgard5.actor-pa", + "long": "midgard5.actor-pa-long" + }, + "wk": { + "short": "midgard5.actor-wk", + "long": "midgard5.actor-wk-long" + } + }, + "fw": 6, + "attribute": "", + "skill": "", + "type": "innate", + "rolls": { + "formulas": { + "0": { + "formula": "1d20 + @i.fw + @i.calc.bonus", + "type": "ew", + "label": "EW" + } + }, + "output": "" + }, + "calc": {} + }, + "effects": [], + "folder": null, + "sort": 0, + "permission": { + "default": 0, + "XD0IpWT6bN4AJiYQ": 3 + }, + "flags": {} + } + ], + "effects": [], + "folder": null, + "sort": 0, + "permission": { + "default": 0, + "XD0IpWT6bN4AJiYQ": 3 + }, + "flags": { + "core": { + "sourceId": "Actor.wpOCjtgirIyBM6jH" + } + }, + "_id": "fuXWE1rW4QIsywAk" +} \ No newline at end of file diff --git a/packs/items/blaupause-gegenstaende/fähigkeit.json b/packs/items/blaupause-gegenstaende/fähigkeit.json index c254031..1391e59 100644 --- a/packs/items/blaupause-gegenstaende/fähigkeit.json +++ b/packs/items/blaupause-gegenstaende/fähigkeit.json @@ -65,10 +65,5 @@ "default": 0, "XD0IpWT6bN4AJiYQ": 3 }, - "flags": { - "core": { - "sourceId": "Item.ieQ6JMEyOoF7n0qy" - } - }, "_id": "nkMkMFNDSdvlP1Jt" } \ No newline at end of file diff --git a/packs/items/blaupause-gegenstaende/rüstung.json b/packs/items/blaupause-gegenstaende/rüstung.json new file mode 100644 index 0000000..bedad89 --- /dev/null +++ b/packs/items/blaupause-gegenstaende/rüstung.json @@ -0,0 +1,46 @@ +{ + "name": "Rüstung", + "type": "armor", + "img": "icons/svg/item-bag.svg", + "data": { + "description": "", + "stats": { + "damageBonus": 0, + "attackBonus": 0, + "defenseBonus": 0, + "movementBonus": 0, + "resistanceMind": 0, + "resistanceBody": 0, + "spellBonus": 0 + }, + "equippable": false, + "equipped": true, + "attributeMod": { + "st": 0, + "gs": 0, + "gw": 0, + "ko": 0, + "in": 0, + "zt": 0, + "au": 0, + "pa": 0, + "wk": 0 + }, + "magic": false, + "lpProtection": 0, + "apProtection": 0, + "rolls": { + "formulas": {}, + "output": "" + }, + "calc": {} + }, + "effects": [], + "folder": null, + "sort": 0, + "permission": { + "default": 0, + "XD0IpWT6bN4AJiYQ": 3 + }, + "_id": "pV1hNavlQGJ9UaEf" +} \ No newline at end of file diff --git a/packs/items/blaupause-gegenstaende/zauber.json b/packs/items/blaupause-gegenstaende/zauber.json new file mode 100644 index 0000000..f92fb69 --- /dev/null +++ b/packs/items/blaupause-gegenstaende/zauber.json @@ -0,0 +1,67 @@ +{ + "name": "Zauber", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "data": { + "description": "", + "spellProcessSelection": { + "none": "midgard5.spell-process-none", + "beherrschen": "midgard5.spell-process-beherrschen", + "bewegen": "midgard5.spell-process-bewegen", + "erkennen": "midgard5.spell-process-erkennen", + "erschaffen": "midgard5.spell-process-erschaffen", + "formen": "midgard5.spell-process-formen", + "veraendern": "midgard5.spell-process-veraendern", + "zerstoeren": "midgard5.spell-process-zerstoeren", + "wundertat": "midgard5.spell-process-wundertat", + "dweomer": "midgard5.spell-process-dweomer", + "zauberlied": "midgard5.spell-process-zauberlied", + "salz": "midgard5.spell-process-salz", + "thaumagraphie": "midgard5.spell-process-thaumagraphie", + "beschwoeren": "midgard5.spell-process-beschwoeren", + "nekromantie": "midgard5.spell-process-nekromantie", + "thaumatherapie": "midgard5.spell-process-thaumatherapie", + "zaubermittel": "midgard5.spell-process-zaubermittel", + "zauberschutz": "midgard5.spell-process-zauberschutz" + }, + "spellTypeSelection": { + "gedanke": "midgard5.spell-type-gedanke", + "geste": "midgard5.spell-type-geste", + "wort": "midgard5.spell-type-wort" + }, + "spellTargetSelection": { + "umgebung": "midgard5.spell-target-umgebung", + "geist": "midgard5.spell-target-geist", + "koerper": "midgard5.spell-target-koerper" + }, + "bonus": 0, + "type": "gedanke", + "process": "none", + "ap": 0, + "castDuration": "", + "range": "", + "effectTarget": "umgebung", + "effectArea": "", + "effectDuration": "", + "origin": "", + "rolls": { + "formulas": { + "0": { + "formula": "1d20 + @c.calc.stats.spellCasting + @i.bonus", + "type": "ew", + "label": "Zaubern" + } + }, + "output": "" + }, + "calc": {} + }, + "effects": [], + "folder": null, + "sort": 0, + "permission": { + "default": 0, + "XD0IpWT6bN4AJiYQ": 3 + }, + "_id": "HQ469FvZkwKfzFfu" +} \ No newline at end of file diff --git a/source/module/M5Base.ts b/source/module/M5Base.ts index f3df0e4..aac02da 100644 --- a/source/module/M5Base.ts +++ b/source/module/M5Base.ts @@ -1,4 +1,48 @@ +export interface M5CharacterCalculatedData { + level: number + attributes: { + st: { value: number, bonus: number } + gs: { value: number, bonus: number } + gw: { value: number, bonus: number } + ko: { value: number, bonus: number } + in: { value: number, bonus: number } + zt: { value: number, bonus: number } + au: { value: number, bonus: number } + pa: { value: number, bonus: number } + wk: { value: number, bonus: number } + } + stats: { + armor: number + defense: number + damageBonus: number + attackBonus: number + defenseBonus: number + movementBonus: number + resistanceMind: number + resistanceBody: number + spellCasting: number + brawl: number + brawlEw: number + poisonResistance: number + enduranceBonus: number + } + skills: { + innate: {} + general: {} + combat: {} + language: {} + custom: {} + } + gear: { + weapons: {} + defensiveWeapons: {} + armor: {} + items: {} + } + spells: {} +} + export interface M5Skill { fw: number attribute: string @@ -13,6 +57,11 @@ export interface M5SkillLearned extends M5Skill { type: string } +export interface M5SkillCalculated extends M5Skill { + label: string, + calc: any +} + export interface M5Attribute { value: number bonus: number @@ -47,4 +96,54 @@ export enum M5EwResult { HIGH = "roll-ew-result-high", FAIL = "roll-ew-result-fail", PASS = "roll-ew-result-pass" -} \ No newline at end of file +} + +export enum M5Attributes { + ST = "st", + GW = "gw", + GS = "gs", + KO = "ko", + IN = "in", + ZT = "zt", + AU = "au", + PA = "pa", + WK = "wk" +} + +export enum M5Stats { + DEFENSE = "defense", + ATTACK = "attack", + DAMAGE = "damage", + MOVEMENT = "movement", + RESISTANCE_MIND = "resistanceMind", + RESISTANCE_BODY = "resistanceBody", + SPELL_CASTING = "spellCasting", + BRAWL = "brawl", + POISON_RESISTANCE = "poisonResistance", + LP = "lp", + AP = "ap" +} + +export enum M5ModType { + ATTRIBUTE = "attribute", + STAT = "stat", + SKILL = "skill" +} + +export enum M5ModOperation { + ADD_100 = "add100", + ADD = "add", + SET = "set", + FIXED = "fixed" +} + +export interface M5ItemMod { + type: M5ModType + id: string + operation: M5ModOperation + value: number +} + +export function enumKeys(obj: O): K[] { + return Object.keys(obj).filter(k => Number.isNaN(+k)) as K[] +} diff --git a/source/module/actors/M5Character.ts b/source/module/actors/M5Character.ts index 2c52cb0..529ae2e 100644 --- a/source/module/actors/M5Character.ts +++ b/source/module/actors/M5Character.ts @@ -1,49 +1,5 @@ import { M5Item } from "../items/M5Item" -import { M5Attribute, M5RollData, M5Skill, M5SkillLearned } from "../M5Base" - -export interface M5CharacterCalculatedData { - level: number - attributes: { - st: { value: number, bonus: number } - gs: { value: number, bonus: number } - gw: { value: number, bonus: number } - ko: { value: number, bonus: number } - in: { value: number, bonus: number } - zt: { value: number, bonus: number } - au: { value: number, bonus: number } - pa: { value: number, bonus: number } - wk: { value: number, bonus: number } - } - stats: { - armor: number - defense: number - damageBonus: number - attackBonus: number - defenseBonus: number - movementBonus: number - resistanceMind: number - resistanceBody: number - spellCasting: number - brawl: number - brawlEw: number - poisonResistance: number - enduranceBonus: number - } - skills: { - innate: {} - general: {} - combat: {} - language: {} - custom: {} - } - gear: { - weapons: {} - defensiveWeapons: {} - armor: {} - items: {} - } - spells: {} -} +import { M5Attribute, M5CharacterCalculatedData, M5RollData, M5Skill, M5SkillCalculated, M5SkillLearned } from "../M5Base" export class M5Character extends Actor { @@ -118,7 +74,13 @@ export class M5Character extends Actor { } as M5CharacterCalculatedData const context = (this as any).data + if (!context) + return null + const data = context.data + if (!data) + return null + ret.level = M5Character.levelFromExp(data.es) ret.attributes.st.value = M5Character.attributeMinMax(data.attributes.st) // TODO item effects @@ -155,6 +117,23 @@ export class M5Character extends Actor { ret.stats.poisonResistance = 30 + Math.floor(ret.attributes.ko.value / 2) ret.stats.enduranceBonus = Math.floor(ret.attributes.ko.value/10) + Math.floor(ret.attributes.st.value/20) + if (!skip.items) { + context.items?.filter(item => item.data.type === "item").forEach(item => { + item.prepareDerivedData() + + let label = item.data.name + if (item.data.data.magic) { + label += "*" + } + + ret.gear.items[item.data._id] = { + label: label, + magic: item.data.data.magic, + calc: item.data.data.calc + } + }) + } + if (!skip.skills) { context.items?.filter(item => item.data.type === "skill").forEach(item => { item.prepareDerivedData() @@ -164,7 +143,7 @@ export class M5Character extends Actor { fw: item.data.data.fw, attribute: item.data.data.attribute, calc: item.data.data.calc - } + } as M5SkillCalculated }) } @@ -227,23 +206,6 @@ export class M5Character extends Actor { }) } - if (!skip.items) { - context.items?.filter(item => item.data.type === "item").forEach(item => { - item.prepareDerivedData() - - let label = item.data.name - if (item.data.data.magic) { - label += "*" - } - - ret.gear.items[item.data._id] = { - label: label, - magic: item.data.data.magic, - calc: item.data.data.calc - } - }) - } - if (!skip.spells) { context.items?.filter(item => item.data.type === "spell").forEach(item => { item.prepareDerivedData() @@ -323,7 +285,7 @@ export class M5Character extends Actor { attribute(name: string): M5Attribute { const context = (this as any).data - return context.data.attributes[name] + return context?.data?.attributes[name] } createSkill(skillName: string): Promise { @@ -334,12 +296,14 @@ export class M5Character extends Actor { return (this as any).createEmbeddedDocuments("Item", [itemData]).then(docs => { const item = docs[0] - return item as M5Item + return item }) } - getSkill(skillId: string): M5Skill { - return (this as any).getEmbeddedDocument("Item", skillId) as M5Skill + getItem(itemId: string): any { + if (!(this as any).data?.items) + return null + return (this as any).getEmbeddedDocument("Item", itemId) } } diff --git a/source/module/items/M5Item.ts b/source/module/items/M5Item.ts index 4e6cc4f..212c53f 100644 --- a/source/module/items/M5Item.ts +++ b/source/module/items/M5Item.ts @@ -1,5 +1,7 @@ +import { ItemData } from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/module.mjs" +import { ConstructorDataType } from "@league-of-foundry-developers/foundry-vtt-types/src/types/helperTypes" import { M5Character } from "../actors/M5Character" -import { M5RollData, M5RollResult, M5Skill } from "../M5Base" +import { enumKeys, M5Attributes, M5ItemMod, M5ModType, M5RollData, M5RollResult, M5Skill, M5Stats } from "../M5Base" import { M5Roll } from "../rolls/M5Roll" export class M5Item extends Item { @@ -16,7 +18,8 @@ export class M5Item extends Item { if (context.data?.attribute && context.data?.attribute !== "" && character) { const attribute = character.attribute(context.data.attribute) - calc.bonus += M5Character.attributeBonus(attribute) + if (attribute) + calc.bonus += M5Character.attributeBonus(attribute) } calc.ew = context.data.fw + calc.bonus @@ -29,9 +32,12 @@ export class M5Item extends Item { if (actor) { const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true }) - calc.ew += actorCalc.stats.attackBonus + if (actorCalc) { + calc.ew += actorCalc.stats.attackBonus + calc.combatSkills = actorCalc.skills.combat + } - const skill = character.getSkill(context.data.skillId) as any + const skill = character.getItem(context.data.skillId) //console.log("M5Item.prepareDerivedData:weapon", context.data, skill?.data?.data) if (skill) { skill.prepareDerivedData() @@ -40,8 +46,6 @@ export class M5Item extends Item { calc.bonus += skillData.calc.bonus calc.fw += skillData.fw } - - calc.combatSkills = actorCalc.skills.combat } } else if (context.type === "defensiveWeapon") { calc.fw = 0 @@ -52,9 +56,12 @@ export class M5Item extends Item { if (actor) { const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true }) - calc.ew += actorCalc.stats.defense + actorCalc.stats.defenseBonus + if (actorCalc) { + calc.ew += actorCalc.stats.defense + actorCalc.stats.defenseBonus + calc.combatSkills = actorCalc.skills.combat + } - const skill = character.getSkill(context.data.skillId) as any + const skill = character.getItem(context.data.skillId) //console.log("M5Item.prepareDerivedData:weapon", context.data, skill?.data?.data) if (skill) { skill.prepareDerivedData() @@ -63,15 +70,90 @@ export class M5Item extends Item { calc.bonus += skillData.calc.bonus calc.fw += skillData.fw } - - calc.combatSkills = actorCalc.skills.combat } } else if (context.type === "spell") { calc.ew = context.data.bonus if (actor) { const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true }) - calc.ew += actorCalc.stats.spellCasting + if (actorCalc) { + calc.ew += actorCalc.stats.spellCasting + } } + } else if (context.type === "mod") { + const parent = (this as any).parent + const actor = parent?.actor + const character = actor as M5Character + + calc.ids = {} + + switch (context.data.type as M5ModType) { + case M5ModType.ATTRIBUTE: { + for (const key of enumKeys(M5Attributes)) { + const val = M5Attributes[key] + calc.ids[key] = (game as Game).i18n.localize(`midgard5.actor-${val}-long`) + } + break + } + case M5ModType.STAT: { + for (const key of enumKeys(M5Stats)) { + const val = M5Stats[key] + calc.ids[key] = (game as Game).i18n.localize(`midgard5.mod-stat-${val}`) + } + break + } + case M5ModType.SKILL: { + if (character) { + const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true }) + if (actorCalc) { + let category = (game as Game).i18n.localize("midgard5.innate-ability") + Object.keys(actorCalc.skills.innate).forEach(skillId => { + const skill = character.getItem(skillId) + if (skill) + calc.ids[skillId] = `${category}: ${skill.data.name}` + }) + } + } + break + } + } + } else if (context.type === "item") { + calc.mods = {} + Object.keys(context.data?.mods).forEach(key => { + const mod = context.data.mods[key] + const modCalc = {} + switch (mod.type) { + case M5ModType.ATTRIBUTE: { + for (const key of enumKeys(M5Attributes)) { + const val = M5Attributes[key] + modCalc[key] = (game as Game).i18n.localize(`midgard5.actor-${val}-long`) + } + break + } + case M5ModType.STAT: { + for (const key of enumKeys(M5Stats)) { + const val = M5Stats[key] + modCalc[key] = (game as Game).i18n.localize(`midgard5.mod-stat-${val}`) + } + break + } + case M5ModType.SKILL: { + if (character) { + const actorCalc = character.derivedData({ weapons: true, defensiveWeapons: true, armor: true, items: true, spells: true }) + if (actorCalc) { + let category = (game as Game).i18n.localize("midgard5.innate-ability") + Object.keys(actorCalc.skills.innate).forEach(skillId => { + const skill = character.getItem(skillId) + if (skill) + modCalc[skillId] = `${category}: ${skill.data.name}` + }) + } + } + break + } + } + + calc.mods[key] = modCalc + }) } } @@ -128,6 +210,10 @@ export class M5Item extends Item { }) return null } - } + } + + getItem(itemId: string): any { + return (this as any).getEmbeddedDocument("Item", itemId) + } } diff --git a/source/module/sheets/M5CharacterSheet.ts b/source/module/sheets/M5CharacterSheet.ts index 3865655..8213fad 100644 --- a/source/module/sheets/M5CharacterSheet.ts +++ b/source/module/sheets/M5CharacterSheet.ts @@ -57,9 +57,26 @@ export default class M5CharacterSheet extends ActorSheet { const context = this.actor.data const item = context.items.get(itemId) + console.log("edit-item", item) item.sheet.render(true) }) + html.find(".item-delete").on("click", async (event) => { + let row = event.target.parentElement + let itemId = row.dataset["item"] + while (!itemId) { + row = row.parentElement + if (!row) + return + itemId = row.dataset["item"] + } + + const context = this.actor.data + const item = context.items.get(itemId) + item.delete() + this.render(false) + }) + html.find(".roll-learned-button").on("click", async (event) => { const row = event.target.parentElement.parentElement let skillId = row.dataset["item"] @@ -193,7 +210,6 @@ export default class M5CharacterSheet extends ActorSheet { if (!data.data) return false - if (data.actorId === this.actor.id) return false diff --git a/source/module/sheets/M5ItemSheet.ts b/source/module/sheets/M5ItemSheet.ts index d845dcb..ee9b42d 100644 --- a/source/module/sheets/M5ItemSheet.ts +++ b/source/module/sheets/M5ItemSheet.ts @@ -1,4 +1,5 @@ import { M5Item } from "../items/M5Item" +import { M5Attributes, M5ItemMod, M5ModOperation, M5ModType } from "../M5Base" export class M5ItemSheet extends ItemSheet { @@ -41,4 +42,108 @@ export class M5ItemSheet extends ItemSheet { }) } + override activateListeners(html: JQuery) { + super.activateListeners(html) + + html.find(".add-mod").on("click", async (event) => { + const context = this.object.data + const mods = context.data.mods + const modIndex = Object.keys(mods).length + mods[modIndex.toString()] = { + type: M5ModType.ATTRIBUTE, + id: M5Attributes.ST, + operation: M5ModOperation.ADD, + value: 0 + } as M5ItemMod + this.object.update({ + data: { + mods: mods + } + }) + }) + + html.find(".item-delete").on("click", async (event) => { + let row = event.target.parentElement + let itemId = row.dataset["item"] + while (!itemId) { + row = row.parentElement + if (!row) + return + itemId = row.dataset["item"] + } + + const context = this.item.data + const item = context.items.get(itemId) + item.delete() + this.render(false) + }) + + // Drag & Drop + if (["item"].includes(this.object.data?.type)) { + const itemToItemAssociation = new DragDrop({ + dragSelector: ".item", + dropSelector: null, + permissions: { dragstart: this._canDragStart.bind(this), drop: this._canDragDrop.bind(this) }, + callbacks: { drop: this._onDropItem.bind(this) }, + }) + itemToItemAssociation.bind(html[0]) + } + } + + _canDragStart(selector) { + console.log("M5ItemSheet._canDragStart", selector) + return this.options.editable && this.object.isOwner + } + + _canDragDrop(selector) { + console.log("M5ItemSheet._canDragDrop", selector) + return true + } + + async _onDropItem(event) { + let data + const obj = this.object + const li = event.currentTarget + + try { + data = JSON.parse(event.dataTransfer.getData("text/plain")) + if (data.type !== "Item") + return false + } catch (err) { + return false + } + + // Case 1 - Import from a Compendium pack + let itemObject + if (data.pack) { + const compendiumObject = await (this as any).importItemFromCollection(data.pack, data.id) + itemObject = compendiumObject.data + } + + // Case 2 - Import from World entity + else { + const originalItem = await (game as Game).items.get(data.id) + itemObject = duplicate(originalItem) + if (!itemObject) + return + } + + if ((itemObject.type === "mod")) { + let mods = obj?.data?.data?.mods + if (!mods) + mods = [] + + itemObject.id = randomID() + console.log("M5ItemSheet._onDropItem", itemObject) + mods.push(itemObject) + + obj.update({ + data: { + mods: mods + } + }) + } + } + + async _onDragItemStart(event) { } } diff --git a/source/style/Character-sheet.less b/source/style/Character-sheet.less index efc8ad1..1cc57d2 100644 --- a/source/style/Character-sheet.less +++ b/source/style/Character-sheet.less @@ -47,6 +47,11 @@ &.center { text-align: center; } + + &.fixed-value { + width: 3rem; + text-align: center; + } } input.skill { diff --git a/source/template.json b/source/template.json index 1323f32..133d4af 100644 --- a/source/template.json +++ b/source/template.json @@ -1,6 +1,6 @@ { "Actor": { - "types": ["npc", "character"], + "types": ["character"], "templates": { "characterDescription": { "info": { @@ -118,7 +118,7 @@ } }, "Item": { - "types": ["skill", "item", "weapon", "defensiveWeapon", "armor", "spell"], + "types": ["skill", "weapon", "defensiveWeapon", "armor", "spell", "item"], "templates": { "itemDescription": { "description": "" @@ -170,6 +170,10 @@ "equippable": false, "equipped": true }, + "physical": { + "value": 0, + "magic": false + }, "spellSelection": { "spellProcessSelection": { "none": "midgard5.spell-process-none", @@ -214,31 +218,33 @@ "0": { "formula": "1d20 + @i.fw + @i.calc.bonus", "type": "ew", - "label": "EW" + "label": "EW", + "enabled": true } }, "output": "" }, "calc": {} }, - "item": { - "templates": ["itemDescription", "stats", "equippable"], - "quantity": 1, + "mod": { + "type": "", + "id": "", + "operation": "", "value": 0, - "magic": false, - "onbody": false, - "attributes": {}, - "groups": {}, + "calc": {} + }, + "item": { + "templates": ["itemDescription", "equippable", "physical"], "rolls": { "formulas": {}, "output": "" }, + "mods": {}, "calc": {} }, "weapon": { - "templates": ["itemDescription", "stats", "equippable"], + "templates": ["itemDescription", "stats", "equippable", "physical"], "special": false, - "magic": false, "ranged": false, "skillId": "", "damageBase": "1d6", @@ -247,12 +253,14 @@ "0": { "formula": "1d20 + @i.calc.fw + @i.calc.bonus + @i.calc.special + @c.calc.stats.attackBonus + @i.stats.attackBonus", "type": "ew", - "label": "Angriff" + "label": "Angriff", + "enabled": true }, "1": { "formula": "@i.damageBase + @i.stats.damageBonus + @c.calc.stats.damageBonus", "type": "dmg", - "label": "Schaden" + "label": "Schaden", + "enabled": true } }, "output": "" @@ -260,16 +268,16 @@ "calc": {} }, "defensiveWeapon": { - "templates": ["itemDescription", "stats", "equippable"], + "templates": ["itemDescription", "stats", "equippable", "physical"], "special": false, - "magic": false, "skillId": "", "rolls": { "formulas": { "0": { "formula": "1d20 + @i.calc.fw + @i.calc.bonus + @i.calc.special + @c.calc.stats.defense + @c.calc.stats.defenseBonus + @i.stats.defenseBonus", "type": "ew", - "label": "Abwehr" + "label": "Abwehr", + "enabled": true } }, "output": "" @@ -277,8 +285,7 @@ "calc": {} }, "armor": { - "templates": ["itemDescription", "stats", "equippable", "attributeMod"], - "magic": false, + "templates": ["itemDescription", "stats", "equippable", "attributeMod", "physical"], "lpProtection": 0, "apProtection": 0, "rolls": { @@ -304,7 +311,8 @@ "0": { "formula": "1d20 + @c.calc.stats.spellCasting + @i.bonus", "type": "ew", - "label": "Zaubern" + "label": "Zaubern", + "enabled": true } }, "output": "" diff --git a/templates/sheets/character/gear.hbs b/templates/sheets/character/gear.hbs index 6f1d7f7..e204062 100644 --- a/templates/sheets/character/gear.hbs +++ b/templates/sheets/character/gear.hbs @@ -4,21 +4,24 @@ {{localize "ITEM.TypeWeapon"}} {{localize "midgard5.ew"}} + {{#each data.calc.gear.weapons as |item itemId|}} {{item.label}} - {{item.calc.ew}} - + {{item.calc.ew}} + + {{/each}} {{localize "midgard5.brawl"}} {{data.calc.stats.brawlEw}} - + + @@ -29,14 +32,16 @@ {{localize "ITEM.TypeDefensiveWeapon"}} {{localize "midgard5.ew"}} + {{#each data.calc.gear.defensiveWeapons as |item itemId|}} {{item.label}} - {{item.calc.ew}} - + {{item.calc.ew}} + + {{/each}} @@ -52,18 +57,20 @@ {{localize "midgard5.defenseBonus-short"}} B Gw + {{#each data.calc.gear.armor as |item itemId|}} {{item.label}} - {{actorItemValue ../actor._id itemId "lpProtection"}} - {{actorItemValue ../actor._id itemId "apProtection"}} - {{actorItemValue ../actor._id itemId "stats.attackBonus"}} - {{actorItemValue ../actor._id itemId "stats.defenseBonus"}} - {{actorItemValue ../actor._id itemId "stats.movementBonus"}} - {{actorItemValue ../actor._id itemId "attributeMod.gw"}} + {{actorItemValue ../actor._id itemId "lpProtection"}} + {{actorItemValue ../actor._id itemId "apProtection"}} + {{actorItemValue ../actor._id itemId "stats.attackBonus"}} + {{actorItemValue ../actor._id itemId "stats.defenseBonus"}} + {{actorItemValue ../actor._id itemId "stats.movementBonus"}} + {{actorItemValue ../actor._id itemId "attributeMod.gw"}} + {{/each}} diff --git a/templates/sheets/character/skills.hbs b/templates/sheets/character/skills.hbs index 98ba50e..2230429 100644 --- a/templates/sheets/character/skills.hbs +++ b/templates/sheets/character/skills.hbs @@ -6,16 +6,18 @@ {{localize "midgard5.bonus"}} {{localize "midgard5.ew"}} + {{#each data.calc.skills.general as |skill skillId|}} {{skill.label}} - {{skill.fw}} - {{skill.calc.bonus}} - {{skill.calc.ew}} - + {{skill.fw}} + {{skill.calc.bonus}} + {{skill.calc.ew}} + + {{/each}} @@ -29,16 +31,18 @@ {{localize "midgard5.bonus"}} {{localize "midgard5.ew"}} + {{#each data.calc.skills.language as |skill skillId|}} {{skill.label}} - {{skill.fw}} - {{skill.calc.bonus}} - {{skill.calc.ew}} - + {{skill.fw}} + {{skill.calc.bonus}} + {{skill.calc.ew}} + + {{/each}} @@ -52,16 +56,18 @@ {{localize "midgard5.bonus"}} {{localize "midgard5.ew"}} + {{#each data.calc.skills.innate as |skill skillId|}} {{skill.label}} - {{skill.fw}} - {{skill.calc.bonus}} - {{skill.calc.ew}} - + {{skill.fw}} + {{skill.calc.bonus}} + {{skill.calc.ew}} + + {{/each}} @@ -75,16 +81,18 @@ {{localize "midgard5.bonus"}} {{localize "midgard5.ew"}} + {{#each data.calc.skills.combat as |skill skillId|}} {{skill.label}} - {{skill.fw}} - {{skill.calc.bonus}} - {{skill.calc.ew}} - + {{skill.fw}} + {{skill.calc.bonus}} + {{skill.calc.ew}} + + {{/each}} @@ -99,6 +107,7 @@ {{localize "midgard5.bonus"}} {{localize "midgard5.ew"}} + @@ -106,10 +115,11 @@ {{localizeMidgard key}} - {{skill.fw}} - {{skillBonus ../actor._id skill}} - {{skillEw ../actor._id skill}} - + {{skill.fw}} + {{skillBonus ../actor._id skill}} + {{skillEw ../actor._id skill}} + + {{/each}} diff --git a/templates/sheets/character/spells.hbs b/templates/sheets/character/spells.hbs index 302e34a..46a5c44 100644 --- a/templates/sheets/character/spells.hbs +++ b/templates/sheets/character/spells.hbs @@ -4,6 +4,7 @@ {{localize "ITEM.TypeSpell"}} {{localize "midgard5.ew"}} + @@ -13,8 +14,9 @@ {{item.label}} {{localize item.process}} - {{item.calc.ew}} - + {{item.calc.ew}} +