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

feat(chat): shift click damage buttons for modifier #276 #277

Open
wants to merge 7 commits into
base: release-0.3.0
Choose a base branch
from
12 changes: 10 additions & 2 deletions src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,11 @@
},
"ApplyDamage": "{actor} takes <strong>{amount} damage</strong>.",
"ApplyHealing": "{actor} recovers <strong>{amount} health</strong>.",
"ModifierDialog": {
"DamageTitle": "Modify Damage",
"HealingTitle": "Modify Healing",
"FocusTitle": "Modify Focus Loss"
},
"DamageCalculation": "Damage Calculation: {calculation}",
"ViewRollsDetails": "Roll Details",
"Welcome": "<h2>Welcome to the Cosmere RPG System!</h2> <p>Thank you for trying out the community-developed Cosmere Roleplaying Game system! You're currently using version <strong>{version}</strong>. As this is an early release, some bugs or missing features are to be expected, but your feedback will help us improve the system. </p> <p>For discussions, updates, and support, join us on the <a href=\"{discordLink}\">Metalworks Discord</a> server.</p> <p>If you encounter any issues or have suggestions, we'd love to hear from you! Please report them on our <a href=\"{issuesLink}\">GitHub Issues page</a> or on the <a href=\"{discordLink}\">Metalworks Discord</a>. Be sure to review the <a href=\"{contributingLink}\">contribution guidelines</a> if you'd like to get involved in development. </p> <p>We're excited to have you on board and hope you enjoy playing in the Cosmere!</p>"
Expand Down Expand Up @@ -1110,6 +1115,7 @@
"Document": "Document",
"Formula": "Formula",
"Value": "Value",
"Modifier": "Modifier",
"Skill": "Skill",
"Attribute": "Attribute",
"SkillTest": "Skill Test",
Expand Down Expand Up @@ -1141,7 +1147,8 @@
"Cancel": "Cancel",
"Update": "Update",
"RemoveFavorite": "Remove Favorite",
"Favorite": "Favorite"
"Favorite": "Favorite",
"Confirm": "Confirm"
},
"Notification": {
"GrazeFocusSpent": "Reduced focus by 1",
Expand Down Expand Up @@ -1241,6 +1248,7 @@
"skipDialogDefault": "Skip/Show Dialog",
"skipDialogAdvantage": "Skip Dialog with Advantage",
"skipDialogDisadvantage": "Skip Dialog with Disadvantage",
"skipDialogRaiseStakes": "Skip Dialog with Raise Stakes"
"skipDialogRaiseStakes": "Skip Dialog with Raise Stakes",
"modifyDialogDamageCard": "Show Dialog to Modify Damage Card Value"
}
}
79 changes: 79 additions & 0 deletions src/system/applications/actor/dialogs/damage-card-modifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { SYSTEM_ID } from '@src/system/constants';
import { TEMPLATES } from '@src/system/utils/templates';

// Constants
const TEMPLATE = `systems/${SYSTEM_ID}/templates/${TEMPLATES.DIALOG_CHAT_MODIFY_DAMAGE}`;

interface DamageModifierDialogOptions {
/**
* Whether this is healing.
*/
isHealing: boolean;

/**
* Who is tending to this actor?
*/
action: string;
}

export class DamageModifierDialog extends foundry.applications.api.DialogV2 {
private constructor(
private isHealing: boolean,
private action: string,
private resolve: (modifier: number) => void,
content: string,
) {
super({
window: {
title: `COSMERE.ChatMessage.ModifierDialog.${action === 'reduce-focus' ? 'FocusTitle' : isHealing ? 'HealingTitle' : 'DamageTitle'}`,
},
content,
buttons: [
{
label: 'GENERIC.Button.Confirm',
action: 'submit',
default: true,
// NOTE: Callback must be async
// eslint-disable-next-line @typescript-eslint/require-await
callback: async () => this.onContinue(),
},
],
});
}

/* --- Statics --- */

public static async show(
options: DamageModifierDialogOptions = {
isHealing: false,
action: '',
},
): Promise<number> {
// Render dialog inner HTML
const content = await renderTemplate(TEMPLATE, {
isHealing: options.isHealing,
action: options.action,
});

// Render dialog and wrap as promise
return new Promise((resolve) => {
void new DamageModifierDialog(
options.isHealing,
options.action,
resolve,
content,
).render(true);
});
}

/* --- Actions --- */

private onContinue() {
const form = this.element.querySelector('form')! as HTMLFormElement & {
modifier: HTMLInputElement;
};

// Resolve
this.resolve(form.modifier.value ? form.modifier.valueAsNumber : 0);
}
}
27 changes: 23 additions & 4 deletions src/system/documents/chat-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import { CosmereActor } from './actor';
import { renderSystemTemplate, TEMPLATES } from '../utils/templates';
import { SYSTEM_ID } from '../constants';
import { AdvantageMode } from '../types/roll';
import { getSystemSetting, SETTINGS } from '../settings';
import { getSystemSetting, KEYBINDINGS, SETTINGS } from '../settings';
import {
areKeysPressed,
getApplyTargets,
getConstantFromRoll,
TargetDescriptor,
} from '../utils/generic';
import ApplicationV2 from '@league-of-foundry-developers/foundry-vtt-types/src/foundry/client-esm/applications/api/application.mjs';
import { DamageModifierDialog } from '../applications/actor/dialogs/damage-card-modifier';

export const MESSAGE_TYPES = {
SKILL: 'skill',
Expand Down Expand Up @@ -837,18 +840,28 @@ export class CosmereChatMessage extends ChatMessage {
event.stopPropagation();

const button = event.currentTarget as HTMLElement;
const promptModify = areKeysPressed(
KEYBINDINGS.MODIFY_DIALOG_DAMAGE_CARD,
);
const action = button.dataset.action;
const multiplier = Number(button.dataset.multiplier);

const targets = getApplyTargets();
if (targets.size === 0) return;

if (action === 'apply-damage' && multiplier) {
const modifier = promptModify
? await DamageModifierDialog.show({
isHealing: multiplier < 0,
action: action,
})
: 0;
const damageRolls = forceRolls ?? this.damageRolls;
const damageToApply = damageRolls.map((r) => ({
amount:
(this.useGraze ? (r.graze?.total ?? 0) : (r.total ?? 0)) *
Math.abs(multiplier),
(this.useGraze
? (r.graze?.total ?? 0) + modifier
: (r.total ?? 0) + modifier) * Math.abs(multiplier),
type: multiplier < 0 ? DamageType.Healing : r.damageType,
}));

Expand All @@ -861,12 +874,18 @@ export class CosmereChatMessage extends ChatMessage {
}

if (action === 'reduce-focus') {
const modifier = promptModify
? await DamageModifierDialog.show({
isHealing: multiplier < 0,
action: action,
})
: 0;
await Promise.all(
Array.from(targets).map(async (t) => {
const target = (t as Token).actor as CosmereActor;
return await target.update({
'system.resources.foc.value':
target.system.resources.foc.value - 1,
target.system.resources.foc.value - (1 + modifier),
});
}),
);
Expand Down
5 changes: 5 additions & 0 deletions src/system/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export const KEYBINDINGS = {
SKIP_DIALOG_ADVANTAGE: 'skipDialogAdvantage',
SKIP_DIALOG_DISADVANTAGE: 'skipDialogDisadvantage',
SKIP_DIALOG_RAISE_STAKES: 'skipDialogRaiseStakes',
MODIFY_DIALOG_DAMAGE_CARD: 'modifyDialogDamageCard',
} as const;

/**
Expand Down Expand Up @@ -184,6 +185,10 @@ export function registerSystemKeybindings() {
name: KEYBINDINGS.SKIP_DIALOG_RAISE_STAKES,
editable: [{ key: 'KeyQ' }],
},
{
name: KEYBINDINGS.MODIFY_DIALOG_DAMAGE_CARD,
editable: [{ key: 'ShiftLeft' }, { key: 'ShiftRight' }],
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm of the opinion that it doesn't need to be a separate keybinding and can just use the generic Skip/Show Dialog. For a couple of reasons:

  1. It keeps the behaviour of modifier keys relatively consistent across the application
  2. It avoids us having to add a new keybinding setting every single time we do something with modifiers
  3. If someone wants to change what their alt/shift/ctrl key does, they only need to change one setting and not several
  4. If we separate keybindings too much we run the risk of key modifiers clashing with each other (e.g. if shift key shows dialog in one case but skips dialog in another, it may cause funky behaviour)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't disagree with your points about binding creep necessarily, except that I absolutely do not think the two keybinds should be the same. They're two entirely different functions in different contexts, and it feels incredibly wrong to have them tied together.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only way I can personally see the skip/show dialog keybind being appropriate for this context is if the dialog appearing is the default behavior, and much like the roll dialog, we have a setting to allow it to be skipped by default.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or the inverse. Have it skipped by default but a setting to show it by default. Then the keybind being "skip/show dialog" works as a catch-all.

];

keybindings.forEach((keybind) => {
Expand Down
2 changes: 2 additions & 0 deletions src/system/utils/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ export const TEMPLATES = {

DIALOG_ITEM_EDIT_REWARD: 'item/goal/dialogs/edit-reward.hbs',
DIALOG_ITEM_EDIT_GRANT_RULE: 'item/talent/dialogs/edit-grant-rule.hbs',

DIALOG_CHAT_MODIFY_DAMAGE: 'chat/dialogs/damage-modifier.hbs',
} as const;

/**
Expand Down
8 changes: 8 additions & 0 deletions src/templates/chat/dialogs/damage-modifier.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<form>
<div class="form-group">
<label>{{ localize "GENERIC.Modifier"}}</label>
<div class="form-fields">
<input name="modifier" type="number" step="1" placeholder="+0" autofocus>
</div>
</div>
</form>