mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 23:13:39 +02:00
Merge c57266e596 into 3cbc18f42b
This commit is contained in:
commit
ecf0ec1d75
29 changed files with 416 additions and 95 deletions
|
|
@ -32,6 +32,11 @@ CONFIG.Dice.daggerheart = {
|
||||||
FateRoll: FateRoll
|
FateRoll: FateRoll
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CONFIG.RegionBehavior.dataModels = {
|
||||||
|
...CONFIG.RegionBehavior.dataModels,
|
||||||
|
...data.regionBehaviors
|
||||||
|
};
|
||||||
|
|
||||||
Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
|
Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
|
||||||
|
|
||||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,11 @@
|
||||||
"customFormula": "Custom Formula",
|
"customFormula": "Custom Formula",
|
||||||
"formula": "Formula"
|
"formula": "Formula"
|
||||||
},
|
},
|
||||||
|
"area": {
|
||||||
|
"sectionTitle": "Areas",
|
||||||
|
"shape": "Shape",
|
||||||
|
"size": "Size"
|
||||||
|
},
|
||||||
"displayInChat": "Display in chat",
|
"displayInChat": "Display in chat",
|
||||||
"deleteTriggerTitle": "Delete Trigger",
|
"deleteTriggerTitle": "Delete Trigger",
|
||||||
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?",
|
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?",
|
||||||
|
|
@ -2435,6 +2440,7 @@
|
||||||
"maxWithThing": "Max {thing}",
|
"maxWithThing": "Max {thing}",
|
||||||
"missingDragDropThing": "Drop {thing} here",
|
"missingDragDropThing": "Drop {thing} here",
|
||||||
"multiclass": "Multiclass",
|
"multiclass": "Multiclass",
|
||||||
|
"name": "Name",
|
||||||
"newCategory": "New Category",
|
"newCategory": "New Category",
|
||||||
"newThing": "New {thing}",
|
"newThing": "New {thing}",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,17 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addEffect(_event) {
|
static async addEffect(event) {
|
||||||
|
const { areaIndex } = event.target.dataset;
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
|
|
||||||
const created = await this.action.item.createEmbeddedDocuments('ActiveEffect', [
|
const created = await this.action.item.createEmbeddedDocuments('ActiveEffect', [
|
||||||
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
|
game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
data.effects.push({ _id: created[0]._id });
|
if (areaIndex !== undefined) data.area[areaIndex].effects.push(created[0]._id);
|
||||||
|
else data.effects.push({ _id: created[0]._id });
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
this.action.item.effects.get(created[0]._id).sheet.render(true);
|
this.action.item.effects.get(created[0]._id).sheet.render(true);
|
||||||
}
|
}
|
||||||
|
|
@ -52,9 +54,19 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
||||||
|
|
||||||
static removeEffect(event, button) {
|
static removeEffect(event, button) {
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const index = button.dataset.index,
|
|
||||||
|
const { areaIndex, index } = button.dataset;
|
||||||
|
let effectId = null;
|
||||||
|
if (areaIndex !== undefined) {
|
||||||
|
effectId = this.action.area[areaIndex].effects[index];
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.area[areaIndex].effects.splice(index, 1);
|
||||||
|
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
} else {
|
||||||
effectId = this.action.effects[index]._id;
|
effectId = this.action.effects[index]._id;
|
||||||
this.constructor.removeElement.bind(this)(event, button);
|
this.constructor.removeElement.call(this, event, button);
|
||||||
|
}
|
||||||
|
|
||||||
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
|
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,21 +31,35 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addEffect(_event) {
|
static async addEffect(_event) {
|
||||||
|
const { areaIndex } = event.target.dataset;
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject();
|
|
||||||
|
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false });
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
|
|
||||||
this.sheetUpdate(data, effectData);
|
this.sheetUpdate(data, effectData);
|
||||||
this.effects = [...this.effects, effectData];
|
this.effects = [...this.effects, effectData];
|
||||||
data.effects.push({ _id: effectData.id });
|
|
||||||
|
if (areaIndex !== undefined) data.area[areaIndex].effects.push(effectData.id);
|
||||||
|
else data.effects.push({ _id: effectData.id });
|
||||||
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeEffect(event, button) {
|
static removeEffect(event, button) {
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const index = button.dataset.index,
|
const { areaIndex, index } = button.dataset;
|
||||||
|
let effectId = null;
|
||||||
|
if (areaIndex !== undefined) {
|
||||||
|
effectId = this.action.area[areaIndex].effects[index];
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.area[areaIndex].effects.splice(index, 1);
|
||||||
|
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
} else {
|
||||||
effectId = this.action.effects[index]._id;
|
effectId = this.action.effects[index]._id;
|
||||||
this.constructor.removeElement.bind(this)(event, button);
|
this.constructor.removeElement.call(this, event, button);
|
||||||
|
}
|
||||||
|
|
||||||
this.sheetUpdate(
|
this.sheetUpdate(
|
||||||
this.action.toObject(),
|
this.action.toObject(),
|
||||||
this.effects.find(x => x.id === effectId),
|
this.effects.find(x => x.id === effectId),
|
||||||
|
|
|
||||||
|
|
@ -95,4 +95,53 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
||||||
});
|
});
|
||||||
return inBounds.length === 1 ? inBounds[0] : null;
|
return inBounds.length === 1 ? inBounds[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getTemplateShape({ type, angle, range, direction } = {}) {
|
||||||
|
const { line, rectangle, inFront, cone } = CONFIG.DH.GENERAL.templateTypes;
|
||||||
|
|
||||||
|
const usedAngle =
|
||||||
|
type === cone.id
|
||||||
|
? (angle ?? CONFIG.MeasuredTemplate.defaults.angle)
|
||||||
|
: type === inFront.id
|
||||||
|
? '180'
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const { grid, distance } = CONFIG.Scene.documentClass.schema.fields.grid.fields;
|
||||||
|
const sceneGridSize = canvas.scene?.grid.size ?? grid.size.initial;
|
||||||
|
const sceneGridDistance = canvas.scene?.grid.distance ?? distance.getInitialValue();
|
||||||
|
const dimensionConstant = sceneGridSize / sceneGridDistance;
|
||||||
|
|
||||||
|
const rangeNumber = Number(range);
|
||||||
|
const settings = canvas.scene?.rangeSettings;
|
||||||
|
const baseDistance =
|
||||||
|
(!Number.isNaN(rangeNumber) ? rangeNumber : settings ? settings[range] : 0) * dimensionConstant;
|
||||||
|
|
||||||
|
const length = baseDistance;
|
||||||
|
const radius = length;
|
||||||
|
|
||||||
|
const shapeWidth = type === line.id ? 5 * dimensionConstant : type === rectangle.id ? length : undefined;
|
||||||
|
const shapeType = type === inFront.id ? cone.id : type;
|
||||||
|
|
||||||
|
const { width, height } = game.canvas.scene.dimensions;
|
||||||
|
return {
|
||||||
|
x: width / 2,
|
||||||
|
y: height / 2,
|
||||||
|
base: {
|
||||||
|
type: 'token',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1
|
||||||
|
},
|
||||||
|
t: shapeType,
|
||||||
|
length: length,
|
||||||
|
width: shapeWidth,
|
||||||
|
height: length,
|
||||||
|
angle: usedAngle,
|
||||||
|
radius: radius,
|
||||||
|
direction: direction ?? 0,
|
||||||
|
type: shapeType
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,3 +115,10 @@ export const advantageState = {
|
||||||
value: 1
|
value: 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const areaTypes = {
|
||||||
|
placed: {
|
||||||
|
id: 'placed',
|
||||||
|
label: 'Placed Area'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -80,12 +80,30 @@ export const groupAttackRange = {
|
||||||
|
|
||||||
/* 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: {
|
||||||
CONE: 'cone',
|
id: 'circle',
|
||||||
RECTANGLE: 'rectangle',
|
label: 'Circle'
|
||||||
LINE: 'line',
|
},
|
||||||
EMANATION: 'emanation',
|
cone: {
|
||||||
INFRONT: 'inFront'
|
id: 'cone',
|
||||||
|
label: 'Cone'
|
||||||
|
},
|
||||||
|
rectangle: {
|
||||||
|
id: 'rectangle',
|
||||||
|
label: 'Rectangle'
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
id: 'line',
|
||||||
|
label: 'Line'
|
||||||
|
},
|
||||||
|
emanation: {
|
||||||
|
id: 'emanation',
|
||||||
|
label: 'Emanation'
|
||||||
|
},
|
||||||
|
inFront: {
|
||||||
|
id: 'inFront',
|
||||||
|
label: 'In Front'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const rangeInclusion = {
|
export const rangeInclusion = {
|
||||||
|
|
@ -1092,3 +1110,18 @@ export const fallAndCollisionDamage = {
|
||||||
damageFormula: '1d20 + 5'
|
damageFormula: '1d20 + 5'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const simpleDispositions = {
|
||||||
|
[-1]: {
|
||||||
|
id: -1,
|
||||||
|
label: 'TOKEN.DISPOSITION.HOSTILE'
|
||||||
|
},
|
||||||
|
[0]: {
|
||||||
|
id: 0,
|
||||||
|
label: 'TOKEN.DISPOSITION.NEUTRAL'
|
||||||
|
},
|
||||||
|
[1]: {
|
||||||
|
id: 1,
|
||||||
|
label: 'TOKEN.DISPOSITION.FRIENDLY'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,4 @@ export * as chatMessages from './chat-message/_modules.mjs';
|
||||||
export * as fields from './fields/_module.mjs';
|
export * as fields from './fields/_module.mjs';
|
||||||
export * as items from './item/_module.mjs';
|
export * as items from './item/_module.mjs';
|
||||||
export * as scenes from './scene/_module.mjs';
|
export * as scenes from './scene/_module.mjs';
|
||||||
|
export * as regionBehaviors from './regionBehavior/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ const fields = foundry.data.fields;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
||||||
static extraSchemas = ['cost', 'uses', 'range'];
|
static extraSchemas = ['area', 'cost', 'uses', 'range'];
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,12 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||||
max: new fields.NumberField({ integer: true, label: 'DAGGERHEART.GENERAL.max' })
|
max: new fields.NumberField({ integer: true, label: 'DAGGERHEART.GENERAL.max' })
|
||||||
},
|
},
|
||||||
{ nullable: true, initial: null }
|
{ nullable: true, initial: null }
|
||||||
|
),
|
||||||
|
targetDispositions: new fields.SetField(
|
||||||
|
new fields.NumberField({
|
||||||
|
choices: CONFIG.DH.GENERAL.simpleDispositions
|
||||||
|
}),
|
||||||
|
{ label: 'Affected Dispositions' }
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -131,13 +137,14 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||||
return armorChange.getArmorData();
|
return armorChange.getArmorData();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDefaultObject() {
|
static getDefaultObject(options = { transfer: true }) {
|
||||||
return {
|
return {
|
||||||
name: 'New Effect',
|
name: 'New Effect',
|
||||||
id: foundry.utils.randomID(),
|
id: foundry.utils.randomID(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
img: 'icons/magic/life/heart-cross-blue.webp',
|
img: 'icons/magic/life/heart-cross-blue.webp',
|
||||||
description: '',
|
description: '',
|
||||||
|
transfer: options.transfer,
|
||||||
statuses: [],
|
statuses: [],
|
||||||
changes: [],
|
changes: [],
|
||||||
system: {
|
system: {
|
||||||
|
|
|
||||||
|
|
@ -7,26 +7,31 @@ export default class DHAbilityUse extends foundry.abstract.TypeDataModel {
|
||||||
img: new fields.StringField({}),
|
img: new fields.StringField({}),
|
||||||
name: new fields.StringField({}),
|
name: new fields.StringField({}),
|
||||||
description: new fields.StringField({}),
|
description: new fields.StringField({}),
|
||||||
actions: new fields.ArrayField(
|
source: new fields.SchemaField({
|
||||||
new fields.ObjectField({
|
actor: new fields.StringField(),
|
||||||
name: new fields.StringField({}),
|
item: new fields.StringField(),
|
||||||
damage: new fields.SchemaField({
|
action: new fields.StringField()
|
||||||
type: new fields.StringField({}),
|
})
|
||||||
value: new fields.StringField({})
|
|
||||||
}),
|
|
||||||
healing: new fields.SchemaField({
|
|
||||||
type: new fields.StringField({}),
|
|
||||||
value: new fields.StringField({})
|
|
||||||
}),
|
|
||||||
cost: new fields.SchemaField({
|
|
||||||
type: new fields.StringField({}),
|
|
||||||
value: new fields.NumberField({})
|
|
||||||
}),
|
|
||||||
target: new fields.SchemaField({
|
|
||||||
type: new fields.StringField({ nullable: true })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const item = actionActor.items.get(this.source.item);
|
||||||
|
return item ? item.system.actionsList?.find(a => a.id === this.source.action) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get action() {
|
||||||
|
const { actionItem: itemAction } = this;
|
||||||
|
if (!this.source.action) return null;
|
||||||
|
if (itemAction) return itemAction;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
export { default as AreaField } from './areaField.mjs';
|
||||||
export { default as CostField } from './costField.mjs';
|
export { default as CostField } from './costField.mjs';
|
||||||
export { default as CountdownField } from './countdownField.mjs';
|
export { default as CountdownField } from './countdownField.mjs';
|
||||||
export { default as UsesField } from './usesField.mjs';
|
export { default as UsesField } from './usesField.mjs';
|
||||||
|
|
|
||||||
40
module/data/fields/action/areaField.mjs
Normal file
40
module/data/fields/action/areaField.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class AreaField extends fields.ArrayField {
|
||||||
|
/**
|
||||||
|
* Action Workflow order
|
||||||
|
*/
|
||||||
|
static order = 150;
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
constructor(options = {}, context = {}) {
|
||||||
|
const element = new fields.SchemaField({
|
||||||
|
name: new fields.StringField({
|
||||||
|
nullable: false,
|
||||||
|
initial: 'Area',
|
||||||
|
label: 'DAGGERHEART.GENERAL.name'
|
||||||
|
}),
|
||||||
|
type: new fields.StringField({
|
||||||
|
nullable: false,
|
||||||
|
choices: CONFIG.DH.ACTIONS.areaTypes,
|
||||||
|
initial: CONFIG.DH.ACTIONS.areaTypes.placed.id,
|
||||||
|
label: 'DAGGERHEART.GENERAL.type'
|
||||||
|
}),
|
||||||
|
shape: new fields.StringField({
|
||||||
|
nullable: false,
|
||||||
|
choices: CONFIG.DH.GENERAL.templateTypes,
|
||||||
|
initial: CONFIG.DH.GENERAL.templateTypes.circle.id,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Config.area.shape'
|
||||||
|
}),
|
||||||
|
/* Could be opened up to allow numbers to be input aswell. Probably best handled via an autocomplete in that case to allow the select options but also free text */
|
||||||
|
size: new fields.StringField({
|
||||||
|
nullable: false,
|
||||||
|
choices: CONFIG.DH.GENERAL.range,
|
||||||
|
initial: CONFIG.DH.GENERAL.range.veryClose.id,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Config.area.size'
|
||||||
|
}),
|
||||||
|
effects: new fields.ArrayField(new fields.DocumentIdField())
|
||||||
|
});
|
||||||
|
super(element, options, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -281,8 +281,14 @@ export function ActionMixin(Base) {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
img: this.baseAction ? this.parent.parent.img : this.img,
|
img: this.baseAction ? this.parent.parent.img : this.img,
|
||||||
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
||||||
|
area: this.area,
|
||||||
summon: this.summon
|
summon: this.summon
|
||||||
},
|
},
|
||||||
|
source: {
|
||||||
|
actor: this.actor.uuid,
|
||||||
|
item: this.item.id,
|
||||||
|
action: this.id
|
||||||
|
},
|
||||||
itemOrigin: this.item,
|
itemOrigin: this.item,
|
||||||
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
||||||
};
|
};
|
||||||
|
|
|
||||||
1
module/data/regionBehavior/_module.mjs
Normal file
1
module/data/regionBehavior/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as applyActiveEffect } from './applyActiveEffect.mjs';
|
||||||
40
module/data/regionBehavior/applyActiveEffect.mjs
Normal file
40
module/data/regionBehavior/applyActiveEffect.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
export default class DhApplyActiveEffect extends CONFIG.RegionBehavior.dataModels.applyActiveEffect {
|
||||||
|
static async #getApplicableEffects(token) {
|
||||||
|
const effects = await Promise.all(this.effects.map(foundry.utils.fromUuid));
|
||||||
|
return effects.filter(
|
||||||
|
effect => !effect.system.targetDispositions.size || effect.system.targetDispositions.has(token.disposition)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onTokenEnter(event) {
|
||||||
|
if (!event.user.isSelf) return;
|
||||||
|
const { token, movement } = event.data;
|
||||||
|
const actor = token.actor;
|
||||||
|
if (!actor) return;
|
||||||
|
const resumeMovement = movement ? token.pauseMovement() : undefined;
|
||||||
|
const effects = await DhApplyActiveEffect.#getApplicableEffects.bind(this)(event.data.token);
|
||||||
|
const toCreate = [];
|
||||||
|
for (const effect of effects) {
|
||||||
|
const data = effect.toObject();
|
||||||
|
delete data._id;
|
||||||
|
if (effect.compendium) {
|
||||||
|
data._stats.duplicateSource = null;
|
||||||
|
data._stats.compendiumSource = effect.uuid;
|
||||||
|
} else {
|
||||||
|
data._stats.duplicateSource = effect.uuid;
|
||||||
|
data._stats.compendiumSource = null;
|
||||||
|
}
|
||||||
|
data._stats.exportSource = null;
|
||||||
|
data.origin = this.parent.uuid;
|
||||||
|
toCreate.push(data);
|
||||||
|
}
|
||||||
|
if (toCreate.length) await actor.createEmbeddedDocuments('ActiveEffect', toCreate);
|
||||||
|
await resumeMovement?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static events = {
|
||||||
|
...CONFIG.RegionBehavior.dataModels.applyActiveEffect.events,
|
||||||
|
[CONST.REGION_EVENTS.TOKEN_ENTER]: this.#onTokenEnter
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -145,6 +145,7 @@ export default class DHRoll extends Roll {
|
||||||
roll: this,
|
roll: this,
|
||||||
parent: chatData.parent,
|
parent: chatData.parent,
|
||||||
targetMode: chatData.targetMode,
|
targetMode: chatData.targetMode,
|
||||||
|
areas: chatData.action?.area,
|
||||||
metagamingSettings
|
metagamingSettings
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,10 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
element.addEventListener('click', this.onApplyEffect.bind(this))
|
element.addEventListener('click', this.onApplyEffect.bind(this))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
html.querySelectorAll('.action-areas').forEach(element =>
|
||||||
|
element.addEventListener('click', this.onCreateAreas.bind(this))
|
||||||
|
);
|
||||||
|
|
||||||
html.querySelectorAll('.roll-target').forEach(element => {
|
html.querySelectorAll('.roll-target').forEach(element => {
|
||||||
element.addEventListener('mouseenter', this.hoverTarget);
|
element.addEventListener('mouseenter', this.hoverTarget);
|
||||||
element.addEventListener('mouseleave', this.unhoverTarget);
|
element.addEventListener('mouseleave', this.unhoverTarget);
|
||||||
|
|
@ -249,6 +253,55 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
this.system.action?.workflow.get('effects')?.execute(config, targets, true);
|
this.system.action?.workflow.get('effects')?.execute(config, targets, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onCreateAreas(event) {
|
||||||
|
const createArea = async selectedArea => {
|
||||||
|
const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid);
|
||||||
|
const { shape: type, size: range } = selectedArea;
|
||||||
|
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range });
|
||||||
|
|
||||||
|
await canvas.regions.placeRegion(
|
||||||
|
{
|
||||||
|
name: selectedArea.name,
|
||||||
|
shapes: [shapeData],
|
||||||
|
restriction: { enabled: false, type: 'move', priority: 0 },
|
||||||
|
behaviors: [
|
||||||
|
{
|
||||||
|
name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'),
|
||||||
|
type: 'applyActiveEffect',
|
||||||
|
system: {
|
||||||
|
effects: effects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
displayMeasurements: true,
|
||||||
|
locked: false,
|
||||||
|
ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE },
|
||||||
|
visibility: CONST.REGION_VISIBILITY.ALWAYS
|
||||||
|
},
|
||||||
|
{ create: true }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.system.action.area.length === 1) createArea(this.system.action.area[0]);
|
||||||
|
else if (this.system.action.area.length > 1) {
|
||||||
|
/* Pop a selection. Possibly a context menu? */
|
||||||
|
new foundry.applications.ux.ContextMenu.implementation(
|
||||||
|
event.target,
|
||||||
|
'.action-areas',
|
||||||
|
this.system.action.area.map((area, index) => ({
|
||||||
|
name: area.name,
|
||||||
|
callback: () => createArea(this.system.action.area[index])
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
jQuery: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
CONFIG.ux.ContextMenu.triggerContextMenu(event, '.action-areas');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
filterPermTargets(targets) {
|
filterPermTargets(targets) {
|
||||||
return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, 'update'));
|
return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, 'update'));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export default function DhTemplateEnricher(match, _options) {
|
||||||
)?.id
|
)?.id
|
||||||
: params.range;
|
: params.range;
|
||||||
|
|
||||||
if (!Object.values(CONFIG.DH.GENERAL.templateTypes).find(x => x === type) || !range) return match[0];
|
if (!CONFIG.DH.GENERAL.templateTypes[type] || !range) return match[0];
|
||||||
|
|
||||||
const label = game.i18n.localize(`DAGGERHEART.CONFIG.TemplateTypes.${type}`);
|
const label = game.i18n.localize(`DAGGERHEART.CONFIG.TemplateTypes.${type}`);
|
||||||
const rangeDisplay = Number.isNaN(Number(range))
|
const rangeDisplay = Number.isNaN(Number(range))
|
||||||
|
|
@ -49,8 +49,6 @@ export default function DhTemplateEnricher(match, _options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renderMeasuredTemplate = async event => {
|
export const renderMeasuredTemplate = async event => {
|
||||||
const { LINE, RECTANGLE, INFRONT, CONE } = CONFIG.DH.GENERAL.templateTypes;
|
|
||||||
|
|
||||||
const button = event.currentTarget,
|
const button = event.currentTarget,
|
||||||
type = button.dataset.type,
|
type = button.dataset.type,
|
||||||
range = button.dataset.range,
|
range = button.dataset.range,
|
||||||
|
|
@ -59,49 +57,16 @@ export const renderMeasuredTemplate = async event => {
|
||||||
|
|
||||||
if (!type || !range || !game.canvas.scene) return;
|
if (!type || !range || !game.canvas.scene) return;
|
||||||
|
|
||||||
const usedType = type === 'inFront' ? 'cone' : type;
|
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({
|
||||||
const usedAngle =
|
type,
|
||||||
type === CONE ? (angle ?? CONFIG.MeasuredTemplate.defaults.angle) : type === INFRONT ? '180' : undefined;
|
angle,
|
||||||
|
range,
|
||||||
let baseDistance = getTemplateDistance(range);
|
direction
|
||||||
|
});
|
||||||
const { grid, distance } = CONFIG.Scene.documentClass.schema.fields.grid.fields;
|
|
||||||
const sceneGridSize = canvas.scene?.grid.size ?? grid.size.initial;
|
|
||||||
const sceneGridDistance = canvas.scene?.grid.distance ?? distance.getInitialValue();
|
|
||||||
const dimensionConstant = sceneGridSize / sceneGridDistance;
|
|
||||||
|
|
||||||
baseDistance *= dimensionConstant;
|
|
||||||
|
|
||||||
const length = baseDistance;
|
|
||||||
const radius = length;
|
|
||||||
|
|
||||||
const shapeWidth = type === LINE ? 5 * dimensionConstant : type === RECTANGLE ? length : undefined;
|
|
||||||
|
|
||||||
const { width, height } = game.canvas.scene.dimensions;
|
|
||||||
const shapeData = {
|
|
||||||
x: width / 2,
|
|
||||||
y: height / 2,
|
|
||||||
base: {
|
|
||||||
type: 'token',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1
|
|
||||||
},
|
|
||||||
t: usedType,
|
|
||||||
length: length,
|
|
||||||
width: shapeWidth,
|
|
||||||
height: length,
|
|
||||||
angle: usedAngle,
|
|
||||||
radius: radius,
|
|
||||||
direction: direction,
|
|
||||||
type: usedType
|
|
||||||
};
|
|
||||||
|
|
||||||
await canvas.regions.placeRegion(
|
await canvas.regions.placeRegion(
|
||||||
{
|
{
|
||||||
name: usedType.capitalize(),
|
name: type.capitalize(),
|
||||||
shapes: [shapeData],
|
shapes: [shapeData],
|
||||||
restriction: { enabled: false, type: 'move', priority: 0 },
|
restriction: { enabled: false, type: 'move', priority: 0 },
|
||||||
behaviors: [],
|
behaviors: [],
|
||||||
|
|
@ -113,11 +78,3 @@ export const renderMeasuredTemplate = async event => {
|
||||||
{ create: true }
|
{ create: true }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTemplateDistance = range => {
|
|
||||||
const rangeNumber = Number(range);
|
|
||||||
if (!Number.isNaN(rangeNumber)) return rangeNumber;
|
|
||||||
|
|
||||||
const settings = canvas.scene?.rangeSettings;
|
|
||||||
return settings ? settings[range] : 0;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ export const preloadHandlebarsTemplates = async function () {
|
||||||
'systems/daggerheart/templates/actionTypes/uses.hbs',
|
'systems/daggerheart/templates/actionTypes/uses.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/roll.hbs',
|
'systems/daggerheart/templates/actionTypes/roll.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/save.hbs',
|
'systems/daggerheart/templates/actionTypes/save.hbs',
|
||||||
|
'systems/daggerheart/templates/actionTypes/area.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/cost.hbs',
|
'systems/daggerheart/templates/actionTypes/cost.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/range-target.hbs',
|
'systems/daggerheart/templates/actionTypes/range-target.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/effect.hbs',
|
'systems/daggerheart/templates/actionTypes/effect.hbs',
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,15 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
|
||||||
|
&.bordered {
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
.inventory-item-header .img-portait img.item-img {
|
||||||
|
border-radius: 6px 0 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:not(.single-img) {
|
&:not(.single-img) {
|
||||||
.img-portait:has(.roll-img):hover {
|
.img-portait:has(.roll-img):hover {
|
||||||
.roll-img {
|
.roll-img {
|
||||||
|
|
|
||||||
|
|
@ -147,4 +147,10 @@
|
||||||
padding-bottom: 7px;
|
padding-bottom: 7px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sub-section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -624,9 +624,15 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
|
&.end-button {
|
||||||
|
margin-left: auto;
|
||||||
|
flex: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -698,6 +704,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-roll-buttons {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
|
||||||
|
|
|
||||||
46
templates/actionTypes/area.hbs
Normal file
46
templates/actionTypes/area.hbs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<fieldset class="one-column" data-key="area">
|
||||||
|
<legend>
|
||||||
|
{{localize "DAGGERHEART.ACTIONS.Config.area.sectionTitle"}}
|
||||||
|
<a><i class="fa-solid fa-plus icon-button" data-action="addElement"></i></a>
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
{{#each source as |area index|}}
|
||||||
|
{{#unless @first}}<line-div></line-div>{{/unless}}
|
||||||
|
<div class="nest-inputs">
|
||||||
|
{{formField ../fields.name value=area.name name=(concat "area." index ".name") localize=true}}
|
||||||
|
<a class="btn" data-tooltip="{{localize "CONTROLS.CommonDelete"}}" data-action="removeElement" data-index="{{index}}"><i class="fas fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
<div class="nest-inputs">
|
||||||
|
{{formField ../fields.type value=area.type name=(concat "area." index ".type") localize=true}}
|
||||||
|
{{formField ../fields.shape value=area.shape name=(concat "area." index ".shape") localize=true}}
|
||||||
|
{{formField ../fields.size value=area.size name=(concat "area." index ".size") localize=true}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sub-section-header">
|
||||||
|
<span>{{localize "DAGGERHEART.GENERAL.Effect.plural"}}</span>
|
||||||
|
<a><i class="fa-solid fa-plus icon-button" data-action="addEffect" data-area-index="{{index}}"></i></a>
|
||||||
|
</div>
|
||||||
|
<div class="two-columns even" style="width: 100%;">
|
||||||
|
{{#each area.effects as |effectId index|}}
|
||||||
|
<input type="hidden" name={{concat "area." @../key ".effects." index}} value="{{effectId}}">
|
||||||
|
|
||||||
|
<div class="inventory-item single-img bordered" data-effect-id="{{effectId}}" data-area-index="{{index}}" data-action="editEffect">
|
||||||
|
<div class="inventory-item-header">
|
||||||
|
{{#with (@root.getEffectDetails effectId) as | details |}}
|
||||||
|
<div class="img-portait">
|
||||||
|
<img class="item-img" src="{{img}}">
|
||||||
|
</div>
|
||||||
|
<div class="item-label">
|
||||||
|
<div class="item-name">{{name}}</div>
|
||||||
|
</div>
|
||||||
|
{{/with}}
|
||||||
|
<div class="controls">
|
||||||
|
<a data-tooltip="{{localize "CONTROLS.CommonDelete"}}" data-action="removeEffect" data-area-index="{{@../key}}" data-index="{{index}}"><i class="fas fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
<fieldset class="one-column">
|
<fieldset class="one-column">
|
||||||
<legend>{{localize "DAGGERHEART.GENERAL.range"}}{{#if fields.target}} & {{localize "DAGGERHEART.GENERAL.Target.single"}}{{/if}}</legend>
|
<legend>{{localize "DAGGERHEART.GENERAL.range"}}{{#if fields.target}} & {{localize "DAGGERHEART.GENERAL.Target.single"}}{{/if}}</legend>
|
||||||
{{formField fields.range value=source.range label=(localize "DAGGERHEART.GENERAL.range") name=(concat path "range") localize=true}}
|
|
||||||
{{#if fields.target}}
|
{{#if fields.target}}
|
||||||
<div class="nest-inputs">
|
<div class="nest-inputs">
|
||||||
{{#if (and source.target.type (not (eq source.target.type 'self')))}}
|
{{formField fields.range value=source.range label=(localize "DAGGERHEART.GENERAL.range") name=(concat path "range") localize=true}}
|
||||||
{{ formField fields.target.amount value=source.target.amount label=(localize "DAGGERHEART.GENERAL.amount") name=(concat path "target.amount") localize=true}}
|
{{#if (and source.target.type (not (eq source.target.type 'self')))}}
|
||||||
{{/if}}
|
{{ formField fields.target.amount value=source.target.amount label=(localize "DAGGERHEART.GENERAL.amount") name=(concat path "target.amount") localize=true}}
|
||||||
{{ formField fields.target.type value=source.target.type label=(localize "DAGGERHEART.GENERAL.Target.single") name=(concat path "target.type") localize=true }}
|
{{/if}}
|
||||||
</div>
|
{{ formField fields.target.type value=source.target.type label=(localize "DAGGERHEART.GENERAL.Target.single") name=(concat path "target.type") localize=true }}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{formField fields.range value=source.range label=(localize "DAGGERHEART.GENERAL.range") name=(concat path "range") localize=true}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
@ -6,4 +6,5 @@
|
||||||
{{> 'systems/daggerheart/templates/actionTypes/uses.hbs' fields=fields.uses.fields source=source.uses}}
|
{{> 'systems/daggerheart/templates/actionTypes/uses.hbs' fields=fields.uses.fields source=source.uses}}
|
||||||
{{> 'systems/daggerheart/templates/actionTypes/cost.hbs' fields=fields.cost.element.fields source=source.cost costOptions=costOptions}}
|
{{> 'systems/daggerheart/templates/actionTypes/cost.hbs' fields=fields.cost.element.fields source=source.cost costOptions=costOptions}}
|
||||||
{{> 'systems/daggerheart/templates/actionTypes/range-target.hbs' fields=(object range=fields.range target=fields.target.fields) source=(object target=source.target range=source.range)}}
|
{{> 'systems/daggerheart/templates/actionTypes/range-target.hbs' fields=(object range=fields.range target=fields.target.fields) source=(object target=source.target range=source.range)}}
|
||||||
|
{{> 'systems/daggerheart/templates/actionTypes/area.hbs' fields=fields.area.element.fields source=source.area}}
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
{{formGroup fields.tint value=source.tint rootId=rootId placeholder="#ffffff"}}
|
{{formGroup fields.tint value=source.tint rootId=rootId placeholder="#ffffff"}}
|
||||||
{{formGroup fields.description value=source.description rootId=rootId}}
|
{{formGroup fields.description value=source.description rootId=rootId}}
|
||||||
{{formGroup fields.disabled value=source.disabled rootId=rootId}}
|
{{formGroup fields.disabled value=source.disabled rootId=rootId}}
|
||||||
|
{{formGroup systemFields.targetDispositions value=source.system.targetDispositions name=(concat "system.targetDispositions") localize=true}}
|
||||||
|
|
||||||
{{#if isActorEffect}}
|
{{#if isActorEffect}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,9 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
{{#if action.area.length}}
|
||||||
|
<div class="roll-buttons action-roll-buttons">
|
||||||
|
<button class="action-areas end-button"><i class="fa-solid fa-crosshairs"></i></button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -14,4 +14,5 @@
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (and hasEffect)}}<button class="duality-action-effect">{{localize "DAGGERHEART.UI.Chat.attackRoll.applyEffect"}}</button>{{/if}}
|
{{#if (and hasEffect)}}<button class="duality-action-effect">{{localize "DAGGERHEART.UI.Chat.attackRoll.applyEffect"}}</button>{{/if}}
|
||||||
|
{{#if areas.length}}<button class="action-areas end-button"><i class="fa-solid fa-crosshairs"></i></button>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue