Skip to content

Commit

Permalink
Merge pull request dmdorman#1227 from phBalance/phBalance/more-dice-s…
Browse files Browse the repository at this point in the history
…kinning

More dice skinning
  • Loading branch information
phBalance authored Sep 15, 2024
2 parents fefdd68 + 0fdf7d2 commit cb9eaa2
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 88 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# 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.
- Using STUN for END now uses skinned dice. [#1212](https://github.com/dmdorman/hero6e-foundryvtt/issues/1212)

## Version 3.0.96

- Fixes for combat tracker edge cases.

Expand Down
41 changes: 28 additions & 13 deletions module/item/item-attack.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1095,11 +1101,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.
</p>
<p>
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.
</p>
<p>
The character typically winds up prone.
</p>
Expand All @@ -1110,10 +1120,10 @@ export async function _onRollKnockback(event) {
knockbackResultTotal / 2,
)}" data-dtype="Number" />
</div>
<p/>
</p>
<p>
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.
</p>
</form>
`;
Expand Down Expand Up @@ -1149,15 +1159,19 @@ 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();

// Bogus attack item
const pdContentsAttack = `
<POWER XMLID="ENERGYBLAST" ID="1695402954902" BASECOST="0.0" LEVELS="${damageRoller.getBaseTotal()}" ALIAS="Knockback" POSITION="0" MULTIPLIER="1.0" GRAPHIC="Burst" COLOR="255 255 255" SFX="Default" SHOW_ACTIVE_COST="Yes" INCLUDE_NOTES_IN_PRINTOUT="Yes" INPUT="PD" USESTANDARDEFFECT="No" QUANTITY="1" AFFECTS_PRIMARY="No" AFFECTS_TOTAL="Yes">
<MODIFIER XMLID="NOKB" ID="1716671836182" BASECOST="-0.25" LEVELS="0" ALIAS="No Knockback" POSITION="-1" MULTIPLIER="1.0" GRAPHIC="Burst" COLOR="255 255 255" SFX="Default" SHOW_ACTIVE_COST="Yes" INCLUDE_NOTES_IN_PRINTOUT="Yes" NAME="" COMMENTS="" PRIVATE="No" FORCEALLOW="No">
<MODIFIER XMLID="NOKB" ID="1716671836182" BASECOST="-0.25" LEVELS="0" ALIAS="No Knockback" POSITION="-1" MULTIPLIER="1.0" GRAPHIC="Burst" COLOR="255 255 255" SFX="Default" SHOW_ACTIVE_COST="Yes" INCLUDE_NOTES_IN_PRINTOUT="Yes" NAME="" COMMENTS="" PRIVATE="No" FORCEALLOW="No">
</MODIFIER>
</POWER>
`;
const pdAttack = await new HeroSystem6eItem(HeroSystem6eItem.itemDataFromXml(pdContentsAttack, actor), {
Expand Down Expand Up @@ -1262,8 +1276,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,
};
Expand Down Expand Up @@ -2220,6 +2235,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,
Expand Down Expand Up @@ -2796,11 +2813,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();
Expand Down
144 changes: 70 additions & 74 deletions module/utility/dice.mjs
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -34,10 +23,41 @@ 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",
},
STUN_FOR_END: {
colorset: "STUN for END",
foreground: "white",
background: "brown",
edge: "brown",
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;
Expand All @@ -48,46 +68,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",
);
});
});

/**
Expand Down Expand Up @@ -322,6 +322,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
Expand Down Expand Up @@ -966,14 +980,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);
Expand All @@ -997,14 +1005,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();
Expand All @@ -1021,14 +1023,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();
Expand Down

0 comments on commit cb9eaa2

Please sign in to comment.