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

Allow Damaging Items to Override Graze Damage #108

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
306c5b2
feat(graze damage override): added initial data model and input field…
zithith Oct 23, 2024
365dd38
feat(graze damage): moved override input into the hasSkillRoll condit…
zithith Oct 25, 2024
98811bd
feat(graze damage): Added separate roll for graze damage when overridden
zithith Oct 28, 2024
b43b612
feat(graze damage): fixed item sheet display toggle
zithith Oct 28, 2024
e402daf
feat(graze damage): Added support for multiple damage rolls with attr…
zithith Nov 2, 2024
87b7747
Merge branch 'release-0.1.2' of https://github.com/zithith/cosmere-rp…
zithith Nov 2, 2024
3d7a6be
feat(graze damage): reverted base damage calculation to a simpler app…
zithith Nov 2, 2024
72f2ddb
chore: added missed null value check
zithith Nov 2, 2024
c735273
feat(graze damage): added hint description to graze override input
zithith Nov 3, 2024
ebcbb4c
feat(graze damage): spread the damage rolls sent to chat cards
zithith Nov 3, 2024
64961a6
feat(graze damage): new approach to building graze rolls.
zithith Nov 10, 2024
bf761c3
chore: removed old comment
zithith Nov 10, 2024
5cd9382
feat(graze overrides): updated damage roll class to account for exist…
zithith Nov 12, 2024
5da4e43
feat(graze damage): updated items to store damage.dice better and ali…
zithith Nov 12, 2024
d0a67ef
feat(graze damage): added ability for damage rolls to filter multiple…
zithith Nov 12, 2024
dd45443
chore: updated damaging items to use new filter function rather than …
zithith Nov 12, 2024
50edf61
feat(graze damage): streamlined a bit of the damage option calculatio…
zithith Nov 12, 2024
4447074
Update hint wording to reflect new variables
zithith Nov 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,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 @@ -614,7 +614,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