Skip to content

Commit

Permalink
feat(damage rolls): allow damaging items to override graze damage
Browse files Browse the repository at this point in the history
* feat(graze damage override): added initial data model and input fields for override formula

* feat(graze damage): moved override input into the hasSkillRoll condition per PR comments

* feat(graze damage): Added separate roll for graze damage when overridden

* feat(graze damage): fixed item sheet display toggle

* feat(graze damage): Added support for multiple damage rolls with attributable sources. Improved base graze damage calculation

* feat(graze damage): reverted base damage calculation to a simpler apporach for now

* chore: added missed null value check

* feat(graze damage): added hint description to graze override input

* feat(graze damage): spread the damage rolls sent to chat cards

* feat(graze damage): new approach to building graze rolls.
Removed extra addition of modifier by apply damage function.
Removed excess style declaration.

* chore: removed old comment

* feat(graze overrides): updated damage roll class to account for existing dice only option

* feat(graze damage): updated items to store damage.dice better and align our implementation following notes from Brotherwise

* feat(graze damage): added ability for damage rolls to filter multiple terms at once

* chore: updated damaging items to use new filter function rather than placeholder

* feat(graze damage): streamlined a bit of the damage option calculations, fixed typos and linting, updated code comments

* Update hint wording to reflect new variables
  • Loading branch information
zithith authored Nov 13, 2024
1 parent 646b78f commit 2700f10
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 20 deletions.
7 changes: 6 additions & 1 deletion src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,12 @@
},
"Damage": {
"Title": "Damage",
"Formula": "Damage Formula"
"Formula": "Damage Formula",
"GrazeOverride": "Override Graze Damage Calculation",
"GrazeShow": "Show New Graze Damage Formula",
"GrazeHide": "Hide New Graze Damage Forumla",
"GrazeFormula": "New Formula",
"GrazeHint": "Formula entered here will be the entirety of the calculation for graze damage. Any die rolls will be rolled separately from the main damage roll. You can pull in all or part of the main roll using @damage.total/@damage.unmodded/@damage.dice (the latter being the system default) or the selected attribute mod with @mod"
},
"Modality": {
"Title": "Modality",
Expand Down
18 changes: 18 additions & 0 deletions src/style/sheets/item/module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,24 @@ app-item-properties {
}
}

app-item-details-damage {
.form-group-stacked {
.header {
display: flex;
align-items: center;

label {
font-weight: bold;
flex: 2;
}

.controls {
margin-right: 0.5rem;
}
}
}
}

app-item-details-equip {
.form-group-stacked {
.header {
Expand Down
20 changes: 20 additions & 0 deletions src/system/applications/item/components/details-damage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ export class DetailsDamageComponent extends HandlebarsApplicationComponent<
static TEMPLATE =
'systems/cosmere-rpg/templates/item/components/details-damage.hbs';

/* eslint-disable @typescript-eslint/unbound-method */
static ACTIONS = {
'toggle-graze-collapsed': DetailsDamageComponent.onToggleGrazeCollapsed,
};
/* eslint-enable @typescript-eslint/unbound-method */
private grazeOverrideCollapsed = true;

/* --- Actions --- */

private static onToggleGrazeCollapsed(this: DetailsDamageComponent) {
this.grazeOverrideCollapsed = !this.grazeOverrideCollapsed;
void this.render();
}

/* --- Context --- */

public _prepareContext(params: never, context: BaseItemSheetRenderContext) {
Expand All @@ -31,9 +45,15 @@ export class DetailsDamageComponent extends HandlebarsApplicationComponent<
const hasSkill =
hasSkillTest && this.application.item.system.activation.skill;

this.grazeOverrideCollapsed = this.application.item.system.damage
.grazeOverrideFormula
? this.application.item.system.damage.grazeOverrideFormula === ''
: this.grazeOverrideCollapsed;

return {
hasSkillTest,
hasSkill,
grazeInputCollapsed: this.grazeOverrideCollapsed,

typeSelectOptions: {
none: '—',
Expand Down
5 changes: 5 additions & 0 deletions src/system/data/item/mixins/damaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface DamagingItemData {
damage: {
formula?: string;
type?: DamageType;
grazeOverrideFormula?: string;
skill?: Skill;
attribute?: Attribute;
};
Expand All @@ -22,6 +23,10 @@ export function DamagingItemMixin<P extends CosmereItem>() {
nullable: true,
blank: false,
}),
grazeOverrideFormula:
new foundry.data.fields.StringField({
nullable: true,
}),
type: new foundry.data.fields.StringField({
nullable: true,
choices: Object.keys(CONFIG.COSMERE.damageTypes),
Expand Down
72 changes: 72 additions & 0 deletions src/system/dice/damage-roll.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DamageType, Skill, Attribute } from '@system/types/cosmere';
import { CosmereActorRollData } from '@system/documents/actor';
import { AdvantageMode } from '@system/types/roll';
import RollTerm from '@league-of-foundry-developers/foundry-vtt-types/src/foundry/client-esm/dice/terms/term.mjs';

export type DamageRollData<
ActorRollData extends CosmereActorRollData = CosmereActorRollData,
Expand All @@ -15,6 +16,11 @@ export type DamageRollData<
attribute: Attribute;
};
attribute?: number;
damage?: {
total: DamageRoll;
unmodded: DamageRoll;
dice: DamageRoll;
};
};

export interface DamageRollOptions
Expand All @@ -34,6 +40,16 @@ export interface DamageRollOptions
* @default AdvantageMode.None
*/
advantageMode?: AdvantageMode;

/**
* Where did this damage come from?
*/
source?: string;

/**
* Nested Roll item for graze damage
*/
graze?: DamageRoll;
}

export class DamageRoll extends foundry.dice.Roll<DamageRollData> {
Expand Down Expand Up @@ -61,6 +77,18 @@ export class DamageRoll extends foundry.dice.Roll<DamageRollData> {
return this.options.mod;
}

get source(): string | undefined {
return this.options.source;
}

get graze(): DamageRoll | undefined {
return this.options.graze;
}

set graze(roll: DamageRoll) {
this.options.graze = roll;
}

public get hasMod() {
return this.options.mod !== undefined;
}
Expand All @@ -79,8 +107,52 @@ export class DamageRoll extends foundry.dice.Roll<DamageRollData> {
return this.options.advantageMode === AdvantageMode.Disadvantage;
}

/* --- Helper Functions --- */

public removeTermSafely(
conditional: (
value: RollTerm,
index: number,
obj: RollTerm[],
) => boolean,
) {
this.terms.findSplice(conditional);
this.cleanUpTerms();
}

public filterTermsSafely(
condition: (value: RollTerm, index: number, obj: RollTerm[]) => boolean,
) {
this.terms = this.terms.filter(condition);
this.cleanUpTerms();
}

public replaceDieResults(sourceDicePool: foundry.dice.terms.DiceTerm[]) {
sourceDicePool.forEach((die) => {
let numDiceToAlter = die.number ?? 0;
while (numDiceToAlter > 0) {
const nextDie = this.dice.find(
(newDie) => newDie.faces === die.faces,
);
if (!nextDie) return;
nextDie.results = die.results;
numDiceToAlter--;
}
});
this._total = this._evaluateTotal();
}

/* --- Internal Functions --- */

private cleanUpTerms() {
while (
this.terms[this.terms.length - 1] instanceof
foundry.dice.terms.OperatorTerm
)
this.terms.pop();
this.resetFormula();
}

private configureModifiers() {
// Find the first die term
const dieTerm = this.terms.find(
Expand Down
2 changes: 2 additions & 0 deletions src/system/dice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { D20Roll, D20RollOptions, D20RollData } from './d20-roll';
import { DamageRoll, DamageRollOptions, DamageRollData } from './damage-roll';
import { AdvantageMode } from '../types/roll';
import { determineConfigurationMode } from '../utils/generic';
import { RollMode } from './types';

export * from './d20-roll';
export * from './damage-roll';
Expand Down Expand Up @@ -135,6 +136,7 @@ export async function damageRoll(
allowStrings: config.allowStrings,
maximize: config.maximize,
minimize: config.minimize,
source: config.source,
});

// Evaluate the roll
Expand Down
4 changes: 3 additions & 1 deletion src/system/documents/actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,9 @@ export class CosmereActor<
public async useItem(
item: CosmereItem,
options?: Omit<CosmereItem.UseOptions, 'actor'>,
): Promise<D20Roll | [D20Roll, DamageRoll] | null> {
): Promise<D20Roll | [D20Roll, ...DamageRoll[]] | null> {
// Checks for relevant Active Effects triggers/manual toggles will go here
// E.g. permanent/conditional: attack bonuses, damage riders, auto opportunity/complications, etc.
return item.use({ ...options, actor: this });
}

Expand Down
10 changes: 5 additions & 5 deletions src/system/documents/chat-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export class CosmereChatMessage extends ChatMessage {
'COSMERE.ChatMessage.Action.ApplyGraze',
),
icon: 'fa-solid fa-shield-halved',
callback: this.onApplyDamage.bind(this, false),
callback: this.onApplyDamage.bind(this),
},
]
: []),
Expand Down Expand Up @@ -317,7 +317,7 @@ export class CosmereChatMessage extends ChatMessage {
);
}

private onApplyDamage(includeMod = true) {
private onApplyDamage() {
// Get selected actor
const actor = (game.canvas!.tokens!.controlled?.[0]?.actor ??
game.user?.character) as CosmereActor | undefined;
Expand All @@ -333,13 +333,13 @@ export class CosmereChatMessage extends ChatMessage {
// Apply damage
void actor.applyDamage(
...damageRolls.map((r) => ({
amount: (r.total ?? 0) + (includeMod ? (r.mod ?? 0) : 0),
amount: r.total ?? 0,
type: r.damageType,
})),
);
}

private onApplyHealing(includeMod = true) {
private onApplyHealing() {
// Get selected actor
const actor = (game.canvas!.tokens!.controlled?.[0]?.actor ??
game.user?.character) as CosmereActor | undefined;
Expand All @@ -355,7 +355,7 @@ export class CosmereChatMessage extends ChatMessage {
// Apply damage
void actor.applyDamage(
...damageRolls.map((r) => ({
amount: (r.total ?? 0) + (includeMod ? (r.mod ?? 0) : 0),
amount: r.total ?? 0,
type: DamageType.Healing,
})),
);
Expand Down
Loading

0 comments on commit 2700f10

Please sign in to comment.