Added Looping

This commit is contained in:
WBHarry 2025-11-18 18:06:54 +01:00
parent 9e21aa177b
commit 6536e84afe
13 changed files with 237 additions and 94 deletions

View file

@ -328,19 +328,26 @@
}, },
"Countdown": { "Countdown": {
"addCountdown": "Add Countdown", "addCountdown": "Add Countdown",
"loopingTypes": {
"noLooping": "No Looping",
"looping": "Looping",
"increasing": "Increasing",
"decreasing": "Decreasing"
},
"FIELDS": { "FIELDS": {
"countdowns": { "countdowns": {
"element": { "element": {
"name": { "label": "Name" }, "name": { "label": "Name" },
"progress": { "progress": {
"current": { "label": "Current" }, "current": { "label": "Current" },
"looping": { "label": "Looping" },
"max": { "label": "Max" }, "max": { "label": "Max" },
"type": { "type": {
"label": { "label": "Label", "hint": "Used for custom" }, "label": { "label": "Label", "hint": "Used for custom" },
"value": { "label": "Value" } "value": { "label": "Value" }
} }
}, },
"type": { "label": "Countdown Type" } "type": { "label": "Progression Type" }
} }
} }
}, },
@ -367,7 +374,9 @@
"currentCountdownValue": "Current: {value}", "currentCountdownValue": "Current: {value}",
"currentCountdownMax": "Max: {value}", "currentCountdownMax": "Max: {value}",
"category": "Category", "category": "Category",
"type": "Type", "progressionType": "Progression Type",
"decreasing": "Decreasing",
"looping": "Looping",
"defaultOwnershipTooltip": "The default player ownership of countdowns", "defaultOwnershipTooltip": "The default player ownership of countdowns",
"hideNewCountdowns": "Hide New Countdowns" "hideNewCountdowns": "Hide New Countdowns"
}, },

View file

@ -267,4 +267,9 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId; const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
this.action.item.effects.get(id).sheet.render(true); this.action.item.effects.get(id).sheet.render(true);
} }
async close(options) {
this.tabGroups.primary = 'base';
await super.close(options);
}
} }

View file

@ -136,7 +136,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
if (this.viewed.turn !== toggleTurn) { if (this.viewed.turn !== toggleTurn) {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.spotlight.id); await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id);
const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints; const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints;
if (autoPoints) { if (autoPoints) {

View file

@ -44,7 +44,8 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels; context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
context.defaultOwnership = this.data.defaultOwnership; context.defaultOwnership = this.data.defaultOwnership;
context.countdownBaseTypes = CONFIG.DH.GENERAL.countdownBaseTypes; context.countdownBaseTypes = CONFIG.DH.GENERAL.countdownBaseTypes;
context.countdownTypes = CONFIG.DH.GENERAL.countdownTypes; context.countdownProgressionTypes = CONFIG.DH.GENERAL.countdownProgressionTypes;
context.countdownLoopingTypes = CONFIG.DH.GENERAL.countdownLoopingTypes;
context.hideNewCountdowns = this.hideNewCountdowns; context.hideNewCountdowns = this.hideNewCountdowns;
context.countdowns = Object.keys(this.data.countdowns).reduce((acc, key) => { context.countdowns = Object.keys(this.data.countdowns).reduce((acc, key) => {
const countdown = this.data.countdowns[key]; const countdown = this.data.countdowns[key];
@ -53,7 +54,9 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].label), typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].label),
progress: { progress: {
...countdown.progress, ...countdown.progress,
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownTypes[countdown.progress.type].label) typeName: game.i18n.localize(
CONFIG.DH.GENERAL.countdownProgressionTypes[countdown.progress.type].label
)
}, },
editing: this.editingCountdowns.has(key) editing: this.editingCountdowns.has(key)
}; };

View file

@ -33,6 +33,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
actions: { actions: {
toggleViewMode: DhCountdowns.#toggleViewMode, toggleViewMode: DhCountdowns.#toggleViewMode,
editCountdowns: DhCountdowns.#editCountdowns, editCountdowns: DhCountdowns.#editCountdowns,
loopCountdown: DhCountdowns.#loopCountdown,
decreaseCountdown: (_, target) => this.editCountdown(false, target), decreaseCountdown: (_, target) => this.editCountdown(false, target),
increaseCountdown: (_, target) => this.editCountdown(true, target) increaseCountdown: (_, target) => this.editCountdown(true, target)
}, },
@ -121,7 +122,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
acc[key] = { acc[key] = {
...countdown, ...countdown,
editable: game.user.isGM || ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, editable: game.user.isGM || ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER,
playerAccess: playersWithAccess.length !== nonGmPlayers.length ? playersWithAccess : [],
noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0 noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0
}; };
return acc; return acc;
@ -176,6 +176,28 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
new game.system.api.applications.ui.CountdownEdit().render(true); new game.system.api.applications.ui.CountdownEdit().render(true);
} }
static async #loopCountdown(_, target) {
if (!DhCountdowns.canPerformEdit()) return;
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
const countdown = settings.countdowns[target.id];
const newMax =
countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
? countdown.progress.max + 1
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
? Math.max(countdown.progress.max - 1, 0)
: countdown.progress.max;
await settings.updateSource({
[`countdowns.${target.id}.progress`]: {
current: newMax,
max: newMax
}
});
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
refreshType: RefreshType.Countdown
});
}
static async editCountdown(increase, target) { static async editCountdown(increase, target) {
if (!DhCountdowns.canPerformEdit()) return; if (!DhCountdowns.canPerformEdit()) return;

View file

@ -611,7 +611,7 @@ export const abilityCosts = {
resource: itemAbilityCosts.resource resource: itemAbilityCosts.resource
}; };
export const countdownTypes = { export const countdownProgressionTypes = {
spotlight: { spotlight: {
id: 'spotlight', id: 'spotlight',
label: 'DAGGERHEART.CONFIG.CountdownType.spotlight' label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
@ -681,6 +681,25 @@ export const countdownBaseTypes = {
} }
}; };
export const countdownLoopingTypes = {
noLooping: {
id: 'noLooping',
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.noLooping'
},
looping: {
id: 'looping',
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.looping'
},
increasing: {
id: 'increasing',
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.increasing'
},
decreasing: {
id: 'decreasing',
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.decreasing'
}
};
export const countdownAppMode = { export const countdownAppMode = {
textIcon: 'text-icon', textIcon: 'text-icon',
iconOnly: 'icon-only' iconOnly: 'icon-only'

View file

@ -38,7 +38,7 @@ export default class DHAttackAction extends DHDamageAction {
const result = await super.use(event, options); const result = await super.use(event, options);
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id); await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
return result; return result;
} }

View file

@ -105,8 +105,8 @@ class DhOldCountdown extends foundry.abstract.DataModel {
type: new fields.SchemaField({ type: new fields.SchemaField({
value: new fields.StringField({ value: new fields.StringField({
required: true, required: true,
choices: CONFIG.DH.GENERAL.countdownTypes, choices: CONFIG.DH.GENERAL.countdownProgressionTypes,
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id, initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id,
label: 'DAGGERHEART.GENERAL.type' label: 'DAGGERHEART.GENERAL.type'
}), }),
label: new fields.StringField({ label: new fields.StringField({
@ -171,10 +171,16 @@ export class DhCountdown extends foundry.abstract.DataModel {
initial: 1, initial: 1,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.max.label' label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.max.label'
}), }),
looping: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.countdownLoopingTypes,
initial: CONFIG.DH.GENERAL.countdownLoopingTypes.noLooping.id,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.looping.label'
}),
type: new fields.StringField({ type: new fields.StringField({
required: true, required: true,
choices: CONFIG.DH.GENERAL.countdownTypes, choices: CONFIG.DH.GENERAL.countdownProgressionTypes,
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id, initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.type.label' label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.type.label'
}) })
}) })

View file

@ -63,58 +63,65 @@
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: light-dark(@dark-blue, @golden) transparent; scrollbar-color: light-dark(@dark-blue, @golden) transparent;
.countdown-edit-container { .countdown-edit-outer-container {
display: grid; display: flex;
grid-template-columns: 48px 1fr 72px; flex-direction: column;
align-items: center; align-items: flex-start;
gap: 8px; gap: 8px;
img { .countdown-edit-container {
width: 52px; width: 100%;
height: 52px; display: grid;
border-radius: 6px; grid-template-columns: 48px 1fr 72px;
} align-items: center;
.countdown-edit-text {
display: flex;
flex-direction: column;
justify-content: center;
gap: 8px; gap: 8px;
.countdown-edit-subtext { img {
width: 52px;
height: 52px;
border-radius: 6px;
}
.countdown-edit-text {
display: flex; display: flex;
gap: 10px; flex-direction: column;
justify-content: center;
gap: 8px;
.countdown-edit-sub-tag { .countdown-edit-subtext {
padding: 3px 5px; display: flex;
font-size: var(--font-size-12); align-items: center;
font: @font-body; gap: 10px;
background: light-dark(@dark-15, @beige-15); .countdown-edit-sub-tag {
border: 1px solid light-dark(@dark, @beige); padding: 3px 5px;
border-radius: 3px; font-size: var(--font-size-12);
font: @font-body;
background: light-dark(@dark-15, @beige-15);
border: 1px solid light-dark(@dark, @beige);
border-radius: 3px;
}
}
}
.countdown-edit-tools {
display: flex;
gap: 8px;
&.same-row {
margin-top: 17.5px;
}
a {
font-size: 16px;
} }
} }
} }
.countdown-edit-tools {
display: flex;
gap: 8px;
&.same-row {
margin-top: 17.5px;
}
a {
font-size: 16px;
}
}
} }
.countdown-edit-subrow { .countdown-edit-subrow {
display: flex; display: flex;
gap: 16px; gap: 8px;
margin: 0 72px 0 56px;
} }
.countdown-edit-input { .countdown-edit-input {
@ -124,11 +131,29 @@
align-items: flex-start; align-items: flex-start;
gap: 2px; gap: 2px;
&.type-input {
min-width: 120px;
}
&.looping-input {
min-width: 120px;
}
&.tiny { &.tiny {
flex: 0; flex: 0;
input { input {
min-width: 2.5rem; min-width: 2.5rem;
} }
.checkbox {
width: 100%;
display: flex;
justify-content: center;
input {
min-width: auto;
}
}
} }
input, input,

View file

@ -55,7 +55,7 @@
.countdown-container { .countdown-container {
display: flex; display: flex;
justify-content: space-between; width: 100%;
&.icon-only { &.icon-only {
gap: 8px; gap: 8px;
@ -72,6 +72,7 @@
} }
.countdown-main-container { .countdown-main-container {
width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16px; gap: 16px;
@ -83,6 +84,7 @@
} }
.countdown-content { .countdown-content {
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
@ -90,7 +92,13 @@
.countdown-tools { .countdown-tools {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16px; justify-content: space-between;
.countdown-tool-controls {
display: flex;
align-items: center;
gap: 16px;
}
.progress-tag { .progress-tag {
border: 1px solid; border: 1px solid;
@ -98,32 +106,53 @@
padding: 2px 4px; padding: 2px 4px;
background-color: light-dark(@beige, @dark-blue); background-color: light-dark(@beige, @dark-blue);
} }
.countdown-tool-icons {
display: flex;
align-items: center;
gap: 8px;
.looping-container {
position: relative;
border: 1px solid light-dark(white, white);
border-radius: 6px;
padding: 2px 4px;
&.should-loop {
background: light-dark(@golden, @golden);
.loop-marker {
color: light-dark(@dark-blue, @dark-blue);
}
}
.direction-marker {
position: absolute;
font-size: 10px;
filter: drop-shadow(0 0 3px black);
top: -3px;
}
}
}
} }
} }
} }
/* Keep incase we want to reintroduce the player pips */
// .countdown-access-container {
// display: grid;
// grid-template-columns: 1fr 1fr 1fr;
// grid-auto-rows: min-content;
// width: 38px;
// gap: 4px;
.countdown-access-container { // .countdown-access {
display: grid; // height: 10px;
grid-template-columns: 1fr 1fr 1fr; // width: 10px;
grid-auto-rows: min-content; // border-radius: 50%;
width: 38px; // border: 1px solid light-dark(@dark-blue, @beige-80);
gap: 4px; // content: '';
// }
.countdown-access { // }
height: 10px;
width: 10px;
border-radius: 50%;
border: 1px solid light-dark(@dark-blue, @beige-80);
content: '';
}
}
.countdown-no-access-container {
width: 38px;
display: flex;
align-items: center;
justify-content: center;
}
} }
} }
} }

View file

@ -14,6 +14,7 @@
</div> </div>
{{formField ../fields.img value=countdown.img name=(concat "countdown." index ".img") label="DAGGERHEART.GENERAL.imagePath" localize=true}} {{formField ../fields.img value=countdown.img name=(concat "countdown." index ".img") label="DAGGERHEART.GENERAL.imagePath" localize=true}}
<div class="nest-inputs"> <div class="nest-inputs">
{{formField ../fields.progress.fields.looping value=countdown.progress.looping name=(concat "countdown." index ".progress.looping") localize=true}}
{{formField ../fields.progress.fields.type value=countdown.progress.type name=(concat "countdown." index ".progress.type") localize=true}} {{formField ../fields.progress.fields.type value=countdown.progress.type name=(concat "countdown." index ".progress.type") localize=true}}
{{formField ../fields.progress.fields.max value=countdown.progress.max name=(concat "countdown." index ".progress.max") localize=true}} {{formField ../fields.progress.fields.max value=countdown.progress.max name=(concat "countdown." index ".progress.max") localize=true}}
</div> </div>

View file

@ -22,6 +22,7 @@
<div class="edit-content"> <div class="edit-content">
{{#each countdowns as | countdown id | }} {{#each countdowns as | countdown id | }}
<fieldset class="countdown-edit-outer-container">
<div class="countdown-edit-container {{#unless countdown.editing}}viewing{{/unless}}" data-id="{{id}}"> <div class="countdown-edit-container {{#unless countdown.editing}}viewing{{/unless}}" data-id="{{id}}">
<a data-action="editCountdownImage" id="{{id}}"><img src="{{countdown.img}}" /></a> <a data-action="editCountdownImage" id="{{id}}"><img src="{{countdown.img}}" /></a>
{{#unless countdown.editing}} {{#unless countdown.editing}}
@ -32,6 +33,15 @@
<div class="countdown-edit-sub-tag">{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.currentCountdownMax" value=countdown.progress.max}}</div> <div class="countdown-edit-sub-tag">{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.currentCountdownMax" value=countdown.progress.max}}</div>
<div class="countdown-edit-sub-tag">{{countdown.typeName}}</div> <div class="countdown-edit-sub-tag">{{countdown.typeName}}</div>
<div class="countdown-edit-sub-tag">{{countdown.progress.typeName}}</div> <div class="countdown-edit-sub-tag">{{countdown.progress.typeName}}</div>
{{#unless (eq countdown.progress.looping "noLooping")}}
<i class="fa-solid fa-repeat"></i>
{{#if (eq countdown.progress.looping "increasing")}}
<i class="fa-solid fa-angles-up"></i>
{{else if (eq countdown.progress.looping "decreasing")}}
<i class="fa-solid fa-angles-down"></i>
{{/if}}
{{/unless}}
</div> </div>
</div> </div>
{{else}} {{else}}
@ -56,20 +66,27 @@
<label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.max"}}</label> <label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.max"}}</label>
<input type="number" name="{{concat "countdowns." id ".progress.max"}}" value="{{countdown.progress.max}}" /> <input type="number" name="{{concat "countdowns." id ".progress.max"}}" value="{{countdown.progress.max}}" />
</div> </div>
<div class="countdown-edit-input"> <div class="countdown-edit-input tiny type-input">
<label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.category"}}</label> <label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.category"}}</label>
<select name="{{concat "countdowns." id ".type"}}"> <select name="{{concat "countdowns." id ".type"}}">
{{selectOptions ../countdownBaseTypes selected=countdown.type localize=true}} {{selectOptions ../countdownBaseTypes selected=countdown.type localize=true}}
</select> </select>
</div> </div>
<div class="countdown-edit-input"> <div class="countdown-edit-input">
<label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.type"}}</label> <label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.progressionType"}}</label>
<select name="{{concat "countdowns." id ".progress.type"}}"> <select name="{{concat "countdowns." id ".progress.type"}}">
{{selectOptions ../countdownTypes selected=countdown.progress.type valueAttr="id" labelAttr="label" localize=true}} {{selectOptions ../countdownProgressionTypes selected=countdown.progress.type localize=true}}
</select>
</div>
<div class="countdown-edit-input looping-input tiny">
<label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.looping"}}</label>
<select name="{{concat "countdowns." id ".progress.looping"}}">
{{selectOptions ../countdownLoopingTypes selected=countdown.progress.looping localize=true}}
</select> </select>
</div> </div>
</div> </div>
{{/if}} {{/if}}
</fieldset>
{{/each}} {{/each}}
</div> </div>
</div> </div>

View file

@ -7,26 +7,33 @@
<div class="countdown-content"> <div class="countdown-content">
{{#unless ../iconOnly}}<label>{{countdown.name}}</label>{{/unless}} {{#unless ../iconOnly}}<label>{{countdown.name}}</label>{{/unless}}
<div class="countdown-tools"> <div class="countdown-tools">
{{#if countdown.editable}}<a data-action="decreaseCountdown" id="{{id}}"><i class="fa-solid fa-minus"></i></a>{{/if}} <div class="countdown-tool-controls">
<div class="progress-tag"> {{#if countdown.editable}}<a data-action="decreaseCountdown" id="{{id}}"><i class="fa-solid fa-minus"></i></a>{{/if}}
{{countdown.progress.current}}/{{countdown.progress.max}} <div class="progress-tag">
{{countdown.progress.current}}/{{countdown.progress.max}}
</div>
{{#if countdown.editable}}<a data-action="increaseCountdown" id="{{id}}"><i class="fa-solid fa-plus"></i></a>{{/if}}
</div>
<div class="countdown-tool-icons">
{{#if (and @root.isGM (not ../iconOnly))}}
{{#if countdown.noPlayerAccess}}
<i class="fa-solid fa-eye-slash" data-tooltip="{{localize "DAGGERHEART.UI.Countdowns.noPlayerAccess"}}"></i>
{{/if}}
{{#unless (eq countdown.progress.looping "noLooping")}}
<a class="looping-container {{#if (eq countdown.progress.current 0)}}should-loop{{/if}}" {{#if (gt countdown.progress.current 0)}}disabled{{/if}} data-action="loopCountdown" id="{{id}}">
<i class="loop-marker fa-solid fa-repeat"></i>
{{#if (eq countdown.progress.looping "increasing")}}
<i class="direction-marker fa-solid fa-angles-up"></i>
{{else if (eq countdown.progress.looping "decreasing")}}
<i class="direction-marker fa-solid fa-angles-down"></i>
{{/if}}
</a>
{{/unless}}
{{/if}}
</div> </div>
{{#if countdown.editable}}<a data-action="increaseCountdown" id="{{id}}"><i class="fa-solid fa-plus"></i></a>{{/if}}
</div> </div>
</div> </div>
</div> </div>
{{#if (and @root.isGM (not ../iconOnly))}}
{{#if (gt countdown.playerAccess.length 0)}}
<div class="countdown-access-container">
{{#each countdown.playerAccess as |player|}}
<div class="countdown-access" style="{{concat "background: " player.color.css ";"}}" data-tooltip="{{player.name}}"></div>
{{/each}}
</div>
{{/if}}
{{#if countdown.noPlayerAccess}}
<div class="countdown-no-access-container"><i class="fa-solid fa-eye-slash" data-tooltip="{{localize "DAGGERHEART.UI.Countdowns.noPlayerAccess"}}"></i></div>
{{/if}}
{{/if}}
</div> </div>
{{/each}} {{/each}}
</div> </div>