mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
Added Combat and CombatTracker
This commit is contained in:
parent
32730b3aac
commit
7eb662ac21
20 changed files with 900 additions and 334 deletions
|
|
@ -3,11 +3,10 @@ import * as applications from './module/applications/_module.mjs';
|
|||
import * as models from './module/data/_module.mjs';
|
||||
import * as documents from './module/documents/_module.mjs';
|
||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||
import DhpCombatTracker from './module/ui/combatTracker.mjs';
|
||||
import DhCombatTracker from './module/ui/combatTracker.mjs';
|
||||
import { GMUpdateEvent, handleSocketEvent, socketEvent } from './module/helpers/socket.mjs';
|
||||
import { registerDHSettings } from './module/applications/settings.mjs';
|
||||
import DhpChatLog from './module/ui/chatLog.mjs';
|
||||
import DhpPlayers from './module/ui/players.mjs';
|
||||
import DhpRuler from './module/ui/ruler.mjs';
|
||||
import DhpTokenRuler from './module/ui/tokenRuler.mjs';
|
||||
import { dualityRollEnricher } from './module/enrichers/DualityRollEnricher.mjs';
|
||||
|
|
@ -74,11 +73,11 @@ Hooks.once('init', () => {
|
|||
Actors.registerSheet(SYSTEM.id, applications.DhpEnvironment, { types: ['environment'], makeDefault: true });
|
||||
|
||||
CONFIG.Combat.dataModels = {
|
||||
base: models.DhpCombat
|
||||
base: models.DhCombat
|
||||
};
|
||||
|
||||
CONFIG.Combatant.dataModels = {
|
||||
base: models.DhpCombatant
|
||||
base: models.DhCombatant
|
||||
};
|
||||
|
||||
CONFIG.ChatMessage.dataModels = {
|
||||
|
|
@ -91,7 +90,7 @@ Hooks.once('init', () => {
|
|||
|
||||
CONFIG.Canvas.rulerClass = DhpRuler;
|
||||
CONFIG.Combat.documentClass = documents.DhpCombat;
|
||||
CONFIG.ui.combat = DhpCombatTracker;
|
||||
CONFIG.ui.combat = DhCombatTracker;
|
||||
CONFIG.ui.chat = DhpChatLog;
|
||||
// CONFIG.ui.players = DhpPlayers;
|
||||
CONFIG.Token.rulerClass = DhpTokenRuler;
|
||||
|
|
@ -111,8 +110,8 @@ Hooks.once('init', () => {
|
|||
|
||||
Hooks.on('ready', () => {
|
||||
ui.resources = new CONFIG.ui.resources();
|
||||
ui.resources.render({force: true});
|
||||
})
|
||||
ui.resources.render({ force: true });
|
||||
});
|
||||
|
||||
Hooks.once('dicesoniceready', () => {});
|
||||
|
||||
|
|
|
|||
19
lang/en.json
19
lang/en.json
|
|
@ -61,6 +61,13 @@
|
|||
"outline": "Outline",
|
||||
"edge": "Edge"
|
||||
}
|
||||
},
|
||||
"VariantRules": {
|
||||
"title": "Variant Rules",
|
||||
"label": "Variant Rules",
|
||||
"hint": "Apply variant rules from the Daggerheart system",
|
||||
"name": "Variant Rules",
|
||||
"actionTokens": "Action Tokens"
|
||||
}
|
||||
},
|
||||
"Automation": {
|
||||
|
|
@ -101,6 +108,12 @@
|
|||
"Hint": "Enable measuring of ranges with the ruler according to set distances."
|
||||
}
|
||||
},
|
||||
"VariantRules": {
|
||||
"ActionTokens": {
|
||||
"Name": "Action Tokens",
|
||||
"Hint": "Give each player action tokens to use in combat"
|
||||
}
|
||||
},
|
||||
"DualityRollColor": {
|
||||
"Name": "Duality Roll Colour Scheme",
|
||||
"Hint": "The display type for Duality Rolls",
|
||||
|
|
@ -329,6 +342,12 @@
|
|||
"grimoire": "Grimoire"
|
||||
}
|
||||
},
|
||||
"Combat": {
|
||||
"giveSpotlight": "Give The Spotlight",
|
||||
"requestSpotlight": "Request The Spotlight",
|
||||
"requestingSpotlight": "Requesting The Spotlight",
|
||||
"combatStarted": "Active"
|
||||
},
|
||||
"LevelUp": {
|
||||
"Tier1": {
|
||||
"Label": "Level 2-4",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import DhAppearance from '../data/settings/Appearance.mjs';
|
||||
import DHAppearanceSettings from './settings/appearanceSettings.mjs';
|
||||
import DhVariantRules from '../data/settings/VariantRules.mjs';
|
||||
import DHVariantRuleSettings from './settings/variantRuleSettings.mjs';
|
||||
|
||||
class DhpAutomationSettings extends FormApplication {
|
||||
constructor(object = {}, options = {}) {
|
||||
|
|
@ -181,7 +183,8 @@ export const registerDHSettings = () => {
|
|||
type: Number,
|
||||
default: 0,
|
||||
onChange: () => {
|
||||
if(ui.resources) ui.resources.render({force: true});
|
||||
if (ui.resources) ui.resources.render({ force: true });
|
||||
ui.combat.render({ force: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -193,7 +196,7 @@ export const registerDHSettings = () => {
|
|||
type: Number,
|
||||
default: 12,
|
||||
onChange: () => {
|
||||
if(ui.resources) ui.resources.render({force: true});
|
||||
if (ui.resources) ui.resources.render({ force: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -204,15 +207,15 @@ export const registerDHSettings = () => {
|
|||
config: true,
|
||||
type: String,
|
||||
choices: {
|
||||
'token': 'Tokens',
|
||||
'bar': 'Bar',
|
||||
'hide': 'Hide'
|
||||
token: 'Tokens',
|
||||
bar: 'Bar',
|
||||
hide: 'Hide'
|
||||
},
|
||||
default: 'token',
|
||||
onChange: value => {
|
||||
if(ui.resources) {
|
||||
if(value === 'hide') ui.resources.close({allowed: true});
|
||||
else ui.resources.render({force: true});
|
||||
if (ui.resources) {
|
||||
if (value === 'hide') ui.resources.close({ allowed: true });
|
||||
else ui.resources.render({ force: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -251,6 +254,13 @@ export const registerDHSettings = () => {
|
|||
}
|
||||
});
|
||||
|
||||
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.variantRules, {
|
||||
scope: 'world',
|
||||
config: false,
|
||||
type: DhVariantRules,
|
||||
default: DhVariantRules.defaultSchema
|
||||
});
|
||||
|
||||
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance, {
|
||||
scope: 'client',
|
||||
config: false,
|
||||
|
|
@ -291,4 +301,13 @@ export const registerDHSettings = () => {
|
|||
type: DHAppearanceSettings,
|
||||
restricted: false
|
||||
});
|
||||
|
||||
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.menu.VariantRules.Name, {
|
||||
name: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.title'),
|
||||
label: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.label'),
|
||||
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.hint'),
|
||||
icon: SYSTEM.SETTINGS.menu.VariantRules.Icon,
|
||||
type: DHVariantRuleSettings,
|
||||
restricted: false
|
||||
});
|
||||
};
|
||||
|
|
|
|||
59
module/applications/settings/variantRuleSettings.mjs
Normal file
59
module/applications/settings/variantRuleSettings.mjs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import DhVariantRules from '../../data/settings/VariantRules.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class DHVariantRuleSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor() {
|
||||
super({});
|
||||
|
||||
this.settings = new DhVariantRules(
|
||||
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.variantRules).toObject()
|
||||
);
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.name');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'daggerheart-appearance-settings',
|
||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
||||
position: { width: '600', height: 'auto' },
|
||||
actions: {
|
||||
reset: this.reset,
|
||||
save: this.save
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true }
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: 'systems/daggerheart/templates/settings/variant-rules.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.settingFields = this.settings;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async updateData(event, element, formData) {
|
||||
const updatedSettings = foundry.utils.expandObject(formData.object);
|
||||
|
||||
await this.settings.updateSource(updatedSettings);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async reset() {
|
||||
this.settings = new DhVariantRules();
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async save() {
|
||||
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.variantRules, this.settings.toObject());
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,10 @@ export const menu = {
|
|||
Range: {
|
||||
Name: 'GameSettingsRange',
|
||||
Icon: 'fa-solid fa-ruler'
|
||||
},
|
||||
VariantRules: {
|
||||
Name: 'GameSettingsVariantrules',
|
||||
Icon: 'fa-solid fa-scale-balanced'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -27,5 +31,6 @@ export const gameSettings = {
|
|||
AbilityArray: 'AbilityArray',
|
||||
RangeMeasurement: 'RangeMeasurement'
|
||||
},
|
||||
appearance: 'Appearance'
|
||||
appearance: 'Appearance',
|
||||
variantRules: 'VariantRules'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
export { default as DhpPC } from './pc.mjs';
|
||||
export { default as DhpClass } from './class.mjs';
|
||||
export { default as DhpSubclass } from './subclass.mjs';
|
||||
export { default as DhpCombat } from './combat.mjs';
|
||||
export { default as DhpCombatant } from './combatant.mjs';
|
||||
export { default as DhCombat } from './combat.mjs';
|
||||
export { default as DhCombatant } from './combatant.mjs';
|
||||
export { default as DhpAdversary } from './adversary.mjs';
|
||||
export { default as DhpFeature } from './feature.mjs';
|
||||
export { default as DhpDomainCard } from './domainCard.mjs';
|
||||
|
|
@ -17,3 +17,6 @@ export { default as DhpAdversaryRoll } from './adversaryRoll.mjs';
|
|||
export { default as DhpDamageRoll } from './damageRoll.mjs';
|
||||
export { default as DhpAbilityUse } from './abilityUse.mjs';
|
||||
export { default as DhpEnvironment } from './environment.mjs';
|
||||
|
||||
export * as items from './item/_module.mjs';
|
||||
export * as messages from './chat-message/_modules.mjs';
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
export default class DhpCombat extends foundry.abstract.TypeDataModel {
|
||||
export default class DhCombat extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
actions: new fields.NumberField({ initial: 0, integer: true }),
|
||||
activeCombatant: new fields.StringField({})
|
||||
started: new fields.BooleanField({ required: true, initial: false })
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
export default class DhpCombatant extends foundry.abstract.TypeDataModel {
|
||||
export default class DhCombatant extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
active: new fields.BooleanField({ initial: false })
|
||||
spotlight: new fields.SchemaField({
|
||||
requesting: new fields.BooleanField({ required: true, initial: false }),
|
||||
active: new fields.BooleanField({ required: true, initial: false })
|
||||
}),
|
||||
actionTokens: new fields.NumberField({ required: true, integer: true, initial: 3 })
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
module/data/settings/VariantRules.mjs
Normal file
13
module/data/settings/VariantRules.mjs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export default class DhVariantRules extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
actionTokens: new fields.SchemaField({
|
||||
enabled: new fields.BooleanField({ required: true, initial: false }),
|
||||
tokens: new fields.NumberField({ required: true, integer: true, initial: 3 })
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
static defaultSchema = {};
|
||||
}
|
||||
|
|
@ -1,44 +1,53 @@
|
|||
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
|
||||
|
||||
export default class DhpCombat extends Combat {
|
||||
_sortCombatants(a, b) {
|
||||
if (a.isNPC !== b.isNPC) {
|
||||
const aVal = a.isNPC ? 0 : 1;
|
||||
const bVal = b.isNPC ? 0 : 1;
|
||||
get combatant() {
|
||||
return this.combatants.contents.find(x => x.system.spotlight.active) ?? null;
|
||||
}
|
||||
|
||||
return aVal - bVal;
|
||||
async startCombat() {
|
||||
this._playCombatSound('startEncounter');
|
||||
const updateData = { 'system.started': true };
|
||||
Hooks.callAll('combatStart', this, updateData);
|
||||
await this.update(updateData);
|
||||
return this;
|
||||
}
|
||||
|
||||
_sortCombatants(a, b) {
|
||||
const aNPC = Number(a.isNPC);
|
||||
const bNPC = Number(b.isNPC);
|
||||
if (aNPC !== bNPC) {
|
||||
return aNPC - bNPC;
|
||||
}
|
||||
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
async useActionToken(combatantId) {
|
||||
const automateActionPoints = await game.settings.get(
|
||||
SYSTEM.id,
|
||||
SYSTEM.SETTINGS.gameSettings.Automation.ActionPoints
|
||||
);
|
||||
// async useActionToken(combatantId) {
|
||||
// const automateActionPoints = await game.settings.get(
|
||||
// SYSTEM.id,
|
||||
// SYSTEM.SETTINGS.gameSettings.Automation.ActionPoints
|
||||
// );
|
||||
|
||||
if (game.user.isGM) {
|
||||
if (this.system.actions < 1) return;
|
||||
// if (game.user.isGM) {
|
||||
// if (this.system.actions < 1) return;
|
||||
|
||||
const update = automateActionPoints
|
||||
? { 'system.activeCombatant': combatantId, 'system.actions': Math.max(this.system.actions - 1, 0) }
|
||||
: { 'system.activeCombatant': combatantId };
|
||||
// const update = automateActionPoints
|
||||
// ? { 'system.activeCombatant': combatantId, 'system.actions': Math.max(this.system.actions - 1, 0) }
|
||||
// : { 'system.activeCombatant': combatantId };
|
||||
|
||||
await this.update(update);
|
||||
} else {
|
||||
const update = automateActionPoints
|
||||
? { 'system.activeCombatant': combatantId, 'system.actions': this.system.actions + 1 }
|
||||
: { 'system.activeCombatant': combatantId };
|
||||
// await this.update(update);
|
||||
// } else {
|
||||
// const update = automateActionPoints
|
||||
// ? { 'system.activeCombatant': combatantId, 'system.actions': this.system.actions + 1 }
|
||||
// : { 'system.activeCombatant': combatantId };
|
||||
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateDocument,
|
||||
uuid: this.uuid,
|
||||
update: update
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
// action: socketEvent.GMUpdate,
|
||||
// data: {
|
||||
// action: GMUpdateEvent.UpdateDocument,
|
||||
// uuid: this.uuid,
|
||||
// update: update
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,199 +1,96 @@
|
|||
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
|
||||
|
||||
export default class DhpCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||
constructor(data, context) {
|
||||
super(data, context);
|
||||
|
||||
Hooks.on(socketEvent.DhpFearUpdate, this.onFearUpdate);
|
||||
}
|
||||
|
||||
get template() {
|
||||
return 'systems/daggerheart/templates/ui/combatTracker.hbs';
|
||||
}
|
||||
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
html.on('click', '.token-action-tokens .use-action-token', this.useActionToken.bind(this));
|
||||
html.on('click', '.encounter-gm-resources .trade-actions', this.tradeActions.bind(this));
|
||||
html.on('click', '.encounter-gm-resources .trade-fear', this.tradeFear.bind(this));
|
||||
html.on('click', '.encounter-gm-resources .icon-button.up', this.increaseResource.bind(this));
|
||||
html.on('click', '.encounter-gm-resources .icon-button.down', this.decreaseResource.bind(this));
|
||||
}
|
||||
|
||||
async useActionToken(event) {
|
||||
event.stopPropagation();
|
||||
const combatant = event.currentTarget.dataset.combatant;
|
||||
await game.combat.useActionToken(combatant);
|
||||
}
|
||||
|
||||
async tradeActions(event) {
|
||||
if (event.currentTarget.classList.contains('disabled')) return;
|
||||
|
||||
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
|
||||
const value = currentFear + 1;
|
||||
|
||||
if (value <= 6) {
|
||||
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: { action: GMUpdateEvent.UpdateFear, update: value }
|
||||
});
|
||||
await game.combat.update({ 'system.actions': game.combat.system.actions - 2 });
|
||||
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||
static DEFAULT_OPTIONS = {
|
||||
actions: {
|
||||
requestSpotlight: this.requestSpotlight,
|
||||
toggleSpotlight: this.toggleSpotlight,
|
||||
setActionTokens: this.setActionTokens
|
||||
}
|
||||
}
|
||||
|
||||
async tradeFear() {
|
||||
if (event.currentTarget.classList.contains('disabled')) return;
|
||||
|
||||
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
|
||||
const value = currentFear - 1;
|
||||
if (value >= 0) {
|
||||
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: { action: GMUpdateEvent.UpdateFear, update: value }
|
||||
});
|
||||
await game.combat.update({ 'system.actions': game.combat.system.actions + 2 });
|
||||
}
|
||||
}
|
||||
|
||||
async increaseResource(event) {
|
||||
if (event.currentTarget.dataset.type === 'action') {
|
||||
await game.combat.update({ 'system.actions': game.combat.system.actions + 1 });
|
||||
}
|
||||
|
||||
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
|
||||
const value = currentFear + 1;
|
||||
if (event.currentTarget.dataset.type === 'fear' && value <= 6) {
|
||||
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: { action: GMUpdateEvent.UpdateFear, update: value }
|
||||
});
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
async decreaseResource(event) {
|
||||
if (event.currentTarget.dataset.type === 'action' && game.combat.system.actions - 1 >= 0) {
|
||||
await game.combat.update({ 'system.actions': game.combat.system.actions - 1 });
|
||||
}
|
||||
|
||||
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
|
||||
const value = currentFear - 1;
|
||||
if (event.currentTarget.dataset.type === 'fear' && value >= 0) {
|
||||
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: { action: GMUpdateEvent.UpdateFear, update: value }
|
||||
});
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
async getData(options = {}) {
|
||||
let context = await super.getData(options);
|
||||
|
||||
// Get the combat encounters possible for the viewed Scene
|
||||
const combat = this.viewed;
|
||||
const hasCombat = combat !== null;
|
||||
const combats = this.combats;
|
||||
const currentIdx = combats.findIndex(c => c === combat);
|
||||
const previousId = currentIdx > 0 ? combats[currentIdx - 1].id : null;
|
||||
const nextId = currentIdx < combats.length - 1 ? combats[currentIdx + 1].id : null;
|
||||
const settings = game.settings.get('core', Combat.CONFIG_SETTING);
|
||||
|
||||
// Prepare rendering data
|
||||
context = foundry.utils.mergeObject(context, {
|
||||
combats: combats,
|
||||
currentIndex: currentIdx + 1,
|
||||
combatCount: combats.length,
|
||||
hasCombat: hasCombat,
|
||||
combat,
|
||||
turns: [],
|
||||
previousId,
|
||||
nextId,
|
||||
started: this.started,
|
||||
control: false,
|
||||
settings,
|
||||
linked: combat?.scene !== null,
|
||||
labels: {}
|
||||
});
|
||||
context.labels.scope = game.i18n.localize(`COMBAT.${context.linked ? 'Linked' : 'Unlinked'}`);
|
||||
if (!hasCombat) return context;
|
||||
|
||||
// Format information about each combatant in the encounter
|
||||
let hasDecimals = false;
|
||||
const turns = [];
|
||||
for (let [i, combatant] of combat.turns.entries()) {
|
||||
if (!combatant.visible) continue;
|
||||
|
||||
// Prepare turn data
|
||||
const resource =
|
||||
combatant.permission >= CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER ? combatant.resource : null;
|
||||
const turn = {
|
||||
id: combatant.id,
|
||||
name: combatant.name,
|
||||
img: await this._getCombatantThumbnail(combatant),
|
||||
active: combatant.id === combat.system.activeCombatant,
|
||||
owner: combatant.isOwner,
|
||||
defeated: combatant.isDefeated,
|
||||
hidden: combatant.hidden,
|
||||
initiative: combatant.initiative,
|
||||
hasRolled: combatant.initiative !== null,
|
||||
hasResource: resource !== null,
|
||||
resource: resource,
|
||||
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
|
||||
playerCharacter: game.user?.character?.id === combatant.actor.id,
|
||||
ownedByPlayer: combatant.hasPlayerOwner
|
||||
};
|
||||
if (turn.initiative !== null && !Number.isInteger(turn.initiative)) hasDecimals = true;
|
||||
turn.css = [turn.active ? 'active' : '', turn.hidden ? 'hidden' : '', turn.defeated ? 'defeated' : '']
|
||||
.join(' ')
|
||||
.trim();
|
||||
|
||||
// Actor and Token status effects
|
||||
turn.effects = new Set();
|
||||
if (combatant.token) {
|
||||
combatant.token.effects.forEach(e => turn.effects.add(e));
|
||||
if (combatant.token.overlayEffect) turn.effects.add(combatant.token.overlayEffect);
|
||||
}
|
||||
if (combatant.actor) {
|
||||
for (const effect of combatant.actor.temporaryEffects) {
|
||||
if (effect.statuses.has(CONFIG.specialStatusEffects.DEFEATED)) turn.defeated = true;
|
||||
else if (effect.icon) turn.effects.add(effect.icon);
|
||||
}
|
||||
}
|
||||
turns.push(turn);
|
||||
}
|
||||
|
||||
// Format initiative numeric precision
|
||||
const precision = CONFIG.Combat.initiative.decimals;
|
||||
turns.forEach(t => {
|
||||
if (t.initiative !== null) t.initiative = t.initiative.toFixed(hasDecimals ? precision : 0);
|
||||
});
|
||||
|
||||
const fear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
|
||||
|
||||
// Merge update data for rendering
|
||||
return foundry.utils.mergeObject(context, {
|
||||
round: combat.round,
|
||||
turn: combat.turn,
|
||||
turns: turns,
|
||||
control: combat.combatant?.players?.includes(game.user),
|
||||
fear: fear
|
||||
});
|
||||
}
|
||||
|
||||
onFearUpdate = async () => {
|
||||
this.render(true);
|
||||
};
|
||||
|
||||
async close(options) {
|
||||
Hooks.off(socketEvent.DhpFearUpdate, this.onFearUpdate);
|
||||
static PARTS = {
|
||||
header: {
|
||||
template: 'systems/daggerheart/templates/ui/combat/combatTrackerHeader.hbs'
|
||||
},
|
||||
tracker: {
|
||||
template: 'systems/daggerheart/templates/ui/combat/combatTracker.hbs'
|
||||
},
|
||||
footer: {
|
||||
template: 'systems/daggerheart/templates/ui/combat/combatTrackerFooter.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
return super.close(options);
|
||||
async _prepareCombatContext(context, options) {
|
||||
await super._prepareCombatContext(context, options);
|
||||
|
||||
Object.assign(context, {
|
||||
fear: game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear)
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareTrackerContext(context, options) {
|
||||
await super._prepareTrackerContext(context, options);
|
||||
|
||||
Object.assign(context, {
|
||||
actionTokens: game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.variantRules).actionTokens
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareTurnContext(combat, combatant, index) {
|
||||
const turn = await super._prepareTurnContext(combat, combatant, index);
|
||||
return { ...turn, isNPC: combatant.isNPC, system: combatant.system.toObject() };
|
||||
}
|
||||
|
||||
_getCombatContextOptions() {
|
||||
return [
|
||||
{
|
||||
name: 'COMBAT.ClearMovementHistories',
|
||||
icon: '<i class="fa-solid fa-shoe-prints"></i>',
|
||||
condition: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
||||
callback: () => this.viewed.clearMovementHistories()
|
||||
},
|
||||
{
|
||||
name: 'COMBAT.Delete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
condition: () => game.user.isGM && !!this.viewed,
|
||||
callback: () => this.viewed.endCombat()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
static async requestSpotlight(_, target) {
|
||||
const { combatantId } = target.closest('[data-combatant-id]')?.dataset ?? {};
|
||||
const combatant = this.viewed.combatants.get(combatantId);
|
||||
await combatant.update({
|
||||
'system.spotlight': {
|
||||
requesting: !combatant.system.spotlight.requesting
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async toggleSpotlight(_, target) {
|
||||
const { combatantId } = target.closest('[data-combatant-id]')?.dataset ?? {};
|
||||
for (var combatant of this.viewed.combatants) {
|
||||
const giveSpotlight = combatant.id === combatantId;
|
||||
|
||||
await combatant.update({
|
||||
'system.spotlight': {
|
||||
requesting: giveSpotlight ? false : combatant.system.spotlight.requesting,
|
||||
active: giveSpotlight ? !combatant.system.spotlight.active : false
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static async setActionTokens(_, target) {
|
||||
const { combatantId, tokenIndex } = target.closest('[data-combatant-id]')?.dataset ?? {};
|
||||
|
||||
const combatant = this.viewed.combatants.get(combatantId);
|
||||
const changeIndex = Number(tokenIndex);
|
||||
const newIndex = combatant.system.actionTokens > changeIndex ? changeIndex : changeIndex + 1;
|
||||
|
||||
await combatant.update({ 'system.actionTokens': newIndex });
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
199
module/ui/combatTrackerOld.mjs
Normal file
199
module/ui/combatTrackerOld.mjs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
|
||||
|
||||
export default class DhpCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||
constructor(data, context) {
|
||||
super(data, context);
|
||||
|
||||
Hooks.on(socketEvent.DhpFearUpdate, this.onFearUpdate);
|
||||
}
|
||||
|
||||
get template() {
|
||||
return 'systems/daggerheart/templates/ui/combatTracker.hbs';
|
||||
}
|
||||
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
html.on('click', '.token-action-tokens .use-action-token', this.useActionToken.bind(this));
|
||||
html.on('click', '.encounter-gm-resources .trade-actions', this.tradeActions.bind(this));
|
||||
html.on('click', '.encounter-gm-resources .trade-fear', this.tradeFear.bind(this));
|
||||
html.on('click', '.encounter-gm-resources .icon-button.up', this.increaseResource.bind(this));
|
||||
html.on('click', '.encounter-gm-resources .icon-button.down', this.decreaseResource.bind(this));
|
||||
}
|
||||
|
||||
async useActionToken(event) {
|
||||
event.stopPropagation();
|
||||
const combatant = event.currentTarget.dataset.combatant;
|
||||
await game.combat.useActionToken(combatant);
|
||||
}
|
||||
|
||||
async tradeActions(event) {
|
||||
if (event.currentTarget.classList.contains('disabled')) return;
|
||||
|
||||
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
|
||||
const value = currentFear + 1;
|
||||
|
||||
if (value <= 6) {
|
||||
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: { action: GMUpdateEvent.UpdateFear, update: value }
|
||||
});
|
||||
await game.combat.update({ 'system.actions': game.combat.system.actions - 2 });
|
||||
}
|
||||
}
|
||||
|
||||
async tradeFear() {
|
||||
if (event.currentTarget.classList.contains('disabled')) return;
|
||||
|
||||
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
|
||||
const value = currentFear - 1;
|
||||
if (value >= 0) {
|
||||
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: { action: GMUpdateEvent.UpdateFear, update: value }
|
||||
});
|
||||
await game.combat.update({ 'system.actions': game.combat.system.actions + 2 });
|
||||
}
|
||||
}
|
||||
|
||||
async increaseResource(event) {
|
||||
if (event.currentTarget.dataset.type === 'action') {
|
||||
await game.combat.update({ 'system.actions': game.combat.system.actions + 1 });
|
||||
}
|
||||
|
||||
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
|
||||
const value = currentFear + 1;
|
||||
if (event.currentTarget.dataset.type === 'fear' && value <= 6) {
|
||||
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: { action: GMUpdateEvent.UpdateFear, update: value }
|
||||
});
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
async decreaseResource(event) {
|
||||
if (event.currentTarget.dataset.type === 'action' && game.combat.system.actions - 1 >= 0) {
|
||||
await game.combat.update({ 'system.actions': game.combat.system.actions - 1 });
|
||||
}
|
||||
|
||||
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
|
||||
const value = currentFear - 1;
|
||||
if (event.currentTarget.dataset.type === 'fear' && value >= 0) {
|
||||
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: { action: GMUpdateEvent.UpdateFear, update: value }
|
||||
});
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
async getData(options = {}) {
|
||||
let context = await super.getData(options);
|
||||
|
||||
// Get the combat encounters possible for the viewed Scene
|
||||
const combat = this.viewed;
|
||||
const hasCombat = combat !== null;
|
||||
const combats = this.combats;
|
||||
const currentIdx = combats.findIndex(c => c === combat);
|
||||
const previousId = currentIdx > 0 ? combats[currentIdx - 1].id : null;
|
||||
const nextId = currentIdx < combats.length - 1 ? combats[currentIdx + 1].id : null;
|
||||
const settings = game.settings.get('core', Combat.CONFIG_SETTING);
|
||||
|
||||
// Prepare rendering data
|
||||
context = foundry.utils.mergeObject(context, {
|
||||
combats: combats,
|
||||
currentIndex: currentIdx + 1,
|
||||
combatCount: combats.length,
|
||||
hasCombat: hasCombat,
|
||||
combat,
|
||||
turns: [],
|
||||
previousId,
|
||||
nextId,
|
||||
started: this.started,
|
||||
control: false,
|
||||
settings,
|
||||
linked: combat?.scene !== null,
|
||||
labels: {}
|
||||
});
|
||||
context.labels.scope = game.i18n.localize(`COMBAT.${context.linked ? 'Linked' : 'Unlinked'}`);
|
||||
if (!hasCombat) return context;
|
||||
|
||||
// Format information about each combatant in the encounter
|
||||
let hasDecimals = false;
|
||||
const turns = [];
|
||||
for (let [i, combatant] of combat.turns.entries()) {
|
||||
if (!combatant.visible) continue;
|
||||
|
||||
// Prepare turn data
|
||||
const resource =
|
||||
combatant.permission >= CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER ? combatant.resource : null;
|
||||
const turn = {
|
||||
id: combatant.id,
|
||||
name: combatant.name,
|
||||
img: await this._getCombatantThumbnail(combatant),
|
||||
active: combatant.id === combat.system.activeCombatant,
|
||||
owner: combatant.isOwner,
|
||||
defeated: combatant.isDefeated,
|
||||
hidden: combatant.hidden,
|
||||
initiative: combatant.initiative,
|
||||
hasRolled: combatant.initiative !== null,
|
||||
hasResource: resource !== null,
|
||||
resource: resource,
|
||||
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
|
||||
playerCharacter: game.user?.character?.id === combatant.actor.id,
|
||||
ownedByPlayer: combatant.hasPlayerOwner
|
||||
};
|
||||
if (turn.initiative !== null && !Number.isInteger(turn.initiative)) hasDecimals = true;
|
||||
turn.css = [turn.active ? 'active' : '', turn.hidden ? 'hidden' : '', turn.defeated ? 'defeated' : '']
|
||||
.join(' ')
|
||||
.trim();
|
||||
|
||||
// Actor and Token status effects
|
||||
turn.effects = new Set();
|
||||
if (combatant.token) {
|
||||
combatant.token.effects.forEach(e => turn.effects.add(e));
|
||||
if (combatant.token.overlayEffect) turn.effects.add(combatant.token.overlayEffect);
|
||||
}
|
||||
if (combatant.actor) {
|
||||
for (const effect of combatant.actor.temporaryEffects) {
|
||||
if (effect.statuses.has(CONFIG.specialStatusEffects.DEFEATED)) turn.defeated = true;
|
||||
else if (effect.icon) turn.effects.add(effect.icon);
|
||||
}
|
||||
}
|
||||
turns.push(turn);
|
||||
}
|
||||
|
||||
// Format initiative numeric precision
|
||||
const precision = CONFIG.Combat.initiative.decimals;
|
||||
turns.forEach(t => {
|
||||
if (t.initiative !== null) t.initiative = t.initiative.toFixed(hasDecimals ? precision : 0);
|
||||
});
|
||||
|
||||
const fear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
|
||||
|
||||
// Merge update data for rendering
|
||||
return foundry.utils.mergeObject(context, {
|
||||
round: combat.round,
|
||||
turn: combat.turn,
|
||||
turns: turns,
|
||||
control: combat.combatant?.players?.includes(game.user),
|
||||
fear: fear
|
||||
});
|
||||
}
|
||||
|
||||
onFearUpdate = async () => {
|
||||
this.render(true);
|
||||
};
|
||||
|
||||
async close(options) {
|
||||
Hooks.off(socketEvent.DhpFearUpdate, this.onFearUpdate);
|
||||
|
||||
return super.close(options);
|
||||
}
|
||||
}
|
||||
|
|
@ -1293,59 +1293,77 @@
|
|||
.daggerheart.sheet.pc div[data-application-part] .sheet-body .inventory-container .inventory-item-list .inventory-row img {
|
||||
width: 32px;
|
||||
}
|
||||
.combat-sidebar .encounter-gm-resources {
|
||||
flex: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 8px 0;
|
||||
.combat-sidebar .encounter-controls.combat {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.combat-sidebar .encounter-gm-resources .gm-resource-controls {
|
||||
.combat-sidebar .encounter-controls.combat .encounter-control-fear-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
justify-content: center;
|
||||
color: black;
|
||||
}
|
||||
.combat-sidebar .encounter-gm-resources .gm-resource-tools {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 0 5px 0 4px;
|
||||
.combat-sidebar .encounter-controls.combat .encounter-control-fear-container .dice {
|
||||
height: 24px;
|
||||
}
|
||||
.combat-sidebar .encounter-gm-resources .gm-resource-tools i {
|
||||
margin: 0 2px;
|
||||
.combat-sidebar .encounter-controls.combat .encounter-control-fear-container .encounter-control-fear {
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
}
|
||||
.combat-sidebar .encounter-gm-resources .gm-resource-tools i.disabled {
|
||||
opacity: 0.6;
|
||||
.combat-sidebar .encounter-controls.combat .encounter-control-fear-container .encounter-control-counter {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.combat-sidebar .encounter-gm-resources .gm-resource-tools i:hover:not(.disabled) {
|
||||
cursor: pointer;
|
||||
filter: drop-shadow(0 0 3px red);
|
||||
.combat-sidebar .encounter-controls.combat .control-buttons {
|
||||
width: min-content;
|
||||
}
|
||||
.combat-sidebar .encounter-gm-resources .gm-resource {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 4px;
|
||||
.combat-sidebar .token-actions {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
.combat-sidebar .token-actions .action-tokens {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.combat-sidebar .token-actions .action-tokens .action-token {
|
||||
height: 24px;
|
||||
border: 1px solid;
|
||||
border-radius: 8px;
|
||||
border: 2px solid black;
|
||||
font-size: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
padding: 8px;
|
||||
--button-size: 0;
|
||||
}
|
||||
.combat-sidebar .token-action-tokens {
|
||||
flex: 0 0 48px;
|
||||
text-align: center;
|
||||
.combat-sidebar .token-actions .action-tokens .action-token.used {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.combat-sidebar .token-action-tokens .use-action-token.disabled {
|
||||
opacity: 0.6;
|
||||
.combat-sidebar .token-actions button {
|
||||
font-size: 22px;
|
||||
}
|
||||
.combat-sidebar .icon-button.spaced {
|
||||
margin-left: 4px;
|
||||
.combat-sidebar .token-actions button.main {
|
||||
background: var(--button-hover-background-color);
|
||||
color: var(--button-hover-text-color);
|
||||
border-color: var(--button-hover-border-color);
|
||||
}
|
||||
.combat-sidebar .icon-button.disabled {
|
||||
opacity: 0.6;
|
||||
.combat-sidebar .token-actions button.main:hover {
|
||||
filter: drop-shadow(0 0 3px var(--button-hover-text-color));
|
||||
}
|
||||
.combat-sidebar .icon-button:hover:not(.disabled) {
|
||||
cursor: pointer;
|
||||
filter: drop-shadow(0 0 3px red);
|
||||
.combat-sidebar .token-actions button.discrete:hover {
|
||||
background: inherit;
|
||||
}
|
||||
.combat-sidebar .token-actions .combatant-control:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.combat-sidebar .token-actions .combatant-control.requesting {
|
||||
filter: drop-shadow(0 0 3px gold);
|
||||
color: var(--button-hover-text-color);
|
||||
}
|
||||
.chat-message.duality {
|
||||
border-color: black;
|
||||
|
|
@ -2722,11 +2740,18 @@ div.daggerheart.views.multiclass {
|
|||
--primary-color-fear: rgba(9, 71, 179, 0.75);
|
||||
--secondary-color-fear: rgba(9, 71, 179, 0.75);
|
||||
--shadow-text-stroke: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
|
||||
--fear-animation: background 0.3s ease, box-shadow .3s ease, border-color .3s ease, opacity .3s ease;
|
||||
}
|
||||
#resources {
|
||||
min-height: calc(var(--header-height) + 4rem);
|
||||
min-width: 4rem;
|
||||
color: #d3d3d3;
|
||||
transition: var(--fear-animation);
|
||||
}
|
||||
#resources header,
|
||||
#resources .controls,
|
||||
#resources .window-resize-handle {
|
||||
transition: var(--fear-animation);
|
||||
}
|
||||
#resources .window-content {
|
||||
padding: 0.5rem;
|
||||
|
|
@ -2822,7 +2847,7 @@ div.daggerheart.views.multiclass {
|
|||
#resources:not(:hover):not(.minimized) header,
|
||||
#resources:not(:hover):not(.minimized) .controls,
|
||||
#resources:not(:hover):not(.minimized) .window-resize-handle {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
#resources:has(.fear-bar) {
|
||||
min-width: 200px;
|
||||
|
|
@ -2903,7 +2928,7 @@ div.daggerheart.views.multiclass {
|
|||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/cinzeldecorative/v17/daaHSScvJGqLYhG8nNt8KPPswUAPniZoaelD.ttf) format('truetype');
|
||||
src: url(https://fonts.gstatic.com/s/cinzeldecorative/v18/daaHSScvJGqLYhG8nNt8KPPswUAPniZoaelD.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
|
|
@ -3186,6 +3211,19 @@ div.daggerheart.views.multiclass {
|
|||
.application.setting.dh-style footer button {
|
||||
flex: 1;
|
||||
}
|
||||
.application.setting.dh-style .form-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.application.setting.dh-style .form-group label {
|
||||
font-size: 16px;
|
||||
}
|
||||
.application.setting.dh-style .form-group .form-fields {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
.system-daggerheart .tagify {
|
||||
background: light-dark(transparent, transparent);
|
||||
border: 1px solid light-dark(#222, #efe6d8);
|
||||
|
|
|
|||
|
|
@ -212,6 +212,22 @@
|
|||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
label {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.form-fields {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.system-daggerheart {
|
||||
|
|
|
|||
185
styles/ui.less
185
styles/ui.less
|
|
@ -1,71 +1,160 @@
|
|||
.combat-sidebar {
|
||||
.encounter-gm-resources {
|
||||
flex: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: @largePadding 0;
|
||||
// .encounter-gm-resources {
|
||||
// flex: 0;
|
||||
// display: flex;
|
||||
// justify-content: center;
|
||||
// padding: @largePadding 0;
|
||||
|
||||
.gm-resource-controls {
|
||||
// .gm-resource-controls {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// align-items: center;
|
||||
// padding: 0 4px;
|
||||
// justify-content: center;
|
||||
// }
|
||||
|
||||
// .gm-resource-tools {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// justify-content: center;
|
||||
// padding: 0 5px 0 @fullPadding;
|
||||
|
||||
// i {
|
||||
// margin: 0 @tinyMargin;
|
||||
// font-size: 16px;
|
||||
|
||||
// &.disabled {
|
||||
// opacity: 0.6;
|
||||
// }
|
||||
|
||||
// &:hover:not(.disabled) {
|
||||
// cursor: pointer;
|
||||
// filter: drop-shadow(0 0 3px @mainShadow);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// .gm-resource {
|
||||
// background: rgba(255, 255, 255, 0.1);
|
||||
// padding: @fullPadding;
|
||||
// border-radius: 8px;
|
||||
// border: @normalBorder solid black;
|
||||
// font-size: 20px;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .token-action-tokens {
|
||||
// flex: 0 0 48px;
|
||||
// text-align: center;
|
||||
|
||||
// .use-action-token {
|
||||
// &.disabled {
|
||||
// opacity: 0.6;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// .icon-button {
|
||||
// &.spaced {
|
||||
// margin-left: @halfMargin;
|
||||
// }
|
||||
|
||||
// &.disabled {
|
||||
// opacity: 0.6;
|
||||
// }
|
||||
|
||||
// &:hover:not(.disabled) {
|
||||
// cursor: pointer;
|
||||
// filter: drop-shadow(0 0 3px @mainShadow);
|
||||
// }
|
||||
// }
|
||||
.encounter-controls.combat {
|
||||
justify-content: space-between;
|
||||
|
||||
.encounter-control-fear-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
justify-content: center;
|
||||
}
|
||||
color: black;
|
||||
|
||||
.gm-resource-tools {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 0 5px 0 @fullPadding;
|
||||
.dice {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
i {
|
||||
margin: 0 @tinyMargin;
|
||||
.encounter-control-fear {
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.encounter-control-counter {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:not(.disabled) {
|
||||
cursor: pointer;
|
||||
filter: drop-shadow(0 0 3px @mainShadow);
|
||||
.control-buttons {
|
||||
width: min-content;
|
||||
}
|
||||
}
|
||||
|
||||
.token-actions {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
|
||||
.action-tokens {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
.action-token {
|
||||
height: 24px;
|
||||
border: 1px solid;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
padding: 8px;
|
||||
--button-size: 0;
|
||||
|
||||
&.used {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gm-resource {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: @fullPadding;
|
||||
border-radius: 8px;
|
||||
border: @normalBorder solid black;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
button {
|
||||
font-size: 22px;
|
||||
|
||||
.token-action-tokens {
|
||||
flex: 0 0 48px;
|
||||
text-align: center;
|
||||
&.main {
|
||||
background: var(--button-hover-background-color);
|
||||
color: var(--button-hover-text-color);
|
||||
border-color: var(--button-hover-border-color);
|
||||
|
||||
.use-action-token {
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
&:hover {
|
||||
filter: drop-shadow(0 0 3px var(--button-hover-text-color));
|
||||
}
|
||||
}
|
||||
|
||||
&.discrete:hover {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
&.spaced {
|
||||
margin-left: @halfMargin;
|
||||
}
|
||||
.combatant-control {
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:hover:not(.disabled) {
|
||||
cursor: pointer;
|
||||
filter: drop-shadow(0 0 3px @mainShadow);
|
||||
&.requesting {
|
||||
filter: drop-shadow(0 0 3px gold);
|
||||
color: var(--button-hover-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
templates/settings/variant-rules.hbs
Normal file
22
templates/settings/variant-rules.hbs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<div>
|
||||
<div class="form-group">
|
||||
<label>{{localize "DAGGERHEART.Settings.Menu.VariantRules.actionTokens"}}</label>
|
||||
|
||||
<div class="form-fields">
|
||||
{{formInput settingFields.schema.fields.actionTokens.fields.enabled value=settingFields._source.actionTokens.enabled}}
|
||||
{{formInput settingFields.schema.fields.actionTokens.fields.tokens value=settingFields._source.actionTokens.tokens disabled=(not settingFields._source.actionTokens.enabled)}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="form-footer">
|
||||
<button data-action="reset">
|
||||
<i class="fa-solid fa-arrow-rotate-left"></i>
|
||||
<span>{{localize "Reset"}}</span>
|
||||
</button>
|
||||
<button data-action="save" >
|
||||
<i class="fa-solid fa-floppy-disk"></i>
|
||||
<span>{{localize "Save Changes"}}</span>
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
72
templates/ui/combat/combatTracker.hbs
Normal file
72
templates/ui/combat/combatTracker.hbs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<ol class="combat-tracker plain">
|
||||
{{#each turns}}
|
||||
<li class="combatant {{ css }}" data-combatant-id="{{ id }}" data-action="activateCombatant">
|
||||
{{!-- Image --}}
|
||||
<img class="token-image" src="{{ img }}" alt="{{ name }}" loading="lazy">
|
||||
|
||||
{{!-- Name & Controls --}}
|
||||
<div class="token-name">
|
||||
<strong class="name">{{ name }}</strong>
|
||||
<div class="combatant-controls">
|
||||
{{#if @root.user.isGM}}
|
||||
<button type="button" class="inline-control combatant-control icon fa-solid fa-eye-slash {{#if hidden}}active{{/if}}"
|
||||
data-action="toggleHidden" data-tooltip aria-label="{{ localize "COMBAT.ToggleVis" }}"></button>
|
||||
<button type="button" class="inline-control combatant-control icon fa-solid fa-skull {{#if isDefeated}}active{{/if}}"
|
||||
data-action="toggleDefeated" data-tooltip
|
||||
aria-label="{{ localize "COMBAT.ToggleDead" }}"></button>
|
||||
{{/if}}
|
||||
{{#if canPing}}
|
||||
<button type="button" class="inline-control combatant-control icon fa-solid fa-bullseye-arrow"
|
||||
data-action="pingCombatant" data-tooltip
|
||||
aria-label="{{ localize "COMBAT.PingCombatant" }}"></button>
|
||||
{{/if}}
|
||||
{{#unless @root.user.isGM}}
|
||||
<button type="button" class="inline-control combatant-control icon fa-solid fa-arrows-to-eye"
|
||||
data-action="panToCombatant" data-tooltip
|
||||
aria-label="{{ localize "COMBAT.PanToCombatant" }}"></button>
|
||||
{{/unless}}
|
||||
{{!-- TODO: Target Control --}}
|
||||
<div class="token-effects" data-tooltip-html="{{ effects.tooltip }}">
|
||||
{{#each effects.icons}}
|
||||
<img class="token-effect" src="{{ img }}" alt="{{ name }}">
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Resource --}}
|
||||
{{#if resource includeZero=true}}
|
||||
<div class="token-resource">
|
||||
<span class="resource">{{ resource }}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if ../combat.system.started}}
|
||||
<div class="token-actions">
|
||||
{{#if isOwner}}
|
||||
{{#if (and (not isNPC) ../actionTokens.enabled)}}
|
||||
<div class="action-tokens">
|
||||
{{#times ../actionTokens.tokens}}
|
||||
<button type="button" class="inline-control main icon fa-solid fa-bolt-lightning action-token {{#if (lte ../system/actionTokens this)}}used{{/if}}" data-action="setActionTokens" data-combatant-id="{{../id}}" data-token-index="{{this}}">
|
||||
</button>
|
||||
{{/times}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if @root.user.isGM}}
|
||||
<button
|
||||
type="button" class="inline-control combatant-control discrete icon fa-solid {{#if system.spotlight.requesting}}fa-hand-sparkles requesting{{else}}fa-hand{{/if}}"
|
||||
data-action="toggleSpotlight" data-combatant-id="{{id}}" data-tooltip aria-label="{{localize "DAGGERHEART.Combat.giveSpotlight"}}"
|
||||
></button>
|
||||
{{else}}
|
||||
<button
|
||||
type="button" class="inline-control combatant-control discrete icon fa-solid {{#if system.spotlight.requesting}}fa-hand-sparkles requesting{{else}}fa-hand{{/if}}"
|
||||
data-action="requestSpotlight" data-combatant-id="{{id}}" data-tooltip aria-label="{{#if system.spotlight.requesting}}{{localize "DAGGERHEART.Combat.requestingSpotlight"}}{{else}}{{localize "DAGGERHEART.Combat.requestSpotlight"}}{{/if}}"
|
||||
></button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
17
templates/ui/combat/combatTrackerFooter.hbs
Normal file
17
templates/ui/combat/combatTrackerFooter.hbs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<nav class="combat-controls" data-tooltip-direction="UP">
|
||||
{{~#if hasCombat~}}
|
||||
{{#if user.isGM}}
|
||||
{{#if combat.system.started}}
|
||||
<button type="button" class="combat-control combat-control-lg" data-action="endCombat">
|
||||
<i class="fa-solid fa-xmark" inert></i>
|
||||
<span>{{ localize "COMBAT.End" }}</span>
|
||||
</button>
|
||||
{{else}}
|
||||
<button type="button" class="combat-control combat-control-lg" data-action="startCombat">
|
||||
<i class="fa-solid fa-swords" inert></i>
|
||||
<span>{{ localize "COMBAT.Begin" }}</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</nav>
|
||||
86
templates/ui/combat/combatTrackerHeader.hbs
Normal file
86
templates/ui/combat/combatTrackerHeader.hbs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<header class="combat-tracker-header">
|
||||
|
||||
{{!-- Encounter Controls --}}
|
||||
{{#if user.isGM}}
|
||||
<nav class="encounters {{ css }}" aria-label="{{ localize "COMBAT.NavLabel" }}">
|
||||
|
||||
{{!-- Cycle Display --}}
|
||||
{{#if displayCycle}}
|
||||
<button type="button" class="inline-control icon fa-solid fa-plus" data-action="createCombat"
|
||||
data-tooltip aria-label="{{ localize "COMBAT.Create" }}"></button>
|
||||
|
||||
<div class="cycle-combats">
|
||||
<button type="button" class="inline-control icon fa-solid fa-caret-left" data-action="cycleCombat"
|
||||
{{#if previousId}}data-combat-id="{{ previousId }}" {{else}}disabled{{/if}}
|
||||
data-tooltip aria-label="{{ localize "COMBAT.EncounterPrevious" }}"></button>
|
||||
<div class="encounter-count">
|
||||
<span class="value">{{ currentIndex }}</span>
|
||||
<span class="separator">/</span>
|
||||
<span class="max">{{ combats.length }}</span>
|
||||
</div>
|
||||
<button type="button" class="inline-control icon fa-solid fa-caret-right" data-action="cycleCombat"
|
||||
{{#if nextId}}data-combat-id="{{ nextId }}" {{else}}disabled{{/if}}
|
||||
data-tooltip aria-label="{{ localize "COMBAT.EncounterNext" }}"></button>
|
||||
</div>
|
||||
|
||||
<button type="button" class="inline-control icon fa-solid fa-gear" data-action="trackerSettings"
|
||||
data-tooltip aria-label="{{ localize "COMBAT.Settings" }}"></button>
|
||||
|
||||
{{!-- Tabbed Display --}}
|
||||
{{else if combats.length}}
|
||||
<button type="button" class="inline-control icon fa-solid fa-plus" data-action="createCombat"
|
||||
data-tooltip aria-label="{{ localize "COMBAT.Create" }}"></button>
|
||||
{{#each combats}}
|
||||
<button type="button" class="inline-control {{#if active}}active{{/if}}" data-action="cycleCombat"
|
||||
data-combat-id="{{ id }}">
|
||||
{{ label }}
|
||||
</button>
|
||||
{{/each}}
|
||||
<button type="button" class="inline-control icon fa-solid fa-gear" data-action="trackerSettings"
|
||||
data-tooltip aria-label="{{ localize "COMBAT.Settings" }}"></button>
|
||||
|
||||
{{!-- No Combats --}}
|
||||
{{else}}
|
||||
<button type="button" class="combat-control-lg" data-action="createCombat">
|
||||
<i class="fa-solid fa-plus" inert></i>
|
||||
<span>{{ localize "COMBAT.Create" }}</span>
|
||||
</button>
|
||||
|
||||
{{/if}}
|
||||
</nav>
|
||||
{{/if}}
|
||||
|
||||
<div class="encounter-controls {{#if hasCombat}}combat{{/if}}">
|
||||
{{#if hasCombat}}
|
||||
<div class="encounter-control-fear-container">
|
||||
<img class="dice " src="../icons/svg/d12-grey.svg"/>
|
||||
<i class="fas fa-skull encounter-control-fear"></i>
|
||||
<div class="encounter-control-counter">{{fear}}</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{!-- Combat Status --}}
|
||||
<strong class="encounter-title">
|
||||
{{#if combats.length}}
|
||||
{{#if combat.system.started}}
|
||||
{{ localize "DAGGERHEART.Combat.combatStarted" }}
|
||||
{{else}}
|
||||
{{ localize "COMBAT.NotStarted" }}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{ localize "COMBAT.None" }}
|
||||
{{/if}}
|
||||
</strong>
|
||||
|
||||
{{!-- Combat Controls --}}
|
||||
{{#if hasCombat}}
|
||||
<div class="control-buttons right flexrow">
|
||||
<div class="spacer"></div>
|
||||
<button type="button" class="encounter-context-menu inline-control combat-control icon fa-solid fa-ellipsis-vertical"
|
||||
{{#unless (and user.isGM hasCombat)}}disabled{{/unless}}></button>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
|
||||
</header>
|
||||
Loading…
Add table
Add a link
Reference in a new issue