mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 23:13:39 +02:00
Implemented group attack logic
This commit is contained in:
parent
02cca277da
commit
54fab46b66
12 changed files with 154 additions and 11 deletions
|
|
@ -131,6 +131,7 @@
|
||||||
"attackName": "Attack Name",
|
"attackName": "Attack Name",
|
||||||
"criticalThreshold": "Critical Threshold",
|
"criticalThreshold": "Critical Threshold",
|
||||||
"includeBase": { "label": "Include Item Damage" },
|
"includeBase": { "label": "Include Item Damage" },
|
||||||
|
"groupAttack": { "label": "Group Attack" },
|
||||||
"multiplier": "Multiplier",
|
"multiplier": "Multiplier",
|
||||||
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
||||||
"resultBased": {
|
"resultBased": {
|
||||||
|
|
@ -3142,7 +3143,8 @@
|
||||||
"tokenActorsMissing": "[{names}] missing Actors",
|
"tokenActorsMissing": "[{names}] missing Actors",
|
||||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
||||||
"knowTheTide": "Know The Tide gained a token",
|
"knowTheTide": "Know The Tide gained a token",
|
||||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}"
|
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}",
|
||||||
|
"noTokenTargeted": "No token is targeted"
|
||||||
},
|
},
|
||||||
"Progress": {
|
"Progress": {
|
||||||
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
toggleSelectedEffect: this.toggleSelectedEffect,
|
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||||
|
updateGroupAttack: this.updateGroupAttack,
|
||||||
toggleCritical: this.toggleCritical,
|
toggleCritical: this.toggleCritical,
|
||||||
submitRoll: this.submitRoll
|
submitRoll: this.submitRoll
|
||||||
},
|
},
|
||||||
|
|
@ -64,15 +65,40 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length);
|
context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length);
|
||||||
context.selectedEffects = this.selectedEffects;
|
context.selectedEffects = this.selectedEffects;
|
||||||
|
|
||||||
|
context.damageOptions = this.config.damageOptions;
|
||||||
|
context.rangeOptions = CONFIG.DH.GENERAL.groupAttackRange;
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateRollConfiguration(_event, _, formData) {
|
static updateRollConfiguration(_event, _, formData) {
|
||||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
const data = foundry.utils.expandObject(formData.object);
|
||||||
foundry.utils.mergeObject(this.config.roll, rest.roll);
|
foundry.utils.mergeObject(this.config.roll, data.roll);
|
||||||
foundry.utils.mergeObject(this.config.modifiers, rest.modifiers);
|
foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
|
||||||
this.config.selectedMessageMode = rest.selectedMessageMode;
|
this.config.selectedMessageMode = data.selectedMessageMode;
|
||||||
|
|
||||||
|
if (data.damageOptions) {
|
||||||
|
const groupAttackNr = data.damageOptions.groupAttack?.nr;
|
||||||
|
if (typeof groupAttackNr !== 'number' || groupAttackNr % 1 !== 0) {
|
||||||
|
data.damageOptions.groupAttack.nr = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foundry.utils.mergeObject(this.config.damageOptions, data.damageOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static updateGroupAttack() {
|
||||||
|
const targets = Array.from(game.user.targets);
|
||||||
|
if (targets.length === 0)
|
||||||
|
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noTokenTargeted'));
|
||||||
|
|
||||||
|
const actorId = this.roll.data.parent.id;
|
||||||
|
const range = this.config.damageOptions.groupAttack.range;
|
||||||
|
const groupAttackTokens = game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(actorId, range);
|
||||||
|
|
||||||
|
this.config.damageOptions.groupAttack.nr = groupAttackTokens.length;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,14 @@ export const range = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const groupAttackRange = {
|
||||||
|
melee: range.melee,
|
||||||
|
veryClose: range.veryClose,
|
||||||
|
close: range.close,
|
||||||
|
far: range.far,
|
||||||
|
veryFar: range.veryFar
|
||||||
|
};
|
||||||
|
|
||||||
/* circle|cone|rect|ray used to be CONST.MEASURED_TEMPLATE_TYPES. Hardcoded for now */
|
/* circle|cone|rect|ray used to be CONST.MEASURED_TEMPLATE_TYPES. Hardcoded for now */
|
||||||
export const templateTypes = {
|
export const templateTypes = {
|
||||||
CIRCLE: 'circle',
|
CIRCLE: 'circle',
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
hasSave: this.hasSave,
|
hasSave: this.hasSave,
|
||||||
onSave: this.save?.damageMod,
|
onSave: this.save?.damageMod,
|
||||||
isDirect: !!this.damage?.direct,
|
isDirect: !!this.damage?.direct,
|
||||||
|
damageOptions: this.damage?.groupAttack ? {} : null,
|
||||||
selectedMessageMode: game.settings.get('core', 'messageMode'),
|
selectedMessageMode: game.settings.get('core', 'messageMode'),
|
||||||
data: this.getRollData(),
|
data: this.getRollData(),
|
||||||
evaluate: this.hasRoll,
|
evaluate: this.hasRoll,
|
||||||
|
|
@ -280,6 +281,26 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.damage) {
|
||||||
|
config.isDirect = this.damage.direct;
|
||||||
|
|
||||||
|
const groupAttackTokens = this.damage.groupAttack
|
||||||
|
? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(
|
||||||
|
this.actor.id,
|
||||||
|
this.damage.groupAttack
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
config.damageOptions = {
|
||||||
|
groupAttack: this.damage.groupAttack
|
||||||
|
? {
|
||||||
|
nr: Math.max(groupAttackTokens.length, 1),
|
||||||
|
range: this.damage.groupAttack
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
DHBaseAction.applyKeybindings(config);
|
DHBaseAction.applyKeybindings(config);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
action: new fields.StringField()
|
action: new fields.StringField()
|
||||||
}),
|
}),
|
||||||
damage: new fields.ObjectField(),
|
damage: new fields.ObjectField(),
|
||||||
|
damageOptions: new fields.ObjectField(),
|
||||||
costs: new fields.ArrayField(new fields.ObjectField()),
|
costs: new fields.ArrayField(new fields.ObjectField()),
|
||||||
successConsumed: new fields.BooleanField({ initial: false })
|
successConsumed: new fields.BooleanField({ initial: false })
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,12 @@ export default class DamageField extends fields.SchemaField {
|
||||||
initial: false,
|
initial: false,
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
||||||
}),
|
}),
|
||||||
direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' })
|
direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }),
|
||||||
|
groupAttack: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.groupAttackRange,
|
||||||
|
blank: true,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label'
|
||||||
|
})
|
||||||
};
|
};
|
||||||
super(damageFields, options, context);
|
super(damageFields, options, context);
|
||||||
}
|
}
|
||||||
|
|
@ -224,6 +229,27 @@ export default class DamageField extends fields.SchemaField {
|
||||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players)
|
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getGroupAttackTokens(actorId, range) {
|
||||||
|
if (!canvas.scene) return [];
|
||||||
|
|
||||||
|
const targets = Array.from(game.user.targets);
|
||||||
|
const { custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
|
||||||
|
const sceneMeasurements = canvas.scene?.flags.daggerheart?.rangeMeasurement;
|
||||||
|
const globalMeasurements = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
||||||
|
).rangeMeasurement;
|
||||||
|
const rangeSettings = sceneMeasurements?.setting === custom.id ? sceneMeasurements : globalMeasurements;
|
||||||
|
|
||||||
|
const maxDistance = rangeSettings[range];
|
||||||
|
return canvas.scene.tokens.filter(x => {
|
||||||
|
if (x.actor?.id !== actorId) return false;
|
||||||
|
if (targets.every(target => x.object.distanceTo(target) > maxDistance)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DHActionDiceData extends foundry.abstract.DataModel {
|
export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
constructFormula(config) {
|
constructFormula(config) {
|
||||||
this.options.isCritical = config.isCritical;
|
this.options.isCritical = config.isCritical;
|
||||||
for (const [index, part] of this.options.roll.entries()) {
|
for (const [index, part] of this.options.roll.entries()) {
|
||||||
|
const isHitpointPart = part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id;
|
||||||
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
||||||
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
||||||
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||||
|
|
@ -169,7 +170,16 @@ export default class DamageRoll extends DHRoll {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.isCritical && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
if (config.damageOptions.groupAttack?.nr > 1 && isHitpointPart) {
|
||||||
|
const damageTypes = [foundry.dice.terms.Die, foundry.dice.terms.NumericTerm];
|
||||||
|
for (const term of part.roll.terms) {
|
||||||
|
if (damageTypes.some(type => term instanceof type)) {
|
||||||
|
term.number *= config.damageOptions.groupAttack.nr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.isCritical && isHitpointPart) {
|
||||||
const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
|
const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
part.roll.terms.push(...this.formatModifier(total));
|
part.roll.terms.push(...this.formatModifier(total));
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
.critical-chip {
|
.critical-chip {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
@ -41,6 +41,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.group-attack-container {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.group-attack-inner-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-attack-tools {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.damage-section-controls {
|
.damage-section-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -419,11 +419,19 @@
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.form-fields {
|
.form-fields {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.select {
|
||||||
|
width: fit-content;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.scalable-input {
|
.scalable-input {
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,16 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless (eq path 'system.attack.')}}<a data-action="addDamage" {{#if @root.allDamageTypesUsed}}disabled{{/if}}><i class="fa-solid fa-plus icon-button"></i></a>{{/unless}}
|
{{#unless (eq path 'system.attack.')}}<a data-action="addDamage" {{#if @root.allDamageTypesUsed}}disabled{{/if}}><i class="fa-solid fa-plus icon-button"></i></a>{{/unless}}
|
||||||
</legend>
|
</legend>
|
||||||
<div class="nest-inputs space-between">
|
<div class="nest-inputs">
|
||||||
{{#if @root.hasBaseDamage}}
|
{{#if @root.hasBaseDamage}}
|
||||||
{{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase name="damage.includeBase" classes="checkbox" localize=true }}
|
{{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase name="damage.includeBase" classes="checkbox" localize=true }}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#unless (eq @root.source.type 'healing')}}
|
{{#unless (eq @root.source.type 'healing')}}
|
||||||
{{formField directField value=source.direct name=(concat path "damage.direct") localize=true classes="checkbox"}}
|
{{formField baseFields.direct value=source.direct name=(concat path "damage.direct") localize=true classes="checkbox"}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
{{#if @root.isNPC}}
|
||||||
|
{{formField baseFields.groupAttack value=source.groupAttack name=(concat path "damage.groupAttack") localize=true classes="select"}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{!-- Handlebars uses Symbol.Iterator to produce index|key. This isn't compatible with our parts object, so we instead use applyTo, which is the same value --}}
|
{{!-- Handlebars uses Symbol.Iterator to produce index|key. This isn't compatible with our parts object, so we instead use applyTo, which is the same value --}}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,24 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
|
{{#if damageOptions.groupAttack}}
|
||||||
|
<fieldset class="group-attack-container">
|
||||||
|
<legend>{{localize "DAGGERHEART.ACTIONS.Settings.groupAttack.label"}}</legend>
|
||||||
|
|
||||||
|
<div class="group-attack-inner-container">
|
||||||
|
<input type="text" data-dtype="Number" name="damageOptions.groupAttack.nr" value="{{damageOptions.groupAttack.nr}}" />
|
||||||
|
|
||||||
|
<div class="group-attack-tools">
|
||||||
|
<select name="damageOptions.groupAttack.range">
|
||||||
|
{{selectOptions rangeOptions selected=damageOptions.groupAttack.range localize=true}}
|
||||||
|
</select>
|
||||||
|
<button data-action="updateGroupAttack"><i class="fa-solid fa-crosshairs"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#unless (empty @root.modifiers)}}
|
{{#unless (empty @root.modifiers)}}
|
||||||
<fieldset class="modifier-container two-columns">
|
<fieldset class="modifier-container two-columns">
|
||||||
<legend>{{localize "DAGGERHEART.GENERAL.Modifier.plural"}}</legend>
|
<legend>{{localize "DAGGERHEART.GENERAL.Modifier.plural"}}</legend>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
>
|
>
|
||||||
{{#if fields.roll}}{{> 'systems/daggerheart/templates/actionTypes/roll.hbs' fields=fields.roll.fields source=source.roll}}{{/if}}
|
{{#if fields.roll}}{{> 'systems/daggerheart/templates/actionTypes/roll.hbs' fields=fields.roll.fields source=source.roll}}{{/if}}
|
||||||
{{#if fields.save}}{{> 'systems/daggerheart/templates/actionTypes/save.hbs' fields=fields.save.fields source=source.save}}{{/if}}
|
{{#if fields.save}}{{> 'systems/daggerheart/templates/actionTypes/save.hbs' fields=fields.save.fields source=source.save}}{{/if}}
|
||||||
{{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=source.damage directField=fields.damage.fields.direct }}{{/if}}
|
{{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=source.damage baseFields=fields.damage.fields }}{{/if}}
|
||||||
{{#if fields.macro}}{{> 'systems/daggerheart/templates/actionTypes/macro.hbs' fields=fields.macro source=source.macro}}{{/if}}
|
{{#if fields.macro}}{{> 'systems/daggerheart/templates/actionTypes/macro.hbs' fields=fields.macro source=source.macro}}{{/if}}
|
||||||
{{#if fields.effects}}{{> 'systems/daggerheart/templates/actionTypes/effect.hbs' fields=fields.effects.element.fields source=source.effects}}{{/if}}
|
{{#if fields.effects}}{{> 'systems/daggerheart/templates/actionTypes/effect.hbs' fields=fields.effects.element.fields source=source.effects}}{{/if}}
|
||||||
{{#if fields.beastform}}{{> 'systems/daggerheart/templates/actionTypes/beastform.hbs' fields=fields.beastform.fields source=source.beastform}}{{/if}}
|
{{#if fields.beastform}}{{> 'systems/daggerheart/templates/actionTypes/beastform.hbs' fields=fields.beastform.fields source=source.beastform}}{{/if}}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue