diff --git a/assets/icons/official/combat-values/mana.png b/assets/icons/official/combat-values/mana.png new file mode 100644 index 00000000..4758ebc7 Binary files /dev/null and b/assets/icons/official/combat-values/mana.png differ diff --git a/lang/de.json b/lang/de.json index 2c0dfd5f..55518a83 100644 --- a/lang/de.json +++ b/lang/de.json @@ -149,6 +149,10 @@ "DS4.CooldownDuration100R": "100 Kampfrunden", "DS4.CooldownDuration1D": "1 Tag", "DS4.CooldownDurationD20D": "W20 Tage", + "DS4.ManaCost": "Manakosten", + "DS4.ManaCostDescription": "The Anzahl an Manapunkten, die es kostet, den Zauber zu wirken.", + "DS4.CalculateManaCost": "Manakosten Automatisch Berechnen", + "DS4.CalculateManaCostConfirmationQuestion": "

Die Manakosten des Zaubers werden automatisch an Hand der Tabelle auf Seite 19 der 5. Ausgabe der Slay! berechnet.

Achtung: Dieser Vorgang überschreibt die bestehenden Werte und kann nicht automatisch rückgängig gemacht werden.

", "DS4.SpellMinimumLevel": "Zugangsstufe", "DS4.SpellMinimumLevelDescription": "Die minimale Stufe, ab der ein Zauberwirker den Zauberspruch erlernen kann.", "DS4.SpellCasterClassHealer": "Heiler", @@ -189,6 +193,9 @@ "DS4.CombatValuesRangedAttack": "Schießen", "DS4.CombatValuesSpellcasting": "Zaubern", "DS4.CombatValuesTargetedSpellcasting": "Zielzaubern", + "DS4.CombatValuesMana": "Mana", + "DS4.CombatValuesManaCurrent": "Aktuelles Mana", + "DS4.CombatValuesManaCurrentAbbr": "MP", "DS4.CombatValuesHitPointsSheet": "Lebenskraft", "DS4.CombatValuesDefenseSheet": "Abwehr", "DS4.CombatValuesInitiativeSheet": "Initiative", @@ -197,6 +204,7 @@ "DS4.CombatValuesRangedAttackSheet": "Schießen", "DS4.CombatValuesSpellcastingSheet": "Zaubern", "DS4.CombatValuesTargetedSpellcastingSheet": "Zielzaubern", + "DS4.CombatValuesManaSheet": "Mana", "DS4.CharacterBaseInfoRace": "Volk", "DS4.CharacterBaseInfoClass": "Klasse", "DS4.CharacterBaseInfoHeroClass": "Heldenklasse", @@ -311,9 +319,11 @@ "DS4.TooltipModifier": "Modifikator", "DS4.TooltipEffects": "Effekte", "DS4.SettingUseSlayingDiceForAutomatedChecksName": "Slayende Würfel", - "DS4.SettingUseSlayingDiceForAutomatedChecksHint": "Benutze Slayende Würfel bei automatisierten Proben.", + "DS4.SettingUseSlayingDiceForAutomatedChecksHint": "Verwende Slayende Würfel bei automatisierten Proben.", "DS4.SettingShowSlayerPointsName": "Slayerpunkte", "DS4.SettingShowSlayerPointsHint": "Zeige Slayerpunkte im Charakterbogen an.", + "DS4.SettingUseManaSystemName": "Manasystem", + "DS4.SettingUseManaSystemHint": "Verwende das Manasystem für Zauber.", "DS4.Checks": "Proben", "DS4.ChecksAppraise": "Schätzen", "DS4.ChecksChangeSpell": "Zauber Wechseln", diff --git a/lang/en.json b/lang/en.json index 19973936..0b3d5b3b 100644 --- a/lang/en.json +++ b/lang/en.json @@ -149,6 +149,10 @@ "DS4.CooldownDuration100R": "100 Rounds", "DS4.CooldownDuration1D": "1 Day", "DS4.CooldownDurationD20D": "D20 Days", + "DS4.ManaCost": "Mana Cost", + "DS4.ManaCostDescription": "The amount of mana points casting the spell costs.", + "DS4.CalculateManaCost": "Automatically Calculate Mana Cost", + "DS4.CalculateManaCostConfirmationQuestion": "

The mana cost of the spell is automatically calculated using the table on page 19 of the 5th edition of the Slay!.

Warning: This process overwrites the existing values and connot be reverted automatically.

", "DS4.SpellMinimumLevel": "Minimum Level", "DS4.SpellMinimumLevelDescription": "The minimum level at which a spell caster may learn the spell.", "DS4.SpellCasterClassHealer": "Healer", @@ -189,6 +193,9 @@ "DS4.CombatValuesRangedAttack": "Ranged Attack", "DS4.CombatValuesSpellcasting": "Spellcasting", "DS4.CombatValuesTargetedSpellcasting": "Targeted Spellcasting", + "DS4.CombatValuesMana": "Mana", + "DS4.CombatValuesManaCurrent": "Current Mana", + "DS4.CombatValuesManaCurrentAbbr": "MP", "DS4.CombatValuesHitPointsSheet": "Hit Points", "DS4.CombatValuesDefenseSheet": "Defense", "DS4.CombatValuesInitiativeSheet": "Initiative", @@ -197,6 +204,7 @@ "DS4.CombatValuesRangedAttackSheet": "RAT", "DS4.CombatValuesSpellcastingSheet": "Spellcasting", "DS4.CombatValuesTargetedSpellcastingSheet": "TSC", + "DS4.CombatValuesManaSheet": "Mana", "DS4.CharacterBaseInfoRace": "Race", "DS4.CharacterBaseInfoClass": "Class", "DS4.CharacterBaseInfoHeroClass": "Hero Class", @@ -314,6 +322,8 @@ "DS4.SettingUseSlayingDiceForAutomatedChecksHint": "Use Slaying Dice for automated checks.", "DS4.SettingShowSlayerPointsName": "Slayer Points", "DS4.SettingShowSlayerPointsHint": "Show Slayer Points in the character sheet.", + "DS4.SettingUseManaSystemName": "Mana System", + "DS4.SettingUseManaSystemHint": "Use the Mana System for spells.", "DS4.Checks": "Checks", "DS4.ChecksAppraise": "Appraise", "DS4.ChecksChangeSpell": "Change Spell", diff --git a/package.json b/package.json index 76898f6f..61ba020f 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@commitlint/cli": "16.2.1", "@commitlint/config-conventional": "16.2.1", "@guanghechen/rollup-plugin-copy": "1.8.6", - "@league-of-foundry-developers/foundry-vtt-types": "9.249.3", + "@league-of-foundry-developers/foundry-vtt-types": "9.249.4", "@rollup/plugin-typescript": "8.3.0", "@seald-io/nedb": "2.2.1", "@types/fs-extra": "9.0.13", diff --git a/scss/components/actor/_combat_value.scss b/scss/components/actor/_combat_value.scss index 40e30685..5e5a12d9 100644 --- a/scss/components/actor/_combat_value.scss +++ b/scss/components/actor/_combat_value.scss @@ -14,7 +14,7 @@ place-items: center; row-gap: 0.125em; - &__value { + &__total { $combat-values-icons-path: "#{variables.$official-icons-path}/combat-values"; @include mixins.centered-content; @@ -49,6 +49,9 @@ &--targetedSpellcasting { background-image: url("#{$combat-values-icons-path}/targeted-spellcasting.png"); } + &--mana { + background-image: url("#{$combat-values-icons-path}/mana.png"); + } } &__label { diff --git a/scss/components/shared/_form_field_icon_button.scss b/scss/components/shared/_form_field_icon_button.scss new file mode 100644 index 00000000..685d56b2 --- /dev/null +++ b/scss/components/shared/_form_field_icon_button.scss @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2021 Johannes Loher + * + * SPDX-License-Identifier: MIT + */ + +.ds4-form-field-icon-button { + &__icon { + margin: 0; + } +} diff --git a/scss/components/shared/_form_field_input_extra_slim.scss b/scss/components/shared/_form_field_input_extra_slim.scss new file mode 100644 index 00000000..59594984 --- /dev/null +++ b/scss/components/shared/_form_field_input_extra_slim.scss @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2021 Johannes Loher + * + * SPDX-License-Identifier: MIT + */ + +.ds4-form-field-input-extra-slim { + &[type="number"], + &[type="text"] { + flex: 0 0 32px !important; // needs to be made more specific to override foundry's style + text-align: center; + } +} diff --git a/scss/ds4.scss b/scss/ds4.scss index acedd839..a6ebcc28 100644 --- a/scss/ds4.scss +++ b/scss/ds4.scss @@ -15,6 +15,8 @@ @use "components/shared/control_button_group"; @use "components/shared/editor"; @use "components/shared/embedded_document_list"; +@use "components/shared/form_field_icon_button"; +@use "components/shared/form_field_input_extra_slim"; @use "components/shared/rollable_image"; @use "components/shared/sheet_body"; @use "components/shared/sheet_form"; diff --git a/spec/item/spell/calculate-spell-price.spec.ts b/spec/item/spell/calculate-spell-price.spec.ts index 56d49f6d..0edb7533 100644 --- a/spec/item/spell/calculate-spell-price.spec.ts +++ b/spec/item/spell/calculate-spell-price.spec.ts @@ -25,6 +25,11 @@ const defaultData: DS4SpellDataSourceData = { unit: "custom", }, cooldownDuration: "0r", + manaCost: { + healer: null, + wizard: null, + sorcerer: null, + }, minimumLevels: { healer: null, wizard: null, diff --git a/src/actor/actor-data-properties-base.ts b/src/actor/actor-data-properties-base.ts index 9aa9e335..7650b32d 100644 --- a/src/actor/actor-data-properties-base.ts +++ b/src/actor/actor-data-properties-base.ts @@ -24,7 +24,7 @@ type DS4ActorDataPropertiesDataAttributes = { type DS4ActorDataPropertiesDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBaseTotal }; type DS4ActorDataPropertiesDataCombatValues = { - [Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints" + [Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints" | "mana" ? ResourceDataBaseTotalMax : ModifiableDataBaseTotal; }; diff --git a/src/actor/actor.ts b/src/actor/actor.ts index ecad7f40..396fcf39 100644 --- a/src/actor/actor.ts +++ b/src/actor/actor.ts @@ -47,12 +47,12 @@ export class DS4Actor extends Actor { const attributes = data.data.attributes; Object.values(attributes).forEach( - (attribute: ModifiableDataBaseTotal) => (attribute.total = attribute.base + attribute.mod), + (attribute: ModifiableDataBaseTotal) => (attribute.total = attribute.base + (attribute.mod ?? 0)), ); const traits = data.data.traits; Object.values(traits).forEach( - (trait: ModifiableDataBaseTotal) => (trait.total = trait.base + trait.mod), + (trait: ModifiableDataBaseTotal) => (trait.total = trait.base + (trait.mod ?? 0)), ); } @@ -158,6 +158,8 @@ export class DS4Actor extends Actor { }); this.data.data.combatValues.hitPoints.max = this.data.data.combatValues.hitPoints.total; + this.data.data.combatValues.mana.max = this.data.data.combatValues.mana.total; + this.data.data.checks.defend = this.data.data.combatValues.defense.total; } @@ -166,7 +168,7 @@ export class DS4Actor extends Actor { * given in dot notation. */ get finalDerivedDataProperties(): string[] { - return ["data.combatValues.hitPoints.max", "data.checks.defend"]; + return ["data.combatValues.hitPoints.max", "data.combatValues.mana.max", "data.checks.defend"]; } /** @@ -203,9 +205,11 @@ export class DS4Actor extends Actor { data.attributes.mind.total + data.traits.aura.total - spellMalusOfEquippedItems; data.combatValues.targetedSpellcasting.base = data.attributes.mind.total + data.traits.dexterity.total - spellMalusOfEquippedItems; + data.combatValues.mana.base = data.attributes.mind.total + data.traits.aura.total; Object.values(data.combatValues).forEach( - (combatValue: ModifiableDataBaseTotal) => (combatValue.total = combatValue.base + combatValue.mod), + (combatValue: ModifiableDataBaseTotal) => + (combatValue.total = combatValue.base + (combatValue.mod ?? 0)), ); } diff --git a/src/actor/character/character.ts b/src/actor/character/character.ts index b8dd87a6..efa72092 100644 --- a/src/actor/character/character.ts +++ b/src/actor/character/character.ts @@ -22,6 +22,13 @@ export class DS4Character extends DS4Actor { get ownableItemTypes(): Array { return [...super.ownableItemTypes, "talent", "racialAbility", "language", "alphabet"]; } + + /** @override */ + protected prepareCombatValues(): void { + super.prepareCombatValues(); + this.data.data.combatValues.mana.base += this.data.data.progression.level; + this.data.data.combatValues.mana.total += this.data.data.progression.level; + } } export interface DS4Character { diff --git a/src/actor/creature/creature.ts b/src/actor/creature/creature.ts index f6ae06a7..9b91e9a6 100644 --- a/src/actor/creature/creature.ts +++ b/src/actor/creature/creature.ts @@ -11,6 +11,13 @@ export class DS4Creature extends DS4Actor { get ownableItemTypes(): Array { return [...super.ownableItemTypes, "specialCreatureAbility"]; } + + /** @override */ + protected prepareCombatValues(): void { + super.prepareCombatValues(); + this.data.data.combatValues.mana.base += this.data.data.baseInfo.foeFactor; + this.data.data.combatValues.mana.total += this.data.data.baseInfo.foeFactor; + } } export interface DS4Creature { diff --git a/src/common/common-data.ts b/src/common/common-data.ts index c2c1a527..3b3ae642 100644 --- a/src/common/common-data.ts +++ b/src/common/common-data.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT export interface ModifiableData { - mod: T; + mod?: T | null | undefined; } export interface HasBase { diff --git a/src/config.ts b/src/config.ts index 76c206ca..1f82bbe3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -158,10 +158,11 @@ const i18nKeys = { rangedAttack: "DS4.CombatValuesRangedAttack", spellcasting: "DS4.CombatValuesSpellcasting", targetedSpellcasting: "DS4.CombatValuesTargetedSpellcasting", + mana: "DS4.CombatValuesMana", }, /** - * The what do display in the actor sheets for the combat value text (in some languages, abbreviations are necessary) + * The what to display in the actor sheets for the combat value text (in some languages, abbreviations are necessary) */ combatValuesSheet: { hitPoints: "DS4.CombatValuesHitPointsSheet", @@ -172,6 +173,7 @@ const i18nKeys = { rangedAttack: "DS4.CombatValuesRangedAttackSheet", spellcasting: "DS4.CombatValuesSpellcastingSheet", targetedSpellcasting: "DS4.CombatValuesTargetedSpellcastingSheet", + mana: "DS4.CombatValuesManaSheet", }, /** diff --git a/src/global.d.ts b/src/global.d.ts index e557d128..4a6f1914 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -12,6 +12,7 @@ declare global { "ds4.showSlayerPoints": boolean; "ds4.classes": Classes; "ds4.heroClasses": HeroClasses; + "ds4.useManaSystem": boolean; } } diff --git a/src/handlebars/handlebars-partials.ts b/src/handlebars/handlebars-partials.ts index 0a0ca83e..5a9f1a23 100644 --- a/src/handlebars/handlebars-partials.ts +++ b/src/handlebars/handlebars-partials.ts @@ -50,6 +50,7 @@ export default async function registerHandlebarsPartials(): Promise { "systems/ds4/templates/sheets/item/tabs/properties.hbs", "systems/ds4/templates/sheets/shared/components/add-button.hbs", "systems/ds4/templates/sheets/shared/components/control-button-group.hbs", + "systems/ds4/templates/sheets/shared/components/form-field-icon-button.hbs", "systems/ds4/templates/sheets/shared/components/rollable-image.hbs", ]; await loadTemplates(templatePaths); diff --git a/src/hooks/init.ts b/src/hooks/init.ts index 0aa55ef3..916ffd33 100644 --- a/src/hooks/init.ts +++ b/src/hooks/init.ts @@ -16,6 +16,7 @@ import registerHandlebarsPartials from "../handlebars/handlebars-partials"; import { getGame } from "../helpers"; import { DS4ItemSheet } from "../item/item-sheet"; import { DS4ItemProxy } from "../item/proxy"; +import { DS4SpellSheet } from "../item/spell/spell-sheet"; import logger from "../logger"; import { macros } from "../macros/macros"; import { migration } from "../migrations"; @@ -69,7 +70,25 @@ async function init() { Actors.registerSheet("ds4", DS4CharacterSheet, { types: ["character"], makeDefault: true }); Actors.registerSheet("ds4", DS4CreatureSheet, { types: ["creature"], makeDefault: true }); Items.unregisterSheet("core", ItemSheet); - Items.registerSheet("ds4", DS4ItemSheet, { makeDefault: true }); + Items.registerSheet("ds4", DS4ItemSheet, { + types: [ + "weapon", + "armor", + "shield", + "equipment", + "loot", + "talent", + "racialAbility", + "language", + "alphabet", + "specialCreatureAbility", + ], + makeDefault: true, + }); + Items.registerSheet("ds4", DS4SpellSheet, { + types: ["spell"], + makeDefault: true, + }); preloadFonts(); await registerHandlebarsPartials(); diff --git a/src/item/item-sheet.ts b/src/item/item-sheet.ts index 245b1cfe..cfa49479 100644 --- a/src/item/item-sheet.ts +++ b/src/item/item-sheet.ts @@ -7,6 +7,7 @@ import { DS4ActiveEffect } from "../active-effect"; import { DS4 } from "../config"; import { getGame } from "../helpers"; +import { DS4Settings, getDS4Settings } from "../settings"; import notifications from "../ui/notifications"; import { enforce } from "../utils"; import { isDS4ItemDataTypePhysical } from "./item-data-source-base"; @@ -40,6 +41,7 @@ export class DS4ItemSheet extends ItemSheet isOwned: this.item.isOwned, actor: this.item.actor, isPhysical: isDS4ItemDataTypePhysical(this.item.data.data), + settings: getDS4Settings(), }; return data; } @@ -128,6 +130,7 @@ interface DS4ItemSheetData extends ItemSheet.Data { isOwned: boolean; actor: DS4ItemSheet["item"]["actor"]; isPhysical: boolean; + settings: DS4Settings; } /** diff --git a/src/item/spell/calculate-mana-const.ts b/src/item/spell/calculate-mana-const.ts new file mode 100644 index 00000000..3adaabb3 --- /dev/null +++ b/src/item/spell/calculate-mana-const.ts @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2022 Johannes Loher +// +// SPDX-License-Identifier: MIT + +import type { CooldownDuration, DS4SpellDataSourceData } from "./spell-data-source"; + +type Level = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20; + +export function calculateManaCost({ + minimumLevels, + cooldownDuration, +}: DS4SpellDataSourceData): DS4SpellDataSourceData["manaCost"] { + // prettier-ignore + const magicalManaTable: Record> = { + 1: { "0r": 1, "1r": 2, "2r": 2, "5r": 2, "10r": 2, "100r": 3, "1d": 4, "d20d": 5 }, + 2: { "0r": 1, "1r": 2, "2r": 2, "5r": 2, "10r": 3, "100r": 3, "1d": 4, "d20d": 5 }, + 3: { "0r": 2, "1r": 2, "2r": 2, "5r": 3, "10r": 3, "100r": 4, "1d": 5, "d20d": 6 }, + 4: { "0r": 3, "1r": 3, "2r": 3, "5r": 4, "10r": 4, "100r": 5, "1d": 6, "d20d": 7 }, + 5: { "0r": 4, "1r": 4, "2r": 4, "5r": 5, "10r": 5, "100r": 6, "1d": 7, "d20d": 8 }, + 6: { "0r": 4, "1r": 5, "2r": 5, "5r": 6, "10r": 6, "100r": 7, "1d": 7, "d20d": 8 }, + 7: { "0r": 5, "1r": 6, "2r": 6, "5r": 7, "10r": 7, "100r": 7, "1d": 8, "d20d": 9 }, + 8: { "0r": 6, "1r": 6, "2r": 7, "5r": 7, "10r": 7, "100r": 8, "1d": 8, "d20d": 9 }, + 9: { "0r": 7, "1r": 7, "2r": 7, "5r": 8, "10r": 8, "100r": 8, "1d": 9, "d20d": 10 }, + 10: { "0r": 7, "1r": 8, "2r": 8, "5r": 8, "10r": 9, "100r": 8, "1d": 10, "d20d": 11 }, + 11: { "0r": 8, "1r": 8, "2r": 9, "5r": 9, "10r": 9, "100r": 10, "1d": 10, "d20d": 11 }, + 12: { "0r": 9, "1r": 9, "2r": 9, "5r": 9, "10r": 10, "100r": 11, "1d": 11, "d20d": 12 }, + 13: { "0r": 10, "1r": 10, "2r": 10, "5r": 10, "10r": 11, "100r": 12, "1d": 12, "d20d": 13 }, + 14: { "0r": 10, "1r": 11, "2r": 11, "5r": 12, "10r": 12, "100r": 13, "1d": 13, "d20d": 14 }, + 15: { "0r": 11, "1r": 12, "2r": 12, "5r": 12, "10r": 12, "100r": 13, "1d": 14, "d20d": 15 }, + 16: { "0r": 12, "1r": 12, "2r": 12, "5r": 12, "10r": 13, "100r": 14, "1d": 15, "d20d": 15 }, + 17: { "0r": 12, "1r": 13, "2r": 13, "5r": 13, "10r": 14, "100r": 15, "1d": 16, "d20d": 16 }, + 18: { "0r": 13, "1r": 14, "2r": 14, "5r": 14, "10r": 14, "100r": 15, "1d": 16, "d20d": 17 }, + 19: { "0r": 14, "1r": 15, "2r": 15, "5r": 15, "10r": 15, "100r": 15, "1d": 16, "d20d": 17 }, + 20: { "0r": 14, "1r": 15, "2r": 15, "5r": 16, "10r": 16, "100r": 16, "1d": 17, "d20d": 18 }, + }; + + const getManaCostForSpellcasterClass = (spellcasterClass: keyof DS4SpellDataSourceData["manaCost"]) => { + const minimumLevel = minimumLevels[spellcasterClass]; + return minimumLevel !== null + ? magicalManaTable[Math.clamped(minimumLevel, 1, 20) as Level][cooldownDuration] + : null; + }; + + return { + healer: getManaCostForSpellcasterClass("healer"), + wizard: getManaCostForSpellcasterClass("wizard"), + sorcerer: getManaCostForSpellcasterClass("sorcerer"), + }; +} diff --git a/src/item/spell/spell-data-source.ts b/src/item/spell/spell-data-source.ts index 7c578dc9..0675115b 100644 --- a/src/item/spell/spell-data-source.ts +++ b/src/item/spell/spell-data-source.ts @@ -18,6 +18,11 @@ export interface DS4SpellDataSourceData extends DS4ItemDataSourceDataBase, DS4It effectRadius: UnitData; duration: UnitData; cooldownDuration: CooldownDuration; + manaCost: { + healer: number | null; + wizard: number | null; + sorcerer: number | null; + }; minimumLevels: { healer: number | null; wizard: number | null; diff --git a/src/item/spell/spell-sheet.ts b/src/item/spell/spell-sheet.ts new file mode 100644 index 00000000..e334eb59 --- /dev/null +++ b/src/item/spell/spell-sheet.ts @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2021 Johannes Loher +// +// SPDX-License-Identifier: MIT + +import { getGame } from "../../helpers"; +import { DS4ItemSheet } from "../item-sheet"; +import { calculateManaCost } from "./calculate-mana-const"; + +import type { DS4Item } from "../item"; + +export class DS4SpellSheet extends DS4ItemSheet { + /** @override */ + activateListeners(html: JQuery): void { + super.activateListeners(html); + + if (!this.options.editable) return; + + html.find(".ds4-calculate-mana-cost").on("click", this.onCalculateManaCost.bind(this)); + } + + /** Calculates the mana cost of the spell automatically. */ + protected onCalculateManaCost(): void { + const game = getGame(); + const manaCost = calculateManaCost(this.item.data.data); + Dialog.confirm({ + title: game.i18n.localize("DS4.CalculateManaCost"), + content: game.i18n.localize("DS4.CalculateManaCostConfirmationQuestion"), + yes: () => this.item.update({ data: { manaCost } }), + }); + } +} + +export interface DS4SpellSheet { + object: DS4Item & { data: { type: "spell"; _source: { type: "spell" } } }; +} diff --git a/src/item/talent/talent.ts b/src/item/talent/talent.ts index dea5782f..2133c84b 100644 --- a/src/item/talent/talent.ts +++ b/src/item/talent/talent.ts @@ -9,7 +9,7 @@ export class DS4Talent extends DS4Item { prepareDerivedData(): void { super.prepareDerivedData(); const data = this.data.data; - data.rank.total = data.rank.base + data.rank.mod; + data.rank.total = data.rank.base + (data.rank.mod ?? 0); } /** @override */ diff --git a/src/settings.ts b/src/settings.ts index bf52e439..d48fcf7b 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -58,6 +58,16 @@ export function registerSystemSettings(): void { restricted: true, type: ClassConfig, }); + + game.settings.register("ds4", "useManaSystem", { + name: "DS4.SettingUseManaSystemName", + hint: "DS4.SettingUseManaSystemHint", + scope: "world", + config: true, + type: Boolean, + default: false, + onChange: renderActorAndSpellSheets, + }); } export interface DS4Settings { @@ -66,6 +76,7 @@ export interface DS4Settings { showSlayerPoints: boolean; classes: Classes; heroClasses: HeroClasses; + useManaSystem: boolean; } export function getDS4Settings(): DS4Settings { @@ -76,6 +87,7 @@ export function getDS4Settings(): DS4Settings { showSlayerPoints: game.settings.get("ds4", "showSlayerPoints"), classes: game.settings.get("ds4", "classes"), heroClasses: game.settings.get("ds4", "heroClasses"), + useManaSystem: game.settings.get("ds4", "useManaSystem"), }; } diff --git a/template.json b/template.json index 64fac089..b6df7e79 100644 --- a/template.json +++ b/template.json @@ -68,6 +68,10 @@ }, "targetedSpellcasting": { "mod": 0 + }, + "mana": { + "mod": 0, + "value": 0 } } } @@ -188,6 +192,11 @@ "unit": "custom" }, "cooldownDuration": "0r", + "manaCost": { + "healer": null, + "wizard": null, + "sorcerer": null + }, "minimumLevels": { "healer": null, "wizard": null, diff --git a/templates/sheets/actor/components/actor-progression.hbs b/templates/sheets/actor/components/actor-progression.hbs index bfb3a860..ee438152 100644 --- a/templates/sheets/actor/components/actor-progression.hbs +++ b/templates/sheets/actor/components/actor-progression.hbs @@ -16,6 +16,18 @@ SPDX-License-Identifier: MIT id="data.combatValues.hitPoints.value-{{data._id}}" value="{{data.data.combatValues.hitPoints.value}}" data-dtype="Number" /> + {{!-- TODO: Find a better place for this --}} + {{#if settings.useManaSystem}} +
+

+

+ +
+ {{/if}} {{#if (eq data.type "character")}} {{#if settings.showSlayerPoints}}
diff --git a/templates/sheets/actor/components/combat-value.hbs b/templates/sheets/actor/components/combat-value.hbs index 3037f92d..e23b702f 100644 --- a/templates/sheets/actor/components/combat-value.hbs +++ b/templates/sheets/actor/components/combat-value.hbs @@ -15,7 +15,7 @@ SPDX-License-Identifier: MIT --}}
-
{{combat-value-data.total}}
diff --git a/templates/sheets/actor/components/combat-values.hbs b/templates/sheets/actor/components/combat-values.hbs index 63ebe6e6..d4aa1707 100644 --- a/templates/sheets/actor/components/combat-values.hbs +++ b/templates/sheets/actor/components/combat-values.hbs @@ -7,9 +7,11 @@ SPDX-License-Identifier: MIT
{{#each config.i18n.combatValues as |combat-value-title combat-value-key|}} + {{#unless (and (eq combat-value-key "mana") (not ../settings.useManaSystem)) }} {{> systems/ds4/templates/sheets/actor/components/combat-value.hbs combat-value-key=combat-value-key combat-value-data=(lookup ../data.data.combatValues combat-value-key) combat-value-label=(lookup ../config.i18n.combatValuesSheet combat-value-key) combat-value-title=combat-value-title actor-id=../data._id}} + {{/unless}} {{/each}}
diff --git a/templates/sheets/item/components/properties/spell.hbs b/templates/sheets/item/components/properties/spell.hbs index eb48d9f2..455e0b1d 100644 --- a/templates/sheets/item/components/properties/spell.hbs +++ b/templates/sheets/item/components/properties/spell.hbs @@ -94,18 +94,42 @@ SPDX-License-Identifier: MIT
+ {{#if settings.useManaSystem}} +
+ +
+ {{> systems/ds4/templates/sheets/shared/components/form-field-icon-button.hbs + class="ds4-calculate-mana-cost" title="DS4.CalculateManaCost" faClasses="fas fa-magic"}} + + + + + + +
+
+ {{/if}}
- + - + - +
diff --git a/templates/sheets/shared/components/form-field-icon-button.hbs b/templates/sheets/shared/components/form-field-icon-button.hbs new file mode 100644 index 00000000..605cf322 --- /dev/null +++ b/templates/sheets/shared/components/form-field-icon-button.hbs @@ -0,0 +1,16 @@ +{{!-- +SPDX-FileCopyrightText: 2021 Johannes Loher + +SPDX-License-Identifier: MIT +--}} + +{{! +!-- Render a form-field icon button. +!-- @param class: A class to identify the button by +!-- @param title: The title to use for the button (will be localized) +!-- @param faClasses: The Fontawesom classes to use for the icon +}} + + diff --git a/yarn.lock b/yarn.lock index 2dda5023..f0476f29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -892,9 +892,9 @@ __metadata: languageName: node linkType: hard -"@league-of-foundry-developers/foundry-vtt-types@npm:9.249.3": - version: 9.249.3 - resolution: "@league-of-foundry-developers/foundry-vtt-types@npm:9.249.3" +"@league-of-foundry-developers/foundry-vtt-types@npm:9.249.4": + version: 9.249.4 + resolution: "@league-of-foundry-developers/foundry-vtt-types@npm:9.249.4" dependencies: "@types/jquery": ~3.5.9 "@types/simple-peer": ~9.11.1 @@ -903,7 +903,7 @@ __metadata: pixi.js: 5.3.11 socket.io-client: 4.3.2 tinymce: 5.10.1 - checksum: 13bbcafd57637a59f44ee136e5edd78b1fedb3f0327027bd2bae8971f2eed6ad5ad84755cebc32e4b08f788e0bf5c08dd06cbe4709ae89746198658ebb511451 + checksum: 612721d98bb6e8496d8000e86619615b2ae968ecb643273545c1a73c7d2faeca611f6d3adbc84b99cdf47b706a604226582731e0be560701a801a6a23bb75cae languageName: node linkType: hard @@ -3262,7 +3262,7 @@ __metadata: "@commitlint/cli": 16.2.1 "@commitlint/config-conventional": 16.2.1 "@guanghechen/rollup-plugin-copy": 1.8.6 - "@league-of-foundry-developers/foundry-vtt-types": 9.249.3 + "@league-of-foundry-developers/foundry-vtt-types": 9.249.4 "@rollup/plugin-typescript": 8.3.0 "@seald-io/nedb": 2.2.1 "@types/fs-extra": 9.0.13