diff --git a/lang/en.json b/lang/en.json index 7ceef2e9..2a55955f 100644 --- a/lang/en.json +++ b/lang/en.json @@ -585,6 +585,7 @@ "SW5E.SheetClassCharacter": "Default Character Sheet", "SW5E.SheetClassCharacterOld": "Old Character Sheet", "SW5E.SheetClassNPC": "Default NPC Sheet", +"SW5E.SheetClassNPCOld": "Old NPC Sheet", "SW5E.SheetClassVehicle": "Default Vehicle Sheet", "SW5E.SheetClassItem": "Default Item Sheet", diff --git a/module/actor/sheets/newSheet/npc.js b/module/actor/sheets/newSheet/npc.js new file mode 100644 index 00000000..42d63fe8 --- /dev/null +++ b/module/actor/sheets/newSheet/npc.js @@ -0,0 +1,132 @@ +import ActorSheet5e from "../base.js"; + +/** + * An Actor sheet for NPC type characters in the SW5E system. + * Extends the base ActorSheet5e class. + * @extends {ActorSheet5e} + */ +export default class ActorSheet5eNPCNew extends ActorSheet5e { + + /** @override */ + get template() { + if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/newActor/limited-sheet.html"; + return `systems/sw5e/templates/actors/newActor/npc-sheet.html`; + } + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ["sw5e", "sheet", "actor", "npc"], + width: 600, + height: 680 + }); + } + + /* -------------------------------------------- */ + + /** + * Organize Owned Items for rendering the NPC sheet + * @private + */ + _prepareItems(data) { + + // Categorize Items as Features and Powers + const features = { + weapons: { label: game.i18n.localize("SW5E.AttackPl"), items: [] , hasActions: true, dataset: {type: "weapon", "weapon-type": "natural"} }, + actions: { label: game.i18n.localize("SW5E.ActionPl"), items: [] , hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, + passive: { label: game.i18n.localize("SW5E.Features"), items: [], dataset: {type: "feat"} }, + equipment: { label: game.i18n.localize("SW5E.Inventory"), items: [], dataset: {type: "loot"}} + }; + + // Start by classifying items into groups for rendering + let [powers, other] = data.items.reduce((arr, item) => { + item.img = item.img || DEFAULT_TOKEN; + item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1); + item.hasUses = item.data.uses && (item.data.uses.max > 0); + item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false); + item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); + item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type)); + if ( item.type === "power" ) arr[0].push(item); + else arr[1].push(item); + return arr; + }, [[], []]); + + // Apply item filters + powers = this._filterItems(powers, this._filters.powerbook); + other = this._filterItems(other, this._filters.features); + + // Organize Powerbook + const powerbook = this._preparePowerbook(data, powers); + + // Organize Features + for ( let item of other ) { + if ( item.type === "weapon" ) features.weapons.items.push(item); + else if ( item.type === "feat" ) { + if ( item.data.activation.type ) features.actions.items.push(item); + else features.passive.items.push(item); + } + else features.equipment.items.push(item); + } + + // Assign and return + data.features = Object.values(features); + data.powerbook = powerbook; + } + + + /* -------------------------------------------- */ + + /** @override */ + getData() { + const data = super.getData(); + + // Challenge Rating + const cr = parseFloat(data.data.details.cr || 0); + const crLabels = {0: "0", 0.125: "1/8", 0.25: "1/4", 0.5: "1/2"}; + data.labels["cr"] = cr >= 1 ? String(cr) : crLabels[cr] || 1; + return data; + } + + /* -------------------------------------------- */ + /* Object Updates */ + /* -------------------------------------------- */ + + /** @override */ + _updateObject(event, formData) { + + // Format NPC Challenge Rating + const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5}; + let crv = "data.details.cr"; + let cr = formData[crv]; + cr = crs[cr] || parseFloat(cr); + if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr); + + // Parent ActorSheet update steps + super._updateObject(event, formData); + } + + /* -------------------------------------------- */ + /* Event Listeners and Handlers */ + /* -------------------------------------------- */ + + /** @override */ + activateListeners(html) { + super.activateListeners(html); + html.find(".health .rollable").click(this._onRollHealthFormula.bind(this)); + } + + /* -------------------------------------------- */ + + /** + * Handle rolling NPC health values using the provided formula + * @param {Event} event The original click event + * @private + */ + _onRollHealthFormula(event) { + event.preventDefault(); + const formula = this.actor.data.data.attributes.hp.formula; + if ( !formula ) return; + const hp = new Roll(formula).roll().total; + AudioHelper.play({src: CONFIG.sounds.dice}); + this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp}); + } +} diff --git a/sw5e.js b/sw5e.js index 8e17c450..452784bb 100644 --- a/sw5e.js +++ b/sw5e.js @@ -26,6 +26,7 @@ import ActorSheet5eCharacter from "./module/actor/sheets/oldSheets/character.js" import ActorSheet5eNPC from "./module/actor/sheets/oldSheets/npc.js"; import ActorSheet5eVehicle from "./module/actor/sheets/oldSheets/vehicle.js"; import ActorSheet5eCharacterNew from "./module/actor/sheets/newSheet/character.js"; +import ActorSheet5eNPCNew from "./module/actor/sheets/newSheet/npc.js"; import ItemSheet5e from "./module/item/sheet.js"; import ShortRestDialog from "./module/apps/short-rest.js"; import TraitSelector from "./module/apps/trait-selector.js"; @@ -51,6 +52,7 @@ Hooks.once("init", function() { ActorSheet5eCharacter, ActorSheet5eCharacterNew, ActorSheet5eNPC, + ActorSheet5eNPCNew, ActorSheet5eVehicle, ItemSheet5e, ShortRestDialog, @@ -99,11 +101,16 @@ Hooks.once("init", function() { makeDefault: false, label: "SW5E.SheetClassCharacterOld" }); - Actors.registerSheet("sw5e", ActorSheet5eNPC, { + Actors.registerSheet("sw5e", ActorSheet5eNPCNew, { types: ["npc"], makeDefault: true, label: "SW5E.SheetClassNPC" }); + Actors.registerSheet("sw5e", ActorSheet5eNPC, { + types: ["npc"], + makeDefault: false, + label: "SW5E.SheetClassNPCOld" + }); Actors.registerSheet('sw5e', ActorSheet5eVehicle, { types: ['vehicle'], makeDefault: true, diff --git a/templates/actors/newActor/npc-sheet.html b/templates/actors/newActor/npc-sheet.html new file mode 100644 index 00000000..05672ee6 --- /dev/null +++ b/templates/actors/newActor/npc-sheet.html @@ -0,0 +1,182 @@ +
\ No newline at end of file diff --git a/templates/actors/oldActor/npc-sheet.html b/templates/actors/oldActor/npc-sheet.html index 6644268e..3e1fd946 100644 --- a/templates/actors/oldActor/npc-sheet.html +++ b/templates/actors/oldActor/npc-sheet.html @@ -149,18 +149,18 @@ {{!-- Traits --}} - {{> "systems/sw5e/templates/actors/parts/actor-traits.html"}} + {{> "systems/sw5e/templates/actors/oldActor/parts/actor-traits.html"}} {{!-- Features Tab --}}