Add functionality for common checks, which can be affected by effects and be performed as macros

This commit is contained in:
Johannes Loher 2021-03-24 09:19:26 +01:00
parent 7fad6416fc
commit f038509910
8 changed files with 202 additions and 7 deletions

View file

@ -1,4 +1,5 @@
import { ModifiableDataBaseTotal, ResourceDataBaseTotalMax } from "../common/common-data";
import { DS4 } from "../config";
import {
DS4ActorDataHelper,
DS4CharacterDataDataBaseInfo,
@ -21,6 +22,7 @@ interface DS4ActorPreparedDataDataBase {
traits: DS4ActorPreparedDataDataTraits;
combatValues: DS4ActorPreparedDataDataCombatValues;
rolling: DS4ActorPreparedDataDataRolling;
checks: DS4ActorPreparedDataDataChecks;
}
interface DS4ActorPreparedDataDataAttributes {
@ -54,6 +56,12 @@ interface DS4ActorPreparedDataDataRolling {
minimumFumbleResult: number;
}
export type Check = keyof typeof DS4.i18n.checks;
type DS4ActorPreparedDataDataChecks = {
[key in Check]: number;
};
// types
interface DS4CharacterPreparedDataData extends DS4ActorPreparedDataDataBase {

View file

@ -2,8 +2,9 @@ import { ModifiableDataBaseTotal } from "../common/common-data";
import { DS4 } from "../config";
import { DS4Item } from "../item/item";
import { ItemType } from "../item/item-data";
import { createCheckRoll } from "../rolls/check-factory";
import { DS4ActorData } from "./actor-data";
import { DS4ActorPreparedData } from "./actor-prepared-data";
import { Check, DS4ActorPreparedData } from "./actor-prepared-data";
/**
* The Actor class for DS4
@ -108,13 +109,20 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
*/
prepareDerivedData(): void {
this._prepareCombatValues();
this._prepareChecks();
}
/**
* The list of properties that are derived from others, given in dot notation.
*/
get derivedDataProperties(): Array<string> {
return Object.keys(DS4.i18n.combatValues).map((combatValue) => `data.combatValues.${combatValue}.total`);
return Object.keys(DS4.i18n.combatValues)
.map((combatValue) => `data.combatValues.${combatValue}.total`)
.concat(
Object.keys(DS4.i18n.checks)
.filter((check) => check !== "defend")
.map((check) => `data.checks.${check}`),
);
}
/**
@ -122,6 +130,7 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
*/
prepareFinalDerivedData(): void {
this.data.data.combatValues.hitPoints.max = this.data.data.combatValues.hitPoints.total;
this.data.data.checks.defend = this.data.data.combatValues.defense.total;
}
/**
@ -129,7 +138,7 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
* given in dot notation.
*/
get finalDerivedDataProperties(): string[] {
return ["data.combatValues.hitPoints.max"];
return ["data.combatValues.hitPoints.max", "data.checks.defend"];
}
/**
@ -204,6 +213,42 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
.reduce((a, b) => a + b, 0);
}
/**
* Prepares the check target numbers of checks for the actor.
*/
protected _prepareChecks(): void {
const data = this.data.data;
data.checks = {
appraise: data.attributes.mind.total + data.traits.intellect.total,
changeSpell: data.attributes.mind.total + data.traits.intellect.total,
climb: data.attributes.mobility.total + data.traits.strength.total,
communicate: data.attributes.mind.total + data.traits.dexterity.total,
decipherScript: data.attributes.mind.total + data.traits.intellect.total,
defend: 0, // assigned in prepareFinalDerivedData as it must always match data.combatValues.defense.total and is not changeable by effects
defyPoison: data.attributes.body.total + data.traits.constitution.total,
disableTraps: data.attributes.mind.total + data.traits.dexterity.total,
featOfStrength: data.attributes.body.total + data.traits.strength.total,
flirt: data.attributes.mind.total + data.traits.aura.total,
haggle: data.attributes.mind.total + Math.max(data.traits.intellect.total, data.traits.intellect.total),
hide: data.attributes.mobility.total + data.traits.agility.total,
jump: data.attributes.mobility.total + data.traits.agility.total,
knowledge: data.attributes.mind.total + data.traits.intellect.total,
openLock: data.attributes.mind.total + data.traits.dexterity.total,
perception: Math.max(data.attributes.mind.total + data.traits.intellect.total, 8),
pickPocket: data.attributes.mobility.total + data.traits.dexterity.total,
readTracks: data.attributes.mind.total + data.traits.intellect.total,
resistDisease: data.attributes.body.total + data.traits.constitution.total,
ride: data.attributes.mobility.total + Math.max(data.traits.agility.total, data.traits.aura.total),
search: Math.max(data.attributes.mind.total + data.traits.intellect.total, 8),
sneak: data.attributes.mobility.total + data.traits.agility.total,
startFire: data.attributes.mind.total + data.traits.dexterity.total,
swim: data.attributes.mobility.total + data.traits.strength.total,
wakeUp: data.attributes.mind.total + data.traits.intellect.total,
workMechanism:
data.attributes.mind.total + Math.max(data.traits.dexterity.total, data.traits.intellect.total),
};
}
/**
* Handle how changes to a Token attribute bar are applied to the Actor.
* This only differs from the base implementation by also allowing negative values.
@ -226,4 +271,17 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
const allowed = Hooks.call("modifyTokenAttribute", { attribute, value, isDelta, isBar }, updates);
return allowed !== false ? this.update(updates) : this;
}
/**
* Roll for a given check.
* @param check The check to perform
*/
async rollCheck(check: Check): Promise<void> {
await createCheckRoll(this.data.data.checks[check], {
rollMode: game.settings.get("core", "rollMode") as Const.DiceRollMode, // TODO(types): Type this setting in upstream
maximumCoupResult: this.data.data.rolling.maximumCoupResult,
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
flavor: game.i18n.format("DS4.ActorCheckFlavor", { actor: this.name, check: DS4.i18n.checks[check] }),
});
}
}

View file

@ -281,6 +281,35 @@ export const DS4 = {
days: "DS4.UnitDaysAbbr",
custom: "DS4.UnitCustomAbbr",
},
checks: {
appraise: "DS4.ChecksAppraise",
changeSpell: "DS4.ChangeSpell",
climb: "DS4.ChecksClimb",
communicate: "DS4.ChecksCommunicate",
decipherScript: "DS4.ChecksDecipherScript",
defend: "DS4.ChecksDefend",
defyPoison: "DS4.ChecksDefyPoison",
disableTraps: "DS4.ChecksDisableTraps",
featOfStrength: "DS4.ChecksFeatOfStrength",
flirt: "DS4.ChecksFlirt",
haggle: "DS4.ChecksHaggle",
hide: "DS4.ChecksHide",
jump: "DS4.ChecksJump",
knowledge: "DS4.ChecksKnowledge",
openLock: "DS4.ChecksOpenLock",
perception: "DS4.ChecksPerception",
pickPocket: "DS4.ChecksPickPocket",
readTracks: "DS4.ChecksReadTracks",
resistDisease: "DS4.ChecksResistDisease",
ride: "DS4.ChecksRide",
search: "DS4.ChecksSearch",
sneak: "DS4.ChecksSneak",
startFire: "DS4.ChecksStartFire",
swim: "DS4.ChecksSwim",
wakeUp: "DS4.ChecksWakeUp",
workMechanism: "DS4.ChecksWorkMechanism",
},
},
/**

View file

@ -1,5 +1,7 @@
import { rollCheck } from "./roll-check";
import { rollItem } from "./roll-item";
export const macros = {
rollItem: rollItem,
rollCheck,
rollItem,
};

View file

@ -0,0 +1,43 @@
import { DS4Actor } from "../actor/actor";
import { Check } from "../actor/actor-prepared-data";
import { DS4 } from "../config";
import { getCanvas } from "../helpers";
import notifications from "../ui/notifications";
/**
* Creates a macro from a check drop.
* Get an existing roll check macro if one exists, otherwise create a new one.
* @param check - The name of the check to perform.
* @param slot - The hotbar slot to use.
*/
export async function createRollCheckMacro(check: Check, slot: string): Promise<void> {
const command = `game.ds4.macros.rollCheck("${check}");`;
const macro =
game.macros?.entities.find((m) => m.name === DS4.i18n.checks[check] && m.data.command === command) ??
(await Macro.create(
{
command,
name: DS4.i18n.checks[check],
type: "script",
// TODO: img, should be addressed in https://git.f3l.de/dungeonslayers/ds4/-/issues/79
flags: { "ds4.checkMacro": true },
},
{ displaySheet: false },
));
game.user?.assignHotbarMacro(macro, slot);
}
/**
* Executes the roll check macro for the given check.
*/
export async function rollCheck(check: Check): Promise<void> {
const speaker = ChatMessage.getSpeaker();
const actor = (getCanvas().tokens.get(speaker.token ?? "")?.actor ?? game.actors?.get(speaker.actor ?? "")) as
| DS4Actor
| null
| undefined;
if (!actor) {
return notifications.warn(game.i18n.localize("DS4.WarningMustControlActorToUseRollCheckMacro"));
}
return actor.rollCheck(check);
}

View file

@ -28,7 +28,6 @@ export async function createRollItemMacro(itemData: DS4ItemData, slot: string):
/**
* Executes the roll item macro for the given itemId.
* @param itemId
*/
export async function rollItem(itemId: string): Promise<void> {
const speaker = ChatMessage.getSpeaker();