Skip to content

Commit

Permalink
Add Homestead sheets (#1064)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
BluSunrize authored Jun 13, 2022
1 parent 5e26685 commit 1acd7a3
Show file tree
Hide file tree
Showing 16 changed files with 345 additions and 132 deletions.
18 changes: 12 additions & 6 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@
"ACTOR.TypeCharacter": "Character",
"ACTOR.TypeMinion": "Minion",
"ACTOR.TypeVehicle": "Vehicle",
"ACTOR.TypeHomestead": "Homestead",

"ITEM.TypeArmour": "Armour",
"ITEM.TypeCareer": "Career",
"ITEM.TypeCriticaldamage": "Critical Damage",
"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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion modules/actors/actor-ffg.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion modules/actors/actor-sheet-ffg.js
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
2 changes: 2 additions & 0 deletions modules/config/ffg-sheetdefaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
241 changes: 121 additions & 120 deletions modules/helpers/actor-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
17 changes: 16 additions & 1 deletion modules/helpers/partial-templates.js
Original file line number Diff line number Diff line change
@@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions modules/items/item-sheet-ffg.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion modules/swffg-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 1acd7a3

Please sign in to comment.