mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-06-05 20:34:15 +02:00
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
* Initial * Removed damage dialogs * Fixed DamageReroll * Fixed d20 modifiers * Fixed * Fixed DiceSoNice multiple damageType reroll * Added triggerChatRollFx * Fixed dice.denomination being lost on damage reroll
223 lines
8.2 KiB
JavaScript
223 lines
8.2 KiB
JavaScript
import { triggerChatRollFx } from '../../helpers/utils.mjs';
|
|
|
|
const fields = foundry.data.fields;
|
|
|
|
const targetsField = () =>
|
|
new fields.ArrayField(
|
|
new fields.SchemaField({
|
|
id: new fields.StringField({}),
|
|
actorId: new fields.StringField({}),
|
|
name: new fields.StringField({}),
|
|
img: new fields.StringField({}),
|
|
difficulty: new fields.NumberField({ integer: true, nullable: true }),
|
|
evasion: new fields.NumberField({ integer: true }),
|
|
hit: new fields.BooleanField({ initial: false }),
|
|
saved: new fields.SchemaField({
|
|
result: new fields.NumberField(),
|
|
success: new fields.BooleanField({ nullable: true, initial: null })
|
|
})
|
|
})
|
|
);
|
|
|
|
export const originItemField = () =>
|
|
new fields.SchemaField({
|
|
type: new fields.StringField({
|
|
choices: CONFIG.DH.ITEM.originItemType,
|
|
initial: CONFIG.DH.ITEM.originItemType.itemCollection
|
|
}),
|
|
itemPath: new fields.StringField(),
|
|
actionIndex: new fields.StringField()
|
|
});
|
|
|
|
export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|
static defineSchema() {
|
|
return {
|
|
title: new fields.StringField(),
|
|
actionDescription: new fields.HTMLField(),
|
|
targets: targetsField(),
|
|
hasRoll: new fields.BooleanField({ initial: false }),
|
|
hasDamage: new fields.BooleanField({ initial: false }),
|
|
hasHealing: new fields.BooleanField({ initial: false }),
|
|
hasEffect: new fields.BooleanField({ initial: false }),
|
|
hasSave: new fields.BooleanField({ initial: false }),
|
|
hasTarget: new fields.BooleanField({ initial: false }),
|
|
isDirect: new fields.BooleanField({ initial: false }),
|
|
onSave: new fields.StringField(),
|
|
source: new fields.SchemaField({
|
|
actor: new fields.StringField(),
|
|
item: new fields.StringField(),
|
|
originItem: originItemField(),
|
|
action: new fields.StringField()
|
|
}),
|
|
damage: new fields.ObjectField(),
|
|
damageOptions: new fields.ObjectField(),
|
|
costs: new fields.ArrayField(new fields.ObjectField()),
|
|
successConsumed: new fields.BooleanField({ initial: false })
|
|
};
|
|
}
|
|
|
|
get roll() {
|
|
switch (this.parent.type) {
|
|
case 'adversaryRoll':
|
|
return this.parent.rolls.find(x => x instanceof game.system.api.dice.D20Roll);
|
|
case 'dualityRoll':
|
|
return this.parent.rolls.find(x => x instanceof game.system.api.dice.DualityRoll);
|
|
case 'fateRoll':
|
|
return this.parent.rolls.find(x => x instanceof game.system.api.dice.FateRoll);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get actionActor() {
|
|
if (!this.source.actor) return null;
|
|
return fromUuidSync(this.source.actor);
|
|
}
|
|
|
|
get actionItem() {
|
|
const actionActor = this.actionActor;
|
|
if (!actionActor || !this.source.item) return null;
|
|
|
|
switch (this.source.originItem.type) {
|
|
case CONFIG.DH.ITEM.originItemType.restMove:
|
|
const restMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves;
|
|
return Array.from(foundry.utils.getProperty(restMoves, `${this.source.originItem.itemPath}`).actions)[
|
|
this.source.originItem.actionIndex
|
|
];
|
|
default:
|
|
const item = actionActor.items.get(this.source.item);
|
|
return item ? item.system.actionsList?.find(a => a.id === this.source.action) : null;
|
|
}
|
|
}
|
|
|
|
get action() {
|
|
const { actionActor, actionItem: itemAction } = this;
|
|
if (!this.source.action) return null;
|
|
if (itemAction) return itemAction;
|
|
else if (actionActor?.system.attack?._id === this.source.action) return actionActor.system.attack;
|
|
return null;
|
|
}
|
|
|
|
get targetMode() {
|
|
return this.parent.targetSelection;
|
|
}
|
|
|
|
set targetMode(mode) {
|
|
if (!this.parent.isAuthor) return;
|
|
this.parent.targetSelection = mode;
|
|
this.registerTargetHook();
|
|
this.updateTargets();
|
|
}
|
|
|
|
get hitTargets() {
|
|
return this.currentTargets.filter(t => t.hit || !this.hasRoll || !this.targetMode);
|
|
}
|
|
|
|
async updateTargets() {
|
|
if (!ui.chat.collection.get(this.parent.id)) return;
|
|
let targets;
|
|
if (this.targetMode) targets = this.targets;
|
|
else
|
|
targets = Array.from(game.user.targets).map(t =>
|
|
game.system.api.fields.ActionFields.TargetField.formatTarget(t)
|
|
);
|
|
|
|
await this.parent.update({
|
|
flags: {
|
|
[game.system.id]: {
|
|
targets: targets,
|
|
targetMode: this.targetMode
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/* TODO: Change how damage data is stored somehow to enable better rerolling */
|
|
async getRerolledDamage() {
|
|
if (!this.damage) return;
|
|
|
|
const rerolls = [];
|
|
const update = { system: { damage: {} } };
|
|
for (const partKey in this.damage) {
|
|
const part = this.damage[partKey];
|
|
const testRoll = Roll.fromData(part.parts[0].roll);
|
|
const rerolled = await testRoll.reroll();
|
|
rerolls.push(rerolled);
|
|
|
|
if (!update.system.damage[partKey]) update.system.damage[partKey] = { parts: [part.parts[0]] };
|
|
const partData = update.system.damage[partKey].parts[0];
|
|
update.system.damage[partKey].total = rerolled.total;
|
|
partData.modifierTotal = rerolled.terms.reduce((acc, x) => {
|
|
if (x.isDeterministic && !x.operator) acc += x.total;
|
|
return acc;
|
|
}, 0);
|
|
partData.dice = rerolled.dice.map(d => ({ ...d.toJSON(), dice: d.denomination }));
|
|
partData.total = rerolled.total;
|
|
partData.roll = rerolled.toJSON();
|
|
}
|
|
|
|
await triggerChatRollFx(rerolls);
|
|
|
|
return update;
|
|
}
|
|
|
|
registerTargetHook() {
|
|
if (!this.parent.isAuthor || !this.hasTarget) return;
|
|
if (this.targetMode && this.parent.targetHook !== null) {
|
|
Hooks.off('targetToken', this.parent.targetHook);
|
|
return (this.parent.targetHook = null);
|
|
} else if (!this.targetMode && this.parent.targetHook === null) {
|
|
return (this.parent.targetHook = Hooks.on(
|
|
'targetToken',
|
|
foundry.utils.debounce(this.updateTargets.bind(this), 50)
|
|
));
|
|
}
|
|
}
|
|
|
|
prepareDerivedData() {
|
|
if (this.hasTarget) {
|
|
this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0;
|
|
this.currentTargets = this.getTargetList();
|
|
// this.registerTargetHook();
|
|
|
|
if (this.hasRoll) {
|
|
this.targetShort = this.targets.reduce(
|
|
(a, c) => {
|
|
if (c.hit) a.hit += 1;
|
|
else a.miss += 1;
|
|
return a;
|
|
},
|
|
{ hit: 0, miss: 0 }
|
|
);
|
|
}
|
|
if (this.hasSave) this.setPendingSaves();
|
|
}
|
|
|
|
this.canViewSecret = this.parent.speakerActor?.testUserPermission(game.user, 'OBSERVER');
|
|
this.canButtonApply = game.user.isGM; //temp
|
|
this.isGM = game.user.isGM; //temp
|
|
}
|
|
|
|
getTargetList() {
|
|
const targets =
|
|
this.targetMode && this.parent.isAuthor
|
|
? this.targets
|
|
: (this.parent.getFlag(game.system.id, 'targets') ?? this.targets),
|
|
reactionRolls = this.parent.getFlag(game.system.id, 'reactionRolls');
|
|
|
|
if (reactionRolls) {
|
|
Object.entries(reactionRolls).forEach(([k, r]) => {
|
|
const target = targets.find(t => t.id === k);
|
|
if (target) target.saved = r;
|
|
});
|
|
}
|
|
|
|
return targets;
|
|
}
|
|
|
|
setPendingSaves() {
|
|
this.pendingSaves = this.targetMode
|
|
? this.targets.filter(target => target.hit && target.saved.success === null).length > 0
|
|
: this.currentTargets.filter(target => target.saved.success === null).length > 0;
|
|
}
|
|
}
|