diff --git a/css/deltagreen.css b/css/deltagreen.css index 09cf6e2..c2070f5 100644 --- a/css/deltagreen.css +++ b/css/deltagreen.css @@ -1044,6 +1044,32 @@ input.item-sheet-short-input { padding-right: 5px; } +/* + Tome Item Sheet +*/ + +.tome-sheet-horizontal-grid-2col { + display: grid; + grid-template-columns: 100px auto; + align-items: center; + text-align: left; + column-gap: 10px; + row-gap: 10px; + padding-bottom: 10px; + padding-right: 5px; +} + +.tome-sheet-handler-info-horizontal-grid-2col { + display: grid; + grid-template-columns: max-content auto; + align-items: left; + text-align: left; + column-gap: 10px; + row-gap: 10px; + padding-bottom: 10px; + padding-right: 5px; +} + /* Unnatural Character Sheet Specific Classes */ @@ -1146,3 +1172,11 @@ input.item-sheet-short-input { align-self: flex-start; text-align: left; } + +.armor-piercing-chat-card-img { + border-radius: 10%; + height: 20px !important; + width: 20px !important; + margin: 0px; + vertical-align: text-bottom; +} diff --git a/lang/en.json b/lang/en.json index 8aea9ad..ed7450d 100644 --- a/lang/en.json +++ b/lang/en.json @@ -287,5 +287,10 @@ "TYPES.Item.bond": "Bond", "TYPES.Item.gear": "Gear", "TYPES.Item.motivation": "Motivation", - "TYPES.Item.weapon": "Weapon" + "TYPES.Item.weapon": "Weapon", + + "DG.Tome.Name": "Name", + "DG.ItemWindow.Tome.Description": "Description", + "DG.ItemWindow.Tome.LanguagePlaceholder": "Language", + "DG.ItemWindow.Tome.StudyTime": "Study Time" } diff --git a/module/deltagreen.js b/module/deltagreen.js index 6bdf9d7..88fa6a8 100644 --- a/module/deltagreen.js +++ b/module/deltagreen.js @@ -81,6 +81,14 @@ Hooks.on("preCreateItem", (item) => { item.updateSource({ img: "systems/deltagreen/assets/icons/person-black-bg.svg", }); + } else if (item.type === "tome") { + item.updateSource({ + img: "systems/deltagreen/assets/icons/book-cover.svg", + }); + } else if (item.type === "ritual") { + item.updateSource({ + img: "systems/deltagreen/assets/icons/bookmarklet.svg", + }); } else { item.updateSource({ img: "systems/deltagreen/assets/icons/swap-bag-black-bg.svg", diff --git a/module/item/item-sheet.js b/module/item/item-sheet.js index 46c11c2..77ed2d7 100644 --- a/module/item/item-sheet.js +++ b/module/item/item-sheet.js @@ -1,3 +1,10 @@ +import { + DGPercentileRoll, + DGLethalityRoll, + DGDamageRoll, + DGSanityDamageRoll, +} from "../roll/roll.js"; + /** * Extend the basic ItemSheet with some very simple modifications * @extends {ItemSheet} @@ -32,10 +39,19 @@ export default class DeltaGreenItemSheet extends ItemSheet { /** @override */ async getData() { const data = super.getData(); + data.enrichedDescription = await TextEditor.enrichHTML( this.object.system.description, { async: true }, ); + + if (data.item.type === "tome" || data.type === "ritual") { + data.enrichedHandlerNotes = await TextEditor.enrichHTML( + this.object.system.handlerNotes, + { async: true }, + ); + } + return data; } @@ -60,6 +76,115 @@ export default class DeltaGreenItemSheet extends ItemSheet { // eslint-disable-next-line no-useless-return if (!this.options.editable) return; - // Roll handlers, click handlers, etc. would go here. + // Rollable abilities - bind to everything with the 'Rollable' class + html.find(".rollable").click(this._onRoll.bind(this)); + html.find(".rollable").contextmenu(this._onRoll.bind(this)); // this is for right-click, which triggers the roll modifier dialogue for most rolls + + // toggle whether information is shown to a player for a tome or not + html.find(".tome-show-hide-info").click((event) => { + event.preventDefault(); + + this.item.system.revealed = !this.item.system.revealed; + + this.item.update({ "system.revealed": this.item.system.revealed }); + }); + } + + /** + * Handle clickable rolls. + * + * @param {Event} event The originating click event + * @async + * @private + */ + async _onRoll(event) { + event.preventDefault(); + const element = event.currentTarget; + const { dataset } = element; + + if (event && event.which === 2) { + // probably don't want rolls to trigger from a middle mouse click so just kill it here + return; + } + + //const item = this.actor.items.get(dataset.iid); + const rollOptions = { + rollType: dataset.rolltype, + key: dataset.key, + //actor: this, + specialTrainingName: dataset?.name || null, // Only applies to Special Training Rolls + item: this, + }; + + // Create a default 1d100 roll just in case. + let roll = new Roll("1d100", {}); + switch (dataset.rolltype) { + case "stat": + case "skill": + case "sanity": + case "special-training": + case "weapon": + roll = new DGPercentileRoll("1D100", {}, rollOptions); + break; + case "lethality": + roll = new DGLethalityRoll("1D100", {}, rollOptions); + break; + case "damage": { + let diceFormula = item.system.damage; + const { skill } = item.system; + if ( + this.actor.type === "agent" && + (skill === "unarmed_combat" || skill === "melee_weapons") + ) { + diceFormula += + this.actor.system.statistics.str.meleeDamageBonusFormula; + } + roll = new DGDamageRoll(diceFormula, {}, rollOptions); + break; + } + case "sanity-damage": { + let combinedFormula; + if (this.item.type === "tome") { + const { successLoss, failedLoss } = this.item.system.sanity; + combinedFormula = `{${successLoss}, ${failedLoss}}`; + } + roll = new DGSanityDamageRoll(combinedFormula, {}, rollOptions); + break; + } + default: + break; + } + this.processRoll(event, roll, rollOptions); + } + + /** + * Show a dialog for the roll and then send to chat. + * Broke this logic out from `_onRoll()` so that other files can call it, + * namely the macro logic. + * + * TODO: Move this logic to the roll.js. + * + * @param {Event} event The originating click event + * @param {Event} roll The roll to show a dialog for and then send to chat. + * @async + */ + async processRoll(event, roll) { + // Open dialog if user requests it (no dialog for Sanity Damage rolls) + if ( + (event.shiftKey || event.which === 3) && + !(roll instanceof DGSanityDamageRoll) + ) { + const dialogData = await roll.showDialog(); + if (!dialogData) return; + if (dialogData.newFormula) { + roll = new DGDamageRoll(dialogData.newFormula, {}, roll.options); + } + roll.modifier += dialogData.targetModifier; + roll.options.rollMode = dialogData.rollMode; + } + // Evaluate the roll. + await roll.evaluate(); + // Send the roll to chat. + roll.toChat(); } } diff --git a/module/roll/roll.js b/module/roll/roll.js index 7fd7163..04ab32e 100644 --- a/module/roll/roll.js +++ b/module/roll/roll.js @@ -671,7 +671,9 @@ export class DGDamageRoll extends DGRoll { .localize("DG.Roll.Damage") .toUpperCase()} ${game.i18n.localize("DG.Roll.For")} ${ this.item.name - }`; + } (${ + this.item.system.armorPiercing + } armor penetration)`; } catch (ex) { // console.log(ex); label = `Rolling DAMAGE for ${label.toUpperCase()}`; diff --git a/template.json b/template.json index 1700db6..a38dd60 100644 --- a/template.json +++ b/template.json @@ -520,7 +520,15 @@ } }, "Item": { - "types": ["weapon", "armor", "motivation", "bond", "gear"], + "types": [ + "weapon", + "armor", + "motivation", + "bond", + "gear", + "tome", + "ritual" + ], "templates": { "base": { "name": "", @@ -564,6 +572,31 @@ "templates": ["base"], "equipped": true, "expense": "Standard" + }, + "tome": { + "templates": ["base"], + "language": "", + "studyTime": "days", + "unnaturalSkillIncrease": 5, + "occultSkillIncrease": 3, + "sanity": { + "notes": "", + "failedLoss": "1D6", + "successLoss": "1" + }, + "handlerNotes": "", + "revealed": false + }, + "ritual": { + "templates": ["base"], + "studyTime": "weeks", + "studySanCost": "1D10", + "studyUnnaturalIncrease": 1, + "activationSanCost": "1D10", + "activationCosts": "6 WP, 0 POW, 0 HP", + "complexity": "Simple", + "activationTime": "days", + "handlerNotes": "" } } } diff --git a/templates/item/item-tome-sheet.html b/templates/item/item-tome-sheet.html new file mode 100644 index 0000000..70e934b --- /dev/null +++ b/templates/item/item-tome-sheet.html @@ -0,0 +1,96 @@ +
+
+
+ + +
+ {{#if item.system.revealed}} + + {{#if (playerHasGamemasterPrivileges)}}{{/if}} + {{localize 'DG.Tome.Name'}}: + + + + Language: + + + Study Time: + + {{else}} + {{#if (playerHasGamemasterPrivileges)}} + + {{#if (playerHasGamemasterPrivileges)}}{{/if}} + {{localize 'DG.Tome.Name'}}: + + + + Language: + + + Study Time: + + {{else}} + + {{#if (playerHasGamemasterPrivileges)}}{{/if}} + {{localize 'DG.Tome.Name'}}: + + ??? + + Language: + ??? + + Study Time: + ??? + {{/if}} + {{/if}} +
+ +
+ + +
+ + {{!-- Sheet Tab Navigation --}} + + + {{!-- Sheet Body --}} +
+ + {{!-- Description Tab --}} +
+ {{editor enrichedDescription target="system.description" button=true owner=owner editable=editable}} +
+ + {{!-- Handler Notes Tab --}} + {{#if (playerHasGamemasterPrivileges)}} +
+
+ Unnatural Skill Increase: + + + Occult Skill Increase: + + + Study Sanity Damage: + + + / + + + +
+ + Handler Notes: + +
+ {{editor enrichedHandlerNotes target="system.handlerNotes" button=true owner=owner editable=editable}} +
+
+ {{/if}} +
+