Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to effect variables #156

Merged
merged 1 commit into from
Jan 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion module/apps/heal-menu-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class HealMenuDialog extends FormApplication {

console.log(JSON.stringify(formData))

let roll = await Helper.roll(formData.bonus, "DND4EBETA.InvalidHealingBonus")
let roll = await Helper.rollWithErrorHandling(formData.bonus, { errorMessageKey: "DND4EBETA.InvalidHealingBonus"})

let surgeValueText = "0"
let surgeValue = 0
Expand Down
2 changes: 1 addition & 1 deletion module/apps/second-wind.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class SecondWindDialog extends DocumentSheet {
}
async _updateObject(event, formData) {

let r = await Helper.roll(formData.bonus, "DND4EBETA.InvalidHealingBonus")
let r = await Helper.rollWithErrorHandling(formData.bonus, { errorMessageKey: "DND4EBETA.InvalidHealingBonus"})

const updateData = {};
if(this.object.data.data.attributes.hp.value <= 0) {
Expand Down
130 changes: 86 additions & 44 deletions module/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class Helper {
}


static applyEffects(arrayOfParts, rollData, actorData, powerData, weaponData = null, effectType) {
static async applyEffects(arrayOfParts, rollData, actorData, powerData, weaponData = null, effectType) {
const debug = game.settings.get("dnd4e", "debugEffectBonus") ? `D&D4eBeta |` : ""
if (actorData.effects) {
const powerInnerData = powerData.data
Expand Down Expand Up @@ -173,7 +173,8 @@ export class Helper {

if (debug) {
console.log(`${debug} based on power source, effect type, damage type and (if weapon) weapon group, properties and damage type the following effect keys are suitable`)
suitableKeywords.forEach((keyword) => console.log(`${debug} ${keyword}`))
suitableKeywords.sort()
console.log(`${debug} ${suitableKeywords.join(", ")}`)
}

// filter out to just the relevant effects by keyword
Expand All @@ -196,35 +197,49 @@ export class Helper {
const keyParts = effect.key.split(".")
if (keyParts.length === 4) {
const bonusType = keyParts[3]
const effectValueString = this.commonReplace(effect.value, actorData, powerData.data, weaponData?.data)
const effectDice = await this.rollWithErrorHandling(effectValueString, {context : effect.key})
const effectValue = effectDice.total
if (bonusType === "untyped") {
if (newParts["untypedEffectBonus"]) {
newParts["untypedEffectBonus"] = parseInt(newParts["untypedEffectBonus"]) + parseInt(effect.value)
console.log(`${debug} ${effect.name} : ${effect.key} = ${effect.value} : Additional untyped Bonus. They Stack.`)
newParts["untypedEffectBonus"] = newParts["untypedEffectBonus"] + effectValue
if (debug) {
console.log(`${debug} ${effect.name} : ${effect.key} => ${effect.value} = ${effectValue}: Additional untyped Bonus. They Stack.`)
}
}
else {
console.log(`${debug} ${effect.name} : ${effect.key} = ${effect.value} : First untyped Bonus`)
newParts["untypedEffectBonus"] = effect.value
newParts["untypedEffectBonus"] = effectValue
if (debug) {
console.log(`${debug} ${effect.name} : ${effect.key} => ${effect.value} = ${effectValue}: First untyped Bonus`)
}
}
}
else {
const key = `${bonusType}EffectBonus`
if (newParts[key]) {
if (newParts[key] < effect.value) {
newParts[key] = effect.value
console.log(`${debug} ${effect.name} : ${effect.key} = ${effect.value} : Is greater than existing ${bonusType}, replacing`)
if (newParts[key] < effectValue) {
newParts[key] = effectValue
if (debug) {
console.log(`${debug} ${effect.name} : ${effect.key} => ${effect.value} = ${effectValue}: Is greater than existing ${bonusType}, replacing`)
}
}
else {
console.log(`${debug} ${effect.name} : ${effect.key} = ${effect.value} : Is not great than existing ${bonusType}, discarding`)
if (debug) {
console.log(`${debug} ${effect.name} : ${effect.key} => ${effect.value} = ${effectValue} : Is not great than existing ${bonusType}, discarding`)
}
}
}
else {
newParts[key] = effect.value
console.log(`${debug} ${effect.name} : ${effect.key} = ${effect.value} : First ${bonusType} Bonus`)
newParts[key] = effectValue
if (debug) {
console.log(`${debug} ${effect.name} : ${effect.key} => ${effect.value} = ${effectValue} : First ${bonusType} Bonus`)
}
}
}
}
else {
ui.notifications.warn(`Tried to process an bonus effect that had too few/many .'s in it: ${effect.key}`)
ui.notifications.warn(`Tried to process an bonus effect that had too few/many .'s in it: ${effect.key}: ${effect.value}`)
console.log(`Tried to process an bonus effect that had too few/many .'s in it: ${effect.key}: ${effect.value}`)
}
}

Expand Down Expand Up @@ -253,7 +268,7 @@ export class Helper {
* @param actorData The data from the actor to use to resolve variables: `actor.data`. This may be null
* @param powerInnerData The data from the power to use to resolve variables. `power.data.data`
* @param weaponInnerData The data from the weapon to use to resolve variables. `item.data.data` This may be null
* @param depth The number of times to recurse down the formula to replace variables, a safety net to stop infinite recursion. Defaults to 1 which will produce 2 loops.
* @param depth The number of times to recurse down the formula to replace variables, a safety net to stop infinite recursion. Defaults to 1 which will produce 2 loops. A depth of 0 will also prevent evaluation of custom effect variables (as that is an infinite hole)
* @param returnDataInsteadOfFormula If set to true it will return a data object of replacement variables instead of the formula string
* @return {String|{}|number} "0" if called with a depth of <0, A substituted formula string if called with returnDataInsteadOfFormula = false (the default) or an object of {variable = value} if called with returnDataInsteadOfFormula = true
*/
Expand Down Expand Up @@ -296,33 +311,6 @@ export class Helper {
else {
console.log("An actor data object without a .data property was passed to common replace. Probably passed actor.data.data by mistake!. Replacing: " + formula)
}

if (actorData.effects) {
const resultObject = {}
const effects = Array.from(actorData.effects.values()).filter((effect) => effect.data?.disabled === false);
effects.forEach((effect) => {
effect.data.changes.forEach((change => {
if (this.variableRegex.test(change.key)) {
if (!resultObject[change.key]) {
resultObject[change.key] = change.value
}
else {
if(this._isNumber(resultObject[change.key]) && this._isNumber(change.value)){
resultObject[change.key] = Number(resultObject[change.key]) + Number(change.value)
} else {
resultObject[change.key] = `${resultObject[change.key]} + ${change.value}`
}
}
}
}))
})

for (const [key, value] of Object.entries(resultObject)) {
newFormula = newFormula.replaceAll(key, value);
}
}


}

if(weaponInnerData) {
Expand Down Expand Up @@ -487,7 +475,8 @@ export class Helper {
dice = this.commonReplace(dice, actorData, powerInnerData, weaponInnerData, depth-1)
newFormula = newFormula.replaceAll("@powMax", dice);
}
} else {
}
else {
//if no weapon is in use replace the weapon keys with nothing.
newFormula = newFormula.replaceAll("@wepAttack", "");
newFormula = newFormula.replaceAll("@wepDamage", "");
Expand Down Expand Up @@ -586,6 +575,52 @@ export class Helper {
}
}

// this is done at the bottom, because I don't want to iterating the entire actor effects collection unless I have to
// as this could get unnecessarily expensive quickly.
// Depth > 0 check is here to prevent an infinite recursion situation as this will call to common replace in case the variable uses a formula
// having got to the bottom of common replace, check to see if there are any more @variables left. If there aren't, then don't bother going any further
if (actorData?.effects && depth > 0 && newFormula.includes('@')) {
const debug = game.settings.get("dnd4e", "debugEffectBonus") ? `D&D4eBeta |` : ""
if (debug) {
console.log(`${debug} Substituting '${formula}', end of processing produced '${newFormula}' which still contains an @variable. Searching active effects for a suitable variable`)
}
const resultObject = {}
const effects = Array.from(actorData.effects.values()).filter((effect) => effect.data?.disabled === false);
effects.forEach((effect) => {
effect.data.changes.forEach((change => {
if (this.variableRegex.test(change.key)) {
if (debug) {
console.log(`${debug} Found custom variable ${change.key} in effect ${effect.data.label}. Value: ${change.value}`)
}
const changeValueReplaced = this.commonReplace(change.value, actorData, powerInnerData, weaponInnerData, 0) // set depth to avoid infinite recursion
if (!resultObject[change.key]) {
resultObject[change.key] = changeValueReplaced
if (debug) {
console.log(`${debug} Effect: ${effect.data.label}. Computed Value: ${change.value} was the first match to ${change.key} `)
}
}
else {
if (debug) {
console.log(`${debug} Effect: ${effect.data.label}. Computed Value: ${change.value} was an additional match to ${change.key} adding to previous`)
}
if(this._isNumber(resultObject[change.key]) && this._isNumber(changeValueReplaced)){
resultObject[change.key] = Number(resultObject[change.key]) + Number(changeValueReplaced)
} else {
resultObject[change.key] = `${resultObject[change.key]} + ${changeValueReplaced}`
}
}
}
}))
})

if (debug) {
console.log(`${debug} Discovered custom variable values in effects to substitute into formula (${newFormula}): ${JSON.stringify(resultObject)}`)
}
for (const [key, value] of Object.entries(resultObject)) {
newFormula = newFormula.replaceAll(key, value);
}
}

return newFormula;
}

Expand Down Expand Up @@ -638,13 +673,20 @@ export class Helper {
*
* @param {String} rollString The roll expression.
* @param {String} errorMessageKey The key that will be localised for the error message if the roll fails.
* @param {String} context Context on the source of the roll string / where it is being used
* @returns {Promise<Roll>} The evaluated Roll instance as a promise
*/
static async roll(rollString, errorMessageKey = "DND4EBETA.InvalidRollExpression") {
static async rollWithErrorHandling(rollString, { errorMessageKey = "DND4EBETA.InvalidRollExpression", context = "" }) {
if (!errorMessageKey) {
errorMessageKey = "DND4EBETA.InvalidRollExpression"
}
if (rollString && rollString !== "") {
const roll = new Roll(rollString);
return roll.roll({async : true}).catch(err => {
ui.notifications.error(game.i18n.localize(errorMessageKey));
let msg = context ? `${game.i18n.localize(errorMessageKey)} (in ${context}) : ${rollString}` : `${game.i18n.localize(errorMessageKey)} : ${rollString}`
ui.notifications.error(msg);
console.log(msg)
console.log(err)
return new Roll("0").roll({async : true});
});
}
Expand Down
6 changes: 3 additions & 3 deletions module/item/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,7 @@ export default class Item4e extends Item {
handlePowerAndWeaponAmmoBonuses(weaponHasAmmoWithBonus, weaponUse.data.data.consume, "weapon used by the power")
}

Helper.applyEffects([parts], rollData, actorData, this.data, weaponUse?.data, "attack")
await Helper.applyEffects([parts], rollData, actorData, this.data, weaponUse?.data, "attack")

// Compose roll options
const rollConfig = {
Expand Down Expand Up @@ -849,7 +849,7 @@ export default class Item4e extends Item {
*
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
*/
rollDamage({event, spellLevel=null, versatile=false}={}) {
async rollDamage({event, spellLevel=null, versatile=false}={}) {
const itemData = this.data.data;
const actorData = this.actor.data;
const actorInnerData = this.actor.data.data;
Expand Down Expand Up @@ -1059,7 +1059,7 @@ export default class Item4e extends Item {
partsCritExpressionReplacement.unshift({target : partsCrit[0], value: critDamageFormulaExpression})
partsMissExpressionReplacement.unshift({target : partsMiss[0], value: missDamageFormulaExpression})

Helper.applyEffects([parts, partsCrit, partsMiss], rollData, actorData, this.data, weaponUse?.data, "damage")
await Helper.applyEffects([parts, partsCrit, partsMiss], rollData, actorData, this.data, weaponUse?.data, "damage")

return damageRoll({
event,
Expand Down