Merged with action branch

This commit is contained in:
WBHarry 2025-06-24 23:30:37 +02:00
commit 90e7000b9c
19 changed files with 280 additions and 496 deletions

View file

@ -60,9 +60,12 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
context.tabs = this._getTabs();
context.config = SYSTEM;
if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id));
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack') context.hasBaseDamage = !!this.action.parent.damage;
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
context.hasBaseDamage = !!this.action.parent.damage;
context.getRealIndex = this.getRealIndex.bind(this);
context.disableOption = this.disableOption.bind(this);
context.isNPC = this.action.actor.type !== 'character';
return context;
}
@ -74,9 +77,9 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
disableOption(index, options, choices) {
const filtered = foundry.utils.deepClone(options);
Object.keys(filtered).forEach(o => {
if(choices.find((c, idx) => c.type === o && index !== idx)) delete filtered[o];
if (choices.find((c, idx) => c.type === o && index !== idx)) delete filtered[o];
});
return filtered
return filtered;
}
getRealIndex(index) {
@ -95,11 +98,18 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
static async updateForm(event, _, formData) {
const submitData = this._prepareSubmitData(event, formData),
data = foundry.utils.expandObject(foundry.utils.mergeObject(this.action.toObject(), submitData)),
container = foundry.utils.getProperty(this.action.parent, this.action.systemPath);
let newActions;
if (Array.isArray(container)) {
newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject()); // Find better way
if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data);
if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data);
} else newActions = data;
const updates = await this.action.parent.parent.update({ [`system.${this.action.systemPath}`]: newActions });
if (!updates) return;
this.action = foundry.utils.getProperty(updates.system, this.action.systemPath)[this.action.index];
this.action = Array.isArray(container)
? foundry.utils.getProperty(updates.system, this.action.systemPath)[this.action.index]
: foundry.utils.getProperty(updates.system, this.action.systemPath);
this.render();
}

View file

@ -42,6 +42,7 @@ export default class CostSelectionDialog extends HandlebarsApplicationMixin(Appl
}
async _prepareContext(_options) {
console.log(this.costs);
const updatedCosts = this.action.calcCosts(this.costs),
updatedUses = this.action.calcUses(this.uses);
return {

View file

@ -38,7 +38,7 @@ export class DHRoll extends Roll {
config = await DialogClass.configure(config, message);
if (!config) return;
}
let roll = new this(config.formula, config.actor, config);
let roll = new this(config.formula, config.data, config);
for (const hook of config.hooks) {
if (Hooks.call(`${SYSTEM.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false)
@ -101,9 +101,6 @@ export class DualityDie extends foundry.dice.terms.Die {
export class D20Roll extends DHRoll {
constructor(formula, data = {}, options = {}) {
super(formula, data, options);
// console.log(data, options)
// this.options = this._prepareData(data);
// this.options = options;
this.createBaseDice();
this.configureModifiers();
@ -194,10 +191,10 @@ export class D20Roll extends DHRoll {
this.applyBaseBonus();
this.options.experiences?.forEach(m => {
if (this.options.actor.experiences?.[m])
if (this.options.data.experiences?.[m])
this.options.roll.modifiers.push({
label: this.options.actor.experiences[m].description,
value: this.options.actor.experiences[m].total
label: this.options.data.experiences[m].description,
value: this.options.data.experiences[m].total
});
});
this.options.roll.modifiers?.forEach(m => {
@ -214,31 +211,17 @@ export class D20Roll extends DHRoll {
}
applyBaseBonus() {
// if(this.options.action) {
if (this.options.type === 'attack')
this.terms.push(...this.formatModifier(this.options.actor.system.attack.modifier));
/* this.options.roll.modifiers?.forEach(m => {
this.terms.push(...this.formatModifier(m));
}) */
// }
this.terms.push(...this.formatModifier(this.options.data.attack.roll.bonus));
}
static async postEvaluate(roll, config = {}) {
if (config.targets?.length) {
/* targets = config.targets.map(target => {
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion
target.hit = roll.total >= difficulty;
return target;
}); */
config.targets.forEach(target => {
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
target.hit = roll.total >= difficulty;
});
} else if (config.roll.difficulty) roll.success = roll.total >= config.roll.difficulty;
// config.roll.advantage = {
// dice: roll.dHope.faces,
// value: roll.dHope.total
// }
config.roll.total = roll.total;
config.roll.formula = roll.formula;
config.roll.advantage = {
@ -247,10 +230,19 @@ export class D20Roll extends DHRoll {
value: roll.dAdvantage?.total
};
config.roll.modifierTotal = config.roll.modifiers.reduce((a, c) => a + c.value, 0);
config.roll.dice = [];
roll.dice.forEach(d => {
config.roll.dice.push({
dice: d.denomination,
total: d.total,
formula: d.formula,
results: d.results
});
});
}
getRollData() {
return this.options.actor.getRollData();
return this.options.data();
}
formatModifier(modifier) {
@ -357,7 +349,6 @@ export class DualityRoll extends D20Roll {
const dieFaces = 6,
bardRallyFaces = this.hasBarRally,
advDie = new foundry.dice.terms.Die({ faces: dieFaces });
// console.log(this.hasAdvantage, this.hasDisadvantage)
if (this.hasAdvantage || this.hasDisadvantage || bardRallyFaces)
this.terms.push(new foundry.dice.terms.OperatorTerm({ operator: '+' }));
if (bardRallyFaces) {
@ -376,22 +367,15 @@ export class DualityRoll extends D20Roll {
}
applyBaseBonus() {
// if(this.options.action) {
// console.log(this.options, this.options.actor.system.traits[this.options.roll.trait].bonus)
// console.log(this.options.actor.system);
/* if(this.options.roll?.trait) this.terms.push(...this.formatModifier(this.options.actor.traits[this.options.roll.trait].total)); */
if (!this.options.roll.modifiers) this.options.roll.modifiers = [];
if (this.options.roll?.trait)
this.options.roll.modifiers.push({
label: `DAGGERHEART.Abilities.${this.options.roll.trait}.name`,
value: this.options.actor.traits[this.options.roll.trait].total
value: this.options.data.traits[this.options.roll.trait].total
});
console.log(this.options);
// } else if(this.options.trait) this.terms.push(...this.formatModifier(this.options.actor.system.traits[this.options.roll.trait].total));
}
static async postEvaluate(roll, config = {}) {
console.log(roll, config);
super.postEvaluate(roll, config);
config.roll.hope = {
dice: roll.dHope.denomination,
@ -427,30 +411,10 @@ export class DamageRoll extends DHRoll {
static DefaultDialog = DamageDialog;
static async postEvaluate(roll, config = {}) {
console.log(roll, config);
config.roll = {
// formula : config.formula,
result: roll.total,
dice: roll.dice
};
if (roll.healing) config.roll.type = roll.healing.type;
/* const dice = [];
const modifiers = [];
for (var i = 0; i < roll.terms.length; i++) {
const term = roll.terms[i];
if (term.faces) {
dice.push({
type: `d${term.faces}`,
rolls: term.results.map(x => x.result),
total: term.results.reduce((acc, x) => acc + x.result, 0)
});
} else if (term.operator) {
} else if (term.number) {
const operator = i === 0 ? '' : roll.terms[i - 1].operator;
modifiers.push({ value: term.number, operator: operator });
}
}
config.roll.dice = dice;
config.roll.modifiers = modifiers; */
}
}

View file

@ -1,3 +1,4 @@
import DHActionConfig from '../config/Action.mjs';
import DaggerheartSheet from './daggerheart-sheet.mjs';
const { ActorSheetV2 } = foundry.applications.sheets;
@ -9,6 +10,7 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
actions: {
reactionRoll: this.reactionRoll,
attackRoll: this.attackRoll,
attackConfigure: this.attackConfigure,
addExperience: this.addExperience,
removeExperience: this.removeExperience,
toggleHP: this.toggleHP,
@ -51,7 +53,9 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.tabs = super._getTabs(this.constructor.TABS);
context.systemFields.attack.fields = this.document.system.attack.schema.fields;
context.isNPC = true;
console.log(context);
return context;
}
@ -78,25 +82,11 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
}
static async attackRoll(event) {
const { modifier, damage, name: attackName } = this.actor.system.attack,
config = {
event: event,
title: attackName,
roll: {
modifier: modifier,
type: 'action'
},
chatMessage: {
type: 'adversaryRoll',
template: 'systems/daggerheart/templates/chat/adversary-attack-roll.hbs'
},
damage: {
value: damage.value,
type: damage.type
},
checkTarget: true
};
this.actor.diceRoll(config);
this.actor.system.attack.use(event);
}
static async attackConfigure(event) {
await new DHActionConfig(this.document.system.attack).render(true);
}
static async addExperience() {

View file

@ -300,13 +300,15 @@ export const diceTypes = {
d4: 'd4',
d6: 'd6',
d8: 'd8',
d10: 'd10',
d12: 'd12',
d20: 'd20'
};
export const multiplierTypes = {
proficiency: 'Proficiency',
spellcast: 'Spellcast'
spellcast: 'Spellcast',
flat: 'Flat'
};
export const getDiceSoNicePresets = () => {

View file

@ -2,6 +2,7 @@ import DamageSelectionDialog from '../../applications/damageSelectionDialog.mjs'
import CostSelectionDialog from '../../applications/costSelectionDialog.mjs';
import { abilities } from '../../config/actorConfig.mjs';
import { DHActionDiceData, DHDamageData, DHDamageField } from './actionDice.mjs';
import DhpActor from '../../documents/actor.mjs';
const fields = foundry.data.fields;
@ -86,8 +87,8 @@ export class DHBaseAction extends foundry.abstract.DataModel {
range: new fields.StringField({
choices: SYSTEM.GENERAL.range,
required: false,
blank: true,
initial: null
blank: true
// initial: null
}),
...this.defineExtraSchema()
};
@ -99,7 +100,8 @@ export class DHBaseAction extends foundry.abstract.DataModel {
roll: new fields.SchemaField({
type: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.GENERAL.rollTypes }),
trait: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.ACTOR.abilities }),
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 })
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }),
bonus: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 })
}),
save: new fields.SchemaField({
trait: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.ACTOR.abilities }),
@ -151,7 +153,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
}
get actor() {
return this.item?.actor;
return this.item instanceof DhpActor ? this.item : this.item?.actor;
}
get chatTemplate() {
@ -203,6 +205,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
source: {
item: this.item._id,
action: this._id
// action: this
},
type: this.type,
hasDamage: !!this.damage?.parts?.length,
@ -236,25 +239,25 @@ export class DHBaseAction extends foundry.abstract.DataModel {
this.spendCost(config.costs.values);
this.spendUses(config.uses);
// console.log(config)
return config;
}
/* ROLL */
hasRoll() {
return this.roll?.type && this.roll?.trait;
// return this.roll?.type && this.roll?.trait;
return this.roll?.type;
}
async proceedRoll(config) {
if (!this.hasRoll()) return config;
const modifierValue = this.actor.system.traits[this.roll.trait].value;
// const modifierValue = this.actor.system.traits[this.roll.trait].value;
config = {
...config,
roll: {
modifiers: [],
trait: this.roll?.trait,
label: game.i18n.localize(abilities[this.roll.trait].label),
// label: game.i18n.localize(abilities[this.roll.trait].label),
label: 'Attack',
type: this.actionType,
difficulty: this.roll?.difficulty
}
@ -359,7 +362,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
name: actor.actor.name,
img: actor.actor.img,
difficulty: actor.actor.system.difficulty,
evasion: actor.actor.system.evasion?.value
evasion: actor.actor.system.evasion?.total
};
}
/* TARGET */
@ -375,7 +378,6 @@ export class DHBaseAction extends foundry.abstract.DataModel {
async applyEffects(event, data, force = false) {
if (!this.effects?.length || !data.system.targets.length) return;
data.system.targets.forEach(async token => {
// console.log(token, force)
if (!token.hit && !force) return;
this.effects.forEach(async e => {
const actor = canvas.tokens.get(token.id)?.actor,
@ -497,7 +499,6 @@ export class DHHealingAction extends DHBaseAction {
}
async rollHealing(event, data) {
console.log(event, data);
let formula = this.healing.value.getFormula(this.actor);
if (!formula || formula == '') return;

View file

@ -11,6 +11,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
initial: 'proficiency',
label: 'Multiplier'
}),
flatMultiplier: new fields.NumberField({ nullable: true, initial: 1, label: 'Flat Multiplier' }),
dice: new fields.StringField({ choices: SYSTEM.GENERAL.diceTypes, initial: 'd6', label: 'Formula' }),
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }),
custom: new fields.SchemaField({
@ -21,9 +22,10 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
}
getFormula(actor) {
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : actor.system[this.multiplier]?.total;
return this.custom.enabled
? this.custom.formula
: `${actor.system[this.multiplier]?.total ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`;
: `${multiplier ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`;
}
}

View file

@ -1,3 +1,6 @@
import DhpItem from '../../documents/item.mjs';
import ActionField from '../fields/actionField.mjs';
import DHWeapon from '../item/weapon.mjs';
import BaseDataActor from './base.mjs';
const resourceField = () =>
@ -39,7 +42,7 @@ export default class DhpAdversary extends BaseDataActor {
hitPoints: resourceField(),
stress: resourceField()
}),
attack: new fields.SchemaField({
/* attack: new fields.SchemaField({
name: new fields.StringField({}),
modifier: new fields.NumberField({ required: true, integer: true, initial: 0 }),
range: new fields.StringField({
@ -55,6 +58,45 @@ export default class DhpAdversary extends BaseDataActor {
initial: SYSTEM.GENERAL.damageTypes.physical.id
})
})
}), */
/* attack: new fields.EmbeddedDocumentField(DhpItem,
{
// type: 'weapon'
// initial: new DhpItem(
// {
// name: 'Attack',
// type: 'weapon'
// },
// {
// parent: this.parent,
// parentCollection: 'items'
// }
// )
// initial: {type: 'weapon'}
}
), */
attack: new ActionField({
initial: {
name: 'Attack',
_id: foundry.utils.randomID(),
systemPath: 'attack',
type: 'attack',
range: 'melee',
target: {
type: 'any',
amount: 1
},
roll: {
type: 'weapon'
},
damage: {
parts: [
{
multiplier: 'flat'
}
]
}
}
}),
experiences: new fields.TypedObjectField(
new fields.SchemaField({
@ -65,4 +107,21 @@ export default class DhpAdversary extends BaseDataActor {
/* Features waiting on pseudo-document data model addition */
};
}
prepareBaseData() {
// console.log(this.attack)
/* if(!this.attack) {
this.attack = new DhpItem(
{
name: 'Attack',
type: 'weapon',
_id: foundry.utils.randomID()
},
{
parent: this.parent,
parentCollection: 'items'
}
)
} */
}
}

View file

@ -1,23 +1,13 @@
import DhpActor from '../../documents/actor.mjs';
import ActionField from '../fields/actionField.mjs';
export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
title: new fields.StringField(),
origin: new fields.StringField({ required: true }),
dice: new fields.DataField(),
roll: new fields.DataField(),
modifiers: new fields.ArrayField(
new fields.SchemaField({
value: new fields.NumberField({ integer: true }),
label: new fields.StringField({})
})
),
advantageState: new fields.BooleanField({ nullable: true, initial: null }),
advantage: new fields.SchemaField({
dice: new fields.StringField({}),
value: new fields.NumberField({ integer: true })
}),
targets: new fields.ArrayField(
new fields.SchemaField({
id: new fields.StringField({}),
@ -29,24 +19,13 @@ export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel {
})
),
hasDamage: new fields.BooleanField({ initial: false }),
hasHealing: new fields.BooleanField({ initial: false }),
hasEffect: new fields.BooleanField({ initial: false }),
/* damage: new fields.SchemaField(
{
value: new fields.StringField({}),
type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false })
},
{ nullable: true, initial: null }
), */
action: new fields.SchemaField({
itemId: new fields.StringField(),
actionId: new fields.StringField()
source: new fields.SchemaField({
actor: new fields.StringField(),
item: new fields.StringField(),
action: new fields.StringField()
})
};
}
prepareDerivedData() {
this.targets.forEach(target => {
target.hit = target.difficulty ? this.total >= target.difficulty : this.total >= target.evasion;
});
}
}

View file

@ -1,11 +1,4 @@
import { DualityRollColor } from '../settings/Appearance.mjs';
const fields = foundry.data.fields;
const diceField = () =>
new fields.SchemaField({
dice: new fields.StringField({}),
value: new fields.NumberField({ integer: true })
});
export default class DHDualityRoll extends foundry.abstract.TypeDataModel {
static dualityResult = {
@ -17,18 +10,7 @@ export default class DHDualityRoll extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
title: new fields.StringField(),
/* origin: new fields.StringField({ required: true }), */
roll: new fields.DataField({}),
/* modifiers: new fields.ArrayField(
new fields.SchemaField({
value: new fields.NumberField({ integer: true }),
label: new fields.StringField({})
})
), */
/* hope: diceField(),
fear: diceField(),
advantageState: new fields.BooleanField({ nullable: true, initial: null }), */
/* advantage: diceField(), */
targets: new fields.ArrayField(
new fields.SchemaField({
id: new fields.StringField({}),
@ -49,47 +31,4 @@ export default class DHDualityRoll extends foundry.abstract.TypeDataModel {
})
};
}
/* get diceTotal() {
return this.hope.value + this.fear.value;
}
get modifierTotal() {
const total = this.modifiers.reduce((acc, x) => acc + x.value, 0);
return {
value: total,
label: total > 0 ? `+${total}` : total < 0 ? `${total}` : ''
};
}
get dualityResult() {
return this.hope.value > this.fear.value
? this.constructor.dualityResult.hope
: this.fear.value > this.hope.value
? this.constructor.dualityResult.fear
: this.constructor.dualityResult.critical;
}
get totalLabel() {
const label =
this.hope.value > this.fear.value
? 'DAGGERHEART.General.Hope'
: this.fear.value > this.hope.value
? 'DAGGERHEART.General.Fear'
: 'DAGGERHEART.General.CriticalSuccess';
return game.i18n.localize(label);
}
get colorful() {
return (
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).dualityColorScheme ===
DualityRollColor.colorful.value
);
}
prepareDerivedData() {
this.hope.discarded = this.hope.value < this.fear.value;
this.fear.discarded = this.fear.value < this.hope.value;
} */
}

View file

@ -8,8 +8,11 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.config.experiences = [];
if (config.source?.action) {
this.item = config.actor.parent.items.get(config.source.item);
this.action = this.item.system.actions.find(a => a._id === config.source.action);
this.item = config.data.parent.items.get(config.source.item);
this.action =
config.data.attack?._id == config.source.action
? config.data.attack
: this.item.system.actions.find(a => a._id === config.source.action);
}
}
@ -47,9 +50,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.experiences = Object.keys(this.config.actor.experiences).map(id => ({
context.experiences = Object.keys(this.config.data.experiences).map(id => ({
id,
...this.config.actor.experiences[id]
...this.config.data.experiences[id]
}));
context.selectedExperiences = this.config.experiences;
context.advantage = this.config.advantage;

View file

@ -266,153 +266,14 @@ export default class DhpActor extends Actor {
* @param {object} [config.costs]
*/
async diceRoll(config, action) {
// console.log(config)
config.source = { ...(config.source ?? {}), actor: this.id };
const newConfig = {
...config,
actor: this.system
};
const roll =
await CONFIG.Dice.daggerheart[this.type === 'character' ? 'DualityRoll' : 'D20Roll'].build(newConfig);
config.source = { ...(config.source ?? {}), actor: this._id };
config.data = this.getRollData();
const roll = await CONFIG.Dice.daggerheart[this.type === 'character' ? 'DualityRoll' : 'D20Roll'].build(config);
return config;
/* let hopeDice = 'd12',
fearDice = 'd12',
advantageDice = 'd6',
disadvantageDice = 'd6',
advantage = config.event.altKey ? true : config.event.ctrlKey ? false : null,
targets,
modifiers = this.formatRollModifier(config.roll),
rollConfig,
formula,
hope,
fear;
}
if (!config.event.shiftKey && !config.event.altKey && !config.event.ctrlKey) {
const dialogClosed = new Promise((resolve, _) => {
this.type === 'character'
? new RollSelectionDialog(
this.system.experiences,
config.costs,
action,
resolve
).render(true)
: new NpcRollSelectionDialog(
this.system.experiences,
resolve
).render(true);
});
rollConfig = await dialogClosed;
advantage = rollConfig.advantage;
hopeDice = rollConfig.hope;
fearDice = rollConfig.fear;
if(rollConfig.costs) config.costs = rollConfig.costs;
rollConfig.experiences.forEach(x =>
modifiers.push({
value: x.value,
label: x.value >= 0 ? `+${x.value}` : `-${x.value}`,
title: x.description
})
);
if (this.type === 'character') {
const automateHope = 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
});
}
}
}
if (this.type === 'character') {
formula = `1${hopeDice} + 1${fearDice}${advantage === true ? ` + 1d6` : advantage === false ? ` - 1d6` : ''}`;
} else {
formula = `${advantage === true || advantage === false ? 2 : 1}d20${advantage === true ? 'kh' : advantage === false ? 'kl' : ''}`;
}
formula += ` ${modifiers.map(x => `+ ${x.value}`).join(' ')}`;
const roll = await Roll.create(formula).evaluate(),
dice = roll.dice.flatMap(dice => ({
denomination: dice.denomination,
number: dice.number,
total: dice.total,
results: dice.results.map(result => ({ result: result.result, discarded: !result.active }))
}));
config.roll.evaluated = roll;
if (this.type === 'character') {
setDiceSoNiceForDualityRoll(roll, advantage);
hope = roll.dice[0].results[0].result;
fear = roll.dice[1].results[0].result;
if (
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation).hope &&
config.roll.type === 'action'
) {
if (hope > fear) {
await this.update({
'system.resources.hope.value': Math.min(
this.system.resources.hope.value + 1,
this.system.resources.hope.max
)
});
} else if (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)
}
});
}
}
}
if (config.targets?.length) {
targets = config.targets.map(target => {
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion
target.hit = roll.total >= difficulty;
return target;
});
} else if(config.roll.difficulty) roll.success = roll.total >= config.roll.difficulty;
if (config.chatMessage) {
const configRoll = {
title: config.title,
origin: this.id,
dice,
roll,
modifiers: modifiers.filter(x => x.label),
advantageState: advantage,
action: config.source,
hasDamage: config.hasDamage,
hasEffect: config.hasEffect
};
if (this.type === 'character') {
configRoll.hope = { dice: hopeDice, value: hope };
configRoll.fear = { dice: fearDice, value: fear };
configRoll.advantage = { dice: advantageDice, value: roll.dice[2]?.results[0].result ?? null };
}
// if (damage) configRoll.damage = damage;
if (targets) configRoll.targets = targets;
const systemData =
this.type === 'character' && !config.roll.simple ? new DHDualityRoll(configRoll) : configRoll,
cls = getDocumentClass('ChatMessage'),
msg = new cls({
type: config.chatMessage.type ?? 'dualityRoll',
sound: config.chatMessage.mute ? null : CONFIG.sounds.dice,
system: systemData,
content: config.chatMessage.template,
rolls: [roll]
});
await cls.create(msg.toObject());
}
return config; */
getRollData() {
return this.system;
}
formatRollModifier(roll) {
@ -575,104 +436,4 @@ export default class DhpActor extends Actor {
}
});
}
/* async takeHealing(healing, type) {
let update = {};
switch (type) {
case SYSTEM.GENERAL.healingTypes.health.id:
update = {
'system.resources.hitPoints.value': Math.min(
this.system.resources.hitPoints.value + healing,
this.system.resources.hitPoints.max
)
};
break;
case SYSTEM.GENERAL.healingTypes.stress.id:
update = {
'system.resources.stress.value': Math.min(
this.system.resources.stress.value + healing,
this.system.resources.stress.max
)
};
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
}
});
}
} */
//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 <=
this.system.resources[action.cost.type].min
) {
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 foundry.applications.handlebars.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 foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/healing-roll.hbs',
{
roll: roll.formula,
total: roll.result,
type: action.healing.type
}
)
});
cls.create(msg.toObject());
}
} */
}

View file

@ -60,48 +60,51 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
super.close(options);
}
getActor(id) {
return game.actors.get(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 = game.actors.get(message.system.source.actor);
const actor = this.getActor(message.system.source.actor);
if (!actor || !game.user.isGM) return true;
if(message.system.source.item && message.system.source.action) {
const item = actor.items.get(message.system.source.item),
action = item?.system?.actions?.find(a => a._id === message.system.source.action);
if(!item || !action || !action?.rollDamage) return;
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);
} else {
await actor.damageRoll(
message.system.title,
message.system.damage,
message.system.targets.filter(x => x.hit).map(x => ({ id: x.id, name: x.name, img: x.img })),
event.shiftKey
);
}
};
onRollHealing = async (event, message) => {
event.stopPropagation();
const actor = game.actors.get(message.system.source.actor);
const actor = this.getActor(message.system.source.actor);
if (!actor || !game.user.isGM) return true;
if(message.system.source.item && message.system.source.action) {
const item = actor.items.get(message.system.source.item),
action = item?.system?.actions?.find(a => a._id === message.system.source.action);
if(!item || !action || !action?.rollHealing) return;
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);
}
};
onApplyEffect = async (event, message) => {
event.stopPropagation();
const actor = game.actors.get(message.system.source.actor);
const actor = this.getActor(message.system.source.actor);
if (!actor || !game.user.isGM) return true;
if(message.system.source.item && message.system.source.action) {
const item = actor.items.get(message.system.source.item),
action = item?.system?.actions?.find(a => a._id === message.system.source.action);
if(!item || !action || !action.applyEffects) return;
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;
await action.applyEffects(event, message);
}
}
};
hoverTarget = event => {
event.stopPropagation();
@ -148,7 +151,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected'));
for (var target of targets) {
await target.actor.modifyResource([{value: healing, type: event.currentTarget.dataset.type}]);
await target.actor.modifyResource([{ value: healing, type: event.currentTarget.dataset.type }]);
}
};

View file

@ -1,33 +1,51 @@
<div class="dice-roll daggerheart chat roll" data-action="expandRoll">
<div class="dice-flavor">{{title}}</div>
<div class="dice-result">
<div class="dice-formula">{{roll.formula}}</div>
<div class="dice-tooltip">
<ol class="dice-rolls">
<div class="dice-hope-container">
{{#each dice}}
<div class="wrapper">
<section class="tooltip-part">
<div class="dice">
{{#each roll.dice}}
<header class="part-header flexrow">
<span class="part-formula">{{number}}{{denomination}}</span>
<span class="part-formula">{{formula}}</span>
<span class="part-total">{{total}}</span>
</header>
<div class="flexrow">
<ol class="dice-rolls">
{{#each results}}
<li class="roll die {{../denomination}}{{#if discarded}} discarded{{/if}} min">{{result}}</li>
<li class="roll die {{../dice}}{{#if discarded}} discarded{{/if}} min">{{result}}</li>
{{/each}}
</ol>
<div class="attack-roll-advantage-container">{{#if ../advantageState}}{{localize "DAGGERHEART.General.Advantage.Full"}}{{/if}}{{#if (eq ../advantageState false)}}{{localize "DAGGERHEART.General.Disadvantage.Full"}}{{/if}}</div>
<div class="attack-roll-advantage-container">
{{#if (eq ../roll.advantage.type 1)}}{{localize "DAGGERHEART.General.Advantage.Full"}}{{/if}}{{#if (eq ../roll.advantage.type -1)}}{{localize "DAGGERHEART.General.Disadvantage.Full"}}{{/if}}
</div>
</div>
{{/each}}
</div>
<div class="modifiers-container">
{{#each modifiers}}
<li class="modifier-value" data-value="{{value}}" title="{{title}}">{{label}}</li>
{{/each}}
</div>
</ol>
</div>
</section>
</div>
</div>
<div class="dice-total">
<div class="dice-total-value">{{roll.total}}</div>
</div>
{{#if (gt targets.length 0)}}
<div class="target-section">
{{#each targets as |target|}}
<div class="dice-total target-container {{#if target.hit}}hit{{else}}miss{{/if}}" data-token="{{target.id}}">
<img src="{{target.img}}" />
<div class="target-inner-container">
{{#if target.hit}}{{localize "Hit"}}{{else}}{{localize "Miss"}}{{/if}}
</div>
</div>
{{/each}}
</div>
{{/if}}
{{#if hasDamage}}
<div class="flexrow">
<button class="duality-action duality-action-damage" data-value="{{roll.total}}"><span>Roll Damage</span></button>
</div>
{{/if}}
</div>
</div>

View file

@ -49,13 +49,43 @@
<fieldset class="two-columns even">
<legend>{{localize "DAGGERHEART.Sheets.Adversary.Attack"}}</legend>
<button data-action="attackConfigure">Configure</button>
<button data-action="attackRoll">Attack</button>
<fieldset class="action-category" style="grid-column: 1 / -1;">
<legend class="action-category-label" data-action="toggleSection" data-section="range">
<div>Name</div>
</legend>
<div class="action-category-data open">
{{formGroup systemFields.attack.fields.name value=source.system.attack.name name="system.attack.name"}}
{{formGroup systemFields.attack.fields.img value=source.img label="Icon" name="system.attack.img"}}
</div>
</fieldset>
<div>
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="range">
<div>Bonus</div>
</legend>
<div class="action-category-data open">
{{formField systemFields.attack.fields.roll.fields.bonus value=source.system.attack.roll.bonus name="system.attack.roll.bonus"}}
</div>
</fieldset>
{{> 'systems/daggerheart/templates/views/actionTypes/range-target.hbs' fields=(object range=systemFields.attack.fields.range target=systemFields.attack.fields.target.fields) source=(object target=source.system.attack.target range=source.system.attack.range) path="system.attack."}}
</div>
{{> 'systems/daggerheart/templates/views/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=source.system.attack.damage path="system.attack."}}
<div style="grid-column: 1 / -1;">
{{> 'systems/daggerheart/templates/views/actionTypes/effect.hbs'}}
</div>
{{!-- <legend>{{localize "DAGGERHEART.Sheets.Adversary.Attack"}}</legend>
{{formGroup systemFields.attack.fields.name value=source.system.attack.name}}
<button data-action="attackRoll">Attack</button>
{{formGroup systemFields.attack.fields.modifier value=source.system.attack.modifier}}
{{formGroup systemFields.attack.fields.range value=source.system.attack.range localize=true}}
{{formGroup systemFields.attack.fields.damage.fields.value value=source.system.attack.damage.value}}
{{formGroup systemFields.attack.fields.damage.fields.type value=source.system.attack.damage.type localize=true}}
{{formGroup systemFields.attack.fields.damage.fields.type value=source.system.attack.damage.type localize=true}} --}}
</fieldset>
</div>
</section>

View file

@ -33,8 +33,10 @@
</fieldset>
</div>
<div class="tab {{this.tabs.config.cssClass}}" data-group="primary" data-tab="config">
{{#unless isNPC}}
{{> 'systems/daggerheart/templates/views/actionTypes/uses.hbs' fields=fields.uses.fields source=source.uses}}
{{> 'systems/daggerheart/templates/views/actionTypes/cost.hbs' fields=fields.cost.element.fields source=source.cost}}
{{/unless}}
{{#if fields.target}}{{> 'systems/daggerheart/templates/views/actionTypes/range-target.hbs' fields=(object range=fields.range target=fields.target.fields) source=(object target=source.target range=source.range)}}{{/if}}
</div>
<div class="tab {{this.tabs.effect.cssClass}}" data-group="primary" data-tab="effect">

View file

@ -4,33 +4,49 @@
<div>Damage</div>
</legend>
<div class="action-category-data open">
<div class="fas fa-plus icon-button" data-action="addDamage"></div>
{{#if @root.hasBaseDamage}}
<div>
{{!-- <input type="checkbox" data-action="addBaseDamage"{{#if @root.hasBaseDamage}} checked{{/if}}> --}}
{{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase label="Include Item Damage" name="damage.includeBase" }}
</div>
{{/if}}
{{#unless @root.isNPC}}
<div class="fas fa-plus icon-button" data-action="addDamage"></div>
{{#if @root.hasBaseDamage}}
<div>
{{!-- <input type="checkbox" data-action="addBaseDamage"{{#if @root.hasBaseDamage}} checked{{/if}}> --}}
{{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase label="Include Item Damage" name="damage.includeBase" }}
</div>
{{/if}}
{{/unless}}
{{#each source.parts as |dmg index|}}
{{#with (@root.getRealIndex index) as | realIndex |}}
<fieldset{{#if dmg.base}} disabled{{/if}}>
{{#unless dmg.base}}
{{formField ../../fields.custom.fields.enabled value=dmg.custom.enabled name=(concat "damage.parts." realIndex ".custom.enabled")}}
{{/unless}}
{{#if @root.isNPC}}
{{formField ../fields.custom.fields.enabled value=dmg.custom.enabled name=(concat ../path "damage.parts." index ".custom.enabled")}}
{{#if dmg.custom.enabled}}
{{formField ../../fields.custom.fields.formula value=dmg.custom.formula name=(concat "damage.parts." realIndex ".custom.formula") localize=true}}
{{formField ../fields.custom.fields.formula value=dmg.custom.formula name=(concat ../path "damage.parts." index ".custom.formula") localize=true}}
{{else}}
<div class="multi-display">
{{formField ../../fields.multiplier value=dmg.multiplier name=(concat "damage.parts." realIndex ".multiplier") localize=true}}
{{formField ../../fields.dice value=dmg.dice name=(concat "damage.parts." realIndex ".dice")}}
{{formField ../../fields.bonus value=dmg.bonus name=(concat "damage.parts." realIndex ".bonus") localize=true}}
{{formField ../fields.flatMultiplier value=dmg.flatMultiplier name=(concat "damage.parts." realIndex ".flatMultiplier") label="Multiplier" }}
{{formField ../fields.dice value=dmg.dice name=(concat ../path "damage.parts." index ".dice")}}
{{formField ../fields.bonus value=dmg.bonus name=(concat ../path "damage.parts." index ".bonus") localize=true}}
</div>
{{/if}}
{{formField ../../fields.type value=dmg.type name=(concat "damage.parts." realIndex ".type") localize=true}}
<input type="hidden" name="damage.parts.{{realIndex}}.base" value="{{dmg.base}}">
{{#unless dmg.base}}<div class="fas fa-trash" data-action="removeDamage" data-index="{{realIndex}}"></div>{{/unless}}
</fieldset>
{{/with}}
{{else}}
{{#with (@root.getRealIndex index) as | realIndex |}}
<fieldset{{#if dmg.base}} disabled{{/if}}>
{{#unless dmg.base}}
{{formField ../../fields.custom.fields.enabled value=dmg.custom.enabled name=(concat "damage.parts." realIndex ".custom.enabled")}}
{{/unless}}
{{#if dmg.custom.enabled}}
{{formField ../../fields.custom.fields.formula value=dmg.custom.formula name=(concat "damage.parts." realIndex ".custom.formula") localize=true}}
{{else}}
<div class="multi-display">
{{formField ../../fields.multiplier value=dmg.multiplier name=(concat "damage.parts." realIndex ".multiplier") localize=true}}
{{#if (eq dmg.multiplier 'flat')}}{{formField ../../fields.flatMultiplier value=dmg.flatMultiplier name=(concat "damage.parts." realIndex ".flatMultiplier") }}{{/if}}
{{formField ../../fields.dice value=dmg.dice name=(concat "damage.parts." realIndex ".dice")}}
{{formField ../../fields.bonus value=dmg.bonus name=(concat "damage.parts." realIndex ".bonus") localize=true}}
</div>
{{/if}}
{{formField ../../fields.type value=dmg.type name=(concat "damage.parts." realIndex ".type") localize=true}}
<input type="hidden" name="damage.parts.{{realIndex}}.base" value="{{dmg.base}}">
{{#unless dmg.base}}<div class="fas fa-trash" data-action="removeDamage" data-index="{{realIndex}}"></div>{{/unless}}
</fieldset>
{{/with}}
{{/if}}
{{/each}}
</div>
</fieldset>

View file

@ -3,15 +3,15 @@
<div>Range{{#if fields.target}} & Target{{/if}}</div>
</legend>
<div class="action-category-data open">
{{formField fields.range value=source.range label="Range" name="range" localize=true}}
{{formField fields.range value=source.range label="Range" name=(concat path "range") localize=true}}
</div>
{{#if fields.target}}
<div class="action-category-data open">
<div class="multi-display">
{{#if (and source.target.type (not (eq source.target.type 'self')))}}
{{ formField fields.target.amount value=source.target.amount label="Amount" name="target.amount" }}
{{ formField fields.target.amount value=source.target.amount label="Amount" name=(concat path "target.amount") }}
{{/if}}
{{ formField fields.target.type value=source.target.type label="Target" name="target.type" localize=true }}
{{ formField fields.target.type value=source.target.type label="Target" name=(concat path "target.type") localize=true }}
</div>
</div>
{{/if}}

View file

@ -3,8 +3,12 @@
<div>Roll</div>
</legend>
<div class="action-category-data open">
{{formField fields.type label="Type" name="roll.type" value=source.type localize=true}}
{{formField fields.trait label="Trait" name="roll.trait" value=source.trait localize=true}}
{{formField fields.difficulty label="Difficulty" name="roll.difficulty" value=source.difficulty}}
{{#if @root.isNPC}}
{{formField fields.bonus label="Bonus" name="roll.bonus" value=source.bonus}}
{{else}}
{{formField fields.type label="Type" name="roll.type" value=source.type localize=true}}
{{formField fields.trait label="Trait" name="roll.trait" value=source.trait localize=true}}
{{formField fields.difficulty label="Difficulty" name="roll.difficulty" value=source.difficulty}}
{{/if}}
</div>
</fieldset>