Make weapons rollable from the character sheet

This commit is contained in:
Johannes Loher 2021-03-04 00:14:16 +01:00
parent c1c0f41743
commit 3d272f2b92
13 changed files with 173 additions and 66 deletions

View file

@ -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

View file

@ -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;
}
}