Skip to content

Commit

Permalink
Merge pull request #4813 from foundryvtt/flat-initiative
Browse files Browse the repository at this point in the history
[#4812] Add setting to use initiative scores rather than rolls
  • Loading branch information
arbron authored Dec 4, 2024
2 parents 85ae4b5 + 4fce9f5 commit 10a2c3a
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 11 deletions.
7 changes: 7 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3836,6 +3836,13 @@
"Name": "Default Skills",
"Hint": "The default skills that appear on NPC sheets regardless of level of proficiency."
},
"INITIATIVESCORE": {
"Name": "Initiative Score",
"Hint": "Use a creature's initiative score (10 + bonus) rather than rolling for initiative.",
"All": "Use Score for Everyone",
"None": "Always Roll for Initiative",
"NPCs": "Use Score for GM NPCs"
},
"LEVELING": {
"Name": "Leveling Mode",
"Hint": "Determine how the players gain new levels.",
Expand Down
45 changes: 34 additions & 11 deletions module/documents/actor/actor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,7 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
init.total = init.mod + initBonus + abilityBonus + globalCheckBonus
+ (flags.initiativeAlert && isLegacy ? 5 : 0)
+ (Number.isNumeric(init.prof.term) ? init.prof.flat : 0);
init.score = 10 + init.total;
}

/* -------------------------------------------- */
Expand Down Expand Up @@ -2024,8 +2025,9 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {

/**
* @typedef {D20RollOptions} InitiativeRollOptions
* @param {D20Roll.ADV_MODE} [advantageMode] A specific advantage mode to apply.
* @property {string} [flavor] Special flavor text to apply to the created message.
* @property {D20Roll.ADV_MODE} [advantageMode] A specific advantage mode to apply.
* @property {number} [fixed] Fixed initiative value to use rather than rolling.
* @property {string} [flavor] Special flavor text to apply to the created message.
*/

/**
Expand All @@ -2038,8 +2040,15 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
if ( this._cachedInitiativeRoll ) return this._cachedInitiativeRoll.clone();
const config = this.getInitiativeRollConfig(options);
if ( !config ) return null;
const formula = ["1d20"].concat(config.parts).join(" + ");
return new CONFIG.Dice.D20Roll(formula, config.data, config.options);

// Create a normal D20 roll
if ( config.options?.fixed === undefined ) {
const formula = ["1d20"].concat(config.parts).join(" + ");
return new CONFIG.Dice.D20Roll(formula, config.data, config.options);
}

// Create a basic roll with the fixed score
return new CONFIG.Dice.BasicRoll(String(config.options.fixed), config.data, config.options);
}

/* -------------------------------------------- */
Expand Down Expand Up @@ -2075,7 +2084,12 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
const tiebreaker = game.settings.get("dnd5e", "initiativeDexTiebreaker");
if ( tiebreaker && Number.isNumeric(ability?.value) ) parts.push(String(ability.value / 100));

// Fixed initiative score
const scoreMode = game.settings.get("dnd5e", "initiativeScore");
const useScore = (scoreMode === "all") || ((scoreMode === "npcs") && game.user.isGM && (this.type === "npc"));

options = foundry.utils.mergeObject({
fixed: useScore ? init.score : undefined,
flavor: options.flavor ?? game.i18n.localize("DND5E.Initiative"),
halflingLucky: flags.halflingLucky ?? false,
maximum: init.roll.max,
Expand All @@ -2101,7 +2115,7 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
/**
* Roll initiative for this Actor with a dialog that provides an opportunity to elect advantage or other bonuses.
* @param {Partial<InitiativeRollOptions>} [rollOptions={}] Options forwarded to the Actor#getInitiativeRoll method.
* @returns {Promise<void>} A promise which resolves once initiative has been rolled for the Actor
* @returns {Promise<void>} A promise which resolves once initiative has been rolled for the Actor.
*/
async rollInitiativeDialog(rollOptions={}) {
const config = {
Expand All @@ -2112,13 +2126,22 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
subject: this
};
if ( !config.rolls[0] ) return;
const dialog = { options: { title: game.i18n.localize("DND5E.InitiativeRoll") } };
const message = { rollMode: game.settings.get("core", "rollMode") };
const rolls = await CONFIG.Dice.D20Roll.build(config, dialog, message);
if ( !rolls.length ) return;

// Temporarily cache the configured roll and use it to roll initiative for the Actor
this._cachedInitiativeRoll = rolls[0];
// Display the roll configuration dialog
if ( config.rolls[0].options?.fixed === undefined ) {
const dialog = { options: { title: game.i18n.localize("DND5E.InitiativeRoll") } };
const message = { rollMode: game.settings.get("core", "rollMode") };
const rolls = await CONFIG.Dice.D20Roll.build(config, dialog, message);
if ( !rolls.length ) return;
this._cachedInitiativeRoll = rolls[0];
}

// Just create a basic roll with the fixed score
else {
const { data, options } = config.rolls[0];
this._cachedInitiativeRoll = new CONFIG.Dice.BasicRoll(String(options.fixed), data, options);
}

await this.rollInitiative({ createCombatants: true });
}

Expand Down
15 changes: 15 additions & 0 deletions module/settings.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,21 @@ export function registerSystemSettings() {
type: Boolean
});

// Use initiative scores for NPCs
game.settings.register("dnd5e", "initiativeScore", {
name: "SETTINGS.DND5E.INITIATIVESCORE.Name",
hint: "SETTINGS.DND5E.INITIATIVESCORE.Hint",
scope: "world",
config: true,
default: "none",
type: String,
choices: {
none: "SETTINGS.DND5E.INITIATIVESCORE.None",
npcs: "SETTINGS.DND5E.INITIATIVESCORE.NPCs",
all: "SETTINGS.DND5E.INITIATIVESCORE.All"
}
});

// Record Currency Weight
game.settings.register("dnd5e", "currencyWeight", {
name: "SETTINGS.5eCurWtN",
Expand Down

0 comments on commit 10a2c3a

Please sign in to comment.