Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#4604] Rework spellcasting configurations #4947

Open
wants to merge 1 commit into
base: 4.2.x
Choose a base branch
from

Conversation

krbz999
Copy link
Contributor

@krbz999 krbz999 commented Dec 31, 2024

  • Moves relevant spellcasting configuration into DND5E.spellcasting and DND5E.spellcastingTables.
  • The prepared mode is now just spell (was initially leveled but it simplified a lot of code throughout to just have it be identical to the name of the object within Actor#system#spells).
  • The always preparation mode is no more, and is instead part of SpellData#preparation.prepared. This property is no longer a boolean but 0, 1, or 2 (unprepared, prepared, always prepared). Migrations are added for spell items and item grant/choice advancements.
  • Whether slots are recovered on SR/LR is now only in DND5E.restTypes instead of in two locations.

The DND5E.spellcasting object entirely controls the preparation and setup of a spellcasting type. Whether it grants spell slots at all (static, for ritual/innate/atwill), whether the slots are spread out across multiple levels (separate, for leveled vs pact slots), whether the spells of this mode can be prepared/always prepared (prepares), and whether it grants cantrips (cantrips).

Each of these types also have progression, identical to how leveled spell progressions are set up. The pact progression is considered a "Full" pact caster. This will make it trivial to add for example a type of pact magic caster that gains pact slots at a higher or lower rate. (This has no repercussions for existing warlocks.)

Caveats:

  • Each of these nested keys must be unique across all the spellcasting modes.

The DND5E.spellcastingTables object is a bit different than usual. Rather than describing the amount of slots at each level (and level of said slots), it describes the increase of slots (or level of slots) at each level. A method, CONFIG.DND5E.spellcasting.calculateSlots, is added to get the total configuration of slots at a given level. This makes it easier to make small adjustments such as adding a single 1st-level spell slot at 9th level; you would only need to add 1: 1 at level 9 rather than editing each level from 9 and onwards.

The new DND5E.spellPreparationStates contains the labels and values of the preparation states (0: unprepared; 1: prepared; 2: always prepared). This is localized and used to create the select options. In item grant/choice advancements, "prepared" is omitted.

Possibly controversial changes:

  • Currently when a cantrip is set to "prepared", it is placed in the "Cantrips" section on the spellbook tab of the actor sheet. If same cantrip is set to "pact", it is placed in the Pact section. This is now changed, so any cantrip that is part of an actual progression ("spell", "pact", but not any of the static ones like ritual/innate/atwill) is placed in the "Cantrips" section.
  • The regular spell slot sections on the actor sheets are labeled "Leveled Magic - {n}th". There is a special handling for these spell slots in the ActivityUseDialog already so I don't feel strongly at all about making another special case for these.

A handful of the static method regarding spell slot configuration in the Actor5e class have been shuffled around a bit to generalize them for the purpose of this configuration. The hook(s) of course still exist and are technically breaking but only minorly so.

Also fixed the fact that _onDropResetData was deleting the wrong property.

Screenshots of various applications with changes:

image

image

image

image

TODO:

  • Add shims? We have not usually added shims for CONFIG objects.
  • Add deprecation warnings for the static Actor5e methods that were renamed.
  • Consider changing static and separate to a single type (string) property to possible help with adding a "spell points" configuration at a later point.
  • Initialize the different objects in DND5E.spellcasting (and the object itself) as custom data models? There are a lot of various checks throughout the code, it might reduce all this further to encapsulate it in a single class.

@@ -212,6 +242,8 @@ export default class SpellData extends ItemDataModel.mixin(ActivitiesTemplate, I
}, { all: [], vsm: [], tags: [] });
labels.components.vsm = game.i18n.getListFormatter({ style: "narrow" }).format(labels.components.vsm);

this.preparation.static = (CONFIG.DND5E.spellcasting[this.preparation.mode] ?? { static: true }).static === true;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A spell belongs to a "static" preparation mode if said prep mode does not exist, or is static: true.

* Does this item scale with any kind of consumption?
* @type {string|null}
*/
get usageScaling() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't used anywhere.

static prepareLeveledSlots(spells, actor, progression, key) {
const slots = CONFIG.DND5E.spellcasting.calculateSlots(key, progression[key]);
for ( const level of Array.fromRange(Object.keys(CONFIG.DND5E.spellLevels).length - 1, 1) ) {
const slot = spells[`${key}${level}`] ??= { value: 0 };
Copy link
Contributor Author

@krbz999 krbz999 Dec 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

During data prep, if the spellcasting is separate, then objects with a key of <name><level> will be created (such as "spell1" through "spell9"), otherwise a single object is created (eg "pact").

This is entirely dynamic, and can be tested by setting pact.table = "spell" and pact.separate = true, which results in pact1 through pact9.

image

const slots = CONFIG.DND5E.spellcasting.calculateSlots(key, progression[key]);
for ( const level of Array.fromRange(Object.keys(CONFIG.DND5E.spellLevels).length - 1, 1) ) {
const slot = spells[`${key}${level}`] ??= { value: 0 };
slot.label = CONFIG.DND5E.spellLevels[level];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a better label. It is currently used only in Favorites, I believe.

@krbz999 krbz999 force-pushed the 4606-refactor-spellcasting branch from 3fcc95b to 9924e33 Compare December 31, 2024 13:56
@arbron arbron added the api label Jan 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants