feat: wip on adding support for mana system

This commit is contained in:
Johannes Loher 2022-02-14 03:25:05 +01:00
parent bdc454d7c0
commit be0c401aa3
31 changed files with 286 additions and 24 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -149,6 +149,10 @@
"DS4.CooldownDuration100R": "100 Kampfrunden", "DS4.CooldownDuration100R": "100 Kampfrunden",
"DS4.CooldownDuration1D": "1 Tag", "DS4.CooldownDuration1D": "1 Tag",
"DS4.CooldownDurationD20D": "W20 Tage", "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": "<p>Die Manakosten des Zaubers werden automatisch an Hand der Tabelle auf Seite 19 der <a href=\"http://dungeonslayers.net/download/Slay05.pdf\">5. Ausgabe der Slay!</a> berechnet.</p><p><strong>Achtung:</strong> Dieser Vorgang überschreibt die bestehenden Werte und kann nicht automatisch rückgängig gemacht werden.</p>",
"DS4.SpellMinimumLevel": "Zugangsstufe", "DS4.SpellMinimumLevel": "Zugangsstufe",
"DS4.SpellMinimumLevelDescription": "Die minimale Stufe, ab der ein Zauberwirker den Zauberspruch erlernen kann.", "DS4.SpellMinimumLevelDescription": "Die minimale Stufe, ab der ein Zauberwirker den Zauberspruch erlernen kann.",
"DS4.SpellCasterClassHealer": "Heiler", "DS4.SpellCasterClassHealer": "Heiler",
@ -189,6 +193,9 @@
"DS4.CombatValuesRangedAttack": "Schießen", "DS4.CombatValuesRangedAttack": "Schießen",
"DS4.CombatValuesSpellcasting": "Zaubern", "DS4.CombatValuesSpellcasting": "Zaubern",
"DS4.CombatValuesTargetedSpellcasting": "Zielzaubern", "DS4.CombatValuesTargetedSpellcasting": "Zielzaubern",
"DS4.CombatValuesMana": "Mana",
"DS4.CombatValuesManaCurrent": "Aktuelles Mana",
"DS4.CombatValuesManaCurrentAbbr": "MP",
"DS4.CombatValuesHitPointsSheet": "Lebenskraft", "DS4.CombatValuesHitPointsSheet": "Lebenskraft",
"DS4.CombatValuesDefenseSheet": "Abwehr", "DS4.CombatValuesDefenseSheet": "Abwehr",
"DS4.CombatValuesInitiativeSheet": "Initiative", "DS4.CombatValuesInitiativeSheet": "Initiative",
@ -197,6 +204,7 @@
"DS4.CombatValuesRangedAttackSheet": "Schießen", "DS4.CombatValuesRangedAttackSheet": "Schießen",
"DS4.CombatValuesSpellcastingSheet": "Zaubern", "DS4.CombatValuesSpellcastingSheet": "Zaubern",
"DS4.CombatValuesTargetedSpellcastingSheet": "Zielzaubern", "DS4.CombatValuesTargetedSpellcastingSheet": "Zielzaubern",
"DS4.CombatValuesManaSheet": "Mana",
"DS4.CharacterBaseInfoRace": "Volk", "DS4.CharacterBaseInfoRace": "Volk",
"DS4.CharacterBaseInfoClass": "Klasse", "DS4.CharacterBaseInfoClass": "Klasse",
"DS4.CharacterBaseInfoHeroClass": "Heldenklasse", "DS4.CharacterBaseInfoHeroClass": "Heldenklasse",
@ -311,9 +319,11 @@
"DS4.TooltipModifier": "Modifikator", "DS4.TooltipModifier": "Modifikator",
"DS4.TooltipEffects": "Effekte", "DS4.TooltipEffects": "Effekte",
"DS4.SettingUseSlayingDiceForAutomatedChecksName": "Slayende Würfel", "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.SettingShowSlayerPointsName": "Slayerpunkte",
"DS4.SettingShowSlayerPointsHint": "Zeige Slayerpunkte im Charakterbogen an.", "DS4.SettingShowSlayerPointsHint": "Zeige Slayerpunkte im Charakterbogen an.",
"DS4.SettingUseManaSystemName": "Manasystem",
"DS4.SettingUseManaSystemHint": "Verwende das Manasystem für Zauber.",
"DS4.Checks": "Proben", "DS4.Checks": "Proben",
"DS4.ChecksAppraise": "Schätzen", "DS4.ChecksAppraise": "Schätzen",
"DS4.ChecksChangeSpell": "Zauber Wechseln", "DS4.ChecksChangeSpell": "Zauber Wechseln",

View file

@ -149,6 +149,10 @@
"DS4.CooldownDuration100R": "100 Rounds", "DS4.CooldownDuration100R": "100 Rounds",
"DS4.CooldownDuration1D": "1 Day", "DS4.CooldownDuration1D": "1 Day",
"DS4.CooldownDurationD20D": "D20 Days", "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": "<p>The mana cost of the spell is automatically calculated using the table on page 19 of the <a href=\"http://dungeonslayers.net/download/Slay05.pdf\">5th edition of the Slay!</a>.</p><p><strong>Warning:</strong> This process overwrites the existing values and connot be reverted automatically.</p>",
"DS4.SpellMinimumLevel": "Minimum Level", "DS4.SpellMinimumLevel": "Minimum Level",
"DS4.SpellMinimumLevelDescription": "The minimum level at which a spell caster may learn the spell.", "DS4.SpellMinimumLevelDescription": "The minimum level at which a spell caster may learn the spell.",
"DS4.SpellCasterClassHealer": "Healer", "DS4.SpellCasterClassHealer": "Healer",
@ -189,6 +193,9 @@
"DS4.CombatValuesRangedAttack": "Ranged Attack", "DS4.CombatValuesRangedAttack": "Ranged Attack",
"DS4.CombatValuesSpellcasting": "Spellcasting", "DS4.CombatValuesSpellcasting": "Spellcasting",
"DS4.CombatValuesTargetedSpellcasting": "Targeted Spellcasting", "DS4.CombatValuesTargetedSpellcasting": "Targeted Spellcasting",
"DS4.CombatValuesMana": "Mana",
"DS4.CombatValuesManaCurrent": "Current Mana",
"DS4.CombatValuesManaCurrentAbbr": "MP",
"DS4.CombatValuesHitPointsSheet": "Hit Points", "DS4.CombatValuesHitPointsSheet": "Hit Points",
"DS4.CombatValuesDefenseSheet": "Defense", "DS4.CombatValuesDefenseSheet": "Defense",
"DS4.CombatValuesInitiativeSheet": "Initiative", "DS4.CombatValuesInitiativeSheet": "Initiative",
@ -197,6 +204,7 @@
"DS4.CombatValuesRangedAttackSheet": "RAT", "DS4.CombatValuesRangedAttackSheet": "RAT",
"DS4.CombatValuesSpellcastingSheet": "Spellcasting", "DS4.CombatValuesSpellcastingSheet": "Spellcasting",
"DS4.CombatValuesTargetedSpellcastingSheet": "TSC", "DS4.CombatValuesTargetedSpellcastingSheet": "TSC",
"DS4.CombatValuesManaSheet": "Mana",
"DS4.CharacterBaseInfoRace": "Race", "DS4.CharacterBaseInfoRace": "Race",
"DS4.CharacterBaseInfoClass": "Class", "DS4.CharacterBaseInfoClass": "Class",
"DS4.CharacterBaseInfoHeroClass": "Hero Class", "DS4.CharacterBaseInfoHeroClass": "Hero Class",
@ -314,6 +322,8 @@
"DS4.SettingUseSlayingDiceForAutomatedChecksHint": "Use Slaying Dice for automated checks.", "DS4.SettingUseSlayingDiceForAutomatedChecksHint": "Use Slaying Dice for automated checks.",
"DS4.SettingShowSlayerPointsName": "Slayer Points", "DS4.SettingShowSlayerPointsName": "Slayer Points",
"DS4.SettingShowSlayerPointsHint": "Show Slayer Points in the character sheet.", "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.Checks": "Checks",
"DS4.ChecksAppraise": "Appraise", "DS4.ChecksAppraise": "Appraise",
"DS4.ChecksChangeSpell": "Change Spell", "DS4.ChecksChangeSpell": "Change Spell",

View file

@ -61,7 +61,7 @@
"@commitlint/cli": "16.2.1", "@commitlint/cli": "16.2.1",
"@commitlint/config-conventional": "16.2.1", "@commitlint/config-conventional": "16.2.1",
"@guanghechen/rollup-plugin-copy": "1.8.6", "@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", "@rollup/plugin-typescript": "8.3.0",
"@seald-io/nedb": "2.2.1", "@seald-io/nedb": "2.2.1",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",

View file

@ -14,7 +14,7 @@
place-items: center; place-items: center;
row-gap: 0.125em; row-gap: 0.125em;
&__value { &__total {
$combat-values-icons-path: "#{variables.$official-icons-path}/combat-values"; $combat-values-icons-path: "#{variables.$official-icons-path}/combat-values";
@include mixins.centered-content; @include mixins.centered-content;
@ -49,6 +49,9 @@
&--targetedSpellcasting { &--targetedSpellcasting {
background-image: url("#{$combat-values-icons-path}/targeted-spellcasting.png"); background-image: url("#{$combat-values-icons-path}/targeted-spellcasting.png");
} }
&--mana {
background-image: url("#{$combat-values-icons-path}/mana.png");
}
} }
&__label { &__label {

View file

@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2021 Johannes Loher
*
* SPDX-License-Identifier: MIT
*/
.ds4-form-field-icon-button {
&__icon {
margin: 0;
}
}

View file

@ -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;
}
}

View file

@ -15,6 +15,8 @@
@use "components/shared/control_button_group"; @use "components/shared/control_button_group";
@use "components/shared/editor"; @use "components/shared/editor";
@use "components/shared/embedded_document_list"; @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/rollable_image";
@use "components/shared/sheet_body"; @use "components/shared/sheet_body";
@use "components/shared/sheet_form"; @use "components/shared/sheet_form";

View file

@ -25,6 +25,11 @@ const defaultData: DS4SpellDataSourceData = {
unit: "custom", unit: "custom",
}, },
cooldownDuration: "0r", cooldownDuration: "0r",
manaCost: {
healer: null,
wizard: null,
sorcerer: null,
},
minimumLevels: { minimumLevels: {
healer: null, healer: null,
wizard: null, wizard: null,

View file

@ -24,7 +24,7 @@ type DS4ActorDataPropertiesDataAttributes = {
type DS4ActorDataPropertiesDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBaseTotal<number> }; type DS4ActorDataPropertiesDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBaseTotal<number> };
type DS4ActorDataPropertiesDataCombatValues = { type DS4ActorDataPropertiesDataCombatValues = {
[Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints" [Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints" | "mana"
? ResourceDataBaseTotalMax<number> ? ResourceDataBaseTotalMax<number>
: ModifiableDataBaseTotal<number>; : ModifiableDataBaseTotal<number>;
}; };

View file

@ -47,12 +47,12 @@ export class DS4Actor extends Actor {
const attributes = data.data.attributes; const attributes = data.data.attributes;
Object.values(attributes).forEach( Object.values(attributes).forEach(
(attribute: ModifiableDataBaseTotal<number>) => (attribute.total = attribute.base + attribute.mod), (attribute: ModifiableDataBaseTotal<number>) => (attribute.total = attribute.base + (attribute.mod ?? 0)),
); );
const traits = data.data.traits; const traits = data.data.traits;
Object.values(traits).forEach( Object.values(traits).forEach(
(trait: ModifiableDataBaseTotal<number>) => (trait.total = trait.base + trait.mod), (trait: ModifiableDataBaseTotal<number>) => (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.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; this.data.data.checks.defend = this.data.data.combatValues.defense.total;
} }
@ -166,7 +168,7 @@ export class DS4Actor extends Actor {
* given in dot notation. * given in dot notation.
*/ */
get finalDerivedDataProperties(): string[] { 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.attributes.mind.total + data.traits.aura.total - spellMalusOfEquippedItems;
data.combatValues.targetedSpellcasting.base = data.combatValues.targetedSpellcasting.base =
data.attributes.mind.total + data.traits.dexterity.total - spellMalusOfEquippedItems; 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( Object.values(data.combatValues).forEach(
(combatValue: ModifiableDataBaseTotal<number>) => (combatValue.total = combatValue.base + combatValue.mod), (combatValue: ModifiableDataBaseTotal<number>) =>
(combatValue.total = combatValue.base + (combatValue.mod ?? 0)),
); );
} }

View file

@ -22,6 +22,13 @@ export class DS4Character extends DS4Actor {
get ownableItemTypes(): Array<ItemType> { get ownableItemTypes(): Array<ItemType> {
return [...super.ownableItemTypes, "talent", "racialAbility", "language", "alphabet"]; 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 { export interface DS4Character {

View file

@ -11,6 +11,13 @@ export class DS4Creature extends DS4Actor {
get ownableItemTypes(): Array<ItemType> { get ownableItemTypes(): Array<ItemType> {
return [...super.ownableItemTypes, "specialCreatureAbility"]; 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 { export interface DS4Creature {

View file

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
export interface ModifiableData<T> { export interface ModifiableData<T> {
mod: T; mod?: T | null | undefined;
} }
export interface HasBase<T> { export interface HasBase<T> {

View file

@ -158,10 +158,11 @@ const i18nKeys = {
rangedAttack: "DS4.CombatValuesRangedAttack", rangedAttack: "DS4.CombatValuesRangedAttack",
spellcasting: "DS4.CombatValuesSpellcasting", spellcasting: "DS4.CombatValuesSpellcasting",
targetedSpellcasting: "DS4.CombatValuesTargetedSpellcasting", 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: { combatValuesSheet: {
hitPoints: "DS4.CombatValuesHitPointsSheet", hitPoints: "DS4.CombatValuesHitPointsSheet",
@ -172,6 +173,7 @@ const i18nKeys = {
rangedAttack: "DS4.CombatValuesRangedAttackSheet", rangedAttack: "DS4.CombatValuesRangedAttackSheet",
spellcasting: "DS4.CombatValuesSpellcastingSheet", spellcasting: "DS4.CombatValuesSpellcastingSheet",
targetedSpellcasting: "DS4.CombatValuesTargetedSpellcastingSheet", targetedSpellcasting: "DS4.CombatValuesTargetedSpellcastingSheet",
mana: "DS4.CombatValuesManaSheet",
}, },
/** /**

1
src/global.d.ts vendored
View file

@ -12,6 +12,7 @@ declare global {
"ds4.showSlayerPoints": boolean; "ds4.showSlayerPoints": boolean;
"ds4.classes": Classes; "ds4.classes": Classes;
"ds4.heroClasses": HeroClasses; "ds4.heroClasses": HeroClasses;
"ds4.useManaSystem": boolean;
} }
} }

View file

@ -50,6 +50,7 @@ export default async function registerHandlebarsPartials(): Promise<void> {
"systems/ds4/templates/sheets/item/tabs/properties.hbs", "systems/ds4/templates/sheets/item/tabs/properties.hbs",
"systems/ds4/templates/sheets/shared/components/add-button.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/control-button-group.hbs",
"systems/ds4/templates/sheets/shared/components/form-field-icon-button.hbs",
"systems/ds4/templates/sheets/shared/components/rollable-image.hbs", "systems/ds4/templates/sheets/shared/components/rollable-image.hbs",
]; ];
await loadTemplates(templatePaths); await loadTemplates(templatePaths);

View file

@ -16,6 +16,7 @@ import registerHandlebarsPartials from "../handlebars/handlebars-partials";
import { getGame } from "../helpers"; import { getGame } from "../helpers";
import { DS4ItemSheet } from "../item/item-sheet"; import { DS4ItemSheet } from "../item/item-sheet";
import { DS4ItemProxy } from "../item/proxy"; import { DS4ItemProxy } from "../item/proxy";
import { DS4SpellSheet } from "../item/spell/spell-sheet";
import logger from "../logger"; import logger from "../logger";
import { macros } from "../macros/macros"; import { macros } from "../macros/macros";
import { migration } from "../migrations"; import { migration } from "../migrations";
@ -69,7 +70,25 @@ async function init() {
Actors.registerSheet("ds4", DS4CharacterSheet, { types: ["character"], makeDefault: true }); Actors.registerSheet("ds4", DS4CharacterSheet, { types: ["character"], makeDefault: true });
Actors.registerSheet("ds4", DS4CreatureSheet, { types: ["creature"], makeDefault: true }); Actors.registerSheet("ds4", DS4CreatureSheet, { types: ["creature"], makeDefault: true });
Items.unregisterSheet("core", ItemSheet); 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(); preloadFonts();
await registerHandlebarsPartials(); await registerHandlebarsPartials();

View file

@ -7,6 +7,7 @@
import { DS4ActiveEffect } from "../active-effect"; import { DS4ActiveEffect } from "../active-effect";
import { DS4 } from "../config"; import { DS4 } from "../config";
import { getGame } from "../helpers"; import { getGame } from "../helpers";
import { DS4Settings, getDS4Settings } from "../settings";
import notifications from "../ui/notifications"; import notifications from "../ui/notifications";
import { enforce } from "../utils"; import { enforce } from "../utils";
import { isDS4ItemDataTypePhysical } from "./item-data-source-base"; import { isDS4ItemDataTypePhysical } from "./item-data-source-base";
@ -40,6 +41,7 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Options, DS4ItemSheetData>
isOwned: this.item.isOwned, isOwned: this.item.isOwned,
actor: this.item.actor, actor: this.item.actor,
isPhysical: isDS4ItemDataTypePhysical(this.item.data.data), isPhysical: isDS4ItemDataTypePhysical(this.item.data.data),
settings: getDS4Settings(),
}; };
return data; return data;
} }
@ -128,6 +130,7 @@ interface DS4ItemSheetData extends ItemSheet.Data<ItemSheet.Options> {
isOwned: boolean; isOwned: boolean;
actor: DS4ItemSheet["item"]["actor"]; actor: DS4ItemSheet["item"]["actor"];
isPhysical: boolean; isPhysical: boolean;
settings: DS4Settings;
} }
/** /**

View file

@ -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<Level, Record<CooldownDuration, number>> = {
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"),
};
}

View file

@ -18,6 +18,11 @@ export interface DS4SpellDataSourceData extends DS4ItemDataSourceDataBase, DS4It
effectRadius: UnitData<DistanceUnit>; effectRadius: UnitData<DistanceUnit>;
duration: UnitData<TemporalUnit>; duration: UnitData<TemporalUnit>;
cooldownDuration: CooldownDuration; cooldownDuration: CooldownDuration;
manaCost: {
healer: number | null;
wizard: number | null;
sorcerer: number | null;
};
minimumLevels: { minimumLevels: {
healer: number | null; healer: number | null;
wizard: number | null; wizard: number | null;

View file

@ -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" } } };
}

View file

@ -9,7 +9,7 @@ export class DS4Talent extends DS4Item {
prepareDerivedData(): void { prepareDerivedData(): void {
super.prepareDerivedData(); super.prepareDerivedData();
const data = this.data.data; 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 */ /** @override */

View file

@ -58,6 +58,16 @@ export function registerSystemSettings(): void {
restricted: true, restricted: true,
type: ClassConfig, 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 { export interface DS4Settings {
@ -66,6 +76,7 @@ export interface DS4Settings {
showSlayerPoints: boolean; showSlayerPoints: boolean;
classes: Classes; classes: Classes;
heroClasses: HeroClasses; heroClasses: HeroClasses;
useManaSystem: boolean;
} }
export function getDS4Settings(): DS4Settings { export function getDS4Settings(): DS4Settings {
@ -76,6 +87,7 @@ export function getDS4Settings(): DS4Settings {
showSlayerPoints: game.settings.get("ds4", "showSlayerPoints"), showSlayerPoints: game.settings.get("ds4", "showSlayerPoints"),
classes: game.settings.get("ds4", "classes"), classes: game.settings.get("ds4", "classes"),
heroClasses: game.settings.get("ds4", "heroClasses"), heroClasses: game.settings.get("ds4", "heroClasses"),
useManaSystem: game.settings.get("ds4", "useManaSystem"),
}; };
} }

View file

@ -68,6 +68,10 @@
}, },
"targetedSpellcasting": { "targetedSpellcasting": {
"mod": 0 "mod": 0
},
"mana": {
"mod": 0,
"value": 0
} }
} }
} }
@ -188,6 +192,11 @@
"unit": "custom" "unit": "custom"
}, },
"cooldownDuration": "0r", "cooldownDuration": "0r",
"manaCost": {
"healer": null,
"wizard": null,
"sorcerer": null
},
"minimumLevels": { "minimumLevels": {
"healer": null, "healer": null,
"wizard": null, "wizard": null,

View file

@ -16,6 +16,18 @@ SPDX-License-Identifier: MIT
id="data.combatValues.hitPoints.value-{{data._id}}" value="{{data.data.combatValues.hitPoints.value}}" id="data.combatValues.hitPoints.value-{{data._id}}" value="{{data.data.combatValues.hitPoints.value}}"
data-dtype="Number" /> data-dtype="Number" />
</div> </div>
{{!-- TODO: Find a better place for this --}}
{{#if settings.useManaSystem}}
<div class="ds4-actor-progression__entry">
<h2 class="ds4-actor-progression__label"><label for="data.mana.value-{{data._id}}"
title="{{localize 'DS4.CombatValuesManaCurrent'}}">{{localize
"DS4.CombatValuesManaCurrentAbbr"}}</label>
</h2>
<input class="ds4-actor-progression__input" type="number" name="data.combatValues.mana.value"
id="data.combatValues.mana.value-{{data._id}}" value="{{data.data.combatValues.mana.value}}"
data-dtype="Number" />
</div>
{{/if}}
{{#if (eq data.type "character")}} {{#if (eq data.type "character")}}
{{#if settings.showSlayerPoints}} {{#if settings.showSlayerPoints}}
<div class="ds4-actor-progression__entry"> <div class="ds4-actor-progression__entry">

View file

@ -15,7 +15,7 @@ SPDX-License-Identifier: MIT
--}} --}}
<div class="ds4-combat-value"> <div class="ds4-combat-value">
<div class="ds4-combat-value__value ds4-combat-value__value--{{combat-value-key}}" <div class="ds4-combat-value__total ds4-combat-value__total--{{combat-value-key}}"
title="{{combat-value-title}}: {{combat-value-data.tooltip}}"> title="{{combat-value-title}}: {{combat-value-data.tooltip}}">
{{combat-value-data.total}} {{combat-value-data.total}}
</div> </div>

View file

@ -7,9 +7,11 @@ SPDX-License-Identifier: MIT
<div class="ds4-combat-values"> <div class="ds4-combat-values">
{{#each config.i18n.combatValues as |combat-value-title combat-value-key|}} {{#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 {{> 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 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 ../config.i18n.combatValuesSheet combat-value-key) combat-value-title=combat-value-title
actor-id=../data._id}} actor-id=../data._id}}
{{/unless}}
{{/each}} {{/each}}
</div> </div>

View file

@ -94,18 +94,42 @@ SPDX-License-Identifier: MIT
</select> </select>
</div> </div>
</div> </div>
{{#if settings.useManaSystem}}
<div class="form-group slim">
<label title="{{localize 'DS4.ManaCostDescription'}}">{{localize "DS4.ManaCost"}}</label>
<div class="form-fields">
{{> systems/ds4/templates/sheets/shared/components/form-field-icon-button.hbs
class="ds4-calculate-mana-cost" title="DS4.CalculateManaCost" faClasses="fas fa-magic"}}
<label for="data.manaCost.healer-{{data._id}}">{{localize "DS4.SpellCasterClassHealer"}}</label>
<input id="data.manaCost.healer-{{data._id}}" class="ds4-form-field-input-extra-slim" data-dtype="Number"
type="number" min="0" step="1" name="data.manaCost.healer" placeholder=""
value="{{data.data.manaCost.healer}}" />
<label for="data.manaCost.sorcerer-{{data._id}}">{{localize "DS4.SpellCasterClassSorcerer"}}</label>
<input id="data.manaCost.sorcerer-{{data._id}}" class="ds4-form-field-input-extra-slim" data-dtype="Number"
type="number" min="0" step="1" name="data.manaCost.sorcerer" placeholder=""
value="{{data.data.manaCost.sorcerer}}" />
<label for="data.manaCost.wizard-{{data._id}}">{{localize "DS4.SpellCasterClassWizard"}}</label>
<input id="data.manaCost.wizard-{{data._id}}" class="ds4-form-field-input-extra-slim" data-dtype="Number"
type="number" min="0" step="1" name="data.manaCost.wizard" placeholder=""
value="{{data.data.manaCost.wizard}}" />
</div>
</div>
{{/if}}
<div class="form-group slim"> <div class="form-group slim">
<label title="{{localize 'DS4.SpellMinimumLevelDescription'}}">{{localize "DS4.SpellMinimumLevel"}}</label> <label title="{{localize 'DS4.SpellMinimumLevelDescription'}}">{{localize "DS4.SpellMinimumLevel"}}</label>
<div class="form-fields"> <div class="form-fields">
<label for="data.minimumLevels.healer-{{data._id}}">{{localize "DS4.SpellCasterClassHealer"}}</label> <label for="data.minimumLevels.healer-{{data._id}}">{{localize "DS4.SpellCasterClassHealer"}}</label>
<input id="data.minimumLevels.healer-{{data._id}}" data-dtype="Number" type="number" min="0" step="1" <input id="data.minimumLevels.healer-{{data._id}}" class="ds4-form-field-input-extra-slim"
name="data.minimumLevels.healer" placeholder="" value="{{data.data.minimumLevels.healer}}" /> data-dtype="Number" type="number" min="0" step="1" name="data.minimumLevels.healer" placeholder=""
value="{{data.data.minimumLevels.healer}}" />
<label for="data.minimumLevels.sorcerer-{{data._id}}">{{localize "DS4.SpellCasterClassSorcerer"}}</label> <label for="data.minimumLevels.sorcerer-{{data._id}}">{{localize "DS4.SpellCasterClassSorcerer"}}</label>
<input id="data.minimumLevels.sorcerer-{{data._id}}" data-dtype="Number" type="number" min="0" step="1" <input id="data.minimumLevels.sorcerer-{{data._id}}" class="ds4-form-field-input-extra-slim"
name="data.minimumLevels.sorcerer" placeholder="" value="{{data.data.minimumLevels.sorcerer}}" /> data-dtype="Number" type="number" min="0" step="1" name="data.minimumLevels.sorcerer" placeholder=""
value="{{data.data.minimumLevels.sorcerer}}" />
<label for="data.minimumLevels.wizard-{{data._id}}">{{localize "DS4.SpellCasterClassWizard"}}</label> <label for="data.minimumLevels.wizard-{{data._id}}">{{localize "DS4.SpellCasterClassWizard"}}</label>
<input id="data.minimumLevels.wizard-{{data._id}}" data-dtype="Number" type="number" min="0" step="1" <input id="data.minimumLevels.wizard-{{data._id}}" class="ds4-form-field-input-extra-slim"
name="data.minimumLevels.wizard" placeholder="" value="{{data.data.minimumLevels.wizard}}" /> data-dtype="Number" type="number" min="0" step="1" name="data.minimumLevels.wizard" placeholder=""
value="{{data.data.minimumLevels.wizard}}" />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View file

@ -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
}}
<button class="ds4-form-field-icon-button {{class}}" type="button" title="{{localize title}}">
<i class="{{faClasses}} ds4-form-field-icon-button__icon"></i>
</button>

View file

@ -892,9 +892,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@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.3 version: 9.249.4
resolution: "@league-of-foundry-developers/foundry-vtt-types@npm:9.249.3" resolution: "@league-of-foundry-developers/foundry-vtt-types@npm:9.249.4"
dependencies: dependencies:
"@types/jquery": ~3.5.9 "@types/jquery": ~3.5.9
"@types/simple-peer": ~9.11.1 "@types/simple-peer": ~9.11.1
@ -903,7 +903,7 @@ __metadata:
pixi.js: 5.3.11 pixi.js: 5.3.11
socket.io-client: 4.3.2 socket.io-client: 4.3.2
tinymce: 5.10.1 tinymce: 5.10.1
checksum: 13bbcafd57637a59f44ee136e5edd78b1fedb3f0327027bd2bae8971f2eed6ad5ad84755cebc32e4b08f788e0bf5c08dd06cbe4709ae89746198658ebb511451 checksum: 612721d98bb6e8496d8000e86619615b2ae968ecb643273545c1a73c7d2faeca611f6d3adbc84b99cdf47b706a604226582731e0be560701a801a6a23bb75cae
languageName: node languageName: node
linkType: hard linkType: hard
@ -3262,7 +3262,7 @@ __metadata:
"@commitlint/cli": 16.2.1 "@commitlint/cli": 16.2.1
"@commitlint/config-conventional": 16.2.1 "@commitlint/config-conventional": 16.2.1
"@guanghechen/rollup-plugin-copy": 1.8.6 "@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 "@rollup/plugin-typescript": 8.3.0
"@seald-io/nedb": 2.2.1 "@seald-io/nedb": 2.2.1
"@types/fs-extra": 9.0.13 "@types/fs-extra": 9.0.13