forked from GitHub-Mirrors/foundry-sw5e

Things unfinished: - Migration - The update adds new sections to the class sheet to allow some light customisation, this hasn't been included, but could be extended for the sake of dynamic classes with automatic class features and more - The French - The packs have not yet been updated, meaning due to the addition of a progression field to the class item, classes now don't set force or tech points - I updated the function calls in starships, but I didn't update it very thoroughly, it'll need checking - I only did a little testing - There has since been updates to DND5e that hasn't made it to release that patch bugs, those should be implemented Things changed from base 5e: - Short rests and long rests were merged into one function, this needed some rewrites to account for force and tech points, and for printing the correct message Extra Comments: - Unfinished code exists for automatic spell scrolls, this could be extended for single use force or tech powers - Weapon proficiencies probably need revising - Elven accuracy, halfling lucky, and reliable talent are present in the roll logic, this probably needs revising for sw5e - SW5e has a variant rule that permits force powers of any alignment to use either charisma or wisdom, that could be implemented - SW5e's version of gritty realism, [Longer Rests](https://sw5e.com/rules/variantRules/Longer%20Rests) differs from base dnd, this could be implemented - Extra ideas I've had while looking through the code can be found in Todos next to the ideas relevant context
134 lines
4.4 KiB
JavaScript
134 lines
4.4 KiB
JavaScript
import { SW5E } from "../config.js";
|
|
|
|
/**
|
|
* A helper class for building MeasuredTemplates for 5e powers and abilities
|
|
* @extends {MeasuredTemplate}
|
|
*/
|
|
export default class AbilityTemplate extends MeasuredTemplate {
|
|
|
|
/**
|
|
* A factory method to create an AbilityTemplate instance using provided data from an Item5e instance
|
|
* @param {Item5e} item The Item object for which to construct the template
|
|
* @return {AbilityTemplate|null} The template object, or null if the item does not produce a template
|
|
*/
|
|
static fromItem(item) {
|
|
const target = getProperty(item.data, "data.target") || {};
|
|
const templateShape = SW5E.areaTargetTypes[target.type];
|
|
if ( !templateShape ) return null;
|
|
|
|
// Prepare template data
|
|
const templateData = {
|
|
t: templateShape,
|
|
user: game.user.data._id,
|
|
distance: target.value,
|
|
direction: 0,
|
|
x: 0,
|
|
y: 0,
|
|
fillColor: game.user.color
|
|
};
|
|
|
|
// Additional type-specific data
|
|
switch ( templateShape ) {
|
|
case "cone":
|
|
templateData.angle = CONFIG.MeasuredTemplate.defaults.angle;
|
|
break;
|
|
case "rect": // 5e rectangular AoEs are always cubes
|
|
templateData.distance = Math.hypot(target.value, target.value);
|
|
templateData.width = target.value;
|
|
templateData.direction = 45;
|
|
break;
|
|
case "ray": // 5e rays are most commonly 1 square (5 ft) in width
|
|
templateData.width = target.width ?? canvas.dimensions.distance;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Return the template constructed from the item data
|
|
const cls = CONFIG.MeasuredTemplate.documentClass;
|
|
const template = new cls(templateData, {parent: canvas.scene});
|
|
const object = new this(template);
|
|
object.item = item;
|
|
object.actorSheet = item.actor?.sheet || null;
|
|
return object;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Creates a preview of the power template
|
|
*/
|
|
drawPreview() {
|
|
const initialLayer = canvas.activeLayer;
|
|
|
|
// Draw the template and switch to the template layer
|
|
this.draw();
|
|
this.layer.activate();
|
|
this.layer.preview.addChild(this);
|
|
|
|
// Hide the sheet that originated the preview
|
|
if ( this.actorSheet ) this.actorSheet.minimize();
|
|
|
|
// Activate interactivity
|
|
this.activatePreviewListeners(initialLayer);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Activate listeners for the template preview
|
|
* @param {CanvasLayer} initialLayer The initially active CanvasLayer to re-activate after the workflow is complete
|
|
*/
|
|
activatePreviewListeners(initialLayer) {
|
|
const handlers = {};
|
|
let moveTime = 0;
|
|
|
|
// Update placement (mouse-move)
|
|
handlers.mm = event => {
|
|
event.stopPropagation();
|
|
let now = Date.now(); // Apply a 20ms throttle
|
|
if ( now - moveTime <= 20 ) return;
|
|
const center = event.data.getLocalPosition(this.layer);
|
|
const snapped = canvas.grid.getSnappedPosition(center.x, center.y, 2);
|
|
this.data.x = snapped.x;
|
|
this.data.y = snapped.y;
|
|
this.refresh();
|
|
moveTime = now;
|
|
};
|
|
|
|
// Cancel the workflow (right-click)
|
|
handlers.rc = event => {
|
|
this.layer.preview.removeChildren();
|
|
canvas.stage.off("mousemove", handlers.mm);
|
|
canvas.stage.off("mousedown", handlers.lc);
|
|
canvas.app.view.oncontextmenu = null;
|
|
canvas.app.view.onwheel = null;
|
|
initialLayer.activate();
|
|
this.actorSheet.maximize();
|
|
};
|
|
|
|
// Confirm the workflow (left-click)
|
|
handlers.lc = event => {
|
|
handlers.rc(event);
|
|
const destination = canvas.grid.getSnappedPosition(this.data.x, this.data.y, 2);
|
|
this.data.update(destination);
|
|
canvas.scene.createEmbeddedDocuments("MeasuredTemplate", [this.data]);
|
|
};
|
|
|
|
// Rotate the template by 3 degree increments (mouse-wheel)
|
|
handlers.mw = event => {
|
|
if ( event.ctrlKey ) event.preventDefault(); // Avoid zooming the browser window
|
|
event.stopPropagation();
|
|
let delta = canvas.grid.type > CONST.GRID_TYPES.SQUARE ? 30 : 15;
|
|
let snap = event.shiftKey ? delta : 5;
|
|
this.data.update({direction: this.data.direction + (snap * Math.sign(event.deltaY))});
|
|
this.refresh();
|
|
};
|
|
|
|
// Activate listeners
|
|
canvas.stage.on("mousemove", handlers.mm);
|
|
canvas.stage.on("mousedown", handlers.lc);
|
|
canvas.app.view.oncontextmenu = handlers.rc;
|
|
canvas.app.view.onwheel = handlers.mw;
|
|
}
|
|
}
|