From 212295069fdea37edfa739acf7adceb97e5246c7 Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Sun, 14 Mar 2021 19:04:28 +0100 Subject: [PATCH] Add functionality to create item macros via drag & drop --- src/lang/de.json | 7 ++++- src/lang/en.json | 9 ++++-- src/module/ds4.ts | 5 +++ src/module/helpers.ts | 6 ++++ src/module/hooks/hooks.ts | 5 +++ src/module/hooks/hotbar-drop.ts | 28 +++++++++++++++++ src/module/item/item.ts | 18 +++++++---- src/module/macros/item-macro.ts | 54 +++++++++++++++++++++++++++++++++ src/module/macros/macros.ts | 5 +++ 9 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 src/module/helpers.ts create mode 100644 src/module/hooks/hooks.ts create mode 100644 src/module/hooks/hotbar-drop.ts create mode 100644 src/module/macros/item-macro.ts create mode 100644 src/module/macros/macros.ts diff --git a/src/lang/de.json b/src/lang/de.json index 3e2c0ae9..3c65708d 100644 --- a/src/lang/de.json +++ b/src/lang/de.json @@ -192,7 +192,12 @@ "DS4.ErrorRollingForItemTypeNotPossible": "Würfeln ist für Items vom Typ '{type}' nicht möglich.", "DS4.ErrorWrongItemType": "Ein Item vom Type '{expectedType}' wurde erwartet aber das Item '{name}' ({id}) ist vom Typ '{actualType}'.", "DS4.ErrorUnexpectedAttackType": "Unerwartete Angriffsart '{actualType}', erwartete Angriffarten: {expectedTypes}", - "DS4.ErrorItemMustBeEquippedToBeRolled": "Um für das Item '{name}' ({id}) vom Typ '{type}' zu würfeln, muss es ausgerüstet sein.", + "DS4.ErrorCanvasIsNotInitialized": "Canvas ist noch nicht initialisiert.", + "DS4.WarningItemMustBeEquippedToBeRolled": "Um für das Item '{name}' ({id}) vom Typ '{type}' zu würfeln, muss es ausgerüstet sein.", + "DS4.WarningMustControlActorToRollItemMacro": "Um ein Item Makro zu nutzen muss ein Aktor kontolliert werden.", + "DS4.WarningControlledActorDoesNotHaveItem": "Der kontrollierte Aktor '{actorName}' ({actorId}) hat kein Item mit der ID '{itemId}'.", + "DS4.WarningItemIsNotRollable": "Für das Item '{name}' ({id}) vom Typ '{type}' kann nicht gewürfelt werden.", + "DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems": "Makros können nur für besessene Items angelegt werden.", "DS4.InfoManuallyEnterSpellBonus": "Der korrekte Wert für den Zauberbonus '{spellBonus}' des Zaubers '{name}' musss manuell angegeben werden.", "DS4.InfoSystemUpdateStart": "Aktualisiere DS4 System von Migrationsversion {currentVersion} auf {targetVersion}. Bitte haben Sie etwas Geduld, schließen Sie nicht das Spiel und fahren Sie nicht den Server herunter.", "DS4.InfoSystemUpdateCompleted": "Aktualisierung des DS4 Systems von Migrationsversion {currentVersion} auf {targetVersion} erfolgreich!", diff --git a/src/lang/en.json b/src/lang/en.json index 056091c6..7a2e5736 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -192,8 +192,13 @@ "DS4.ErrorRollingForItemTypeNotPossible": "Rolling is not possible for items of type '{type}'.", "DS4.ErrorWrongItemType": "Expected an item of type '{expectedType}' but item '{name}' ({id}) is of type '{actualType}'.", "DS4.ErrorUnexpectedAttackType": "Unexpected attack type '{actualType}', expected it to be one of: {expectedTypes}", - "DS4.ErrorItemMustBeEquippedToBeRolled": "To roll for item '{name}' ({id}) of type '{type}', it needs to be equipped.", - "DS4.InfoManuallyEnterSpellBonus": "The correct value of the spell bons '{spellBonus}' of the spell '{name}' needs to be entered by manually.", + "DS4.ErrorCanvasIsNotInitialized": "Canvas is not initialized yet.", + "DS4.WarningItemMustBeEquippedToBeRolled": "To roll for item '{name}' ({id}) of type '{type}', it needs to be equipped.", + "DS4.WarningMustControlActorToRollItemMacro": "You must control an actor to be able to use an item macro.", + "DS4.WarningControlledActorDoesNotHaveItem": "Your controlled actor '{actorName}' ({actorId}) does not have any item with the id '{itemId}'.", + "DS4.WarningItemIsNotRollable": "Item '{name}' ({id}) of type '{type}' is not rollable.", + "DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems": "Macros can only be created for owned items.", + "DS4.InfoManuallyEnterSpellBonus": "The correct value of the spell bonus '{spellBonus}' of the spell '{name}' needs to be entered by manually.", "DS4.InfoSystemUpdateStart": "Migrating DS4 system from migration version {currentVersion} to {targetVersion}. Please be patient and do not close your game or shut down your server.", "DS4.InfoSystemUpdateCompleted": "Migration of DS4 system from migration version {currentVersion} to {targetVersion} successful!", "DS4.UnitRounds": "Rounds", diff --git a/src/module/ds4.ts b/src/module/ds4.ts index 67d03724..710bbd68 100644 --- a/src/module/ds4.ts +++ b/src/module/ds4.ts @@ -10,6 +10,8 @@ import { registerSystemSettings } from "./settings"; import { migration } from "./migrations"; import registerHandlebarsHelpers from "./handlebars/handlebars-helpers"; import registerHandlebarsPartials from "./handlebars/handlebars-partials"; +import { macros } from "./macros/macros"; +import registerHooks from "./hooks/hooks"; Hooks.once("init", async () => { console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`); @@ -20,6 +22,7 @@ Hooks.once("init", async () => { DS4, createCheckRoll, migration, + macros, }; CONFIG.DS4 = DS4; @@ -43,6 +46,8 @@ Hooks.once("init", async () => { await registerHandlebarsPartials(); registerHandlebarsHelpers(); + + registerHooks(); }); /** diff --git a/src/module/helpers.ts b/src/module/helpers.ts new file mode 100644 index 00000000..21a8c7db --- /dev/null +++ b/src/module/helpers.ts @@ -0,0 +1,6 @@ +export function getCanvas(): Canvas { + if (!(canvas instanceof Canvas) || !canvas.ready) { + throw new Error(game.i18n.localize("DS4.ErrorCanvasIsNotInitialized")); + } + return canvas; +} diff --git a/src/module/hooks/hooks.ts b/src/module/hooks/hooks.ts new file mode 100644 index 00000000..3161b8f0 --- /dev/null +++ b/src/module/hooks/hooks.ts @@ -0,0 +1,5 @@ +import registerHotbarHook from "./hotbar-drop"; + +export default function registerHooks(): void { + registerHotbarHook(); +} diff --git a/src/module/hooks/hotbar-drop.ts b/src/module/hooks/hotbar-drop.ts new file mode 100644 index 00000000..9cd7b544 --- /dev/null +++ b/src/module/hooks/hotbar-drop.ts @@ -0,0 +1,28 @@ +import { DS4Item } from "../item/item"; +import { DS4ItemData } from "../item/item-data"; +import { createItemMacro } from "../macros/item-macro"; +import notifications from "../ui/notifications"; + +export default function registerHotbarHook(): void { + Hooks.on("hotbarDrop", async (hotbar: Hotbar, data: { type: string } & Record, slot: string) => { + switch (data.type) { + case "Item": { + if (!("data" in data)) { + return notifications.warn(game.i18n.localize("DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems")); + } + const itemData = data.data as DS4ItemData; + + if (!DS4Item.rollableItemTypes.includes(itemData.type)) { + return notifications.warn( + game.i18n.format("DS4.WarningItemIsNotRollable", { + name: itemData.name, + id: itemData._id, + type: itemData.type, + }), + ); + } + await createItemMacro(itemData, slot); + } + } + }); +} diff --git a/src/module/item/item.ts b/src/module/item/item.ts index ac9b90cd..1374f9fd 100644 --- a/src/module/item/item.ts +++ b/src/module/item/item.ts @@ -2,7 +2,7 @@ import { DS4Actor } from "../actor/actor"; import { DS4 } from "../config"; import { createCheckRoll } from "../rolls/check-factory"; import notifications from "../ui/notifications"; -import { AttackType, DS4ItemData } from "./item-data"; +import { AttackType, DS4ItemData, ItemType } from "./item-data"; /** * The Item class for DS4 @@ -40,6 +40,13 @@ export class DS4Item extends Item { return 1; } + /** + * The list of item types that are rollable. + */ + static get rollableItemTypes(): ItemType[] { + return ["weapon", "spell"]; + } + /** * Roll a check for a action with this item. */ @@ -71,8 +78,8 @@ export class DS4Item extends Item { } if (!this.data.data.equipped) { - throw new Error( - game.i18n.format("DS4.ErrorItemMustBeEquippedToBeRolled", { + return notifications.warn( + game.i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", { name: this.name, id: this.id, type: this.data.type, @@ -104,8 +111,8 @@ export class DS4Item extends Item { } if (!this.data.data.equipped) { - throw new Error( - game.i18n.format("DS4.ErrorItemMustBeEquippedToBeRolled", { + return notifications.warn( + game.i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", { name: this.name, id: this.id, type: this.data.type, @@ -158,7 +165,6 @@ export class DS4Item extends Item { } return `${selectedAttackType}Attack` as const; }, - render: () => undefined, // TODO(types): This is actually optional, remove when types are updated ) options: { jQuery: true }, }); return answer; diff --git a/src/module/macros/item-macro.ts b/src/module/macros/item-macro.ts new file mode 100644 index 00000000..d6ed404b --- /dev/null +++ b/src/module/macros/item-macro.ts @@ -0,0 +1,54 @@ +import { DS4Actor } from "../actor/actor"; +import { getCanvas } from "../helpers"; +import { DS4ItemData } from "../item/item-data"; +import notifications from "../ui/notifications"; + +/** + * Create a Macro from an item drop. + * Get an existing item macro if one exists, otherwise create a new one. + * @param itemData - The item data + * @param slot - The hotbar slot to use + */ +export async function createItemMacro(itemData: DS4ItemData, slot: string): Promise { + const command = `game.ds4.macros.rollItemMacro("${itemData._id}");`; + const macro = + game.macros?.entities.find((m) => m.name === itemData.name && m.data.command === command) ?? + (await Macro.create( + { + command, + name: itemData.name, + type: "script", + img: itemData.img, + flags: { "ds4.itemMacro": true }, + }, + { displaySheet: false }, + )); + game.user?.assignHotbarMacro(macro, (slot as unknown) as number); // TODO(types): adjust so that cast is not needed, slot should be an optional string +} + +/** + * Execute the item macro for the given itemId. + * @param itemId + */ +export async function rollItemMacro(itemId: string): Promise { + 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.WarningMustControlActorToRollItemMacro")); + } + const item = actor.items?.get(itemId); + if (!item) { + return notifications.warn( + game.i18n.format("DS4.WarningControlledActorDoesNotHaveItem", { + actorName: actor.name, + actorId: actor.id, + itemId, + }), + ); + } + + return item.roll(); +} diff --git a/src/module/macros/macros.ts b/src/module/macros/macros.ts new file mode 100644 index 00000000..c4a13e3f --- /dev/null +++ b/src/module/macros/macros.ts @@ -0,0 +1,5 @@ +import { rollItemMacro } from "./item-macro"; + +export const macros = { + rollItemMacro, +};