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",
|
||||
"criticalThreshold": "Critical Threshold",
|
||||
"includeBase": { "label": "Include Item Damage" },
|
||||
"groupAttack": { "label": "Group Attack" },
|
||||
"multiplier": "Multiplier",
|
||||
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
||||
"resultBased": {
|
||||
|
|
@ -3142,7 +3143,8 @@
|
|||
"tokenActorsMissing": "[{names}] missing Actors",
|
||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
||||
"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": {
|
||||
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
},
|
||||
actions: {
|
||||
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||
updateGroupAttack: this.updateGroupAttack,
|
||||
toggleCritical: this.toggleCritical,
|
||||
submitRoll: this.submitRoll
|
||||
},
|
||||
|
|
@ -64,15 +65,40 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length);
|
||||
context.selectedEffects = this.selectedEffects;
|
||||
|
||||
context.damageOptions = this.config.damageOptions;
|
||||
context.rangeOptions = CONFIG.DH.GENERAL.groupAttackRange;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static updateRollConfiguration(_event, _, formData) {
|
||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||
foundry.utils.mergeObject(this.config.roll, rest.roll);
|
||||
foundry.utils.mergeObject(this.config.modifiers, rest.modifiers);
|
||||
this.config.selectedMessageMode = rest.selectedMessageMode;
|
||||
const data = foundry.utils.expandObject(formData.object);
|
||||
foundry.utils.mergeObject(this.config.roll, data.roll);
|
||||
foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
export const templateTypes = {
|
||||
CIRCLE: 'circle',
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
hasSave: this.hasSave,
|
||||
onSave: this.save?.damageMod,
|
||||
isDirect: !!this.damage?.direct,
|
||||
damageOptions: this.damage?.groupAttack ? {} : null,
|
||||
selectedMessageMode: game.settings.get('core', 'messageMode'),
|
||||
data: this.getRollData(),
|
||||
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);
|
||||
return config;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
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 })
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,7 +18,12 @@ export default class DamageField extends fields.SchemaField {
|
|||
initial: false,
|
||||
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);
|
||||
}
|
||||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ export default class DamageRoll extends DHRoll {
|
|||
constructFormula(config) {
|
||||
this.options.isCritical = config.isCritical;
|
||||
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.terms = Roll.parse(part.roll.formula, config.data);
|
||||
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);
|
||||
if (total > 0) {
|
||||
part.roll.terms.push(...this.formatModifier(total));
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
gap: 4px;
|
||||
.critical-chip {
|
||||
flex: 0;
|
||||
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -419,11 +419,19 @@
|
|||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.form-fields {
|
||||
height: 32px;
|
||||
align-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
&.select {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.scalable-input {
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@
|
|||
{{/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}}
|
||||
</legend>
|
||||
<div class="nest-inputs space-between">
|
||||
<div class="nest-inputs">
|
||||
{{#if @root.hasBaseDamage}}
|
||||
{{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase name="damage.includeBase" classes="checkbox" localize=true }}
|
||||
{{/if}}
|
||||
{{#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}}
|
||||
{{#if @root.isNPC}}
|
||||
{{formField baseFields.groupAttack value=source.groupAttack name=(concat path "damage.groupAttack") localize=true classes="select"}}
|
||||
{{/if}}
|
||||
</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 --}}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,24 @@
|
|||
</button>
|
||||
</div>
|
||||
{{/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)}}
|
||||
<fieldset class="modifier-container two-columns">
|
||||
<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.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.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}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue