Make weapons rollable from the character sheet
This commit is contained in:
parent
c1c0f41743
commit
3d272f2b92
13 changed files with 173 additions and 66 deletions
|
@ -124,8 +124,7 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
|
||||
html.find(".item-change").on("change", this._onItemChange.bind(this));
|
||||
|
||||
// Rollable abilities.
|
||||
html.find(".rollable").click(this._onRoll.bind(this));
|
||||
html.find(".rollable-item").on("click", this._onRoll.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -239,17 +238,9 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
*/
|
||||
protected _onRoll(event: JQuery.ClickEvent): void {
|
||||
event.preventDefault();
|
||||
const element = event.currentTarget;
|
||||
const dataset = element.dataset;
|
||||
|
||||
if (dataset.roll) {
|
||||
const roll = new Roll(dataset.roll, this.actor.data.data);
|
||||
const label = dataset.label ? `Rolling ${dataset.label}` : "";
|
||||
roll.roll().toMessage({
|
||||
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
|
||||
flavor: label,
|
||||
});
|
||||
}
|
||||
const id = $(event.currentTarget).parents(".item").data("itemId");
|
||||
const item = this.actor.getOwnedItem(id);
|
||||
item.roll();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
|
|
|
@ -8,7 +8,8 @@ import { DS4CreatureActorSheet } from "./actor/sheets/creature-sheet";
|
|||
import { createCheckRoll } from "./rolls/check-factory";
|
||||
import { registerSystemSettings } from "./settings";
|
||||
import { migration } from "./migrations";
|
||||
import handlebarsHelpers from "./handlebars-helpers";
|
||||
import registerHandlebarsHelpers from "./handlebars/handlebars-helpers";
|
||||
import registerHandlebarsPartials from "./handlebars/handlebars-partials";
|
||||
|
||||
Hooks.once("init", async () => {
|
||||
console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`);
|
||||
|
@ -44,37 +45,6 @@ Hooks.once("init", async () => {
|
|||
registerHandlebarsHelpers();
|
||||
});
|
||||
|
||||
async function registerHandlebarsPartials() {
|
||||
const templatePaths = [
|
||||
"systems/ds4/templates/item/partials/sheet-header.hbs",
|
||||
"systems/ds4/templates/item/partials/description.hbs",
|
||||
"systems/ds4/templates/item/partials/details.hbs",
|
||||
"systems/ds4/templates/item/partials/effects.hbs",
|
||||
"systems/ds4/templates/item/partials/body.hbs",
|
||||
"systems/ds4/templates/actor/partials/items-overview.hbs",
|
||||
"systems/ds4/templates/actor/partials/talents-abilities-overview.hbs",
|
||||
"systems/ds4/templates/actor/partials/spells-overview.hbs",
|
||||
"systems/ds4/templates/actor/partials/overview-add-button.hbs",
|
||||
"systems/ds4/templates/actor/partials/overview-control-buttons.hbs",
|
||||
"systems/ds4/templates/actor/partials/attributes-traits.hbs",
|
||||
"systems/ds4/templates/actor/partials/combat-values.hbs",
|
||||
"systems/ds4/templates/actor/partials/profile.hbs",
|
||||
"systems/ds4/templates/actor/partials/character-progression.hbs",
|
||||
"systems/ds4/templates/actor/partials/special-creature-abilities-overview.hbs",
|
||||
"systems/ds4/templates/actor/partials/character-inventory.hbs",
|
||||
"systems/ds4/templates/actor/partials/creature-inventory.hbs",
|
||||
"systems/ds4/templates/actor/partials/talent-rank-equation.hbs",
|
||||
"systems/ds4/templates/actor/partials/item-list-header.hbs",
|
||||
"systems/ds4/templates/actor/partials/item-list-entry.hbs",
|
||||
"systems/ds4/templates/actor/partials/currency.hbs",
|
||||
];
|
||||
return loadTemplates(templatePaths);
|
||||
}
|
||||
|
||||
function registerHandlebarsHelpers() {
|
||||
Object.entries(handlebarsHelpers).forEach(([key, helper]) => Handlebars.registerHelper(key, helper));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function runs after game data has been requested and loaded from the servers, so entities exist
|
||||
*/
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
export default { htmlToPlainText, isEmpty };
|
||||
|
||||
function htmlToPlainText(input: string | null | undefined): string | null | undefined {
|
||||
if (!input) return;
|
||||
return $(input).text();
|
||||
}
|
||||
|
||||
function isEmpty(input: Array<unknown> | null | undefined): boolean {
|
||||
return (input?.length ?? 0) === 0;
|
||||
}
|
12
src/module/handlebars/handlebars-helpers.ts
Normal file
12
src/module/handlebars/handlebars-helpers.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export default function registerHandlebarsHelpers(): void {
|
||||
Object.entries(helpers).forEach(([key, helper]) => Handlebars.registerHelper(key, helper));
|
||||
}
|
||||
|
||||
const helpers = {
|
||||
htmlToPlainText: (input: string | null | undefined): string | null | undefined => {
|
||||
if (!input) return;
|
||||
return $(input).text();
|
||||
},
|
||||
|
||||
isEmpty: (input: Array<unknown> | null | undefined): boolean => (input?.length ?? 0) === 0,
|
||||
};
|
26
src/module/handlebars/handlebars-partials.ts
Normal file
26
src/module/handlebars/handlebars-partials.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
export default async function registerHandlebarsPartials(): Promise<void> {
|
||||
const templatePaths = [
|
||||
"systems/ds4/templates/item/partials/sheet-header.hbs",
|
||||
"systems/ds4/templates/item/partials/description.hbs",
|
||||
"systems/ds4/templates/item/partials/details.hbs",
|
||||
"systems/ds4/templates/item/partials/effects.hbs",
|
||||
"systems/ds4/templates/item/partials/body.hbs",
|
||||
"systems/ds4/templates/actor/partials/items-overview.hbs",
|
||||
"systems/ds4/templates/actor/partials/talents-abilities-overview.hbs",
|
||||
"systems/ds4/templates/actor/partials/spells-overview.hbs",
|
||||
"systems/ds4/templates/actor/partials/overview-add-button.hbs",
|
||||
"systems/ds4/templates/actor/partials/overview-control-buttons.hbs",
|
||||
"systems/ds4/templates/actor/partials/attributes-traits.hbs",
|
||||
"systems/ds4/templates/actor/partials/combat-values.hbs",
|
||||
"systems/ds4/templates/actor/partials/profile.hbs",
|
||||
"systems/ds4/templates/actor/partials/character-progression.hbs",
|
||||
"systems/ds4/templates/actor/partials/special-creature-abilities-overview.hbs",
|
||||
"systems/ds4/templates/actor/partials/character-inventory.hbs",
|
||||
"systems/ds4/templates/actor/partials/creature-inventory.hbs",
|
||||
"systems/ds4/templates/actor/partials/talent-rank-equation.hbs",
|
||||
"systems/ds4/templates/actor/partials/item-list-header.hbs",
|
||||
"systems/ds4/templates/actor/partials/item-list-entry.hbs",
|
||||
"systems/ds4/templates/actor/partials/currency.hbs",
|
||||
];
|
||||
await loadTemplates(templatePaths);
|
||||
}
|
|
@ -32,10 +32,13 @@ type DS4LanguageData = DS4ItemDataHelper<DS4LanguageDataData, "language">;
|
|||
type DS4AlphabetData = DS4ItemDataHelper<DS4AlphabetDataData, "alphabet">;
|
||||
type DS4SpecialCreatureAbilityData = DS4ItemDataHelper<DS4SpecialCreatureAbilityDataData, "specialCreatureAbility">;
|
||||
|
||||
export type AttackType = keyof typeof DS4["i18n"]["attackTypes"];
|
||||
|
||||
interface DS4WeaponDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical, DS4ItemDataDataEquipable {
|
||||
attackType: "melee" | "ranged" | "meleeRanged";
|
||||
attackType: AttackType;
|
||||
weaponBonus: number;
|
||||
opponentDefense: number;
|
||||
rollable?: boolean;
|
||||
}
|
||||
|
||||
interface DS4ArmorDataData
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { DS4ItemData } from "./item-data";
|
||||
import { DS4Actor } from "../actor/actor";
|
||||
import { DS4 } from "../config";
|
||||
import { createCheckRoll } from "../rolls/check-factory";
|
||||
import { AttackType, DS4ItemData } from "./item-data";
|
||||
|
||||
/**
|
||||
* The Item class for DS4
|
||||
|
@ -17,6 +20,9 @@ export class DS4Item extends Item<DS4ItemData> {
|
|||
const data = this.data.data;
|
||||
data.rank.total = data.rank.base + data.rank.mod;
|
||||
}
|
||||
if (this.data.type === "weapon") {
|
||||
this.data.data.rollable = true;
|
||||
}
|
||||
}
|
||||
|
||||
isNonEquippedEuipable(): boolean {
|
||||
|
@ -32,4 +38,78 @@ export class DS4Item extends Item<DS4ItemData> {
|
|||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll a check for a action with this item.
|
||||
*/
|
||||
async roll(): Promise<void> {
|
||||
if (!this.isOwnedItem()) {
|
||||
throw new Error(game.i18n.format("DS4.ErrorCannotRollUnownedItem", { name: this.name, id: this.id }));
|
||||
}
|
||||
if (this.data.type === "weapon") {
|
||||
await this.rollWeapon();
|
||||
} else {
|
||||
throw new Error(game.i18n.format("DS4.ErrorRollingForItemTypeNotPossible", { type: this.data.type }));
|
||||
}
|
||||
}
|
||||
|
||||
private async rollWeapon(this: this & { readonly isOwned: true }): Promise<void> {
|
||||
if (!(this.data.type === "weapon")) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorWrongItemType", {
|
||||
actualType: this.data.type,
|
||||
expectedType: "weapon",
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const owner = (this.actor as unknown) as DS4Actor; // TODO(types): Improve so that the concrete Actor type is known here
|
||||
const weaponBonus = this.data.data.weaponBonus;
|
||||
const combatValue = await this.getCombatValueKeyForAttackType(this.data.data.attackType);
|
||||
const checkTargetValue = (owner.data.data.combatValues[combatValue].total as number) + weaponBonus;
|
||||
await createCheckRoll(checkTargetValue, { rollMode: game.settings.get("core", "rollMode") }); // TODO: Get maxCritSuccess and minCritFailure from Actor once we store them there
|
||||
}
|
||||
|
||||
private async getCombatValueKeyForAttackType(attackType: AttackType): Promise<"meleeAttack" | "rangedAttack"> {
|
||||
if (attackType === "meleeRanged") {
|
||||
const { melee, ranged } = { ...DS4.i18n.attackTypes };
|
||||
const identifier = "attack-type-selection";
|
||||
const label = game.i18n.localize("DS4.AttackType");
|
||||
const answer = Dialog.prompt({
|
||||
title: game.i18n.localize("DS4.AttackTypeSelection"),
|
||||
content: await renderTemplate("systems/ds4/templates/common/simple-select-form.hbs", {
|
||||
label,
|
||||
identifier,
|
||||
options: { melee, ranged },
|
||||
}),
|
||||
label: game.i18n.localize("DS4.GenericOkButton"),
|
||||
callback: (html) => {
|
||||
const selectedAttackType = html.find(`#${identifier}`).val();
|
||||
if (selectedAttackType !== "melee" && selectedAttackType !== "ranged") {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorUnexpectedAttackType", {
|
||||
actualType: selectedAttackType,
|
||||
expectedTypes: "'melee', 'ranged'",
|
||||
}),
|
||||
);
|
||||
}
|
||||
return `${selectedAttackType}Attack` as const;
|
||||
},
|
||||
render: () => undefined, // TODO(types): This is actually optional, remove when types are updated )
|
||||
options: { jQuery: true },
|
||||
});
|
||||
return answer;
|
||||
} else {
|
||||
return `${attackType}Attack` as const;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-guarding variant to check if the item is owned.
|
||||
*/
|
||||
isOwnedItem(): this is this & { readonly isOwned: true } {
|
||||
return this.isOwned;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ async function askGmModifier(
|
|||
buttons: {
|
||||
ok: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
label: game.i18n.localize("DS4.RollDialogOkButton"),
|
||||
label: game.i18n.localize("DS4.GenericOkButton"),
|
||||
callback: (html) => {
|
||||
if (!("jquery" in html)) {
|
||||
throw new Error(
|
||||
|
@ -160,7 +160,7 @@ async function askGmModifier(
|
|||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("DS4.RollDialogCancelButton"),
|
||||
label: game.i18n.localize("DS4.GenericCancelButton"),
|
||||
},
|
||||
},
|
||||
default: "ok",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue