daggerheart/module/documents/actor.mjs
WBHarry b24cdcc9ed
Adding Prettier
* Added prettier with automatic useage on pre-commit to avoid style breakage
* Ran Prettier on the project
2025-05-23 18:57:50 +02:00

416 lines
15 KiB
JavaScript

import DamageSelectionDialog from '../applications/damageSelectionDialog.mjs';
import NpcRollSelectionDialog from '../applications/npcRollSelectionDialog.mjs';
import RollSelectionDialog from '../applications/rollSelectionDialog.mjs';
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
export default class DhpActor extends Actor {
_preCreate(data, changes, user) {
if (data.type === 'pc') {
data.prototypeToken = { actorLink: true, disposition: 1, sight: { enabled: true } };
}
super._preCreate(data, changes, user);
}
prepareData() {
super.prepareData();
}
async _preUpdate(changed, options, user) {
//Level Down
if (
changed.system?.levelData?.changedLevel &&
this.system.levelData.currentLevel > changed.system.levelData.changedLevel
) {
changed.system.levelData.currentLevel = changed.system.levelData.changedLevel;
changed.system.levelData.levelups = Object.keys(this.system.levelData.levelups).reduce((acc, x) => {
if (x > changed.system.levelData.currentLevel) {
acc[`-=${x}`] = null;
}
return acc;
}, {});
changed.system.attributes = Object.keys(this.system.attributes).reduce((acc, key) => {
acc[key] = {
levelMarks: this.system.attributes[key].levelMarks.filter(
x => x <= changed.system.levelData.currentLevel
)
};
return acc;
}, {});
changed.system.experiences = this.system.experiences.filter(
x => x.level <= changed.system.levelData.currentLevel
);
if (
this.system.multiclass &&
this.system.multiclass.system.multiclass > changed.system.levelData.changedLevel
) {
const multiclassFeatures = this.items.filter(x => x.system.multiclass);
for (var feature of multiclassFeatures) {
await feature.delete();
}
}
}
super._preUpdate(changed, options, user);
}
async diceRoll(modifier, shiftKey) {
if (this.type === 'pc') {
return await this.dualityRoll(modifier, shiftKey);
} else {
return await this.npcRoll(modifier, shiftKey);
}
}
async npcRoll(modifier, shiftKey) {
let nrDice = 1;
let advantage = null;
const modifiers = [
{
value: Number.parseInt(modifier.value),
label: modifier.value >= 0 ? `+${modifier.value}` : `-${modifier.value}`,
title: modifier.title
}
];
if (!shiftKey) {
const dialogClosed = new Promise((resolve, _) => {
new NpcRollSelectionDialog(this.system.experiences, resolve).render(true);
});
const result = await dialogClosed;
nrDice = result.nrDice;
advantage = result.advantage;
result.experiences.forEach(x =>
modifiers.push({
value: x.value,
label: x.value >= 0 ? `+${x.value}` : `-${x.value}`,
title: x.description
})
);
}
const roll = new Roll(
`${nrDice}d20${advantage === true ? 'kh' : advantage === false ? 'kl' : ''} ${modifiers.map(x => `+ ${x.value}`).join(' ')}`
);
let rollResult = await roll.evaluate();
const diceResults = rollResult.dice.flatMap(x => x.results.flatMap(result => ({ value: result.result })));
return { roll, diceResults: diceResults, modifiers: modifiers };
}
async dualityRoll(modifier, shiftKey, bonusDamage = []) {
let hopeDice = 'd12',
fearDice = 'd12',
advantageDice = null,
disadvantageDice = null,
bonusDamageString = '';
const modifiers = [
{
value: modifier.value ? Number.parseInt(modifier.value) : 0,
label: modifier.value >= 0 ? `+${modifier.value}` : `-${modifier.value}`,
title: modifier.title
}
];
if (!shiftKey) {
const dialogClosed = new Promise((resolve, _) => {
new RollSelectionDialog(
this.system.experiences,
bonusDamage,
this.system.resources.hope.value,
resolve
).render(true);
});
const result = await dialogClosed;
(hopeDice = result.hope),
(fearDice = result.fear),
(advantageDice = result.advantage),
(disadvantageDice = result.disadvantage);
result.experiences.forEach(x =>
modifiers.push({
value: x.value,
label: x.value >= 0 ? `+${x.value}` : `-${x.value}`,
title: x.description
})
);
bonusDamageString = result.bonusDamage;
const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope);
if (automateHope && result.hopeUsed) {
await this.update({
'system.resources.hope.value': this.system.resources.hope.value - result.hopeUsed
});
}
}
const roll = new Roll(
`1${hopeDice} + 1${fearDice}${advantageDice ? ` + 1${advantageDice}` : disadvantageDice ? ` - 1${disadvantageDice}` : ''} ${modifiers.map(x => `+ ${x.value}`).join(' ')}`
);
let rollResult = await roll.evaluate();
rollResult.dice[0].options.appearance = {
colorset: 'inspired',
foreground: '#FFFFFF',
background: '#008080',
outline: '#000000',
edge: '#806400',
texture: 'bloodmoon',
material: 'metal',
font: 'Arial Black',
system: 'standard'
};
if (advantageDice || disadvantageDice) {
rollResult.dice[1].options.appearance = {
colorset: 'inspired',
foreground: disadvantageDice ? '#b30000' : '#FFFFFF',
background: '#008080',
outline: disadvantageDice ? '#000000' : '#000000',
edge: '#806400',
texture: 'bloodmoon',
material: 'metal',
font: 'Arial Black',
system: 'standard'
};
rollResult.dice[2].options.appearance = {
colorset: 'bloodmoon',
foreground: '#000000',
background: '#430070',
outline: '#b30000',
edge: '#000000',
texture: 'bloodmoon',
material: 'metal',
font: 'Arial Black',
system: 'standard'
};
} else {
rollResult.dice[1].options.appearance = {
colorset: 'bloodmoon',
foreground: '#000000',
background: '#430070',
outline: '#b30000',
edge: '#000000',
texture: 'bloodmoon',
material: 'metal',
font: 'Arial Black',
system: 'standard'
};
}
const hope = rollResult.dice[0].results[0].result;
const advantage = advantageDice ? rollResult.dice[1].results[0].result : null;
const disadvantage = disadvantageDice ? rollResult.dice[1].results[0].result : null;
const fear =
advantage || disadvantage ? rollResult.dice[2].results[0].result : rollResult.dice[1].results[0].result;
if (disadvantage) {
rollResult = { ...rollResult, total: rollResult.total - Math.max(hope, disadvantage) };
}
if (advantage) {
rollResult = { ...rollResult, total: 'Select Hope Die' };
}
const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope);
if (automateHope && hope > fear) {
await this.update({
'system.resources.hope.value': Math.min(
this.system.resources.hope.value + 1,
this.system.resources.hope.max
)
});
}
if (automateHope && hope === fear) {
await this.update({
'system.resources': {
'hope.value': Math.min(this.system.resources.hope.value + 1, this.system.resources.hope.max),
'stress.value': Math.max(this.system.resources.stress.value - 1, 0)
}
});
}
return {
roll,
rollResult,
hope: { dice: hopeDice, value: hope },
fear: { dice: fearDice, value: fear },
advantage: { dice: advantageDice, value: advantage },
disadvantage: { dice: disadvantageDice, value: disadvantage },
modifiers: modifiers,
bonusDamageString
};
}
async damageRoll(damage, shiftKey) {
let rollString = damage.value;
let bonusDamage = damage.bonusDamage?.filter(x => x.initiallySelected) ?? [];
if (!shiftKey) {
const dialogClosed = new Promise((resolve, _) => {
new DamageSelectionDialog(rollString, bonusDamage, this.system.resources.hope.value, resolve).render(
true
);
});
const result = await dialogClosed;
bonusDamage = result.bonusDamage;
rollString = result.rollString;
const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope);
if (automateHope && result.hopeUsed) {
await this.update({
'system.resources.hope.value': this.system.resources.hope.value - result.hopeUsed
});
}
}
const roll = new Roll(rollString);
let rollResult = await roll.evaluate();
const dice = [];
const modifiers = [];
for (var i = 0; i < rollResult.terms.length; i++) {
const term = rollResult.terms[i];
if (term.faces) {
dice.push({ type: `d${term.faces}`, value: term.total });
} else if (term.operator) {
} else if (term.number) {
const operator = i === 0 ? '' : rollResult.terms[i - 1].operator;
modifiers.push(`${operator}${term.number}`);
}
}
const cls = getDocumentClass('ChatMessage');
const msg = new cls({
user: game.user.id,
content: await renderTemplate('systems/daggerheart/templates/chat/damage-roll.hbs', {
roll: rollString,
total: rollResult.total,
dice: dice,
modifiers: modifiers
}),
rolls: [roll]
});
cls.create(msg.toObject());
}
async takeDamage(damage, type) {
const hpDamage =
damage >= this.system.damageThresholds.severe
? 3
: damage >= this.system.damageThresholds.major
? 2
: damage >= this.system.damageThresholds.minor
? 1
: 0;
const update = {
'system.resources.health.value': Math.min(
this.system.resources.health.value + hpDamage,
this.system.resources.health.max
)
};
if (game.user.isGM) {
await this.update(update);
} else {
await game.socket.emit(`system.${SYSTEM.id}`, {
action: socketEvent.GMUpdate,
data: {
action: GMUpdateEvent.UpdateDocument,
uuid: this.uuid,
update: update
}
});
}
}
async takeHealing(healing, type) {
let update = {};
switch (type) {
case SYSTEM.GENERAL.healingTypes.health.id:
update = { 'system.resources.health.value': Math.max(this.system.resources.health.value - healing, 0) };
break;
case SYSTEM.GENERAL.healingTypes.stress.id:
update = { 'system.resources.stress.value': Math.max(this.system.resources.stress.value - healing, 0) };
break;
}
if (game.user.isGM) {
await this.update(update);
} else {
await game.socket.emit(`system.${SYSTEM.id}`, {
action: socketEvent.GMUpdate,
data: {
action: GMUpdateEvent.UpdateDocument,
uuid: this.uuid,
update: update
}
});
}
}
async emulateItemDrop(data) {
const event = new DragEvent('drop', { altKey: game.keyboard.isModifierActive('Alt') });
return this.sheet._onDropItem(event, { data: data });
}
//Move to action-scope?
async useAction(action) {
const userTargets = Array.from(game.user.targets);
const otherTarget = action.target.type === SYSTEM.ACTIONS.targetTypes.other.id;
if (otherTarget && userTargets.length === 0) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.ActionRequiresTarget'));
return;
}
if (action.cost.type != null && action.cost.value != null) {
if (this.system.resources[action.cost.type].value < action.cost.value - 1) {
ui.notifications.error(game.i18n.localize(`Insufficient ${action.cost.type} to use this ability`));
return;
}
}
// const targets = otherTarget ? userTargets : [game.user.character];
if (action.damage.type) {
let roll = { formula: action.damage.value, result: action.damage.value };
if (Number.isNaN(Number.parseInt(action.damage.value))) {
roll = await new Roll(`1${action.damage.value}`).evaluate();
}
const cls = getDocumentClass('ChatMessage');
const msg = new cls({
user: game.user.id,
content: await renderTemplate('systems/daggerheart/templates/chat/damage-roll.hbs', {
roll: roll.formula,
total: roll.result,
type: action.damage.type
})
});
cls.create(msg.toObject());
}
if (action.healing.type) {
let roll = { formula: action.healing.value, result: action.healing.value };
if (Number.isNaN(Number.parseInt(action.healing.value))) {
roll = await new Roll(`1${action.healing.value}`).evaluate();
}
const cls = getDocumentClass('ChatMessage');
const msg = new cls({
user: game.user.id,
content: await renderTemplate('systems/daggerheart/templates/chat/healing-roll.hbs', {
roll: roll.formula,
total: roll.result,
type: action.healing.type
})
});
cls.create(msg.toObject());
}
}
}