Coder Social home page Coder Social logo

effectivetray's Introduction

Effective Tray (for D&D Fifth Edition)

A module for dnd5e on foundryvtt that allows the effects and damage trays to be used more effectively.

Effects Tray

Allows users to use the effect tray as much or as little as wanted by the GM, up to and including transferal to targets they do not own. If using this feature (which is on by default), left click to apply effects to selected tokens that the user owns (as the system's default behavior), or right click to apply effects to tokens that the user does not own.

Damage Tray

Allows users to use the damage tray for selected tokens that they own, and for targeted tokens (either that they own, or ones they don't own with a setting). User ability to use the damage tray is on by default, but unless the relevant setting is selected, they cannot damage targets.

Settings

  • Transfer to Target: Allow users to transfer effects to targets with right click (on by default).
  • Legacy Targeting for Effects: Apply effects to target with right click, rather than the target source control (on by default).
  • Damage Target: Allow users to damage targets (that they don't own) with the damage tray (off by default).
  • Delete Instead of Refresh: Attempting to transfer an effect to an actor that has it already will delete it rather than refreshing its duration (off by default).
  • Filtering based on actor type, permissions, and token disposition. This prevents users from seeing and interacting with effects of certain origins, depending on GM preference (no filtering is performed by default).
  • Use Default Trays: Adds settings (off by default) to use the default effects and damage trays. Only the features below this setting will function if a given tray is in its default mode.
  • Don't Close Trays on Apply: Don't automatically close trays when hitting submit. Won't work with default effects tray (now off by default).
  • Scroll on Expand: Scroll chat to bottom when expanding a tray that is at the bottom (experimental, on by default).
  • Remove 'Apply Effect to Actor': On the time of creation (i.e. drag & drop), remove 'Apply Effect to Actor' from effects on items that have a duration to allow for normal use of the timer (on by default).
  • Multiple Effects with Concentration: Allow multiple effects to be applied from spells with concentration (off by default).

API

Now includes three helper functions exposed in the global scope under effectiv, effectiv.applyEffect, effectiv.applyDamage and effective.partitionTargets. These functions have not been heavily tested and are included now in the hopes that, if someone wants to use them, they will also test them for issues.

applyEffect, a helper function to allow users to apply effects. It allows users to apply effects via macro (or other module), and can take a variety of types of data when doing so, allowing effect data to be passed as the full ActiveEffect document, an effect Uuid, or an object (note that passing it as an object will not interact with refreshing duration or deleting effects of same origin, as determined by module setting, because creating an effect from an object will have its own unique origin). Similarly, this function allows target data to be passed as an array of Uuids, a single Uuid, an array of Tokens, or a Set, as game.user.targets.

This helper also allows the use of the other things the effects tray does, primarily flagging effects with spellLevel, or any other arbitrary flags (via effectData), and making an effect dependent on a concentration effect (so it will be deleted when the concentration effect is). If concentration is used, because it passed as an ID, you must also pass the Uuid or Actor document of the actor the concentration effect is on.

/**
 * Helper function to allow for macros or other applications to apply effects to owned and unowned targets.
 * @param {string|object|ActiveEffect5e} effect            The effect to apply.
 * @param {Set<Token5e>|Token5e[]|string[]|string} targets Targeted tokens.
 * @param {object} [options]
 * @param {object} [options.effectData]                    A generic data object, which typically handles the level the originating spell was cast at, 
 *                                                         if it originated from a spell, if any. Use flags like { "flags.dnd5e.spellLevel": 1 }.
 * @param {string} [options.concentration]                 The ID (not Uuid) of the concentration effect this effect is dependent on, if any.
 * @param {string|Actor5e} [options.caster]                The Uuid or Actor5e document of the actor that cast the spell that requires concentration, if any.
 */
async function applyEffect(
  effect, 
  targets, 
  { effectData: null, concentration: null, caster: null } = {}
)
/* in use...*/
effectiv.applyEffect(
  effect, 
  targets, 
  { effectData: null, concentration: null, caster: null } = {}
)

applyDamage, helper function to allow users to apply damage. This function has been tested not at all and is included as a courtesy. Personally I find the way damage information must be structured to respect resistances, etc is too much of a mess to test this even a single time, but if someone really wants to do this over a socket but didn't write their own socket handler for it...here you go. Full, extensive documentation of the array that must be created is in scripts/api.mjs, copied directly from dnd5e, with the exception that socket transmission requires the sets to be arrays. Unlike applyEffect, this applies no damage via the requesting client, and so is basically only meant for damaging unowned targets. Users wishing to apply damage to owned targets should simply use the system's Actor#applyDamage.

  /**
   * The below documentation is from the system's damage application method, except all Sets are arrays.
   * Please see the system's documentation for a complete understanding of this method, as this helper is only
   * provided as a courtesy.
   * 
   * Description of a source of damage. 
   *
   * @typedef {object} DamageDescription
   * @property {number} value            Amount of damage.
   * @property {string} type             Type of damage.
   * @property {Array<string>} properties  Physical properties that affect damage application.
   * @property {object} [active]
   * @property {number} [active.multiplier]      Final calculated multiplier.
   * @property {boolean} [active.modifications]  Did modification affect this description?
   * @property {boolean} [active.resistance]     Did resistance affect this description?
   * @property {boolean} [active.vulnerability]  Did vulnerability affect this description?
   * @property {boolean} [active.immunity]       Did immunity affect this description?
   */

  /**
   * Options for damage application.
   *
   * @typedef {object} DamageApplicationOptions
   * @property {boolean|Array<string>} [downgrade]  Should this actor's resistances and immunities be downgraded by one
   *                                              step? A Array of damage types to be downgraded or `true` to downgrade
   *                                              all damage types.
   * @property {number} [multiplier=1]         Amount by which to multiply all damage.
   * @property {object|boolean} [ignore]       Array to `true` to ignore all damage modifiers. If Array to an object, then
   *                                           values can either be `true` to indicate that the all modifications of
   *                                           that type should be ignored, or a Array of specific damage types for which
   *                                           it should be ignored.
   * @property {boolean|Array<string>} [ignore.immunity]       Should this actor's damage immunity be ignored?
   * @property {boolean|Array<string>} [ignore.resistance]     Should this actor's damage resistance be ignored?
   * @property {boolean|Array<string>} [ignore.vulnerability]  Should this actor's damage vulnerability be ignored?
   * @property {boolean|Array<string>} [ignore.modification]   Should this actor's damage modification be ignored?
   * @property {boolean} [invertHealing=true]  Automatically invert healing types to it heals, rather than damages.
   * @property {"damage"|"healing"} [only]     Apply only damage or healing parts. Untyped rolls will always be applied.
   */

  /**
   * Apply a certain amount of damage or healing to the health pool for Actor
   * @param {DamageDescription[]|number} damages     Damages to apply.
   * @param {DamageApplicationOptions} [options={}]  Damage application options.
   * @returns {Promise<Actor5e>}                     A Promise which resolves once the damage has been applied.
   */

  /**
   * Helper function to allow for macros or other applications to apply damage via socket request.
   * @param {array} damage Array of damage objects; see above.
   * @param {array} opts   Object of options (which may inlude arrays); see above.
   * @param {string} id    Uuid of the target.
   */
async function applyDamage(damage = [], opts = {}, id)
/* in use...*/
effectiv.applyDamage(damage = [], opts = {}, id)

partitionTargets, a function similar to foundry's Array#partition but specifically designed to handle game.user.targets, a set, or an array of tokens. It sorts them into two arrays, the first array containing tokens that the user owns, and the second array containing those token's document.uuids. This can be useful for determining what information needs to be sent over sockets. I have no idea why anyone would use this function, but here it is.

/**
 * Sort tokens into owned and unowned categories.
 * @param {Set|array} targets The set or array of tokens to be sorted.
 * @returns {array}           An Array of length two, the elements of which are the partitioned pieces of the original.
 */
function partitionTargets(targets)
/* in use...*/
effectiv.partitionTargets(targets)

Hooks

Now includes two hooks, effectiv.preApplyEffect and effectiv.applyEffect. The former allows the data to be modified and explicitly returning false will prevent the effect from being applied. The later passes the same (modified in the case of effectData), information in its final state upon application.

These hooks have not been extensively tested.

/**
 * Hook called before the effect is completed and applied.
 * @param {Actor5e} actor                The actor to create the effect on.
 * @param {ActiveEffect5e} effect        The effect to create.
 * @param {object} effectData            A generic data object that contains spellLevel in a `dnd5e` scoped flag, and whatever else.
 * @param {ActiveEffect5e} concentration The concentration effect on which `effect` is dependent, if it requires concentration.
 */
Hooks.call("effectiv.preApplyEffect", actor, effect, { effectData, concentration });
/**
 * Hook called before the effect is completed and applied. Same as abvove except for effectData
 * @param {Actor5e} actor                The actor to create the effect on.
 * @param {ActiveEffect5e} effect        The effect to create.
 * @param {object} effectData            The packaged effect immediately before application.
 * @param {ActiveEffect5e} concentration The concentration effect on which `effect` is dependent, if it requires concentration.
 */
Hooks.callAll("effectiv.applyEffect", actor, effect, { effectData, concentration });

Technical Details

Scope: Replaces the effects tray in chat messages with a similar one that allows all users, not just GMs or the chat message's creator, to apply active effects to tokens they control (and have selected) by looping over all messages in the "dnd5e.renderChatMessage" hook. Extends the system's damage application element class to allow users who are not GMs to use the damage tray. Additionally, transmits target target and change data via sockets to allow an active GM client to apply effects (or damage) to a non GM user's targets.

License: MIT License.

Additional Info: Thanks to Zhell for help with adding actors to a set and lots of other stuff (and for the github action), and to Flix for much encouragement. Thanks also to ChaosOS for help with sockets, and DrentalBot for help with suppressing the system's context menu.

effectivetray's People

Contributors

etiquettestartshere avatar larkinabout avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar

effectivetray's Issues

MidiQoL Incompatibility

There is some issue that this mod has where the damage calculation is wildly off if Midi is active. For example, I rolled a damage of 9, but in the tray, it wants to apply 18 (target does not have weakness against damage type). Do you know what could be happening?

When a player creates an effect on their character, an error is thrown if the originating item requires concentration

Repro:
(1) user A owns actor A and casts Bless.
(2) (optional) remove the concentration tracking effect created on actor A by the Bless spell.
(3) user B owns actor B and applies the bless effect to their actor via the chat log.
(4) if the concentration tracking effect still exists, an error is thrown due to player B not being allowed to update the concentration tracking effect on actor A. If the concentration tracking effect was deleted in step (2), the below error is thrown instead.

image

Setup:

  • dnd5e: 3.2.1
  • foundry: 12.328
  • module: 1.2.7

remove expand tray settings to use the system's

the only downside is that the system only offers to expand all trays rather than granular configuration. however, i hate coding these settings, and am happy to just use the system's method where needed and only touch this to maintain parity.

don't close on apply will remain a supported feature.

Effects with duplicate names all apply on the first listener

this is due to the fact that the listener is tied to the name and not the id or uuid. since the id is derived or injected via handlebars(?) and not there on the time of the render hook, the uuid should be used for the click listener instead

3.3

i simply havent downloaded anything but an early build, so i havent updated it to test. it probably works fine; change the manifest yourself, otherwise wait till i update.

Add CONTRIBUTING.md

Add a CONTRIBUTING.md that says something like:

If you want to contribute, if your fix or suggestion is short and easy to understand, you can just make a PR. For anything more complicated, please make an issue first. Any style is fine, any kind of suggestion is fine. PRs that touch more than one issue or idea are also fine as long as they come with an explanation. PRs can target any branch numbered after the current release. Bear in mind my skill is limited and that will change what might get implemented. โ€ปPlease do not change anything to typescript.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.