mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 15:03:37 +02:00
Functioning setup
This commit is contained in:
parent
4b92001f97
commit
0cb7ede933
24 changed files with 350 additions and 72 deletions
|
|
@ -19,7 +19,8 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
|||
return context;
|
||||
}
|
||||
|
||||
static async addEffect(_event) {
|
||||
static async addEffect(event) {
|
||||
const { areaIndex } = event.target.dataset;
|
||||
if (!this.action.effects) return;
|
||||
const data = this.action.toObject();
|
||||
|
||||
|
|
@ -27,7 +28,10 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
|||
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
|
||||
]);
|
||||
|
||||
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.action.item.effects.get(created[0]._id).sheet.render(true);
|
||||
}
|
||||
|
|
@ -52,9 +56,20 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
|||
|
||||
static removeEffect(event, button) {
|
||||
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.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
else {
|
||||
effectId = this.action.effects[index]._id;
|
||||
this.constructor.removeElement.bind(this)(event, button);
|
||||
this.constructor.removeElement.bind(this)(event, button);
|
||||
}
|
||||
|
||||
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,4 +95,48 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
|||
});
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
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 */
|
||||
export const templateTypes = {
|
||||
CIRCLE: 'circle',
|
||||
CONE: 'cone',
|
||||
RECTANGLE: 'rectangle',
|
||||
LINE: 'line',
|
||||
EMANATION: 'emanation',
|
||||
INFRONT: 'inFront'
|
||||
circle: {
|
||||
id: 'circle',
|
||||
label: 'Circle'
|
||||
},
|
||||
cone: {
|
||||
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 = {
|
||||
|
|
@ -1092,3 +1110,18 @@ export const fallAndCollisionDamage = {
|
|||
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 items from './item/_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) {
|
||||
static extraSchemas = ['cost', 'uses', 'range'];
|
||||
static extraSchemas = ['area', 'cost', 'uses', 'range'];
|
||||
|
||||
/** @inheritDoc */
|
||||
static defineSchema() {
|
||||
|
|
|
|||
|
|
@ -93,7 +93,10 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
|||
max: new fields.NumberField({ integer: true, label: 'DAGGERHEART.GENERAL.max' })
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
)
|
||||
),
|
||||
targetDispositions: new fields.SetField(new fields.NumberField({
|
||||
choices: CONFIG.DH.GENERAL.simpleDispositions,
|
||||
}), { label: 'Affected Dispositions' }),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export { default as AreaField } from './areaField.mjs';
|
||||
export { default as CostField } from './costField.mjs';
|
||||
export { default as CountdownField } from './countdownField.mjs';
|
||||
export { default as UsesField } from './usesField.mjs';
|
||||
|
|
|
|||
35
module/data/fields/action/areaField.mjs
Normal file
35
module/data/fields/action/areaField.mjs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
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({
|
||||
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);
|
||||
}
|
||||
}
|
||||
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';
|
||||
38
module/data/regionBehavior/applyActiveEffect.mjs
Normal file
38
module/data/regionBehavior/applyActiveEffect.mjs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
export default class DhApplyActiveEffect extends CONFIG.RegionBehavior.dataModels.applyActiveEffect {
|
||||
static async #getApplicableEffects(token) {
|
||||
const effects = await Promise.all(this.effects.map(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,
|
||||
parent: chatData.parent,
|
||||
targetMode: chatData.targetMode,
|
||||
areas: chatData.action?.area,
|
||||
metagamingSettings
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,10 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
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 => {
|
||||
element.addEventListener('mouseenter', this.hoverTarget);
|
||||
element.addEventListener('mouseleave', this.unhoverTarget);
|
||||
|
|
@ -249,6 +253,73 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
this.system.action?.workflow.get('effects')?.execute(config, targets, true);
|
||||
}
|
||||
|
||||
async onCreateAreas(event) {
|
||||
let selectedArea = null;
|
||||
if (this.system.action.area.length === 1)
|
||||
selectedArea = 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,
|
||||
'.scene-environment',
|
||||
this.system.action.area.map((area, index) => ({
|
||||
name: index,
|
||||
callback: () => {
|
||||
if (scene.flags.daggerheart.sceneEnvironments[0] !== environment.uuid) {
|
||||
const newEnvironments = scene.flags.daggerheart.sceneEnvironments;
|
||||
const newFirst = newEnvironments.splice(
|
||||
newEnvironments.findIndex(x => x === environment.uuid),
|
||||
1
|
||||
)[0];
|
||||
newEnvironments.unshift(newFirst);
|
||||
emitAsGM(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
scene.update.bind(scene),
|
||||
{ 'flags.daggerheart.sceneEnvironments': newEnvironments },
|
||||
scene.uuid
|
||||
);
|
||||
}
|
||||
|
||||
environment.sheet.render({ force: true });
|
||||
}
|
||||
})),
|
||||
{
|
||||
jQuery: false,
|
||||
fixed: true
|
||||
}
|
||||
);
|
||||
|
||||
CONFIG.ux.ContextMenu.triggerContextMenu(event, '.scene-environment');
|
||||
|
||||
|
||||
}
|
||||
|
||||
if(!selectedArea) return;
|
||||
const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid);
|
||||
const { shape: type, size: range } = this.system.action.area[0];
|
||||
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range });
|
||||
|
||||
await canvas.regions.placeRegion(
|
||||
{
|
||||
name: 'Test',
|
||||
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 }
|
||||
);
|
||||
}
|
||||
|
||||
filterPermTargets(targets) {
|
||||
return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, 'update'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default function DhTemplateEnricher(match, _options) {
|
|||
)?.id
|
||||
: 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 rangeDisplay = Number.isNaN(Number(range))
|
||||
|
|
@ -49,8 +49,6 @@ export default function DhTemplateEnricher(match, _options) {
|
|||
}
|
||||
|
||||
export const renderMeasuredTemplate = async event => {
|
||||
const { LINE, RECTANGLE, INFRONT, CONE } = CONFIG.DH.GENERAL.templateTypes;
|
||||
|
||||
const button = event.currentTarget,
|
||||
type = button.dataset.type,
|
||||
range = button.dataset.range,
|
||||
|
|
@ -59,49 +57,16 @@ export const renderMeasuredTemplate = async event => {
|
|||
|
||||
if (!type || !range || !game.canvas.scene) return;
|
||||
|
||||
const usedType = type === 'inFront' ? 'cone' : type;
|
||||
const usedAngle =
|
||||
type === CONE ? (angle ?? CONFIG.MeasuredTemplate.defaults.angle) : type === INFRONT ? '180' : undefined;
|
||||
|
||||
let baseDistance = getTemplateDistance(range);
|
||||
|
||||
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
|
||||
};
|
||||
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({
|
||||
type,
|
||||
angle,
|
||||
range,
|
||||
direction,
|
||||
});
|
||||
|
||||
await canvas.regions.placeRegion(
|
||||
{
|
||||
name: usedType.capitalize(),
|
||||
name: type.capitalize(),
|
||||
shapes: [shapeData],
|
||||
restriction: { enabled: false, type: 'move', priority: 0 },
|
||||
behaviors: [],
|
||||
|
|
@ -112,12 +77,4 @@ export const renderMeasuredTemplate = async event => {
|
|||
},
|
||||
{ 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;
|
||||
};
|
||||
};
|
||||
|
|
@ -16,7 +16,8 @@ export default class RegisterHandlebarsHelpers {
|
|||
empty: this.empty,
|
||||
pluralize: this.pluralize,
|
||||
positive: this.positive,
|
||||
isNullish: this.isNullish
|
||||
isNullish: this.isNullish,
|
||||
debug: this.debug,
|
||||
});
|
||||
}
|
||||
static add(a, b) {
|
||||
|
|
@ -92,4 +93,9 @@ export default class RegisterHandlebarsHelpers {
|
|||
static isNullish(a) {
|
||||
return a === null || a === undefined;
|
||||
}
|
||||
|
||||
static debug(a) {
|
||||
console.log(a);
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export const preloadHandlebarsTemplates = async function () {
|
|||
'systems/daggerheart/templates/actionTypes/uses.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/roll.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/save.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/area.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/cost.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/range-target.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/effect.hbs',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue