From 67767da1097703f68e6a7bb2eded60b32a5e5560 Mon Sep 17 00:00:00 2001 From: Jonas Karlsson Date: Sun, 30 Aug 2020 13:17:13 +0200 Subject: [PATCH] feat: Issue #59 Add support for advantage/disadvantage If you shift-click a skill a dialog where you can choose advantage/disadvantage, difficulty and modifiers show up. Note that this skill roll dialog uses the Cepheus Engine rules, i.e. it adds a modifier for difficulty rather than change the target number. A roll above 8 is considered a success. The Effect of the roll is the result - 8. I'd like to make the result row show 'Effect: 0' (or whatever the result was), but haven't figured out how yet, so that will have to wait for another time. --- package-lock.json | 18 +-- src/module/config.ts | 20 +++- src/module/sheets/TwodsixActorSheet.ts | 66 ++++++++--- src/module/utils/TwodsixRolls.ts | 105 ++++++++++++++++++ src/twodsix.ts | 2 +- .../templates/actors/parts/actor-skills.html | 39 +++---- static/templates/chat/roll-dialog.html | 41 +++++++ 7 files changed, 243 insertions(+), 48 deletions(-) create mode 100644 src/module/utils/TwodsixRolls.ts create mode 100644 static/templates/chat/roll-dialog.html diff --git a/package-lock.json b/package-lock.json index 996b80a94..faaada3c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1760,19 +1760,19 @@ "dev": true }, "copy-webpack-plugin": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.0.3.tgz", - "integrity": "sha512-q5m6Vz4elsuyVEIUXr7wJdIdePWTubsqVbEMvf1WQnHGv0Q+9yPRu7MtYFPt+GBOXRav9lvIINifTQ1vSCs+eA==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.0.4.tgz", + "integrity": "sha512-zCazfdYAh3q/O4VzZFiadWGpDA2zTs6FC6D7YTHD6H1J40pzo0H4z22h1NYMCl4ArQP4CK8y/KWqPrJ4rVkZ5A==", "dev": true, "requires": { - "cacache": "^15.0.4", + "cacache": "^15.0.5", "fast-glob": "^3.2.4", "find-cache-dir": "^3.3.1", "glob-parent": "^5.1.1", "globby": "^11.0.1", "loader-utils": "^2.0.0", "normalize-path": "^3.0.0", - "p-limit": "^3.0.1", + "p-limit": "^3.0.2", "schema-utils": "^2.7.0", "serialize-javascript": "^4.0.0", "webpack-sources": "^1.4.3" @@ -3202,7 +3202,7 @@ "dev": true }, "foundry-pc-types": { - "version": "gitlab:foundry-projects/foundry-pc/foundry-pc-types#6ead51c20f4027a3ed0ce7ab474bb54a645269af", + "version": "gitlab:foundry-projects/foundry-pc/foundry-pc-types#1a46d0fdef5dc851a4744f5062754d70f30a4a73", "from": "gitlab:foundry-projects/foundry-pc/foundry-pc-types", "dev": true, "requires": { @@ -10724,9 +10724,9 @@ "optional": true }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.3.0.tgz", + "integrity": "sha512-Q9Q9RlMM08eWfdPPmDDrXd8Ny3R1sY/DaRDR2zTPPneJ6GYiLx3++fPiZobv49ovkYAnHl/P72Ie3HWXIRVVYA==", "dev": true, "requires": { "punycode": "^2.1.0" diff --git a/src/module/config.ts b/src/module/config.ts index 491524cff..d98eb4de3 100644 --- a/src/module/config.ts +++ b/src/module/config.ts @@ -4,10 +4,28 @@ export const TWODSIX:any = {}; /** * The sets of rules variants one can use - * TODO Should be loaded from json, really + * Not currently used for anything. TODO Remove? * @type {Object} */ TWODSIX.VARIANTS = { "ce": "Cepheus Engine", } +TWODSIX.ROLLTYPES = { + Advantage: "3d6kh2", + Normal: "2d6", + Disadvantage: "3d6kl2" +} + +//This is defined the CE way, but it's mathematically equivalent to other variants. +TWODSIX.DIFFICULTIES = { + Simple: 6, + Easy: 4, + Routine: 2, + Average: 0, + Difficult: -2, + VeryDifficult: -4, + Formidable: -6, + Impossible: -8 +} + diff --git a/src/module/sheets/TwodsixActorSheet.ts b/src/module/sheets/TwodsixActorSheet.ts index 9d72a9b02..86a875fae 100644 --- a/src/module/sheets/TwodsixActorSheet.ts +++ b/src/module/sheets/TwodsixActorSheet.ts @@ -1,3 +1,6 @@ +import TwodsixItem from "../entities/TwodsixItem"; +import {TwodsixRolls} from "../utils/TwodsixRolls"; + export class TwodsixActorSheet extends ActorSheet { /** @@ -39,13 +42,13 @@ export class TwodsixActorSheet extends ActorSheet { for (const i of sheetData.items) { i.img = i.img || CONST.DEFAULT_TOKEN; // Append to gear. - if (i.type === 'storage'){storage.push(i);} - if (i.type === 'inventory'){inventory.push(i);} - if (i.type === 'equipment'){equipment.push(i);} - if (i.type === 'weapon'){weapon.push(i);} - if (i.type === 'armor'){armor.push(i);} - if (i.type === 'augment'){augment.push(i);} - if (i.type === 'skills'){skills.push(i);} + if (i.type === 'storage') {storage.push(i);} + if (i.type === 'inventory') {inventory.push(i);} + if (i.type === 'equipment') {equipment.push(i);} + if (i.type === 'weapon') {weapon.push(i);} + if (i.type === 'armor') {armor.push(i);} + if (i.type === 'augment') {augment.push(i);} + if (i.type === 'skills') {skills.push(i);} } // Assign and return actorData.storage = storage; @@ -144,22 +147,55 @@ export class TwodsixActorSheet extends ActorSheet { * @param {Event} event The originating click event * @private */ - _onRoll(event:{ preventDefault:() => void; currentTarget:any; }):void { + _onRoll(event: { preventDefault: any; currentTarget: any; shiftKey?: any; }):void { event.preventDefault(); const element = event.currentTarget; const dataset = element.dataset; + const itemId = $(event.currentTarget).parents('.item').attr('data-item-id'); + const item = this.actor.getOwnedItem(itemId) as TwodsixItem; + 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 - }); + if (item.type === 'skills' && event.shiftKey) { + this.rollSkill(itemId, event, dataset); + } else { + 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 + }); + } + } + } + + rollSkill( + skillId:string, + event:{ preventDefault:() => void; currentTarget:any; }, + dataset:{ roll:string; } + ):Promise { + + const skillData = {}; + const skills = this.getData().actor.skills; + if (!skills.length) { + return; } + + const rollParts = dataset.roll.split("+"); + + const flavorParts:string[] = []; + flavorParts.push(`${skills[0].name}`); + + return TwodsixRolls.Roll({ + parts: rollParts, + data: skillData, + flavorParts: flavorParts, + title: `${skills[0].name}`, + speaker: ChatMessage.getSpeaker({actor: this.getData().actor}), + }); } - //Unused, but something like is needed to support cascade/subskills, so letting it stay for now. + //Unused, but something like it is needed to support cascade/subskills, so letting it stay for now. /** * Handle skill upgrade * @param {Event} event The originating click event diff --git a/src/module/utils/TwodsixRolls.ts b/src/module/utils/TwodsixRolls.ts new file mode 100644 index 000000000..0f1155b8f --- /dev/null +++ b/src/module/utils/TwodsixRolls.ts @@ -0,0 +1,105 @@ +export class TwodsixRolls { + static async Roll({ + parts = [], + data = {}, + flavorParts = [], + title = null, + speaker = null, + } = {}):Promise { + let rolled = false; + const usefulParts = parts.filter(function (el) { + return el != '' && el; + }); + + const template = 'systems/twodsix/templates/chat/roll-dialog.html'; + const dialogData = { + formula: usefulParts.join(' '), + data: data, + rollType: CONFIG.TWODSIX.ROLLTYPES.Normal, + rollTypes: CONFIG.TWODSIX.ROLLTYPES, + rollMode: game.settings.get('core', 'rollMode'), + rollModes: CONFIG.Dice.rollModes, + difficulty: CONFIG.TWODSIX.DIFFICULTIES.Normal, + difficulties: CONFIG.TWODSIX.DIFFICULTIES + }; + + const buttons = { + ok: { + label: "Roll", + icon: '', + callback: (html) => { + roll = TwodsixRolls._handleRoll({ + form: html[0].children[0], + rollParts: usefulParts, + flavorParts, + speaker, + }); + rolled = true; + }, + }, + cancel: { + icon: '', + label: "Cancel", + }, + }; + + const html = await renderTemplate(template, dialogData); + let roll:Roll; + return new Promise((resolve) => { + new Dialog({ + title: title, + content: html, + buttons: buttons, + default: 'ok', + close: () => { + resolve(rolled ? roll : false); + }, + }).render(true); + }); + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static _handleRoll({form = null, rollParts = [], data = {}, flavorParts = [], speaker = null,}):Roll { + let rollMode = game.settings.get('core', 'rollMode'); + + if (form !== null) { + data['bonus'] = form.bonus.value; + data['difficulty'] = form.difficulty.value; + data['rollType'] = form.rollType.value; + data['rollMode'] = form.rollMode.value; + } + + if (data['bonus'] && data['bonus'].length > 0) { + rollParts.push("" + data['bonus']); + } + + if (data['difficulty'] && data['difficulty'].length > 0) { + rollParts.push("" + CONFIG.TWODSIX.DIFFICULTIES[data['difficulty']]); + flavorParts.unshift(`${data['difficulty']}`); + } + + flavorParts.unshift("Rolling:"); + + if (data['rollType'] && data['rollType'].length > 0) { + rollParts[0] = CONFIG.TWODSIX.ROLLTYPES[data['rollType']]; + flavorParts.push("with"); + flavorParts.push(`${data['rollType']}`); + } + + //So that the result is the Effect of the skill roll. + rollParts.push("-8"); + + const roll = new Roll(rollParts.join('+'), data).roll(); + const flavor = flavorParts.join(' '); + + rollMode = form ? form.rollMode.value : rollMode; + roll.toMessage( + { + speaker: speaker, + flavor: flavor + }, + {rollMode: rollMode} + ); + return roll; + } +} diff --git a/src/twodsix.ts b/src/twodsix.ts index 70ae74b4e..9f2894a45 100644 --- a/src/twodsix.ts +++ b/src/twodsix.ts @@ -57,13 +57,13 @@ Hooks.once('init', async function () { /** * Set an initiative formula for the system - * TODO Should be done via a setting * @type {String} */ CONFIG.Combat.initiative = { formula: "1d6", decimals: 1 }; + registerHandlebarsHelpers(); registerSettings(); await preloadTemplates(); diff --git a/static/templates/actors/parts/actor-skills.html b/static/templates/actors/parts/actor-skills.html index 89b75005c..7c7a49f34 100644 --- a/static/templates/actors/parts/actor-skills.html +++ b/static/templates/actors/parts/actor-skills.html @@ -13,27 +13,22 @@ {{#each actor.skills as |item id|}}
- -
    -
  1. - - roll skill - {{item.name}} - {{data.value}} - {{data.characteristic}} ({{numberFormat data.mod decimals=0 sign=true}}) - - - {{data.total}} - - - - - - - - -
  2. -
-
+ +
    +
  1. + + + {{item.name}} + {{data.value}} + {{data.characteristic}} ({{numberFormat data.mod decimals=0 sign=true}}) + {{data.total}} + + + + + +
  2. +
+
{{/each}} diff --git a/static/templates/chat/roll-dialog.html b/static/templates/chat/roll-dialog.html new file mode 100644 index 000000000..2a7c977cc --- /dev/null +++ b/static/templates/chat/roll-dialog.html @@ -0,0 +1,41 @@ +
+
Note that this skill roll dialog uses the Cepheus Engine rules, i.e. it adds a modifier for difficulty rather than change the target number. It then subtracts 8 from the result, showing the Effect of the skill roll.
+
+ +
+
+ +
+
+ +
+
+ +
+