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

Provide a way for custom classes to configure the features they grant upon level up within the class sheet interface. #878

Closed
aaclayton opened this issue Dec 12, 2020 · 18 comments
Assignees
Labels

Comments

@aaclayton
Copy link
Contributor

Originally in GitLab by @akrigline

This could easily be a module, but I feel it would be a valuable addition to the dnd5e system itself, so any module which does this should have the goal of becoming a MR here. League of FoundryVTT Devs Trello Card

Goal

Create a simple UI where a user can drag and drop Items (feat type only) onto various levels, which then gets saved as a flag on the class item.

Once this data is saved on the item itself, all Class type items could be iterated over and inject this data into the dnd5e CONFIG.DND5E.classFeatures object when the game is Ready.

Mockup

image

Background

https://discord.com/channels/170995199584108546/670336046164213761/786978330053836853

Paraphrasing from Atro about how a custom class can leverage the classFeatures system

  1. Create a custom Class item
  2. Create a module with an "ready" hook that extends the CONFIG.DND5E.classFeatures object to define the data structure for the features which are provided by the class

Ideally you should use a solution that doesn't involve editing the dnd5e system files because that will just get overwritten each time the system updates.

Hooks.on("ready", () => {
  CONFIG.DND5E.classFeatures["my-custom-class"] = {
    // Your class configuration here
  };
});
@aaclayton
Copy link
Contributor Author

The key question is where the class configuration should be saved. Saving the inventory of features that a class provides on the class item itself works pretty well for the purposes of a compendium pack or a world-level class - but when that class becomes an owned item, it is really inefficient (even wasteful) to retain all that metadata on the actor as it would unnecessarily duplicate/inflate the data.

Ideally there should be one canonical reference configuration for "this is the class" which is why - at least for now - a code-based approach to the configuration was preferred. Making that configuration editable in the UI though poses a problem for where and how to persist the data.

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @zeel01

There could be some analog of Token actors made for owned items? Where an item could reference the data of the original canonical item, and be either linked (changes to any instance change the original, changes to original are reflected in every instance) or unlinked (stores a set of deltas). So like Tokens and synthetic Actors, these synthetic items would exist as owned items, and an adaptor system would link them up with the "real" data.

This would satisfy the need to have all the data available on each class item, while avoiding the data inflation. Plus, this would allow the GM to make changes to the class as a whole which would be reflected in all Actors that have that class, alleviating some of the work in updating. This could extend to other types of items too, something I've often wished for.

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @iaguastalli

I am working on a sort of "charactermancer" that hits this topic on the tangent, but I've been looking at the compendium items for races, classes, etc a lot this last couple days, plus the data on data.dnd5e.config.*

I am not sure how this is done currently as I still dont get that much along with JS, but I think that generic information about classes (and races, and class features, and etc) could be kept there, loaded by the 5e system. So items on the compendium would stay lightweight with the instance-revelant information as now (how many levels in this class I have, what is the hit die, how many have I used, etc), and keep the generic info (what proficiencies this (sub)race/(sub)class gives, what is the ASI this race gives at 1st levle) etc accesible in some programmatic way. "special" features could just stay as they are now.

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @akrigline

The data system of core foundry doesn't seem concerned with this, because the same could be said for all items. A 'longsword' is a longsword, its data doesn't usually change just because its owned, yet the data is duplicated. If the data stored on the class is a simple mapping of levels to item ids, it's not that much data being duplicated.

Thus I don't think it is necessarily horrible that a class have this same weakness, indeed with Class Feature Variants from Tasha's, this is actually a feature.

Additionally, following the same pattern of owned items decoupling themselves from the un-owned item makes Class Feature variants easier to support from one character to the next.

Two different Rangers might want different class feature variants selected. Allowing the owned class's features at each level to be edited automatically supports this.

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @TPNils

I was working on this as a module, though since it would be seperate from the system it had it's own issues. I have a persenal intrest in this issue since the biggest pain for my players is the character creation, they are currently using dnd beyond, which is amazing by itself, but doesn't itegrate the way I would like it to do. As such I would like to offer my assistance and ideas.

I'm not sure if the core devs agree with what I would want, but I would like to aim for a character builder that at least comes close to dnd beyond and allows players to manually add non SRD content that they legally own (which obviously won't be in the system but should support it)

  • Gain a predefined feature (barb lvl 1 rage)
  • Modify a feature (ex: Mindless Rage adds condition immunities which could auto apply with active effects when you activate rage)
  • Choose between 2 or more features (tasha's optional features, take an ASI/feat)
  • Gain some one-time items and/or currency, I think this only happens on your first level (global level, not per class)
  • Allow you to choose spells, which should be corrently filtered => only see wizard spells for a wizard
  • Add fixed "always prepaired" spells (ex: clerics)
  • Choose skill proficiencies and gain fixed saving throw proficenties (also after lvl1, see monk Diamond body)
  • I imagine at some point in the future non-SRD content will be purchable and should somehow automatically hook into your available options

This issue is specific for classes, though I would like to point out there are also backgrounds and races which require simular behaviour

  • Fire genasi get a cantrip at lvl 1 and a one-time use of burning hands at lvl 3 (add items)
  • Gain fixed/variable languages, darkvision, skills, etc.. (change the actor data)
  • Gain some one-time items and/or currency
  • Grant a fixed feature (ex: Acolyte - Shelter of the Faithful)
  • Suggested characteristics, though I find these persenally not all that important, they could just be added to the background description

The way I was solving some of these issues by extending the active effects system. It sounded kind of hacky but I found that it worked very well. Here is an example
I created a tavern brawler feature, add an active effect and link 2 items to the active effect. When the feature is added to an actor, it automatically adds the items. Since it can be tracked how the item was added, you can't (accidentally) deleted it, you would need to delete the feat to also delete the unarmed strike and grapple.

Altough it doesn't have to make use of the active effects, but it definitly should have a simular approach. Allowing players to tinker with their character, change classes when they don't know yet what they want without cleaning up anything they have gained from their previous choice.
image

This is as far as I have gotten with the implementation. There are still some ideas I have to implement the rest

  • Choose between multiple features: Add a new item type "select" where you manually select which option you want.
  • For spells, the spells themself probably need to be updated to include (sub) classes which can learn them. Than add a new "spell-select" item which will look for any possible spell that matches and shows them as an option
  • For non-SRD content, lets take ranger as an example. The Favored Enemy/Foe would be a select item type in the SRD, but one of the options will point to a compendium item. Today, that means the system needs to be smart enough to hide options it can't find => auto selecting if there is only 1 available. That way SRD is only SRD but when the time comes to expand it is possible
  • Allow to add an "is active" filter to active effects, so when you reach lvl 2 range you automatically gain a fighting style which you should choose from

Some things that I have not worked out with this

  • One-time gains
  • Modifiying items, I origionally aimed to take advantage of DAE/midi-qol to solve this, but that's not an option for a system
  • Adding fixed "always prepaired" spells may result in having a spell twice (aiming for dnd beyond like behaviour, this should be solved to give that extra clean 'it just works' feel, but it is a minor issue.
  • How to store the data. My implementation just copies the data, though I do believe there is an argument to be made that classes should be able to be updated by the system. When a new book comes out like tasha which modifies the base class, players shouldn't have to re-import the new classes. Though at this point I don't have a solution for this that wouldn't screw over homebrewers who change the base classes them self.

PS. I am not quite sure how gitlab notification work, so I just tag @aaclayton to be safe

@aaclayton
Copy link
Contributor Author

Can provide some more complete feedback later - but a quick comment:

I'm not sure if the core devs agree with what I would want, but I would like to aim for a character builder that at least comes close to dnd beyond and allows players to manually add non SRD content that they legally own (which obviously won't be in the system but should support it)

This is definitely a long term goal. The thing that I want to be very careful about is to accomplish work towards this goal slowly and methodically in a way that establishes well designed and maintainable underlying data structures and design patterns that avoid hacks/workarounds and instead invest in the right underlying foundation.

A key challenge is the addition of complexity (which is unavoidable in order to accomplish this goal) - but unless we have a beautiful solution for some things the added complexity will ultimately end up being worse than the value of having this type of system.

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @akrigline

Marked breaking because this proposal involves moving where the map of features for a class/subclass is stored onto the class item itself.

Proposal: Add two keys to the class item data model formatted how the feature map is currently formatted:

  "features": {
    "1": ["Compendium.dnd5e.classfeatures.VoR0SUrNX5EJVPIO", "Compendium.dnd5e.classfeatures.SZbsNbaxFFGwBpNK"],
    "7": ["Compendium.dnd5e.classfeatures.SCVjqRdlZ9cvHVSR"]
  },
  "subclasses": {
    "path-of-the-berserker": {
      "label": "Path of the Berserker",
      "source": "PHB pg. 49",
      "features": {
         "2": ["Compendium.dnd5e.classfeatures.lT8GsPOPgRzDC3QJ", "Compendium.dnd5e.classfeatures.wKdRtFsvGfMKQHLY"],
      }
    }
  }

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @arbron

Should we introduce a new subclass type at this time? Having subclasses as separate types would allow us to add more structured data (such as when subclasses grant spellcasting) and would allow subclasses to be more portable (aka module of new subclasses that can be easily dragged onto an existing class).

This would probably be structured something like this in class data:

"subclasses": {
  "college-of-creation": "Compendium.dnd5e.classes.123456789",
  "college-of-eloquence": "Compendium.dnd5e.classes.987654321",
}

And then the subclasses would contain their own features section in the same format as the class one.

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @iaguastalli

I think that Subclass would be too much of the same things as a Class. What I did when dealing with Races/Subraces is have a nullable "parent" reference so that subraces know who their parent is. Some more analysis could be done to see if I'm not breaking any best practice violently, but it seems to work nicely, and it allows a quick and direct reference that allows for things like merging stuff from parent and subtype

(in Races is probably more evident as parent classes give some ASI and their subclasses add to that, but proficiencies is a common area too, where there's many an example like Bards having XYZ proficiencies and their martial subclasses adding some)

@aaclayton
Copy link
Contributor Author

Shifting to a later milestone to allow more planning as a group.

@aaclayton
Copy link
Contributor Author

Comment from @Kelmey

When creating custom classes it doesn't appear to be possible to configure features to be added automatically. Looking around it seems the file classFeatures.js in the dnd5e module specifies this association for the official classes.

It would be great to be able to configure this without modifying the system module. Perhaps the system could look at another file which lives in the current world in addition to classFeatures.js? As a thought, perhaps hardcode it or allow it to be specified in System Setting.

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @iaguastalli

@arbron I think my alternative of having same-structure-as-children works nicely, and would make this cleaner too with Calego's code. At the very least, it could work as an initial approach to subclasses and subraces, then if need be, more specific sub-types could be defined.

So class would have an array of subclasses, which will just be references to other Class items (no special subclass feature) and/or the children class could have a reference to their Parent Class. But having the same item type works best imo, so that any extra features we add for either classes or subclasses are available for the other.

This should work for Races too, for #342

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @iaguastalli

Would it make sense to add a System setting where you can drag Class items (while also following the Mockup idea that Class items would hold a list of features per level), and those Class items are iterated on Ready to build up the classFeatures array ?

About the data bloating, maybe owned Class items would not hold that feature list, and just reference the original object?

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @arbron

The problem about doing this in a system setting is that makes it harder to share custom classes between worlds. If the data is included in the classes, everything always travels with the class, so all you have to do is stick it in a module compendium and away you go. Other methods that involve changing config files or dynamically running code are going to be much more complex changes that will put off content creators without programming experience.

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @iaguastalli

I agree fully that the list of class features should be, in one shape or another, inside the Class item, for the reason you stated: it makes it easy for people to move around the class between worlds, adding it into a shared compendium, etc.

Sadly, that still leaves custom Classes outside of the "level up flow", as its based around the classFeatures.js as far as I understand, and that's hardcoded on the system. That's where I suggested using a system setting, IF owned classes would not hold the Features list, based on Andrew's first reply on this issue. Of course if the owned item had the whole list, then we could just peek in there. But thinking of an alternative, maybe even if the owned Class item would not hold the features list, we could use the core.sourceId flag to find back the class item With the list, maybe?

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @Kelmey

I don't think we need the perfect solution to be the first thing implemented.

Right now, since the data is stored in the 5e system, we are in a poor position. Why not implement something that should (not a professional developer, sorry if I don't understand the nuances) be somewhat straightforward like having a file in the root of a world that is merged with the classFeatures.js in the 5e system.

This isn't the best solution but it will let knowledgeable users create class feature associations while we wait for you awesome devs to come up with an elegant solution.

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @MaxPat931

I think having a System Setting for "Customize Class Features" (or the like) that brings up a dialog similar to the Class Leveling Editor module would be worth exploring. That module does have an Import/Export feature which would make transferring class settings between worlds easy, and would also give users the ability to have different settings between worlds (perhaps one world has a lot of homebrew, and another is strictly by the books, but classes and class features are stored in the same shared compendium). Class Leveling Editor also provides good flexibility between how a class's features are defined, a user can either write and use js directly via the Import/Export feature, or use the UI to drag and drop features to their correct class/level.

@aaclayton
Copy link
Contributor Author

Originally in GitLab by @akrigline

#1400 (and the whole Advancement epic) has overtaken this issue and I'm going to mark this one done
It is currently possible to create/edit and delete the item grant advancements on a class item via the UI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants