mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-03-07 22:46:12 +01:00
393 lines
16 KiB
JavaScript
393 lines
16 KiB
JavaScript
import { getCommandTarget } from '../../helpers/utils.mjs';
|
|
|
|
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
|
constructor() {
|
|
super();
|
|
|
|
this.targetTemplate = {
|
|
activeLayer: undefined,
|
|
document: undefined,
|
|
object: undefined,
|
|
minimizedSheets: [],
|
|
config: undefined,
|
|
targets: undefined
|
|
};
|
|
this.setupHooks();
|
|
}
|
|
|
|
addChatListeners = async (app, html, data) => {
|
|
html.querySelectorAll('.duality-action-damage').forEach(element =>
|
|
element.addEventListener('click', event => this.onRollDamage(event, data.message))
|
|
);
|
|
html.querySelectorAll('.duality-action-healing').forEach(element =>
|
|
element.addEventListener('click', event => this.onRollHealing(event, data.message))
|
|
);
|
|
html.querySelectorAll('.target-save-container').forEach(element =>
|
|
element.addEventListener('click', event => this.onRollSave(event, data.message))
|
|
);
|
|
html.querySelectorAll('.roll-all-save-button').forEach(element =>
|
|
element.addEventListener('click', event => this.onRollAllSave(event, data.message))
|
|
);
|
|
html.querySelectorAll('.duality-action-effect').forEach(element =>
|
|
element.addEventListener('click', event => this.onApplyEffect(event, data.message))
|
|
);
|
|
html.querySelectorAll('.target-container').forEach(element => {
|
|
element.addEventListener('mouseenter', this.hoverTarget);
|
|
element.addEventListener('mouseleave', this.unhoverTarget);
|
|
element.addEventListener('click', this.clickTarget);
|
|
});
|
|
html.querySelectorAll('.button-target-selection').forEach(element => {
|
|
element.addEventListener('click', event => this.onTargetSelection(event, data.message));
|
|
});
|
|
html.querySelectorAll('.damage-button').forEach(element =>
|
|
element.addEventListener('click', event => this.onDamage(event, data.message))
|
|
);
|
|
html.querySelectorAll('.healing-button').forEach(element =>
|
|
element.addEventListener('click', event => this.onHealing(event, data.message))
|
|
);
|
|
html.querySelectorAll('.target-indicator').forEach(element =>
|
|
element.addEventListener('click', this.onToggleTargets)
|
|
);
|
|
html.querySelectorAll('.advantage').forEach(element =>
|
|
element.addEventListener('mouseenter', this.hoverAdvantage)
|
|
);
|
|
html.querySelectorAll('.advantage').forEach(element =>
|
|
element.addEventListener('click', event => this.selectAdvantage.call(this, event, data.message))
|
|
);
|
|
html.querySelectorAll('.ability-use-button').forEach(element =>
|
|
element.addEventListener('click', event => this.abilityUseButton.call(this, event, data.message))
|
|
);
|
|
html.querySelectorAll('.action-use-button').forEach(element =>
|
|
element.addEventListener('click', event => this.actionUseButton.call(this, event, data.message))
|
|
);
|
|
};
|
|
|
|
setupHooks() {
|
|
Hooks.on('renderChatMessageHTML', this.addChatListeners.bind());
|
|
}
|
|
|
|
close(options) {
|
|
Hooks.off('renderChatMessageHTML', this.addChatListeners);
|
|
super.close(options);
|
|
}
|
|
|
|
async getActor(id) {
|
|
// return game.actors.get(id);
|
|
return await fromUuid(id);
|
|
}
|
|
|
|
getAction(actor, itemId, actionId) {
|
|
const item = actor.items.get(itemId),
|
|
action =
|
|
actor.system.attack?._id === actionId
|
|
? actor.system.attack
|
|
: item?.system?.actions?.find(a => a._id === actionId);
|
|
return action;
|
|
}
|
|
|
|
onRollDamage = async (event, message) => {
|
|
event.stopPropagation();
|
|
const actor = await this.getActor(message.system.source.actor);
|
|
if (!actor || !game.user.isGM) return true;
|
|
if (message.system.source.item && message.system.source.action) {
|
|
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
|
if (!action || !action?.rollDamage) return;
|
|
await action.rollDamage(event, message);
|
|
}
|
|
};
|
|
|
|
onRollHealing = async (event, message) => {
|
|
event.stopPropagation();
|
|
const actor = await this.getActor(message.system.source.actor);
|
|
if (!actor || !game.user.isGM) return true;
|
|
if (message.system.source.item && message.system.source.action) {
|
|
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
|
if (!action || !action?.rollHealing) return;
|
|
await action.rollHealing(event, message);
|
|
}
|
|
};
|
|
|
|
onRollSave = async (event, message) => {
|
|
event.stopPropagation();
|
|
const actor = await this.getActor(message.system.source.actor),
|
|
tokenId = event.target.closest('[data-token]')?.dataset.token,
|
|
token = game.canvas.tokens.get(tokenId);
|
|
if (!token?.actor || !token.isOwner) return true;
|
|
if (message.system.source.item && message.system.source.action) {
|
|
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
|
if (!action || !action?.hasSave) return;
|
|
action.rollSave(token, event, message);
|
|
}
|
|
};
|
|
|
|
onRollAllSave = async (event, message) => {
|
|
event.stopPropagation();
|
|
const targets = event.target.parentElement.querySelectorAll(
|
|
'.target-section > [data-token] .target-save-container'
|
|
);
|
|
targets.forEach(el => {
|
|
el.dispatchEvent(new PointerEvent('click', { shiftKey: true }));
|
|
});
|
|
};
|
|
|
|
onApplyEffect = async (event, message) => {
|
|
event.stopPropagation();
|
|
const actor = await this.getActor(message.system.source.actor);
|
|
if (!actor || !game.user.isGM) return true;
|
|
if (message.system.source.item && message.system.source.action) {
|
|
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
|
if (!action || !action?.applyEffects) return;
|
|
const { isHit, targets } = this.getTargetList(event, message);
|
|
if (targets.length === 0)
|
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
|
await action.applyEffects(event, message, targets);
|
|
}
|
|
};
|
|
|
|
onTargetSelection = async (event, message) => {
|
|
event.stopPropagation();
|
|
const targetSelection = Boolean(event.target.dataset.targetHit),
|
|
msg = ui.chat.collection.get(message._id);
|
|
if (msg.system.targetSelection === targetSelection) return;
|
|
if (targetSelection !== true && !Array.from(game.user.targets).length)
|
|
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
|
msg.system.targetSelection = targetSelection;
|
|
msg.system.prepareDerivedData();
|
|
ui.chat.updateMessage(msg);
|
|
};
|
|
|
|
getTargetList = (event, message) => {
|
|
const targetSelection = event.target
|
|
.closest('.message-content')
|
|
.querySelector('.button-target-selection.target-selected'),
|
|
isHit = Boolean(targetSelection.dataset.targetHit);
|
|
return {
|
|
isHit,
|
|
targets: isHit
|
|
? message.system.targets.filter(t => t.hit === true).map(target => game.canvas.tokens.get(target.id))
|
|
: Array.from(game.user.targets)
|
|
};
|
|
};
|
|
|
|
hoverTarget = event => {
|
|
event.stopPropagation();
|
|
const token = canvas.tokens.get(event.currentTarget.dataset.token);
|
|
if (!token?.controlled) token._onHoverIn(event, { hoverOutOthers: true });
|
|
};
|
|
|
|
unhoverTarget = event => {
|
|
const token = canvas.tokens.get(event.currentTarget.dataset.token);
|
|
if (!token?.controlled) token._onHoverOut(event);
|
|
};
|
|
|
|
clickTarget = event => {
|
|
event.stopPropagation();
|
|
const token = canvas.tokens.get(event.currentTarget.dataset.token);
|
|
if (!token) {
|
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist'));
|
|
return;
|
|
}
|
|
|
|
game.canvas.pan(token);
|
|
};
|
|
|
|
onDamage = async (event, message) => {
|
|
event.stopPropagation();
|
|
const { isHit, targets } = this.getTargetList(event, message);
|
|
|
|
if (message.system.onSave && isHit) {
|
|
const pendingingSaves = message.system.targets.filter(
|
|
target => target.hit && target.saved.success === null
|
|
);
|
|
if (pendingingSaves.length) {
|
|
const confirm = await foundry.applications.api.DialogV2.confirm({
|
|
window: { title: 'Pending Reaction Rolls found' },
|
|
content: `<p>Some Tokens still need to roll their Reaction Roll.</p><p>Are you sure you want to continue ?</p><p><i>Undone reaction rolls will be considered as failed</i></p>`
|
|
});
|
|
if (!confirm) return;
|
|
}
|
|
}
|
|
|
|
if (targets.length === 0)
|
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
|
for (let target of targets) {
|
|
let damage = message.system.roll.total;
|
|
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true)
|
|
damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
|
|
|
|
await target.actor.takeDamage(damage, message.system.roll.type);
|
|
}
|
|
};
|
|
|
|
onHealing = async (event, message) => {
|
|
event.stopPropagation();
|
|
const targets = Array.from(game.user.targets);
|
|
|
|
if (targets.length === 0)
|
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
|
|
|
for (var target of targets) {
|
|
await target.actor.takeHealing([{ value: message.system.roll.total, type: message.system.roll.type }]);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Toggle visibility of target containers.
|
|
* @param {MouseEvent} event
|
|
*/
|
|
onToggleTargets(event) {
|
|
event.stopPropagation();
|
|
event.currentTarget.parentElement
|
|
?.querySelectorAll('.target-container')
|
|
.forEach(el => el.classList.toggle('hidden'));
|
|
}
|
|
|
|
/**
|
|
* Highlight advantage icons on hover.
|
|
* @param {MouseEvent} event
|
|
*/
|
|
hoverAdvantage(event) {
|
|
const parent = event.currentTarget.parentElement;
|
|
if (!parent) return;
|
|
|
|
parent.querySelectorAll('.advantage').forEach(el => {
|
|
if (el !== event.currentTarget) {
|
|
el.classList.toggle('unused');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle selecting an advantage and disable further selection.
|
|
* @param {MouseEvent} event
|
|
* @param {object} message
|
|
*/
|
|
async selectAdvantage(event, message) {
|
|
event.stopPropagation();
|
|
|
|
const updateMessage = game.messages.get(message._id);
|
|
await updateMessage?.update({
|
|
system: { advantageSelected: event.currentTarget.id === 'hope' ? 1 : 2 }
|
|
});
|
|
|
|
const parent = event.currentTarget.parentElement;
|
|
if (!parent) return;
|
|
|
|
parent.querySelectorAll('.advantage').forEach(el => {
|
|
el.replaceWith(el.cloneNode(true));
|
|
});
|
|
}
|
|
|
|
abilityUseButton = async (event, message) => {
|
|
event.stopPropagation();
|
|
|
|
// Get the action data from the chat message
|
|
const actionData = message.system.actions[Number.parseInt(event.currentTarget.dataset.index)];
|
|
// Get the currently selected actor (from selected token)
|
|
const actor = getCommandTarget();
|
|
if (!actor) {
|
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noSelectedToken'));
|
|
return;
|
|
}
|
|
|
|
// Try to find the original action from the source item first
|
|
const sourceActor = message.system.source?.actor ? await foundry.utils.fromUuid(message.system.source.actor) : null;
|
|
const sourceItem = message.system.source?.item ? sourceActor?.items?.get(message.system.source.item) : null;
|
|
|
|
if (sourceItem && sourceActor) {
|
|
// Find the specific action on the source item
|
|
const sourceAction = sourceItem.system.actions?.find(a => a._id === actionData._id);
|
|
if (sourceAction) {
|
|
try {
|
|
// Temporarily override the action's actor reference to use the selected actor
|
|
const originalActor = sourceAction.actor;
|
|
const originalItem = sourceAction.item;
|
|
|
|
// Create temporary getters that return our selected actor
|
|
Object.defineProperty(sourceAction, 'actor', {
|
|
get: () => actor,
|
|
configurable: true
|
|
});
|
|
|
|
Object.defineProperty(sourceAction, 'item', {
|
|
get: () => ({
|
|
...originalItem,
|
|
actor: actor,
|
|
parent: actor
|
|
}),
|
|
configurable: true
|
|
});
|
|
|
|
// Use the action
|
|
await sourceAction.use(event);
|
|
|
|
// Restore the original references
|
|
Object.defineProperty(sourceAction, 'actor', {
|
|
get: () => originalActor,
|
|
configurable: true
|
|
});
|
|
|
|
Object.defineProperty(sourceAction, 'item', {
|
|
get: () => originalItem,
|
|
configurable: true
|
|
});
|
|
|
|
return;
|
|
} catch (e) {
|
|
console.error('Error using source action from chat:', e);
|
|
// Restore original references if there was an error
|
|
delete sourceAction.actor;
|
|
delete sourceAction.item;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback: Create action using an existing item from the selected actor
|
|
try {
|
|
// Find any weapon or item on the selected actor to use as a template
|
|
const actorItems = actor.items.filter(item => item.system.actions?.length > 0);
|
|
let templateItem = actorItems.find(item => item.system.actions.some(a => a.type === actionData.type));
|
|
|
|
if (!templateItem && actorItems.length > 0) {
|
|
templateItem = actorItems[0]; // Use any item with actions as a fallback
|
|
}
|
|
|
|
if (templateItem) {
|
|
// Use the template item's action structure but with our action data
|
|
const { actionsTypes } = await import('../../data/action/_module.mjs');
|
|
const ActionClass = actionsTypes[actionData.type];
|
|
|
|
if (ActionClass) {
|
|
// Create the action with the template item's system as parent
|
|
const actionInstance = new ActionClass({
|
|
...actionData,
|
|
_id: foundry.utils.randomID(),
|
|
name: actionData.name || game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${actionData.type}.name`)
|
|
}, { parent: templateItem.system });
|
|
|
|
await actionInstance.use(event);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Final fallback - just show a notification
|
|
ui.notifications.info(`Using ${actionData.name || actionData.type} action with selected token: ${actor.name}`);
|
|
|
|
} catch (e) {
|
|
console.error('Error using action from chat:', e);
|
|
ui.notifications.error(`Error using action: ${e.message}`);
|
|
}
|
|
};
|
|
|
|
actionUseButton = async (_, message) => {
|
|
const parent = await foundry.utils.fromUuid(message.system.actor);
|
|
const actionType = Object.values(message.system.moves)[0].actions[0];
|
|
const cls = CONFIG.DH.ACTIONS.actionTypes[actionType.type];
|
|
const action = new cls(
|
|
{ ...actionType, _id: foundry.utils.randomID(), name: game.i18n.localize(actionType.name) },
|
|
{ parent: parent }
|
|
);
|
|
|
|
action.use();
|
|
}
|
|
}
|