ds4/src/module/item/item.ts

120 lines
4.4 KiB
TypeScript
Raw Normal View History

import { DS4Actor } from "../actor/actor";
import { DS4 } from "../config";
import { createCheckRoll } from "../rolls/check-factory";
import { AttackType, DS4ItemData } from "./item-data";
2020-12-23 18:23:26 +01:00
2020-12-23 16:52:20 +01:00
/**
2021-02-07 11:51:36 +01:00
* The Item class for DS4
2020-12-23 16:52:20 +01:00
*/
2021-01-26 03:55:18 +01:00
export class DS4Item extends Item<DS4ItemData> {
2020-12-23 16:52:20 +01:00
/**
2021-02-07 11:51:36 +01:00
* @override
2020-12-23 16:52:20 +01:00
*/
2020-12-23 18:23:26 +01:00
prepareData(): void {
2020-12-23 16:52:20 +01:00
super.prepareData();
this.prepareDerivedData();
2020-12-23 16:52:20 +01:00
}
prepareDerivedData(): void {
2021-02-05 03:42:42 +01:00
if (this.data.type === "talent") {
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 {
return "equipped" in this.data.data && !this.data.data.equipped;
}
/**
* The number of times that active effect changes originating from this item should be applied.
*/
get activeEffectFactor(): number | undefined {
if (this.data.type === "talent") {
return this.data.data.rank.total;
}
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 ownerDataData = ((this.actor as unknown) as DS4Actor).data.data; // 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 = (ownerDataData.combatValues[combatValue].total as number) + weaponBonus;
await createCheckRoll(checkTargetValue, {
rollMode: game.settings.get("core", "rollMode"),
maxCritSuccess: ownerDataData.rolling.maximumCoupResult,
minCritFailure: ownerDataData.rolling.minimumFumbleResult,
});
}
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;
}
2020-12-23 16:52:20 +01:00
}