Skip to content

Commit

Permalink
Merge pull request #32 from fvtt-fria-ligan/feat/add-lock-initiative
Browse files Browse the repository at this point in the history
  • Loading branch information
aMediocreDad authored Aug 21, 2023
2 parents 246f6c9 + 5994b64 commit 8290904
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 13 deletions.
63 changes: 58 additions & 5 deletions src/combat/combat.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ import { duplicateCombatant, getCombatantsSharingToken } from './duplicate-comba
import { removeSlowAndFastActions } from './slow-and-fast-actions';

export default class YearZeroCombat extends Combat {
/* ------------------------------------------ */
/* Properties */
/* ------------------------------------------ */

get history() {
return this.getFlag(MODULE_ID, 'history') || {};
}

async setHistory($history) {
return this.setFlag(MODULE_ID, 'history', $history);
}

/* ------------------------------------------ */
/* Methods */
/* ------------------------------------------ */
/**
* @param {string|string[]} ids The IDs of all the combatants in the combat
* @param {InitiativeOptions} [options] Additional initiative options
Expand Down Expand Up @@ -56,7 +71,7 @@ export default class YearZeroCombat extends Combat {
if (cards.length > 1) {
cards.sort((a, b) => (a.value - b.value) * Utils.getCardSortOrderModifier(combatant.keepState));

if (game.settings.get(MODULE_ID, SETTINGS_KEYS.INITIATIVE_AUTODRAW)) {
if (game.settings.get(MODULE_ID, SETTINGS_KEYS.AUTO_SELECT_BEST_CARD)) {
card = cards[0];
}
else {
Expand All @@ -67,6 +82,7 @@ export default class YearZeroCombat extends Combat {
card = cards[0];
}


// Updates the combatant.
const updateData = {
initiative: card.value,
Expand Down Expand Up @@ -244,6 +260,7 @@ export default class YearZeroCombat extends Combat {
/** @override */
async resetAll() {
for (const combatant of this.combatants) {
if (combatant.lockInitiative && !combatant.isDefeated) continue;
await combatant.resetInitiative();
}
return this.update({ turn: 0, combatants: this.combatants.toObject() }, { diff: false });
Expand All @@ -269,8 +286,12 @@ export default class YearZeroCombat extends Combat {
if (game.settings.get(MODULE_ID, SETTINGS_KEYS.INITIATIVE_RESET_DECK_ON_START)) {
await Utils.resetInitiativeDeck();
}
const ids = this.combatants.filter(c => !c.isDefeated && c.initiative == null).map(c => c.id);
await this.rollInitiative(ids);

if (game.settings.get(MODULE_ID, SETTINGS_KEYS.INITIATIVE_AUTODRAW)) {
const ids = this.combatants.filter(c => !c.isDefeated && c.initiative == null).map(c => c.id);
await this.rollInitiative(ids);
}

return super.startCombat();
}

Expand All @@ -290,6 +311,10 @@ export default class YearZeroCombat extends Combat {

/** @override */
async nextRound() {

// Save the state of the combatants before we end the previous round.
await this.setHistory({ ...this.history, [this.round]: this.combatants.map(c => c.toObject()) });

const updates = [];
for (const combatant of this.combatants) {
try {
Expand All @@ -308,8 +333,33 @@ export default class YearZeroCombat extends Combat {

await super.nextRound();

// Check if state exists for this round and restore it.
const roundState = this.history[this.round];

// Resets the initiative of all combatants at the start of the round.
if (game.settings.get(MODULE_ID, SETTINGS_KEYS.RESET_EACH_ROUND)) this.#resetInitiativeAtEndOfRound();
if (game.settings.get(MODULE_ID, SETTINGS_KEYS.RESET_EACH_ROUND) && !roundState) {
this.#resetInitiativeAtEndOfRound();
}
else if (roundState) await this.update({ ['combatants']: roundState }, { diff: false });

return this;
}

/* ------------------------------------------ */

/** @override */
async previousRound() {
// Save the state of the combatants before we end the current round.
await this.setHistory({ ...this.history, [this.round]: this.combatants.map(c => c.toObject()) });

// Proceed to previous round.
await super.previousRound();

// Check if state exists for this round and restore it.
const roundState = this.history[this.round];
if (roundState) {
await this.update({ ['combatants']: roundState }, { diff: false });
}

return this;
}
Expand All @@ -332,6 +382,8 @@ export default class YearZeroCombat extends Combat {
return AudioHelper.play(data);
}

/* ------------------------------------------ */

/**
* Resets the initiative of all combatants at the start of the round. Optionally resets the initiative deck,
* and draws cards for combatants with no initiative.
Expand All @@ -342,7 +394,8 @@ export default class YearZeroCombat extends Combat {
await this.resetAll();

if (game.settings.get(MODULE_ID, SETTINGS_KEYS.INITIATIVE_RESET_DECK_ON_START)) {
await Utils.resetInitiativeDeck();
const lockedCards = this.combatants.filter(c => c.lockInitiative).map(c => c.cardValue);
await Utils.resetInitiativeDeck(true, lockedCards);
}

if (game.settings.get(MODULE_ID, SETTINGS_KEYS.INITIATIVE_AUTODRAW)) {
Expand Down
1 change: 1 addition & 0 deletions src/combat/combatant.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export default class YearZeroCombatant extends Combatant {
*/
getNumberOfCardsToDraw() {
// TODO add talents here, if any.
if (this.lockInitiative) return 0;
return this.drawSize;
}

Expand Down
7 changes: 6 additions & 1 deletion src/lang/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ SETTINGS.ActorSpeedAttributeHint: >-
The property key in the actor's data to the attribute that defines how many
initiative cards are kept (speed) by the combatant (e.g. system.speed.value).
SETTINGS.AutoDraw: Automatic Initiative Draw
SETTINGS.AutoDrawHint: Whether to automatically draw initiative cards.
SETTINGS.AutoDrawHint: Whether to automatically draw initiative cards when combat starts.
SETTINGS.AutoSelectBestCard: Choose Best Card
SETTINGS.AutoSelectBestCardHint: >-
Forgo the card selection dialog on multiple drawn initiative cards, instead opting for
the best (fastest) initiative card.
SETTINGS.DiscardPile: Discard Pile ID
SETTINGS.DiscardPileHint: ID or name of the Discard pile where to put the drawn initiative cards.
SETTINGS.DuplicateCombatantsOnCombatStart: Duplicate Combatants
Expand Down Expand Up @@ -64,6 +68,7 @@ YZEC.CombatTracker.SlowAction: Slow Action
YZEC.CombatTracker.SubmitColor: Submit
YZEC.CombatTracker.SwapInitiative: Swap Initiative
YZEC.CombatTracker.UnfollowLeader: Unfollow {name}
YZEC.CombatTracker.lockInitiative: Lock Initiative
YZEC.CombatantConfig.DrawSize: Draw Size
YZEC.CombatantConfig.InitiativeCard: Initiative Card
YZEC.CombatantConfig.InitiativeCardAvailable: Available
Expand Down
15 changes: 15 additions & 0 deletions src/module/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ YZEC.CombatTracker = {
visibility: 'owner',
},
],
lockInitiative: [
{
eventName: 'lock-initiative-button-clicked',
label: 'YZEC.CombatTracker.lockInitiative',
icon: 'fas fa-lock',
id: 'lock-initiative-button',
property: 'lockInitiative',
visibility: 'owner',
condition: (combat, combatant) => {
const combatHasBegun = combat.active && combat.started;
const notInGroup = !combatant.groupId;
return combatHasBegun && notInGroup;
},
},
],
},
};

Expand Down
1 change: 1 addition & 0 deletions src/module/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const SETTINGS_KEYS = {
/** @type {'maxDrawSize'} */ MAX_DRAW_SIZE: 'maxDrawSize',
/** @type {'slowAndFastActions'} */ SLOW_AND_FAST_ACTIONS: 'slowAndFastActions',
/** @type {'resetEachRound'} */ RESET_EACH_ROUND: 'resetEachRound',
/** @type {'autoSelectBestCard'} */ AUTO_SELECT_BEST_CARD: 'autoSelectBestCard',
};

/** @enum {string} */
Expand Down
6 changes: 6 additions & 0 deletions src/module/handlebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ function registerHandlebarsHelpers() {
// Handlebars.registerHelper('ratio', function (a, b) {
// return (a / b) * 100;
// });

Handlebars.registerHelper('test', function (...args) {
const [cb, ...params] = args;
if (typeof cb !== 'function') return true;
return !!cb(...params);
});
}

export function initializeHandlebars() {
Expand Down
10 changes: 10 additions & 0 deletions src/module/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ export function registerSystemSettings() {
config: true,
type: Boolean,
default: false,
requiresReload: true,
});

game.settings.register(MODULE_ID, SETTINGS_KEYS.AUTO_SELECT_BEST_CARD, {
name: 'SETTINGS.AutoSelectBestCard',
hint: 'SETTINGS.AutoSelectBestCardHint',
scope: 'world',
config: true,
type: Boolean,
default: false,
});

game.settings.register(MODULE_ID, SETTINGS_KEYS.INITIATIVE_MESSAGING, {
Expand Down
20 changes: 18 additions & 2 deletions src/sidebar/combat-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
/* https://gitlab.com/peginc/swade */
/* ------------------------------------------ */

import {
combatTrackerOnToggleDefeatedStatus,
duplicateCombatant, getCombatantsSharingToken,
} from '@combat/duplicate-combatant';
import { YZEC } from '@module/config';
import { MODULE_ID, SETTINGS_KEYS } from '@module/constants';
import { combatTrackerOnToggleDefeatedStatus,
duplicateCombatant, getCombatantsSharingToken } from '@combat/duplicate-combatant';
import { getCombatantSortOrderModifier, resetInitiativeDeck } from '@utils/utils';
import YearZeroCombatGroupColor from '../apps/combat-group-color';

Expand Down Expand Up @@ -437,6 +439,10 @@ export default class YearZeroCombatTracker extends CombatTracker {
cfg.buttons.unshift(...YZEC.CombatTracker.DefaultCombatantControls.slowAndFastActions);
}

if (game.settings.get(MODULE_ID, SETTINGS_KEYS.RESET_EACH_ROUND)) {
cfg.buttons.unshift(...YZEC.CombatTracker.DefaultCombatantControls.lockInitiative);
}

CONFIG.YZE_COMBAT.CombatTracker.config = cfg;
return cfg;
}
Expand Down Expand Up @@ -473,6 +479,16 @@ export default class YearZeroCombatTracker extends CombatTracker {
const sortedButtons = buttons.reduce(
(acc, button) => {
const { visibility, ...buttonConfig } = button;

// Create getters for the button properties.
if (!CONFIG.Combatant.documentClass.prototype.hasOwnProperty(buttonConfig.property)) {
Object.defineProperty(CONFIG.Combatant.documentClass.prototype, buttonConfig.property, {
get() {
return this.getFlag(MODULE_ID, buttonConfig.property);
},
});
}

if (visibility === 'gm') {
acc.gmButtons.push(buttonConfig);
}
Expand Down
8 changes: 5 additions & 3 deletions src/templates/sidebar/combat-tracker.hbs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{{! Defines a partial that is used below to iterate over various button contexts}}
{{#*inline "buttonPartial"}}
{{#each buttons}}
<a class="combatant-control {{#if (lookup ../combatant property)}}active{{/if}}" id={{id}} data-event={{eventName}}
data-property="{{property}}" data-tooltip="{{localize label}}">
<i class="fas {{icon}}"></i></a>
{{#if (test condition @root.combat ../combatant)}}
<a class="combatant-control {{#if (lookup ../combatant property)}}active{{/if}}" id={{id}} data-event={{eventName}}
data-property="{{property}}" data-tooltip="{{localize label}}">
<i class="fas {{icon}}"></i></a>
{{/if}}
{{/each}}
{{/inline}}

Expand Down
9 changes: 7 additions & 2 deletions src/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,16 @@ export function getInitiativeDeckDiscardPile(strict = false) {
* Recalls all the discarded initiative cards
* and shuffles them back into the initiative deck.
* @param {boolean} [chatNotification=false] Whether to send a chat notification.
* @param {number[]} [toExclude=[]] Array of card values to exclude from the reset.
*/
export async function resetInitiativeDeck(chatNotification = false) {
export async function resetInitiativeDeck(chatNotification = false, toExclude = []) {
const initiativeDeck = getInitiativeDeck(true);
await initiativeDeck.recall({ chatNotification });
const discardPile = getInitiativeDeckDiscardPile(true);
const toRecall = discardPile.cards.filter(c => !toExclude.includes(c.value)).map(c => c.id);

await discardPile.pass(initiativeDeck, toRecall, { chatNotification });
await initiativeDeck.shuffle({ chatNotification });

ui.notifications.info('YZEC.Combat.Initiative.ResetDeck', { localize: true });
}

Expand Down

0 comments on commit 8290904

Please sign in to comment.