From 1acd7a38dac9fadefce366972a5afc5f4c3a88cd Mon Sep 17 00:00:00 2001 From: BluSunrize <4106382+BluSunrize@users.noreply.github.com> Date: Mon, 13 Jun 2022 22:58:42 +0200 Subject: [PATCH] Add Homestead sheets (#1064) I found that I wanted my PCs to have a base where they can store equipment so they don't carry everything on their sheets. That in turn reminded me that SWRPG added "Homesteads" as a concept in the Far Horizons splatbook and so I got to work implementing this sheet as a match for that. It should also serve well for military bases, palaces, apartments, or whatever else the PCs base themselves out of. I'd love to see this accepted, because along with this I'm hoping to bring automatic encumbrance calculation back in, and this gives characters a place to offload their gear. **Notes on the PR:** - I intentionally didn't squash everything yet, but feel free to squash merge. I wanted the dev-process of the homestead sheet to become apparent and allow for easier feedback - I opted to make the homestead's storage and upgrades into two separate files in `actor/parts`. I feel like `ffg-weapon-armor-gear` is already very crowded with both character and vehicle stuff in there and I didn't want to bloat it further. - I've been testing this code without issue in my personal game. Consider that an integration test, since we can't really do automated testing for this kind of stuff. --- lang/en.json | 18 +- modules/actors/actor-ffg.js | 2 +- modules/actors/actor-sheet-ffg.js | 2 +- modules/config/ffg-sheetdefaults.js | 2 + modules/helpers/actor-helpers.js | 241 +++++++++--------- modules/helpers/partial-templates.js | 17 +- modules/items/item-sheet-ffg.js | 1 + modules/swffg-main.js | 2 +- scss/components/_homesteadsheet.scss | 26 ++ scss/starwarsffg.scss | 1 + styles/starwarsffg.css | 14 + template.json | 19 +- templates/actors/ffg-homestead-sheet.html | 42 +++ .../items/ffg-homesteadupgrade-sheet.html | 32 +++ .../parts/actor/ffg-homestead-storage.html | 32 +++ .../parts/actor/ffg-homestead-upgrades.html | 26 ++ 16 files changed, 345 insertions(+), 132 deletions(-) create mode 100644 scss/components/_homesteadsheet.scss create mode 100644 templates/actors/ffg-homestead-sheet.html create mode 100644 templates/items/ffg-homesteadupgrade-sheet.html create mode 100644 templates/parts/actor/ffg-homestead-storage.html create mode 100644 templates/parts/actor/ffg-homestead-upgrades.html diff --git a/lang/en.json b/lang/en.json index fa34df5d..9d4093cc 100644 --- a/lang/en.json +++ b/lang/en.json @@ -2,6 +2,7 @@ "ACTOR.TypeCharacter": "Character", "ACTOR.TypeMinion": "Minion", "ACTOR.TypeVehicle": "Vehicle", + "ACTOR.TypeHomestead": "Homestead", "ITEM.TypeArmour": "Armour", "ITEM.TypeCareer": "Career", @@ -9,15 +10,16 @@ "ITEM.TypeCriticalinjury": "Critical Injury", "ITEM.TypeForcepower": "Force Power", "ITEM.TypeGear": "Gear", + "ITEM.TypeHomesteadupgrade": "Homestead Upgrade", "ITEM.TypeItemattachment": "Item Attachment", "ITEM.TypeItemmodifier": "Item Modifier", "ITEM.TypeTalent": "Talent", - "ITEM.TypeShipattachment": "Ship Attachment", - "ITEM.TypeShipweapon": "Ship Weapon", - "ITEM.TypeSignatureability": "Signature Ability", - "ITEM.TypeSpecialization": "Specialization", - "ITEM.TypeSpecies": "Species", - "ITEM.TypeWeapon": "Weapon", + "ITEM.TypeShipattachment": "Ship Attachment", + "ITEM.TypeShipweapon": "Ship Weapon", + "ITEM.TypeSignatureability": "Signature Ability", + "ITEM.TypeSpecialization": "Specialization", + "ITEM.TypeSpecies": "Species", + "ITEM.TypeWeapon": "Weapon", "SWFFG.Name": "Name", "SWFFG.Species": "Species", @@ -78,6 +80,7 @@ "SWFFG.ItemsRarity": "Rarity", "SWFFG.ItemsQuantity": "Quantity", "SWFFG.ItemsQty": "Qty", + "SWFFG.ItemsStorage": "Storage", "SWFFG.Talents": "Talents", "SWFFG.TalentsName": "Name", "SWFFG.TalentsActivation": "Activation", @@ -137,6 +140,9 @@ "SWFFG.VehicleDefensePort": "Port", "SWFFG.VehicleDefenseStarboard": "Stbd", "SWFFG.VehicleAttachmentHardPointRequired": "HP Req", + "SWFFG.HomesteadTabDescription": "Description", + "SWFFG.HomesteadTabUpgrades": "Upgrades", + "SWFFG.HomesteadTabStorage": "Item Storage", "SWFFG.SkillsNameAstrogation": "Astrogation", "SWFFG.SkillsNameAthletics": "Athletics", "SWFFG.SkillsNameBrawl": "Brawl", diff --git a/modules/actors/actor-ffg.js b/modules/actors/actor-ffg.js index 48ba832c..ce697105 100644 --- a/modules/actors/actor-ffg.js +++ b/modules/actors/actor-ffg.js @@ -51,7 +51,7 @@ export class ActorFFG extends Actor { data.biography = PopoutEditor.renderDiceImages(data.biography, actorData); // localize characteristic names - if (actorData.type !== "vehicle") { + if (actorData.type !== "vehicle" && actorData.type !== "homestead") { for (let characteristic of Object.keys(data.characteristics)) { const strId = `SWFFG.Characteristic${this._capitalize(characteristic)}`; const localizedField = game.i18n.localize(strId); diff --git a/modules/actors/actor-sheet-ffg.js b/modules/actors/actor-sheet-ffg.js index 7f2ded75..6fe68419 100644 --- a/modules/actors/actor-sheet-ffg.js +++ b/modules/actors/actor-sheet-ffg.js @@ -103,7 +103,7 @@ export class ActorSheetFFG extends ActorSheet { default: } - if (this.actor.data.type !== "vehicle") { + if (this.actor.data.type !== "vehicle" && this.actor.data.type !== "homestead") { // Filter out skills that are not custom (manually added) or part of the current system skill list Object.keys(data.data.skills) .filter(s => !data.data.skills[s].custom && !CONFIG.FFG.skills[s]) diff --git a/modules/config/ffg-sheetdefaults.js b/modules/config/ffg-sheetdefaults.js index c33b917a..bed7fc88 100644 --- a/modules/config/ffg-sheetdefaults.js +++ b/modules/config/ffg-sheetdefaults.js @@ -2,12 +2,14 @@ export const default_width = { character: 630, minion: 595, vehicle: 595, + homestead: 595, }; export const default_height = { character: 783, minion: 644, vehicle: 824, + homestead: 783, }; export const sheet_defaults = { diff --git a/modules/helpers/actor-helpers.js b/modules/helpers/actor-helpers.js index 144b9c02..df1d10b2 100644 --- a/modules/helpers/actor-helpers.js +++ b/modules/helpers/actor-helpers.js @@ -5,145 +5,146 @@ export default class ActorHelpers { formData = expandObject(formData); const ownedItems = this.actor.items.map((item) => item.data); - if (this.object.data.type !== "vehicle") { - // Handle characteristic updates - Object.keys(CONFIG.FFG.characteristics).forEach((key) => { - let total = ModifierHelpers.getCalculateValueForAttribute(key, this.actor.data.data.attributes, ownedItems, "Characteristic"); - let x = parseInt(formData.data.characteristics[key].value, 10) - total; - let y = parseInt(formData.data.attributes[key].value, 10) + x; - if (y > 0) { - formData.data.attributes[key].value = y; - } else { - formData.data.attributes[key].value = 0; - } - }); - // Handle stat updates - Object.keys(CONFIG.FFG.character_stats).forEach((k) => { - const key = CONFIG.FFG.character_stats[k].value; - - let total = ModifierHelpers.getCalculateValueForAttribute(key, this.actor.data.data.attributes, ownedItems, "Stat"); - - let statValue = 0; - let isFormValueVisible = true; - if (key === "Soak") { - if (formData.data.stats[k]?.value) { - statValue = parseInt(formData.data.stats[k].value, 10); - // the soak value is autocalculated we need to account for Brawn - statValue = statValue - parseInt(formData.data.characteristics.Brawn.value, 10); - } else { - statValue = 0; - isFormValueVisible = false; - } - } else if (key === "Encumbrance") { - if (formData.data.stats[k]?.max) { - statValue = parseInt(formData.data.stats[k].max, 10); - // the encumbrance value is autocalculated we need to account for 5 + Brawn - statValue = statValue - parseInt(formData.data.characteristics.Brawn.value + 5, 10); + if (this.object.data.type !== "homestead") { + if (this.object.data.type !== "vehicle") { + // Handle characteristic updates + Object.keys(CONFIG.FFG.characteristics).forEach((key) => { + let total = ModifierHelpers.getCalculateValueForAttribute(key, this.actor.data.data.attributes, ownedItems, "Characteristic"); + let x = parseInt(formData.data.characteristics[key].value, 10) - total; + let y = parseInt(formData.data.attributes[key].value, 10) + x; + if (y > 0) { + formData.data.attributes[key].value = y; } else { - statValue = 0; - isFormValueVisible = false; + formData.data.attributes[key].value = 0; } - } else if (key === "Defence-Melee") { - statValue = parseInt(formData.data.stats.defence.melee, 10); - } else if (key === "Defence-Ranged") { - statValue = parseInt(formData.data.stats.defence.ranged, 10); - } else { - if (formData.data?.stats[k]?.max) { - statValue = parseInt(formData.data.stats[k].max, 10); + }); + // Handle stat updates + Object.keys(CONFIG.FFG.character_stats).forEach((k) => { + const key = CONFIG.FFG.character_stats[k].value; + + let total = ModifierHelpers.getCalculateValueForAttribute(key, this.actor.data.data.attributes, ownedItems, "Stat"); + + let statValue = 0; + let isFormValueVisible = true; + if (key === "Soak") { + if (formData.data.stats[k]?.value) { + statValue = parseInt(formData.data.stats[k].value, 10); + // the soak value is autocalculated we need to account for Brawn + statValue = statValue - parseInt(formData.data.characteristics.Brawn.value, 10); + } else { + statValue = 0; + isFormValueVisible = false; + } + } else if (key === "Encumbrance") { + if (formData.data.stats[k]?.max) { + statValue = parseInt(formData.data.stats[k].max, 10); + // the encumbrance value is autocalculated we need to account for 5 + Brawn + statValue = statValue - parseInt(formData.data.characteristics.Brawn.value + 5, 10); + } else { + statValue = 0; + isFormValueVisible = false; + } + } else if (key === "Defence-Melee") { + statValue = parseInt(formData.data.stats.defence.melee, 10); + } else if (key === "Defence-Ranged") { + statValue = parseInt(formData.data.stats.defence.ranged, 10); } else { - statValue = 0; - isFormValueVisible = false; + if (formData.data?.stats[k]?.max) { + statValue = parseInt(formData.data.stats[k].max, 10); + } else { + statValue = 0; + isFormValueVisible = false; + } } - } - let x = statValue - (isFormValueVisible ? total : 0); + let x = statValue - (isFormValueVisible ? total : 0); - let y = parseInt(formData.data.attributes[key].value, 10) + x; - if (key === "Soak") { - const autoSoakCalculation = (typeof this.actor.data?.flags?.config?.enableAutoSoakCalculation === "undefined" && game.settings.get("starwarsffg", "enableSoakCalc")) || this.actor.data.flags.starwarsffg?.config.enableAutoSoakCalculation; + let y = parseInt(formData.data.attributes[key].value, 10) + x; + if (key === "Soak") { + const autoSoakCalculation = (typeof this.actor.data?.flags?.config?.enableAutoSoakCalculation === "undefined" && game.settings.get("starwarsffg", "enableSoakCalc")) || this.actor.data.flags.starwarsffg?.config.enableAutoSoakCalculation; - if (autoSoakCalculation) { - y = 0; + if (autoSoakCalculation) { + y = 0; + } } - } - - if (y > 0) { - formData.data.attributes[key].value = y; - } else { - formData.data.attributes[key].value = 0; - } - }); - // Handle skill rank updates - Object.keys(this.object.data.data.skills).forEach((key) => { - let total = ModifierHelpers.getCalculateValueForAttribute(key, this.actor.data.data.attributes, ownedItems, "Skill Rank"); - let x = parseInt(formData.data.skills[key]?.rank, 10) - total; - let y = parseInt(formData.data.attributes[key]?.value, 10) + x; - if (y > 0) { - formData.data.attributes[key].value = y; - } else { - formData.data.attributes[key].value = 0; - } - }); - - // Handle credits - if (formData.data.stats?.credits?.value) { - const rawCredits = formData.data.stats?.credits.value - ?.toString() - .match(/^(?!.*\.).*|.*\./)[0] - .replace(/[^0-9]+/g, ""); - formData.data.stats.credits.value = parseInt(rawCredits, 10); - } - } else { - // Handle stat updates - Object.keys(CONFIG.FFG.vehicle_stats).forEach((k) => { - const key = CONFIG.FFG.vehicle_stats[k].value; - - let total = ModifierHelpers.getCalculateValueForAttribute(key, this.actor.data.data.attributes, ownedItems, "Stat"); - - let statValue = 0; - let isFormValueVisible = true; - if (k === "shields") { - } else if (formData.data?.stats[k]?.max) { - statValue = parseInt(formData.data.stats[k].max, 10); - } else { - if (formData.data.stats[k]?.value) { - statValue = parseInt(formData.data.stats[k].value, 10); + if (y > 0) { + formData.data.attributes[key].value = y; + } else { + formData.data.attributes[key].value = 0; + } + }); + // Handle skill rank updates + Object.keys(this.object.data.data.skills).forEach((key) => { + let total = ModifierHelpers.getCalculateValueForAttribute(key, this.actor.data.data.attributes, ownedItems, "Skill Rank"); + let x = parseInt(formData.data.skills[key]?.rank, 10) - total; + let y = parseInt(formData.data.attributes[key]?.value, 10) + x; + if (y > 0) { + formData.data.attributes[key].value = y; } else { - statValue = 0; - isFormValueVisible = false; + formData.data.attributes[key].value = 0; } + }); + + // Handle credits + + if (formData.data.stats?.credits?.value) { + const rawCredits = formData.data.stats?.credits.value + ?.toString() + .match(/^(?!.*\.).*|.*\./)[0] + .replace(/[^0-9]+/g, ""); + formData.data.stats.credits.value = parseInt(rawCredits, 10); } + } else { + // Handle stat updates + Object.keys(CONFIG.FFG.vehicle_stats).forEach((k) => { + const key = CONFIG.FFG.vehicle_stats[k].value; - if (k === "shields") { - let newAttr = formData.data.attributes[key].value.split(","); - ["fore", "port", "starboard", "aft"].forEach((position, index) => { - let shieldValue = parseInt(formData.data.stats[k][position], 10); - let x = shieldValue - (total[index] ? total[index] : 0); - let y = parseInt(newAttr[index], 10) + x; - if (y > 0) { - newAttr[index] = y; + let total = ModifierHelpers.getCalculateValueForAttribute(key, this.actor.data.data.attributes, ownedItems, "Stat"); + + let statValue = 0; + let isFormValueVisible = true; + if (k === "shields") { + } else if (formData.data?.stats[k]?.max) { + statValue = parseInt(formData.data.stats[k].max, 10); + } else { + if (formData.data.stats[k]?.value) { + statValue = parseInt(formData.data.stats[k].value, 10); } else { - newAttr[index] = 0; + statValue = 0; + isFormValueVisible = false; } - }); - formData.data.attributes[key].value = newAttr; - } else { - let allowNegative = false; - if (statValue < 0 && k === "handling") { - allowNegative = true; } - let x = statValue - (isFormValueVisible ? total : 0); - let y = parseInt(formData.data.attributes[key].value, 10) + x; - if (y > 0 || allowNegative) { - formData.data.attributes[key].value = y; + + if (k === "shields") { + let newAttr = formData.data.attributes[key].value.split(","); + ["fore", "port", "starboard", "aft"].forEach((position, index) => { + let shieldValue = parseInt(formData.data.stats[k][position], 10); + let x = shieldValue - (total[index] ? total[index] : 0); + let y = parseInt(newAttr[index], 10) + x; + if (y > 0) { + newAttr[index] = y; + } else { + newAttr[index] = 0; + } + }); + formData.data.attributes[key].value = newAttr; } else { - formData.data.attributes[key].value = 0; + let allowNegative = false; + if (statValue < 0 && k === "handling") { + allowNegative = true; + } + let x = statValue - (isFormValueVisible ? total : 0); + let y = parseInt(formData.data.attributes[key].value, 10) + x; + if (y > 0 || allowNegative) { + formData.data.attributes[key].value = y; + } else { + formData.data.attributes[key].value = 0; + } } - } - }); + }); + } } - // Handle the free-form attributes list const formAttrs = expandObject(formData)?.data?.attributes || {}; const attributes = Object.values(formAttrs).reduce((obj, v) => { diff --git a/modules/helpers/partial-templates.js b/modules/helpers/partial-templates.js index bc6072e1..138fc971 100644 --- a/modules/helpers/partial-templates.js +++ b/modules/helpers/partial-templates.js @@ -1,6 +1,21 @@ export default class TemplateHelpers { static async preload() { - const templatePaths = ["systems/starwarsffg/templates/parts/shared/ffg-modifiers.html", "systems/starwarsffg/templates/parts/actor/ffg-skills.html", "systems/starwarsffg/templates/parts/actor/ffg-weapon-armor-gear.html", "systems/starwarsffg/templates/parts/actor/ffg-talents.html", "systems/starwarsffg/templates/parts/actor/ffg-forcepowers.html", "systems/starwarsffg/templates/parts/actor/ffg-criticalinjury.html", "systems/starwarsffg/templates/parts/shared/ffg-block.html", "systems/starwarsffg/templates/parts/actor/ffg-signatureability.html", "systems/starwarsffg/templates/chat/roll-forcepower-card.html", "systems/starwarsffg/templates/chat/roll-weapon-card.html", "systems/starwarsffg/templates/parts/shared/ffg-tabs.html", "systems/starwarsffg/templates/parts/actor/ffg-healingitem.html"]; + const templatePaths = [ + "systems/starwarsffg/templates/parts/shared/ffg-modifiers.html", + "systems/starwarsffg/templates/parts/actor/ffg-skills.html", + "systems/starwarsffg/templates/parts/actor/ffg-weapon-armor-gear.html", + "systems/starwarsffg/templates/parts/actor/ffg-homestead-upgrades.html", + "systems/starwarsffg/templates/parts/actor/ffg-homestead-storage.html", + "systems/starwarsffg/templates/parts/actor/ffg-talents.html", + "systems/starwarsffg/templates/parts/actor/ffg-forcepowers.html", + "systems/starwarsffg/templates/parts/actor/ffg-criticalinjury.html", + "systems/starwarsffg/templates/parts/shared/ffg-block.html", + "systems/starwarsffg/templates/parts/actor/ffg-signatureability.html", + "systems/starwarsffg/templates/chat/roll-forcepower-card.html", + "systems/starwarsffg/templates/chat/roll-weapon-card.html", + "systems/starwarsffg/templates/parts/shared/ffg-tabs.html", + "systems/starwarsffg/templates/parts/actor/ffg-healingitem.html" + ]; return loadTemplates(templatePaths); } diff --git a/modules/items/item-sheet-ffg.js b/modules/items/item-sheet-ffg.js index 513ff69e..754c4561 100644 --- a/modules/items/item-sheet-ffg.js +++ b/modules/items/item-sheet-ffg.js @@ -82,6 +82,7 @@ export class ItemSheetFFG extends ItemSheet { case "armour": case "gear": case "shipattachment": + case "homesteadupgrade": this.position.width = 385; this.position.height = 615; break; diff --git a/modules/swffg-main.js b/modules/swffg-main.js index ca003ed4..ee6a4aff 100644 --- a/modules/swffg-main.js +++ b/modules/swffg-main.js @@ -252,7 +252,7 @@ Hooks.once("init", async function () { } catch (err) {} Hooks.on("createActor", (actor) => { - if (actor.type !== "vehicle") { + if (actor.type !== "vehicle" && actor.type !== "homestead") { if (CONFIG.FFG?.alternateskilllists?.length) { let skilllist = game.settings.get("starwarsffg", "skilltheme"); try { diff --git a/scss/components/_homesteadsheet.scss b/scss/components/_homesteadsheet.scss new file mode 100644 index 00000000..c49ea523 --- /dev/null +++ b/scss/components/_homesteadsheet.scss @@ -0,0 +1,26 @@ +&.sheet { + &.actor { + .homestead { + + .sheet-header .homestead-img { + height: 20vmin; + } + + .biography-values { + border: 0; + } + + .sheet-body { + height: calc(100% - 28.875rem); + overflow:auto; + + .biography { + height: calc(100% - 1.5rem); + overflow:auto; + } + } + + } + } +} + diff --git a/scss/starwarsffg.scss b/scss/starwarsffg.scss index e9988e8b..53b7298e 100644 --- a/scss/starwarsffg.scss +++ b/scss/starwarsffg.scss @@ -32,6 +32,7 @@ @import "components/talentsheet"; @import "components/minionsheet"; @import "components/vehiclesheet"; + @import "components/homesteadsheet"; @import "components/itemvehicleweaponsheet"; @import "components/itemvehicleattachmentsheet"; @import "components/groupmanager"; diff --git a/styles/starwarsffg.css b/styles/starwarsffg.css index 162b8e07..c84673ed 100644 --- a/styles/starwarsffg.css +++ b/styles/starwarsffg.css @@ -2043,6 +2043,20 @@ img { .starwarsffg.sheet.actor .vehicle .injuries .resource.full { width: 100%; } +.starwarsffg.sheet.actor .homestead .sheet-header .homestead-img { + height: 20vmin; +} +.starwarsffg.sheet.actor .homestead .biography-values { + border: 0; +} +.starwarsffg.sheet.actor .homestead .sheet-body { + height: calc(100% - 28.875rem); + overflow: auto; +} +.starwarsffg.sheet.actor .homestead .sheet-body .biography { + height: calc(100% - 1.5rem); + overflow: auto; +} .starwarsffg .item-sheet-vehicle-weapon .charname input { color: #1f1c37; } diff --git a/template.json b/template.json index 95a84610..5289e9ea 100644 --- a/template.json +++ b/template.json @@ -1,6 +1,6 @@ { "Actor": { - "types": ["character", "minion", "vehicle"], + "types": ["character", "minion", "vehicle", "homestead"], "templates": { "biography": { "biography": "" @@ -527,11 +527,26 @@ "label": "SWFFG.Consumables" } } + }, + "homestead": { + "templates": ["biography", "attributes"], + "cost": { + "value": 0, + "type": "Number", + "label": "Cost", + "adjusted": 0 + }, + "consumables" : { + "value" : 1, + "duration" : "months", + "type": "Number", + "label": "SWFFG.Consumables" + } } }, "Item": { "types": [ - "armour", "career", "criticaldamage", "criticalinjury", "forcepower", "gear", "itemattachment", "itemmodifier", "talent", "shipattachment", "shipweapon", "signatureability", "specialization", "species", "weapon"], + "armour", "career", "criticaldamage", "criticalinjury", "forcepower", "gear", "itemattachment", "itemmodifier", "talent", "shipattachment", "shipweapon", "homesteadupgrade", "signatureability", "specialization", "species", "weapon"], "templates": { "core": { "description": "", diff --git a/templates/actors/ffg-homestead-sheet.html b/templates/actors/ffg-homestead-sheet.html new file mode 100644 index 00000000..1bac63ea --- /dev/null +++ b/templates/actors/ffg-homestead-sheet.html @@ -0,0 +1,42 @@ +
diff --git a/templates/items/ffg-homesteadupgrade-sheet.html b/templates/items/ffg-homesteadupgrade-sheet.html new file mode 100644 index 00000000..b9abf11c --- /dev/null +++ b/templates/items/ffg-homesteadupgrade-sheet.html @@ -0,0 +1,32 @@ + diff --git a/templates/parts/actor/ffg-homestead-storage.html b/templates/parts/actor/ffg-homestead-storage.html new file mode 100644 index 00000000..e5f21675 --- /dev/null +++ b/templates/parts/actor/ffg-homestead-storage.html @@ -0,0 +1,32 @@ +{{!-- Storage --}} +