Skip to content

Commit

Permalink
Merge pull request #834 from kgar/dnd5e-4.1.x-compat-d20roll-fix
Browse files Browse the repository at this point in the history
Dnd5e 4.1.x compat d20roll fix
  • Loading branch information
kgar authored Nov 13, 2024
2 parents 6601b92 + 3060380 commit 428dcbf
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 40 deletions.
12 changes: 6 additions & 6 deletions src/components/spellbook/SpellbookFooter.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
{#if $context.spellcastingInfo.calculations.rangedMod !== $context.spellcastingInfo.calculations.meleeMod}
<button
type="button"
on:click={(ev) =>
rollRawSpellAttack(
on:click={async (ev) =>
await rollRawSpellAttack(
ev,
$context.actor,
'rsak',
Expand All @@ -73,8 +73,8 @@
</button>
<button
type="button"
on:click={(ev) =>
rollRawSpellAttack(
on:click={async (ev) =>
await rollRawSpellAttack(
ev,
$context.actor,
'msak',
Expand All @@ -101,8 +101,8 @@
{:else}
<button
type="button"
on:click={(ev) =>
rollRawSpellAttack(
on:click={async (ev) =>
await rollRawSpellAttack(
ev,
$context.actor,
undefined,
Expand Down
2 changes: 1 addition & 1 deletion src/foundry/foundry-and-system.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ declare global {
var Application: any;
var AudioHelper: any;
var ChatMessage: any;
var CONFIG: CONFIG;
var CONFIG: CONFIG & { Dice: any };
var CONST: any;
var ContextMenu: any;
var DefaultSheetsConfig: any;
Expand Down
62 changes: 62 additions & 0 deletions src/foundry/foundry.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,65 @@ export type ContextMenuEntry = {
callback?: ($entryElement: any) => void;
condition?: ($entryElement: any) => void;
};

/**
* Configuration data for the process of creating one or more basic rolls.
*/
export interface BasicRollProcessConfiguration {
rolls: BasicRollConfiguration[];
evaluate?: boolean;
event?: Event;
hookNames?: string[];
subject?: Document;
target?: number;
}

/**
* Configuration data for an individual roll.
*/
export interface BasicRollConfiguration {
parts?: string[];
data?: object;
situational?: boolean;
options?: BasicRollOptions;
subject: any;
}

/**
* Options allowed on a basic roll.
*/
export interface BasicRollOptions {
target?: number;
[key: string]: any;
}

/* -------------------------------------------- */

/**
* Configuration data for the roll prompt.
*/
export interface BasicRollDialogConfiguration {
configure?: boolean;
applicationClass?: any;
options?: Record<string, any>;
}


/* -------------------------------------------- */

/**
* Configuration data for creating a roll message.
*/
export interface BasicRollMessageConfiguration {
create?: boolean;
rollMode?: string;
data?: object;
}

export type D20Roll = {
build(
config: BasicRollProcessConfiguration,
dialog: BasicRollDialogConfiguration,
message: BasicRollMessageConfiguration
): D20Roll;
};
100 changes: 67 additions & 33 deletions src/utils/formula.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import type {
} from 'src/types/types';
import { FoundryAdapter } from 'src/foundry/foundry-adapter';
import { isNil } from './data';
import type {
BasicRollConfiguration,
BasicRollDialogConfiguration,
BasicRollMessageConfiguration,
BasicRollProcessConfiguration,
} from 'src/foundry/foundry.types';
import { TidyFlags } from 'src/foundry/TidyFlags';

export function scaleCantripDamageFormula(spell: Item5e, formula: string) {
try {
Expand Down Expand Up @@ -308,12 +315,40 @@ export function getDcTooltip(actor: Actor5e, spellAbility: string) {

type RawSpellAttackType = 'rsak' | 'msak';

export function rollRawSpellAttack(
export async function rollRawSpellAttack(
ev: MouseEvent,
actor: Actor5e,
attackType?: RawSpellAttackType,
spellcastingAbility?: string
) {
const rollConfig: BasicRollProcessConfiguration = {
evaluate: true,
event: ev,
hookNames: ['rawSpellAttack', 'd20Test'],
rolls: [getSpellAttackRoll(actor, attackType, spellcastingAbility)],
subject: actor,
};

let flavorKey =
attackType === 'rsak'
? 'TIDY5E.ActorRangedSpellAttackFlavorText'
: attackType === 'msak'
? 'TIDY5E.ActorMeleeSpellAttackFlavorText'
: 'TIDY5E.ActorSpellAttackFlavorText';

let flavor = FoundryAdapter.localize(flavorKey);

const messageConfig: BasicRollMessageConfiguration = {
rollMode: game.settings.get('core', 'rollMode'),
data: {
'flags.dnd5e.roll': {
type: 'attack',
},
speaker: ChatMessage.getSpeaker({ actor: actor }),
flavor,
},
};

let titleKey =
attackType === 'rsak'
? 'TIDY5E.ActorRangedSpellAttackTitle'
Expand All @@ -325,23 +360,39 @@ export function rollRawSpellAttack(
actorName: actor.name,
});

let flavorKey =
attackType === 'rsak'
? 'TIDY5E.ActorRangedSpellAttackFlavorText'
: attackType === 'msak'
? 'TIDY5E.ActorMeleeSpellAttackFlavorText'
: 'TIDY5E.ActorSpellAttackFlavorText';
const dialog: BasicRollDialogConfiguration = {
options: { title },
};

let flavor = FoundryAdapter.localize(flavorKey);
const rolls = await CONFIG.Dice.D20Roll.build(
rollConfig,
dialog,
messageConfig
);

debug(rolls);
}

function getSpellAttackRoll(
actor: any,
attackType: string | undefined,
spellcastingAbility: string | undefined
): BasicRollConfiguration {
const effectiveAttackType = attackType ?? 'rsak';

const rollData: Record<string, string> = {};
const rollData: Record<string, any> = {};

const parts: string[] = [];

// Ability score modifier
spellcastingAbility ??= actor.system.attributes.spellcasting;
const filteredClass = TidyFlags.classFilter.get(actor);

spellcastingAbility ??= actor.itemTypes.class.find(
(x: Item5e) => x.system.identifier === filteredClass
)?.system.spellcasting?.ability;

const spellcastingMod = actor.system.abilities[spellcastingAbility!]?.mod;

if (spellcastingAbility !== 'none' && spellcastingMod) {
parts.push('@mod');
rollData.mod = spellcastingMod;
Expand All @@ -357,30 +408,13 @@ export function rollRawSpellAttack(
parts.push(actorBonusAttack);
}

const rollConfig = foundry.utils.mergeObject(
{
actor: actor,
data: rollData,
critical: actor.flags['dnd5e']?.spellCriticalThreshold,
title: title,
flavor: flavor,
return {
parts,
data: rollData,
options: {
elvenAccuracy: actor.flags['dnd5e']?.elvenAccuracy ?? false,
halflingLucky: actor.flags['dnd5e']?.halflingLucky ?? false,
dialogOptions: {
width: 400,
top: ev ? ev.clientY - 80 : null,
left: ev ? ev.clientX + 40 : null,
},
messageData: {
'flags.dnd5e.roll': {
type: 'attack',
},
speaker: ChatMessage.getSpeaker({ actor: actor }),
},
event: ev,
},
{}
);
rollConfig.parts = parts;
dnd5e.dice.d20Roll(rollConfig);
subject: actor,
};
}

0 comments on commit 428dcbf

Please sign in to comment.