diff --git a/lang/en.json b/lang/en.json index f0f71088..6bcbb654 100644 --- a/lang/en.json +++ b/lang/en.json @@ -660,6 +660,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/less/original/apps.less b/less/original/apps.less index d123b6ec..34a2d302 100644 --- a/less/original/apps.less +++ b/less/original/apps.less @@ -421,7 +421,7 @@ } .item-name { padding-left: 5px; - .modesto(); + //.modesto(); font-size: 16px; } } diff --git a/less/update/components/actor-global.less b/less/update/components/actor-global.less index 878877bd..81be9466 100644 --- a/less/update/components/actor-global.less +++ b/less/update/components/actor-global.less @@ -93,6 +93,11 @@ .charlevel { .russoOne(17px); text-align: right; + input { + display: inline-block; + width: 42px; + height: auto; + } } .experience { @@ -932,6 +937,9 @@ &>.panel { grid-template-rows: 32px 24px 24px auto; } + h3.power-dc { + line-height: 24px; + } .powercasting-ability { display: grid; grid-template-columns: 2fr 1fr 1fr; @@ -991,4 +999,56 @@ } } } + &.npc { + .swalt-sheet { + header { + h1.character-name { + align-self: auto; + } + .npc-size { + .russoOne(18px); + line-height: 28px; + } + .attributes { + grid-template-columns: repeat(3, 1fr); + footer { + &.proficiency { + margin-top: 0; + line-height: 24px; + text-align: center; + } + &.hit-points { + display: block; + } + } + } + } + nav.sheet-navigation { + grid-template-columns: repeat(4, 1fr); + } + .tab.attributes { + .traits-resources { + display: block; + + .counter { + display: flex; + .counter-value { + margin-left: auto; + } + } + // section.traits { + // display:block; + // } + } + } + .tab.powerbook { + input.powercasting-level { + width: 48px; + } + } + .tab.biography.active { + display: block; + } + } + } } \ No newline at end of file diff --git a/less/update/components/actor-themes.less b/less/update/components/actor-themes.less index aa1ed4d5..6e2a5f44 100644 --- a/less/update/components/actor-themes.less +++ b/less/update/components/actor-themes.less @@ -404,4 +404,13 @@ } } } + &.npc { + .swalt-sheet { + header { + .experience { + color: @actorProficiencyTextColor; + } + } + } + } } \ No newline at end of file diff --git a/module/actor/sheets/newSheet/npc.js b/module/actor/sheets/newSheet/npc.js new file mode 100644 index 00000000..450faabb --- /dev/null +++ b/module/actor/sheets/newSheet/npc.js @@ -0,0 +1,137 @@ +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, + width: 800, + tabs: [{ + navSelector: ".root-tabs", + contentSelector: ".sheet-body", + initial: "attributes" + }], + }); + } + + /* -------------------------------------------- */ + + /** + * 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-dark.css b/sw5e-dark.css index 1f896724..cd1e8030 100644 --- a/sw5e-dark.css +++ b/sw5e-dark.css @@ -767,3 +767,6 @@ body.dark-theme .sw5e.sheet.actor .swalt-sheet .tab.notes section > input { color: #E81111; border-bottom: 2px solid #0d99cc; } +body.dark-theme .sw5e.sheet.actor.npc .swalt-sheet header .experience { + color: #4f4f4f; +} diff --git a/sw5e-global.css b/sw5e-global.css index 32f78c35..585f880e 100644 --- a/sw5e-global.css +++ b/sw5e-global.css @@ -783,6 +783,11 @@ input[type="reset"]:disabled { letter-spacing: 0.5px; text-align: right; } +.sw5e.sheet.actor .swalt-sheet header .level-experience .charlevel input { + display: inline-block; + width: 42px; + height: auto; +} .sw5e.sheet.actor .swalt-sheet header .level-experience .experience { font-family: 'Russo One'; font-size: 17px; @@ -1507,6 +1512,9 @@ input[type="reset"]:disabled { .sw5e.sheet.actor .swalt-sheet .tab.powerbook > .panel { grid-template-rows: 32px 24px 24px auto; } +.sw5e.sheet.actor .swalt-sheet .tab.powerbook h3.power-dc { + line-height: 24px; +} .sw5e.sheet.actor .swalt-sheet .tab.powerbook .powercasting-ability { display: grid; grid-template-columns: 2fr 1fr 1fr; @@ -1567,6 +1575,45 @@ input[type="reset"]:disabled { .sw5e.sheet.actor .swalt-sheet.limited .tab.biography { grid-template-columns: 100%; } +.sw5e.sheet.actor.npc .swalt-sheet header h1.character-name { + align-self: auto; +} +.sw5e.sheet.actor.npc .swalt-sheet header .npc-size { + font-family: 'Russo One'; + font-size: 18px; + font-weight: 400; + letter-spacing: 0.5px; + line-height: 28px; +} +.sw5e.sheet.actor.npc .swalt-sheet header .attributes { + grid-template-columns: repeat(3, 1fr); +} +.sw5e.sheet.actor.npc .swalt-sheet header .attributes footer.proficiency { + margin-top: 0; + line-height: 24px; + text-align: center; +} +.sw5e.sheet.actor.npc .swalt-sheet header .attributes footer.hit-points { + display: block; +} +.sw5e.sheet.actor.npc .swalt-sheet nav.sheet-navigation { + grid-template-columns: repeat(4, 1fr); +} +.sw5e.sheet.actor.npc .swalt-sheet .tab.attributes .traits-resources { + display: block; +} +.sw5e.sheet.actor.npc .swalt-sheet .tab.attributes .traits-resources .counter { + display: flex; +} +.sw5e.sheet.actor.npc .swalt-sheet .tab.attributes .traits-resources .counter .counter-value { + margin-left: auto; +} +.sw5e.sheet.actor.npc .swalt-sheet .tab.powerbook input.powercasting-level { + width: 48px; +} +.sw5e.sheet.actor.npc .swalt-sheet .tab.biography.active { + display: block; +} @keyframes pause-spin { from { transform: rotate(0deg); diff --git a/sw5e-light.css b/sw5e-light.css index 3c53971b..121dfa83 100644 --- a/sw5e-light.css +++ b/sw5e-light.css @@ -767,3 +767,6 @@ body.light-theme .sw5e.sheet.actor .swalt-sheet .tab.notes section > input { color: #c40f0f; border-bottom: 2px solid #0d99cc; } +body.light-theme .sw5e.sheet.actor.npc .swalt-sheet header .experience { + color: #4f4f4f; +} diff --git a/sw5e.css b/sw5e.css index 426fe368..8328f03d 100644 --- a/sw5e.css +++ b/sw5e.css @@ -461,9 +461,7 @@ } .sw5e.sheet .items-list .items-header .item-name { padding-left: 5px; - font-family: 'Russo One'; - font-size: 14px; - font-weight: 400; + font-size: 16px; } .sw5e.sheet .items-list .item-name { flex: 2; @@ -1060,9 +1058,7 @@ overflow-y: auto; } .sw5e.sheet.item { - min-height: 400px; - max-height: 95%; - min-width: 480px; + min-height: 420px; /* ----------------------------------------- */ /* Sheet Header */ /* ----------------------------------------- */ @@ -1083,7 +1079,7 @@ border: 2px solid #000; } .sw5e.sheet.item .sheet-header .item-subtitle { - flex: 0 0 100px; + flex: 0 0 80px; height: 60px; margin: 0; padding: 5px; @@ -1538,3 +1534,30 @@ max-width: 40px; text-align: right; } +input[type="number"] { + width: calc(100% - 2px); + min-width: 20px; + height: 26px; + background: rgba(0, 0, 0, 0.05); + padding: 1px 3px; + margin: 0; + color: #191813; + font-family: inherit; + font-size: inherit; + text-align: inherit; + line-height: inherit; + border: 1px solid #7a7971; + border-radius: 3px; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + -moz-appearance: textfield; +} +input[type="number"]:focus { + box-shadow: 0 0 5px red; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; +} diff --git a/sw5e.js b/sw5e.js index 335acd28..ca288fe3 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"; @@ -52,6 +53,7 @@ Hooks.once("init", function() { ActorSheet5eCharacter, ActorSheet5eCharacterNew, ActorSheet5eNPC, + ActorSheet5eNPCNew, ActorSheet5eVehicle, ItemSheet5e, ShortRestDialog, @@ -101,11 +103,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..18689155 --- /dev/null +++ b/templates/actors/newActor/npc-sheet.html @@ -0,0 +1,189 @@ +
\ No newline at end of file