From d1bc5f1611ffa48e9b5d8e44f4dc07ee75e68c80 Mon Sep 17 00:00:00 2001 From: phBalance Date: Sat, 14 Sep 2024 19:57:36 -0600 Subject: [PATCH 1/4] fix(knockback): correct apply knockback dialog formatting (HTML and XML) --- module/item/item-attack.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/module/item/item-attack.mjs b/module/item/item-attack.mjs index 497038c6..47188d6f 100644 --- a/module/item/item-attack.mjs +++ b/module/item/item-attack.mjs @@ -1110,7 +1110,7 @@ export async function _onRollKnockback(event) { knockbackResultTotal / 2, )}" data-dtype="Number" /> -

+

NOTE: Don't forget to move the token to the appropriate location as KB movement is not automated. @@ -1157,7 +1157,8 @@ async function _rollApplyKnockback(token, knockbackDice) { // Bogus attack item const pdContentsAttack = ` - + + `; const pdAttack = await new HeroSystem6eItem(HeroSystem6eItem.itemDataFromXml(pdContentsAttack, actor), { From 9b2bfee809db7507d90e1cd2d6a7d8cf7ac1b00c Mon Sep 17 00:00:00 2001 From: phBalance Date: Sat, 14 Sep 2024 20:38:21 -0600 Subject: [PATCH 2/4] refactor(knockback): clean up knockback dialog text --- module/item/item-attack.mjs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/module/item/item-attack.mjs b/module/item/item-attack.mjs index 47188d6f..53fcf892 100644 --- a/module/item/item-attack.mjs +++ b/module/item/item-attack.mjs @@ -1095,11 +1095,15 @@ export async function _onRollKnockback(event) { 2, item.actor, )}${getSystemDisplayUnits(item.actor.system.is5e)} they are knocked into a solid object, - to a maximum of the PD + BODY of the object hit. + to a maximum of the PD + BODY of the object hit. +

+

A character takes 1d6 damage for every ${getRoundedDownDistanceInSystemUnits( 4, item.actor, - )}${getSystemDisplayUnits(item.actor.system.is5e)} knocked back if no object intervenes. + )}${getSystemDisplayUnits(item.actor.system.is5e)} they are knocked back if no object intervenes. +

+

The character typically winds up prone.

@@ -1113,7 +1117,7 @@ export async function _onRollKnockback(event) {

- NOTE: Don't forget to move the token to the appropriate location as KB movement is not automated. + NOTE: Don't forget to move the token to the appropriate location as KB movement is not automated.

`; From 7956aa997aa8ebd8d1150b9e8da5128f045d1045 Mon Sep 17 00:00:00 2001 From: phBalance Date: Sat, 14 Sep 2024 21:33:52 -0600 Subject: [PATCH 3/4] feat(knockback): show knockback rolls and skin them --- CHANGELOG.md | 7 +- module/item/item-attack.mjs | 18 +++-- module/utility/dice.mjs | 133 ++++++++++++++++-------------------- 3 files changed, 76 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d7d6370..f493e422 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Releases -## Version 3.0.96 [Hero System 6e (Unofficial) v2](https://github.com/dmdorman/hero6e-foundryvtt) +## Version 3.0.97 (So far...) [Hero System 6e (Unofficial) v2](https://github.com/dmdorman/hero6e-foundryvtt) + +- Fix apply knockback dialog for Firefox. +- Applying knockback now rolls knockback skinned dice. + +## Version 3.0.96 - Fixes for combat tracker edge cases. diff --git a/module/item/item-attack.mjs b/module/item/item-attack.mjs index 53fcf892..677a569f 100644 --- a/module/item/item-attack.mjs +++ b/module/item/item-attack.mjs @@ -8,7 +8,7 @@ import { performAdjustment, renderAdjustmentChatCards } from "../utility/adjustm import { getRoundedDownDistanceInSystemUnits, getSystemDisplayUnits } from "../utility/units.mjs"; import { HeroSystem6eItem, RequiresASkillRollCheck } from "../item/item.mjs"; import { ItemAttackFormApplication } from "../item/item-attack-application.mjs"; -import { HeroRoller } from "../utility/dice.mjs"; +import { DICE_SO_NICE_CUSTOM_SETS, HeroRoller } from "../utility/dice.mjs"; import { clamp } from "../utility/compatibility.mjs"; import { calculateVelocityInSystemUnits } from "../ruler.mjs"; import { Attack } from "../utility/attack.mjs"; @@ -1153,7 +1153,10 @@ export async function _onRollKnockback(event) { async function _rollApplyKnockback(token, knockbackDice) { const actor = token.actor; - const damageRoller = new HeroRoller().addDice(parseInt(knockbackDice), "Knockback").makeNormalRoll(); + const damageRoller = new HeroRoller() + .setPurpose(DICE_SO_NICE_CUSTOM_SETS.KNOCKBACK) + .addDice(parseInt(knockbackDice), "Knockback") + .makeNormalRoll(); await damageRoller.roll(); const damageRenderedResult = await damageRoller.render(); @@ -1267,8 +1270,9 @@ async function _rollApplyKnockback(token, knockbackDice) { const speaker = ChatMessage.getSpeaker({ actor: actor }); const chatData = { + type: CONST.CHAT_MESSAGE_TYPES.ROLL, + rolls: damageRoller.rawRolls(), user: game.user._id, - content: cardHtml, speaker: speaker, }; @@ -2225,6 +2229,8 @@ export async function _onApplyDamageToSpecificToken(event, tokenId) { const speaker = ChatMessage.getSpeaker({ actor: item.actor }); const chatData = { + type: CONST.CHAT_MESSAGE_TYPES.ROLL, + rolls: damageDetail.knockbackRoller?.rawRolls(), user: game.user._id, content: cardHtml, speaker: speaker, @@ -2801,11 +2807,9 @@ async function _calcKnockback(body, item, options, knockbackMultiplier) { } knockbackRoller = new HeroRoller() + .setPurpose(DICE_SO_NICE_CUSTOM_SETS.KNOCKBACK) .makeBasicRoll() - .addNumber( - body * (knockbackMultiplier > 1 ? knockbackMultiplier : 1), // TODO: Consider supporting multiplication in HeroRoller - "Max potential knockback", - ) + .addNumber(body * (knockbackMultiplier > 1 ? knockbackMultiplier : 1), "Max potential knockback") .addNumber(-parseInt(options.knockbackResistance || 0), "Knockback resistance") .addDice(-Math.max(0, knockbackDice)); await knockbackRoller.roll(); diff --git a/module/utility/dice.mjs b/module/utility/dice.mjs index 36177442..842176f2 100644 --- a/module/utility/dice.mjs +++ b/module/utility/dice.mjs @@ -1,17 +1,6 @@ import { isGameV12OrLater } from "./compatibility.mjs"; -const DICE_SO_NICE_CUSTOM_SETS = { - STUNx: { - colorset: "Stun Multiplier", - foreground: "white", - background: "blue", - edge: "blue", - material: "wood", - fontScale: { - d6: 1.1, - }, - visibility: "visible", - }, +export const DICE_SO_NICE_CUSTOM_SETS = { HIT_LOC: { colorset: "Hit Location - Body Part", foreground: "black", @@ -34,10 +23,30 @@ const DICE_SO_NICE_CUSTOM_SETS = { }, visibility: "visible", }, + KNOCKBACK: { + colorset: "Knockback", + foreground: "black", + background: "orange", + edge: "orange", + material: "wood", + fontScale: { + d6: 1.1, + }, + visibility: "visible", + }, + STUNx: { + colorset: "Stun Multiplier", + foreground: "white", + background: "blue", + edge: "blue", + material: "wood", + fontScale: { + d6: 1.1, + }, + visibility: "visible", + }, }; -const DICE_SO_NICE_CATEGORY_NAME = "Hero System 6e (Unofficial) V2"; - // v11/v12 compatibility shim. // TODO: Cleanup eslint file with these terms const Die = CONFIG.Dice.terms.d; @@ -48,46 +57,26 @@ const OperatorTerm = CONFIG.Dice.termTypes.OperatorTerm; const RollTermClass = foundry.dice?.terms.RollTerm ? foundry.dice.terms.RollTerm : RollTerm; /** - * Add colour sets into Dice So Nice! This allows users to see what the colour set is for each function. + * Add our custom colour sets into Dice So Nice! This allows users to see what the colour set is for each function. * Players can then choose to use that theme for maximum confusion as to which are their rolls and which - * are the extras for hit location or stun multiplier. + * are the extras for hit location, stun multiplier, etc. */ Hooks.once("diceSoNiceReady", (diceSoNice) => { - diceSoNice.addColorset( - { - ...{ - name: "Stun Multiplier", - description: "Stun Multiplier Dice", - category: DICE_SO_NICE_CATEGORY_NAME, - }, - ...DICE_SO_NICE_CUSTOM_SETS.STUNx, - }, - "default", - ); - - diceSoNice.addColorset( - { - ...{ - name: "Hit Location - Body Part", - description: "Hit Location - Body Part Dice", - category: DICE_SO_NICE_CATEGORY_NAME, - }, - ...DICE_SO_NICE_CUSTOM_SETS.HIT_LOC, - }, - "default", - ); - - diceSoNice.addColorset( - { - ...{ - name: "Hit Location - Body Side", - description: "Hit Location - Body Side Dice", - category: DICE_SO_NICE_CATEGORY_NAME, + Object.keys(DICE_SO_NICE_CUSTOM_SETS).forEach((key) => { + const customSet = DICE_SO_NICE_CUSTOM_SETS[key]; + + diceSoNice.addColorset( + { + ...{ + name: customSet.colorset, + description: `${customSet.colorset} Dice`, + category: game.system.title, + }, + ...customSet, }, - ...DICE_SO_NICE_CUSTOM_SETS.HIT_LOC_SIDE, - }, - "default", - ); + "default", + ); + }); }); /** @@ -322,6 +311,20 @@ export class HeroRoller { return this; } + /** + * + * @param {DICE_SO_NICE_CUSTOM_SETS} purpose + * @returns {HeroRoller} + */ + setPurpose(purpose) { + if (purpose && game.settings.get(game.system.id, "DiceSkinning")) { + if (!this._options) this._options = {}; + this._options.appearance = foundry.utils.deepClone(purpose); + } + + return this; + } + /** * V11 and V12 (or later) behave differently. V11 can have a operatorTerm to start * terms but it cannot have negative dice terms. V12, on the other hand, cannot handle @@ -966,14 +969,8 @@ export class HeroRoller { if (this._type === HeroRoller.ROLL_TYPE.KILLING && !this._useHitLocation) { // NOTE: It appears there is no standard effect for the STUNx per APG p 53 // although there don't appear to be any mention of this in other books. - this._killingStunMultiplierHeroRoller = new HeroRoller( - game.settings.get(game.system.id, "DiceSkinning") - ? { - appearance: foundry.utils.deepClone(DICE_SO_NICE_CUSTOM_SETS.STUNx), - } - : {}, - this._buildRollClass, - ) + this._killingStunMultiplierHeroRoller = new HeroRoller({}, this._buildRollClass) + .setPurpose(DICE_SO_NICE_CUSTOM_SETS.STUNx) .makeBasicRoll() .addDieMinus1Min1(this._killingStunMultiplier === "1d6-1" ? 1 : 0) .addHalfDice(this._killingStunMultiplier === "1d3" ? 1 : 0); @@ -997,14 +994,8 @@ export class HeroRoller { let locationName; if (this._alreadyHitLocation === "none") { - this._hitLocationRoller = new HeroRoller( - game.settings.get(game.system.id, "DiceSkinning") - ? { - appearance: foundry.utils.deepClone(DICE_SO_NICE_CUSTOM_SETS.HIT_LOC), - } - : {}, - this._buildRollClass, - ) + this._hitLocationRoller = new HeroRoller({}, this._buildRollClass) + .setPurpose(DICE_SO_NICE_CUSTOM_SETS.HIT_LOC) .makeBasicRoll() .addDice(3); await this._hitLocationRoller.roll(); @@ -1021,14 +1012,8 @@ export class HeroRoller { CONFIG.HERO.sidedLocations.has(locationName) && this._alreadyHitLocationSide === "none" ) { - this._hitSideRoller = new HeroRoller( - game.settings.get(game.system.id, "DiceSkinning") - ? { - appearance: foundry.utils.deepClone(DICE_SO_NICE_CUSTOM_SETS.HIT_LOC_SIDE), - } - : {}, - this._buildRollClass, - ) + this._hitSideRoller = new HeroRoller({}, this._buildRollClass) + .setPurpose(DICE_SO_NICE_CUSTOM_SETS.HIT_LOC_SIDE) .makeBasicRoll() .addDice(1); await this._hitSideRoller.roll(); From 0fdf7d2e1ae3206e6d1867f6052b0f4157ad3b24 Mon Sep 17 00:00:00 2001 From: phBalance Date: Sat, 14 Sep 2024 21:53:25 -0600 Subject: [PATCH 4/4] feat(STUN for END): skin STUN for END dice rolls --- CHANGELOG.md | 1 + module/item/item-attack.mjs | 8 +++++++- module/utility/dice.mjs | 11 +++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f493e422..b84c3213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fix apply knockback dialog for Firefox. - Applying knockback now rolls knockback skinned dice. +- Using STUN for END now uses skinned dice. [#1212](https://github.com/dmdorman/hero6e-foundryvtt/issues/1212) ## Version 3.0.96 diff --git a/module/item/item-attack.mjs b/module/item/item-attack.mjs index 677a569f..09925452 100644 --- a/module/item/item-attack.mjs +++ b/module/item/item-attack.mjs @@ -320,6 +320,8 @@ export async function AttackToHit(item, options) { // ------------------------------------------------- const setManeuver = actor.items.find((o) => o.type == "maneuver" && o.name === "Set" && o.system.active); + let stunForEndHeroRoller = null; + const heroRoller = new HeroRoller() .makeSuccessRoll() .addNumber(11, "Base to hit") @@ -567,7 +569,10 @@ export async function AttackToHit(item, options) { return; } - const stunForEndHeroRoller = new HeroRoller().makeBasicRoll().addDice(stunDice); + stunForEndHeroRoller = new HeroRoller() + .setPurpose(DICE_SO_NICE_CUSTOM_SETS.STUN_FOR_END) + .makeBasicRoll() + .addDice(stunDice); await stunForEndHeroRoller.roll(); const stunRenderedResult = await stunForEndHeroRoller.render(); stunDamageForEnd = stunForEndHeroRoller.getBasicTotal(); @@ -917,6 +922,7 @@ export async function AttackToHit(item, options) { rolls: targetData .map((target) => target.roller?.rawRolls()) .flat() + .concat(stunForEndHeroRoller?.rawRolls()) .filter(Boolean), user: game.user._id, content: cardHtml, diff --git a/module/utility/dice.mjs b/module/utility/dice.mjs index 842176f2..f8c3fd0f 100644 --- a/module/utility/dice.mjs +++ b/module/utility/dice.mjs @@ -45,6 +45,17 @@ export const DICE_SO_NICE_CUSTOM_SETS = { }, visibility: "visible", }, + STUN_FOR_END: { + colorset: "STUN for END", + foreground: "white", + background: "brown", + edge: "brown", + material: "wood", + fontScale: { + d6: 1.1, + }, + visibility: "visible", + }, }; // v11/v12 compatibility shim.