Skip to content

Commit

Permalink
Merge pull request #4 from UranusBytes/dev
Browse files Browse the repository at this point in the history
Release 0.0.4
  • Loading branch information
UranusBytes authored Jul 24, 2022
2 parents 12d8399 + 20a2ab0 commit 7a6dc7a
Show file tree
Hide file tree
Showing 18 changed files with 723 additions and 229 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ Jun 16, 2022
Jun 25, 2022
* Major refactor of system code to support flexibility
* Adjust dialog to support new UI/UX
* TOTALLY BROKEN fudging of rolls
* TOTALLY BROKEN fudging of rolls

## v0.0.4-ALPHA.0
Jul 23, 2022
* Refactor of all
20 changes: 20 additions & 0 deletions NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
DND5e Hooks
- preRollAbilitySave
- preRollAbilityCheck
- preRollDeathSave
- preRollSkillCheck

- RollAbilitySave
- RollAbilityCheck
- RollDeathSave
- RollSkillCheck

- preItemRoll
- preAttackRoll
- preDamageRoll

- ItemRoll
- AttackRoll (item, roll, actor, rollOptions)
- Damageroll

- itemTemplateSet ?
38 changes: 25 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
# Die Hard
Die Hard
========
This Foundry VTT module is intended to provide functionality that modifies/adjusted die rolls in certain systems.

**NOTE:** This module is still VERY alpha and under active development. I would not recommend using in a real game yet...

# Reporting Issues and Suggestions
Development
===========
## Reporting Issues and Suggestions
Open an issue: https://github.com/UranusBytes/foundry-die-hard/issues

# Development Planning
## Future Planning
Currently being (quasi) managed here: https://github.com/users/UranusBytes/projects/1

## Currently Supported Functionality
## Currently Supported Systems
* DND5e (current)

## Future Supported Functionality
## Future Supported Systems
* PF2e

## Troubleshoot
Extensive logging is used within the module, with debug logging enabled with the package debugging enabled

## Known Issues
* Raw die rolls for actors not working (even if fudge defined on GM/Player)
* Actors throwing raw It's possible to define a fudge that is impossible to achieve (especially when considering modifiers. Or to define a fudge of "> 20" for a d20). The failsafe is attempting to fudge 150 times, at which point the closest possible is provided.
* When the fudge config dialog is open, fudge status/list is not updated if any are changed by other GMs and/or PC/Actor rolls

## Currently Supported Functionality
### Fudge
Expand All @@ -24,13 +34,15 @@ With the module enabled, a poop icon will be displayed above the message tray.

![die-hard-fudge-1](docs/die-hard-fudge-1.jpg)

If there are active fudges, the poop icon will pulse orange

Clicking on this icon will open a configuration dialog.

![die-hard-fudge-2](docs/die-hard-fudge-2.jpg)

Within the dialog, to create a new Fudge do the following:
* Select the radio beside the actor (currently only PCs) this should affect
* Select the type of roll this should affect
* Select the GM/Online Player/actor (currently only online player PCs) this should affect
* Select the type of roll this should affect (system specific or raw die roll)
* Enter a formula using the format of "OPERATOR VALUE"
* Available operators are:
* `<`
Expand All @@ -42,18 +54,18 @@ Within the dialog, to create a new Fudge do the following:
* Examples: `< 5` or `> 15`
* Click on `Create Fudge`

All active fudges are listed at the bottom.
To delete an active fudge
All active fudges are listed at the right.
Status details if a fudge is active or disabled.
A pulsing green circular arrow means the fudge will re-enable itself after being used

![die-hard-fudge-3](docs/die-hard-fudge-3.jpg)

#### Mechanics
The way Fudge works is that the next die roll of that type for that Actor will be evaluated against the formula defined. If it doesn't meet the formula criteria, then the die is rerolled in the background (max of 150 times), with the final result presented to the PC. The GM will get a whisper that outlines if the fudge was used (with all failed results), or if it was removed without being used (if first roll met formula criteria).
# Mechanics
The way Fudge works is that the next die roll of that type (either system specific or a raw die roll) for that Player or Actor will be evaluated against the formula defined. If it doesn't meet the formula criteria, then the die is rerolled in the background (max of 150 times), with the final result presented to the PC. As it is re-rolling, if the attempted re-roll is "closer" to the desired fudge value, it will be kept. (For circumstances where the fudge can never be achieved, at least get as close as possible) The GM will get a whisper that outlines if the fudge was used (with all failed results), or if it was removed without being used (if first roll met formula criteria). When a fudge is used to influence a die (or would have, but original result was sufficient), then the fudge is disabled.

![die-hard-fudge-4](docs/die-hard-fudge-4.jpg)

### Known Issues
* TBD


## Future Planned Functionality
### Karmic dice
Expand Down
Binary file modified docs/die-hard-fudge-2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/die-hard-fudge-3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion module.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"title": "Die Hard",
"description": "Adjustments for DND5e die rolls like fudging...",
"author": "Jeremy (UranusBytes)",
"version": "0.0.3",
"version": "0.0.4",
"minimumCoreVersion": "9",
"compatibleCoreVersion": "9",
"esmodules": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class DieHardFudge {
export class DEPRECATED_DieHardFudge {
constructor() {

}
Expand Down
3 changes: 3 additions & 0 deletions scripts/classes/DieHardConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export default class DieHardConfig {
dieHardLog(true, 'DieHardConfig - registerSettings')
let dieHardSettings = {
system: null,
debug: {
allActors: true
},
fudgeConfig: {
maxFudgeAttemptsPerRoll: 150
},
Expand Down
130 changes: 83 additions & 47 deletions scripts/classes/DieHardDnd5e.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ export class DieHardDnd5e extends DieHardSystem{
constructor() {
dieHardLog(false, 'DieHardDnd5e - constructor');
super();
this.init();
}

init() {
dieHardLog(false, 'DieHardDnd5e - init');

libWrapper.register('foundry-die-hard', 'CONFIG.Actor.documentClass.prototype.rollAbilitySave', this.actorRollAbilitySave, 'WRAPPER');
libWrapper.register('foundry-die-hard', 'CONFIG.Actor.documentClass.prototype.rollSkill', this.actorRollAbilitySave, 'WRAPPER');
libWrapper.register('foundry-die-hard', 'CONFIG.Actor.documentClass.prototype.rollAbilityTest', this.actorRollAbilityTest, 'WRAPPER');
libWrapper.register('foundry-die-hard', 'CONFIG.Actor.documentClass.prototype.rollDeathSave', this.actorRollAbilitySave, 'WRAPPER');
libWrapper.register('foundry-die-hard', 'CONFIG.Actor.documentClass.prototype.rollDeathSave', this.actorRollDeathSave, 'WRAPPER');

libWrapper.register('foundry-die-hard', 'CONFIG.Item.documentClass.prototype.rollAttack', this.entityRollAttack, 'WRAPPER');

libWrapper.register('foundry-die-hard', 'game.dnd5e.dice.D20Roll.prototype._evaluate', this.d20rollEvaluate, 'WRAPPER');

// See notes in DieHardFudgeD20Roll
Expand Down Expand Up @@ -51,43 +48,31 @@ export class DieHardDnd5e extends DieHardSystem{
]
}

hookReady() {
dieHardLog(false, 'Dnd 5e System Hook - Ready');
}


getFudgeWhatOptions() {
return this.fudgeWhatOptions;
hookReady() {
dieHardLog(false, 'Dnd 5e System Hook - Ready');
}

_evalFudge(result, operator, operatorValue) {
dieHardLog(false, 'DieHardDnd5e - fudgeD20Roll - _evalFudge', result, operator, operatorValue);
switch (operator) {
case '>': return result > operatorValue;
case '<': return result < operatorValue;
case '>=': return result >= operatorValue;
case '<=': return result <= operatorValue;
case '!=': return result !== operatorValue;
case '=': return result === operatorValue;
}
}
isPromise(p) {
if (typeof p === 'object' && typeof p.then === 'function') {
return true;
if (typeof p === 'object' && typeof p.then === 'function') {
return true;
}

return false;
}

return false;
}
fudgeD20Roll(result, evaluate_options) {
dieHardLog(false, 'DieHardDnd5e - fudgeD20Roll');
// dieHardLog(false, 'DieHardDnd5e - fudgeD20Roll - result', result);
let fudgeOperator = result.data.fudgeOperator
let fudgeOperatorValue = result.data.fudgeOperatorValue

let gen_new_result = false;
let evalResult = this._evalFudge(result.total, fudgeOperator, fudgeOperatorValue)
let evalResult = this.evalFudge(result.total, fudgeOperator, fudgeOperatorValue)
if (evalResult) {
dieHardLog(false, 'DieHardDnd5e - fudgeD20Roll: Fudge not needed, but still wiped from actor');
this._dmToGm('DieHard-Fudge: Fudge not needed, but still wiped from actor...');
this.dmToGm('DieHard-Fudge: Fudge not needed, but still wiped from actor...');
} else {
gen_new_result = true;
let dmMessage = "Fudge (" + result.data.fudgeHow + ") values:" + result.total;
Expand All @@ -97,6 +82,7 @@ export class DieHardDnd5e extends DieHardSystem{
while (gen_new_result && SafetyLoopIndex > 0) {
SafetyLoopIndex--;

// ToDo: Can a "clone()" or a "reroll()" be used instead? https://foundryvtt.com/api/Roll.html#clone
const new_roll = new DieHardFudgeD20Roll(
result.formula,
result.data, {
Expand All @@ -111,40 +97,44 @@ export class DieHardDnd5e extends DieHardSystem{
halflingLucky: result.options.halflingLucky,
reliableTalent: result.options.reliableTalent
});

new_roll.evaluate({async: false, minimize: evaluate_options.minimize, maximize: evaluate_options.maximize});
new_roll.evaluate({ async: false, minimize: evaluate_options.minimize, maximize: evaluate_options.maximize });
dieHardLog(false, 'DieHardDnd5e - fudgeD20Roll: New roll: ', new_roll)

evalResult = this._evalFudge(new_roll.total, fudgeOperator, fudgeOperatorValue)
evalResult = this.evalFudge(new_roll.total, fudgeOperator, fudgeOperatorValue)
if (evalResult) {
dieHardLog(false, 'DieHardDnd5e - fudgeD20Roll: New result: ' + new_roll.total)
gen_new_result = false;
foundry.utils.mergeObject(result, new_roll);
this._dmToGm(dmMessage);
this.dmToGm(dmMessage);
} else {
dieHardLog(false, 'DieHardDnd5e - fudgeD20Roll: New result insufficient (' + new_roll.total + "). Try again...")
// New roll is insufficient, but lets at least check if it is "closer"
if (this.isBetterFudge(result.total, new_roll.total, fudgeOperator, fudgeOperatorValue)) {
dieHardLog(false, 'DieHardDnd5e - fudgeD20Roll: New result insufficient, but at least better (' + new_roll.total + "). Try again (tries left: " + SafetyLoopIndex + ")...")
foundry.utils.mergeObject(result, new_roll);
} else {
dieHardLog(false, 'DieHardDnd5e - fudgeD20Roll: New result insufficient (' + new_roll.total + "). Try again (tries left: " + SafetyLoopIndex + ")...")
}
dmMessage += ',' + new_roll.total;
}
}
if (SafetyLoopIndex === 0) {
dieHardLog(false, 'DieHardDnd5e - fudgeD20Roll: Tried until retry safety killed...');
this._dmToGm('DieHard-Fudge: Gave up trying to fudge; loop safety reached...');
this.dmToGm('DieHard-Fudge: Gave up trying to fudge; loop safety reached...');
}
}

dieHardLog(false, 'Done with modify_results', result);
return result
}

d20rollEvaluate(wrapped, evaluate_options) {
dieHardLog(false, 'DieHardDnd5e.d20rollEvaluate');
dieHardLog(false, 'DieHardDnd5e.d20rollEvaluate', this.data);

let fudge = false;
// Determine if fudge is active
dieHardLog(false, 'DieHardDnd5e.d20rollEvaluate - this', this);
if (this.data.fudge === true) {
// ToDo: Only enable if fudge is active
evaluate_options.async = false;


if (this instanceof CONFIG.Dice.DieHardFudgeD20Roll) {
// This is a recursive roll; do sync
evaluate_options.async = false;
Expand All @@ -153,33 +143,79 @@ export class DieHardDnd5e extends DieHardSystem{
fudge = true;
}
} else {
console.log('No fudging today!')
dieHardLog(false, 'DieHardDnd5e.d20rollEvaluate - No fudging planned for this roll');
}

let result = wrapped.call(evaluate_options)
// If a fudge re-roll is allowed
if (fudge){
result.then(function(value) {game.settings.get('foundry-die-hard', 'dieHardSettings').system.fudgeD20Roll(value, evaluate_options)})
}
let result = wrapped(evaluate_options);

/*
Original WORKING ???
//let result = wrapped(evaluate_options); - ORIGINAL
let result = wrapped.bind(this)(evaluate_options);
// If a fudge re-roll is allowed
if (fudge){
result.then((value) => game.settings.get('foundry-die-hard', 'dieHardSettings').system.fudgeD20Roll(value, evaluate_options));
}
*/

return result
}

/**
* Generic wrapper for all rolls
* @param options
* @param actorId
* @param rollType
*/
wrappedRoll(options, actorId, rollType) {
dieHardLog(false, 'DieHardDnd5e.wrappedRoll', this);
//dieHardLog(false, 'DieHardDnd5e.wrappedRoll - this', this);
//dieHardLog(false, 'DieHardDnd5e.wrappedRoll - options', options);
//dieHardLog(false, 'DieHardDnd5e.wrappedRoll - actorId', actorId);
//dieHardLog(false, 'DieHardDnd5e.wrappedRoll - rollType', rollType);

// Check if actor has an active fudge
let actorFudges = game.actors.get(actorId).getFlag('foundry-die-hard', 'activeFudges');
let actorFudges = game.actors.get(actorId).getFlag('foundry-die-hard', 'fudges');
if (! Array.isArray(actorFudges)) {
actorFudges = []
}
dieHardLog(false, 'DieHardDnd5e.wrappedRoll - actorFudges', actorFudges);
let fudgeIndex = actorFudges.findIndex(element => { return element.what === rollType;});
if (fudgeIndex !== -1) {
dieHardLog(false, 'Actor has active fudge', actorFudges[fudgeIndex].how);
foundry.utils.mergeObject(options, {data: {fudge: true, fudgeOperator: actorFudges[fudgeIndex].operator, fudgeOperatorValue: actorFudges[fudgeIndex].operatorValue, fudgeHow: actorFudges[fudgeIndex].how }});
let fudgeIndex = actorFudges.findIndex(element => { return element.whatId === rollType;});
if (fudgeIndex !== -1 && actorFudges[fudgeIndex].statusActive) {
dieHardLog(false, 'DieHardDnd5e.wrappedRoll - active actor fudge', actorFudges[fudgeIndex]);
foundry.utils.mergeObject(options, {data: {fudge: true, fudgeOperator: actorFudges[fudgeIndex].operator, fudgeOperatorValue: actorFudges[fudgeIndex].operatorValue, fudgeHow: actorFudges[fudgeIndex].howFormula }});
// Delete the fudge from the actor
let deletedFudge = actorFudges.splice(fudgeIndex,1)
game.actors.get(actorId).setFlag('foundry-die-hard', 'activeFudges', actorFudges);
game.actors.get(actorId).setFlag('foundry-die-hard', 'fudges', actorFudges);
// Check if still have active fudges;
this.refreshActiveFudgesIcon();
}

// Check if user has an active fudge
let userFudges = game.users.current.getFlag('foundry-die-hard', 'fudges');
if (! Array.isArray(userFudges)) {
userFudges = []
}
fudgeIndex = userFudges.findIndex(element => { return element.whatId === rollType;});
if (fudgeIndex !== -1 && userFudges[fudgeIndex].statusActive) {
dieHardLog(false, 'DieHardDnd5e.wrappedRoll - active user fudge', userFudges[fudgeIndex]);
foundry.utils.mergeObject(options, {data: {fudge: true, fudgeOperator: userFudges[fudgeIndex].operator, fudgeOperatorValue: userFudges[fudgeIndex].operatorValue, fudgeHow: userFudges[fudgeIndex].howFormula }});
if (userFudges[fudgeIndex].statusEndless) {
dieHardLog(false, 'DieHardSystem.wrappedRoll - fudge is endless');
} else {
// Disable the fudge
userFudges[fudgeIndex].statusActive = false

// Delete the fudge from the user
// let deletedFudge = userFudges.splice(fudgeIndex, 1)
game.users.current.setFlag('foundry-die-hard', 'fudges', userFudges);
// Check if still have active fudges;
this.refreshActiveFudgesIcon();
}
}
}

actorRollSkill(wrapped, skillId, options={}) {
Expand Down
6 changes: 6 additions & 0 deletions scripts/classes/DieHardFudgeD20Roll.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Because this extends D20Roll, I could not find a clean way to import. I tried m
*/

import D20Roll from '../../../../systems/dnd5e/module/dice/d20-roll.js'
import {dieHardLog} from "../lib/helpers.js";

export class DieHardFudgeD20Roll extends D20Roll {
// This is a simple extension
Expand All @@ -16,4 +17,9 @@ export class DieHardFudgeD20Roll extends D20Roll {
static get defaultOptions() {
return super.defaultOptions;
}

evaluate({minimize=false, maximize=false, async}={}) {
dieHardLog(false, 'DieHardDnd5e - D20Roll: evaluate: ', async)
super.evaluate({minimize: minimize, maximize: maximize, async: async})
}
}
Loading

0 comments on commit 7a6dc7a

Please sign in to comment.