Skip to content

Commit

Permalink
Feat: add Target DM roll setting (#1511)
Browse files Browse the repository at this point in the history
This release adds a new feature where GM's can specify a list of custom modifiers (as new string setting) to be used for item rolls.  Such modifiers are typically Target DM (e.g. target is prone).  For the open rules sets, some of the more common modifiers have been pre-populated.  

The modifier list appears as a select list in the roll dialog.  The details of this new feature are found here:
https://github.com/xdy/twodsix-foundryvtt/wiki/Custom-Target-Modifiers
Thanks to @Zee for the suggestion.

This release also includes Spanish localization updates courtesy of @ForjaSalvaje
  • Loading branch information
marvin9257 authored Dec 13, 2023
1 parent 08f0194 commit 635b4bc
Show file tree
Hide file tree
Showing 14 changed files with 142 additions and 16 deletions.
38 changes: 26 additions & 12 deletions src/module/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ const RULESETS = Object.freeze({
showComponentDM: true,
encumbranceFraction: "0.33",
encumbranceModifier: -1,
useDegreesOfSuccess: 'CE'
useDegreesOfSuccess: 'CE',
targetDMList: "Aiming +1, Cover (half) -1, Cover (three quarter) -2, Cover (full) -4, Movement -1, Dodges -1, Prone (ranged) -2, Prone (melee) +2, Recoil in Zero G -2"
}
},
CEL: {
Expand Down Expand Up @@ -115,7 +116,8 @@ const RULESETS = Object.freeze({
showComponentDM: true,
encumbranceFraction: "0.33",
encumbranceModifier: -1,
useDegreesOfSuccess: 'none'
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (hard) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1, Overwatch w/Shield -2"
}
},
CEFTL: {
Expand Down Expand Up @@ -155,7 +157,8 @@ const RULESETS = Object.freeze({
showComponentDM: false,
encumbranceFraction: "0.33",
encumbranceModifier: 0,
useDegreesOfSuccess: 'none'
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (hard) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1, Overwatch w/Shield -2"
},
},
CEATOM: {
Expand Down Expand Up @@ -195,7 +198,8 @@ const RULESETS = Object.freeze({
showComponentDM: false,
encumbranceFraction: "0.5",
encumbranceModifier: 0,
useDegreesOfSuccess: 'none'
useDegreesOfSuccess: 'none',
targetDMList: ""
}
},
BARBARIC: {
Expand Down Expand Up @@ -234,7 +238,8 @@ const RULESETS = Object.freeze({
showComponentDM: false,
encumbranceFraction: "0.5",
encumbranceModifier: 0,
useDegreesOfSuccess: 'none'
useDegreesOfSuccess: 'none',
targetDMList: ""
},
},
CEQ: {
Expand Down Expand Up @@ -275,7 +280,8 @@ const RULESETS = Object.freeze({
showComponentDM: false,
encumbranceFraction: "0.33",
encumbranceModifier: 0,
useDegreesOfSuccess: 'none'
useDegreesOfSuccess: 'none',
targetDMList: ""
}
},
CD: {
Expand Down Expand Up @@ -323,7 +329,8 @@ const RULESETS = Object.freeze({
showComponentDM: true,
encumbranceFraction: "0.33",
encumbranceModifier: -2,
useDegreesOfSuccess: 'none'
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (hard) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1, Overwatch w/Shield -2"
}
},
CDEE: {
Expand Down Expand Up @@ -371,7 +378,8 @@ const RULESETS = Object.freeze({
showComponentDM: true,
encumbranceFraction: "0.33",
encumbranceModifier: -2,
useDegreesOfSuccess: 'none'
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (hard) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1, Overwatch w/Shield -2"
}
},
CLU: {
Expand Down Expand Up @@ -419,7 +427,8 @@ const RULESETS = Object.freeze({
showComponentDM: true,
encumbranceFraction: "0.33",
encumbranceModifier: -2,
useDegreesOfSuccess: 'none'
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (hard) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1, Overwatch w/Shield -2"
}
},

Expand Down Expand Up @@ -468,7 +477,8 @@ const RULESETS = Object.freeze({
showComponentDM: false,
encumbranceFraction: "0.33",
encumbranceModifier: -1,
useDegreesOfSuccess: 'none'
useDegreesOfSuccess: 'none',
targetDMList: "Obscured -1, Cover (good) -2, Cover (heavy) -3, Cover (total) -4, Running -1, Prone (ranged) -2, Darkness -2, Dim Light -1, Shield -1"
}
},

Expand Down Expand Up @@ -793,6 +803,8 @@ export const WEAPON_RANGE_TYPES = {
}
};

export const TARGET_DM = {};

export type TWODSIX = {
CHARACTERISTICS: typeof CHARACTERISTICS,
CONSUMABLES: typeof CONSUMABLES,
Expand Down Expand Up @@ -823,7 +835,8 @@ export type TWODSIX = {
EQUIPPED_STATES: typeof EQUIPPED_STATES,
EQUIPPED_TOGGLE_OPTIONS: typeof EQUIPPED_TOGGLE_OPTIONS,
RANGE_MODIFIERS_TYPES: typeof RANGE_MODIFIERS_TYPES,
WEAPON_RANGE_TYPES: typeof WEAPON_RANGE_TYPES
WEAPON_RANGE_TYPES: typeof WEAPON_RANGE_TYPES,
TARGET_DM: object
};

export const TWODSIX = {
Expand Down Expand Up @@ -856,5 +869,6 @@ export const TWODSIX = {
EQUIPPED_STATES: EQUIPPED_STATES,
EQUIPPED_TOGGLE_OPTIONS: EQUIPPED_TOGGLE_OPTIONS,
RANGE_MODIFIERS_TYPES: RANGE_MODIFIERS_TYPES,
WEAPON_RANGE_TYPES: WEAPON_RANGE_TYPES
WEAPON_RANGE_TYPES: WEAPON_RANGE_TYPES,
TARGET_DM: TARGET_DM
};
5 changes: 5 additions & 0 deletions src/module/hooks/ready.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { applyToAllActors } from "../utils/migration-utils";
import { correctMissingUntrainedSkill } from "../entities/TwodsixActor";
import { setDocumentPartials, updateStatusIcons } from "../settings/DisplaySettings";
import { switchCss } from "../settings";
import { generateTargetDMObject } from "../utils/targetModifiers";

Hooks.once("ready", async function () {
//Prevent a conflict with Twodsix conditions
if (game.modules.get("combat-utility-belt")?.active) {
Expand Down Expand Up @@ -94,6 +96,9 @@ Hooks.once("ready", async function () {
}
}

//create custom DM List Object
generateTargetDMObject();

});

//This function is a kludge to reset token actors overrides not being calculated correctly on initialize
Expand Down
2 changes: 2 additions & 0 deletions src/module/settings/AdvancedSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export default class AdvancedSettings extends FormApplication {
setting.value = game.settings.get(setting.namespace ?? setting.module, settingName);
if (setting.choices === "Color") {
setting.htmlType = "Color";
} else if (setting.choices === "textarea") {
setting.htmlType = "Textarea";
} else if (setting.choices) {
setting.htmlType = "Select";
} else {
Expand Down
6 changes: 4 additions & 2 deletions src/module/settings/RulsetSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

import AdvancedSettings from "./AdvancedSettings";
import {TWODSIX} from "../config";
import {booleanSetting, numberSetting, stringChoiceSetting, stringSetting} from "./settingsUtils";
import {booleanSetting, largeStringSetting, numberSetting, stringChoiceSetting, stringSetting} from "./settingsUtils";
import { refreshWindow } from "./DisplaySettings";
import { generateTargetDMObject } from "../utils/targetModifiers";

export default class RulesetSettings extends AdvancedSettings {
static create() {
Expand Down Expand Up @@ -96,12 +97,13 @@ export default class RulesetSettings extends AdvancedSettings {
settings.animals_robots.push(booleanSetting("animalTypesIndependentofNiche", false));
settings.animals_robots.push(booleanSetting("displayReactionMorale", false));
settings.damage.push(booleanSetting("useDodgeParry", false));
settings.damage.push(stringSetting("damageTypeOptions", "", false, "world"));
settings.damage.push(largeStringSetting("damageTypeOptions", "", false, "world"));
settings.damage.push(booleanSetting('addEffectToDamage', true, false, "world", checkManualDamageSetting));
settings.damage.push(booleanSetting('addEffectToManualDamage', false, false, "world", checkManualDamageSetting));
settings.roll.push(stringChoiceSetting('useDegreesOfSuccess', "none", true, TWODSIX.SuccessTypes));
settings.roll.push(booleanSetting("overrideSuccessWithNaturalCrit", false));
settings.general.push(stringChoiceSetting('rangeModifierType', "none", true, TWODSIX.RANGE_MODIFIERS_TYPES));
settings.roll.push(largeStringSetting('targetDMList', "", false, "world", generateTargetDMObject));
return settings;
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/module/settings/settingsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export function stringSetting(key: string, defaultValue: string, config = false,
return key;
}

export function largeStringSetting(key: string, defaultValue: string, config = false, scope = 'world', onChange?: ((value: string) => void) | undefined): string {
registerSetting(key.replace('.', ''), scope, config, defaultValue, String, onChange, "textarea");
return key;
}

export function colorSetting(key: string, defaultValue: string, choices = "Color", config = false, scope = 'world', onChange?: ((value: string) => void) | undefined): string {
registerSetting(key.replace('.', ''), scope, config, defaultValue, String, onChange, choices);
return key;
Expand Down
5 changes: 3 additions & 2 deletions src/module/sheets/TwodsixItemSheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,9 @@ export class TwodsixItemSheet extends AbstractTwodsixItemSheet {
*/
export function getDamageTypes(isWeapon:boolean): object {
const returnObject = {};
if (game.settings.get('twodsix', 'damageTypeOptions') !== "") {
let protectionTypeLabels:string[] = game.settings.get('twodsix', 'damageTypeOptions').split(',');
const damageTypeOptions:string = game.settings.get('twodsix', 'damageTypeOptions');
if (damageTypeOptions !== "") {
let protectionTypeLabels:string[] = damageTypeOptions.split(',');
protectionTypeLabels = protectionTypeLabels.map((s:string) => s.trim());
for (const type of protectionTypeLabels) {
Object.assign(returnObject, {[camelCase(type)]: type});
Expand Down
11 changes: 11 additions & 0 deletions src/module/utils/TwodsixDiceRoll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export class TwodsixDiceRoll {
let modifierValue = 0;
if (modifierName === "characteristic") {
modifierValue = this.actor.getCharacteristicModifier(this.rollSettings.rollModifiers[modifierName]);
} else if (modifierName === "targetModifier") {
modifierValue = TWODSIX.TARGET_DM[this.rollSettings.rollModifiers.targetModifier].value;
} else {
modifierValue = this.rollSettings.rollModifiers[modifierName];
}
Expand Down Expand Up @@ -196,6 +198,9 @@ export class TwodsixDiceRoll {
if(this.rollSettings.rollModifiers.weaponsRange !== 0) {
returnValue.push("weaponsRange");
}
if(this.rollSettings.rollModifiers.targetModifier !== "key0") {
returnValue.push("targetModifier");
}
if(this.rollSettings.rollModifiers.attachments !== 0) {
returnValue.push("attachments");
}
Expand Down Expand Up @@ -268,6 +273,12 @@ export class TwodsixDiceRoll {
const charShortName:string = this.rollSettings.displayLabel;
flavorText += (this.rollSettings.skillRoll ? ` &` : ` ${usingString}`) + ` ${charShortName}` + (showModifiers ? `(${characteristicValue})` : ``) + ` ${description}`;
flavorTable += `<tr><td>${description}</td><td>${charShortName}</td><td class="centre">${characteristicValue}</td></tr>`;
} else if (modifierName === "targetModifier") {
const modifierObj = TWODSIX.TARGET_DM[this.rollSettings.rollModifiers.targetModifier];
const modValue = addSign(modifierObj.value);
flavorText += ` + ${description}`;
flavorText += showModifiers ? `(${modValue})` : ``;
flavorTable += `<tr><td>${description}</td><td>${modifierObj.label}</td><td class="centre">${modValue}</td></tr>`;
} else {
switch (modifierName) {
case "item":
Expand Down
8 changes: 8 additions & 0 deletions src/module/utils/TwodsixRollSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import TwodsixActor from "../entities/TwodsixActor";
import { simplifySkillName } from "./utils";
import { effectType } from "../hooks/showStatusIcons";
import { addSign, getCharacteristicFromDisplayLabel } from "./utils";
import { getTargetDMSelectObject } from "./targetModifiers";

export class TwodsixRollSettings {
difficulty:{ mod:number, target:number };
Expand All @@ -22,6 +23,7 @@ export class TwodsixRollSettings {
itemRoll:boolean;
itemName: string;
showRangeModifier: boolean;
showTargetModifier: boolean;
difficulties:CE_DIFFICULTIES | CEL_DIFFICULTIES;
displayLabel:string;
extraFlavor:string;
Expand Down Expand Up @@ -114,6 +116,7 @@ export class TwodsixRollSettings {
this.itemRoll = !!(anItem);
this.itemName = settings?.itemName ?? itemName;
this.showRangeModifier = (game.settings.get('twodsix', 'rangeModifierType') !== 'none' && anItem?.type === "weapon" && settings?.rollModifiers?.rangeLabel) ?? false;
this.showTargetModifier = Object.keys(TWODSIX.TARGET_DM).length > 1;
this.displayLabel = settings?.displayLabel ?? displayLabel;
this.extraFlavor = settings?.extraFlavor ?? "";
this.selectedTimeUnit = "none";
Expand All @@ -132,6 +135,7 @@ export class TwodsixRollSettings {
weaponsHandling: settings?.rollModifiers?.weaponsHandling ?? 0,
weaponsRange: settings?.rollModifiers?.weaponsRange ?? 0,
rangeLabel: settings?.rollModifiers?.rangeLabel ?? "",
targetModifier: settings?.rollModifiers?.targetModifier ?? "key0",
appliedEffects: {},
chain: settings?.rollModifiers?.chain ?? 0,
selectedSkill: aSkill?.uuid,
Expand Down Expand Up @@ -205,6 +209,9 @@ export class TwodsixRollSettings {
skillLabel: this.skillName,
itemLabel: this.itemName,
showRangeModifier: this.showRangeModifier,
showTargetModifier: this.showTargetModifier,
targetModifier: this.rollModifiers.targetModifier,
targetDMList: getTargetDMSelectObject(),
skillRoll: this.skillRoll,
itemRoll: this.itemRoll,
timeUnits: TWODSIX.TimeUnits,
Expand Down Expand Up @@ -235,6 +242,7 @@ export class TwodsixRollSettings {
this.rollModifiers.other = parseInt(buttonHtml.find('[name="rollModifiers.other"]').val(), 10);
this.rollModifiers.wounds = dialogData.showWounds ? parseInt(buttonHtml.find('[name="rollModifiers.wounds"]').val(), 10) : 0;
this.rollModifiers.selectedSkill = dialogData.skillRoll ? buttonHtml.find('[name="rollModifiers.selectedSkill"]').val() : "";
this.rollModifiers.targetModifier = (dialogData.showTargetModifier) ? buttonHtml.find('[name="rollModifiers.targetModifier"]').val() : this.rollModifiers.targetModifier;

if(!dialogData.showEncumbered || !["strength", "dexterity", "endurance"].includes(getKeyByValue(TWODSIX.CHARACTERISTICS, this.rollModifiers.characteristic))) {
//either dont show modifier or not a physical characterisitc
Expand Down
55 changes: 55 additions & 0 deletions src/module/utils/targetModifiers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck This turns off *all* typechecking, make sure to remove this once foundry-vtt-types are updated to cover v10.

import { TWODSIX } from "../config";

/**
* A function that parses the string setting 'targetDMList' into an object and saves the object to TWODSIX.TARGET_DM.
* Always adds a {key0: {value: 0, label:'None'}} entry to object
* @returns {void}
*/
export function generateTargetDMObject():void {
const modifierObject = {
"key0": {
label: game.i18n.localize("TWODSIX.Chat.Roll.RangeModifierTypes.none"),
value: 0
}
};
const parseString:string = game.settings.get('twodsix', 'targetDMList');
if (parseString !== "") {
let i = 1;
const customDMs:string[] = parseString.replace(/[\t\n\r]/gm, ' ').split(',');
for (const modifier of customDMs) {
// eslint-disable-next-line no-useless-escape
const re = new RegExp(/([^\d]*?)([-+]?\d+$)/g);
const parsedResult: RegExpMatchArray | null = re.exec(modifier);
if (parsedResult) {
const keyValue = `key${i}`;
Object.assign(modifierObject, {
[keyValue]: {
label: parsedResult[1].trim() || game.i18n.localize("TWODSIX.Ship.Unknown"),
value: parseInt(parsedResult[2]) || 0
}
});
++i;
}
}
}
TWODSIX.TARGET_DM = modifierObject;
// console.log(TWODSIX.TARGET_DM, Object.keys(TWODSIX.TARGET_DM).length, getTargetDMSelectObject());
}

/**
* A function that takes the string setting 'targetDMList' parses it into an object and saves it to TWODSIX.TARGET_DM
* @returns {object} A select object with format {key# : 'Target DM Label (DM Val)'} useable for selectObject handlebar helper
*/
export function getTargetDMSelectObject(): object {
const returnValue = {};
for (const key of Object.keys(TWODSIX.TARGET_DM)) {
Object.assign(returnValue, {
[key]: `${TWODSIX.TARGET_DM[key].label} (${TWODSIX.TARGET_DM[key].value})`
});
}
return returnValue;
}

1 change: 1 addition & 0 deletions src/types/twodsix.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ declare global {
'twodsix.omitPSIifZero': boolean;
'twodsix.rangeModifierType': string;
'twodsix.equippedToggleStates': string;
'twodsix.targetDMList': string;
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions static/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@
"Modifier": "Modifier",
"Description": "Description",
"DM": "DM",
"TargetModifier": "Target DM",
"Characteristic": "Characteristic",
"Condition": "Condition",
"Attack": "Attack",
Expand Down Expand Up @@ -1329,6 +1330,10 @@
"equippedToggleStates": {
"hint": "Equipment location toggle states to use for actor sheet. Core: backpack and equipped only, Default: backpack, equipped and ship, All: backpack, equipped, vehicle, ship, and base",
"name": "Allowable equipment locations for actor sheet"
},
"targetDMList": {
"hint": "A comma separated list of target difficulty modifiers. The list has the format of 'label1 number1, label2 number2...'. For example, 'Obscure -1, Prone (melee) +1, Prone (Ranged) -1'.",
"name": "List of target modifiers"
}
},
"CloseAndCreateNew": "Close and create new",
Expand Down
4 changes: 4 additions & 0 deletions static/styles/twodsix_moduleFix.css
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ div.pdf-app section.window-content {
margin-top: 4px;
}

.item-piles-chat-card li:nth-child(odd) {
background-color: var(--s2d6-light-background-transparent);
}

/******** Monk's Active Tile Trigggers********/

.action-sheet .display-value, .multiple-dropdown {
Expand Down
9 changes: 9 additions & 0 deletions static/templates/chat/throw-dialog.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@
</label>
</div>
{{/if}}
{{#if showTargetModifier}}
<div class="form-group">
<label>{{localize "TWODSIX.Chat.Roll.TargetModifier"}}
<select class="select" name="rollModifiers.targetModifier" value={{rollModifiers.targetModifier}}>
{{selectOptions targetDMList selected=targetModifier}}
</select>
</label>
</div>
{{/if}}
{{/if}}
<div class="form-group">
<label>{{localize "TWODSIX.Chat.Roll.Other"}}
Expand Down
Loading

0 comments on commit 635b4bc

Please sign in to comment.